@marimo-team/islands 0.24.3-dev81 → 0.24.3-dev83

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.
Files changed (47) hide show
  1. package/dist/{ErrorBoundary-CCi-VkF7.js → ErrorBoundary-Uf_LvjvY.js} +1 -1
  2. package/dist/{cells-BIfOnGBK.js → cells-DYIkSxs6.js} +4 -0
  3. package/dist/{chat-ui-LWkjhObe.js → chat-ui-BXj3uYjy.js} +4 -4
  4. package/dist/{code-visibility-Bsfqbe2z.js → code-visibility-DxgQWu1j.js} +2 -2
  5. package/dist/{common-BnKC1tWB.js → common-ByAtncLr.js} +3 -3
  6. package/dist/{data-grid-overlay-editor-CJnlWykO.js → data-grid-overlay-editor-DL_blFyL.js} +1 -1
  7. package/dist/{error-entries-CR0oClSL.js → error-entries-C0NsUqXl.js} +1 -1
  8. package/dist/{feedback-button-MWISfIIf.js → feedback-button-AZcBqfoV.js} +4 -4
  9. package/dist/{glide-data-editor-dcmP8mKS.js → glide-data-editor-BJdZncJk.js} +3 -3
  10. package/dist/{globals-fCMBgbAD.js → globals-yWarpyN4.js} +1 -1
  11. package/dist/{html-to-image-_i03yR4q.js → html-to-image-CeYYvQNb.js} +1 -1
  12. package/dist/main.js +12 -11
  13. package/dist/{number-overlay-editor-DhNE6pBe.js → number-overlay-editor-BatBVYxr.js} +1 -1
  14. package/dist/{process-output-D3zQyTAS.js → process-output-BCTjlyuH.js} +2 -2
  15. package/dist/{reveal-component-Cdcgngkc.js → reveal-component-D02jcpiJ.js} +3 -3
  16. package/dist/style.css +1 -1
  17. package/package.json +1 -1
  18. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +186 -0
  19. package/src/components/editor/alerts/__tests__/startup-progress.test.tsx +185 -0
  20. package/src/components/editor/alerts/connection-notice.tsx +100 -54
  21. package/src/components/editor/alerts/startup-output.tsx +173 -0
  22. package/src/components/editor/alerts/startup-progress.tsx +204 -0
  23. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +53 -4
  24. package/src/components/editor/chrome/panels/__tests__/sandbox-details.test.tsx +358 -0
  25. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +5 -2
  26. package/src/components/editor/chrome/panels/packages-panel.tsx +32 -21
  27. package/src/components/editor/chrome/panels/sandbox-details-state.ts +38 -0
  28. package/src/components/editor/chrome/panels/sandbox-panel.tsx +188 -131
  29. package/src/components/editor/chrome/panels/sandbox-toggle.tsx +52 -0
  30. package/src/components/editor/chrome/wrapper/app-chrome.tsx +7 -0
  31. package/src/components/editor/package-alert.tsx +112 -66
  32. package/src/core/alerts/__tests__/environment.test.ts +183 -0
  33. package/src/core/alerts/__tests__/state.test.tsx +240 -0
  34. package/src/core/alerts/environment.ts +57 -0
  35. package/src/core/alerts/state.ts +155 -99
  36. package/src/core/islands/bootstrap.ts +2 -1
  37. package/src/core/kernel/messages.ts +2 -2
  38. package/src/core/network/connection-notice.ts +45 -12
  39. package/src/core/network/connection.ts +19 -0
  40. package/src/core/network/useConnectionNotice.ts +53 -0
  41. package/src/core/packages/__tests__/useSandboxController.test.tsx +86 -5
  42. package/src/core/packages/sandbox-state.ts +56 -4
  43. package/src/core/packages/useSandboxController.ts +27 -7
  44. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +199 -8
  45. package/src/core/websocket/types.ts +1 -1
  46. package/src/core/websocket/useMarimoKernelConnection.tsx +31 -19
  47. package/src/stories/sandbox.stories.tsx +230 -50
@@ -0,0 +1,240 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ // @vitest-environment jsdom
3
+
4
+ import { act, renderHook } from "@testing-library/react";
5
+ import { createStore, Provider } from "jotai";
6
+ import { expect, it } from "vitest";
7
+ import type { EnvironmentOperation } from "../environment";
8
+ import { alertAtom, getPackageAlert, useAlertActions } from "../state";
9
+
10
+ it.each(["install", "remove"] as const)(
11
+ "restores active %s progress without announcing old successes",
12
+ (action) => {
13
+ const store = createStore();
14
+ const { result } = renderHook(() => useAlertActions(), {
15
+ wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
16
+ });
17
+ const operation: EnvironmentOperation = {
18
+ operation_id: "package",
19
+ action,
20
+ source: "kernel",
21
+ status: { kind: "succeeded" },
22
+ packages: { numpy: "succeeded" },
23
+ logs: { numpy: "Done\n" },
24
+ };
25
+ const snapshot = (value: EnvironmentOperation) =>
26
+ result.current.setEnvironment({
27
+ source: value.source,
28
+ state: { restart_required: false, operations: [value] },
29
+ });
30
+ act(() => snapshot(operation));
31
+ expect(getPackageAlert(store.get(alertAtom))).toBeNull();
32
+ expect(store.get(alertAtom).environments.kernel.operations).toEqual([
33
+ operation,
34
+ ]);
35
+
36
+ const active: EnvironmentOperation = {
37
+ ...operation,
38
+ status: { kind: "running" },
39
+ packages: { numpy: "running" },
40
+ logs: { numpy: "Working\n" },
41
+ };
42
+ act(() => snapshot(active));
43
+ expect(getPackageAlert(store.get(alertAtom))).toEqual({
44
+ ...active,
45
+ id: active.operation_id,
46
+ kind: "environment",
47
+ restartRequired: false,
48
+ });
49
+
50
+ // Reconnecting can complete work that was already visible on this page.
51
+ act(() => snapshot(operation));
52
+ expect(getPackageAlert(store.get(alertAtom))).toEqual({
53
+ ...operation,
54
+ id: operation.operation_id,
55
+ kind: "environment",
56
+ restartRequired: false,
57
+ });
58
+ act(() => result.current.clearPackageAlert(operation.operation_id));
59
+ act(() => snapshot(operation));
60
+ expect(getPackageAlert(store.get(alertAtom))).toBeNull();
61
+
62
+ // A live result still gives feedback for a new package action.
63
+ act(() =>
64
+ result.current.updateEnvironment({
65
+ ...operation,
66
+ operation_id: "new",
67
+ log_mode: "replace",
68
+ }),
69
+ );
70
+ expect(getPackageAlert(store.get(alertAtom))?.id).toBe("new");
71
+
72
+ // Earlier changes can still require a restart after a later action succeeds.
73
+ act(() => result.current.clearPackageAlert("new"));
74
+ act(() =>
75
+ result.current.setEnvironment({
76
+ source: operation.source,
77
+ state: { restart_required: true, operations: [operation] },
78
+ }),
79
+ );
80
+ expect(getPackageAlert(store.get(alertAtom))).toEqual({
81
+ ...operation,
82
+ id: operation.operation_id,
83
+ kind: "environment",
84
+ restartRequired: true,
85
+ });
86
+ },
87
+ );
88
+
89
+ it.each(["prepare", "sync"] as const)(
90
+ "retains %s progress without turning it into a package alert",
91
+ (action) => {
92
+ const store = createStore();
93
+ const { result } = renderHook(() => useAlertActions(), {
94
+ wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
95
+ });
96
+ const operation: EnvironmentOperation = {
97
+ operation_id: "sandbox",
98
+ action,
99
+ source: "kernel",
100
+ status: { kind: "succeeded" },
101
+ packages: {},
102
+ logs: { environment: "Environment ready\n" },
103
+ };
104
+ const environment = { restart_required: false, operations: [operation] };
105
+
106
+ // A refreshed browser restores the result without announcing it again.
107
+ act(() =>
108
+ result.current.setEnvironment({ source: "kernel", state: environment }),
109
+ );
110
+ expect(getPackageAlert(store.get(alertAtom))).toBeNull();
111
+ expect(store.get(alertAtom).environments.kernel).toEqual(environment);
112
+
113
+ const install: EnvironmentOperation = {
114
+ ...operation,
115
+ operation_id: "install",
116
+ action: "install",
117
+ status: { kind: "running" },
118
+ packages: { numpy: "running" },
119
+ logs: { numpy: "Installing\n" },
120
+ };
121
+ act(() =>
122
+ result.current.updateEnvironment({ ...install, log_mode: "replace" }),
123
+ );
124
+ const packageAlert = getPackageAlert(store.get(alertAtom));
125
+ for (const status of [
126
+ { kind: "running" },
127
+ { kind: "failed", error: "Sync failed" },
128
+ ] as const) {
129
+ act(() =>
130
+ result.current.updateEnvironment({
131
+ ...operation,
132
+ status,
133
+ log_mode: "replace",
134
+ }),
135
+ );
136
+ expect(getPackageAlert(store.get(alertAtom))).toEqual(packageAlert);
137
+ }
138
+
139
+ act(() => result.current.clearPackageAlert(install.operation_id));
140
+ act(() =>
141
+ result.current.updateEnvironment({ ...operation, log_mode: "replace" }),
142
+ );
143
+ expect(getPackageAlert(store.get(alertAtom))).toBeNull();
144
+ },
145
+ );
146
+
147
+ it.each(["kernel", "server"] as const)(
148
+ "preserves the selected %s result across snapshots and follows new work",
149
+ (source) => {
150
+ const store = createStore();
151
+ const { result } = renderHook(() => useAlertActions(), {
152
+ wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
153
+ });
154
+ const selected: EnvironmentOperation = {
155
+ operation_id: "selected",
156
+ action: "install",
157
+ source,
158
+ status: { kind: "failed", error: "Network unavailable" },
159
+ packages: { numpy: "failed" },
160
+ logs: { numpy: "Selected logs\n" },
161
+ };
162
+ const other: EnvironmentOperation = {
163
+ ...selected,
164
+ operation_id: "other",
165
+ source: source === "kernel" ? "server" : "kernel",
166
+ status: { kind: "succeeded" },
167
+ packages: { numpy: "succeeded" },
168
+ logs: { numpy: "Other logs\n" },
169
+ };
170
+ const snapshot = (operation: EnvironmentOperation) =>
171
+ result.current.setEnvironment({
172
+ source: operation.source,
173
+ state: { restart_required: false, operations: [operation] },
174
+ });
175
+ act(() => snapshot(selected));
176
+ for (const operations of [
177
+ [selected, other],
178
+ [other, selected],
179
+ ]) {
180
+ act(() => operations.forEach(snapshot));
181
+ expect(getPackageAlert(store.get(alertAtom))).toEqual({
182
+ ...selected,
183
+ id: selected.operation_id,
184
+ kind: "environment",
185
+ restartRequired: false,
186
+ });
187
+ }
188
+
189
+ // Live work in the other environment should still take over the banner.
190
+ const active: EnvironmentOperation = {
191
+ ...other,
192
+ operation_id: "new-work",
193
+ status: { kind: "running" },
194
+ packages: { numpy: "running" },
195
+ logs: { numpy: "Installing\n" },
196
+ };
197
+ act(() =>
198
+ result.current.updateEnvironment({ ...active, log_mode: "replace" }),
199
+ );
200
+ act(() => snapshot(selected));
201
+ expect(getPackageAlert(store.get(alertAtom))?.id).toBe(active.operation_id);
202
+ act(() =>
203
+ result.current.updateEnvironment({
204
+ ...active,
205
+ status: { kind: "succeeded" },
206
+ packages: { numpy: "succeeded" },
207
+ logs: {},
208
+ log_mode: "append",
209
+ }),
210
+ );
211
+ act(() => snapshot(selected));
212
+ expect(getPackageAlert(store.get(alertAtom))).toEqual({
213
+ ...active,
214
+ id: active.operation_id,
215
+ kind: "environment",
216
+ restartRequired: false,
217
+ status: { kind: "succeeded" },
218
+ packages: { numpy: "succeeded" },
219
+ });
220
+
221
+ // Replacing that environment with an empty snapshot removes its selection.
222
+ act(() =>
223
+ result.current.setEnvironment({
224
+ source: active.source,
225
+ state: { restart_required: false, operations: [] },
226
+ }),
227
+ );
228
+ expect(getPackageAlert(store.get(alertAtom))?.id).toBe(
229
+ selected.operation_id,
230
+ );
231
+ act(() =>
232
+ result.current.updateEnvironment({
233
+ ...other,
234
+ operation_id: "latest-result",
235
+ log_mode: "replace",
236
+ }),
237
+ );
238
+ expect(getPackageAlert(store.get(alertAtom))?.id).toBe("latest-result");
239
+ },
240
+ );
@@ -0,0 +1,57 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { NotificationMessageData, schemas } from "@/core/kernel/messages";
4
+
5
+ export type EnvironmentState = schemas["EnvironmentState"];
6
+ export type EnvironmentOperation = schemas["EnvironmentOperation"];
7
+ export type EnvironmentSource = EnvironmentOperation["source"];
8
+
9
+ export function emptyEnvironmentState(): EnvironmentState {
10
+ return { restart_required: false, operations: [] };
11
+ }
12
+
13
+ /** Apply a live delta after the connection's authoritative snapshot. */
14
+ export function reduceEnvironmentState(
15
+ state: EnvironmentState,
16
+ update: NotificationMessageData<"environment-operation">,
17
+ ): EnvironmentState {
18
+ const previous = state.operations.find(
19
+ (operation) => operation.operation_id === update.operation_id,
20
+ );
21
+ const logs = { ...previous?.logs };
22
+ for (const [name, content] of Object.entries(update.logs)) {
23
+ logs[name] =
24
+ update.log_mode === "replace" ? content : (logs[name] ?? "") + content;
25
+ }
26
+
27
+ const operation: EnvironmentOperation = {
28
+ operation_id: update.operation_id,
29
+ action: update.action,
30
+ status: update.status,
31
+ packages: update.packages,
32
+ logs,
33
+ source: update.source,
34
+ };
35
+ const operations = state.operations.filter(
36
+ (item) =>
37
+ (previous && update.status.kind === "running") ||
38
+ item.status.kind === "running" ||
39
+ (item.action === "prepare" && update.action !== "prepare"),
40
+ );
41
+ const index = operations.findIndex(
42
+ (item) => item.operation_id === update.operation_id,
43
+ );
44
+ if (index === -1) {
45
+ operations.push(operation);
46
+ } else {
47
+ operations[index] = operation;
48
+ }
49
+
50
+ return {
51
+ restart_required:
52
+ state.restart_required ||
53
+ update.status.kind === "restart-required" ||
54
+ Object.values(update.packages).includes("restart-required"),
55
+ operations,
56
+ };
57
+ }
@@ -1,9 +1,16 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { useAtomValue } from "jotai";
4
- import type { PackageInstallationStatus } from "@/core/kernel/messages";
4
+ import type { NotificationMessageData } from "@/core/kernel/messages";
5
5
  import { createReducerAndAtoms } from "@/utils/createReducer";
