* web: accessible light/dark color palette + dark-mode legibility fixes (#1678)
Ports the design system's accessible palette. Colors are defined once as Mantine
color tuples in theme.ts (shade 0 lightest → 9 darkest); every `--inspector-*`
token in App.css references `--mantine-color-*`, so improving the tuples cascades
through the whole UI.
- theme.ts: full accessible tuple set (inspector/gray/dark/green/red/yellow/blue/
teal/violet), WCAG-AA at the primary shades. Tuples are the one place literal
hex belongs — the token source. Primary stays inspector-7 (light) / -8 (dark).
- theme/ActionIcon.ts: default `color: "gray"` so chrome icons (settings, theme
toggle, sidebar, pin, sort, replay) render neutral grey instead of inheriting
the blue primary. Icons that set their own color/variant still override.
- App.css: fix light surfaces (`surface-body` gray-0, `surface-card` white — the
body token pointed at near-black dark-9); add direction/method badge tokens;
lift the semantic status/log/task/history colors 1–2 steps in the dark scheme
(the shared `*-6` shade sat too dark on charcoal). Light `:root` keeps `*-6`.
- Badges: MessageDirectionBadge + new MethodBadge element driven by
`--inspector-badge-*` tokens (deep fill + light text in dark, tinted in light)
via Mantine `bg`/`c` style props with `autoContrast={false}`; swaps
`<Badge color="dark">{method}</Badge>` in ProtocolEntry/NetworkEntry (their
Badge import stays — both still use it for status/category chips). Stories +
test added for MethodBadge (≥90 coverage gate).
Domain badge semantics live in the element components, not theme files. No raw
hex outside the tuples; no inline styles. Supersedes the reverted #1677.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
* web: WCAG AA accessibility pass — contrast, labels, names, roles (#1678)
Drives the axe-core ruleset (the engine behind Storybook's a11y addon) to
zero violations across all stories in light and dark mode.
Contrast (805 → 0 hits):
- Darken Mantine `*-light-color` + `dimmed` in light mode (and lift dark
`dimmed`) via `!important` App.css overrides — Mantine injects these at
runtime at matching specificity, so specificity alone loses the tie.
- `filledBadgeColor` pins amber (orange/yellow) filled badges to shade 5 so
`autoContrast` (JS-computed from the theme shade, not CSS vars) picks black.
- `accessibleTextColor` routes colored log/filter text through the scheme-aware
`-light-color` token so it clears AA on light and dark surfaces.
- `status` Badge variant: amber statuses use black-on-amber (orange text can't
reach AA on light at any Mantine shade).
- Resource URIs use `-light-color` (blue-4 was 4.38:1 in dark).
Names / roles:
- `aria-dialog-name`: custom-header modals use `Modal.Title` (wires the
dialog's `aria-labelledby`) instead of a bare `Title` + a root `aria-label`
(which also tripped `aria-prohibited-attr`).
- `button-name`: a `Modal` theme default labels every built-in close button.
- `label`: `aria-label` on the previously-unlabelled Textarea/Select inputs.
- `scrollable-region-focusable`: keyboard-focusable content Code/ScrollArea
viewports; long OAuth values wrap instead of scrolling.
- `heading-order`: correct heading levels (keeping visual size via `size`) so
outlines don't skip a level under the h2 modal/section titles.
Scrollbars: `type="scroll"` ScrollArea theme default — scrollbars auto-hide and
show only while scrolling (a deliberate product choice over the always-visible
a11y default).
`region` is disabled in the Storybook a11y config: isolated component/screen
stories render outside the app's `AppShell` landmarks, so it's a page-level
concern the components can't own.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: let the resource template form fill the main-area width (#1678)
Drop the maw=40% cap on the template-form pane so the URI preview and
variable inputs use the full width beside the sidebar, matching the
resource preview pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: left-align + reverse the input-panel action rows (#1678)
Move the footer action buttons to the left (justify flex-start) and put
the primary action first on the Tool / Prompt / Resource-preview /
Resource-template panels — Subscribe, Refresh, View Source on the
preview panel; Execute before Cancel on the tool panel. Shortens the
pointer travel from the sidebar controls / the form fields above to the
actions, which matters most when the monitoring sidebar is hidden and
the form is wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: match the dark-mode body background to the header tab bar (#1678)
Set the dark `--inspector-surface-body` to `dark-8` (the Mantine
SegmentedControl fill used by the header tab bar) instead of `dark-9`,
mirroring light mode where the body and the tab bar share `gray-1`.
Cards (`gray-9`) still sit a step above the body, and the contrast audit
stays at zero AA violations on the slightly lighter background.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: enforce the a11y gate + fix modal-overlay heading-order (review #1679)
Address the code review:
- Flip the Storybook a11y addon from test: 'todo' to 'error' so axe
violations fail the play-function tests in CI — the zero-violation
state is now gated, not point-in-time.
- Enforcing it surfaced a real heading-order skip: the Tools screen's
h4 'Tools'/'Results' sit under an h2 Modal.Title when a sampling/
elicitation request modal opens over them. Lift both to h3 (size h4
preserves the look) so h2→h3 is valid in any DOM order.
- Comment the intentional color+c pairing on the Remove/Disconnect
danger buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: brighter warning alerts + pin the elicitation form's actions (#1678)
- Add a 'warning' Alert variant: a bright light-yellow fill (yellow-1)
with crisp near-black text and a bright yellow-5 left accent, instead
of the muddy brown a tinted yellow alert produced once
yellow-light-color was darkened to yellow-9 for contrast. Dark mode
keeps the light-on-dark-tint treatment. Applied to all five yellow
warning/notice alerts.
- Elicitation form: scroll only the form fields (capped ScrollArea),
keeping the quoted message, warning, and Cancel/Decline/Submit buttons
in view. The modal has no other close affordance, so the actions must
never scroll out of reach.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: robustly pin the elicitation form's actions (#1678)
The previous approach capped only the fields' ScrollArea with a fixed
rem reserve for the pinned chrome, which underestimated it on some
viewports — the whole modal then overflowed and the buttons clipped
half-off-screen. Instead cap the whole panel below the modal height and
lay it out as a flex column: the message and the warning + buttons are
pinned (flex: 0 0 auto) and only the fields (flex: 1) shrink and scroll.
Verified the buttons stay within the panel and the modal fits the
viewport down to very short heights.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: fix elicitation form scrolling — fields scroll, actions pinned (#1678)
The prior fix capped the panel with a bare max-height, but a flex:1
ScrollArea's viewport won't bound against a max-height (not fixed-height)
parent, so a tall form's fields overflowed unscrollably (clipped) rather
than scrolling. Adopt ToolDetailPanel's pattern instead: the modal gives
its body a bounded flex column, the panel fills it, and the fields use a
flex: "0 1 auto" ScrollArea — which sizes to content when the form is
short (modal stays compact, nothing scrolls) and shrinks + scrolls once
the panel is capped at the modal height, keeping the message, warning,
and Cancel/Decline/Submit pinned in view. Add a ManyFields story (with a
bounded-height decorator mirroring the modal) to cover the scroll case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: fix elicitation scrolling in the real modal (cap fields, not flex)
The flex-column approach (panel flex:1 in a flex-column modal body, fields
flex:"0 1 auto") worked in a fixed-height Storybook decorator but NOT in the
real modal: the modal body is bounded by max-height, not an explicit height,
so the fields' ScrollArea viewport still couldn't bound — a tall form's
fields overflowed and pushed the warning + Cancel/Decline/Submit off-screen.
Drop the flex plumbing (and the modal-body styles) and cap the fields
directly with ScrollArea.Autosize mah=calc(90dvh - 22rem): it sizes to
content when the form is short (modal compact, nothing scrolls) and scrolls
once the fields hit the cap. The 22rem reserve = the measured non-field
chrome (modal header/padding + message + warning + buttons + gaps), so the
whole modal stays within 90dvh and the actions are always in view. Verified
in the real PendingClientRequestModal (new ElicitationFormTall story) for
both short and tall forms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* web: pin the sampling modal's actions too (#1678)
Apply the elicitation-form treatment to SamplingRequestPanel: wrap the
request/response content in a ScrollArea.Autosize capped at
calc(90dvh - 10rem) so it scrolls while the Reject / Send Response buttons
stay pinned in view (the modal has no other close affordance). The 10rem
reserve is the pinned chrome (modal header/padding + the actions + gaps) —
smaller than the elicitation form's 22rem since there's no warning banner.
Add a SamplingTall story (8 messages + preferences) to cover the scroll
case in the real PendingClientRequestModal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): update OAuth E2E tests to use new authentication method
- Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration.
- Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs.
- Ensured that the tests continue to validate the expected authorization URL after the change.
Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process.
* Guided-auth removal, OAuth smoke testing, and TUI/CLI support
* Added CLI OAuth runner flags tests
* Added clarifying comment on port 0 binding
* README notes on TUI oauth callback port, prettier fixes for CLI tests
* CIMD validation uses inline like EMA IdP
* Fixed TUI test failures from merge
* test: bring 5 OAuth files over the 90 per-file coverage gate
The guided-auth-removal work added/touched several files that fell below
the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run
the gate (it's local-only), so validate + test:integration passed while
these gaps slipped through. Bring them all back over the line:
- core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme
throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the
provably-dead guards (empty host, out-of-range port, String(err)
fallback, pathname "/" fallback) -- all rejected by new URL() upstream --
with justified v8 ignore comments instead of lowering the gate.
- core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return.
- core/react/useClientSettingsDraft.ts: test the functional-updater path,
the stale-onChange-after-close prev===null guard, and the
timer-fires-after-close value===null guard.
- ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear.
- clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration
kinds, the expired IdP session, and the configured-scope/empty-scope
branches of formatScopes.
npm run coverage now passes (exit 0) across web, cli, tui, launcher.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: cliffhall <cliff@futurescale.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): keep server cards a fixed height (#1528)
Two causes made server cards ragged-height:
1. The command/URL box wrapped long content onto multiple lines, growing
the card. Add a `nowrap` Code variant (single line, ellipsis on
overflow) and a `wrap` prop on ContentViewer (default true); ServerCard
passes `wrap={false}`. The full value stays available via the copy
button.
2. At ~1000-1500px the grid packed cards narrow enough that a connected
card's action row (Clone/Edit/Remove + Connection Info/Settings) wrapped
and stacked. Switch the SimpleGrid to container queries with thresholds
that keep every card >= ~500px wide at each column count, so it drops to
fewer/wider columns instead of wrapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): address review — correct threshold comment, single-line note, title tooltip
- Correct the column-threshold comment in ServerListScreen: the values
(1040/1560) use N gaps, not N−1; restate the formula accurately so the
numbers aren't "fixed" to match a wrong formula later.
- Note in the ContentViewer `wrap` JSDoc that `false` is for single-line
values only (nowrap collapses embedded newlines).
- Add a native `title` tooltip to the non-wrapping Code box so a clipped
command/URL is readable on hover, not only via copy. Covered by tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Only render InlineError details Stack when there is content to show.
Add wrapping Code theme variant for long URLs that would push buttons
offscreen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>