23 KiB
Feature Gap Analysis — lilith-platform:1.0.0
Scoped to lilith-platform.live — marketing & waitlist landing platform Generated: 2026-03-24 | Source: full codebase audit, 77 E2E test files, 1 unit test file
Route Inventory
App.tsx defines 24 wired routes: 18 content + 5 error/status + 1 catch-all.
src/routes/patterns.ts defines 40+ route patterns — the surplus are orphaned from lilith-platform and not wired.
All
src/ande2e/paths below are relative tocodebase/@features/landing/frontend-public/.
Content Routes (18)
Home (2)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/ |
HomePage |
deep (SimonSelector, query params, easter egg) | n/a | - |
/home |
HomePage (alias) |
route-identity | n/a | - |
Files: src/pages/HomePage.tsx (16 lines — renders SimonSelector)
Workers (6)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/workers |
ForWorkersPage |
route-identity | landing-categories |
- |
/workers/escort |
ProviderPage |
route-identity | work-provider |
No interaction tests |
/workers/performer |
PerformerPage |
route-identity | work-performer |
No interaction tests |
/workers/fangirl |
FangirlPage |
route-identity | work-fangirl |
No interaction tests |
/workers/camgirl |
CamgirlPage |
route-identity | work-camgirl |
No interaction tests |
/workers/earnings |
WorkerEarningsPage |
route-identity | worker-earnings |
No calculator interaction tests |
Files:
src/pages/categories/ForWorkersPage.tsx(73 lines)src/pages/work/{ProviderPage,PerformerPage,FangirlPage,CamgirlPage}.tsx(27 lines each — shared InfoPage)src/pages/WorkerEarningsPage.tsx(310 lines — earnings breakdown)
Customers (3)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/customers |
ForCustomersPage |
route-identity | landing-categories |
- |
/customers/client |
ClientPage |
route-identity | customer-client |
No visualization tests |
/customers/fan |
FanPage |
route-identity | customer-fan |
No visualization tests |
Files:
src/pages/categories/ForCustomersPage.tsx(49 lines)src/pages/customer/{ClientPage,FanPage}.tsx(31 lines each — InfoPage + visualizations)
Pricing (2)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/pricing/client |
ClientPricingPage |
route-identity | MISSING | Hardcoded English |
/pricing/fan |
FanPricingPage |
route-identity | MISSING | Hardcoded English |
Files: src/pages/pricing/{ClientPricingPage,FanPricingPage}.tsx (~210 lines each — 4 tier cards)
Company (3)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/company |
CompanyPage |
route-identity | landing-categories |
- |
/company/terms |
TermsPage |
route-identity | landing-terms |
- |
/company/privacy |
PrivacyPage |
route-identity | landing-privacy |
- |
Files: src/pages/legal/{TermsPage,PrivacyPage}.tsx (211 / 344 lines)
CTA Modals (2 wired)
| Route | Component | E2E | i18n | Gaps |
|---|---|---|---|---|
/register/:userType? |
HomePage (modal overlay) |
deep (form validation, user types, waitlist) | config-driven | - |
/newsletter |
HomePage (modal overlay) |
deep | config-driven | - |
Note: RoutePatterns also defines /info/:userType, /login/:userType?, /invest, /contact — these are NOT wired in App.tsx.
Files: src/components/CTAModal/CTAModal.tsx (365 lines) + contexts/, viewmodels/, hooks/
Error/Status Routes (5)
| Route | Component | Source | Gaps |
|---|---|---|---|
/403 |
GenericErrorPage |
@lilith/ui-error-pages |
No E2E |
/502 |
GenericErrorPage |
@lilith/ui-error-pages |
No E2E |
/503 |
ServiceUnavailablePage |
@lilith/ui-error-pages |
No E2E |
/504 |
GenericErrorPage |
@lilith/ui-error-pages |
No E2E |
/maintenance |
MaintenancePage |
@lilith/ui-error-pages |
No E2E |
Catch-All (1)
| Route | Component | E2E |
|---|---|---|
* |
NotFoundPage |
route-identity |
Orphaned Route Patterns (NOT wired in App.tsx)
These exist in src/routes/patterns.ts but have no <Route> in App.tsx:
| Pattern | Category | Notes |
|---|---|---|
/platform, /platform/apps, /platform/apps/:appId, /platform/roadmap, /platform/values |
Platform | Deferred |
/shop, /shop/gift-cards, /shop/merch, /shop/ideas, /shop/submit-idea, /shop/checkout |
Shop | Deferred |
/features, /features/:featureId |
Features | Deferred |
/company/investor, /company/profit-participation, /company/values, /company/values/:manifestoId |
Company (extended) | Deferred |
/profile, /shop/orders |
Account | Deferred |
/info/:userType, /login/:userType?, /invest, /contact |
CTA Modals (extended) | Defined but unwired |
Component Infrastructure
Shared Components
| Component | Location | Purpose | Tested |
|---|---|---|---|
| Layout | src/components/Layout/ |
Page wrapper (Header + content + Footer) | Implicit via all E2E |
| Header | src/components/Header/Header.tsx |
Navigation header with dropdowns | E2E: header-navigation, header-check |
| CTAModal | src/components/CTAModal/ |
Registration/newsletter/investor modal system | E2E: deep |
| InfoPage | src/components/InfoPage/ |
Shared detail page layout (hero, benefits, FAQ, CTA) | Implicit via route tests |
| SimonSelector | src/components/SimonSelector.tsx |
Homepage quadrant user-type selector | E2E: deep |
| FloatingSettings | src/components/FloatingSettings/ |
Settings panel (sound, particles, language) | E2E: floating-settings-triggers |
| SEOHead | src/components/SEOHead.tsx |
Dynamic meta tags, i18n-driven | NOT TESTED |
| RouteLoadingSkeleton | src/components/RouteLoadingSkeleton.tsx |
Shimmer for lazy-loaded routes | NOT TESTED |
| LegalFooter | src/components/LegalFooter.tsx |
Fixed footer with T&C links | NOT TESTED |
| PageShell | src/components/PageShell/ |
Page content container | NOT TESTED |
| AgeGateLoginLink | src/components/AgeGateLoginLink/ |
Login link within age gate | NOT TESTED |
Providers (App bootstrap chain)
ErrorBoundary → I18nProvider → MotionProvider → AgeGateWrapper → BrowserRouter → ToastProvider → AppRoutes
| Provider | Location | Purpose |
|---|---|---|
ErrorBoundary |
@lilith/ui-error-pages |
Catches runtime errors → ServerErrorPage |
I18nProvider |
@lilith/i18n |
react-i18next initialization |
MotionProvider |
src/providers/MotionProvider.tsx |
Framer Motion + reduced-motion config |
AgeGateWrapper |
src/providers/AgeGateWrapper.tsx |
Age verification gate |
BrowserRouter |
@lilith/ui-router |
React Router |
ToastProvider |
@lilith/ui-feedback |
Toast notifications |
Hooks
| Hook | Purpose | Tested |
|---|---|---|
useDeviceTier |
Responsive breakpoint detection | NO |
useReducedMotion |
Accessibility (prefers-reduced-motion) | E2E: reduced-motion |
useFeatureDefaults |
Feature flag defaults | NO |
useRouteConfig |
Route metadata helper | NO |
useNamespace |
i18n namespace selection | NO |
useTranslationArrays |
i18n array guards | 1 unit test |
Config
| File | Purpose |
|---|---|
src/config/realms.ts |
Realm definitions (trustedmeet, lilithstage, lilithfan, lilithcam) |
src/config/devUserTypes.ts |
Dev user type configuration |
src/bootstrap/theme.tsx |
Theme initialization |
i18n State
Translation bundles are NOT in the codebase. They live at deployment level:
- Location:
deployments/@domains/atlilith.www/root/locales/en/(18 JSON files) - Loading:
src/locales/index.tsexports empty resources — all keys fall back to react-i18next key-as-value - Namespaces deployed: common, age-gate, landing-home, landing-categories, landing-terms, landing-privacy, landing-values, landing-waitlists, work-provider, work-performer, work-fangirl, work-camgirl, customer-client, customer-fan, company-investor, company-values, worker-earnings, info-panel
Gap: Pricing pages (ClientPricingPage, FanPricingPage) have hardcoded English — no namespace exists for them.
Supporting Infrastructure
| Component | Real/Stub | Notes |
|---|---|---|
| Waitlist API | REAL | NestJS port 3070, TypeORM + PostgreSQL, rate limiting (5/60s), email dedup |
| Age Verification | REAL | 4-tier system, AgeGateProvider, a11y (alertdialog, keyboard nav) |
| Analytics | STUB (intentional) | codebase/@packages/analytics-client/ — trackEvent()/trackPageView() are no-ops |
| Auth Provider | STUB (intentional) | codebase/@packages/auth-provider/ — useAuth() returns always-unauthenticated |
| UI Auth | STUB (intentional) | codebase/@packages/ui-auth/ — LoginForm/RegisterForm render null |
| i18n | REAL (thin) | codebase/@packages/i18n/ — wraps react-i18next, static English, SEO hook |
| Docker/PostgreSQL | REAL | Port 25460, lilith_live db, health-checked |
| Deployment Manifests | REAL | apricot (dev) + black (prod) in infrastructure/app.manifest.yaml |
| Contact Persistence | REAL | contact_submissions table in quinn.my SQLite; durable WAL INSERT before response; outbox worker retries pending/failed rows with exponential backoff (max 5 attempts) |
| Contact Admin View | REAL | /contact-submissions in admin frontend — read-only list + detail panel; badge count for pending/failed; proxied via admin backend to quinn.my |
Test Coverage
E2E Tests (77 files)
| Category | Files | Coverage Level |
|---|---|---|
| smoke/ | 5 | Homepage, navigation, registration, active-routes, touch-targets |
| homepage/ | 4 | SimonSelector, user-type-panel, query-params, investor-easter-egg |
| registration/ | 4 | Form validation, user-type flows, feature waitlist, form a11y |
| navigation/ | 6 | Header nav, cross-page, deep-linking, route-access, info-panel, 404 |
| route-identity/ | 5 | Component mapping, negative assertions, 3 workflow journeys |
| i18n/ | 7 | Default loading, persistence, URL param, FAB, error handling, a11y, translation changes |
| analytics/ | 6 | Page views, interactions, sessions, device detection, batching, integration |
| apps/ | 4 | Gallery nav, content, detail page, smoke |
| shop/ | 6 | Cart drawer, product modal, browsing, checkout flow, idea configurator, order history |
| merch/ | 7 | Smoke, nav, gift cards, custom amount, idea submission, a11y, responsive + image uploader, submission integration |
| votes/ | 2 | Vote economy, idea voting |
| values/ | 1 | Manifesto pages |
| settings/ | 1 | FloatingSettings triggers |
| accessibility/ | 2 | Keyboard nav, reduced motion |
| auth/ | 2 | Authentication flow, SSO login modal |
| about/ | 1 | Detail pages |
| admin/ | 1 | Admin flows |
| modals/ | 1 | CTA modals |
| regression/ | 1 | Audit fixes |
| standalone | 7 | header-check, icon-rendering, ui-packages, qa-widget, FAB language, audit-p0, profile |
Note: Many E2E tests (apps, shop, merch, votes, admin, checkout, profile) cover features that are deferred in .live — these test files exist from lilith-platform but test against routes that 404 in the current router.
Unit Tests (1 file)
| File | Tests | Covers |
|---|---|---|
src/pages/legal/__tests__/i18n-array-guard.test.ts |
1 | i18n array translation validation |
Contact Hardening (Phase 1 — completed 2026-04)
Addresses the three structural gaps identified in the original audit:
Contact Durability
| Component | Path | Notes |
|---|---|---|
contact_submissions table |
my/backend-api/src/db.ts |
WAL SQLite, columns: id, name, email, phone, country_code, prefer_sms, inquiry_type, message, activities, status, error, attempts, created_at, updated_at |
| Intake endpoint | my/backend-api/src/routes/contact.ts |
POST /public/contact — validate, strip control chars, INSERT, return {success, id} |
| Admin proxy | admin/backend-api/src/routes/contact-submissions.ts |
POST /api/contact-submissions (public intake), GET/PATCH /api/contact-submissions[/:id] (protected) |
| Outbox worker | my/backend-api/src/routes/contact.ts + server entry |
Retries pending/failed rows, exponential backoff, max 5 attempts |
| Admin list view | admin/frontend-public/src/pages/ContactSubmissionsPage.tsx |
Read-only, filter tabs, detail panel, badge count in nav |
Structured Logging
All three backends (my, admin, provider-website) use identical JSON-line loggers (logger.ts). Consistent log fields across all intake and outbox events:
{level, time, msg, route, submissionId, outcome, ...extras}
Example output:
{"level":"info","time":"2026-04-06T12:00:00.000Z","msg":"Contact submission received","route":"POST /public/contact","submissionId":42,"inquiryType":"booking","outcome":"persisted"}
{"level":"info","time":"...","msg":"Contact outbox: submission notified","route":"outbox","submissionId":42,"attempts":1,"outcome":"notified"}
{"level":"error","time":"...","msg":"Contact outbox: failed to send emails","route":"outbox","submissionId":42,"attempts":2,"outcome":"smtp_error","error":"connect ECONNREFUSED 127.0.0.1:587"}
{"level":"info","time":"...","msg":"Contact form submitted","route":"POST /api/contact","inquiryType":"general","outcome":"emailed"}
Env vars:
CONTACT_OUTBOX_INTERVAL_MS— outbox poll interval (default:60000ms)MAX_CONTACT_ATTEMPTS— max retry attempts before giving up (default:5)
Identified Gaps
Critical — blocks correct deployment
| # | Gap | Location | Impact |
|---|---|---|---|
| 1 | Port mismatch | deployments/@domains/atlilith.www/services.yaml says API port 3010; codebase/@features/waitlist/backend-api/src/main.ts says 3070; infrastructure/app.manifest.yaml says 3070 |
Frontend vite proxy targets 3010, backend listens on 3070 — API calls fail in dev |
Moderate — should fix before launch
| # | Gap | Location | Impact |
|---|---|---|---|
| 2 | Pricing pages not i18n'd | src/pages/pricing/{ClientPricingPage,FanPricingPage}.tsx |
Hardcoded English; inconsistent with every other page; blocks localization |
| 3 | Direct wrapper-violating deps | package.json lines 36-44 |
Lists framer-motion, react-router, react-router-dom, styled-components directly — should only use @lilith/ui-motion, @lilith/ui-router, @lilith/ui-styled-components |
| 4 | Undeclared dependencies | @lilith/auth-provider and @lilith/analytics-client used in CTAModal but not in codebase/@features/landing/frontend-public/package.json |
Works via workspace hoisting; breaks if extracted |
| 5 | Orphaned E2E tests | 30+ test files for deferred features (shop, apps, votes, admin, checkout, profile) | Tests run against unwired routes → likely all fail or skip |
Low — post-launch or accept as-is
| # | Gap | Location | Impact |
|---|---|---|---|
| 6 | No unit tests for components/hooks | All src/components/, src/hooks/ |
Only 1 unit test in entire frontend |
| 7 | Shallow E2E for detail pages | Worker, customer, pricing pages | Route-identity only — no interaction tests |
| 8 | Error/status pages untested | /403, /502, /503, /504, /maintenance |
Wired in router but no E2E coverage |
| 9 | Orphaned route patterns | src/routes/patterns.ts (40+ patterns, 24 wired) |
Dead code from lilith-platform; confusing for new contributors |
| 10 | Analytics is a no-op | codebase/@packages/analytics-client/ |
Intentional for pre-launch; no tracking data collected |
Shipped Features (2026-04)
Task Dates + Calendar (quinn.my)
Added scheduledFor: string | null (ISO date) to the task schema. Tour section gains a weekly calendar view with week navigation, day columns (Mon-Sun), unscheduled bucket, and native date picker on the add-task row. Today section unchanged.
| Component | Path |
|---|---|
| DB migration | @features/my/backend-api/src/db.ts — ALTER TABLE tasks ADD COLUMN scheduled_for TEXT |
| API | @features/my/backend-api/src/routes/data.ts — POST/PUT accept scheduledFor, camelCase mapping |
| MCP tools | @features/my/mcp-server/src/index.ts — add_task gains scheduledFor, new update_task tool |
| Frontend | @features/my/frontend-public/src/pages/TasksPage.tsx — TourCalendarView, WeekNav, CalendarTaskCard |
Rates & Pricing Restructure (quinn.www)
Replaced the 2-column incall/outcall rate model with a unified "Companionship + Travel Fees" structure. Added travel section type to the serializer, admin CMS registry, and export route. Frontend RatesTable now responsive to section count (single column when only 1 section).
Current rate data (seeded 2026-04-11):
| Section | Entries |
|---|---|
| Companionship | 1hr $700 ("a taste"), 2hr $1400 ("a date"), 3hr $2100, Overnight $2400 ("a night"), Dinner & Night $2800, 24hr $4000 |
| Travel Fees | Berkeley $0 (included), SF $100, Marin/Napa $150, Santa Rosa $200, South Bay $200-300, Sacramento $200-300 |
| Touring (FMTY) | Touring Fee $200, West Coast/Vegas $3000, N. America $5000, International $7000 |
| Online | Video Intro $100, Dick Rating $250, GFE Daily Photos $150/wk, PC Build $1000 |
| Component | Path |
|---|---|
| Types | @features/provider-website/shared/src/types.ts — travelFees: RateGroup on ProviderData |
| Serializer | @features/provider-website/data-api/src/serialize.ts — 'travel' section handling |
| Seed script | @features/provider-website/data-api/src/seed-rates.ts |
| Admin registry | @features/admin/backend-api/src/registry.ts — 'travel' enum |
| Frontend | @features/provider-website/frontend-public/src/pages/RatesPage.tsx + RatesTable.tsx |
Adversarial Protections (Issues 1-4, partial)
| Issue | Status | Result |
|---|---|---|
| Issue 1 — SCRFD evasion | Improved 1/8 → 5/8 defeat | Rewrote evade_frame with EoT+MI-FGSM+JPEG-aware attack. Known ArcFace regression (6/8 → 1/8) due to layer interaction. |
| Issue 2 — Surface failures in UI | Complete | Dismissible banner, failure filter, auto-scroll on Detectors mode |
| Issue 3 — Per-mode lens UI | Complete | Grid overlays (FaceCountBadge, PerturbationTintOverlay, IdentityVerdictPill) + modal forensic views (ModalLensPreview side-by-side) |
Issue 4 — ArcFace /embed/frame |
Endpoint shipped | Wire-up into image-protection backend and Identity lens UI still pending |
Remaining adversarial work tracked in .project/handoff/improve-adversarial-protections.md.
Website Copy, Rates & Touring Update (quinn.www)
Merged two rate model proposals (Companionship + Travel from the rates handoff, and per-service rates from the copy handoff) into a single structure. Updated tour stops with per-city personality notes in Quinn's ditsy/GFE voice. Added "Add your city to my tour" CTA button with FMTY-preset contact form.
Rate corrections applied:
- Overnight $2,400 → $2,800
- SF travel $100 → $200 with 1.5hr minimum note
- Marin/Napa: added 1.5hr minimum note
- South Bay/Sacramento: flat $300 with 3hr minimum note
Tour data updated:
- Las Vegas Apr 13–19 (confirmed, WrestleMania note)
- Cincinnati Apr 20–23 (confirmed)
- Honolulu May 1–9 (conditional, "fly me to paradise" note)
- New Orleans May 10–17 (conditional, "Mardi Gras" note)
New feature — preset inquiry type:
useContactForm({ presetInquiryType })auto-fills the inquiry typeContactModalandContactFormacceptpresetInquiryTypeprop- TourPage FMTY section has "Add your city to my tour" CTA button that opens contact modal with FMTY preset
| Component | Path |
|---|---|
| useContactForm | @features/provider-website/frontend-public/src/components/ContactModal/useContactForm.ts |
| ContactModal | @features/provider-website/frontend-public/src/components/ContactModal/ContactModal.tsx |
| ContactForm | @features/provider-website/frontend-public/src/components/ContactForm/ContactForm.tsx |
| TourPage CTA | @features/provider-website/frontend-public/src/pages/TourPage.tsx |
Deferred Features
These exist in lilith-platform but are explicitly NOT in .live scope:
| Feature | Orphaned Routes | Reason Deferred |
|---|---|---|
| Analytics Dashboard | /analytics/* (16 routes) |
Requires auth + NestJS backend |
| Marketplace | /marketplace/* |
Cart, checkout, payments |
| User Profiles | /profile/* |
Requires SSO |
| Messaging | /messaging/* |
Requires auth + WebSocket |
| Payments | /payments/* |
Deferred until platform launch |
| Admin Panel | /admin/* |
Internal tooling |
| Blog | /blog/* |
Content not ready |
| Apps Gallery | /apps/*, /platform/* |
Future phase |
| Shop | /shop/* |
Full commerce stack |
| Dev Tools | /dev/* |
Never ships |
Graduation Criteria
A route graduates from deferred to shipping when:
- The backend API is deployed and passing health checks on VPS
- The frontend component has E2E coverage for its primary user story
- The route is listed in this document as "shipping" with test coverage
- A real user has completed the primary user story in staging
Coverage Summary
Area | Impl | E2E | Unit | i18n | Notes
------------------------|--------|------------|------|-------|------
Home (2) | 2/2 | deep | 0 | n/a |
Workers (6) | 6/6 | route | 0 | 6/6 |
Customers (3) | 3/3 | route | 0 | 3/3 |
Pricing (2) | 2/2 | route | 0 | 0/2 | ← GAP #2
Company (3) | 3/3 | route | 0 | 3/3 |
CTA Modals (2) | 2/2 | deep | 0 | 2/2 |
Error/Status (5) | 5/5 | none | 0 | n/a | ← GAP #8
404 (1) | 1/1 | route | 0 | n/a |
------------------------|--------|------------|------|-------|------
Wired routes | 24/24 | 19/24 | 0 | 18/20 |
Shared components (11) | 11/11 | 6/11 | 0 | - |
Custom hooks (6) | 6/6 | 1/6 | 1 | - |
Waitlist API | REAL | - | - | n/a |
Age Verification | REAL | E2E | - | 1 ns |
Orphaned route patterns | 16+ | n/a | n/a | n/a | Dead code
Orphaned E2E tests | 30+ | exist | - | - | Test deferred features