6
6
  import { generateUUID } from "@/utils/uuid";
7
+ import {
8
+ emptyEnvironmentState,
9
+ type EnvironmentOperation,
10
+ type EnvironmentSource,
11
+ type EnvironmentState,
12
+ reduceEnvironmentState,
13
+ } from "./environment";
7
14
 
8
15
  type Identified<T> = { id: string } & T;
9
16
 
@@ -14,12 +21,18 @@ export interface MissingPackageAlert {
14
21
  source?: "kernel" | "server";
15
22
  }
16
23
 
17
- export interface InstallingPackageAlert {
18
- kind: "installing";
19
- packages: PackageInstallationStatus;
20
- logs?: { [key: string]: string } | null;
21
- log_status?: "append" | "start" | "done" | null;
22
- source?: "kernel" | "server";
24
+ export interface EnvironmentOperationAlert extends EnvironmentOperation {
25
+ action: "install" | "remove";
26
+ kind: "environment";
27
+ restartRequired: boolean;
28
+ }
29
+
30
+ function isPackageOperation(
31
+ operation: EnvironmentOperation,
32
+ ): operation is EnvironmentOperation & {
33
+ action: EnvironmentOperationAlert["action"];
34
+ } {
35
+ return operation.action === "install" || operation.action === "remove";
23
36
  }
24
37
 
25
38
  export interface StartupLogsAlert {
@@ -28,118 +41,161 @@ export interface StartupLogsAlert {
28
41
  }
29
42
 
30
43
  export function isMissingPackageAlert(
31
- alert: MissingPackageAlert | InstallingPackageAlert,
44
+ alert: MissingPackageAlert | EnvironmentOperationAlert,
32
45
  ): alert is MissingPackageAlert {
33
46
  return alert.kind === "missing";
34
47
  }
35
48
 
36
- export function isInstallingPackageAlert(
37
- alert: MissingPackageAlert | InstallingPackageAlert,
38
- ): alert is InstallingPackageAlert {
39
- return alert.kind === "installing";
49
+ export function isEnvironmentOperationAlert(
50
+ alert: MissingPackageAlert | EnvironmentOperationAlert,
51
+ ): alert is EnvironmentOperationAlert {
52
+ return alert.kind === "environment";
40
53
  }
41
54
 
42
- /** Prominent alerts.
43
- *
44
- * Right now we only have one type of alert.
45
- */
46
55
  interface AlertState {
47
56
  packageAlert:
48
57
  | Identified<MissingPackageAlert>
49
- | Identified<InstallingPackageAlert>
58
+ | { kind: "environment"; id: string; source: EnvironmentSource }
50
59
  | null;
60
+ environments: Record<EnvironmentSource, EnvironmentState>;
51
61
  startupLogsAlert: StartupLogsAlert | null;
52
- packageLogs: { [packageName: string]: string };
53
62
  }
54
63
 
55
- const { valueAtom: alertAtom, useActions } = createReducerAndAtoms(
56
- () =>
57
- ({
58
- packageAlert: null,
59
- startupLogsAlert: null,
60
- packageLogs: {},
61
- }) as AlertState,
62
- {
63
- addPackageAlert: (
64
- state,
65
- alert: MissingPackageAlert | InstallingPackageAlert,
66
- ) => {
67
- const newPackageLogs = { ...state.packageLogs };
68
-
69
- // Handle streaming logs for installing package alerts
70
- if (isInstallingPackageAlert(alert) && alert.logs && alert.log_status) {
71
- for (const [packageName, newContent] of Object.entries(alert.logs)) {
72
- switch (alert.log_status) {
73
- case "start":
74
- // Start new log for this package
75
- newPackageLogs[packageName] = newContent;
76
-
77
- break;
78
-
79
- case "append": {
80
- // Append to existing log
81
- const prevContent = newPackageLogs[packageName] || "";
82
- newPackageLogs[packageName] = prevContent + newContent;
83
-
84
- break;
85
- }
86
- case "done": {
87
- // Append final content and mark as done
88
- const prevContent = newPackageLogs[packageName] || "";
89
- newPackageLogs[packageName] = prevContent + newContent;
90
-
91
- break;
92
- }
93
- // No default
94
- }
64
+ function setEnvironment(
65
+ state: AlertState,
66
+ source: EnvironmentSource,
67
+ environment: EnvironmentState,
68
+ ): AlertState {
69
+ // Sandbox preparation and sync use the existing sandbox UI.
70
+ const environments = { ...state.environments, [source]: environment };
71
+ const operations = Object.values(environments)
72
+ .flatMap((item) => item.operations)
73
+ .filter(isPackageOperation);
74
+ const incoming = environment.operations.filter(isPackageOperation);
75
+ const alert = state.packageAlert;
76
+ const selected =
77
+ alert?.kind === "environment"
78
+ ? operations.find(
79
+ (item) =>
80
+ item.operation_id === alert.id && item.source === alert.source,
81
+ )
82
+ : undefined;
83
+ // A snapshot restores work and unresolved issues, not old success banners.
84
+ const needsAttention = (operation: EnvironmentOperation) =>
85
+ operation.status.kind !== "succeeded" ||
86
+ environments[operation.source].restart_required;
87
+ const operation =
88
+ (selected?.status.kind === "running" ? selected : undefined) ??
89
+ incoming.findLast((item) => item.status.kind === "running") ??
90
+ operations.findLast((item) => item.status.kind === "running") ??
91
+ selected ??
92
+ incoming.findLast(needsAttention) ??
93
+ operations.findLast(needsAttention);
94
+ return {
95
+ ...state,
96
+ environments,
97
+ packageAlert: operation
98
+ ? {
99
+ kind: "environment",
100
+ id: operation.operation_id,
101
+ source: operation.source,
95
102
  }
96
- }
97
-
98
- const existingAlert = state.packageAlert;
99
- const alertId = existingAlert?.id || generateUUID();
103
+ : state.packageAlert?.kind === "missing"
104
+ ? state.packageAlert
105
+ : null,
106
+ };
107
+ }
100
108
 
101
- return {
109
+ export const { valueAtom: alertAtom, useActions: useAlertActions } =
110
+ createReducerAndAtoms(
111
+ (): AlertState => ({
112
+ packageAlert: null,
113
+ startupLogsAlert: null,
114
+ environments: {
115
+ kernel: emptyEnvironmentState(),
116
+ server: emptyEnvironmentState(),
117
+ },
118
+ }),
119
+ {
120
+ addMissingPackageAlert: (state, alert: MissingPackageAlert) => ({
102
121
  ...state,
103
- packageAlert: { id: alertId, ...alert },
104
- packageLogs: newPackageLogs,
105
- };
106
- },
107
-
108
- clearPackageAlert: (state, id: string) => {
109
- return state.packageAlert !== null && state.packageAlert.id === id
110
- ? { ...state, packageAlert: null, packageLogs: {} }
111
- : state;
112
- },
113
-
114
- addStartupLog: (
115
- state,
116
- logData: { content: string; status: "append" | "start" | "done" },
117
- ) => {
118
- const prevContent = state.startupLogsAlert?.content || "";
119
- return {
122
+ packageAlert: { id: generateUUID(), ...alert },
123
+ }),
124
+
125
+ updateEnvironment: (
126
+ state,
127
+ update: NotificationMessageData<"environment-operation">,
128
+ ) => {
129
+ const source = update.source;
130
+ const environment = reduceEnvironmentState(
131
+ state.environments[source],
132
+ update,
133
+ );
134
+ if (!isPackageOperation(update)) {
135
+ return {
136
+ ...state,
137
+ environments: { ...state.environments, [source]: environment },
138
+ };
139
+ }
140
+ return setEnvironment(
141
+ {
142
+ ...state,
143
+ packageAlert: {
144
+ kind: "environment",
145
+ id: update.operation_id,
146
+ source,
147
+ },
148
+ },
149
+ source,
150
+ environment,
151
+ );
152
+ },
153
+
154
+ setEnvironment: (
155
+ state,
156
+ snapshot: NotificationMessageData<"environment-state">,
157
+ ) => setEnvironment(state, snapshot.source, snapshot.state),
158
+
159
+ clearPackageAlert: (state, id: string) =>
160
+ state.packageAlert?.id === id
161
+ ? { ...state, packageAlert: null }
162
+ : state,
163
+
164
+ addStartupLog: (
165
+ state,
166
+ logData: { content: string; status: "append" | "start" | "done" },
167
+ ) => ({
120
168
  ...state,
121
169
  startupLogsAlert: {
122
- ...state.startupLogsAlert,
123
- content: prevContent + logData.content,
170
+ content: (state.startupLogsAlert?.content ?? "") + logData.content,
124
171
  status: logData.status,
125
172
  },
126
- };
127
- },
173
+ }),
128
174
 
129
- clearStartupLogsAlert: (state) => {
130
- return { ...state, startupLogsAlert: null };
175
+ clearStartupLogsAlert: (state) => ({ ...state, startupLogsAlert: null }),
131
176
  },
132
- },
133
- );
134
-
135
- /**
136
- * React hook to get the Alert state.
137
- */
138
- export const useAlerts = () => useAtomValue(alertAtom);
139
-
140
- /**
141
- * React hook to get the Alerts actions.
142
- */
143
- export function useAlertActions() {
144
- return useActions();
177
+ );
178
+
179
+ export function getPackageAlert(
180
+ state: AlertState,
181
+ ): Identified<MissingPackageAlert | EnvironmentOperationAlert> | null {
182
+ const alert = state.packageAlert;
183
+ if (alert?.kind !== "environment") {
184
+ return alert;
185
+ }
186
+ const environment = state.environments[alert.source];
187
+ const operation = environment.operations.find(
188
+ (item) => item.operation_id === alert.id,
189
+ );
190
+ return operation && isPackageOperation(operation)
191
+ ? { ...operation, ...alert, restartRequired: environment.restart_required }
192
+ : null;
193
+ }
194
+
195
+ export function useAlerts() {
196
+ const state = useAtomValue(alertAtom);
197
+ return {
198
+ packageAlert: getPackageAlert(state),
199
+ startupLogsAlert: state.startupLogsAlert,
200
+ };
145
201
  }
@@ -181,7 +181,8 @@ function handleMessage(
181
181
  // Unsupported operations in islands mode
182
182
  case "banner":
183
183
  case "missing-package-alert":
184
- case "installing-package-alert":
184
+ case "environment-operation":
185
+ case "environment-state":
185
186
  case "completion-result":
186
187
  case "reload":
187
188
  case "focus-cell":
@@ -36,8 +36,8 @@ export type CompletionResultMessage =
36
36
  export type HumanReadableStatus = schemas["HumanReadableStatus"];
37
37
  export type FunctionCallResultMessage =
38
38
  NotificationMessageData<"function-call-result">;
39
- export type PackageInstallationStatus =
40
- schemas["InstallingPackageAlertNotification"]["packages"];
39
+ export type PackageOperationStatus =
40
+ schemas["EnvironmentOperationNotification"]["packages"];
41
41
  export type DataColumnPreview = NotificationMessageData<"data-column-preview">;
42
42
  export type SQLTablePreview = NotificationMessageData<"sql-table-preview">;
43
43
  export type SQLTableListPreview =