@anchrd/intel-ui 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/src/i18n/en.json CHANGED
@@ -147,6 +147,11 @@
147
147
  "tools.emptyHelp": "The portal answered, and it offers your account no tools. An administrator decides in the portal which servers exist and who may reach them.",
148
148
  "tools.unreachable": "Portal not reachable",
149
149
  "tools.unreachableHelp": "The company MCP portal did not answer, so Intel cannot tell which tools you have. Nothing is stored here; try again once the portal responds.",
150
+ "tools.connectExpired": "The portal sign-in took too long",
151
+ "tools.connectExpiredHelp": "The connection to the company MCP portal has to be completed within a few minutes, and this one ran out while the authorization screen was open. Nothing went wrong and nothing was refused — start it again and confirm the servers when the screen appears.",
152
+ "tools.connectRetry": "Start again",
153
+ "tools.connectFailed": "Portal sign-in failed",
154
+ "tools.connectFailedHelp": "The sign-in to the company MCP portal broke before it could be answered. This is a technical fault, not a decision about your access — reload to try again, and tell an administrator if it stays.",
150
155
  "tools.origin": "Origin",
151
156
  "tools.originPortal": "Company portal",
152
157
  "tools.inputSchema": "Input schema",
@@ -1,7 +1,7 @@
1
1
  import type { ToolCapability } from "@anchrd/intel-contract";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { useRouterState } from "@tanstack/react-router";
4
- import { AlertTriangle, ChevronRight, PlugZap, ShieldOff, Wrench } from "lucide-react";
4
+ import { AlertTriangle, ChevronRight, PlugZap, ShieldOff, Timer, Wrench } from "lucide-react";
5
5
  import * as React from "react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
@@ -44,11 +44,17 @@ export function Tools() {
44
44
  // A hit from the header search arrives as `?select=<tool name>`, and the row it names opens with
45
45
  // the list. Nothing else about the row changes: it is still only a disclosure.
46
46
  const requested = useRouterState({ select: (state) => selectedFrom(state.location.search) });
47
- // The silent sign-in returns here with a marker when the portal refused. Only its presence is
48
- // used: the value comes from outside and could carry provider detail, so it is never rendered.
49
- const refused =
50
- typeof window !== "undefined" &&
51
- new URLSearchParams(window.location.search).has("connectError");
47
+ // The silent sign-in returns here with a marker when it failed. The value comes from outside
48
+ // and could carry provider detail, so it is never rendered — it is only compared against the
49
+ // codes Intel itself writes, to tell "you may not" from "the sign-in broke" (#93): the first is
50
+ // a sentence about access, the second must not masquerade as one.
51
+ const connectError =
52
+ typeof window === "undefined"
53
+ ? null
54
+ : new URLSearchParams(window.location.search).get("connectError");
55
+ const refused = connectError !== null;
56
+ const accessDenied =
57
+ connectError === "portal_sign_in_refused" || connectError === "permission_required";
52
58
  const [signingIn, setSigningIn] = React.useState(false);
53
59
 
54
60
  // ⚠️ Nobody is asked to start this. The portal sign-in is a browser redirect, so the screen
@@ -90,7 +96,20 @@ export function Tools() {
90
96
  ) : !catalog.data.portalConnected ? (
91
97
  signingIn ? (
92
98
  <p className="text-sm text-muted-foreground">{i18n.t("tools.signingIn")}</p>
93
- ) : (
99
+ ) : connectError === "portal_sign_in_expired" ? (
100
+ // Ran out of time on the consent screen. Nothing is broken and nobody was refused, so
101
+ // neither of the other two sentences fits — and this one is the only case the reader
102
+ // can fix alone, by starting over (#95).
103
+ <Notice
104
+ icon={<Timer aria-hidden="true" className="mx-auto size-8 text-muted-foreground" />}
105
+ title={i18n.t("tools.connectExpired")}
106
+ help={i18n.t("tools.connectExpiredHelp")}
107
+ >
108
+ <Button variant="outline" onClick={() => data.startPortalSignIn("/tools")}>
109
+ {i18n.t("tools.connectRetry")}
110
+ </Button>
111
+ </Notice>
112
+ ) : accessDenied || !refused ? (
94
113
  // The end of the silent walk for somebody no Access policy carries. It is a sentence
95
114
  // about access, not an invitation to connect: there is nothing they could click that
96
115
  // would change the answer, and what the portal replied is not repeated here.
@@ -102,6 +121,16 @@ export function Tools() {
102
121
  title={i18n.t("tools.noAccess")}
103
122
  help={i18n.t("tools.noAccessHelp")}
104
123
  />
124
+ ) : (
125
+ // Every other marker is machinery, not policy: the sign-in broke before anyone could
126
+ // be allowed or refused. Saying "no access" here sent the last reader hunting through
127
+ // Access policies while the real fault sat in a failed client registration (#93).
128
+ <Notice
129
+ alert
130
+ icon={<PlugZap aria-hidden="true" className="mx-auto size-8 text-destructive" />}
131
+ title={i18n.t("tools.connectFailed")}
132
+ help={i18n.t("tools.connectFailedHelp")}
133
+ />
105
134
  )
106
135
  ) : catalog.data.items.length === 0 ? (
107
136
  <Notice