Singleton Config Rows
How registry-wide config, theme, navigation, and DTCG data are stored as singleton database rows.
Most per-project settings a registry serves aren't a collection with many rows, they're a
single upserted row per registry, keyed by a literal id: 'default' primary key. A workspace
push replaces the whole row wholesale; there's no partial-merge semantics, so re-running
push:all is always safe to repeat.
The singleton tables
| Table | Holds | Push endpoint | Read |
|---|---|---|---|
handoff_registry_config | The app block from handoff.config.* — title, client, breakpoints, sort orders, etc. | POST /api/registry/config | GET /api/registry/config (public) |
handoff_registry_theme | Compiled theme CSS text, pushed as-is (no SCSS/Tailwind compilation on the registry, see Model). | POST /api/registry/theme | GET /api/registry/theme.css (public, compiled) |
handoff_registry_appearance | Structured UI customization set via /account/appearance (logo selection, color/font overrides) — not pushed from the workspace, edited directly in the registry. Its css column is appended after the pushed theme CSS. | — (edited in-app) | Composited into /api/registry/theme.css |
handoff_registry_navigation | The page navigation tree, derived from the workspace's pages/ structure. | POST /api/registry/navigation | Served via getDataProvider() |
handoff_registry_dtcg | The compiled DTCG manifest + css/scss/tailwind/dtcg dist output, plus the axis-aware brand/scheme token trees, see Concepts → Tokens. | POST /api/registry/dtcg | GET /api/registry/dtcg (+ /download) |
handoff_registry_icons | The full icon catalog (IconCatalogEntry[]). | POST /api/registry/icons | GET /api/registry/icons (+ /download) |
handoff_registry_logos | The full logo set (light/dark/color/mono variants). | POST /api/registry/logos | GET /api/registry/logos (+ GET /api/registry/logo.svg) |
handoff_registry_font is the one exception worth flagging: it's one row per font file
(primary key = filename), not a true singleton, because a project can push more than one font
family/weight/style combination. It's grouped with the singletons here because it follows the
same "whole-file replace via push, no versioned history" model, see
Concepts → Assets.
See API → /api/registry for the full endpoint reference,
including a few narrower endpoints outside this table (asset ingest, image-slot specs,
per-component preview management).
Why singleton rows, not versioned collections
These data types are whole-project settings, not individually-addressable entities the UI or an agent needs to look up by id; there's exactly one active theme, one active navigation tree, one active token manifest per registry at any time. Modeling them as a single upserted row keeps the push contract simple (whole-file replace) and avoids paying for change-history machinery these types don't need, unlike, say, a component, where the changelog tracks a history of pushes per entity.
updatedByUserId depends on which credential the push used
Every singleton table carries an updatedByUserId column. A push authenticated with the legacy
HANDOFF_SYNC_SECRET is treated as automation and leaves it null; a push authenticated with a
handoff-app login JWT carries the signing user's id (verifySyncAuth returns the token's
sub), so it's attributed. It's also populated when a value is changed directly in the registry
UI, e.g. handoff_registry_appearance via /account/appearance.
Auth tables aren't singleton rows
user, account, session, and the OAuth/CLI-device tables (oauth_client,
cli_device_session, …) are ordinary multi-row tables managed by NextAuth and the OAuth/device
flows, a different part of the schema from the config singletons above. See
Auth for how those work.