@marimo-team/islands 0.19.7-dev36 → 0.19.7-dev37

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/dist/main.js CHANGED
@@ -73168,7 +73168,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
73168
73168
  return Logger.warn("Failed to get version from mount config"), null;
73169
73169
  }
73170
73170
  }
73171
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev36"), showCodeInRunModeAtom = atom(true);
73171
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev37"), showCodeInRunModeAtom = atom(true);
73172
73172
  atom(null);
73173
73173
  var import_compiler_runtime$88 = require_compiler_runtime();
73174
73174
  function useKeydownOnElement(e, r) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.19.7-dev36",
3
+ "version": "0.19.7-dev37",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -37,6 +37,7 @@ import {
37
37
  type PanelType,
38
38
  } from "../types";
39
39
  import { BackendConnectionStatus } from "./footer-items/backend-status";
40
+ import { LspStatus } from "./footer-items/lsp-status";
40
41
  import { PanelsWrapper } from "./panels";
41
42
  import { PendingAICells } from "./pending-ai-cells";
42
43
  import { useAiPanelTab } from "./useAiPanel";
@@ -490,6 +491,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
490
491
  />
491
492
  <div className="border-l border-border h-4 mx-1" />
492
493
  <BackendConnectionStatus />
494
+ <LspStatus />
493
495
  <div className="flex-1" />
494
496
  <Button
495
497
  size="xs"
@@ -0,0 +1,178 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { atom, useAtomValue, useSetAtom } from "jotai";
4
+ import { AlertCircleIcon, CheckCircle2Icon } from "lucide-react";
5
+ import type React from "react";
6
+ import { Spinner } from "@/components/icons/spinner";
7
+ import { Tooltip } from "@/components/ui/tooltip";
8
+ import { toast } from "@/components/ui/use-toast";
9
+ import { API } from "@/core/network/api";
10
+ import { connectionAtom } from "@/core/network/connection";
11
+ import type {
12
+ LspHealthResponse,
13
+ LspRestartRequest,
14
+ LspRestartResponse,
15
+ } from "@/core/network/types";
16
+ import { isAppConnected } from "@/core/websocket/connection-utils";
17
+ import { useAsyncData } from "@/hooks/useAsyncData";
18
+ import { useInterval } from "@/hooks/useInterval";
19
+
20
+ const CHECK_LSP_HEALTH_INTERVAL_MS = 60_000;
21
+
22
+ export const lspHealthAtom = atom<LspHealthResponse | null>(null);
23
+
24
+ export const LspStatus: React.FC = () => {
25
+ const connection = useAtomValue(connectionAtom).state;
26
+ const setLspHealth = useSetAtom(lspHealthAtom);
27
+
28
+ const { isFetching, data, refetch } = useAsyncData(async () => {
29
+ if (!isAppConnected(connection)) {
30
+ return null;
31
+ }
32
+
33
+ try {
34
+ const health = await API.get<LspHealthResponse>("/lsp/health");
35
+ setLspHealth(health);
36
+ return health;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }, [connection]);
41
+
42
+ useInterval(refetch, {
43
+ delayMs: isAppConnected(connection) ? CHECK_LSP_HEALTH_INTERVAL_MS : null,
44
+ whenVisible: true,
45
+ });
46
+
47
+ const handleRestart = async () => {
48
+ try {
49
+ const result = await API.post<LspRestartRequest, LspRestartResponse>(
50
+ "/lsp/restart",
51
+ {},
52
+ );
53
+
54
+ if (result.success) {
55
+ toast({
56
+ title: "LSP Servers Restarted",
57
+ description:
58
+ result.restarted.length > 0
59
+ ? `Restarted: ${result.restarted.join(", ")}`
60
+ : "No servers needed restart",
61
+ });
62
+ } else {
63
+ toast({
64
+ variant: "danger",
65
+ title: "LSP Restart Failed",
66
+ description: Object.entries(result.errors ?? {})
67
+ .map(([k, v]) => `${k}: ${v}`)
68
+ .join("\n"),
69
+ });
70
+ }
71
+
72
+ // Refresh health status
73
+ refetch();
74
+ } catch (error) {
75
+ toast({
76
+ variant: "danger",
77
+ title: "LSP Restart Failed",
78
+ description: error instanceof Error ? error.message : "Unknown error",
79
+ });
80
+ }
81
+ };
82
+
83
+ // Don't show if no LSP servers are configured
84
+ if (!data || data.servers.length === 0) {
85
+ return null;
86
+ }
87
+
88
+ const getStatusIcon = () => {
89
+ if (isFetching) {
90
+ return <Spinner size="small" />;
91
+ }
92
+
93
+ if (!data) {
94
+ return <AlertCircleIcon className="w-4 h-4" />;
95
+ }
96
+
97
+ switch (data.status) {
98
+ case "healthy":
99
+ return <CheckCircle2Icon className="w-4 h-4 text-(--green-9)" />;
100
+ case "degraded":
101
+ return <AlertCircleIcon className="w-4 h-4 text-(--yellow-11)" />;
102
+ case "unhealthy":
103
+ return <AlertCircleIcon className="w-4 h-4 text-(--yellow-11)" />;
104
+ }
105
+ };
106
+
107
+ const getServerStatusDisplay = (
108
+ status: "starting" | "running" | "stopped" | "crashed" | "unresponsive",
109
+ lastPingMs: number | null | undefined,
110
+ ) => {
111
+ switch (status) {
112
+ case "running":
113
+ return `✓ OK${lastPingMs == null ? "" : ` (${lastPingMs.toFixed(0)}ms)`}`;
114
+ case "starting":
115
+ return "⋯ Starting";
116
+ case "stopped":
117
+ return "✗ Stopped";
118
+ case "crashed":
119
+ return "✗ Crashed";
120
+ case "unresponsive":
121
+ return "✗ Not responding";
122
+ }
123
+ };
124
+
125
+ const getServerStatusColor = (
126
+ status: "starting" | "running" | "stopped" | "crashed" | "unresponsive",
127
+ ) => {
128
+ switch (status) {
129
+ case "running":
130
+ return "text-(--green-9)";
131
+ case "starting":
132
+ return "text-(--yellow-11)";
133
+ default:
134
+ return "text-(--red-9)";
135
+ }
136
+ };
137
+
138
+ const tooltipContent = (
139
+ <div className="text-sm">
140
+ <b>LSP Status</b>
141
+ <div className="mt-1 text-xs space-y-1">
142
+ {data?.servers.map((server) => (
143
+ <div key={server.serverId} className="flex justify-between gap-2">
144
+ <span>{server.serverId}</span>
145
+ <span className={getServerStatusColor(server.status)}>
146
+ {getServerStatusDisplay(server.status, server.lastPingMs)}
147
+ </span>
148
+ </div>
149
+ ))}
150
+ </div>
151
+ {data?.status === "healthy" ? null : (
152
+ <div className="mt-2 text-xs text-muted-foreground">
153
+ Click to restart failed servers
154
+ </div>
155
+ )}
156
+ </div>
157
+ );
158
+
159
+ const handleClick = () => {
160
+ if (data?.status !== "healthy") {
161
+ void handleRestart();
162
+ }
163
+ };
164
+
165
+ return (
166
+ <Tooltip content={tooltipContent} data-testid="footer-lsp-status">
167
+ <button
168
+ type="button"
169
+ onClick={handleClick}
170
+ className="p-1 hover:bg-accent rounded flex items-center gap-1.5 text-xs text-muted-foreground"
171
+ data-testid="lsp-status"
172
+ >
173
+ {getStatusIcon()}
174
+ <span>LSP</span>
175
+ </button>
176
+ </Tooltip>
177
+ );
178
+ };
@@ -100,6 +100,10 @@ export type InvokeAiToolRequest = schemas["InvokeAiToolRequest"];
100
100
  export type InvokeAiToolResponse = schemas["InvokeAiToolResponse"];
101
101
  export type ClearCacheRequest = schemas["ClearCacheRequest"];
102
102
  export type GetCacheInfoRequest = schemas["GetCacheInfoRequest"];
103
+ export type LspHealthResponse = schemas["LspHealthResponse"];
104
+ export type LspRestartRequest = schemas["LspRestartRequest"];
105
+ export type LspRestartResponse = schemas["LspRestartResponse"];
106
+ export type LspServerHealth = schemas["LspServerHealth"];
103
107
 
104
108
  /**
105
109
  * Requests sent to the BE during run/edit mode.