"""ui_settings: the 8 semantic state colors (phase 93, task 01) Revision ID: 0016 Revises: 0015 Create Date: 2026-09-10 Phase 93 (theme completion — the owner's black/white/gray theme still leaked color: the green "Theme saved." text, the red Stale/Revoked pills, the green LOCAL badge, the amber tool-call lines): the three semantic state families (``--ok-*``, ``--err-*``, ``--accent-*``) join the storable palette as 8 more tab-controlled variables (17 total — B3 revised, owner permission 2026-09-10, TODO.md L3; PLAN.md is being redone by the owner, the decision is recorded in the phase overview). They flow through the resolver, the pre-paint tag, the CSP hash, and the admin API exactly like the 9 identity colors (all of those are ``COLOR_FIELDS``-driven — zero logic changes): * ``ui_settings.ok_bg`` / ``ok_ink`` — VARCHAR(7) NULL ``#rrggbb`` (NULL = the built-ins ``#10241b`` / ``#6ee7a8`` — exact); * ``ui_settings.err_bg`` / ``err_ink`` / ``err_line`` — VARCHAR(7) NULL (NULL = ``#2d0a0a`` / ``#fca5a5`` / ``#ef4444`` — exact); * ``ui_settings.accent_bg`` / ``accent_ink`` / ``accent_line`` — VARCHAR(7) NULL (NULL = ``#2b2110`` / ``#fbbf24`` / ``#f59e0b`` — exact). EIGHT additive, fully reversible columns on the single-row table (A13); no server defaults (the row is created only by the PUT upsert, house rule). """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "0016" down_revision = "0015" branch_labels = None depends_on = None #: The 8 semantic columns (name, built-in the NULL means) — upgrade #: adds them in this order, downgrade drops them in reverse. _SEMANTIC_COLUMNS: tuple[str, ...] = ( "ok_bg", "ok_ink", "err_bg", "err_ink", "err_line", "accent_bg", "accent_ink", "accent_line", ) def upgrade() -> None: for name in _SEMANTIC_COLUMNS: op.add_column( "ui_settings", sa.Column(name, sa.String(length=7), nullable=True) ) def downgrade() -> None: # Safe order: the 8 columns are the only 0016 artefacts — dropping # them (reverse order, for symmetry) leaves 0015's schema # byte-identical (A13, fully reversible). for name in reversed(_SEMANTIC_COLUMNS): op.drop_column("ui_settings", name)