Skip to content

Themes Module

The Themes module manages user-authored UI themes — bundles of CSS-variable overrides plus optional component-token tweaks — with a strict draft-vs-published lifecycle and per-publish versioning. A theme starts life as a draft owned by the creating user (visible only to them); publishing freezes the current config into an immutable published snapshot with a version number. Drafts can be edited freely; published snapshots are write-once. The web client’s ThemeContext and the @blobs/sdk-solid ThemeRegistry both consume published snapshots.

Class Diagram

uses

uses

caches

queries

Controller

-service: Service

-logger: Logger

+Create(w, r)

+GetByID(w, r)

+List(w, r)

+Update(w, r)

+Delete(w, r)

+Unpublish(w, r)

+Publish(w, r)

Service

-repo: Repository

-cache: Cache

-logger: Logger

+Create()

+GetByID()

+ListByUser()

+Update()

+Delete()

Repository

-db: Database

+Query()

+Insert()

+Update()

+Delete()

Cache

Database

Request Flow

  1. Create / Update → Controller validates input; Service writes a draft theme row.
  2. Publish → PublishTheme snapshots the current draft into a versioned record. Major-version flag bumps vN+1.0; minor bumps vN.M+1. Older published versions remain queryable for clients pinned to them.
  3. Unpublish → UnpublishTheme deletes the published-snapshot row by id, leaving the source draft untouched. The path id is the published row’s id (the value returned by Publish). Used by the SDK’s PublishThemeAction to provide undo support.
  4. List → ListByUser returns paged results (limit/offset) along with total count for page navigation.
  5. Delete → Soft-deletes the theme; published versions stay accessible to existing consumers until manually purged.

Key Components

Controller

Standard REST surface plus a dedicated Publish endpoint that takes an isMajorVersion boolean.

Service

  • Versioning lives here, not in the Repository — PublishTheme orchestrates the snapshot/version-row creation as a single transaction.
  • Publishing invalidates any cached “current published theme” entries so clients see the new version on next fetch.

Repository

Two related tables: themes (the editable draft per user) and theme_versions (immutable snapshots). InsertVersion is called transactionally with the themes update during publish.

Special Behaviors

Drafts are private; published snapshots are public

Drafts are visible only to the owning user — listings filter by owner_id for non-published rows. Published snapshots are world-readable; that’s how the SDK’s ThemeRegistry can resolve them on a logged-out home page. Clients can pin to a specific version (v1.2.0) so a publish doesn’t surprise users mid-session.

Major vs. minor version bumps

PublishTheme(themeId, isMajorVersion):

  • isMajorVersion: true → v(N+1).0
  • isMajorVersion: false → vN.(M+1)

The SDK’s PublishThemeAction exposes this as a single boolean. Older versions remain queryable for clients pinned to them.

Unpublish is a per-snapshot delete, not a draft revert

UnpublishTheme(publishedId) removes exactly the published-snapshot row identified by publishedId (returned by Publish). The source draft is untouched. This is what makes PublishThemeAction.undo clean: it knows the exact id to remove, not “the most recent publish for theme X.”

Cache invalidation on publish

Publishing busts the cached “current published theme” entry keyed by user id. The next read repopulates it from the new snapshot. There’s no eventual-consistency window — the cache is invalidated synchronously in the same transaction.

SDK Surface

The web client’s @blobs/sdk-solid package layers a ThemeRegistry + ThemeLoader on top of this — see ThemeConfig for the structured shape the registry works with.

Cross-Module Integration

ModuleRelationship
UsersEach theme has a single owner; deletes cascade.
SettingsPer-user “active theme” preference is stored in user_settings.

API Reference

See the interactive API reference /themes tag for live examples.