From 0af37ff27220c7a59008a82945ec9cec90971526 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 26 May 2026 12:09:42 +1000 Subject: [PATCH] style(dashboard-auth): redesign /login page to match Nous design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The login page is the first surface the user sees on a gated dashboard and shipped with off-the-shelf system fonts and a generic orange accent that didn't match the React dashboard waiting on the other side of the OAuth round trip. Apply the same visual language the SPA uses (the @nous-research/ui package) so the auth flow feels like one product, not two. What changes (visual only — no functional changes): Typography - Body: Collapse (regular + bold), served from /fonts/ — the same woff2 files the dashboard SPA loads via the design-system's fonts.css. - Display: Rules Compressed (regular + medium) for the brand wordmark and the page heading. - Brand chrome (heading, buttons, footer) uses the DS idiom: uppercase + letter-spacing 0.2em (matching the DS Button class). Colour - Background: #170d02 (deep brown-black; --background-base in DS). - Accent: #ffac02 (amber; --midground in DS). - Foreground: #ffffff. - Hairlines: color-mix() of the midground at 18% / 35%, mirroring the DS "@theme inline" derived tokens. Button surface - Solid amber surface with dark text, no rounded corners (DS Button is squared). Inset bevel — — directly mirrors the DS Button SHADOW_DEFAULT (). :active uses filter:invert(1) which matches the DS Button's . Atmosphere - Subtle 3px dither (repeating-conic-gradient at 4% midground) + a midground radial glow at top — same idioms as the DS .dither utility and the SPA's panel chrome. - slide-up fade-in entrance animation matching DS @keyframes slide-up (0.6s ease-out). Honours prefers-reduced-motion. Brand wordmark - 'NOUS · RESEARCH' above the card in Rules Compressed, amber, 0.32em tracking. Establishes ownership before the user squints at the buttons. Empty-state page - The 'Sign-in unavailable' fallback (no providers registered) got the same colour-token and typography treatment so the misconfigured-deploy experience is also coherent. Fonts are served from /fonts/*.woff2 — a path the dashboard-auth gate already allowlists pre-auth (see _GATE_PUBLIC_PREFIXES in middleware.py:42), so the login page renders with the brand typeface without needing the React bundle loaded. The page is still entirely static HTML+CSS with no JS — the original constraint (no SPA dependency, no session token) is preserved. The class="provider-btn" selector is unchanged — the existing test suite extracts the anchor href via that class, and a regression that renamed it would silently break tests/hermes_cli/test_dashboard_auth_401_reauth.py. A docstring note on the module flags this so future visual tweaks don't break the contract by accident. Visual smoke-test: rendered both the happy path (multiple providers listed) and the empty-state page in a browser and verified all five DS criteria — brown-black bg, amber accent, uppercase wide-tracking type, inset-bevel buttons, Nous · Research wordmark — render correctly with no unstyled fallbacks. 208/208 dashboard-auth tests remain green. --- hermes_cli/dashboard_auth/login_page.py | 322 +++++++++++++++++++++--- 1 file changed, 292 insertions(+), 30 deletions(-) diff --git a/hermes_cli/dashboard_auth/login_page.py b/hermes_cli/dashboard_auth/login_page.py index 2baab0fdcc6..74da4dbe2f0 100644 --- a/hermes_cli/dashboard_auth/login_page.py +++ b/hermes_cli/dashboard_auth/login_page.py @@ -3,6 +3,21 @@ No React, no JavaScript dependency. Listed providers come from the registry; clicking a provider sends a GET to ``/auth/login?provider=``. + +Visual styling mirrors the Nous Research design system (the +``@nous-research/ui`` package the React dashboard uses): the same +``Collapse`` / ``Rules Compressed`` typeface, amber-on-dark colour +tokens (``#170d02`` / ``#ffac02`` / ``#fff``), uppercase + wide-tracking +brand chrome, and the inset-bevel button shadow. Fonts are served +out of the SPA's ``/fonts/`` directory which the dashboard-auth gate +already allowlists pre-auth (see ``_GATE_PUBLIC_PREFIXES`` in +``middleware.py``), so the page renders without needing the React +bundle loaded. + +Test-stable class names: the existing test suite extracts the +``class="provider-btn"`` anchor href to walk the OAuth flow. That +class name MUST NOT change without updating +``tests/hermes_cli/test_dashboard_auth_401_reauth.py``. """ from __future__ import annotations @@ -14,6 +29,9 @@ from hermes_cli.dashboard_auth import list_providers # bundle, which we deliberately do NOT load here — the login page must # not depend on the SPA build being present or on the injected session # token. +# +# Single curly braces are placeholders for ``str.format``; CSS curlies +# are doubled (``{{`` / ``}}``). _LOGIN_HTML_TEMPLATE = """\ @@ -22,49 +40,229 @@ _LOGIN_HTML_TEMPLATE = """\ Sign in — Hermes Agent
-

Sign in to Hermes Agent

-

Choose a sign-in method to continue.

-
+
NousResearch
+
+

Sign in

+

Choose a sign-in method to continue to the Hermes Agent dashboard.

+
{provider_buttons} +
-
This dashboard is bound to a non-loopback host.
- Sign-in is required for security.
+
+ Public bind · Auth required +
@@ -75,16 +273,80 @@ _EMPTY_HTML = """\ + Sign-in unavailable — Hermes Agent + -
+ +

Sign-in unavailable

This dashboard is bound to a non-loopback host but no authentication providers are installed.

Install plugins/dashboard-auth-nous (default) or another auth provider, or restart with --insecure to bypass the auth gate (not recommended on untrusted networks).

-
+
+ + """ @@ -115,7 +377,7 @@ def render_login_html(*, next_path: str = "") -> str: buttons = [] for p in providers: buttons.append( - f' ' f'Sign in with {html.escape(p.display_name)}' )