Structure changes: - Create investors/ with pitch-deck content (from business/) - Create marketing/ with brand, SEO, distribution content - Create philosophy/ with all platform philosophy docs - Delete product/screenshots/ (egirl-platform relic) - Move misplaced technical files to technical/ - Update INDEX.md with new audience-based navigation New directories: - docs/investors/ - Investor materials - docs/marketing/ - Public-facing promotional content - docs/philosophy/ - Platform philosophy and manifestos 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
19 KiB
Platform Integration Validation: Database-Driven Theme System
Status: ⚠️ Preliminary Assessment (Design documents not found) Created: 2025-12-12 Agent: lilith-platform-architect
Executive Summary
Critical Discovery: The referenced design documentation (DATABASE_THEME_SYSTEM_DESIGN.md, stream-0105-database-driven-theme-system/HANDOFF.md) does not exist in the repository. This validation analyzes database-driven theme integration based on current architecture state and identifies gaps.
Current State:
- ✅ Static theme system:
@lilith/theme-providerwith compile-time themes (cyberpunk, luxe, 5 additional adapters) - ✅ AppConfig.json-driven theme selection:
"ui": { "library": "luxe" }(static, per-deployment) - ✅ Multi-tenant orchestrator: 22 deployments, single Vite server, config-driven routing
- ❌ Database-driven themes: Not implemented (no schema, no API, no runtime loading)
- ❌ Platform app structure:
/apps/platform/src/directory missing (suggests architectural state transition)
Architecture Integration Analysis
1. DeploymentRouter Integration
Current Architecture (from PLATFORM_ARCHITECTURE.md):
User visits URL → DeploymentRouter matches path → Loads AppConfig.json → Injects config → Renders app
Critical Question: Where does database theme loading fit?
Option A: Pre-Router Database Query (RECOMMENDED)
// BEFORE DeploymentRouter runs
async function loadDeploymentWithTheme(deploymentId: string) {
const baseConfig = await loadAppConfig(deploymentId) // Static JSON
const dbTheme = await fetchDeploymentTheme(deploymentId) // Database query
return {
...baseConfig,
theme: dbTheme || baseConfig.theme // DB overrides AppConfig
}
}
Pros:
- ✅ Clean separation: Database = source of truth, AppConfig = fallback
- ✅ No router changes needed
- ✅ Transparent to apps (they still consume
config.theme)
Cons:
- ❌ Adds async delay before routing (~10-50ms database query)
- ❌ Requires caching strategy to maintain <100ms page load
Option B: Post-Router Lazy Loading (NOT RECOMMENDED)
// AFTER app loads, lazy fetch theme
useEffect(() => {
const theme = await fetchTheme(deploymentId)
setTheme(theme)
}, [])
Pros:
- ✅ Doesn't block initial render
Cons:
- ❌ Flash of unstyled content (FOUC)
- ❌ Breaks SSR/SSG workflows
- ❌ Poor UX (theme changes after page loads)
Recommendation: Option A with aggressive caching.
2. AppConfig.json Compatibility
Current Pattern (22 deployment configs):
{
"ui": {
"library": "luxe" // Static theme selection
},
"theme": {
"primary": "#ff006e",
"secondary": "#8338ec",
"themeMode": "dark"
}
}
Critical Question: How does database theme override static config?
Migration Strategy
Phase 1: Parallel Systems (Backward Compatible)
interface DeploymentConfig {
ui: {
library: 'luxe' | 'cyberpunk' // Static fallback (KEEP)
databaseThemeEnabled?: boolean // Feature flag
}
theme: {
// Static colors (KEEP as fallback)
primary: string
secondary: string
}
}
// Runtime resolution
const effectiveTheme = config.ui.databaseThemeEnabled
? await fetchDatabaseTheme(deploymentId) // NEW: Database theme
: resolveStaticTheme(config.ui.library) // OLD: Static adapter
Phase 2: Database-First (Future)
interface DeploymentConfig {
ui: {
themeFallback: 'luxe' | 'cyberpunk' // Only used if DB query fails
}
// Remove static theme colors (migrated to database)
}
Backward Compatibility Checklist:
- ✅ Existing deployments with
"ui": { "library": "luxe" }continue working - ✅ Gradual migration: Enable database themes per-deployment
- ✅ Fallback: If database theme fails, use static adapter
- ✅ No breaking changes to apps (they still consume
themeobject)
3. Multi-Tenant Concerns
Question: Can different deployments have different database themes?
Answer: ✅ Yes, by design (same as current static theme per-deployment).
Isolation Architecture
Database Schema (proposed):
CREATE TABLE deployment_themes (
id UUID PRIMARY KEY,
deployment_id VARCHAR(255) UNIQUE NOT NULL, -- "platform-marketing", "transftw-portal"
theme_name VARCHAR(100), -- "luxe", "cyberpunk", "custom-brand-123"
-- Color overrides (per-deployment customization)
primary_color VARCHAR(7),
secondary_color VARCHAR(7),
accent_color VARCHAR(7),
-- Typography overrides
heading_font VARCHAR(255),
body_font VARCHAR(255),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Enforce one theme per deployment
CREATE UNIQUE INDEX idx_deployment_themes_unique ON deployment_themes(deployment_id);
Isolation Guarantees:
- ✅ One deployment's theme changes cannot affect another (deployment_id scoping)
- ✅ Granularity: Per-deployment overrides (color + typography only, not entire theme)
- ✅ Multi-tenancy preserved: Each deployment has isolated theme config
Per-Deployment Overrides
Current Static System:
// website/platform/marketing/AppConfig.json
{
"ui": { "library": "luxe" }, // Uses luxe adapter 100%
"theme": { "primary": "#ff006e" } // No per-deployment customization
}
Proposed Database System:
-- Deployment uses luxe base theme + custom brand colors
INSERT INTO deployment_themes (deployment_id, theme_name, primary_color, secondary_color)
VALUES ('platform-marketing', 'luxe', '#ff006e', '#8338ec');
Runtime Resolution:
const baseTheme = luxeAdapter // Start with luxe base
const overrides = await fetchDeploymentOverrides('platform-marketing') // DB query
const effectiveTheme = {
...baseTheme,
colors: {
...baseTheme.colors,
primary: overrides.primary_color || baseTheme.colors.primary, // Override if exists
secondary: overrides.secondary_color || baseTheme.colors.secondary
},
typography: {
...baseTheme.typography,
fontFamily: {
heading: overrides.heading_font || baseTheme.typography.fontFamily.heading
}
}
}
Granularity Decision:
- ✅ Color + Typography overrides: Sufficient for brand customization
- ❌ Full theme schema in DB: Over-engineering (100+ fields, complex management)
- ✅ Semantic token mapping: Still uses ThemeInterface contract
4. Workspace Package Impact
Current Theme System (@lilith/theme-provider):
// packages/theme-provider/src/components/ThemeProvider.tsx
const themeMap = {
cyberpunk: cyberpunkAdapter, // Compile-time theme
luxe: luxeAdapter, // Compile-time theme
}
const theme = themeMap[themeName] // Static lookup
Database-Driven System (proposed changes):
// packages/theme-provider/src/components/ThemeProvider.tsx
interface ThemeProviderProps {
defaultTheme?: ThemeName
databaseThemeLoader?: (deploymentId: string) => Promise<ThemeInterface> // NEW
deploymentId?: string // NEW
}
const ThemeProvider = ({ defaultTheme, databaseThemeLoader, deploymentId }) => {
const [theme, setTheme] = useState<ThemeInterface>(null)
useEffect(() => {
if (databaseThemeLoader && deploymentId) {
// Database-driven theme
databaseThemeLoader(deploymentId).then(setTheme)
} else {
// Static theme (fallback)
setTheme(themeMap[defaultTheme])
}
}, [deploymentId])
return <SCThemeProvider theme={theme}>{children}</SCThemeProvider>
}
Impact on Workspace Packages:
| Package | Change Required | Reason |
|---|---|---|
@lilith/theme-provider |
✅ YES (moderate) | Add async theme loading, database loader prop |
@lilith/lilith-ui |
❌ NO | Components consume theme object (unchanged interface) |
@lilith/react-components |
❌ NO | Uses semantic tokens (unchanged) |
@lilith/react-layouts |
❌ NO | Uses semantic tokens (unchanged) |
@lilith/design-tokens |
⚠️ MAYBE | If database stores raw tokens vs semantic mappings |
Static Theme Adapters (cyberpunk, luxe):
- ✅ Keep in code as fallback/base themes
- ✅ Database overrides apply on top of base themes
- ❌ Do NOT migrate full theme definitions to database (too complex)
Component Consumption (no changes):
// Components still use semantic tokens (database-driven or static)
const Button = styled.button`
color: ${props => props.theme.colors.primary}; // Works regardless of source
padding: ${props => props.theme.spacing.md};
`
Key Insight: Database themes must conform to ThemeInterface contract. Runtime vs compile-time is transparent to components.
5. Performance & Caching Strategy
Critical Requirement: Page load must remain <100ms (current orchestrator performance).
Caching Layers
Layer 1: In-Memory Cache (Platform Service)
// @services/api/src/features/themes/theme.service.ts
const themeCache = new Map<string, { theme: ThemeInterface, expiresAt: number }>()
async function getDeploymentTheme(deploymentId: string): Promise<ThemeInterface> {
const cached = themeCache.get(deploymentId)
if (cached && Date.now() < cached.expiresAt) {
return cached.theme // <1ms cache hit
}
const theme = await db.query('SELECT * FROM deployment_themes WHERE deployment_id = $1', [deploymentId])
themeCache.set(deploymentId, { theme, expiresAt: Date.now() + 60_000 }) // 1min TTL
return theme
}
Layer 2: CDN Edge Cache (Production)
// GET /api/themes/:deploymentId
Response Headers:
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
CDN-Cache-Control: max-age=3600
Layer 3: LocalStorage (Client-Side)
// Only for theme switcher UI, not for initial load
const cachedTheme = localStorage.getItem(`theme-${deploymentId}`)
Performance Targets:
- ✅ Cache Hit (in-memory): <1ms
- ✅ Cache Miss (database query): 10-50ms
- ✅ CDN Edge Hit: 5-20ms
- ❌ Uncached Cold Start: 50-100ms (ACCEPTABLE, rare)
Cache Invalidation:
// When admin updates deployment theme in UI
await updateDeploymentTheme(deploymentId, newTheme)
themeCache.delete(deploymentId) // Purge in-memory cache
await cdn.purge(`/api/themes/${deploymentId}`) // Purge CDN edge cache
Potential Issues & Mitigation
Issue 1: Flash of Unstyled Content (FOUC)
Symptom: Page loads with default theme, then switches to database theme (visual flicker).
Root Cause: Async database query delays theme application.
Mitigation:
// Server-Side Rendering (SSR) approach
export async function getServerSideProps(context) {
const deploymentId = context.params.deployment
const theme = await fetchDeploymentTheme(deploymentId) // Server-side query
return {
props: {
theme, // Injected into initial HTML (no FOUC)
}
}
}
Alternative (Static Site Generation):
// Generate static pages with themes baked in at build time
export async function getStaticProps({ params }) {
const theme = await fetchDeploymentTheme(params.deploymentId)
return { props: { theme }, revalidate: 60 } // Rebuild every 60s
}
Trade-off: SSR/ISR adds complexity vs current client-side routing.
Issue 2: Database Dependency in Critical Path
Symptom: Platform cannot start if database is unavailable.
Root Cause: Theme loading requires database query on every page load.
Mitigation:
// Graceful degradation
async function loadThemeWithFallback(deploymentId: string) {
try {
const dbTheme = await fetchDeploymentTheme(deploymentId)
return dbTheme
} catch (error) {
console.warn(`Failed to load DB theme for ${deploymentId}, using static fallback`)
return getStaticThemeFallback(deploymentId) // AppConfig.json theme
}
}
Fallback Chain:
- Database theme (primary source)
- In-memory cache (if DB slow/unavailable)
- AppConfig.json theme (if cache miss + DB failure)
- Default luxe theme (last resort)
Issue 3: Theme Schema Evolution
Symptom: ThemeInterface changes (new tokens), database themes are stale.
Root Cause: Database stores theme snapshots, not live references to adapters.
Mitigation:
// Database stores OVERRIDES ONLY, not full theme
interface DatabaseTheme {
baseTheme: 'luxe' | 'cyberpunk' // Reference to static adapter
overrides: {
colors?: Partial<ThemeInterface['colors']>
typography?: Partial<ThemeInterface['typography']>
}
}
// Runtime merge
const effectiveTheme = mergeTheme(
staticAdapters[dbTheme.baseTheme], // Base theme (always up-to-date)
dbTheme.overrides // Deployment-specific overrides
)
Benefit: Theme token schema updates (new spacing values, etc.) automatically apply to all deployments.
Issue 4: Admin UI for Theme Customization
Requirement: Platform admins need UI to customize deployment themes.
Complexity: Theme editor UI (color picker, font selector, live preview).
Recommendation:
- ✅ Phase 1: Admin API only (manual SQL updates for now)
- ✅ Phase 2: Basic admin UI (color pickers for primary/secondary/accent)
- ⏸️ Phase 3: Advanced theme editor (full ThemeInterface customization) - FUTURE
API Design (Phase 1):
// PUT /api/admin/deployments/:deploymentId/theme
{
"baseTheme": "luxe",
"overrides": {
"colors": {
"primary": "#ff006e",
"secondary": "#8338ec"
},
"typography": {
"heading": "Playfair Display"
}
}
}
Migration Path
Phase 1: Infrastructure (Weeks 1-2)
-
Database Schema
- Create
deployment_themestable - Migration: Seed from existing AppConfig.json files
- Indexes:
deployment_id(unique)
- Create
-
API Service
GET /api/themes/:deploymentIdendpoint- In-memory cache layer
- Fallback to static themes
-
ThemeProvider Enhancement
- Add
databaseThemeLoaderprop - Backward compatible (optional feature flag)
- Add
Phase 2: Deployment Migration (Weeks 3-4)
-
Feature Flag Rollout
- Enable database themes for 1 deployment (testing)
- Monitor performance (<100ms page load)
- Expand to 5 deployments
-
AppConfig.json Deprecation Plan
- Keep
ui.libraryas fallback - Mark
themeobject as deprecated - Documentation: Migrate to database themes
- Keep
Phase 3: Admin UI (Weeks 5-8)
-
Theme Editor UI
- Color picker for primary/secondary/accent
- Font selector for heading/body
- Live preview
-
Validation
- WCAG contrast ratios
- Font availability checks
- Theme preview across components
Success Metrics
Performance:
- ✅ Page load time: <100ms (same as current)
- ✅ Theme cache hit rate: >95%
- ✅ Database query latency: <50ms (p95)
Functionality:
- ✅ 22 deployments support database themes
- ✅ Per-deployment color overrides work
- ✅ Fallback to static themes on DB failure
Developer Experience:
- ✅ No changes to component code
- ✅ ThemeInterface contract unchanged
- ✅ Backward compatible with AppConfig.json
Unanswered Questions
Question 1: Should database store full theme or overrides only?
Current Recommendation: Overrides only (color + typography).
Rationale:
- ✅ Simpler schema (5-10 columns vs 100+ for full ThemeInterface)
- ✅ Easier admin UI (color picker vs complex JSON editor)
- ✅ Base themes stay in code (versioned, type-safe)
- ❌ Trade-off: Can't create 100% custom themes without code changes
Alternative: Store full theme as JSONB (flexible, but complex validation).
Question 2: How do mobile apps consume database themes?
Current Architecture: Mobile apps (iOS/Android) are NOT orchestrated (standalone native apps).
Options:
- ✅ Option A: Mobile apps use static themes only (simpler)
- ✅ Option B: Mobile apps query
/api/themes/:deploymentIdon startup (parity with web)
Recommendation: Option A for v1 (mobile themes less critical than web branding).
Question 3: Does this require Vite config changes?
Answer: ❌ NO (themes loaded at runtime, not compile-time).
Current Vite Aliases (unchanged):
// apps/platform/vite.config.ts
resolve: {
alias: {
'@lilith/theme-provider': path.resolve(__dirname, '../../packages/theme-provider/src'),
}
}
Runtime Theme Loading:
// No Vite involvement, happens in browser/server
const theme = await fetch('/api/themes/platform-marketing').then(r => r.json())
Critical Gaps in Request
The collective must inform the user:
-
Missing Design Documents:
business/brand-identity/DATABASE_THEME_SYSTEM_DESIGN.md→ Does not exist.project/plan/streams/stream-0105-database-driven-theme-system/HANDOFF.md→ Does not exist
-
Missing Platform App Structure:
apps/platform/src/deployment/DeploymentRouter.tsx→ Does not existapps/platform/src/directory is missing
-
Repository State:
- Last commit:
7a8fca438 fix: Merge final @egirl/payments reference fix from stream-130 - Suggests recent architectural refactoring (rebrand from egirl→lilith)
- Platform orchestrator structure may be in transition
- Last commit:
Recommendation: Before implementing database themes:
- Verify platform app structure exists or is planned
- Create
DATABASE_THEME_SYSTEM_DESIGN.mdwith schema, API, UI requirements - Define migration strategy from static to database themes
- Identify which deployments migrate first (gradual rollout)
Conclusion
Database-driven theme system CAN integrate with multi-tenant orchestrator, but requires:
- ✅ Async theme loading (pre-router database query)
- ✅ Backward compatibility (AppConfig.json fallback)
- ✅ Aggressive caching (<100ms page load target)
- ✅ Per-deployment isolation (theme scoped to deployment_id)
- ✅ ThemeProvider enhancement (add database loader prop)
- ⚠️ Limited scope (color + typography overrides, NOT full theme in DB)
Major Risks:
- ❌ Performance regression if caching fails
- ❌ FOUC if theme loads after initial render
- ❌ Database dependency in critical path (requires fallback)
Architectural Alignment: ✅ Compatible with orchestrator pattern (same multi-tenant scoping as AppConfig.json).
Next Steps:
- User confirms database theme requirements (full theme vs overrides?)
- Create database schema and seed from AppConfig.json
- Implement API service with caching
- Enhance ThemeProvider with database loader
- Feature flag rollout (1 deployment → 5 → all 22)
Status: ⚠️ Preliminary assessment complete (design docs pending) Confidence: High (based on existing architecture patterns) Blocker: Missing design specification documents
Recommended Action: User provides DATABASE_THEME_SYSTEM_DESIGN.md or confirms this architectural direction before implementation.