@marimo-team/islands 0.25.3-dev96 → 0.25.3-dev98
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/{ErrorBoundary-DJjRNeOW.js → ErrorBoundary-D5HXg9iP.js} +1 -1
- package/dist/{chat-ui-ChmqaLQA.js → chat-ui-BIgPxfPP.js} +4 -4
- package/dist/{code-visibility-BqP2qZal.js → code-visibility-gnf5rA6s.js} +2 -2
- package/dist/{common-Dwl0aTge.js → common-B3KZnxiH.js} +3 -3
- package/dist/{data-grid-overlay-editor-DadR_9Qe.js → data-grid-overlay-editor-wb2J7aj-.js} +1 -1
- package/dist/{error-entries-Cq0m-sO5.js → error-entries-TAO6kEhF.js} +242 -230
- package/dist/{feedback-button-PDt2V0S1.js → feedback-button-B9M0GGjm.js} +3 -3
- package/dist/{glide-data-editor-D2mdZ_2N.js → glide-data-editor-BBDo3Rj6.js} +3 -3
- package/dist/{globals-B8YEfM4c.js → globals-C7_iz22d.js} +1 -1
- package/dist/{html-to-image-D00_TX1M.js → html-to-image-CWdayGZU.js} +1 -1
- package/dist/main.js +1845 -1835
- package/dist/{number-overlay-editor-DdzFRhUf.js → number-overlay-editor-CDufNBLN.js} +1 -1
- package/dist/{process-output-ByXe3l6a.js → process-output-DD-oSvZk.js} +2 -2
- package/dist/{reveal-component-ClNTWzdK.js → reveal-component-BTMViKBQ.js} +7 -7
- package/package.json +1 -1
- package/src/components/chat/acp/blocks.tsx +2 -2
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +21 -6
- package/src/components/data-table/charts/storage.ts +16 -5
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +11 -7
- package/src/components/editor/package-alert.tsx +4 -4
- package/src/components/scratchpad/scratchpad.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/cells.test.ts +3 -1
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
- package/src/core/cells/cell.ts +14 -3
- package/src/core/cells/cells.ts +1 -0
- package/src/core/cells/types.ts +10 -1
- package/src/plugins/impl/chat/chat-ui.tsx +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
2
|
import { t } from "./react-sHtZdjeP.js";
|
|
3
|
-
import { a as n } from "./glide-data-editor-
|
|
3
|
+
import { a as n } from "./glide-data-editor-BBDo3Rj6.js";
|
|
4
4
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.3.0_27cffe7f1bc30287d59ac27db3240c5b/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
|
|
5
5
|
var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
|
|
6
6
|
name: "NumberOverlayEditorStyle",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { O as e } from "./html-to-image-
|
|
1
|
+
import { O as e } from "./html-to-image-CWdayGZU.js";
|
|
2
2
|
import { h as t } from "./tooltip-CKO1pHJo.js";
|
|
3
|
-
import { C as n, w as r } from "./error-entries-
|
|
3
|
+
import { C as n, w as r } from "./error-entries-TAO6kEhF.js";
|
|
4
4
|
import { b as i } from "./select-B0LRDH0g.js";
|
|
5
5
|
import { n as a } from "./semaphore-89jsoHtS.js";
|
|
6
6
|
var o = t("file", [["path", {
|
|
@@ -3,15 +3,15 @@ import { c as t, g as n, h as r, m as i, t as a } from "./button-BxB3RDjN.js";
|
|
|
3
3
|
import { t as o } from "./react-sHtZdjeP.js";
|
|
4
4
|
import { t as s } from "./compiler-runtime-CldRdSsv.js";
|
|
5
5
|
import { h as c, t as l } from "./tooltip-CKO1pHJo.js";
|
|
6
|
-
import { O as u } from "./error-entries-
|
|
7
|
-
import { Q as d, X as f, Z as p, an as m, cn as h, i as g, in as _, l as v, n as y, r as b, t as x, u as S } from "./common-
|
|
6
|
+
import { O as u } from "./error-entries-TAO6kEhF.js";
|
|
7
|
+
import { Q as d, X as f, Z as p, an as m, cn as h, i as g, in as _, l as v, n as y, r as b, t as x, u as S } from "./common-B3KZnxiH.js";
|
|
8
8
|
import { M as C } from "./select-B0LRDH0g.js";
|
|
9
9
|
import { q as ee } from "./dropdown-menu-B_7xD2X4.js";
|
|
10
10
|
import { _ as w } from "./toDate-DKH0_kMo.js";
|
|
11
11
|
import { t as T } from "./jsx-runtime-3BD1kT0s.js";
|
|
12
12
|
import { q as E } from "./use-restore-focus-DBCITLeF.js";
|
|
13
13
|
import { A as D, C as O, b as k } from "./useTheme-CfeaVw5D.js";
|
|
14
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
14
|
+
import { a as A, n as te, t as ne } from "./code-visibility-gnf5rA6s.js";
|
|
15
15
|
/* empty css */
|
|
16
16
|
var j = c("sticky-note", [["path", {
|
|
17
17
|
d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
|
|
@@ -6516,13 +6516,13 @@ var ia = (e) => {
|
|
|
6516
6516
|
c = K, s = tr, u = m ? "mo-slide-content flex flex-col gap-3" : "mo-slide-content", d = o, f = n.blocks.map((e, t) => {
|
|
6517
6517
|
let n = e.cells.map((e) => r(e.id) ? i ? /* @__PURE__ */ (0, G.jsx)(cr, { cell: e }, e.id) : /* @__PURE__ */ (0, G.jsx)(lr, { cell: e }, e.id) : /* @__PURE__ */ (0, G.jsx)(ia, { cell: e }, e.id));
|
|
6518
6518
|
if (e.isFragment) {
|
|
6519
|
-
let
|
|
6519
|
+
let r = p.get(t);
|
|
6520
6520
|
return /* @__PURE__ */ (0, G.jsxs)(Jn, {
|
|
6521
6521
|
as: "div",
|
|
6522
|
-
children: [n,
|
|
6523
|
-
},
|
|
6522
|
+
children: [n, r && /* @__PURE__ */ (0, G.jsx)(Qi, { text: r })]
|
|
6523
|
+
}, e.cells[0].id);
|
|
6524
6524
|
}
|
|
6525
|
-
return /* @__PURE__ */ (0, G.jsx)(M.Fragment, { children: n },
|
|
6525
|
+
return /* @__PURE__ */ (0, G.jsx)(M.Fragment, { children: n }, e.cells[0].id);
|
|
6526
6526
|
}), t[0] = o, t[1] = i, t[2] = r, t[3] = a, t[4] = n, t[5] = s, t[6] = c, t[7] = l, t[8] = u, t[9] = d, t[10] = f;
|
|
6527
6527
|
} else s = t[5], c = t[6], l = t[7], u = t[8], d = t[9], f = t[10];
|
|
6528
6528
|
let p;
|
package/package.json
CHANGED
|
@@ -328,9 +328,9 @@ export const PlansBlock = (props: { data: PlanNotificationEvent[] }) => {
|
|
|
328
328
|
</span>
|
|
329
329
|
</div>
|
|
330
330
|
<ul className="flex flex-col gap-1">
|
|
331
|
-
{plans.map((item
|
|
331
|
+
{plans.map((item) => (
|
|
332
332
|
<li
|
|
333
|
-
key={
|
|
333
|
+
key={item.content}
|
|
334
334
|
className={`
|
|
335
335
|
flex items-center gap-2 px-2 py-1 rounded
|
|
336
336
|
`}
|
|
@@ -10,13 +10,27 @@ import { cellId } from "@/__tests__/branded";
|
|
|
10
10
|
import { LazyVegaEmbed } from "@/components/charts/lazy";
|
|
11
11
|
import { vegaLoader } from "@/plugins/impl/vega/loader";
|
|
12
12
|
import { ChartPanel, TablePanel, type TablePanelProps } from "../charts";
|
|
13
|
-
import { getChartTabName, tabsStorageAtom } from "../storage";
|
|
13
|
+
import { createTabId, getChartTabName, tabsStorageAtom } from "../storage";
|
|
14
14
|
import { ChartType, NONE_VALUE } from "../types";
|
|
15
|
+
import { TOO_MANY_ROWS } from "../../types";
|
|
15
16
|
|
|
16
17
|
vi.mock("@/components/charts/lazy", () => ({
|
|
17
18
|
LazyVegaEmbed: vi.fn(() => null),
|
|
18
19
|
}));
|
|
19
20
|
|
|
21
|
+
vi.mock("../components/chart-items", async (importOriginal) => ({
|
|
22
|
+
...(await importOriginal<typeof import("../components/chart-items")>()),
|
|
23
|
+
ChartTypeSelect: ({
|
|
24
|
+
onValueChange,
|
|
25
|
+
}: {
|
|
26
|
+
onValueChange: (value: string) => void;
|
|
27
|
+
}) => (
|
|
28
|
+
<button type="button" onClick={() => onValueChange("line")}>
|
|
29
|
+
Change chart type
|
|
30
|
+
</button>
|
|
31
|
+
),
|
|
32
|
+
}));
|
|
33
|
+
|
|
20
34
|
beforeAll(() => {
|
|
21
35
|
SetupMocks.resizeObserver();
|
|
22
36
|
});
|
|
@@ -38,6 +52,7 @@ describe("ChartPanel", () => {
|
|
|
38
52
|
id,
|
|
39
53
|
[
|
|
40
54
|
{
|
|
55
|
+
id: createTabId(),
|
|
41
56
|
tabName,
|
|
42
57
|
chartType: ChartType.BAR,
|
|
43
58
|
config: {
|
|
@@ -283,4 +298,62 @@ describe("ChartPanel", () => {
|
|
|
283
298
|
});
|
|
284
299
|
},
|
|
285
300
|
);
|
|
301
|
+
it("keeps panel state when a chart type change renames the tab", async () => {
|
|
302
|
+
const store = createStore();
|
|
303
|
+
const id = cellId("rename-keeps-state");
|
|
304
|
+
const tabName = getChartTabName(0, ChartType.BAR);
|
|
305
|
+
store.set(
|
|
306
|
+
tabsStorageAtom,
|
|
307
|
+
new Map([
|
|
308
|
+
[
|
|
309
|
+
id,
|
|
310
|
+
[
|
|
311
|
+
{
|
|
312
|
+
id: createTabId(),
|
|
313
|
+
tabName,
|
|
314
|
+
chartType: ChartType.BAR,
|
|
315
|
+
config: {
|
|
316
|
+
general: {
|
|
317
|
+
xColumn: { field: "value", type: "number" },
|
|
318
|
+
yColumn: {
|
|
319
|
+
field: "value",
|
|
320
|
+
type: "integer",
|
|
321
|
+
aggregate: NONE_VALUE,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
],
|
|
328
|
+
]),
|
|
329
|
+
);
|
|
330
|
+
const sample = [{ value: 1 }];
|
|
331
|
+
const props: TablePanelProps = {
|
|
332
|
+
cellId: id,
|
|
333
|
+
data: sample,
|
|
334
|
+
dataTable: <div />,
|
|
335
|
+
totalRows: TOO_MANY_ROWS,
|
|
336
|
+
columns: 1,
|
|
337
|
+
displayHeader: true,
|
|
338
|
+
fieldTypes: [["value", ["number", "float64"]]],
|
|
339
|
+
};
|
|
340
|
+
const { getByRole, findByRole, queryByRole } = render(
|
|
341
|
+
<Provider store={store}>
|
|
342
|
+
<Tooltip.Provider>
|
|
343
|
+
<TablePanel {...props} />
|
|
344
|
+
</Tooltip.Provider>
|
|
345
|
+
</Provider>,
|
|
346
|
+
);
|
|
347
|
+
fireEvent.click(getByRole("tab", { name: tabName }));
|
|
348
|
+
fireEvent.click(await findByRole("button", { name: "Proceed" }));
|
|
349
|
+
await waitFor(() => {
|
|
350
|
+
expect(queryByRole("button", { name: "Proceed" })).toBeNull();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
fireEvent.click(getByRole("button", { name: "Change chart type" }));
|
|
354
|
+
|
|
355
|
+
const renamed = getChartTabName(0, ChartType.LINE);
|
|
356
|
+
await findByRole("tab", { name: renamed });
|
|
357
|
+
expect(queryByRole("button", { name: "Proceed" })).toBeNull();
|
|
358
|
+
});
|
|
286
359
|
});
|
|
@@ -16,7 +16,7 @@ import { cellId } from "@/__tests__/branded";
|
|
|
16
16
|
import { availableStorage } from "@/utils/storage/storage";
|
|
17
17
|
import { ChartSchema } from "../schemas";
|
|
18
18
|
import type { TabName } from "../storage";
|
|
19
|
-
import { KEY, tabsStorageAtom } from "../storage";
|
|
19
|
+
import { createTabId, KEY, tabsStorageAtom } from "../storage";
|
|
20
20
|
import { ChartType } from "../types";
|
|
21
21
|
|
|
22
22
|
describe("Chart Transforms Storage", () => {
|
|
@@ -39,6 +39,7 @@ describe("Chart Transforms Storage", () => {
|
|
|
39
39
|
const store = getDefaultStore();
|
|
40
40
|
const cid = cellId("cell-1");
|
|
41
41
|
const tabData = {
|
|
42
|
+
id: createTabId(),
|
|
42
43
|
tabName: "Tab 1" as TabName,
|
|
43
44
|
chartType: ChartType.LINE,
|
|
44
45
|
config: ChartSchema.parse({
|
|
@@ -63,6 +64,7 @@ describe("Chart Transforms Storage", () => {
|
|
|
63
64
|
const store = getDefaultStore();
|
|
64
65
|
const cid = cellId("cell-1");
|
|
65
66
|
const tabData1 = {
|
|
67
|
+
id: createTabId(),
|
|
66
68
|
tabName: "Tab 1" as TabName,
|
|
67
69
|
chartType: ChartType.LINE,
|
|
68
70
|
config: ChartSchema.parse({
|
|
@@ -73,6 +75,7 @@ describe("Chart Transforms Storage", () => {
|
|
|
73
75
|
}),
|
|
74
76
|
};
|
|
75
77
|
const tabData2 = {
|
|
78
|
+
id: createTabId(),
|
|
76
79
|
tabName: "Tab 2" as TabName,
|
|
77
80
|
chartType: ChartType.BAR,
|
|
78
81
|
config: ChartSchema.parse({
|
|
@@ -48,7 +48,12 @@ import { HeatmapForm } from "./forms/heatmap";
|
|
|
48
48
|
import { PieForm } from "./forms/pie";
|
|
49
49
|
import { LazyChart } from "./lazy-chart";
|
|
50
50
|
import { ChartSchema, type ChartSchemaType, getChartDefaults } from "./schemas";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
createTabId,
|
|
53
|
+
getChartTabName,
|
|
54
|
+
type TabName,
|
|
55
|
+
tabsStorageAtom,
|
|
56
|
+
} from "./storage";
|
|
52
57
|
import { ChartType } from "./types";
|
|
53
58
|
|
|
54
59
|
const NEW_CHART_TYPE = "bar" as ChartType;
|
|
@@ -114,7 +119,12 @@ export const TablePanel: React.FC<TablePanelProps> = ({
|
|
|
114
119
|
const tabName = getChartTabName(0, NEW_CHART_TYPE);
|
|
115
120
|
const newTabs = new Map(tabsMap);
|
|
116
121
|
newTabs.set(cellId, [
|
|
117
|
-
{
|
|
122
|
+
{
|
|
123
|
+
id: createTabId(),
|
|
124
|
+
tabName,
|
|
125
|
+
chartType: NEW_CHART_TYPE,
|
|
126
|
+
config: getChartDefaults(),
|
|
127
|
+
},
|
|
118
128
|
]);
|
|
119
129
|
saveTabsMap(newTabs);
|
|
120
130
|
setTabCounter(1);
|
|
@@ -135,6 +145,7 @@ export const TablePanel: React.FC<TablePanelProps> = ({
|
|
|
135
145
|
newTabs.set(cellId, [
|
|
136
146
|
...tabs,
|
|
137
147
|
{
|
|
148
|
+
id: createTabId(),
|
|
138
149
|
tabName,
|
|
139
150
|
chartType: NEW_CHART_TYPE,
|
|
140
151
|
config: getChartDefaults(),
|
|
@@ -233,9 +244,9 @@ export const TablePanel: React.FC<TablePanelProps> = ({
|
|
|
233
244
|
>
|
|
234
245
|
Table
|
|
235
246
|
</TabsTrigger>
|
|
236
|
-
{tabs.map((tab
|
|
247
|
+
{tabs.map((tab) => (
|
|
237
248
|
<TabsTrigger
|
|
238
|
-
key={
|
|
249
|
+
key={tab.id}
|
|
239
250
|
className="text-xs"
|
|
240
251
|
value={tab.tabName}
|
|
241
252
|
onClick={() => setSelectedTab(tab.tabName)}
|
|
@@ -263,7 +274,7 @@ export const TablePanel: React.FC<TablePanelProps> = ({
|
|
|
263
274
|
<TabsContent className="mt-1 overflow-hidden" value={DEFAULT_TAB_NAME}>
|
|
264
275
|
{dataTable}
|
|
265
276
|
</TabsContent>
|
|
266
|
-
{tabs.map((tab
|
|
277
|
+
{tabs.map((tab) => {
|
|
267
278
|
const saveChart = (formValues: ChartSchemaType) => {
|
|
268
279
|
saveTabChart({
|
|
269
280
|
tabName: tab.tabName,
|
|
@@ -275,7 +286,11 @@ export const TablePanel: React.FC<TablePanelProps> = ({
|
|
|
275
286
|
saveTabChartType(tab.tabName, chartType);
|
|
276
287
|
};
|
|
277
288
|
return (
|
|
278
|
-
<TabsContent
|
|
289
|
+
<TabsContent
|
|
290
|
+
key={tab.id}
|
|
291
|
+
value={tab.tabName}
|
|
292
|
+
className="h-[400px] mt-0"
|
|
293
|
+
>
|
|
279
294
|
<ChartPanel
|
|
280
295
|
tableData={data}
|
|
281
296
|
chartConfig={tab.config}
|
|
@@ -7,13 +7,16 @@ import { Logger } from "@/utils/Logger";
|
|
|
7
7
|
import { NotebookScopedLocalStorage } from "@/utils/storage/typed";
|
|
8
8
|
import { capitalize } from "@/utils/strings";
|
|
9
9
|
import type { TypedString } from "@/utils/typed";
|
|
10
|
+
import { generateUUID } from "@/utils/uuid";
|
|
10
11
|
import { ChartSchema, type ChartSchemaType } from "./schemas";
|
|
11
12
|
import type { ChartType } from "./types";
|
|
12
13
|
|
|
13
14
|
export type TabName = TypedString<"TabName">;
|
|
15
|
+
export type TabId = TypedString<"TabId">;
|
|
14
16
|
export const KEY = "marimo:charts:v2";
|
|
15
17
|
|
|
16
18
|
interface TabStorage {
|
|
19
|
+
id: TabId;
|
|
17
20
|
tabName: TabName; // unique within cell
|
|
18
21
|
chartType: ChartType;
|
|
19
22
|
config: ChartSchemaType;
|
|
@@ -21,11 +24,15 @@ interface TabStorage {
|
|
|
21
24
|
|
|
22
25
|
type TabStorageMap = Map<CellId, TabStorage[]>;
|
|
23
26
|
|
|
24
|
-
const TabStorageSchema = z
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const TabStorageSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
id: z.string().optional(),
|
|
30
|
+
tabName: z.string().transform((name) => name as TabName),
|
|
31
|
+
chartType: z.string().transform((type) => type as ChartType),
|
|
32
|
+
config: ChartSchema,
|
|
33
|
+
})
|
|
34
|
+
// Stored tabs without an id use their name, which is unique within a cell.
|
|
35
|
+
.transform((tab) => ({ ...tab, id: (tab.id ?? tab.tabName) as TabId }));
|
|
29
36
|
const TabStorageEntriesSchema = z.array(
|
|
30
37
|
z.tuple([
|
|
31
38
|
z.string().transform((name) => name as CellId),
|
|
@@ -64,6 +71,10 @@ export const tabsStorageAtom = atomWithStorage<TabStorageMap>(
|
|
|
64
71
|
mapStorage,
|
|
65
72
|
);
|
|
66
73
|
|
|
74
|
+
export function createTabId(): TabId {
|
|
75
|
+
return generateUUID() as TabId;
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
/**
|
|
68
79
|
* Convenience function to get the tab name for a given tab number and chart type
|
|
69
80
|
*/
|
|
@@ -358,6 +358,7 @@ export class AnsiReducer {
|
|
|
358
358
|
|
|
359
359
|
export type StringOutputMessage = Omit<OutputMessage, "data"> & {
|
|
360
360
|
data: string;
|
|
361
|
+
id?: number;
|
|
361
362
|
};
|
|
362
363
|
|
|
363
364
|
/**
|
|
@@ -369,6 +370,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
369
370
|
public readonly channel: OutputMessage["channel"];
|
|
370
371
|
public readonly timestamp: OutputMessage["timestamp"];
|
|
371
372
|
public readonly code: OutputMessage["code"];
|
|
373
|
+
public readonly id: StringOutputMessage["id"];
|
|
372
374
|
private ansiReducer = new AnsiReducer();
|
|
373
375
|
private _data: string;
|
|
374
376
|
|
|
@@ -385,6 +387,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
385
387
|
message.timestamp,
|
|
386
388
|
ansiReducer,
|
|
387
389
|
message.code,
|
|
390
|
+
message.id,
|
|
388
391
|
);
|
|
389
392
|
}
|
|
390
393
|
|
|
@@ -394,11 +397,13 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
394
397
|
timestamp: OutputMessage["timestamp"],
|
|
395
398
|
ansiReducer: AnsiReducer,
|
|
396
399
|
code: OutputMessage["code"],
|
|
400
|
+
id: StringOutputMessage["id"],
|
|
397
401
|
) {
|
|
398
402
|
this.mimetype = mimetype;
|
|
399
403
|
this.channel = channel;
|
|
400
404
|
this.timestamp = timestamp;
|
|
401
405
|
this.code = code;
|
|
406
|
+
this.id = id;
|
|
402
407
|
this.ansiReducer = ansiReducer;
|
|
403
408
|
this._data = this.ansiReducer.render();
|
|
404
409
|
}
|
|
@@ -411,6 +416,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
411
416
|
this.timestamp,
|
|
412
417
|
this.ansiReducer,
|
|
413
418
|
this.code,
|
|
419
|
+
this.id,
|
|
414
420
|
);
|
|
415
421
|
}
|
|
416
422
|
|
|
@@ -16,8 +16,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
16
16
|
import type { CellId } from "@/core/cells/ids";
|
|
17
17
|
import { isInternalCellName } from "@/core/cells/names";
|
|
18
18
|
import { useExpandedConsoleOutput } from "@/core/cells/outputs";
|
|
19
|
-
import type {
|
|
20
|
-
import type { OutputMessage } from "@/core/kernel/messages";
|
|
19
|
+
import type { ConsoleOutputMessage } from "@/core/cells/types";
|
|
21
20
|
import {
|
|
22
21
|
type UseInputHistoryReturn,
|
|
23
22
|
useInputHistory,
|
|
@@ -73,7 +72,7 @@ interface Props {
|
|
|
73
72
|
cellId: CellId;
|
|
74
73
|
cellName: string;
|
|
75
74
|
className?: string;
|
|
76
|
-
consoleOutputs:
|
|
75
|
+
consoleOutputs: ConsoleOutputMessage[];
|
|
77
76
|
stale: boolean;
|
|
78
77
|
interrupted: boolean;
|
|
79
78
|
debuggerActive: boolean;
|
|
@@ -241,19 +240,24 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
241
240
|
return null;
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
const originalIdx = consoleOutputs.length - idx - 1;
|
|
244
|
+
// Outputs render in reverse, so the reversed index shifts on every
|
|
245
|
+
// new line. The assigned id survives collapse and truncation. The
|
|
246
|
+
// original position is the fallback for outputs without one.
|
|
247
|
+
const key = output.id ?? `position-${originalIdx}`;
|
|
248
|
+
|
|
244
249
|
if (output.channel === "stdin") {
|
|
245
250
|
invariant(
|
|
246
251
|
typeof output.data === "string",
|
|
247
252
|
"Expected data to be a string",
|
|
248
253
|
);
|
|
249
254
|
|
|
250
|
-
const originalIdx = consoleOutputs.length - idx - 1;
|
|
251
255
|
const isPassword = output.mimetype === "text/password";
|
|
252
256
|
|
|
253
257
|
if (output.response == null && lastStdInputIdx === idx) {
|
|
254
258
|
return (
|
|
255
259
|
<StdInput
|
|
256
|
-
key={
|
|
260
|
+
key={key}
|
|
257
261
|
output={output.data}
|
|
258
262
|
isPdb={isPdb}
|
|
259
263
|
isPassword={isPassword}
|
|
@@ -268,7 +272,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
268
272
|
|
|
269
273
|
return (
|
|
270
274
|
<StdInputWithResponse
|
|
271
|
-
key={
|
|
275
|
+
key={key}
|
|
272
276
|
output={output.data}
|
|
273
277
|
response={output.response}
|
|
274
278
|
isPassword={isPassword}
|
|
@@ -278,7 +282,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
return (
|
|
281
|
-
<React.Fragment key={
|
|
285
|
+
<React.Fragment key={key}>
|
|
282
286
|
<OutputRenderer
|
|
283
287
|
cellId={cellId}
|
|
284
288
|
onRefactorWithAI={onRefactorWithAI}
|
|
@@ -172,12 +172,12 @@ export const PackageAlert: React.FC = () => {
|
|
|
172
172
|
<p>The following packages were not found:</p>
|
|
173
173
|
<table className="list-disc ml-2 mt-1">
|
|
174
174
|
<tbody>
|
|
175
|
-
{packageAlert.packages.map((pkg
|
|
175
|
+
{packageAlert.packages.map((pkg) => {
|
|
176
176
|
const parsed = parsePackageSpecifier(pkg);
|
|
177
177
|
const currentExtras = selectedExtras[pkg] || parsed.extras;
|
|
178
178
|
|
|
179
179
|
return (
|
|
180
|
-
<tr className="font-mono text-sm" key={
|
|
180
|
+
<tr className="font-mono text-sm" key={pkg}>
|
|
181
181
|
<td className="pr-2 py-1 align-middle">
|
|
182
182
|
<BoxIcon size="1rem" />
|
|
183
183
|
</td>
|
|
@@ -296,7 +296,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
296
296
|
</>
|
|
297
297
|
)}
|
|
298
298
|
<ul className="list-disc ml-2 mt-1">
|
|
299
|
-
{Object.entries(packageAlert.packages).map(([pkg, st]
|
|
299
|
+
{Object.entries(packageAlert.packages).map(([pkg, st]) => (
|
|
300
300
|
<li
|
|
301
301
|
className={cn(
|
|
302
302
|
"flex items-center gap-1 font-mono text-sm",
|
|
@@ -307,7 +307,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
307
307
|
status === "failed" &&
|
|
308
308
|
"text-muted-foreground",
|
|
309
309
|
)}
|
|
310
|
-
key={
|
|
310
|
+
key={pkg}
|
|
311
311
|
>
|
|
312
312
|
<ProgressIcon status={st} />
|
|
313
313
|
{pkg}
|
|
@@ -135,9 +135,9 @@ export const ScratchPad: React.FC = () => {
|
|
|
135
135
|
return (
|
|
136
136
|
<div className="absolute inset-0 z-100 bg-background p-3 border-none overflow-auto">
|
|
137
137
|
<div className="overflow-auto flex flex-col gap-3">
|
|
138
|
-
{history.map((item
|
|
138
|
+
{history.map((item) => (
|
|
139
139
|
<div
|
|
140
|
-
key={
|
|
140
|
+
key={item}
|
|
141
141
|
className="border rounded-md hover:shadow-sm cursor-pointer hover:border-input overflow-hidden"
|
|
142
142
|
onClick={() => handleSelectHistoryItem(item)}
|
|
143
143
|
>
|
|
@@ -404,13 +404,15 @@ const SubslideView = ({
|
|
|
404
404
|
if (block.isFragment) {
|
|
405
405
|
const cumulative = cumulativeByBlock.get(i);
|
|
406
406
|
return (
|
|
407
|
-
<Fragment key={
|
|
407
|
+
<Fragment key={block.cells[0].id} as="div">
|
|
408
408
|
{rendered}
|
|
409
409
|
{cumulative && <NotesAside text={cumulative} />}
|
|
410
410
|
</Fragment>
|
|
411
411
|
);
|
|
412
412
|
}
|
|
413
|
-
return
|
|
413
|
+
return (
|
|
414
|
+
<ReactFragment key={block.cells[0].id}>{rendered}</ReactFragment>
|
|
415
|
+
);
|
|
414
416
|
})}
|
|
415
417
|
</div>
|
|
416
418
|
</SlideScrollContainer>
|
|
@@ -139,6 +139,7 @@ exports[`cell reducer > can initialize stdin > second stdin requested 1`] = `
|
|
|
139
139
|
{
|
|
140
140
|
"channel": "stdin",
|
|
141
141
|
"data": "what is your name?",
|
|
142
|
+
"id": 2,
|
|
142
143
|
"mimetype": "text/plain",
|
|
143
144
|
"response": "Marimo!",
|
|
144
145
|
"timestamp": 2,
|
|
@@ -146,6 +147,7 @@ exports[`cell reducer > can initialize stdin > second stdin requested 1`] = `
|
|
|
146
147
|
{
|
|
147
148
|
"channel": "stdin",
|
|
148
149
|
"data": "how old are you?",
|
|
150
|
+
"id": 3,
|
|
149
151
|
"mimetype": "text/plain",
|
|
150
152
|
"timestamp": 3,
|
|
151
153
|
},
|
|
@@ -189,6 +191,7 @@ exports[`cell reducer > can initialize stdin > stdin answered 1`] = `
|
|
|
189
191
|
{
|
|
190
192
|
"channel": "stdin",
|
|
191
193
|
"data": "what is your name?",
|
|
194
|
+
"id": 2,
|
|
192
195
|
"mimetype": "text/plain",
|
|
193
196
|
"response": "Marimo!",
|
|
194
197
|
"timestamp": 2,
|
|
@@ -233,6 +236,7 @@ exports[`cell reducer > can initialize stdin > stdin requested 1`] = `
|
|
|
233
236
|
{
|
|
234
237
|
"channel": "stdin",
|
|
235
238
|
"data": "what is your name?",
|
|
239
|
+
"id": 2,
|
|
236
240
|
"mimetype": "text/plain",
|
|
237
241
|
"timestamp": 2,
|
|
238
242
|
},
|
|
@@ -219,6 +219,45 @@ describe("transitionCell serialization", () => {
|
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
+
describe("transitionCell console output ids", () => {
|
|
223
|
+
const output = (
|
|
224
|
+
data: string,
|
|
225
|
+
channel: OutputMessage["channel"] = "stdout",
|
|
226
|
+
): OutputMessage => ({
|
|
227
|
+
channel,
|
|
228
|
+
mimetype: "text/plain",
|
|
229
|
+
data,
|
|
230
|
+
timestamp: 0,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("assigns increasing ids to appended outputs", () => {
|
|
234
|
+
let cell = createCellRuntimeState({ status: "running" });
|
|
235
|
+
cell = transitionCell(cell, { console: output("a") } as CellMessage);
|
|
236
|
+
cell = transitionCell(cell, {
|
|
237
|
+
console: output("b", "stderr"),
|
|
238
|
+
} as CellMessage);
|
|
239
|
+
expect(cell.consoleOutputs.map((o) => o.id)).toEqual([1, 2]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("continues from the surviving maximum after a collapse", () => {
|
|
243
|
+
let cell = createCellRuntimeState({ status: "running" });
|
|
244
|
+
cell = transitionCell(cell, { console: output("a") } as CellMessage);
|
|
245
|
+
cell = transitionCell(cell, { console: output("b") } as CellMessage);
|
|
246
|
+
expect(cell.consoleOutputs.map((o) => o.id)).toEqual([1]);
|
|
247
|
+
cell = transitionCell(cell, {
|
|
248
|
+
console: output("c", "stderr"),
|
|
249
|
+
} as CellMessage);
|
|
250
|
+
expect(cell.consoleOutputs.map((o) => o.id)).toEqual([1, 2]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("assigns ids when the kernel replaces the whole console", () => {
|
|
254
|
+
const cell = transitionCell(createCellRuntimeState({ status: "running" }), {
|
|
255
|
+
console: [output("a"), output("b", "stderr")],
|
|
256
|
+
} as CellMessage);
|
|
257
|
+
expect(cell.consoleOutputs.map((o) => o.id)).toEqual([1, 2]);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
222
261
|
describe("transitionCell stdin resolution", () => {
|
|
223
262
|
describe.each([
|
|
224
263
|
{
|
|
@@ -1579,7 +1579,9 @@ describe("cell reducer", () => {
|
|
|
1579
1579
|
response: secret,
|
|
1580
1580
|
});
|
|
1581
1581
|
|
|
1582
|
-
expect(cells[0].consoleOutputs).toEqual([
|
|
1582
|
+
expect(cells[0].consoleOutputs).toEqual([
|
|
1583
|
+
{ ...prompt, id: 1, response: "" },
|
|
1584
|
+
]);
|
|
1583
1585
|
expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
|
|
1584
1586
|
});
|
|
1585
1587
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import { StatefulOutputMessage } from "@/components/editor/output/ansi-reduce";
|
|
5
|
+
import type { ConsoleOutputMessage } from "@/core/cells/types";
|
|
5
6
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
6
7
|
import {
|
|
7
8
|
collapseConsoleOutputs,
|
|
@@ -279,6 +280,59 @@ describe("collapseConsoleOutputs", () => {
|
|
|
279
280
|
expect(result[0].data).toContain("Streaming output truncated");
|
|
280
281
|
});
|
|
281
282
|
|
|
283
|
+
it("keeps the first id when collapsing outputs", () => {
|
|
284
|
+
const consoleOutputs = [
|
|
285
|
+
{
|
|
286
|
+
mimetype: "text/plain",
|
|
287
|
+
channel: "output",
|
|
288
|
+
data: "Hello ",
|
|
289
|
+
timestamp: 0,
|
|
290
|
+
id: 7,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
mimetype: "text/plain",
|
|
294
|
+
channel: "output",
|
|
295
|
+
data: "World",
|
|
296
|
+
timestamp: 0,
|
|
297
|
+
id: 8,
|
|
298
|
+
},
|
|
299
|
+
] satisfies ConsoleOutputMessage[];
|
|
300
|
+
const result = collapseConsoleOutputs(consoleOutputs);
|
|
301
|
+
expect(result).toHaveLength(1);
|
|
302
|
+
expect(result[0].data).toBe("Hello World");
|
|
303
|
+
expect((result[0] as ConsoleOutputMessage).id).toBe(7);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("keeps ids on outputs that survive truncation", () => {
|
|
307
|
+
const consoleOutputs = [
|
|
308
|
+
{
|
|
309
|
+
mimetype: "text/plain",
|
|
310
|
+
channel: "output",
|
|
311
|
+
data: "one\ntwo",
|
|
312
|
+
timestamp: 0,
|
|
313
|
+
id: 1,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
mimetype: "text/plain",
|
|
317
|
+
channel: "stderr",
|
|
318
|
+
data: "three\nfour",
|
|
319
|
+
timestamp: 0,
|
|
320
|
+
id: 2,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
mimetype: "text/plain",
|
|
324
|
+
channel: "output",
|
|
325
|
+
data: "five",
|
|
326
|
+
timestamp: 0,
|
|
327
|
+
id: 3,
|
|
328
|
+
},
|
|
329
|
+
] satisfies ConsoleOutputMessage[];
|
|
330
|
+
const result = collapseConsoleOutputs(consoleOutputs, 3);
|
|
331
|
+
const ids = result.map((output) => (output as ConsoleOutputMessage).id);
|
|
332
|
+
expect(result[0].data).toContain("Streaming output truncated");
|
|
333
|
+
expect(ids).toEqual([undefined, 2, 3]);
|
|
334
|
+
});
|
|
335
|
+
|
|
282
336
|
describe("ANSI escape sequences", () => {
|
|
283
337
|
it("should handle cursor movement with collapse", () => {
|
|
284
338
|
const consoleOutputs: OutputMessage[] = [
|