Bladeren bron

fix(app): use blue for server status attention (#39217)

Brendan Allan 2 weken geleden
bovenliggende
commit
284e97068c

+ 23 - 1
packages/app/src/components/status-popover-indicator.test.ts

@@ -1,11 +1,21 @@
 import { describe, expect, test } from "bun:test"
-import { hasNonBlockingServiceIssue, serverStatusDotClass } from "./status-popover-indicator"
+import {
+  hasNonBlockingServiceIssue,
+  hasServiceNeedingAttention,
+  serverStatusDotClass,
+} from "./status-popover-indicator"
 
 describe("serverStatusDotClass", () => {
   test("uses the success token while the server and services are healthy", () => {
     expect(serverStatusDotClass({ ready: true, serverHealth: true, issue: false })).toBe("bg-icon-success-base")
   })
 
+  test("uses the session attention token when a service needs attention", () => {
+    expect(serverStatusDotClass({ ready: true, serverHealth: true, attention: true, issue: true })).toBe(
+      "bg-v2-background-bg-accent",
+    )
+  })
+
   test("uses the warning token for non-blocking issues while the server is online", () => {
     expect(serverStatusDotClass({ ready: true, serverHealth: true, issue: true })).toBe("bg-icon-warning-base")
   })
@@ -34,3 +44,15 @@ describe("hasNonBlockingServiceIssue", () => {
     expect(hasNonBlockingServiceIssue({ mcp: [], lsp: ["connected"] })).toBe(false)
   })
 })
+
+describe("hasServiceNeedingAttention", () => {
+  test("detects MCP states that need user attention", () => {
+    expect(hasServiceNeedingAttention({ mcp: ["needs_auth"] })).toBe(true)
+    expect(hasServiceNeedingAttention({ mcp: ["needs_client_registration"] })).toBe(true)
+  })
+
+  test("ignores states that do not need user attention", () => {
+    expect(hasServiceNeedingAttention({ mcp: ["failed"] })).toBe(false)
+    expect(hasServiceNeedingAttention({ mcp: ["connected", "pending", "disabled"] })).toBe(false)
+  })
+})

+ 11 - 1
packages/app/src/components/status-popover-indicator.ts

@@ -1,6 +1,10 @@
 import type { LspStatus } from "@opencode-ai/sdk/v2/client"
 import type { McpServer } from "@opencode-ai/client/promise"
 
+export function hasServiceNeedingAttention(input: { mcp: Array<McpServer["status"]["status"]> }) {
+  return input.mcp.some((status) => status === "needs_auth" || status === "needs_client_registration")
+}
+
 export function hasNonBlockingServiceIssue(input: {
   mcp: Array<McpServer["status"]["status"]>
   lsp: Array<LspStatus["status"]>
@@ -11,9 +15,15 @@ export function hasNonBlockingServiceIssue(input: {
   )
 }
 
-export function serverStatusDotClass(input: { ready: boolean; serverHealth: boolean | undefined; issue: boolean }) {
+export function serverStatusDotClass(input: {
+  ready: boolean
+  serverHealth: boolean | undefined
+  attention?: boolean
+  issue: boolean
+}) {
   if (input.serverHealth === false) return "bg-icon-critical-base"
   if (!input.ready || input.serverHealth === undefined) return "bg-border-weak-base"
+  if (input.attention) return "bg-v2-background-bg-accent"
   if (input.issue) return "bg-icon-warning-base"
   if (input.serverHealth === true) return "bg-icon-success-base"
   return "bg-border-weak-base"

+ 19 - 1
packages/app/src/components/status-popover.tsx

@@ -9,7 +9,11 @@ import { ServerConnection, useServer } from "@/context/server"
 import { useServerSDK } from "@/context/server-sdk"
 import { useSync } from "@/context/sync"
 import { useGlobal } from "@/context/global"
-import { hasNonBlockingServiceIssue, serverStatusDotClass } from "./status-popover-indicator"
+import {
+  hasNonBlockingServiceIssue,
+  hasServiceNeedingAttention,
+  serverStatusDotClass,
+} from "./status-popover-indicator"
 
 const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody })))
 const ServerBody = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverServerBody })))
@@ -22,6 +26,11 @@ export function StatusPopover() {
   const [shown, setShown] = createSignal(false)
   const serverHealth = () => global.servers.health[server.key]?.healthy
   const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready))
+  const attention = createMemo(() =>
+    hasServiceNeedingAttention({
+      mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
+    }),
+  )
   const issue = createMemo(() =>
     hasNonBlockingServiceIssue({
       mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
@@ -49,6 +58,7 @@ export function StatusPopover() {
             class={`absolute -top-px -right-px size-1.5 rounded-full ${serverStatusDotClass({
               ready: ready(),
               serverHealth: serverHealth(),
+              attention: attention(),
               issue: issue(),
             })}`}
           />
@@ -85,6 +95,11 @@ function DirectoryStatusPopover() {
   const [shown, setShown] = createSignal(false)
   const serverHealth = () => global.servers.health[ServerConnection.key(server().server)]?.healthy
   const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready))
+  const attention = createMemo(() =>
+    hasServiceNeedingAttention({
+      mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
+    }),
+  )
   const issue = createMemo(() =>
     hasNonBlockingServiceIssue({
       mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
@@ -95,6 +110,7 @@ function DirectoryStatusPopover() {
     shown: shown(),
     ready: ready(),
     serverHealth: serverHealth(),
+    attention: attention(),
     issue: issue(),
     label: language.t("status.popover.trigger"),
     onOpenChange: setShown,
@@ -118,6 +134,7 @@ function ServerStatusPopover() {
     shown: shown(),
     ready: serverHealth() !== undefined,
     serverHealth: serverHealth(),
+    attention: false,
     issue: false,
     label: language.t("status.popover.trigger"),
     onOpenChange: setShown,
@@ -135,6 +152,7 @@ type StatusPopoverState = {
   shown: boolean
   ready: boolean
   serverHealth: boolean | undefined
+  attention: boolean
   issue: boolean
   label: string
   onOpenChange: (value: boolean) => void