Skip to content

fix(ui): compact the mode switcher, make its tabs read as tabs, and stop it hiding what the decision needs - #1135

Merged
Dumbris merged 1 commit into
mainfrom
claude/mode-switcher-ux-polish
Aug 30, 2026
Merged

fix(ui): compact the mode switcher, make its tabs read as tabs, and stop it hiding what the decision needs#1135
Dumbris merged 1 commit into
mainfrom
claude/mode-switcher-ux-polish

Conversation

@Dumbris

@Dumbris Dumbris commented Aug 30, 2026

Copy link
Copy Markdown
Member

Pull Request

Description

Follow-up to #1133, which shipped the header mode switcher. Two problems with it:
the dropdown was too text-heavy and scrolled internally, so half the decision sat below a fold; and the
Surface / Schema detail tabs read as loose text rather than a control.

Compaction. Every option is now ONE visible line — label, a summary that carries the cost (the only
real basis for choosing), and the endpoint chip — with the full explanation on hover and a docs link per
axis. Measured on a live instance: both tabs 390px, no scrolling, fits a 700px viewport.

Tabs. They used tabs-bordered, this app's page-level nav idiom (Settings, ServerDetail), where a
hairline under the active item works because there is a long rule to read it against. At dropdown scale it
is invisible. They now use tabs-box — the segmented control AddServerModal already uses for a compact
one-of-two choice — whose filled active segment is the boundary that makes the row read as a control, and
the label/value pair now stacks inside the pill instead of wrapping outside it.

Note for a follow-up, not fixed here: frontend/src/views/Dashboard.vue:122 uses tabs-boxed, the
DaisyUI 4 spelling, which renders completely flat under v5 — the Usage/Overview switcher has the same
"looks like plain text" problem, independently of this change.

What a QA/UX review of the compaction found

Reviewed across five lenses (UX/IA, copy accuracy against the Go backend, accessibility, no-scroll geometry
across viewports and themes, regression), each finding adversarially verified against the running app. 16
confirmed. Several were introduced by the compaction itself:

  • Code Exec's prerequisite became hover-only — the worst of them. That surface has no tool-calling path
    but the code_execution tool (buildCodeExecModeTools omits call_tool_*), and that tool is a refusing
    stub while enable_code_execution is false — which is the default. So the panel would stage a surface
    that finds tools and can call none of them, while the pending notice claimed "/mcp/code serves it now".
    GET /api/v1/routing now reports code_execution_enabled, and both the option row and the pending
    notice say so inline, in warning tone, when it is unmet.
  • The scroll fallback was removed entirely. Compaction is what achieves "no scrolling", but with
    max-h/overflow gone the panel's bottom became permanently unreachable at 200% zoom or on a short
    window — it is absolutely positioned inside a sticky header the page cannot scroll into view. A
    viewport-relative cap is back. Measured: 700px viewport → 390px panel, no scroll; 420px viewport → caps at
    340px, scrolls, last item reachable.
  • Two false claims in the new copy: Direct does not list "nothing hidden" (quarantined, disabled and
    out-of-profile servers are filtered out), and Retrieve is not the "smallest context of the three".
  • Badges that lied. Under code execution /mcp is pinned to full schemas (Spec 085 FR-011), so the
    header badge and the Schema-detail chip no longer repeat a compact/deferred config that surface ignores.
    The badge also names the panel's own word ("Signatures") instead of the raw config value ("compact"),
    which appeared nowhere in the UI it pointed at.
  • Accessibility. Selection was conveyed only by a background tint and an unlabelled <svg> — nothing an
    assistive tech can read. Options are now role="radio" + aria-checked inside labelled radiogroups.
    aria-busy replaces disabled during a write, which was dropping focus to <body> mid-interaction.
    aria-haspopup says dialog rather than menu, the panel carries a role and label, and the muted text
    was raised to pass AA in both themes (the inactive tab's sub-label measured 2.88:1 light / 3.15:1 dark).
  • One docs link served both serialization axes — they are separate specs and separate pages.

Testing

  • I have tested these changes locally
  • I have added/updated tests that prove my fix is effective or my feature works
  • All existing tests pass

1001 frontend unit tests (20 in mode-switcher.spec.ts, including new cases for the prerequisite warning,
the pending notice that must not promise an unusable surface, aria-checked on both axes, and the per-axis
docs links); internal/httpapi incl. the new code_execution_enabled field; golangci-lint v2 clean;
swagger regenerated.

Manually verified against a live isolated instance in the exact broken combination
(routing_mode: code_execution + enable_code_execution: false): the row and the notice both warn, and the
panel measures correctly at 1280x700 and 1280x420.

Notes for the reviewer

  • Not fixed here: below ~1220px the header's right-hand cluster overflows the viewport and takes the
    switcher button off-screen with it — I confirmed the button itself is unreachable at 781px, so the
    clipped dropdown is a symptom, not the cause. That is pre-existing header layout and deserves its own
    change rather than a patch inside this component.
  • The tests now assert the long copy is reachable in the hover hints rather than inline. That is
    deliberate: it proves the information was relocated, not dropped. The copy-length budgets in
    mode-switcher.spec.ts are what keep the viewport cap from ever engaging at normal heights.

…ion needs

The dropdown was too text-heavy and scrolled internally, so half the decision sat
below a fold. Every option is now ONE visible line — label, a summary that
carries the cost, and the endpoint chip — with the full explanation on hover and
a docs link per axis. Both tabs measure 390px and fit without scrolling.

The tab row read as loose text because it used `tabs-bordered`, this app's
page-level nav idiom, where a hairline is invisible at dropdown scale. It now
uses `tabs-box` — the segmented control AddServerModal already uses for a
compact one-of-two choice — whose filled active segment is the boundary that
makes the row read as a control. (`tabs-boxed` is the DaisyUI 4 spelling and
renders flat under v5; Dashboard.vue still has that bug independently.)

A QA/UX review of the compaction found things it had hidden or broken, fixed
here:

- **Code Exec's prerequisite was hover-only.** The code-execution surface has no
  tool-calling path but the code_execution tool, which refuses while the feature
  is off — and it is off by default. Selecting that mode staged a surface that
  finds tools and can call none of them, and the pending notice claimed
  "/mcp/code serves it now". /api/v1/routing now reports
  `code_execution_enabled`, and the row and the notice say so inline, in warning
  tone, when it is unmet.
- **No scroll fallback.** Dropping max-height and overflow entirely left the
  panel's bottom unreachable at 200% zoom or on a short window — it is
  absolutely positioned inside a sticky header the page cannot scroll. A
  viewport-relative cap is back; at normal heights it never engages, which the
  copy budget in the tests is what keeps true.
- **Two false claims.** Direct did not list "nothing hidden" (quarantine,
  disabled and out-of-profile servers are filtered), and Retrieve is not the
  "smallest context of the three".
- **Badges that lied.** Under code execution /mcp is pinned to full schemas, so
  the header badge and the tab chip no longer repeat a compact/deferred config
  the surface does not use. The badge also names the panel's word
  ("Signatures"), not the raw config value.
- **Accessibility.** Selection was conveyed only by a background tint and an
  unlabelled tick: options are now `role="radio"` + `aria-checked` inside
  labelled radiogroups. `aria-busy` replaces `disabled` during a write, which
  was dropping focus to `<body>` mid-interaction. `aria-haspopup` says `dialog`,
  the panel has a role and label, and the muted text meets AA in both themes.
- **One docs link served two axes.** They are separate specs and separate pages.

Not fixed here: below ~1220px the header's right-hand cluster overflows the
viewport and takes the switcher button off-screen with it. That is pre-existing
header layout, not the dropdown, and needs its own change.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 943704a
Status: ✅  Deploy successful!
Preview URL: https://12824585.mcpproxy-docs.pages.dev
Branch Preview URL: https://claude-mode-switcher-ux-poli.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: claude/mode-switcher-ux-polish

Available Artifacts

  • archive-darwin-amd64 (29 MB)
  • archive-darwin-arm64 (26 MB)
  • archive-linux-amd64 (17 MB)
  • archive-linux-arm64 (15 MB)
  • archive-windows-amd64 (29 MB)
  • archive-windows-arm64 (26 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (23 MB)
  • installer-dmg-darwin-arm64 (21 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 33300226310 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris
Dumbris merged commit b7d803e into main Aug 30, 2026
39 checks passed
@Dumbris
Dumbris deleted the claude/mode-switcher-ux-polish branch August 30, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants