Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Feature ID: PF-95
Status: ✅ Complete (Phase 1–3; Phase 4 email branding deferred)
Spec: PF-95-tenant-white-labeling-theming.md (v1.2.0) Last Updated: 2026-03-25

Overview

PF-95 adds per-tenant visual theming (CSS custom properties), optional pf_organizations.subdomain, subdomain_verified, prefer_subdomain_redirect, and coordination with email branding. It extends the platform foundation without introducing cross-core imports. Consumers use @/platform/theming (planned) for theme resolution and CSS variable injection.

Integration Points (from Spec)

PointTypeDocument / Spec
PF-01 (Organizations)Datapf_organizations; subdomain, subdomain_verified, prefer_subdomain_redirect
PF-74 (Org URL Routing)Platform internalImplements Canonical redirect matrix (R1–R5) on primary host; path org routing unchanged
PF-77 (Tenant Domain Management)Platform / externalVercel host + SSL; custom apex domain out of scope for PF-95
PF-81 (Multi-Tenant Host & URL Strategy)ReferenceStrategy umbrella; normative redirect table lives in PF-95 spec
PF-86 (Email Signatures)Platform LayerTheme color tokens for HTML notification templates (Phase 4)
PF-77 / VercelExternal infraSubdomain SSL and hostname configuration

Platform Layer / Event / API Contracts

  • Platform layer (planned): @/platform/themingTenantThemeProvider, useTenantTheme, useTenantThemeForLogin (or equivalent) calling RPCs below for pre-auth, shared types aligned with pf_tenant_themes rows.
  • Pre-auth (database): Call pf_public_login_branding_by_subdomain(subdomain_label) when the request host is a tenant subdomain; call pf_public_login_branding_by_slug(slug) when resolving branding from PF-74 path slug (e.g. login with org context). Do not expose anonymous SELECT on pf_tenant_themes. Functions return a fixed JSON projection (login-safe fields only); see spec Data Model.
  • Authenticated: Standard Supabase CRUD on pf_tenant_themes with RLS; org subdomain flags updated via existing org update patterns and pf.subdomain.manage.
  • Event contracts: None planned for MVP.
  • Single active theme: Partial unique index (organization_id) WHERE is_active + BEFORE trigger to deactivate sibling rows (spec DDL).

Canonical redirect matrix (normative summary)

Eligibility: subdomain IS NOT NULL and subdomain_verified = true and prefer_subdomain_redirect = true.
RuleRequestAction
R1Primary app host + /o/{slug}/…If org for slug meets eligibility → 308 to https://{subdomain}.{apex}/o/{slug}/… (preserve path + query).
R2Tenant subdomain host + /o/{slug}/…No redirect.
R3Primary host, path without /o/:slugNo PF-95 redirect.
R4Unknown subdomain hostNo PF-95 redirect (platform default).
R5Wrong subdomain host for resolved org (optional)308 to canonical tenant host (recommended; PF-74 may defer with implementation log).
Full text and notes (308 vs 302, loop prevention, PF-77 boundary) are in the spec section Canonical redirect matrix (PF-74 / PF-81). PF-74 owns implementation (middleware, edge, or client bootstrap — PF-74 choice).

Security & Tenancy

  • All theme rows scoped by organization_id; RLS via pf_has_org_access (SECURITY DEFINER helper; no inline role-assignment queries in policies).
  • Permission keys (PF-30): pf.theme.view, pf.theme.manage, pf.subdomain.manage — constants + seeds with feature UI.
  • No arbitrary CSS/HTML injection; colors validated to safe formats; assets via approved upload paths.
  • Pre-auth: Only pf_public_login_branding_* RPCs; fixed column allowlist; search_path pinned inside functions.
  • Redirects: Gated on subdomain_verified to avoid redirect loops to hosts without valid TLS.