diff --git a/codebase/@features/provider-website/frontend-public/index.html b/codebase/@features/provider-website/frontend-public/index.html
index f3950aed..e7968e6c 100644
--- a/codebase/@features/provider-website/frontend-public/index.html
+++ b/codebase/@features/provider-website/frontend-public/index.html
@@ -11,13 +11,13 @@
-
Quinn — San Francisco Escort
-
+ Quinn — Cali Bimbo Trans Escort & Gamedev
+
-
-
+
+
diff --git a/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.test.tsx b/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.test.tsx
index 967936ec..118b8b2a 100644
--- a/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.test.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.test.tsx
@@ -118,15 +118,18 @@ describe('useMeta', () => {
expect(metaContent('meta[name="twitter:image"]')).toBe(custom);
});
- it('restores homepage defaults on unmount', () => {
+ it('does not reset tags on unmount (the next route owns the head)', () => {
+ // The previous implementation reset to homepage defaults on unmount, which
+ // raced the incoming route's own effect. Each navigation now fully owns the
+ // tag set, so unmount is a no-op — the tags persist until the next resolve.
const { unmount } = renderHook(
() => useMeta({ title: 'Gallery — Quinn', description: 'Photos and looks.' }),
{ wrapper: makeWrapper('/gallery') },
);
unmount();
- expect(document.title).toBe('Quinn — San Francisco Escort');
- expect(linkHref('canonical')).toBe(SITE_URL);
- expect(metaContent('meta[property="og:url"]')).toBe(SITE_URL);
+ expect(document.title).toBe('Gallery — Quinn');
+ expect(linkHref('canonical')).toBe(`${SITE_URL}/gallery`);
+ expect(metaContent('meta[property="og:url"]')).toBe(`${SITE_URL}/gallery`);
});
it('homepage canonical is the bare site URL', () => {
diff --git a/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.ts b/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.ts
index 8b604908..7dea341f 100644
--- a/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.ts
+++ b/codebase/@features/provider-website/frontend-public/src/hooks/useMeta.ts
@@ -1,23 +1,28 @@
/**
- * useMeta — Imperatively upserts document.head meta/link tags per route.
+ * useMeta — imperatively syncs document.head meta/link tags to the current route.
*
- * Covers: title, description, og:*, twitter:*, canonical.
- * No external library — plain useEffect with attribute-selector upsert.
+ * Thin React layer over the pure resolver in src/meta/resolveMeta.ts:
+ * 1. Match the live pathname against the route registry → its RouteMeta.
+ * 2. Resolve admin-editable overrides (siteText {namespace}.meta_title /
+ * .meta_description) on top of the registry fallbacks.
+ * 3. Layer any caller override (entity pages pass titleFull / description).
+ * 4. Run resolveMeta() to get the final title/description/og/canonical, with
+ * tour-awareness applied on TOUR_AWARE_PATHS.
+ * 5. Upsert the tags. The next route's resolve overwrites them — no unmount
+ * reset (the previous reset-to-homepage cleanup caused a flicker race).
+ *
+ * Usage:
+ * useMeta() // static page — all meta from registry
+ * useMeta({ titleFull, description }) // entity page — supplies its own title
*/
import { useEffect } from 'react';
import { useLocation } from '@lilith/ui-router';
import { useProviderData } from './useProviderData';
import { useTourStatus } from './useTourStatus';
-
-const SITE_URL = 'https://transquinnftw.com';
-const DEFAULT_OG_IMAGE = 'https://transquinnftw.com/og-image.jpg';
-
-interface MetaOptions {
- title: string;
- description: string;
- ogImage?: string;
-}
+import { useSiteText } from './useSiteText';
+import { metaForPath } from '@/meta/routeMetaTable';
+import { resolveMeta, type MetaInput, type MetaDescriptor } from '@/meta/resolveMeta';
function upsertMeta(attrs: Record, content: string): void {
const selector = Object.entries(attrs)
@@ -26,9 +31,7 @@ function upsertMeta(attrs: Record, content: string): void {
let el = document.head.querySelector(`meta${selector}`);
if (!el) {
el = document.createElement('meta');
- for (const [k, v] of Object.entries(attrs)) {
- el.setAttribute(k, v);
- }
+ for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
document.head.appendChild(el);
}
el.setAttribute('content', content);
@@ -44,51 +47,78 @@ function upsertLink(rel: string, href: string): void {
el.setAttribute('href', href);
}
-export function useMeta({ title, description, ogImage = DEFAULT_OG_IMAGE }: MetaOptions): void {
+function applyToDom(d: MetaDescriptor): void {
+ document.title = d.title;
+ upsertMeta({ name: 'description' }, d.description);
+ upsertMeta({ name: 'robots' }, d.noindex ? 'noindex, noai, noimageai' : 'index, follow, noai, noimageai');
+
+ upsertMeta({ property: 'og:type' }, d.ogType);
+ upsertMeta({ property: 'og:title' }, d.title);
+ upsertMeta({ property: 'og:description' }, d.description);
+ upsertMeta({ property: 'og:url' }, d.canonical);
+ upsertMeta({ property: 'og:image' }, d.ogImage);
+
+ upsertMeta({ name: 'twitter:card' }, 'summary_large_image');
+ upsertMeta({ name: 'twitter:title' }, d.title);
+ upsertMeta({ name: 'twitter:description' }, d.description);
+ upsertMeta({ name: 'twitter:image' }, d.ogImage);
+
+ upsertLink('canonical', d.canonical);
+}
+
+export function useMeta(override: MetaInput = {}): void {
const location = useLocation();
const data = useProviderData();
const { activeStop } = useTourStatus();
- // Derive location-aware defaults from live data
- const locationLabel = activeStop
- ? `${activeStop.city}${activeStop.state ? `, ${activeStop.state}` : ''}`
- : data.identity.location || 'San Francisco';
- const defaultTitle = `${data.identity.name || 'Quinn'} — ${locationLabel} Escort`;
- const defaultDescription = activeStop
- ? `Currently in ${locationLabel}. Upscale trans escort — bookings by text only.`
- : `Upscale trans escort based in ${locationLabel}. Touring nationwide. Bookings by text only.`;
+ const matched = metaForPath(location.pathname);
+ const ns = matched?.meta?.namespace ?? '__meta';
+
+ // Admin-editable overrides. Hooks run unconditionally with a stable count;
+ // the namespace/key are args, so varying them per route is fine. Empty
+ // fallbacks resolve to '' (miss) → the resolver applies its base defaults.
+ const adminTitle = useSiteText(ns, 'meta_title', matched?.meta?.title ?? '');
+ const adminDescription = useSiteText(ns, 'meta_description', matched?.meta?.description ?? '');
+
+ // Flatten to primitives so the effect re-runs only on a real change.
+ const pathname = location.pathname;
+ const routePattern = matched?.routePattern;
+ const providerName = data.identity.name || 'Quinn';
+ const homeLocation = data.identity.location || 'San Francisco';
+ const stopCity = activeStop?.city ?? '';
+ const stopState = activeStop?.state ?? '';
+
+ const title = override.title ?? adminTitle;
+ const titleFull = override.titleFull;
+ const description = override.description ?? adminDescription;
+ const ogImage = override.ogImage ?? matched?.meta?.ogImage;
+ const ogType = override.ogType;
+ const noindex = override.noindex ?? matched?.meta?.noindex;
useEffect(() => {
- const canonicalUrl = `${SITE_URL}${location.pathname}`;
-
- document.title = title;
-
- upsertMeta({ name: 'description' }, description);
-
- upsertMeta({ property: 'og:title' }, title);
- upsertMeta({ property: 'og:description' }, description);
- upsertMeta({ property: 'og:url' }, canonicalUrl);
- upsertMeta({ property: 'og:image' }, ogImage);
-
- upsertMeta({ name: 'twitter:card' }, 'summary_large_image');
- upsertMeta({ name: 'twitter:title' }, title);
- upsertMeta({ name: 'twitter:description' }, description);
- upsertMeta({ name: 'twitter:image' }, ogImage);
-
- upsertLink('canonical', canonicalUrl);
-
- return () => {
- // Restore to location-aware homepage defaults on unmount
- document.title = defaultTitle;
- upsertMeta({ name: 'description' }, defaultDescription);
- upsertMeta({ property: 'og:title' }, defaultTitle);
- upsertMeta({ property: 'og:description' }, defaultDescription);
- upsertMeta({ property: 'og:url' }, SITE_URL);
- upsertMeta({ property: 'og:image' }, DEFAULT_OG_IMAGE);
- upsertMeta({ name: 'twitter:title' }, defaultTitle);
- upsertMeta({ name: 'twitter:description' }, defaultDescription);
- upsertMeta({ name: 'twitter:image' }, DEFAULT_OG_IMAGE);
- upsertLink('canonical', SITE_URL);
- };
- }, [title, description, ogImage, location.pathname, defaultTitle, defaultDescription]);
+ const descriptor = resolveMeta(
+ { title, titleFull, description, ogImage, ogType, noindex },
+ {
+ pathname,
+ routePattern,
+ providerName,
+ homeLocation,
+ activeStop: stopCity ? { city: stopCity, state: stopState || undefined } : null,
+ },
+ );
+ applyToDom(descriptor);
+ }, [
+ title,
+ titleFull,
+ description,
+ ogImage,
+ ogType,
+ noindex,
+ pathname,
+ routePattern,
+ providerName,
+ homeLocation,
+ stopCity,
+ stopState,
+ ]);
}
diff --git a/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.test.ts b/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.test.ts
new file mode 100644
index 00000000..ed49a96d
--- /dev/null
+++ b/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.test.ts
@@ -0,0 +1,92 @@
+import { describe, it, expect } from 'vitest';
+import { resolveMeta, formatTitle, DEFAULT_OG_IMAGE, SITE_URL, type MetaContext } from './resolveMeta';
+
+const baseCtx: MetaContext = {
+ pathname: '/gallery',
+ routePattern: '/gallery',
+ providerName: 'Quinn',
+ homeLocation: 'San Francisco',
+ activeStop: null,
+};
+
+describe('formatTitle', () => {
+ it('appends the provider suffix to a bare page label', () => {
+ expect(formatTitle('Gallery', 'Quinn')).toBe('Gallery — Quinn');
+ });
+
+ it('is idempotent when the label already contains the name', () => {
+ expect(formatTitle('Gallery — Quinn', 'Quinn')).toBe('Gallery — Quinn');
+ expect(formatTitle("Quinn's Shop", 'Quinn')).toBe("Quinn's Shop");
+ });
+
+ it('returns empty for an empty label', () => {
+ expect(formatTitle('', 'Quinn')).toBe('');
+ expect(formatTitle(undefined, 'Quinn')).toBe('');
+ });
+});
+
+describe('resolveMeta', () => {
+ it('templates a page-label title', () => {
+ const d = resolveMeta({ title: 'Gallery' }, baseCtx);
+ expect(d.title).toBe('Gallery — Quinn');
+ });
+
+ it('uses titleFull verbatim, bypassing the template', () => {
+ const d = resolveMeta({ titleFull: 'Escort in Chicago | Quinn' }, baseCtx);
+ expect(d.title).toBe('Escort in Chicago | Quinn');
+ });
+
+ it('falls back to the brand base default when no title is given', () => {
+ const d = resolveMeta({}, baseCtx);
+ expect(d.title).toBe('Quinn — Cali Bimbo Trans Escort & Gamedev');
+ expect(d.description).toContain('Cali bimbo, trans escort, and gamedev');
+ expect(d.description).toContain('San Francisco');
+ });
+
+ it('derives a tour-aware title on a tour-aware path with an active stop', () => {
+ const d = resolveMeta(
+ {},
+ { ...baseCtx, pathname: '/', routePattern: '/', activeStop: { city: 'Chicago', state: 'IL' } },
+ );
+ expect(d.title).toBe('Quinn — Now in Chicago, IL');
+ expect(d.description).toContain('Currently in Chicago, IL');
+ });
+
+ it('ignores tour state on non-tour-aware paths', () => {
+ const d = resolveMeta(
+ { title: 'Gallery' },
+ { ...baseCtx, activeStop: { city: 'Chicago', state: 'IL' } },
+ );
+ expect(d.title).toBe('Gallery — Quinn');
+ });
+
+ it('prepends the live location to a tour-aware page that supplies its own description', () => {
+ const d = resolveMeta(
+ { title: 'Tour Schedule', description: 'Upcoming cities and dates.' },
+ { ...baseCtx, pathname: '/tour', routePattern: '/tour', activeStop: { city: 'Miami' } },
+ );
+ expect(d.title).toBe('Quinn — Now in Miami');
+ expect(d.description).toBe('Currently in Miami. Upcoming cities and dates.');
+ });
+
+ it('builds the canonical from the live pathname', () => {
+ const d = resolveMeta({ title: 'Rates' }, { ...baseCtx, pathname: '/rates' });
+ expect(d.canonical).toBe(`${SITE_URL}/rates`);
+ });
+
+ it('defaults og:image and og:type', () => {
+ const d = resolveMeta({ title: 'Rates' }, baseCtx);
+ expect(d.ogImage).toBe(DEFAULT_OG_IMAGE);
+ expect(d.ogType).toBe('website');
+ });
+
+ it('honors per-page og overrides and noindex', () => {
+ const d = resolveMeta(
+ { title: 'X', ogImage: `${SITE_URL}/x.jpg`, ogType: 'article', noindex: true },
+ baseCtx,
+ );
+ expect(d.ogImage).toBe(`${SITE_URL}/x.jpg`);
+ expect(d.ogType).toBe('article');
+ expect(d.noindex).toBe(true);
+ });
+});
diff --git a/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.ts b/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.ts
index 1a3916b3..4ae599d3 100644
--- a/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.ts
+++ b/codebase/@features/provider-website/frontend-public/src/meta/resolveMeta.ts
@@ -29,6 +29,17 @@
export const SITE_URL = 'https://transquinnftw.com';
export const DEFAULT_OG_IMAGE = `${SITE_URL}/og-image.jpg`;
+/**
+ * Brand direction (2026): Cali bimbo · gamedev · trans escort. This is the
+ * positioning the home/base title, the og fallback, the static index.html, and
+ * the About page all align on. Keep these strings in sync with index.html's
+ * hardcoded /og block (the no-JS crawler fallback).
+ */
+export const BRAND_DESCRIPTOR = 'Cali Bimbo Trans Escort & Gamedev';
+function baseDescription(homeLocation: string): string {
+ return `Cali bimbo, trans escort, and gamedev — based in ${homeLocation}, touring nationwide. Bookings by text only.`;
+}
+
/** Paths whose title/description reflect live tour state. Key pages only —
* everything else gets a stable, cacheable title. Matched against the route
* PATTERN (registry path), not the raw pathname, so it survives params. */
@@ -99,7 +110,7 @@ function resolveTitle(input: MetaInput, ctx: MetaContext): string {
const templated = formatTitle(input.title, ctx.providerName);
if (templated) return templated;
- return `${ctx.providerName} — ${ctx.homeLocation} Escort`;
+ return `${ctx.providerName} — ${BRAND_DESCRIPTOR}`;
}
function resolveDescription(input: MetaInput, ctx: MetaContext): string {
@@ -115,9 +126,9 @@ function resolveDescription(input: MetaInput, ctx: MetaContext): string {
}
if (isTourAware(ctx) && ctx.activeStop) {
- return `Currently in ${stopLabel(ctx.activeStop)}. Upscale trans escort — bookings by text only.`;
+ return `Currently in ${stopLabel(ctx.activeStop)}. Cali bimbo trans escort & gamedev — bookings by text only.`;
}
- return `Upscale trans escort based in ${ctx.homeLocation}. Touring nationwide. Bookings by text only.`;
+ return baseDescription(ctx.homeLocation);
}
export function resolveMeta(input: MetaInput, ctx: MetaContext): MetaDescriptor {
diff --git a/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.test.ts b/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.test.ts
new file mode 100644
index 00000000..376a240e
--- /dev/null
+++ b/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.test.ts
@@ -0,0 +1,44 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { describe, it, expect } from 'vitest';
+import { ROUTE_META, metaForPath } from './routeMetaTable';
+
+// The meta table mirrors route paths from src/routes/registry.tsx. Importing the
+// registry here would eagerly pull the entire page-element graph (HomePage → the
+// sound engine, etc.), so instead we read its SOURCE and extract `path:` literals
+// to guard against drift — every meta key must reference a real route.
+function registryPaths(): Set {
+ // vitest runs with cwd at the package root (frontend-public).
+ const src = readFileSync(resolve(process.cwd(), 'src/routes/registry.tsx'), 'utf8');
+ const paths = new Set();
+ for (const m of src.matchAll(/path:\s*'([^']+)'/g)) paths.add(m[1]);
+ return paths;
+}
+
+describe('routeMetaTable drift guard', () => {
+ it('every meta key is a registered route path', () => {
+ const known = registryPaths();
+ const orphans = Object.keys(ROUTE_META).filter(p => !known.has(p));
+ expect(orphans).toEqual([]);
+ });
+});
+
+describe('metaForPath', () => {
+ it('resolves an exact static route', () => {
+ expect(metaForPath('/gallery').meta).toEqual(ROUTE_META['/gallery']);
+ });
+
+ it('normalizes a trailing slash', () => {
+ expect(metaForPath('/gallery/').meta).toEqual(ROUTE_META['/gallery']);
+ });
+
+ it('keeps the root path intact', () => {
+ expect(metaForPath('/').meta).toEqual(ROUTE_META['/']);
+ expect(metaForPath('/').routePattern).toBe('/');
+ });
+
+ it('returns undefined meta for entity/param routes (they override)', () => {
+ expect(metaForPath('/blog/my-post').meta).toBeUndefined();
+ expect(metaForPath('/_/escorts/in-chicago').meta).toBeUndefined();
+ });
+});
diff --git a/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.ts b/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.ts
new file mode 100644
index 00000000..c80daecd
--- /dev/null
+++ b/codebase/@features/provider-website/frontend-public/src/meta/routeMetaTable.ts
@@ -0,0 +1,80 @@
+/**
+ * routeMetaTable — the single source of truth for per-route SEO meta.
+ *
+ * Deliberately framework-free (no JSX, no React, no page imports) so it can be
+ * consumed by:
+ * - the client `useMeta` hook (today), without dragging the route element
+ * graph (HomePage, sound engine, …) into the meta path, and
+ * - a phase-2 edge / prerender injector that string-replaces index.html's
+ * /og block for non-JS crawlers + link unfurlers.
+ *
+ * Keyed by EXACT pathname. Param/entity routes (/blog/:slug, /shop/:slug,
+ * /_/escorts/:segment, …) are absent on purpose — those pages derive their
+ * title from loaded data and pass it to useMeta() as an override, which the
+ * resolver layers on top of the base defaults.
+ *
+ * Patterns must mirror the route paths in src/routes/registry.tsx. The registry
+ * owns ROUTING (elements, nav, footer); this table owns SEO META. Keep the two
+ * in sync when adding/removing a static route.
+ *
+ * `title` / `description` are pre-template fallbacks (page labels, not full
+ * titles — the "— Quinn" suffix is applied centrally by resolveMeta). Admin
+ * edits override them via siteText {namespace}.meta_title / .meta_description.
+ */
+
+export type RouteMeta = {
+ /** siteText namespace holding admin-editable meta_title / meta_description. */
+ namespace?: string;
+ /** Page label / title fallback, pre-template (e.g. "Gallery"). */
+ title?: string;
+ /** Description fallback. */
+ description?: string;
+ /** Per-route og:image override (absolute URL). */
+ ogImage?: string;
+ /** Emit robots noindex for this route. */
+ noindex?: boolean;
+};
+
+/** Exact-path → meta. See TOUR_AWARE_PATHS in resolveMeta for the routes whose
+ * title/description reflect live tour state ('/' and '/tour'). */
+export const ROUTE_META: Readonly> = {
+ // Tour-aware — empty fallbacks resolve to "Quinn — {homeLocation} Escort".
+ '/': { namespace: 'home' },
+ '/tour': { namespace: 'tour', title: 'Tour Schedule', description: 'Upcoming cities and dates.' },
+
+ '/about': { namespace: 'about', title: 'About', description: 'Meet Quinn — Cali bimbo, trans escort, and gamedev. Her story, her vibe, and what to expect.' },
+ '/gallery': { namespace: 'gallery', title: 'Gallery', description: 'Photos and looks.' },
+ '/rates': { namespace: 'rates', title: 'Rates', description: 'Service menu and rates.' },
+ '/destinations': {
+ namespace: 'destinations',
+ title: 'Destinations',
+ description: 'Quinn is an upscale trans escort available worldwide via Fly Me To You. Browse cities and book your destination.',
+ },
+ '/booking': { namespace: 'booking', title: 'Booking', description: 'Reserve a date with Quinn.' },
+ '/fmty': { namespace: 'fmty', title: 'Fly Me To You', description: 'Commission a private visit. You arrange the travel, Quinn arrives.' },
+ '/etiquette': { namespace: 'etiquette', title: 'Etiquette', description: 'Etiquette guidelines for booking with Quinn.' },
+ '/shop': { namespace: 'shop', title: "Quinn's Shop", description: 'Browse pre-loved clothing, accessories, and more.' },
+ '/links': { namespace: 'links', title: 'Links', description: 'All links.' },
+ '/get-in-touch': { namespace: 'contact', title: 'Contact', description: 'Get in touch.' },
+ '/contact': { namespace: 'contact', title: 'Contact', description: 'Get in touch.' },
+ '/banners': { title: 'Verified Profiles', description: 'Quinn is a verified independent escort on trusted platforms. View her verified profiles.' },
+ '/drops': { namespace: 'drops', title: 'Drops', description: "Exclusive content drops — shop Quinn's latest releases on OnlyFans, Fansly, and more." },
+ '/specialties': {
+ namespace: 'specialties',
+ title: 'Specialties',
+ description: "Browse Quinn's full menu of services — GFE, overnight sessions, kink-friendly experiences, and more. Upscale trans escort in San Francisco.",
+ },
+ '/duos': { title: 'Duo Sessions', description: 'Duo sessions with Quinn — two-person experiences, carefully arranged. Upscale trans escort in San Francisco, touring nationwide.' },
+ '/my-schedule': { title: 'My Schedule', description: "See upcoming tour stops and register your interest in booking during Quinn's next visit to your city." },
+ '/blog': { title: 'Writing', description: 'Notes, thoughts, and updates from Quinn.' },
+};
+
+export type MatchedRouteMeta = { routePattern: string; meta?: RouteMeta };
+
+/** Resolve the meta for a live pathname. Trailing slashes are normalized.
+ * Entity/param routes return undefined meta (they rely on useMeta overrides);
+ * routePattern echoes the normalized pathname for the resolver's tour check. */
+export function metaForPath(pathname: string): MatchedRouteMeta {
+ const normalized = pathname.length > 1 ? pathname.replace(/\/$/, '') : pathname;
+ return { routePattern: normalized, meta: ROUTE_META[normalized] };
+}
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/AboutPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/AboutPage.tsx
index ea285c8c..2569cd13 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/AboutPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/AboutPage.tsx
@@ -418,9 +418,7 @@ function TouringStatus(): ReactNode {
// ── Page ──────────────────────────────────────────────────────────────────────
export default function AboutPage(): ReactNode {
- const metaTitle = useSiteText('about', 'meta_title', 'About Quinn');
- const metaDescription = useSiteText('about', 'meta_description', 'Background, vibe, and what to expect.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const data = useProviderData();
const { physical, identity, about } = data;
const featuredPhoto = data.gallery.find((img) => img.featured);
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/BannersPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/BannersPage.tsx
index 3f693061..9c3e7281 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/BannersPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/BannersPage.tsx
@@ -177,10 +177,7 @@ function BannerItem({ profile }: { profile: VerifiedProfile }): ReactNode {
export default function BannersPage(): ReactNode {
const data = useProviderData();
- useMeta({
- title: 'Verified Profiles — Quinn, San Francisco Escort',
- description: 'Quinn is a verified independent escort on trusted platforms. View her verified profiles.',
- });
+ useMeta();
const profiles = data.verifiedProfiles ?? [];
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/BlogPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/BlogPage.tsx
index d7fe681d..112ed48a 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/BlogPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/BlogPage.tsx
@@ -101,7 +101,7 @@ function formatDate(iso: string): string {
}
export default function BlogPage(): ReactNode {
- useMeta({ title: 'Writing — Quinn', description: 'Notes, thoughts, and updates from Quinn.' });
+ useMeta();
const { data: posts, loading, error } = useBlogList();
return (
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/BookingPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/BookingPage.tsx
index 51dad4bb..a4bdc923 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/BookingPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/BookingPage.tsx
@@ -58,9 +58,7 @@ const RatesNote = styled.p`
`;
export default function BookingPage(): ReactNode {
- const metaTitle = useSiteText('booking', 'meta_title', 'Booking \u2014 Quinn');
- const metaDescription = useSiteText('booking', 'meta_description', 'Reserve a date with Quinn.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('booking', 'section_title', 'Book an Appointment');
const subtitle = useSiteText('booking', 'subtitle', 'Four simple steps');
const sectionContact = useSiteText('booking', 'section_contact', 'Contact');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/ContactPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/ContactPage.tsx
index 188effdc..3344717a 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/ContactPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/ContactPage.tsx
@@ -7,13 +7,10 @@ import { useEffect, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from '@lilith/ui-router';
import { useMeta } from '@/hooks/useMeta';
-import { useSiteText } from '@/hooks/useSiteText';
import { ContactModal } from '@/components/ContactModal/ContactModal';
export default function ContactPage(): ReactNode {
- const metaTitle = useSiteText('contact', 'meta_title', 'Contact — Quinn');
- const metaDescription = useSiteText('contact', 'meta_description', 'Get in touch.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const navigate = useNavigate();
function handleClose(): void {
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/ContentDropsPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/ContentDropsPage.tsx
index a489dd34..a78c65f4 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/ContentDropsPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/ContentDropsPage.tsx
@@ -182,14 +182,12 @@ interface LightboxTarget {
}
export default function ContentDropsPage(): ReactNode {
- const metaTitle = useSiteText('drops', 'meta_title', 'Drops — Quinn');
- const metaDescription = useSiteText('drops', 'meta_description', 'Exclusive content drops — shop Quinn\'s latest releases on OnlyFans, Fansly, and more.');
const sectionTitle = useSiteText('drops', 'section_title', 'Drops');
const stateLoading = useSiteText('drops', 'state_loading', 'loading...');
const stateError = useSiteText('drops', 'state_error', 'Couldn\'t load drops.');
const stateEmpty = useSiteText('drops', 'state_empty', 'Nothing here yet.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const { data: drops, loading, error } = useContentDrops();
const [lightbox, setLightbox] = useState(null);
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/DestinationsPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/DestinationsPage.tsx
index dd25ac16..261403ad 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/DestinationsPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/DestinationsPage.tsx
@@ -167,9 +167,7 @@ const TourBadge = styled.span`
// ---------------------------------------------------------------------------
export default function DestinationsPage(): ReactNode {
- const metaTitle = useSiteText('destinations', 'meta_title', 'Destinations — Quinn | Worldwide FMTY');
- const metaDescription = useSiteText('destinations', 'meta_description', 'Quinn is an upscale trans escort available worldwide via Fly Me To You. Browse cities and book your destination.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('destinations', 'section_title', 'Destinations');
const subtitle = useSiteText('destinations', 'subtitle', 'You book the flight. I show up.');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/DuosPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/DuosPage.tsx
index e2952b2e..f06afc46 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/DuosPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/DuosPage.tsx
@@ -138,10 +138,7 @@ function TrystIcon(): ReactNode {
// ── Page ──────────────────────────────────────────────────────────────────────
export default function DuosPage(): ReactNode {
- useMeta({
- title: 'Duo Sessions — Quinn | Trans Escort San Francisco',
- description: 'Duo sessions with Quinn — two-person experiences, carefully arranged. Upscale trans escort in San Francisco, touring nationwide.',
- });
+ useMeta();
const { contact } = useProviderData();
const { rewritePhotoSrc } = useProviderConfig();
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/EtiquettePage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/EtiquettePage.tsx
index ce7547fd..501e5136 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/EtiquettePage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/EtiquettePage.tsx
@@ -156,9 +156,7 @@ function EtiquetteSectionCard({
}
export default function EtiquettePage(): ReactNode {
- const metaTitle = useSiteText('etiquette', 'meta_title', 'Etiquette — Quinn');
- const metaDescription = useSiteText('etiquette', 'meta_description', 'Etiquette guidelines for booking with Quinn.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('etiquette', 'section_title', 'Etiquette');
const subtitle = useSiteText('etiquette', 'subtitle', 'Guidelines for a respectful experience');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/FmtyPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/FmtyPage.tsx
index aa095627..a02b0715 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/FmtyPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/FmtyPage.tsx
@@ -97,10 +97,7 @@ const AddCityCta = styled.button`
`;
export default function FmtyPage(): ReactNode {
- useMeta({
- title: useSiteText('fmty', 'meta_title', 'Fly Me To You — Quinn'),
- description: useSiteText('fmty', 'meta_description', 'Commission a private visit. You arrange the travel, Quinn arrives.'),
- });
+ useMeta();
const sectionTitle = useSiteText('tour', 'fmty_section_title', 'Fly Me To You');
const sectionSubtitle = useSiteText('tour', 'fmty_subtitle', 'You book the flight. I show up.');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/GalleryPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/GalleryPage.tsx
index 2686b1e0..3570ea33 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/GalleryPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/GalleryPage.tsx
@@ -34,10 +34,8 @@ const SkeletonTile = styled(Skeleton)`
`;
export default function GalleryPage(): ReactNode {
- const metaTitle = useSiteText('gallery', 'meta_title', 'Gallery — Quinn');
- const metaDescription = useSiteText('gallery', 'meta_description', 'Photos and looks.');
const sectionTitle = useSiteText('gallery', 'section_title', 'Gallery');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const data = useProviderData();
const isLoading = useProviderDataLoading();
const audience = useAudience();
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/HomePage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/HomePage.tsx
index cae2ddcc..c866c722 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/HomePage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/HomePage.tsx
@@ -235,23 +235,9 @@ export default function HomePage(): ReactNode {
const ctaBookSession = useSiteText('home', 'cta_book_session', 'Book a session');
const revealHint = useSiteText('home', 'reveal_hint', 'Tap to reveal');
- // Location-aware SEO — single source of truth from tour status.
- // Active stop → "Currently in {city}"; next stop → "Heading to {city}";
- // fallback → identity.location (home base).
- const locationLabel = activeStop
- ? `${activeStop.city}${activeStop.state ? `, ${activeStop.state}` : ''}`
- : data.identity.location || 'San Francisco';
- const metaTitle = `${data.identity.name || 'Quinn'} — ${locationLabel} Escort`;
- const metaDescription = activeStop
- ? `Currently in ${locationLabel}. Upscale trans escort — bookings by text only.`
- : nextStop
- ? `Heading to ${nextStop.city}, ${nextStop.state} soon. Upscale trans escort based in ${data.identity.location}.`
- : `Upscale trans escort based in ${data.identity.location}. Touring nationwide. Bookings by text only.`;
-
- useMeta({
- title: metaTitle,
- description: metaDescription,
- });
+ // Location-aware SEO is centralized: '/' is a TOUR_AWARE_PATH, so useMeta()
+ // derives the title/description from the active tour stop via resolveMeta.
+ useMeta();
const navigate = useNavigate();
const sounds = useSounds();
const { trackInteraction } = useAnalytics();
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/LinksPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/LinksPage.tsx
index f8578137..014c95f5 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/LinksPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/LinksPage.tsx
@@ -533,9 +533,7 @@ function HazardousExternalCard({
// ---------------------------------------------------------------------------
export default function LinksPage(): ReactNode {
- const metaTitle = useSiteText('links', 'meta_title', 'Links — Quinn');
- const metaDescription = useSiteText('links', 'meta_description', 'All links.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const ctaBookNow = useSiteText('links', 'cta_book_now', 'Book Now');
const ctaGallery = useSiteText('links', 'cta_gallery', 'Gallery');
const ctaRates = useSiteText('links', 'cta_rates', 'Rates');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/MySchedulePage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/MySchedulePage.tsx
index baa0c1da..60ceedbd 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/MySchedulePage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/MySchedulePage.tsx
@@ -522,10 +522,7 @@ function StopRow({ stop, vipToken }: StopRowProps): ReactNode {
// ---------------------------------------------------------------------------
export default function MySchedulePage(): ReactNode {
- useMeta({
- title: 'My Schedule — Quinn',
- description: 'See upcoming tour stops and register your interest in booking during Quinn\'s next visit to your city.',
- });
+ useMeta();
const [searchParams] = useSearchParams();
const vipToken = searchParams.get('vip');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/RatesPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/RatesPage.tsx
index db88a373..dcfb91fd 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/RatesPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/RatesPage.tsx
@@ -70,9 +70,7 @@ const FansTeaserText = styled.p`
`;
export default function RatesPage(): ReactNode {
- const metaTitle = useSiteText('rates', 'meta_title', 'Rates — Quinn');
- const metaDescription = useSiteText('rates', 'meta_description', 'Service menu and rates.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('rates', 'section_title', 'Rates');
const subtitle = useSiteText('rates', 'subtitle', 'All prices in USD');
const labelAddons = useSiteText('rates', 'label_addons', 'Add-Ons');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/ShopPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/ShopPage.tsx
index cf679125..3167ae7f 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/ShopPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/ShopPage.tsx
@@ -10,7 +10,6 @@ import { createPortal } from 'react-dom';
import { useNavigate } from '@lilith/ui-router';
import { useProviderData } from '@/hooks/useProviderData';
import { useMeta } from '@/hooks/useMeta';
-import { useSiteText } from '@/hooks/useSiteText';
import { Section } from '@/components/shared/Section';
import { ShopSignupModal } from '@/components/ShopSignupModal/ShopSignupModal';
import { ShopGrid } from '@/components/Shop/ShopGrid';
@@ -33,9 +32,7 @@ function ShopTheater(): ReactNode {
}
export default function ShopPage(): ReactNode {
- const metaTitle = useSiteText('shop', 'meta_title', "Quinn's Shop — Pre-Loved Finds");
- const metaDescription = useSiteText('shop', 'meta_description', 'Browse pre-loved clothing, accessories, and more.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const data = useProviderData();
const listings = data.shop ?? [];
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/SpecialtiesPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/SpecialtiesPage.tsx
index c20f0c96..d6ead6b1 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/SpecialtiesPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/SpecialtiesPage.tsx
@@ -129,9 +129,7 @@ const ItemDesc = styled.span`
// ---------------------------------------------------------------------------
export default function SpecialtiesPage(): ReactNode {
- const metaTitle = useSiteText('specialties', 'meta_title', 'Specialties — Quinn | San Francisco Trans Escort');
- const metaDescription = useSiteText('specialties', 'meta_description', `Browse Quinn's full menu of services — GFE, overnight sessions, kink-friendly experiences, and more. Upscale trans escort in San Francisco.`);
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('specialties', 'section_title', 'Specialties');
const subtitle = useSiteText('specialties', 'subtitle', 'Full Service Menu');
diff --git a/codebase/@features/provider-website/frontend-public/src/pages/TourPage.tsx b/codebase/@features/provider-website/frontend-public/src/pages/TourPage.tsx
index 3044498d..997a42fd 100644
--- a/codebase/@features/provider-website/frontend-public/src/pages/TourPage.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/pages/TourPage.tsx
@@ -344,9 +344,7 @@ const FmtyActionButton = styled.button`
// ---------------------------------------------------------------------------
export default function TourPage(): ReactNode {
- const metaTitle = useSiteText('tour', 'meta_title', 'Tour Schedule — Quinn');
- const metaDescription = useSiteText('tour', 'meta_description', 'Upcoming cities and dates.');
- useMeta({ title: metaTitle, description: metaDescription });
+ useMeta();
const sectionTitle = useSiteText('tour', 'section_title', 'Tour Schedule');
const tourSubtitle = useSiteText('tour', 'subtitle', '2026 World Tour');
const note1 = useSiteText('tour', 'note_1', 'Dates shift based on bookings — text early if a city interests you.');
diff --git a/codebase/@features/provider-website/frontend-public/src/routes/registry.tsx b/codebase/@features/provider-website/frontend-public/src/routes/registry.tsx
index 8ac33377..1ca62de9 100644
--- a/codebase/@features/provider-website/frontend-public/src/routes/registry.tsx
+++ b/codebase/@features/provider-website/frontend-public/src/routes/registry.tsx
@@ -90,30 +90,6 @@ export type FooterPlacement = {
order: number;
};
-// ── Meta descriptor ───────────────────────────────────────────────────────────
-/**
- * Per-route SEO meta. The single source of truth for static-route titles and
- * descriptions; consumed by useMeta (client) and, in phase 2, an edge/prerender
- * injector. See src/meta/resolveMeta.ts for the resolution pipeline.
- *
- * `title` / `description` are pre-template fallbacks (page labels, not full
- * titles — the "— Quinn" suffix is applied centrally). Admin edits override
- * them via siteText: useMeta reads `{namespace}.meta_title` / `.meta_description`
- * when `namespace` is set, falling back to these strings.
- */
-export type RouteMeta = {
- /** siteText namespace holding admin-editable meta_title / meta_description. */
- namespace?: string;
- /** Page label / title fallback, pre-template (e.g. "Gallery"). */
- title?: string;
- /** Description fallback. */
- description?: string;
- /** Per-route og:image override (absolute URL). */
- ogImage?: string;
- /** Emit robots noindex for this route. */
- noindex?: boolean;
-};
-
// ── Registry entry ────────────────────────────────────────────────────────────
export type RouteEntry = {
/** URL pattern (React Router syntax) */
@@ -132,8 +108,6 @@ export type RouteEntry = {
nav?: NavPlacement;
/** Footer placement. null / absent = not in footer. */
footer?: FooterPlacement;
- /** SEO meta for this route. Absent = resolver base defaults (home-style). */
- meta?: RouteMeta;
/** True = route is served in VITE_MAINTENANCE_MODE=true. */
maintenance: boolean;
};
@@ -145,9 +119,6 @@ export const routeRegistry: readonly RouteEntry[] = [
path: '/',
element: ,
// HomePage is not lazy — no prefetchImport needed
- // Tour-aware (see TOUR_AWARE_PATHS): title/desc reflect the active stop.
- // Empty fallbacks → resolver base default "Quinn — {homeLocation} Escort".
- meta: { namespace: 'home' },
maintenance: true,
},
@@ -158,7 +129,6 @@ export const routeRegistry: readonly RouteEntry[] = [
prefetchImport: () => import('@/pages/AboutPage'),
navLabel: { namespace: 'nav', key: 'about', fallback: 'About' },
nav: { group: 'primary', order: 1 },
- meta: { namespace: 'about', title: 'About', description: 'Background, vibe, and what to expect.' },
maintenance: true,
},
{
@@ -167,7 +137,6 @@ export const routeRegistry: readonly RouteEntry[] = [
prefetchImport: () => import('@/pages/GalleryPage'),
navLabel: { namespace: 'nav', key: 'gallery', fallback: 'Gallery' },
nav: { group: 'primary', order: 2 },
- meta: { namespace: 'gallery', title: 'Gallery', description: 'Photos and looks.' },
maintenance: true,
},
{
@@ -176,7 +145,6 @@ export const routeRegistry: readonly RouteEntry[] = [
prefetchImport: () => import('@/pages/RatesPage'),
navLabel: { namespace: 'nav', key: 'rates', fallback: 'Rates' },
nav: { group: 'primary', order: 3 },
- meta: { namespace: 'rates', title: 'Rates', description: 'Service menu and rates.' },
maintenance: true,
},
{
@@ -187,8 +155,6 @@ export const routeRegistry: readonly RouteEntry[] = [
footerLabel: { namespace: 'footer', key: 'cta_tour_schedule', fallback: 'Tour Schedule' },
nav: { group: 'primary', order: 4 },
footer: { section: 'touring', order: 1 },
- // Tour-aware (see TOUR_AWARE_PATHS).
- meta: { namespace: 'tour', title: 'Tour Schedule', description: 'Upcoming cities and dates.' },
maintenance: true,
},
{
@@ -199,7 +165,6 @@ export const routeRegistry: readonly RouteEntry[] = [
footerLabel: { namespace: 'footer', key: 'cta_destinations', fallback: 'Destinations' },
nav: { group: 'primary', order: 5 },
footer: { section: 'touring', order: 2 },
- meta: { namespace: 'destinations', title: 'Destinations', description: 'Quinn is an upscale trans escort available worldwide via Fly Me To You. Browse cities and book your destination.' },
maintenance: true,
},
{
@@ -208,7 +173,6 @@ export const routeRegistry: readonly RouteEntry[] = [
prefetchImport: () => import('@/pages/BookingPage'),
navLabel: { namespace: 'nav', key: 'book', fallback: 'Book' },
nav: { group: 'primary', order: 6 },
- meta: { namespace: 'booking', title: 'Booking', description: 'Reserve a date with Quinn.' },
maintenance: false,
},
diff --git a/deployments/@domains/quinn.my/nginx/quinn-api-proxy.conf b/deployments/@domains/quinn.my/nginx/quinn-api-proxy.conf
index 2d01bbb8..11ab4931 100644
--- a/deployments/@domains/quinn.my/nginx/quinn-api-proxy.conf
+++ b/deployments/@domains/quinn.my/nginx/quinn-api-proxy.conf
@@ -97,7 +97,19 @@ location ~ ^/api/clients(/.*)?$ {
proxy_pass http://127.0.0.1:3030/my/clients$1$is_args$args;
include /etc/nginx/snippets/quinn-api-proxy-headers.conf;
}
-location ~ ^/api/(bookings|claude-accounts|credentials|financials|flight-monitor|flights|hotel-observations|hotel-stays|inspiration|journal|notifications|outreach|pending-income|planner|price-watches|projects|prospector|prospects|reminders|tour-hotels|tour-legs|tour-stops)(/.*)?$ {
+# ─── prospects → INTERNAL quinn.api on black ─────────────────────────────────
+# The Prospector stream reads the macsync DB (macsync.messages / send_queue /
+# calendars). On quinn-vps QUINN_MACSYNC_DB_URL points at a stale local replica
+# (old `icloud` schema, 0 rows) so prospectStream 500s here. black is the
+# canonical INTERNAL backend and holds the live macsync data, so route the
+# authenticated prospects surface there. Cookie is forwarded (headers snippet),
+# so black's ssoRequired gate validates the same SSO session. Must precede the
+# 1:1 catch-all below — first matching regex location wins.
+location ~ ^/api/prospects(/.*)?$ {
+ proxy_pass http://10.0.0.11:3030/my/prospects$1$is_args$args;
+ include /etc/nginx/snippets/quinn-api-proxy-headers.conf;
+}
+location ~ ^/api/(bookings|claude-accounts|credentials|financials|flight-monitor|flights|hotel-observations|hotel-stays|inspiration|journal|notifications|outreach|pending-income|planner|price-watches|projects|prospector|reminders|tour-hotels|tour-legs|tour-stops)(/.*)?$ {
proxy_pass http://127.0.0.1:3030/my/$1$2$is_args$args;
include /etc/nginx/snippets/quinn-api-proxy-headers.conf;
}
\ No newline at end of file