Commit graph inspector/clients/web/src
Author SHA1 Message Date
cliffhall
1e6718e2fd test(web): disable Mantine transition timers in the unit env
CI failed with an unhandled `ReferenceError: window is not defined` thrown from
a Mantine `Transition` `setTimeout` (`use-transition.mjs`) firing after the test
environment was torn down — every test passed, but the leaked timer failed the
run. happy-dom has no `matchMedia`, so `useReducedMotion` resolves to
"motion allowed" and every Transition (ScreenStage, Modal, …) schedules real
enter/exit timers; a timer that outlives its test throws post-teardown. This
surfaced now because MonitoringScreen started wrapping its screens in
ScreenStage (a Transition) where it previously rendered them directly.

Mock `window.matchMedia` in the test setup to report
`prefers-reduced-motion: reduce`, so Mantine transitions render instantly (no
timers). All other queries return `false`, matching the prior absent-matchMedia
behavior, so `useMediaQuery`-driven layout is unchanged. Full unit suite passes
with no unhandled errors across repeated runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 22:52:48 -04:00
cliffhall
a26cbac609 review: harden version read + fix MonitoringScreen docstring
Address PR #1641 review feedback:

- Add readInspectorVersionSafe() (non-throwing) in core/node/version.ts and use
  it in the web backend, so a failure to resolve the root manifest hides the
  (cosmetic) version badge instead of crashing the backend at startup. Covered
  by tests for both the resolved and unresolvable paths.
- Update MonitoringScreen's docstrings: it now wraps every supplied screen in a
  ScreenStage (only the active tab mounts, cross-fading) rather than rendering
  just the active tab's node.

The third fetch-consolidation suggestion (one useInitialConfig instead of three
GET /api/config hooks) is tracked as a follow-up in #1643.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 22:40:34 -04:00
cliffhall
ea8715c9c4 style(web): size + space the toolbar icon buttons to match the header
Make the server-list toolbar's expand/collapse toggle and open/close-monitoring
icons the same width and spacing as the header's theme / client-settings icons:

- ListToggle (default variant), PinColumnButton, and the MonitoringControls
  close button now render as `ActionIcon variant="subtle" size={36}` (glyph 20),
  matching the header's 36px ActionIcons, instead of wider `Button`s.
- ServerListControls uses `gap="sm"` so its buttons sit the same 12px apart as
  the header's RightSection controls.

Update a TaskListPanel test that found the toggle by a brittle
`mantine-Button-root` class to select it by its accessible name instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 20:49:18 -04:00
cliffhall
0c6fafbf60 feat(web): add an open-monitoring-column button to the server list
When a server is connected, has monitor screens to show, and the monitoring
column isn't open, show an "open monitoring column" button to the right of the
list expand/collapse toggle on the server list. It reuses PinColumnButton (the
same right-sidebar affordance the monitor screens use, now with an optional
label), so opening the column is reachable from the server list — not only from
a monitor screen's pin button. InspectorView gates it on
`canPin && monitorAvailable.length > 0 && !effectivePinned`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 20:23:27 -04:00
cliffhall
13a1d774de refactor(web): share screen fade-up animation with the monitoring column
Extract the primary pane's per-screen enter/exit transition into a shared
`ScreenStage` element (the `fade-up` Mantine Transition: incoming screen slides
up + fades in while the outgoing fades down + out, both absolutely positioned).
InspectorView now imports it (behavior unchanged), and the pinned monitoring
column uses it too — via a new `fill` variant that stretches the stage to the
column's height — so switching Logs/Protocol/Network animates the same way as
switching the main screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 20:12:01 -04:00
cliffhall
5d024ab54f feat(web): add copyright notice + footer/toolbar polish (#1639)
- Add a grey CopyrightBadge ("Copyright © Model Context Protocol a Series of LF
  Projects, LLC.") to the footer. Version (bottom-left) and copyright
  (bottom-right) share the bottom `xl` margin band: vertically centered in it
  and inset by `xl` to line up with the content's left/right margins.
- Move the list expand/collapse toggle to the right of the "Add Servers" button
  in ServerListControls.
- Make the monitoring column's close-sidebar control match the open-sidebar
  PinColumnButton theme (Button size sm / variant subtle) instead of a gray
  ActionIcon, so the open/close affordances read as a pair.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 20:11:52 -04:00
cliffhall
650e5bd020 feat(web): show the Inspector version in the lower-right corner (#1639)
Display the single-source version (root package.json) in the web UI's
lower-right corner, in grey — matching the CLI and TUI. The browser can't read
the filesystem, so the version flows backend → browser via GET /api/config:

- Add `version?: string` to InitialConfigPayload; the web backend
  (webServerConfigToInitialPayload) reads it once via the shared
  readInspectorVersion() and tags it onto the payload.
- Add a `useInspectorVersion` hook mirroring useSandboxUrl/useServerListWritable.
- Add a presentational `VersionBadge` element rendered fixed lower-right, grey,
  non-interactive; styling lives in a `versionBadge` Text theme variant (no
  inline styles, --inspector-* tokens).
- Wire it into App.tsx.

Verified live: /api/config serves `version: "2.0.0"` and the badge renders
`v2.0.0` fixed at the lower-right in grey. Tests: useInspectorVersion (full
branch coverage), VersionBadge (unit + Storybook), web-server-config payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 19:07:31 -04:00
cliffhall
7815548300 refactor(version): single-source the version from the root package.json
The Inspector ships as one package with one version, but four client
package.jsons each carried their own `version` and were read at runtime
(cli.ts, tui App.tsx), and core hardcoded a third, stale value (0.20.0) that
the TUI and web actually reported to servers. Consolidate onto the root
package.json as the single source of truth.

- Add core/node/version.ts: shared, Node-only readInspectorVersion() that walks
  up to the root package.json (always shipped in the tarball). 100% covered by a
  new integration test.
- CLI and TUI read the version via the shared reader instead of their own
  manifest; TUI now also passes a real clientIdentity (was falling through to
  the stale core default).
- Drop the `version` field from all four client package.jsons; the root keeps
  the only version. TUI header name/description become local display constants.
- core default client identity: replace the stale 0.20.0 hardcode with a neutral
  0.0.0 placeholder (real clients pass clientIdentity; web will source its
  version from GET /api/config, see #1639).
- Stop shipping clients/cli/package.json and clients/tui/package.json (no longer
  read at runtime) — supersedes the earlier "ship them" fix; update pack:verify
  and the root README packaging invariants accordingly.
- Add core/node/** to the web coverage include so the new module is gated.
- Add the shared-code architecture diagram to the root README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 17:31:21 -04:00
cliffhall
da1205441b docs(web): note the intentionally-preserved History section labels (#1623)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 20:51:25 -04:00
cliffhall
20e4ce7b43 fix(web): keep wide network-entry URLs from overflowing the column (#1623)
Long request URLs stretched every Network card to their own max-content
width, bleeding the cards (and their status badges) past the pinned
monitoring column. Mantine's ScrollArea `content` slot defaults to
`min-width: max-content`, so a non-wrapping URL forced the whole list wider
than its viewport.

Add an opt-in `constrainContentWidth` prop to EmbeddableScrollArea that
relaxes that slot to `min-width: 0`, so the list can't grow past its
viewport and each row manages its own overflow — the URL already lives in a
horizontal ScrollArea, which now actually scrolls. Scoped to the Network
stream panel (Logs/Protocol rows wrap or truncate, so they keep the default
where a long line scrolls the list).

Verified in Storybook (embedded 380px column): cards clamp to the viewport,
no list/page horizontal overflow, and the URL scrolls within its own area.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 20:11:43 -04:00
cliffhall
9824055425 refactor(web): unify message-direction arrows and rename Pinned section (#1623)
- Direction labels now both point right so the arrow reads clearly at
  badge size: incoming "client ← server" becomes "server → client"
  (from → to), in MessageDirectionBadge and MessageDirectionFilter
  (shared by the Protocol and Network views).
- The Protocol list's pinned section header now reads "Pinned Messages"
  (was "Pinned Requests"), matching the "Messages" panel title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 19:45:52 -04:00
cliffhall
7f58b67ea6 refactor(web): rename Protocol list header "Requests" to "Messages" (#1623)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 19:21:56 -04:00
cliffhall
ace4250d31 refactor(web): rename "History" tab/feature to "Protocol" (#1623)
Rename the web client's History tab and its supporting code to Protocol
so the feature reads as the MCP "Protocol" view.

- Tab renders as "Protocol"; INSPECTOR_TAB_IDS, InspectorView tab
  constants/MonitorTab, TAB_UI_REGISTRY key, and navigation.ts tab id all
  updated to Protocol.
- Rename components + folders: HistoryScreen -> ProtocolScreen,
  HistoryListPanel -> ProtocolListPanel, HistoryEntry -> ProtocolEntry,
  HistoryControls -> ProtocolControls, historyUtils -> protocolUtils
  (plus their tests and stories, and exported types).
- Rename App/InspectorView plumbing: historyUi, pinnedHistoryIds,
  on*History* handlers/props, replayHistoryRequest,
  isReplayableHistoryMethod, sort/compact scopes, etc. -> Protocol names.
- Export filenames use the "protocol" slug (buildExportFilename +
  ExportKind).

Preserved (per issue #1623 decisions):
- The in-panel section discriminator keeps its "pinned" | "history"
  values, and the sub-sections stay labeled Pinned / History.
- window.history and core message-log/replay concepts are untouched.

npm run ci passes (format, lint, build, unit tests, >=90 per-file
coverage gate, smoke, Storybook).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 19:04:54 -04:00
cliffhall
a759777ceb refactor(web): content-gate History in the failure column (#1621)
Re-review follow-up (#1622):

- Observation A: a returning user whose last-pinned monitor tab was
  History would land on the (empty) History tab on a stdio failure instead
  of the Console diagnostic. Root cause: History is *always* empty on a
  connect failure (the message log clears on the error transition), yet it
  was pushed unconditionally, so it could precede/hide the real diagnostic.
  Fix: content-gate History like Console/Network — only offer it when it
  has entries. On a fresh failure the diagnostic (Console/Network) is the
  only tab, so it always leads, without touching tab-switching or adding
  state. This also resolves the earlier "opens onto an empty column" note:
  when nothing has been captured yet, monitorAvailable is empty and the
  column stays closed, opening reactively once stderr/fetch content lands.

Tests: stdio/HTTP failure now assert the diagnostic leads and History is
absent; added "leads with Console even when stored tab was History",
"keeps the column closed until a diagnostic has content", and "offers
History when it has captured content". Verified live (time server →
Console-only column). Full `npm run ci` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 16:22:09 -04:00
cliffhall
87630f2356 refactor(web): address review — scope failure UX to connect attempts, dedupe timing (#1621)
Review feedback on #1622:

- #1 (medium): `connectionStatus === "error"` also covers a mid-session
  crash of a previously-connected server, not just a failed connect
  attempt. Scope the auto-open + failure tab set to connect attempts only:
  the auto-open effect's error arm now requires the previous status to not
  be `connected` (connecting/disconnected → error), and `failed` (render)
  requires `erroredServerId`, which the parent sets only for connect-
  attempt failures. A mid-session crash now closes the column like any
  teardown instead of reopening a user-closed column / dropping the live
  Logs tab. This also aligns the column behavior with the red border.
- #3 (minor): extract the column slide duration into a shared
  `monitorColumnAnimation.ts` (MONITOR_COLUMN_ANIM_MS +
  FAILED_CARD_SCROLL_DELAY_MS) so InspectorView's Transition and
  ServerCard's scroll delay can't drift.
- #4 (nit): composite key for ConsoleScreen entry rows instead of the
  array index.

Added a mid-session-crash test (connected → error with no erroredServerId
closes the column). Verified live: a connect failure still opens the
column to Console. Full `npm run ci` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 16:06:33 -04:00
cliffhall
71d33d70c4 feat(web): scroll the failed server card into view on connection failure (#1621)
Opening the monitoring column on a failed connection reflows the server
grid to fewer/narrower columns, which can push the failed card below the
fold. Scroll it into view so the red-bordered card is visible without
hunting for it.

- ServerCard scrolls itself into view on the disconnected→errored
  transition (mirrors the freshly-added highlight scroll), deferred
  ~320ms so the column open + grid reflow settle first — otherwise
  scrollIntoView targets the card's pre-reflow position. Guarded by a ref
  so it fires once per failure and won't fight a user who scrolls away.

Verified live: the failed `time` card scrolls fully into view after the
column opens. Full `npm run ci` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 15:05:34 -04:00
cliffhall
c6999dff96 feat(web): red border on a server card whose connection attempt failed (#1621)
Flags the server whose last connection attempt failed with a red card
border, cleared when another server is connected or a new connection is
attempted — a persistent, at-a-glance companion to the failure toast and
the auto-opened monitoring column.

- App tracks `failedServerId`: set in onToggleConnection's non-auth
  handshake catch, cleared at the start of any new connect attempt. It is
  deliberately kept out of resetSessionScopedUiState, since the failure's
  own `disconnect` event would otherwise clear it the same tick (that same
  event clears `activeServer`, which is why the flag is tracked separately).
- New `errored` Card theme variant (red border via a new
  `--inspector-error-border` token), mirroring the `highlighted` green
  border. Precedence: disabled (dimmed) > errored > highlighted.
- Plumbed `erroredServerId` through InspectorView → ServerListScreen →
  ServerCard (`errored` prop).

Full `npm run ci` passes (validate + coverage + smoke + storybook).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 14:48:17 -04:00
cliffhall
46b311ad5c test(web): pass Console props in InspectorView stories (#1621)
The InspectorView story args predate the new required Console props, so the
"Connection Error" play story crashed rendering (`stderrLogs` undefined →
`.length` throw) — caught by the Storybook step of `npm run ci`, which I
should have run before the previous push.

- Add `stderrLogs` / `consoleUi` / `onConsoleUiChange` / `onClearConsole` /
  `onExportConsole` to the story args, with a `demoStderr` fixture.
- Make the Connection Error story a stdio-failure narrative: it now opens
  the column to Console showing the captured startup error, with `network:
  []` so Network isn't offered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 14:25:46 -04:00
cliffhall
219298b24d feat(web): add Console (stderr) tab; show it on stdio connect failure (#1621)
A failed stdio launch had no useful diagnostic surface: the message log
(History) is cleared on the error transition and there are no HTTP
requests (Network), so the auto-opened monitoring column was empty. The
process's stderr — which explains the failure (e.g. "No module named
mcp_server_time") — was captured in StderrLogState but never displayed.

- New ConsoleScreen renders the captured stderr stream (search / sort /
  clear / export; embeddable in the monitoring column), wired through App
  via the existing useStderrLog hook + StderrLogState.
- Console is a pinnable monitor tab like Logs/History/Network, gated to
  stdio servers (HTTP has no child process). It lives in the header menu
  when the column isn't pinned, and moves into the column when it is.
- The failure column now picks its tabs by which diagnostic actually
  captured content (stderr → Console, fetch requests → Network), not by
  the active server's transport: a connect failure fires the client
  `disconnect` event, which clears `activeServer`, so transport can't be
  read back there. The memo re-runs as stderr/fetch entries stream in, so
  the diagnostic tab appears as soon as the failing process prints.

Verified live against a failing `python -m mcp_server_time` stdio server:
the column auto-opens to Console showing the real ModuleNotFoundError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 14:06:13 -04:00
cliffhall
85153c6232 feat(web): open the monitoring column on a failed connection (#1621)
On a connection failure the user was left without an obvious way to see
why. Open the monitoring column automatically on the transition into the
`error` status (mirroring the connect-success behavior from #1616) so the
diagnostics surface right when they're needed.

- Broaden the auto-open effect to fire on the transition into `error` as
  well as `connected`, tracked via a general previous-status ref so it
  fires once per transition and never on a mount that starts errored.
- A failed connect never negotiated capabilities, so the failure column
  offers History (the local client-side message log) and — for non-stdio
  transports — Network (HTTP requests), the entries that explain the
  failure. Logs is omitted (it needs the server `logging` capability).
- `effectivePinned` now allows the column while `error`, not only while
  `connected`; it still closes on a clean disconnect or narrowing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 13:12:32 -04:00
cliffhall
97deacd875 test(web): cover compact-layout expand toggle in HistoryEntry
Adds a test that clicks Expand/Collapse in the embedded (compact)
layout, exercising the ExpandToggle onToggle callback that was
uncovered (line 193). Restores HistoryEntry.tsx to 100% coverage,
fixing the CI per-file function-coverage gate failure (was 85.71%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-09 10:19:59 -04:00
cliffhall
32a3610737 Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts:
#	clients/cli/package-lock.json
#	clients/cli/package.json
#	clients/web/src/App.tsx
#	clients/web/src/lib/environmentFactory.test.ts
#	clients/web/src/lib/environmentFactory.ts
#	clients/web/src/lib/remoteOAuthStorage.test.ts
#	clients/web/src/lib/remoteOAuthStorage.ts
#	clients/web/src/test/core/auth/connection-state.test.ts
#	clients/web/src/test/core/auth/providers.test.ts
#	clients/web/src/test/core/auth/storage-browser.test.ts
#	clients/web/src/test/core/auth/storage-remote.test.ts
#	clients/web/src/test/core/mcp/oauthManager.test.ts
#	clients/web/src/test/integration/auth/node/storage.test.ts
#	clients/web/src/test/integration/storage/adapters.test.ts
#	clients/web/src/utils/clearServerOAuthState.test.ts
#	clients/web/src/utils/clearServerOAuthState.ts
#	core/auth/connection-state.ts
#	core/auth/oauth-storage.ts
#	core/auth/providers.ts
#	core/auth/storage.ts
#	core/auth/store.ts
#	core/storage/adapters/remote-storage.ts
2026-07-09 09:41:08 -04:00
cliffhall
20e08e2b1d feat(web): open the monitoring column on connect (#1616)
Auto-open the monitoring column when a connection is established. Gated on the
disconnected → connected transition (a ref-tracked edge), so it fires on an
actual connect rather than on every render while connected or on a mount that
starts already-connected (which would fight a user who closed it). The column
still only appears when wide and a monitor tab is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 22:26:06 -04:00
cliffhall
efddd94bea fix(web): use a time-only timestamp in the compact column entries (#1616)
At the 320px column minimum the full ISO timestamp ate most of line 1. Use an
HH:MM:SS (UTC) form in the embedded HistoryEntry/NetworkEntry headers only; the
full-size layouts keep the full ISO string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:59:23 -04:00
cliffhall
3550b64032 feat(web): compact two-line HistoryEntry/NetworkEntry for the narrow column (#1616)
In the pinned monitoring column (embedded), the single-line entry headers wrap
badly. Add compact two-line variants gated on `embedded`:

- HistoryEntry: line 1 = time + client/server badge + elapsed + status; line 2 =
  method (and target, truncated) on the left with the controls on the right,
  where Replay becomes an icon (new ReplayButton element) next to Pin/Expand.
- NetworkEntry: line 1 = time + HTTP method + category + elapsed + status; line 2
  = the URL in a horizontal ScrollArea (new Text `nowrap` variant) with the
  expand toggle on the right.

The full-size (non-embedded) layouts are unchanged. `embedded` is threaded from
the History/Network panels into the entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:53:23 -04:00
cliffhall
bcf10c75dd style(web): put the timestamp first in HistoryEntry to match NetworkEntry
Move the Client/Server direction badge to the right of the timestamp so the
monospace timestamp is leftmost, matching the NetworkEntry header layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:28:48 -04:00
cliffhall
aba77d47f5 fix(web): restore resize handle + refine column close/animation behavior (#1616)
- Fix the resize handle disappearing: the animation wrapper is a Mantine Group
  (default align: center), and the full-height handle has no intrinsic height, so
  it collapsed to 0. Set align: stretch on the wrapper.
- Closing the column now leaves the primary on the screen currently selected in
  the header (commits the active tab first), instead of swapping in the column's
  screen.
- Keep the primary screen's standard ScreenStage transition; only the column
  (handle + column) slides open/closed from the right with a fade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:25:36 -04:00
cliffhall
c5e064ff71 feat(web): monitoring column search, close-to-fullscreen, and open/close animation (#1616)
- Add a search box to MonitoringControls (right of the tabs). It's a single
  shared search kept across column tab switches, filtering whichever screen is
  shown. Embedded panels apply this text but still ignore the sidebar-only
  filters (levels/categories/directions/method), which have no control there.
- Close button now uses the "collapse sidebar" icon (TbLayoutSidebarRightCollapse),
  the mirror of the pin button's expand icon.
- Closing the column carries its selection to the primary view: the screen the
  user was watching becomes the active header tab (rather than snapping back to
  the pre-pin selection).
- Animate the column open/close: the handle + column slide in from the right and
  fade (reversing on close) via a Mantine Transition.
- HistoryListPanel: drop the redundant "History (N)" header when there are no
  pinned requests (lone unpinned section renders headerless).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:16:15 -04:00
cliffhall
9b76272ad1 fix(web): address review feedback on the monitoring column (#1616)
- ResizeHandle: clear the `resizing-col` body class on unmount so a drag
  interrupted by the handle unmounting (narrow past the breakpoint / disconnect)
  can't leave page-wide user-select:none / col-resize applied.
- Embedded monitor column now shows the full unfiltered stream instead of
  silently mirroring the full-size screen's live filter (the column has no
  filter control to explain an empty/partial list).
- MonitoringControls: give the SegmentedControl an aria-label ("Monitoring
  screen") so the tab group has an accessible name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:41:00 -04:00
cliffhall
daf736c6e7 style(web): match the pin-as-column button to the expand/collapse toggle
Render PinColumnButton as a subtle size-sm Button with a size-20 icon, the same
style as the panel's ListToggle, instead of a default ActionIcon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:26:35 -04:00
cliffhall
591d6b9736 refactor(web): move expand/collapse toggle between Export and the pin button
On the History and Network panels, place the ListToggle (expand/collapse)
after Export and before the pin-as-column button. (Logs has no such toggle.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:24:25 -04:00
cliffhall
3713e71b18 refactor(web): move the pin-as-column button to the far right of each toolbar
Place the "pin as column" icon after Clear/Export (rightmost) on the Logs,
History, and Network panel toolbars instead of at the front.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:20:24 -04:00
cliffhall
081fd789f1 feat(web): pin Logging/History/Network as a resizable column (#1616)
Add the ability to "pin" the Logging, History, and Network screens into a
second column on the right of the InspectorView, so a primary screen and a
monitor screen are visible at once (e.g. watch Logs while calling a Tool).

- A "pin as column" button on the Logs/History/Network panel toolbars opens
  the column (shown only when wide + connected).
- The column hosts a MonitoringControls tab row (available monitor tabs +
  close) over the selected embedded screen. Pinning removes the monitor group
  from the header tab bar; closing (x) returns it.
- The divider between the primary area and the column is draggable (pointer +
  keyboard); width is clamped and persisted.
- The split collapses to one column below ~1040px (the ServerListScreen
  single-card breakpoint) and while disconnected, without discarding the pin
  preference — it reopens when wide + connected again.

State (pinned/tab/width) is view-local and persisted via the existing
useLocalStorage pattern, so no new props thread through App and ViewHeader is
unchanged (it just receives a filtered tab list). Screens/panels gain an
`embedded` mode (fills the parent height, drops the 340px filter sidebar) via a
shared EmbeddableScrollArea element.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:52:41 -04:00
cliffhall
98517c2758 test(auth): fix type error in remote backend write() test
`tsc -b` (run in CI's validate step, but not by vitest locally) rejected
`ok.mock.calls[0][1].body` — under the project's strict indexing the
untyped mock's call tuple is `[]`, so `[0]` is possibly-undefined and
`[1]` is out of range. Capture the request body inside a `vi.fn<typeof
fetch>` implementation instead of indexing into `mock.calls`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:43:41 -04:00
cliffhall
4c30dc6e2e test(auth): make the browser-safe guard transitive
Extend the Node-free guard beyond oauth-persist.ts to every module
reachable from the browser's RemoteOAuthStorage (storage-remote,
oauth-storage, store, oauth-persist, store-serialize), so a forbidden
store-io/node:/atomically import added one hop away is caught too — not
just a direct import in oauth-persist.ts. Addresses the non-blocking
hardening note on Claude review of #1612.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:25:14 -04:00
cliffhall
1e6a2825d4 fix(auth): keep oauth-persist browser-safe (move Node file backend out)
The refactor routed the browser's RemoteOAuthStorage through
`oauth-persist.ts`, which value-imported the Node-only `store-io.js`
(`atomically` -> `stubborn-fs` -> `node:process.getuid`, plus
`node:fs/promises`). That dragged Node built-ins into the browser bundle
and the web app crashed to a blank page at runtime — a regression from
v2/main, where browser OAuth storage used a Zustand JSON adapter with no
Node imports. Unit/integration tests run in node/happy-dom and
`smoke:web` only checks the served HTML, so none of them execute the app
in a real browser and the break slipped through.

Fix, following the existing `store-id.ts` precedent:
- Split the pure JSON (de)serializers into Node-free
  `core/storage/store-serialize.ts`; `store-io.ts` re-exports them so
  existing importers are unaffected.
- Move the Node-only `createFileOAuthPersistBackend` into
  `core/auth/node/oauth-persist-file.ts`; update `storage-node.ts` and
  the storage adapters test to import from there.
- `oauth-persist.ts` now imports only the Node-free serializers, so the
  browser bundle no longer pulls in `node:fs`/`atomically`.

Tests:
- oauth-persist-browser-safe.test.ts: source-level guard that
  `oauth-persist.ts` never imports store-io/node:/atomically.
- Direct unit tests for the isomorphic remote + session backends
  (restoring oauth-persist.ts coverage after the file backend moved out).
- store.test.ts: OAuthMemoryStore unit coverage incl. replace() fallbacks.
- storage.test.ts: clearAllOAuthClientState null-snapshot (no file) path.

Verified end-to-end in a real browser (Playwright): DCR OAuth flow
completes, tokens persist via the remote store, and after a full page
reload the server reconnects with no re-authentication.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:20:31 -04:00
cliffhall
cdeb2316d0 test(auth): assert parseOAuthPersistBlob({}) returns null
Document the contract the remote read() path leans on — the server's
missing-file response is `c.json({}, 200)`, and the backend now passes
that empty object straight to parseOAuthPersistBlob. Addresses the
optional note on Claude review of #1612.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 13:57:16 -04:00
cliffhall
edc7dbb1c1 Merge remote-tracking branch 'origin/v2/main' into v2/1549-oauth-storage-no-zustand
# Conflicts:
#	core/auth/store.ts
2026-07-08 13:44:12 -04:00
cliffhall
ba48e06ffd fix(auth): guard OAuth-storage async paths against unhandled rejections
Review follow-ups on the RemoteOAuthStorage web migration:

- useEmaIdpLoginState.logout(): add a .catch to the now-async
  clearEmaIdpSession call so a storage failure no longer surfaces as an
  unhandled rejection; leave loginState unchanged on failure (the session
  was not actually cleared). Add a test asserting no unhandled rejection.
- createOAuthStore: attach a no-op catch to the eager persistLoadPromise
  so a hydration read that fails before any load() awaits it does not
  become an unhandled rejection; real awaiters still observe the rejection
  via the shared reference.
- storage-remote.test: replace `as unknown as typeof fetch` double-casts
  with the typed vi.fn<typeof fetch>() pattern the same PR established.
- remoteOAuthStorage: correct the doc comment to describe the cache-of-one
  accurately instead of implying a keyed map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 10:59:40 -04:00
Bob Dickinson
af174666d6 fix(auth): address Claude review on OAuth persist serialization
Serialize persist() in OAuthStorageBase so overlapping remote POSTs cannot
reorder and clobber state. Regenerate launcher package-lock.json to drop
stale zustand entries. Let parseOAuthPersistBlob accept parsed objects
from res.json() without a redundant JSON round-trip.
2026-07-05 18:38:13 -07:00
Bob Dickinson
3c65bdb4c6 Removed Zustand storage, cleanup up auth storage IO interfaces, implementations, and usage. 2026-07-04 21:56:45 -07:00
Bob Dickinson
512f45d357 fix(auth): reject load() when persist read fails 2026-07-03 14:50:04 -07:00
cliffhall
cfbdfdf2bd Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts:
#	AGENTS.md
#	clients/cli/README.md
#	clients/web/src/App.test.tsx
#	clients/web/src/App.tsx
#	clients/web/src/test/core/auth/utils.test.ts
#	core/mcp/node/transport.ts
#	test-servers/src/composable-test-server.ts
2026-07-03 17:39:07 -04:00
Bob Dickinson
a24091484c fix(auth): await OAuth storage load() to fix remote OAuth races 2026-07-03 14:18:00 -07:00
Bob Dickinson
7bd26c938e refactor: migrate to RemoteOAuthStorage for web OAuth handling 2026-07-03 13:11:13 -07:00
cliffhall
143e0d3f71 test+docs: add inspectorTabs test; document mid-session auth modules
- Add a dedicated `inspectorTabs.test.ts` (was relying only on incidental
  coverage from App.test.tsx) — covers the tab-id constants and both branches
  of the `isInspectorTabId` type guard; 100% per-file.
- AGENTS.md: extend the `core/auth/` structure-tree entry to describe the new
  mid-session recovery modules (challenge parsing, scope union, shared OAuth UX
  copy, force-reauthorization) and the node loopback callback flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZyeWManYTvcXxjcrhxSQc
2026-07-03 13:20:29 -04:00
cliffhall
74c6491b52 refactor(web): resolve Mantine/CSS rule violations in mid-session auth UI
- App.tsx: replace the inline `style={{}}` on the re-auth banner wrapper with
  Mantine style props (pos/top/bg) plus a `.reauth-banner-bar` class for the
  z-index/box-shadow that have no Mantine shorthand.
- App.tsx: extract the ~100-line inline `onAuthorize` and the inline `onCancel`
  step-up handlers into named `handleStepUpAuthorize`/`handleStepUpCancel`.
- ReAuthBanner: move the flat `styles={{ root }}` (background/border) into a
  `reauth` Alert theme variant; replace the raw `<span>` with `<Text>`.
- StepUpAuthModal: extract the inline `onClick` wrapper into `handleAuthorize`.

No behavior change; validate + integration green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZyeWManYTvcXxjcrhxSQc
2026-07-03 13:03:01 -04:00
cliffhall
f4b6095719 test: close ≥90% per-file coverage gaps for mid-session auth
The mid-session auth work passed `validate`/CI but failed the local-only
`npm run test:coverage` per-file gate (≥90% lines/statements/functions/
branches) on 12 files. Add targeted unit/integration tests so every changed
file clears the gate; annotate provably-dead defensive branches with justified
`/* v8 ignore */` comments rather than relaxing the gate.

Files brought to ≥90 on all four dimensions:
- core/auth: mcpAuth, oauthUx, utils, challenge, node/runner-interactive-oauth
- core/mcp: oauthManager, inspectorClient, node/authChallengeFetch,
  remote/remoteClientTransport, remote/node/{remote-session,server}
- clients/web/src/utils: oauthResume, browserTabVisibility

`npm run test:coverage` now passes (3575 tests, 0 threshold failures);
`npm run validate` green across all four clients.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZyeWManYTvcXxjcrhxSQc
2026-07-03 12:54:37 -04:00
Bob Dickinson
a066873e37 Fixes for remaining issues 2026-07-02 18:36:44 -07:00
Bob Dickinson
8f1ed7588d Prettier fix on last commit 2026-07-02 18:03:18 -07:00