feat(design): the panel now asks whether the app agrees with its own sheet
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 58s
CI & Build / Build & push image (push) Successful in 43s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 58s
CI & Build / Build & push image (push) Successful in 43s
Retiring rulebook #2 left the /design drift panel with no data source, and because its empty state was well-written the feature read as working while it could only ever render "nothing designated" (#2419). The original question is genuinely gone: theme.css is generated from design system 2, so checking the system against a sheet derived from it would be a tautology. The question that survives is the one no server can answer. A generated sheet still has to be LOADED and APPLIED, and nothing checked that it was: absent the record declares a token the app doesn't have — the sheet was never regenerated after the record changed, or never loaded differs the app has it with another value — a stale sheet, or a later rule that overrode it unrecorded the app declares a token in the record's own family that the record has never heard of Both sides go through the same engine so the comparison is honest: declared values are set on an offscreen probe and read back, which performs the same var() substitution the browser already did to the live values. Comparing raw strings would mark every derived token as drift. The designation moved with the feature — design_rulebook_id becomes ui_design_system_id, with a migration deleting the retired key rather than leaving an inert row. The prose extractor it fed goes too (#2288 said its runtime role ended when the import landed). Three orphans of the same shape, found alongside and fixed here: - darkOverriddenNames hardcoded [data-theme="dark"]. The sheet went dark-first months ago, so it matched nothing and the "mode-aware" flag silently left the gallery. Now matches the SHAPE of a mode selector, which also holds for an install whose modes aren't light and dark. - groupFor's prefix table never heard of --fs-, so 110 tokens sat under "other". Groups now come from the record where there is one; the table can only know families that shipped with the product (rule #115). - The type scale was a hand-written table of nine sizes marked "no token", true when written and false since the scale was recorded. Now rendered from whatever size tokens the sheet declares, so it can't go stale twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
This commit is contained in:
@@ -37,6 +37,7 @@ from scribe.services.design_cascade import (
|
||||
resolve_tokens,
|
||||
would_cycle,
|
||||
)
|
||||
from scribe.services.settings import get_setting
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -134,6 +135,40 @@ async def get_design_system(user_id: int, design_system_id: int) -> DesignSystem
|
||||
return system
|
||||
|
||||
|
||||
# Which design system this install's own UI is built from. A plain setting
|
||||
# rather than a column: no migration, discoverable in the Settings UI (rule
|
||||
# #25), and honest about being a per-install claim rather than a property of the
|
||||
# system — the same system can be the record for an app that never loads it.
|
||||
UI_DESIGN_SYSTEM_SETTING = "ui_design_system_id"
|
||||
|
||||
|
||||
async def ui_design_system(user_id: int) -> tuple[int | None, DesignSystem | None]:
|
||||
"""The design system this install says its UI is built from.
|
||||
|
||||
Returns `(id, system)`. Three outcomes, deliberately distinguishable:
|
||||
|
||||
- `(None, None)` — nothing designated. The NORMAL state for any install but
|
||||
the one that set it up (rule #115), not an error.
|
||||
- `(id, None)` — designated, but gone or not readable by this caller. A
|
||||
misconfiguration worth naming rather than silently degrading to "none",
|
||||
which is exactly the failure that orphaned the panel this feeds (#2419).
|
||||
- `(id, system)` — designated and readable.
|
||||
|
||||
A non-numeric setting value reads as nothing designated: the value is only
|
||||
ever written by a `<select>` of real ids, so garbage here means hand-edited
|
||||
or stale, and refusing to guess is better than raising on a page load.
|
||||
"""
|
||||
raw = (await get_setting(user_id, UI_DESIGN_SYSTEM_SETTING, "")).strip()
|
||||
if not raw:
|
||||
return None, None
|
||||
try:
|
||||
system_id = int(raw)
|
||||
except ValueError:
|
||||
logger.warning("Ignoring non-numeric %s: %r", UI_DESIGN_SYSTEM_SETTING, raw)
|
||||
return None, None
|
||||
return system_id, await get_design_system(user_id, system_id)
|
||||
|
||||
|
||||
async def list_design_systems(user_id: int) -> list[DesignSystem]:
|
||||
"""The caller's own systems, ordered by title. Empty is normal."""
|
||||
async with async_session() as session:
|
||||
|
||||
Reference in New Issue
Block a user