Skip to content

feat(landing): persist per-tenant LandingTheme + branding endpoint (Phase 2)#202

Merged
saltyskip merged 1 commit into
mainfrom
feat/tenant-landing-theme
Jun 15, 2026
Merged

feat(landing): persist per-tenant LandingTheme + branding endpoint (Phase 2)#202
saltyskip merged 1 commit into
mainfrom
feat/tenant-landing-theme

Conversation

@saltyskip

Copy link
Copy Markdown
Owner

What & why

Phase 1 (#201) shipped the Default template + palette engine but with no persistence — every tenant rendered Rift defaults (teal). Phase 2 adds the missing tenant-level branding layer so a tenant's landing pages can match their brand. Cascade is now Tenant.landing_theme → Rift default (per-link social_preview content still overlays in the renderer).

This is what unblocks making e.g. the orangerock tenant orange.

Changes

  • TenantDoc.landing_theme: Option<LandingTheme> — mirrors the default_redirect_mode precedent; old docs deserialize to None.
  • TenantsRepository::set_landing_theme ($set) + mock impls.
  • TenantsServiceupdate_landing_theme / get_landing_theme, with validation in the service layer (reuses core::validation hex/url/cta validators + brand_name/tagline length caps) so REST and a future MCP tool share one enforcement path. New TenantError enum (Invalid→400, Storage→500).
  • GET/PUT /v1/tenant/branding (api/auth/tenants) behind SESSION | SECRET auth; thin handlers, #[tracing::instrument]. Registered in OpenAPI with LandingTheme + its enums.
  • Resolve path loads the tenant theme (resolve_landing_theme), resilient to missing repo/None → defaults.
  • tenants_service wired onto AppState (it was already constructed in main.rs).

API

PUT /v1/tenant/branding        # full replace; body = LandingTheme
GET /v1/tenant/branding        # stored theme or Rift defaults

Tests

  • Integration: PUT branding → resolve renders the brand (name, tagline, CTA, derived --accent); GET returns defaults when unset; invalid hex → 400.
  • fmt + clippy -D warnings clean. Full cargo test running post-push per the CI-ordering rule in CLAUDE.md.

Notes / open

  • OG-image hero: per feedback, featuring the OG image as a sharp hero is the wrong call (OG images are small). Pending a decision — drop it and lead with logo+color, or reuse it as a blurred ambient backdrop. Not changed in this PR.
  • Follow-ups: orphaned App.{app_name,icon_url,theme_color} removal; optional MCP tenant.set_branding tool (LandingTheme is already MCP-schema-ready).

🤖 Generated with Claude Code

…hase 2)

Phase 1 shipped the template + palette engine with no persistence, so every
tenant rendered Rift defaults. Phase 2 adds the tenant-level branding layer.

- TenantDoc gains `landing_theme: Option<LandingTheme>` (mirrors the
  default_redirect_mode precedent); old docs deserialize to None.
- TenantsRepository::set_landing_theme ($set) + mock impls.
- TenantsService::update_landing_theme / get_landing_theme, with validation in
  the service layer (reuses core::validation hex/url/cta validators + length
  caps) so both transports share one enforcement path. New TenantError enum.
- GET/PUT /v1/tenant/branding behind SESSION|SECRET auth (api/auth/tenants),
  registered in the OpenAPI spec along with LandingTheme + its enums.
- Resolve path now loads the tenant's theme (resolve_landing_theme), falling
  back to defaults; the cascade is Tenant.landing_theme -> Rift default.
- tenants_service wired onto AppState (already constructed in main.rs).

Tests: branding integration (PUT -> resolve renders the brand; GET defaults;
invalid color -> 400).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rift Ready Ready Preview, Comment Jun 15, 2026 8:25pm

Request Review

@saltyskip
saltyskip merged commit 57e449c into main Jun 15, 2026
4 checks passed
@saltyskip
saltyskip deleted the feat/tenant-landing-theme branch June 15, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant