Aug 5 - Add new design (switchable)

This commit is contained in:
2026-08-05 14:07:38 -04:00
parent 07379bd915
commit f92d8783bf
15 changed files with 2899 additions and 568 deletions
@@ -0,0 +1,44 @@
"""0003 — placeholder for the missing early revision `0003_add_user_active`
WHY THIS FILE EXISTS
────────────────────
`phase1_projects_roles.py` declares `down_revision = '0003_add_user_active'`,
but no migration script with that revision id exists in migrations/versions/.
The early 00010003 scripts were lost from the repository at some point; the
schema changes they made are long since applied in every environment (the
`users.active` column exists), so nothing is missing from the database — only
the node in Alembic's revision graph.
Alembic tolerates that as a warning while it walks the graph, but as soon as it
has to build the full revision map (which any `flask db upgrade <target>` does),
it fails hard:
KeyError: '0003_add_user_active'
This file restores the node so the chain resolves. It is intentionally a NO-OP:
it makes no schema change in either direction, and it declares itself the base
of the chain (`down_revision = None`) because the revisions below it no longer
exist as files.
It never runs in practice — every environment's alembic_version is already far
past it — but if it ever did, upgrade() and downgrade() do nothing, so it is
safe either way.
DO NOT DELETE. Removing it re-breaks `flask db upgrade`.
"""
revision = '0003_add_user_active'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# No-op: the schema change this revision originally made (users.active)
# is already present in every environment.
pass
def downgrade():
# No-op: nothing to reverse.
pass
@@ -0,0 +1,51 @@
"""phase48 — per-user web portal design preference
Adds to `users`:
ui_theme VARCHAR(16) NOT NULL DEFAULT 'classic'
Drives the design A/B test: 'classic' renders the original top-navbar shell
(layouts/classic.html), 'modern' renders the new sidebar shell
(layouts/modern.html). Every existing account starts on 'classic', so the
portal looks and behaves exactly as before until a user opts in.
The value doubles as the user's vote — /ui/theme-votes tallies it.
Uses an INFORMATION_SCHEMA existence check — safe to re-run.
"""
revision = 'phase48_user_ui_theme'
down_revision = 'phase47_sched_acknowledged'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def _column_exists(conn, table, column):
return conn.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() "
"AND TABLE_NAME = :t AND COLUMN_NAME = :c"
), {"t": table, "c": column}).scalar() > 0
def upgrade():
bind = op.get_bind()
if not _column_exists(bind, 'users', 'ui_theme'):
op.execute(sa.text(
"ALTER TABLE users "
"ADD COLUMN ui_theme VARCHAR(16) NOT NULL DEFAULT 'classic'"
))
# Idempotent backfill for any row that predates the DEFAULT.
op.execute(sa.text(
"UPDATE users SET ui_theme = 'classic' "
"WHERE ui_theme IS NULL OR ui_theme NOT IN ('classic', 'modern')"
))
def downgrade():
bind = op.get_bind()
if _column_exists(bind, 'users', 'ui_theme'):
op.execute(sa.text("ALTER TABLE users DROP COLUMN ui_theme"))