refactor(web): dashboard typography & contrast pass#28832
refactor(web): dashboard typography & contrast pass#28832austinpickett wants to merge 19 commits into
Conversation
Why - Whole dashboard was force-uppercased by a single \`uppercase\` on the App.tsx root, which inherited into every page and forced ~23 \`normal-case\` opt-outs across 7 files just to keep dynamic content (model names, theme names, etc.) readable. - Micro-typography (\`text-[0.55rem]\` / \`text-[0.6rem]\` / \`text-[9px]\` / \`text-[10px]\` / \`text-[11px]\`) combined with stacked alpha (\`text-muted-foreground/60\` over a 55%-alpha base, \`opacity-30\` on nav headers) produced text that fails WCAG AA at small sizes. - Per-theme \`--theme-font-sans\` was being clobbered by a hard-coded \`font-mondwest\` on the App.tsx root. Changes - Drop global \`uppercase\` + \`font-mondwest\` from the App.tsx root; default to \`text-text-primary\` so body content inherits the theme font. - Map \`--color-muted-foreground\` to \`--color-text-secondary\` so the long tail of \`text-muted-foreground\` call sites get a WCAG-AA-targeted color instead of 55%-alpha midground. - Apply the new DS \`text-display\` utility on intentional brand chrome (sidebar nav section labels, page titles, mobile header brand, segmented filters, badges, ChatSidebar headings). - Remove the 23 \`normal-case\` opt-outs that only existed to fight the global \`uppercase\`. Retain \`normal-case\` on the 4 DS \`Button\` instances that legitimately display dynamic content. - Bump every \`text-[0.55-0.7rem]\` / \`text-[9-11px]\` to \`text-xs\` (12px floor) across PluginsPage, ConfigPage, SkillsPage, ModelsPage, SessionsPage, AnalyticsPage, LogsPage, EnvPage, ChatPage, ChatSidebar, ToolCall, ModelInfoCard, ModelPickerDialog, SidebarStatusStrip, SidebarFooter, OAuthProvidersCard, SlashPopover, ThemeSwitcher, LanguageSwitcher, BottomPickSheet, AutoField. - Replace stacked-alpha refs (\`text-muted-foreground/60\`, \`text-midground/70\`, \`opacity-30/50/60\` on text) with semantic tokens (\`text-text-secondary\`, \`text-text-tertiary\`, \`text-text-disabled\`). - Bump \`@nous-research/ui\` to 0.16.0 (which adds the \`text-display\` utility and semantic text tokens this PR depends on). - Add a Typography & contrast rules section to \`web/README.md\` codifying the 12px text floor, 0.7 opacity floor on text, "uppercase via text-display only" rule, and "prefer semantic tokens" guideline so the dashboard doesn't drift back. This pairs with NousResearch/design-language#22 which provides the \`text-display\` utility and semantic text tokens. Co-authored-by: Cursor <cursoragent@cursor.com>
🔎 Lint report:
|
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors the web dashboard’s typography and contrast to make brand styling opt-in (via the new text-display utility) and to improve readability across themes by switching from stacked alpha colors to semantic text tokens. It updates the app shell, multiple pages/components, and codifies the new rules in web/README.md, alongside a design-system dependency bump.
Changes:
- Remove global
uppercase+font-mondwestinheritance from the App root and apply display styling only where intended. - Replace low-contrast alpha-stacked text colors and sub-12px micro-type with semantic
text-text-*tokens and atext-xsfloor across pages/components. - Update theming compatibility by remapping
--color-muted-foregroundand document the typography/contrast rules; bump@nous-research/uito0.16.0.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/App.tsx | Drops global uppercase/display font, updates sidebar/header chrome to opt-in text-display + semantic text colors. |
| web/src/index.css | Remaps --color-muted-foreground to --color-text-secondary to improve contrast for legacy text-muted-foreground call sites. |
| web/src/plugins/PluginPage.tsx | Swaps alpha-stacked midground colors for semantic text tokens. |
| web/src/pages/SkillsPage.tsx | Replaces micro-type + stacked alpha with text-xs and semantic tokens; applies text-display to filter/category chrome. |
| web/src/pages/SessionsPage.tsx | Raises small text to text-xs and replaces muted alpha text with semantic token classes. |
| web/src/pages/ProfilesPage.tsx | Removes now-unneeded normal-case wrapper that previously opted out of global uppercase. |
| web/src/pages/PluginsPage.tsx | Applies text-display selectively and replaces arbitrary font sizes / alpha colors with semantic tokens. |
| web/src/pages/ModelsPage.tsx | Raises micro-type to text-xs, replaces muted alpha with semantic tokens, and applies text-display to intentional chrome labels. |
| web/src/pages/LogsPage.tsx | Uses semantic text token for debug lines and raises badge text size to text-xs. |
| web/src/pages/EnvPage.tsx | Refactors unset-row de-emphasis away from low opacity and raises text sizes to text-xs with semantic tokens. |
| web/src/pages/ConfigPage.tsx | Updates filter/section chrome to text-display and replaces micro-type + stacked alpha. |
| web/src/pages/ChatPage.tsx | Removes now-unneeded normal-case root opt-out and updates muted text styling to semantic tokens. |
| web/src/pages/AnalyticsPage.tsx | Raises micro-type to text-xs and swaps muted alpha icons/text for semantic tokens. |
| web/src/components/ToolCall.tsx | Updates tool-call meta styling to semantic tokens and text-display for section labels. |
| web/src/components/ThemeSwitcher.tsx | Switches headings/meta text to text-display + semantic tokens and raises micro-type to text-xs. |
| web/src/components/SlashPopover.tsx | Replaces muted alpha meta text with semantic token usage. |
| web/src/components/SidebarStatusStrip.tsx | Replaces opacity-based de-emphasis with semantic token coloring and raises text size floor. |
| web/src/components/SidebarFooter.tsx | Applies text-display/semantic token styling and removes low-contrast alpha usage. |
| web/src/components/OAuthProvidersCard.tsx | Raises badge micro-type and replaces opacity-based meta text with semantic tokens. |
| web/src/components/ModelPickerDialog.tsx | Applies semantic token styling to provider meta and text-display for the “current” tag. |
| web/src/components/ModelInfoCard.tsx | Raises micro-type and replaces muted alpha with semantic tokens. |
| web/src/components/LanguageSwitcher.tsx | Applies text-display for intended chrome and uses semantic token text colors. |
| web/src/components/ChatSidebar.tsx | Removes global casing opt-out and applies text-display + semantic tokens to sidebar labels. |
| web/src/components/BottomPickSheet.tsx | Switches heading styling to text-display and semantic token coloring. |
| web/src/components/AutoField.tsx | Replaces micro-type and muted alpha with text-xs and semantic tokens. |
| web/README.md | Adds typography/contrast rules for size floors, opacity guidance, text-display, fonts, and tokens. |
| web/package.json | Bumps @nous-research/ui to 0.16.0. |
| web/package-lock.json | Updates lockfile for the DS bump and new transitive dependencies. |
| nix/web.nix | Updates fetchNpmDeps hash to match the new npm dependency graph. |
Files not reviewed (1)
- web/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Clarify that raw uppercase is legacy-only (prefer text-display for new code) and broaden when normal-case is appropriate on DS buttons. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop mondwest on version footer (mono-ui only) and opt the mobile model/tools sheet title into text-display after removing global uppercase. Co-authored-by: Cursor <cursoragent@cursor.com>
Register @font-face rules for Mondwest, Collapse, and Rules so DS components load brand fonts after the 0.14.x split from globals.css. Also merged main to sync the branch. Co-authored-by: Cursor <cursoragent@cursor.com>
Page title h1 and mobile/sidebar brand labels now opt into the DS text-display utility (with Mondwest on brand wordmarks) so chrome stays uppercase after removing the global App root transform. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the Typography mondwest prop on sidebar/mobile brand labels; it forced tracking-[0.1875rem] instead of the original tight brand tracking. Use font-mondwest via className like main did via shell inheritance. Co-authored-by: Cursor <cursoragent@cursor.com>
text-display alone is correct in source but stale web_dist or missing DS CSS leaves nav looking title-cased; explicit uppercase restores brand chrome. Document Vite vs dashboard URL in web README. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace Typography on Hermes Agent labels with a plain span using font-mondwest + uppercase (matching main via shell inheritance). Typography was injecting font-sans and fighting font-mondwest; text-display was not the pre-refactor brand style. Co-authored-by: Cursor <cursoragent@cursor.com>
Restore main-style Typography on the brand title (Collapse via font-sans), without font-mondwest or forced uppercase on the logo. Co-authored-by: Cursor <cursoragent@cursor.com>
Restore pre-typography-refactor brand classes and explicitly opt out of uppercase so the logo stays "Hermes Agent", not HERMES. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Apply sentence-case Mondwest via opt-in helpers on cards, tables, and modals instead of layout globals so sidebar wordmark and page titles keep their existing sans styling. Co-authored-by: Cursor <cursoragent@cursor.com>
Improve dashboard consistency with uppercase actions, ghost refresh controls, sessions overview tabs with inline search and dual pagination, env section show-more in headers, OAuth copy/login layout fixes, and namespaced plugin API routes. Co-authored-by: Cursor <cursoragent@cursor.com>
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR #28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR #28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
|
Merged via PR #30714 (commit 487c398 on main). Your 19 commits were squashed into a single commit with your authorship preserved ( A small follow-up was added on top (commit 8cf977c) to widen Thanks for the typography + contrast pass and for picking up the category-namespaced plugin routing along the way. |
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
* feat(ci): 4-way matrix slicing with LPT duration-balanced distribution
run_tests_parallel.py:
- --slice I/N flag (also HERMES_TEST_SLICE env var) runs only the
I-th slice of N, distributing files across slices by cached
duration using LPT (Longest Processing Time first) greedy
algorithm so each slice gets roughly equal wall time
- Duration cache (test_durations.json): maps relative file paths to
last-observed subprocess wall time. _save_durations merges with
existing cache so entries from other slices are preserved.
- Per-file subprocess timing in progress output + end-of-run
distribution summary (percentiles, top-10 slowest, <1s/<2s counts)
- Unknown files default to 2.0s estimate (~P50), spread evenly by LPT
.github/workflows/tests.yml:
- Matrix strategy: slice [1, 2, 3, 4] with fail-fast: false
- Each slice restores duration cache from main (stable key, no SHA),
runs its portion, uploads per-slice durations as artifacts
- save-durations job (main only, if: always()) downloads all 4
artifacts, merges into single cache entry for future PRs
- Timeout reduced from 60min to 30min per slice (~1/4 the work)
Cache design:
- Stable key (test-durations) not keyed by commit SHA — durations
are about files, not commits, and SHA-keyed caches miss on every
new commit and on PR merge commits
- actions/cache scoping: main's cache is visible to all PRs targeting
main; feature branches without a cache still work (default 2.0s)
- No dotfile prefix (upload-artifact v7 skips hidden files)
* test: 4-way slice benchmark (with cache save)
* fix(test): deflake two intermittent CI failures
- test_browser_secret_exfil: mock _run_browser_command instead of
launching real Chrome (secret check is pre-launch, browser is
irrelevant to the assertion)
- test_web_server: add time.sleep(0.05) after pub.send_text() to
yield the event loop before receive_text(). TestClient's sync mode
can race the broadcast handler otherwise, hanging the test.
* fix: clean push triggers
* feat(ci): use 6-way slicing based on benchmark results
Benchmarked 4/5/6/7/8 slices with LPT duration-balanced distribution:
- 4 slices: 4.8m wall, 135s spread
- 5 slices: 3.4m wall, 46s spread
- 6 slices: 3.3m wall, 26s spread ← optimal
- 7 slices: 3.9m wall, 109s spread
- 8 slices: 3.7m wall, 96s spread
6 slices is the sweet spot: lowest wall time, tightest spread.
7+ gets slower due to per-slice startup overhead dominating.
Also removes benchmark branch markers from save-durations condition.
* refactor(web): dashboard typography & contrast pass
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
* fix(plugins): widen _sanitize_plugin_name for category-namespaced names
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed
names like `observability/langfuse` and `image_gen/openai`, but
`_sanitize_plugin_name` still rejected forward slash and so dashboard
update + remove on those plugins fell through to '404 not found' even
though they exist on disk.
Adds an opt-in `allow_subdir=True` flag that:
- Permits internal forward slashes (category-namespaced plugin keys
emitted by `_discover_all_plugins`).
- Strips leading and trailing slashes.
- Still rejects `..` and backslash, and still asserts the resolved
target lives inside `plugins_dir`.
Opted in at the two read-paths that operate on installed plugins:
`_require_installed_plugin` (CLI update/remove) and
`_user_installed_plugin_dir` (dashboard update/remove). The install
path keeps the default (`allow_subdir=False`) because freshly-cloned
plugins always land top-level under `~/.hermes/plugins/<name>/`.
Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
* fix(skills,pairing): path traversal guard in uninstall, lock list_pending, hash file paths
- skills_hub: validate that uninstall_skill's install_path resolves
inside SKILLS_DIR before calling shutil.rmtree, preventing recursive
deletion of arbitrary directories via poisoned lock.json entries
- skills_hub: include file paths (not just contents) in
bundle_content_hash so swapping filenames between files changes the
hash, strengthening update-detection integrity
- pairing: wrap list_pending() in self._lock so _cleanup_expired() file
writes don't race with concurrent generate_code()/approve_code() calls
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(skills): make content_hash filename-sensitive too (symmetric with bundle_content_hash)
PR NousResearch#6656 added rel_path + \x00 prefixing to ``bundle_content_hash`` so a
filename swap between two files in a bundle changes the digest. But it
only patched the in-memory side — ``content_hash`` in ``tools/skills_guard.py``
(the on-disk equivalent) still hashed file contents only.
These two functions need to stay symmetric: ``check_for_skill_updates``
compares the disk hash of an installed skill against the bundle hash
of the upstream copy. With the asymmetric fix, every clean install
showed as drifted because the digests no longer matched
(2 existing tests in ``test_skills_hub.py`` started failing as soon as
the contributor's change landed).
Apply the same ``rel_path + \x00 + content`` shape to the disk-side
function. Both functions now produce the same digest for the same skill
content laid out two ways. Documented the symmetry invariant in the
docstring so a future change to either function knows to touch both.
Also adds tests/tools/test_pr_6656_regressions.py with 10 regression
tests covering all three fixes salvaged in PR NousResearch#6656:
- uninstall_skill path traversal (4 cases: parent segments, absolute
paths, symlink escape, legitimate skill)
- bundle_content_hash filename swap detection (4 cases: in-memory
swap, identity, disk-side swap, bundle↔disk symmetry)
- list_pending lock contract (2 cases: source-grep contract, smoke)
Also fixes AUTHOR_MAP entry for @aaronlab — their commit email
(1115117931@qq.com) maps to "aaronagent" which isn't a real GitHub
login, so changelog @mentions would 404.
* infographic: PR NousResearch#6656 skill hub safety audit salvage
* fix(file-safety): block read_file on HERMES_HOME credential stores (NousResearch#17656)
`get_read_block_error` previously only denied reads inside
`${HERMES_HOME}/skills/.hub`, which left `auth.json` (provider OAuth
state + plaintext API keys) and `.anthropic_oauth.json` (Anthropic PKCE
tokens) directly readable by the agent. A prompt-injection reaching
`read_file` could exfiltrate active provider credentials in plaintext.
Mode-0600 file permissions only protect against *other Unix users* —
the agent runs as the file's owner, so `read_file` is unaffected.
Extend the existing deny list with the three credential paths
identified in NousResearch#17656 (`auth.json`, `auth.lock`, `.anthropic_oauth.json`).
The check uses the same `Path.resolve()` pattern as `skills/.hub`, so
symlink/path-traversal indirection is caught too. The agent doesn't
need to read these directly — `auxiliary_client` and `credential_pool`
consume them through process env / OAuth flows that bypass `read_file`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(file-safety): block auth.json read via TERMINAL_CWD relative path
read_file_tool resolves relative paths against TERMINAL_CWD (or the
task's live terminal cwd), but the prior call passed the original
unresolved string to get_read_block_error. That function's own
resolve() is anchored at the Python process cwd, so when a task's
TERMINAL_CWD pointed at HERMES_HOME and the agent issued read_file
on the relative path "auth.json", the credential-store denylist was
never reached and the file was read normally.
Pass the already-resolved absolute path string at the file_tools call
site, document the contract on get_read_block_error, and add a
read_file_tool-level regression test that pins the relative-path
case under TERMINAL_CWD == HERMES_HOME.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(file-safety): widen read-deny to .env, mcp-tokens/, webhook secrets, root
Extends @briandevans's PR NousResearch#17659 from {auth.json, auth.lock,
.anthropic_oauth.json} to also cover:
- HERMES_HOME/.env (provider API keys)
- HERMES_HOME/webhook_subscriptions.json (per-route HMAC secrets)
- HERMES_HOME/mcp-tokens/ (OAuth token directory; dir
+ everything inside)
…AND iterates over both _hermes_home_path() AND _hermes_root_path()
so profile-mode runs (HERMES_HOME = <root>/profiles/<name>) also block
<root>/{auth.json, .env, mcp-tokens/, ...}. Same widening shape as the
write-deny side already does (NousResearch#15981, NousResearch#14157).
Explicitly NOT a security boundary. Per the personal-assistant trust
model, the terminal tool runs as the same OS user and can `cat
auth.json` directly. This read-deny exists as defense-in-depth:
- Models that respect tool denials empirically tend to stop rather
than reach for the shell.
- The denial surfaces an audit trail when something tries to read
credentials — easier to spot in logs than a generic `cat`.
Docstring + error message both flag this as defense-in-depth so future
contributors don't mistake it for a real security boundary and don't
re-decline reports that propose the same fix shape.
Absorbs the .env and mcp-tokens/ coverage from @tomqiaozc's parallel
PR NousResearch#8055 (closed-as-duplicate, credited).
Co-authored-by: Tom Qiao <zqiao@microsoft.com>
* infographic: PR NousResearch#17659 read-deny credentials salvage
* feat(inbox): add Hermes Inbox API surface E.1
---------
Co-authored-by: ethernet <arilotter@gmail.com>
Co-authored-by: Austin Pickett <pickett.austin@gmail.com>
Co-authored-by: Hermes <noreply@nousresearch.com>
Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
Co-authored-by: aaronagent <1115117931@qq.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
Co-authored-by: Tom Qiao <zqiao@microsoft.com>
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
#AI commit#
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix. #AI commit#
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
Removes the global `uppercase` + `font-mondwest` from the App.tsx root
that forced every page to opt-out, replaces stacked-alpha text colors
with semantic tokens for WCAG-AA contrast across all 7 themes, and
applies the new `text-display` utility from @nous-research/ui@0.16.0
on intentional brand chrome (page titles, sidebar headings, segmented
filters) only. Bumps every sub-12px arbitrary text size to text-xs.
Also widens the dashboard plugin routes (/api/dashboard/agent-plugins/
{name:path}/...) so category-namespaced plugins like observability/
langfuse and image_gen/openai can be enable/disabled from the dashboard
— previously the FE encodeURIComponent-ed the slash and the backend
{name} route rejected it. _validate_plugin_name still blocks .. and
backslash, and strips leading/trailing slash.
Touches sessions/env/keys page chrome and adds two new i18n keys
(`overview`, `showMore`/`showLess`) across all 18 locales.
Squashes 19 commits from PR NousResearch#28832.
Co-authored-by: Hermes <noreply@nousresearch.com>
Follow-up to PR NousResearch#28832 — the dashboard plugin routes now accept slashed names like `observability/langfuse` and `image_gen/openai`, but `_sanitize_plugin_name` still rejected forward slash and so dashboard update + remove on those plugins fell through to '404 not found' even though they exist on disk. Adds an opt-in `allow_subdir=True` flag that: - Permits internal forward slashes (category-namespaced plugin keys emitted by `_discover_all_plugins`). - Strips leading and trailing slashes. - Still rejects `..` and backslash, and still asserts the resolved target lives inside `plugins_dir`. Opted in at the two read-paths that operate on installed plugins: `_require_installed_plugin` (CLI update/remove) and `_user_installed_plugin_dir` (dashboard update/remove). The install path keeps the default (`allow_subdir=False`) because freshly-cloned plugins always land top-level under `~/.hermes/plugins/<name>/`. Adds 6 targeted unit tests covering the new flag's allow/reject matrix.
What does this PR do?
Pays down accumulated typography and contrast debt in the dashboard:
uppercase+font-mondweston the App.tsx root that was inheriting into every page and forcing ~23normal-caseopt-outs across 7 files.text-displayutility from@nous-research/ui@0.16.0on intentional brand chrome (page titles, sidebar section headings, segmented filters) so the brand uppercase look is opt-in per element instead of global.text-muted-foreground/60over a 55%-alpha base,opacity-30on nav headers, etc.) with the new semantic text tokens (text-text-primary/secondary/tertiary/disabled) so contrast stays WCAG-AA across all 7 built-in themes.text-[…rem|px]arbitrary value totext-xs(12px floor) where it was actually content rather than decoration.--color-muted-foregroundto--color-text-secondaryinindex.cssso existingtext-muted-foregroundcall sites land on the new contrast floor without per-file changes.web/README.mdso we don't drift back.Pairs with NousResearch/design-language#22 (the DS PR that ships
text-display+ semantic text tokens + sourcemaps).Related Issue
Fixes #
Type of Change
web/README.md)Changes Made
App / theming
web/src/App.tsx— drop globaluppercase+font-mondwest; default totext-text-primary. Applytext-display+ semantic text tokens on sidebar nav section labels, mobile header brand, sidebar brand, system actions.web/src/index.css— map--color-muted-foregroundto--color-text-secondary.Components
web/src/components/AutoField.tsx,BottomPickSheet.tsx,ChatSidebar.tsx,LanguageSwitcher.tsx,ModelInfoCard.tsx,ModelPickerDialog.tsx,OAuthProvidersCard.tsx,SidebarFooter.tsx,SidebarStatusStrip.tsx,SlashPopover.tsx,ThemeSwitcher.tsx,ToolCall.tsx— replace micro-type + stacked alpha withtext-xsand semantic tokens; applytext-displayon intentional brand chrome only.Pages
web/src/pages/AnalyticsPage.tsx,ChatPage.tsx,ConfigPage.tsx,EnvPage.tsx,LogsPage.tsx,ModelsPage.tsx,PluginsPage.tsx,ProfilesPage.tsx,SessionsPage.tsx,SkillsPage.tsx— same sweep across each page.web/src/plugins/PluginPage.tsx— swaptext-midground/70→text-text-secondary/text-text-tertiary.Dependency bump
web/package.json—@nous-research/ui0.14.0→0.16.0(DS PR providestext-display+ semantic text tokens).web/package-lock.json— regenerated for the DS bump.nix/web.nix—fetchNpmDepshash refreshed vianix run .#fix-lockfiles.Docs
web/README.md— new "Typography & contrast rules" section: 12px text floor, 0.7 opacity floor on text,text-display(prefer over rawuppercase), prefer semantic tokens.How to Test
cd web && npm install && npm run build && npm run dev— verify the dashboard builds and renders.default-large,midnight,ember,mono,cyberpunk,rose. Body content should pick up each theme'sfontSansinstead of Mondwest. Brand chrome should remain Mondwest + uppercase.text-xs). The only sub-12px content remaining should be decorative (chart stripe overlay on ModelsPage, empty-state icons).normal-caseon DS buttons still works where sentence-case UI is intended (model picker, theme/language switchers, EnvPage toggle, sidebar "New chat").pytest tests/ -q— no changes outsideweb/, but run it to confirm.Checklist
Code
refactor(web): …)pytest tests/ -qand all tests pass — N/A, frontend-only refactorDocumentation & Housekeeping
docs/, docstrings) —web/README.mdupdated with rules sectioncli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs