hermes-agent/web
Austin Pickett 0bbf325a8f
fix(dashboard): scope chat sidebar model card to selected profile (#46665)
* fix(dashboard): scope chat sidebar model card to selected profile

The PTY already honors ?profile= on profile switch, but the JSON-RPC
sidecar created sessions against the dashboard launch profile. Pass the
management profile through session.create and reconnect on switch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(dashboard): sync active profile with management scope

Align the sidebar switcher with the sticky active profile on load and
when "Set as active" is clicked, so Chat and management pages match
what the Profiles page shows as active.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(dashboard): auto-reconnect chat sidebar on profile switch

Bump the sidecar connection version when profile or PTY channel changes,
matching the manual Reconnect path so gateway and events sockets come
back without clicking the error banner.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(dashboard): prevent model selector chevron overlapping label

Use inline flex layout instead of Button suffix, which is absolutely
positioned and overlapped truncated model names at px-0.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 12:50:19 -04:00
..
public feat(web): add /api/pty WebSocket bridge to embed TUI in dashboard 2026-04-24 10:51:49 -04:00
src fix(dashboard): scope chat sidebar model card to selected profile (#46665) 2026-06-15 12:50:19 -04:00
eslint.config.js feat: web UI dashboard for managing Hermes Agent (#8756) 2026-04-12 22:26:28 -07:00
index.html feat(web): mobile dashboard UX polish (#28127) 2026-05-18 15:20:31 -04:00
package.json fix(cli,deps): clear esbuild audit loop 2026-06-15 06:18:27 -07:00
README.md refactor(web): dashboard typography & contrast pass 2026-05-22 19:50:32 -07:00
tsconfig.app.json change(tooling): typecheck in CI, update ts to 6 2026-06-10 11:59:34 -04:00
tsconfig.json feat: web UI dashboard for managing Hermes Agent (#8756) 2026-04-12 22:26:28 -07:00
tsconfig.node.json feat: web UI dashboard for managing Hermes Agent (#8756) 2026-04-12 22:26:28 -07:00
vite.config.ts feat(dashboard): always enable embedded chat; remove dashboard --tui flag 2026-06-04 03:03:35 -07:00

Hermes Agent — Web UI

Browser-based dashboard for managing Hermes Agent configuration, API keys, and monitoring active sessions.

Stack

  • Vite + React 19 + TypeScript
  • Tailwind CSS v4 with custom dark theme
  • shadcn/ui-style components (hand-rolled, no CLI dependency)

Development

# Start the backend API server
cd ../
python -m hermes_cli.main web --no-open

# In another terminal, start the Vite dev server (with HMR + API proxy)
cd web/
npm install
npm run dev

Open the Vite URL printed in the terminal (usually http://localhost:5173). That is the live-reload UI.

hermes dashboard on port 9119 serves the built bundle from hermes_cli/web_dist/, not the Vite dev server — changes in web/src/ will not appear there until you run npm run build and restart the dashboard (or use web --no-open + Vite as above).

The Vite dev server proxies /api requests to http://127.0.0.1:9119 (the FastAPI backend).

Build

npm run build

This outputs to ../hermes_cli/web_dist/, which the FastAPI server serves as a static SPA. The built assets are included in the Python package via pyproject.toml package-data.

Structure

src/
├── components/ui/   # Reusable UI primitives (Card, Badge, Button, Input, etc.)
├── lib/
│   ├── api.ts       # API client — typed fetch wrappers for all backend endpoints
│   └── utils.ts     # cn() helper for Tailwind class merging
├── pages/
│   ├── StatusPage   # Agent status, active/recent sessions
│   ├── ConfigPage   # Dynamic config editor (reads schema from backend)
│   └── EnvPage      # API key management with save/clear
├── App.tsx          # Main layout and navigation
├── main.tsx         # React entry point
└── index.css        # Tailwind imports and theme variables

Typography & contrast rules

Read before adding or editing UI styles. These rules keep the dashboard legible across all built-in themes and stop drift back into the patterns the design system was just refactored out of.

Text size floor

  • Minimum body size: text-xs (12px / 0.75rem). Do not use arbitrary text-[0.6rem], text-[0.65rem], text-[9px], text-[10px], or text-[11px] on copy, hints, labels, counts, or badges. Use the standard scale: text-xs, text-sm, text-base.
  • Smaller sizes are only acceptable on decorative overlays (chart stripes, empty-state icons) — never on text the user is meant to read.

Opacity floor on text

  • Never apply opacity below 0.7 to text. No opacity-30, opacity-50, opacity-60 on <span>s, <p>s, labels, etc.
  • Do not stack opacity tokens. Patterns like text-muted-foreground/60, text-midground/70, text-foreground/50 create unpredictable WCAG failures because the parent token already has alpha.
  • Use the semantic text tokens from @nous-research/ui's globals.css:
    • text-text-primary — default body text.
    • text-text-secondary — subtitles, meta, inactive nav.
    • text-text-tertiary — small chrome labels, counts, footnotes.
    • text-text-disabled — disabled states.
    • text-text-on-accent — text on filled accent surfaces.

Brand uppercase via text-display, not raw uppercase

  • The dashboard preserves the Nous brand uppercase aesthetic, but it is opt-in per element, not global.
  • Apply uppercase via the DS utility text-display on brand chrome only — page titles, nav section headings, badges, brand wordmark. DS components (Button, Badge, Tabs, Segmented, etc.) already self-apply text-display.
  • Do not introduce new uppercase (the literal Tailwind class) in hermes-agent/web/src. Prefer text-display for new brand chrome. Legacy uppercase call sites (e.g. components/ui/label.tsx, card.tsx) remain until migrated.
  • The app shell no longer forces uppercase globally, so blanket normal-case opt-outs are unnecessary. Use normal-case only where a DS component applies text-display but the label should stay sentence case — e.g. dynamic user content (model slugs, theme names) or fixed UI copy that is not brand chrome (EnvPage “not configured” toggle, sidebar “New chat”).

Fonts

Typography is opt-in per surface, not global on layout shells — the app shell and page header keep their original theme/expanded fonts; Mondwest applies only where explicitly set.

Tier Classes Use for
Brand chrome font-mondwest text-display (or themedChrome) Sidebar nav, card section headers (CardTitle), Segmented filter buttons, filter panel headings
Themed body font-mondwest normal-case (or themedBody) Card content (Card, CardDescription), session/platform rows, analytics tables — scoped to the component
Page chrome font-expanded Page header h1 (PageHeaderProvider) — sentence case, not text-display
Wordmark Typography + size/tracking only Sidebar/mobile “Hermes Agent” — mixed case, no Mondwest, no text-display
Technical font-mono-ui / font-mono / font-courier Model slugs, env keys, schedules, YAML, repo URLs
  • Do not put themedBody or themedFont on <main>, App, or other layout wrappers — it overrides component-scoped styles.
  • Card applies themedBody; CardTitle uses text-display (uppercase chrome); CardDescription uses themedBody.
  • NouiTypography defaults to font-sans unless a font prop is passed.
  • Do not use raw font-sans or font-display (theme sans variable) on new dashboard UI — prefer Mondwest tiers above where brand-appropriate.

Color tokens

  • Prefer semantic tokens (text-text-*, bg-card, border-border, text-foreground, text-destructive, text-success, text-warning) over raw layer references (text-midground, text-foreground).
  • text-muted-foreground is now wired to --color-text-secondary, so existing call sites stay correct, but new code should prefer the semantic name.
  • When you genuinely need a non-token color (icon de-emphasis on a chart, terminal foreground via inline style), keep alpha at ≥ 0.7 for any text.