Skip to content

React and ink upgrade#1146

Merged
cliffhall merged 2 commits intomodelcontextprotocol:v1.5/mainfrom
BobDickinson:react-and-ink-upgrade
Mar 16, 2026
Merged

React and ink upgrade#1146
cliffhall merged 2 commits intomodelcontextprotocol:v1.5/mainfrom
BobDickinson:react-and-ink-upgrade

Conversation

@BobDickinson
Copy link
Copy Markdown
Contributor

Updated @mcp-ui/client so that I could then update React (to 19.x) and Ink (to 6.x) where they used to be for the TUI.

Undid the TUI "background" hack I had to put in place when I rolled it back.

Fixed TUI tab navigation (I thought this was related to the package rollbacks, but it turned out it was not).

@BobDickinson
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 15, 2026

Claude finished @BobDickinson's task in 2m 15s —— View job


PR Review: React and Ink Upgrade

  • Gather context and read changed files
  • Review package changes
  • Review TUI component changes
  • Review web client changes
  • Provide final feedback

Overall: Clean, well-executed upgrade. The changes are minimal and focused on the dependency bump with no unnecessary scope creep. LGTM with a few minor observations below.


Package Changes

The upgrade chain is logical:

  • @mcp-ui/client 6 → 7 (web) → enables React 19
  • react/react-dom 18 → 19 (TUI, web, core)
  • ink 5 → 6 (TUI) → requires React 19
  • All @types/react* updated accordingly
  • @radix-ui/* bumped (all within same major, backward compatible)
  • Root package.json ink override updated correctly from ^5.2.1 to ^6.0.0

Minor observation: core/package.json's react peer dependency is now "^19.0.0", which excludes React 18 consumers. Since inspector-core is "private": true and only consumed internally, this has no external impact — just worth noting if it ever gets published.


ModalBackdrop Removal (TUI)

The deletion of clients/tui/src/components/ModalBackdrop.tsx is clean and correct. Ink 5 lacked backgroundColor support on Box, which required the workaround of filling a region with Text components. Ink 6 adds native support, so:

  • ModalBackdrop is removed entirely
  • All four modal components (DetailsModal, PromptTestModal, ResourceTestModal, ToolTestModal) now use backgroundColor="black" directly on the modal Box container
  • All footer bars across tab components (AuthTab, HistoryTab, InfoTab, PromptsTab, RequestsTab, ResourcesTab, ToolsTab, App.tsx) now use backgroundColor="gray" on their Box wrappers

No leftover imports or references to ModalBackdrop remain.


React 19 Ref Callback Fix (clients/web/src/components/AppsTab.tsx)

// Before (React 18 style)
ref={(ref) => (formRefs.current[key] = ref)}

// After (React 19 compatible)
ref={(ref) => { formRefs.current[key] = ref; }}

This is the right fix. In React 19, ref callback return values are treated as cleanup functions. The expression form implicitly returns the assigned value, which React 19 would attempt to call as a cleanup function on unmount, causing a runtime error. The block body explicitly returns undefined. ToolsTab.tsx already used the block body form and needed no changes — no other similar patterns were found in the codebase.


Tab Selection Bug Fix (clients/tui/src/App.tsx)

// Before: unconditionally reset focus on every tab change
useEffect(() => {
  if (activeTab === "messages") {
    setFocus("messagesList");
  } else if (activeTab === "requests") {
    setFocus("requestsList");
  } else {
    setFocus("tabContentList");
  }
}, [activeTab]);

// After: only adjust focus if it was in the "wrong" region
useEffect(() => {
  if (activeTab === "messages") {
    if (focus === "tabContentList" || focus === "tabContentDetails") {
      setFocus("messagesList");
    }
  } else if (activeTab === "requests") {
    if (focus === "tabContentList" || focus === "tabContentDetails") {
      setFocus("requestsList");
    }
  } else {
    if (focus === "messagesList" || focus === "messagesDetail" ||
        focus === "requestsList" || focus === "requestsDetail") {
      setFocus("tabContentList");
    }
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTab]);

The fix is correct. The old behavior would steal focus away from "tabs" or "serverList" whenever a tab switch occurred (e.g., auto-switching away from the logging tab for non-stdio servers). The new behavior correctly leaves "tabs" and "serverList" focus undisturbed, only redirecting focus that was in an incompatible content region.

The eslint-disable-next-line comment is acceptable here — reading focus inside the effect without listing it as a dependency is intentional to prevent re-triggering the effect when focus changes. The inline comment explains this reasoning.

One small alternative worth considering (not a blocker): using useRef to capture the focus value at the time of the tab change would make the dependency omission explicit at the type level, but the current approach is pragmatic and the comment is clear enough.


Summary

Change Assessment
Package upgrades (React 19, Ink 6, @mcp-ui/client 7) ✅ Correct and complete
ModalBackdrop removal ✅ Clean, no leftover references
backgroundColor on Box across TUI ✅ Consistent application
React 19 ref callback fix ✅ Necessary correctness fix
Tab selection bug fix ✅ Well-reasoned, correctly scoped

No bugs found. Two commits are well-scoped and separated by concern.

@BobDickinson
Copy link
Copy Markdown
Contributor Author

I manually validated the TUI tab selection fix. I also validated several apps to make sure the mcp-ui update didn't break anything obvious.

@BobDickinson BobDickinson requested a review from cliffhall March 15, 2026 07:12
@cliffhall cliffhall added the v1.5 Issues and PRs for v1.5 label Mar 15, 2026
@cliffhall cliffhall merged commit f53f3bc into modelcontextprotocol:v1.5/main Mar 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1.5 Issues and PRs for v1.5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants