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
Request Flow
- Create / Update → Controller validates input; Service writes a draft theme row.
- Publish →
PublishThemesnapshots the current draft into a versioned record. Major-version flag bumpsvN+1.0; minor bumpsvN.M+1. Older published versions remain queryable for clients pinned to them. - Unpublish →
UnpublishThemedeletes the published-snapshot row by id, leaving the source draft untouched. The path id is the published row’s id (the value returned byPublish). Used by the SDK’sPublishThemeActionto provide undo support. - List →
ListByUserreturns paged results (limit/offset) along with total count for page navigation. - 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 —
PublishThemeorchestrates 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).0isMajorVersion: 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
ThemesService—create,getById,list,update,delete,publish,unpublish.Theme— wire shape including version metadata.CreateThemeRequest,UpdateThemeRequest— generated.actions/theme—CreateThemeAction,UpdateThemeAction,DeleteThemeAction,PublishThemeAction(with full undo/redo support, even for publishes).
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
| Module | Relationship |
|---|---|
| Users | Each theme has a single owner; deletes cascade. |
| Settings | Per-user “active theme” preference is stored in user_settings. |
API Reference
See the interactive API reference /themes tag for live examples.