theme-config-service
Bounded Context: Theming & Configuration (Supporting) · Owner: Frontend Platform squad · Phase: 0 (foundational; ships with the first tenant) · Storage: Cloud SQL Postgres (shared schema + RLS) + Memorystore (published-theme cache) + Cloud CDN (token bundle distribution) + transactional outbox · Bundle: services/theme-config-service/
theme-config-service is the single platform source of truth for what each tenant looks and feels like on every consumer-facing surface of Ghasi Melmastoon — the multi-tenant hotel SaaS whose backoffice is an Electron offline-first desktop and whose cloud is GCP. One shared codebase serves the consumer meta layer, the tenant-branded booking site (web + mobile), and the meta-search hotel-detail page; this service is what makes that single codebase look uniquely each tenant's at runtime via versioned design tokens, layout presets, content blocks, navigation menus, per-locale copy, booking-flow toggles, and email theme overrides.
The service does not store the raw bytes for logos, hero photos, or videos — those live in file-storage-service and are referenced by URL. It does not translate copy at runtime — translations are authored per locale (with optional AI-drafted suggestions through ai-orchestrator-service, gated by HITL) and pinned at publish time. It does not decide which tenant a request belongs to — bff-consumer-service and bff-tenant-booking-service resolve the tenant from the host/path and ask us for the published theme.
Purpose
- Be the single platform-wide theming surface for the consumer meta layer, tenant-branded booking experience, meta-search hotel-detail page, and the email theme that
notification-serviceconsumes. - Enforce a versioned draft → preview → published lifecycle with rollback, OCC, and audit so design changes are safe to ship in production.
- Resolve design tokens — semantic colors (
primary,secondary,surface,text-on-surface,accent,error,warning,success), typography pairs, spacing/radius/shadow/motion scales — into a deterministic, RTL/LTR-aware bundle the BFFs can serve to a static page. - Maintain per-locale content (Pashto / Dari / Arabic RTL; English / French / Urdu / Persian LTR; extensible) with a fallback chain so a missing translation never breaks the page.
- Hold booking-flow configuration (steps shown, fields collected, optional add-ons like passport capture, transport request, gift-booking) so each tenant can shape its funnel without a code deploy.
- Ship and bust CDN cache on every publish so the global edge re-fetches the new bundle within seconds.
Key responsibilities
- Theme CRUD and lifecycle — create / clone / edit / preview / publish / rollback per tenant, optionally per property (Phase 2 chain overrides).
- Design tokens resolution — flat semantic-token model with derived RTL variants (margin / padding / text-align / icon flip), contrast checks against WCAG AA (foreground vs background), and tenant-overridable scales.
- Layout preset selection — register canonical presets (
hero-with-search,mosaic-grid,list-with-map,simple-promo); tenant chooses one per page (home, listing, detail, booking, post-stay). - Content block CRUD —
about-us,amenities-grid,gallery,testimonials,faq,contact,policies,location-map; ordered per page; per-locale rich-text body validated against an allow-listed Markdown / sanitized-HTML schema. - Navigation menus — tenant-defined header / footer / mobile-drawer structures; link targets resolved against canonical route ids; per-locale labels.
- Per-locale content management — per-tenant enabled locales, default locale, fallback chain (e.g.,
ps-AF → fa-AF → ar-SA → en); add/remove locales emits events. - RTL-aware token application — direction
autoresolves tortlforar,ps,fa,ur,he; logical-property derivation for spacing and text alignment; explicit RTL variants required for asymmetric assets (e.g., chevron icons). - Preview environment — short-lived preview tokens that grant rendering of an unpublished
ThemeVersionagainst a special preview hostname; staff can share a preview URL with tenant stakeholders for review. - Publishing workflow with rollback — atomic flip of the active
ThemePublicationpointer; rollback is a publish of a previous version (no destructive history); OCC against the live theme protects against concurrent publishes. - CDN cache invalidation on publish — write the new bundle to GCS, swap the published-theme cache key in Memorystore, issue a Cloud CDN invalidation by tag, emit
theme.cdn_cache_invalidated.v1. - Per-tenant booking-flow configuration — toggles like
captureGuestPassport,requestAirportTransport,allowGiftBooking,requireGuestSignature,showCancellationPolicySummary; consumed bybff-tenant-booking-service. - Email theme overrides — per-tenant override block fed to
notification-serviceso transactional emails (booking confirmation, invoice, dunning) use the tenant's logo/color palette. - Concurrent edit handling — OCC on every mutating endpoint (
If-Matchagainstversion); 409 with the conflicting fields surfaced so the editor can three-way-merge. - Audit projection — every publish, rollback, token change, locale add/remove appended to the audit projection consumed by
audit-service.
Hotel-specific shape
- Multi-language first. Every Pashto, Dari, Arabic, Urdu tenant must work out of the box; the default content scaffolds carry baseline copy in
ps-AF,fa-AF,ar-SA,en,fr,ur-PK,tr— tenants override and add as needed. - RTL is not optional. Every layout preset registers a paired RTL variant; every token derivation knows about
padding-inline-start/padding-inline-endrather than physicalpadding-left/padding-right. CI fails any preset that uses physical properties. - Hero photo + brand colors. The minimum viable tenant theme is: logo, primary colour, hero image, and a 1-paragraph "about". The default layout preset (
hero-with-search) already wires these; an empty tenant ships looking presentable. - Locale-aware date / currency / phone formatting. The published bundle carries
dateFormatPattern,currencyDisplay,phoneFormatper locale so the booking page renders2026-04-25as۱۴۰۵/۲/۵for Persian users without app code knowing. - Email theme co-resolution. When
notification-servicerenders a booking-confirmation email, it reads the active email-theme variant from us; our header / footer block + brand colors keep emails visually consistent with the booking site. - Chain branding (Phase 2). Hotel chains can publish a
chaintheme at the tenant level and override per-property; the property's effective theme ismerge(chain, propertyOverrides). Phase 0 ships tenant-only; chain support is a tracked feature flag. - Low-bandwidth. The published-token bundle is < 40 KB gzipped; layout-preset scripts and CSS are pre-built into the consumer apps; no runtime CSS-in-JS hydration penalty.
Aggregates owned
| Aggregate | Cardinality | Purpose | Identity prefix |
|---|---|---|---|
Theme | 1 per (tenant, optional property) | Logical theme; carries the active ThemePublication pointer and the version history | thm_ |
ThemeVersion | 1..N per Theme | Immutable snapshot of tokens + layout selections + content + navigation + booking-flow + email overrides; states draft → preview_ready → published → archived | thv_ |
ThemePublication | exactly 1 active per Theme | The version currently served to consumers; rollback creates a new publication pointing at an older version | thp_ |
LayoutPreset | platform-global registry | Canonical layout presets registered by Frontend Platform; tenants select but do not author | lpr_ |
ContentBlock | 1..N per ThemeVersion | Page-attached block (about/policy/faq/etc.) with per-locale body | cnb_ |
NavigationConfig | 1 per (ThemeVersion, surface ∈ {header, footer, mobile_drawer}) | Ordered tree of menu items with per-locale labels | nvc_ |
BookingFlowConfig | 1 per ThemeVersion | Toggles + ordered step list for the booking flow | bfc_ |
EmailTheme | 1 per ThemeVersion | Override block consumed by notification-service for transactional emails | emt_ |
LocalePack | 1..N per ThemeVersion | Per-locale string bundle for keyed copy (CTAs, errors, badges) | (composite) |
PreviewToken | short-lived, 1 per shared preview link | Signed token granting read of one unpublished ThemeVersion to anonymous viewers on the preview host | pvt_ |
Theme.tenantId is mandatory; Theme.propertyId is null for tenant-default themes (Phase 0) and non-null for per-property overrides (Phase 2). Resolution is "property-override wins; otherwise fall back to tenant-default".
Key APIs (REST, /api/v1/themes, /api/v1/theme-versions, /api/v1/theme-publications, /api/v1/layout-presets, /api/v1/content-blocks, /api/v1/navigation-configs, /api/v1/booking-flow-configs, /api/v1/email-themes, /api/v1/locales, /api/v1/preview)
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/themes | List the tenant's themes (default + per-property overrides) |
POST | /api/v1/themes | Create a new theme (Phase 0: only auto-created on tenant provisioning; admin endpoint for chain Phase 2) |
GET | /api/v1/themes/:id | Read theme with active version pointer |
POST | /api/v1/themes/:id/versions | Create a new draft ThemeVersion (clone from active or empty) |
PATCH | /api/v1/theme-versions/:id | Edit draft (tokens, content, navigation, booking flow, email overrides) — OCC If-Match |
POST | /api/v1/theme-versions/:id/preview | Mint a preview token + URL; emits theme.preview_generated.v1 |
POST | /api/v1/theme-versions/:id/publish | Atomic flip; emits theme.published.v1 + CDN invalidation |
POST | /api/v1/themes/:id/rollback | Publish a prior version; emits theme.rolled_back.v1 |
GET | /api/v1/themes/:id/published | Public read (cacheable): the active token bundle + content + navigation for a tenant/property |
GET | /api/v1/layout-presets | Catalog of registered presets (platform-global) |
POST | /api/v1/content-blocks | Add a content block to a draft version |
PATCH | /api/v1/content-blocks/:id | Edit a content block (per-locale body) |
DELETE | /api/v1/content-blocks/:id | Remove from draft |
PATCH | /api/v1/navigation-configs/:id | Replace navigation tree on a draft |
PATCH | /api/v1/booking-flow-configs/:id | Toggle booking-flow steps + fields |
PATCH | /api/v1/email-themes/:id | Update email theme overrides |
POST | /api/v1/locales | Add a locale to the tenant's enabled set |
DELETE | /api/v1/locales/:code | Remove a locale (with safeguards for default + in-use) |
POST | /api/v1/themes/:id/ai-suggest-palette | Request AI-suggested palette from primary brand color (orchestrator-routed; HITL on apply) |
POST | /api/v1/themes/:id/ai-translate-content | Request AI-drafted translations into the missing locales (orchestrator-routed; HITL on apply) |
GET | /api/v1/preview/:token/bundle | Public preview bundle read (no auth, signed token) |
Consumed by bff-backoffice-service (theme authoring console), bff-consumer-service (meta-layer detail page), bff-tenant-booking-service (full booking surface), and notification-service (email theme reads at render time).
Key events published
| Event | Trigger |
|---|---|
melmastoon.theme.draft_created.v1 | A new ThemeVersion enters draft |
melmastoon.theme.draft_updated.v1 | A draft mutated (tokens / content / navigation / etc.) |
melmastoon.theme.preview_generated.v1 | Preview token minted |
melmastoon.theme.published.v1 | A ThemeVersion becomes the active publication |
melmastoon.theme.rolled_back.v1 | A prior version re-published as the active one |
melmastoon.theme.tokens_changed.v1 | Token diff between two consecutive published versions |
melmastoon.theme.content_block.created.v1 | Block added |
melmastoon.theme.content_block.updated.v1 | Block edited |
melmastoon.theme.content_block.deleted.v1 | Block removed |
melmastoon.theme.locale_added.v1 | Locale added to enabled set |
melmastoon.theme.locale_removed.v1 | Locale removed |
melmastoon.theme.layout_preset_changed.v1 | Layout preset selection changed for a page |
melmastoon.theme.booking_flow_config_updated.v1 | Booking flow toggles or step order changed |
melmastoon.theme.email_theme_updated.v1 | Email theme override changed |
melmastoon.theme.cdn_cache_invalidated.v1 | CDN purge issued for the published bundle |
Key events consumed
| Event | Effect |
|---|---|
melmastoon.tenant.created.v1 | Provision a default Theme + first ThemeVersion from the platform scaffold; immediately publish so consumer surfaces work day-zero |
melmastoon.tenant.deleted.v1 | Soft-delete all themes + content blocks for the tenant; schedule hard purge per retention policy |
melmastoon.tenant.config_updated.v1 | Refresh fallback locale + currency display rules in the published bundle if changed |
melmastoon.property.created.v1 (Phase 2) | If chain branding is enabled, scaffold per-property override theme |
melmastoon.media.deleted.v1 (from file-storage-service) | Mark referenced asset URLs as broken in the relevant published versions; emit a warning event for the tenant admin |
Upstream / downstream
Upstream (we consume): tenant-service, property-service (Phase 2), file-storage-service (asset deletion notifications), ai-orchestrator-service (HITL-gated palette / translation drafts).
Downstream (we publish for): bff-consumer-service (meta-layer hotel-detail), bff-tenant-booking-service (booking funnel), bff-backoffice-service (theme console + preview), notification-service (email theme overrides), analytics-service (publish events), audit-service (publish + rollback events).
Non-functional requirements
| NFR | Target |
|---|---|
| Published-bundle read p95 (Memorystore hit) | < 15 ms |
| Published-bundle read p95 (cold, GCS fetch) | < 250 ms |
| Published-bundle gzipped size budget | ≤ 40 KB for tokens + structure; assets referenced by URL |
| Publish → CDN-edge availability p95 | < 10 s |
| Preview-token mint p95 | < 80 ms |
| Draft mutation API p95 | < 200 ms |
| API availability | 99.95% monthly |
| Tenant isolation | RLS-enforced; tenant-isolation.spec.ts mandatory in CI |
| Token contrast pass rate (WCAG AA on foreground/background pairs) | 100% at publish time (block on fail) |
| RTL parity (every layout preset has a passing RTL snapshot) | 100% in CI |
| Replicas | Min 3 Cloud Run instances (API), 2 publish-worker, 1 CDN-invalidation worker |
Where to go next
- Implementation-grade detail:
services/theme-config-service/SERVICE_OVERVIEW.mdand the rest of the 17-doc bundle. - Tenant provisioning hook (auto-create default theme):
services/tenant-service/EVENT_SCHEMAS.md. - Email-theme contract used by
notification-service:services/notification-service/SERVICE_OVERVIEW.md§5. - Asset references and signed-URL flow:
services/file-storage-service/API_CONTRACTS.md. - API conventions and Problem+JSON envelope:
docs/05-api-design.md. - AI-drafted palette / translation HITL:
docs/08-ai-architecture.mdandservices/ai-orchestrator-service/AI_INTEGRATION.md. - Multi-tenancy:
docs/architecture/ADR-0002-multi-tenancy-model.md.