════════════════════════════════════════════════════════════════════════════
 JQC — phase48: Modern web portal design (A/B test with user switch)
════════════════════════════════════════════════════════════════════════════

WHAT THIS DOES
──────────────
Adds a second, complete web portal design (sidebar shell, teal-blue palette
from JQC_design.pptx) alongside the existing one. Each user picks which design
they see; the choice is stored on their account and doubles as their vote.

The classic design is byte-for-byte unchanged apart from one added menu item
("Try the New Design"). No route, endpoint, function, model or column was
renamed, and no functionality was removed.


HOW IT WORKS (root of the design, not a workaround)
───────────────────────────────────────────────────
1. `app/templates/base.html` used to hold the entire page chrome. That markup
   moved verbatim to `app/templates/layouts/classic.html`.

2. `base.html` is now a single line — `{% extends jqc_layout %}`. Jinja resolves
   `{% block %}` overrides through the whole inheritance chain, so all 69 page
   templates keep `{% extends "base.html" %}` and needed ZERO edits.

3. `jqc_layout` is supplied by the new `inject_ui_theme()` context processor in
   `app/__init__.py`, driven by the new `users.ui_theme` column
   ('classic' | 'modern').

4. Pages whose layout genuinely differs in the deck get an override file under
   `app/templates/modern/<same path>.html`. `ThemedEnvironment.get_template()`
   (app/__init__.py) swaps `dashboard.html` → `modern/dashboard.html` only when
   `g.jqc_theme == 'modern'`. The swap happens in `get_template()` rather than
   in the loader **on purpose**: Jinja's template cache is keyed on the name
   that `get_template()` receives, so a cached modern template can never be
   served to a classic user or vice versa. A loader-level swap would have that
   bug.

5. Every other page renders its existing markup inside the modern shell and is
   restyled by `static/css/theme_modern.css`, which loads after `theme.css` and
   is scoped to `body.jqc-modern`. Classic pages never load that file.


FILES — PLACEMENT MAP
─────────────────────
NEW
  app/templates/layouts/classic.html        ← old base.html verbatim + one
                                              "Try the New Design" menu item in
                                              the user dropdown
  app/templates/layouts/modern.html         ← new sidebar shell (top bar, search,
                                              bell, avatar, sidebar nav, switch)
  app/static/css/theme_modern.css           ← modern skin, scoped to .jqc-modern
  app/routes/ui.py                          ← blueprint `ui`
                                              POST /ui/theme          switch_theme()
                                              GET  /ui/about          about()
                                              GET  /ui/support-center support_center()
                                              GET  /ui/theme-votes    theme_votes()  (admin)
  app/templates/modern/dashboard.html       ← deck slide 1
  app/templates/modern/facilities/list.html ← deck slide 5 (hub cards + the
                                              original list, unchanged, below)
  app/templates/ui/about.html               ← new About Us page
  app/templates/ui/support_center.html      ← deck slide 6 support hub
  app/templates/ui/theme_votes.html         ← admin vote tally
  migrations/versions/phase48_user_ui_theme.py
  migrations/versions/0003_add_user_active.py
      NO-OP stub. Repairs a PRE-EXISTING break in the Alembic revision graph:
      phase1_projects_roles.py declares down_revision = '0003_add_user_active'
      but that script is not in the repo (the early 0001-0003 files were lost).
      Alembic warns while walking the graph but raises KeyError as soon as it
      builds the full revision map, which any `flask db upgrade <target>` does.
      The stub restores the node with down_revision = None and empty
      upgrade()/downgrade(). No schema effect. Do not delete it.

MODIFIED
  app/templates/base.html
      Entire file replaced by the one-line dispatcher (old content now lives in
      layouts/classic.html).
  app/models/user.py
      class User — added `ui_theme` column after `active`.
      VARCHAR(16) NOT NULL DEFAULT 'classic'.
  app/__init__.py
      + `ThemedEnvironment` class above create_app()
      + `app.jinja_environment = ThemedEnvironment` as the FIRST statement in
        create_app() (must precede any touch of app.jinja_env — it is a cached
        property)
      + modern-template index built at boot, before_request `resolve_ui_theme()`,
        context processor `inject_ui_theme()` (also exposes `now_display`)
      + `from app.routes import ui` and `app.register_blueprint(ui.bp)`


DEPLOY — STEP 1: CODE
─────────────────────
  cd /home/jqc/janitorial_qc
  # back up the two files being replaced wholesale
  cp app/templates/base.html /tmp/base.html.bak
  cp app/__init__.py         /tmp/__init__.py.bak

  # unzip the package over the repo root (paths already match)
  unzip -o jqc_phase48_modern_design.zip -d /home/jqc/janitorial_qc

  chown -R jqc:jqc /home/jqc/janitorial_qc/app

DEPLOY — STEP 2: MIGRATION  (run separately, after the code is in place)
───────────────────────────────────────────────────────────────────────
  cd /home/jqc/janitorial_qc
  source venv/bin/activate          # adjust if your venv path differs
  flask db upgrade

  Expect: phase47_sched_acknowledged → phase48_user_ui_theme
  With the 0003 stub in place, `flask db heads` reports exactly one head
  (phase48_user_ui_theme) and no "Revision ... is not present" warning.
  The migration uses an INFORMATION_SCHEMA existence check and an idempotent
  backfill — safe to re-run.

  Verify:
    mysql -e "SHOW COLUMNS FROM users LIKE 'ui_theme';" janitorial_qc

DEPLOY — STEP 3: RESTART
────────────────────────
  sudo systemctl restart jqc          # or your unit name
  journalctl -u jqc -n 40 --no-pager

  Look for:  "UI themes | modern overrides indexed: 2"

  No Nginx change is required — no new external host, no CSP change.


VERIFICATION
────────────
 1. Log in. Portal looks exactly as before (everyone starts on classic).
 2. Account menu (top right) → "Try the New Design" → same page reloads in the
    sidebar design, flash message confirms.
 3. Dashboard: 4 KPI tiles + Inspection / Open Issues / SLA Issues cards +
    Scheduled + Recent Activities. Click each number — it lands on the same
    filtered list the classic dashboard links to.
 4. Facility: 4 hub cards, then the full grouped facility list underneath.
    Add Facility / Print All QR / Delete modal all still work.
 5. Sidebar → Supports and About Us render.
 6. Bell icon: badge count and dropdown behave as on classic.
 7. Sidebar → "Classic Design" button (or account menu) → returns to classic.
 8. Log out and back in — the design choice persists.
 9. Admin account menu → "Design Vote Tally" shows the split.
10. Audit Trail shows UPDATE / User / "ui_theme=classic→modern" for each switch.
11. Narrow the browser below 992px — the sidebar becomes an off-canvas drawer
    behind the hamburger.


ROLLBACK
────────
Fastest (no deploy): reset everyone to classic —
    mysql -e "UPDATE users SET ui_theme='classic';" janitorial_qc
The modern design becomes unreachable; nothing else changes.

Full rollback:
    cp /tmp/base.html.bak   app/templates/base.html
    cp /tmp/__init__.py.bak app/__init__.py
    rm -rf app/templates/layouts app/templates/modern app/templates/ui \
           app/static/css/theme_modern.css app/routes/ui.py
    flask db downgrade phase47_sched_acknowledged
    sudo systemctl restart jqc


KNOWN SCOPE LIMITS (deliberate)
───────────────────────────────
• Deck slides 2 (Reports), 3 (Inspections) and 4 (Issues) are NOT rebuilt as
  separate templates. Their existing structure already matches the deck
  (title + subtitle, filter row, KPI row, cards, table) and theme_modern.css
  restyles them — dark-teal table headers, pill filters, rounded cards. Building
  parallel copies of those three templates would duplicate several hundred lines
  of filter/permission/export logic and double the maintenance surface during a
  vote. Say the word after the vote and I will rebuild whichever ones you keep.
• Slide 3's "Scheduled Inspection In Progress" panel appears on the modern
  DASHBOARD (where the route already supplies that data). Putting it on the
  Inspections page as well needs an additive query in `inspections.index` —
  small, but it is a route change, so it is not in this package.
• The deck's "Overall Score" and "Avg. Score" KPI tiles are not on the modern
  dashboard: `dashboard.index` does not compute either value today. Adding them
  means new aggregate queries in the route — flag it and I will send that
  separately.
• "Customize" on the Facility hub points at Templates (inspection templates),
  the closest existing feature. There is no facility field/tag configuration
  screen in the app yet.
