Conversation
ServerStatusIndicator keeps the availability classification and the authorize branch, and composes ui/status-indicator for the icon, label and popover, so other surfaces can reuse the shape without the server status model. Names the popover content as well. Radix gives PopoverContent role=dialog without a name, and the trigger string ends in Show details, which reads as an instruction to open what is already open. Signed-off-by: Anna Effort <anna.effort@ibm.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The reusable component can still render an unnamed dialog when its optional content label is omitted.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
What changed in this PR
Extracts a reusable status-indicator presentation component while retaining server-specific availability and authorization logic.
Changes:
- Adds the reusable
StatusIndicatorcomponent and tests. - Refactors
ServerStatusIndicatorto compose it. - Adds localized popover accessible names.
| File | Description |
|---|---|
src/components/ui/status-indicator.tsx |
Adds the reusable status UI shell. |
src/components/ui/status-indicator.test.tsx |
Tests rendering, popovers, and accessibility. |
src/components/servers/ServerStatusIndicator.tsx |
Uses the extracted component. |
src/i18n/locales/en-US/mcpServer.json |
Adds the English dialog label. |
src/i18n/locales/es-ES/mcpServer.json |
Adds the Spanish dialog label. |
src/i18n/locales/pt-BR/mcpServer.json |
Adds the Portuguese dialog label. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
contentAriaLabel is optional, so a caller passing children without it produced the unnamed dialog this component exists to avoid. Falls back to fullLabel and then label, both of which describe the status the popover explains. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Extracting the shell split one pair of class strings across two files. Also covers the trigger's accessible name, which comes from its content rather than triggerAriaLabel. Signed-off-by: Anna Effort <anna.effort@ibm.com>
marekdano
left a comment
There was a problem hiding this comment.
Findings
🟠 Medium
1. isAbbreviated truthy check drops falsy fullLabel
File: src/components/ui/status-indicator.tsx:58
isAbbreviated uses Boolean(fullLabel && fullLabel !== label) — a truthy check instead of an "is defined" check. If fullLabel is ever "" while label is non-empty (e.g. a blank/missing translation), it's treated as "not abbreviated": the sr-only full-label span is skipped, and the aria-label fallback chain (contentAriaLabel ?? fullLabel ?? label) skips the falsy empty string too — silently losing the intended accessible name.
2. Status icon JSX duplicated across two files
File: src/components/servers/ServerStatusIndicator.tsx:66 (vs. status-indicator.tsx:63-67)
The status icon JSX is now duplicated (once in the authorize-button branch, once in StatusIndicator's content builder), where before it was one shared icon const. Any future icon/a11y tweak has to be made in both places, or the authorize button's icon silently desyncs from every other state. The exported STATUS_INDICATOR_TRIGGER_CLASS constant looks like the escape-hatch symptom of the abstraction boundary being drawn in the wrong place.
3. triggerAriaLabel optional despite being "required"
File: src/components/ui/status-indicator.tsx:16
triggerAriaLabel is typed optional even though its doc comment says it's "Required wherever children are given," with no runtime enforcement. A future caller with children but no triggerAriaLabel silently loses the guaranteed descriptive name.
🟡 Low
4. Dead size prop reinvents existing pattern
File: src/components/ui/status-indicator.tsx:19, 27-30
The new size?: "xs" | "sm" prop and its hand-rolled SIZE_CLASS map are dead code — no caller anywhere passes size — and reinvent a pattern the codebase already has via class-variance-authority (used for size variants in button.tsx and badge.tsx).
5. Docstring claims consumers that don't exist yet
File: src/components/ui/status-indicator.tsx:43
The docstring claims the catalog card and source picker are consumers sharing this shape, but this PR only migrates ServerStatusIndicator. CatalogResults.tsx and SourceSelection.tsx still hand-roll their own icon+label markup — the docstring is currently misleading until those two are migrated.
6. Unconditional formatMessage calls when non-interactive
File: src/components/servers/ServerStatusIndicator.tsx:110
triggerAriaLabel/contentAriaLabel are computed via intl.formatMessage unconditionally, even when interactive={false} (where StatusIndicator discards both and renders a plain span). The old code early-returned before this formatting.
7. Ambiguous aria-label fallback for popover content
File: src/components/ui/status-indicator.tsx:90
When a caller omits contentAriaLabel, the popover's aria-label falls back to the bare status word (e.g. "Available") with no entity identifier — multiple indicators for different entities would get identical accessible names. Better than pre-PR (no aria-label at all), but ambiguous.
vishu-bh
left a comment
There was a problem hiding this comment.
implementation looks clean and focused. A minor edge case to check
| </> | ||
| ); | ||
|
|
||
| if (!interactive || !children) { |
There was a problem hiding this comment.
children is typed as ReactNode, so 0 is valid renderable content. This truthiness check treats 0 as absent and silently disables the popover. Could we use children == null instead and add a children={0} regression test?
Share the status icon with the authorize branch, drop the unused size prop, and fall through a blank content label rather than leaving the popover dialog unnamed. Signed-off-by: Anna Effort <anna.effort@ibm.com>
|
Thanks for reviewing! Changes added in 82ac6a2 & f025d8c. Details: @marekdano's review: From LLM-assisted analysis of the feedback, the recommendation is to move forward with these changes: Feedback item 2: implemented.
Feedback item 4: implemented.
Feedback item 5: implemented.
Feedback item 1: partly implemented.
From LLM-assisted analysis of the feedback, the recommendation is to not move forward with these changes: Feedback item 3: not implemented.
Feedback item 7: not implemented.
Feedback item 6: not implemented.
@vishu-bh's review:
Lmk if I'm missing something, though pls! 🙌 |
No caller passes it, and it cannot stop a button nesting inside one: it only lets a caller who already knows to avoid it. Signed-off-by: Anna Effort <anna.effort@ibm.com>
marekdano
left a comment
There was a problem hiding this comment.
All findings have been addressed!
The PR looks good now.
LGTM 🚀
vishu-bh
left a comment
There was a problem hiding this comment.
PR looks god to merge but needs conflicts resolution

Moves the presentational part of
ServerStatusIndicatorintoui/status-indicator, so other surfaces can reuse the icon, label and popover without the server availability model.ServerStatusIndicatorkeeps the classification and the authorize branch, and composes the new component.Behaviour is unchanged.
ServerStatusIndicator.test.tsxis the same as on main and still passes.PopoverContentalso gets anaria-label. Radix gives itrole="dialog"with no name, so it announced as an unnamed dialog. The trigger string will not work here, since it ends in "Show details" and would tell you to open something already open.Sequence note: merge after #150, which rewrites the same return statement. Then, rebase this onto #150, then #153 onto this.