Sentinel Signal

v1.0.656 — Customer portal (portal.sentinelsignal.io) brand redesign

Source: docs/portal-brand-redesign-v1.0.656.md

Document Content

v1.0.656 — Customer portal (portal.sentinelsignal.io) brand redesign

Context

The homepage rebrand (v1.0.654) gave sentinelsignal.io a new dark "Sentinel Signal Systems" identity. The user reported that portal.sentinelsignal.io/portal/status — and by extension the whole customer portal — still looked like the old product: a light cream theme, inconsistent per-page nav, and copy that predated the rebrand. Since the portal is directly, publicly reachable (not gated behind the marketing site), visitors landing there saw a jarring, disconnected brand. The user asked for a full redesign, not just a color swap.

What was found

  • app/main.py has no Jinja2/templating engine. 9 routes read a static file from app/ui/*.html verbatim and splice SEO <head> tags in via string .replace(). 4 routes (/portal/documents[/{slug}], /portal/blog[/{slug}]) build the entire page as a Python f-string directly in main.py.
  • No shared header/footer mechanism existed at all. Every one of the 13 app/ui/*.html files hardcoded its own <head>, its own <header class="hero"> (doubling as both hero and nav — there was no persistent navbar), and its own <footer> with a duplicated inline year-script. These had already drifted (different nav-link sets per page).
  • app/ui/styles.css (810 lines) was only lightly tokenized — a handful of :root vars plus ~34 hardcoded hex colors scattered through component rules. A pure token swap would not have fully re-themed the page.
  • Fonts already matched (Space Grotesk + IBM Plex Mono, same Google Fonts import as marketing) — just not applied consistently.
  • Three pages carry real product functionality via ID/class-selector JavaScript, not just decoration: console.html (live API console, 689-line app.js), portal-lite.html (copy-to-clipboard CLI snippet generation), and portal-lite-dashboard.html (1,014-line account-management mini-SPA — signup/login, trial-key issuance, Stripe checkout, API-key create/rotate/revoke against live control-plane/billing endpoints).

Design decision

Every element ID and CSS class selected by JavaScript stays on the exact same element, unrenamed. Everything about how those elements look — colors, fonts, spacing, borders, buttons, cards, tables, pills, badges — was fully rebuilt to the new design language, not just recolored. New chrome (navbar, footer, section dividers) was added structurally since none of it is JS-selected. This delivered the full redesign while keeping live billing/auth/console functionality byte-identical — confirmed live-testable via getElementById/querySelector checks against the real rendered response, not just visual inspection.

What shipped

Shared navbar + footer (app/main.py): two new render functions, _render_portal_navbar() and _render_portal_footer(), generate the new chrome once. _inject_portal_chrome() swaps <!--PORTAL_NAVBAR--> (inserted after <body>) and <!--PORTAL_FOOTER--> (replacing each page's old footer) markers for the rendered output, wired into the existing _portal_html_response() helper — so all 9 static-file routes and all 4 f-string routes get it automatically, no per-route changes needed beyond the marker swap in each page source. This fixed the actual drift bug: every page now shows the same nav links and the same footer (brand column with contact emails, Product/Developers/Company link columns, "SIGNAL BEFORE ACTION." tagline, legal line).

Full visual rebuild (app/ui/styles.css): new :root tokens matching the marketing palette (--bg: #0b0f14, --panel: #151e27, --signal: #e7b84b, --healthy/--warning/--critical). Every component rule that hardcoded a color was rebuilt: buttons, cards (.op-card, .docs-card, .plan-card), method pills (GET/POST/PUT/DELETE now map to healthy/signal/warning/critical), badges, code blocks, tables — all restyled to the new dark, amber-accented visual language. New shared-chrome classes (.site-header, .nav-row, .brand-mark, .nav-links, .site-footer, .footer-grid) duplicated from marketing's stylesheet (no shared-asset pipeline exists between the two separately-deployed services). The SignalLine motif (inline SVG + CSS pulse animation, prefers-reduced-motion-aware) was added as a section divider on the eight lower-risk, mostly-static pages (index, docs-home, pricing, governance, terms, privacy, mcp, mcp-demo) — skipped entirely on the three JS-heavy pages to keep their structural risk at zero.

Copy alignment (app/ui/index.html, /portal route): the portal's own hero eyebrow and page title changed from "Sentinel Signal Systems" (now redundant — the navbar carries that identity site-wide) to "Sentinel Signal Healthcare Intelligence" — this portal is that product's own app, the same relationship verify.sentinelsignal.io has to naming itself "Verify" under the parent brand. No other copy changed.

Verification

  • tests/unit/test_product_messaging.py: new tests assert the shared navbar/footer functions carry every required link/brand element, every one of the 13 static pages carries both chrome markers and no stale per-page footer, and the stylesheet uses the new design tokens.
  • Full suite run: python -m pytest tests/ -q — 321 passed, zero failures, including 3 new tests added for this change.
  • Live-rendered verification via TestClient against the real FastAPI routes (not just raw source files): all 15 portal routes return 200 with exactly one navbar/footer/year element each and no leftover unreplaced markers; every JS-dependent element ID on the three high-risk pages (console, portal-lite, portal-lite-dashboard — ~30 IDs checked) confirmed present and unchanged.

Not done / explicitly out of scope

  • Verify's own UI (a third, separately-themed dark palette — teal/cyan accents, system fonts, no shared tokens with either marketing or the portal) was identified during investigation as a real brand inconsistency but left untouched — not part of this task.
  • No change to portal page content/IA (docs, pricing, legal text, MCP quickstart) beyond the one /portal eyebrow/title alignment above — visual restyling only, per the same boundary already established for the marketing-site rebrand.
  • No new templating engine or frontend framework/build step introduced — the marker-based chrome injection extends the existing string-replace pattern already used for SEO head injection.