@marimo-team/frontend 0.25.3-dev95 → 0.25.3-dev97
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/assets/{CellStatus-CplYFNTy.js → CellStatus-fK4n2NPc.js} +1 -1
- package/dist/assets/{ErrorBoundary-CdGuz4a_.js → ErrorBoundary-CAEGdoFW.js} +2 -2
- package/dist/assets/{JsonOutput-Csbov8Fx.js → JsonOutput-CyAKmZDD.js} +1 -1
- package/dist/assets/{MarimoErrorOutput-czclLlOn.js → MarimoErrorOutput-CgZvP0p0.js} +1 -1
- package/dist/assets/{RenderHTML-CNDedpS4.js → RenderHTML-DQvxDDNS.js} +1 -1
- package/dist/assets/{RunButton-CRfdOxVd.js → RunButton-BBDnDL6G.js} +1 -1
- package/dist/assets/{add-cell-with-ai-BAIX7r0P.js → add-cell-with-ai-Clg5FELx.js} +2 -2
- package/dist/assets/{add-connection-dialog-DrDuM-tI.js → add-connection-dialog-BqdWmD-7.js} +1 -1
- package/dist/assets/{agent-panel-CoXq_rlA.js → agent-panel-5RxG5Y82.js} +3 -3
- package/dist/assets/{ai-model-dropdown-COvWn46z.js → ai-model-dropdown-Bvl1ktQq.js} +2 -2
- package/dist/assets/{app-config-button-CtxoYG4F.js → app-config-button-0QTeQ2Mv.js} +1 -1
- package/dist/assets/{blob-DCjrjZKW.js → blob-5VcB8K_t.js} +1 -1
- package/dist/assets/{cache-panel-CbWZcBu0.js → cache-panel-BV-c7ed0.js} +1 -1
- package/dist/assets/{cell-editor-BnL0Sorl.js → cell-editor-Byw5y-oI.js} +2 -2
- package/dist/assets/{cell-link-BFbgKiRH.js → cell-link-C5yYyzuF.js} +1 -1
- package/dist/assets/{cells-yOs72eZm.js → cells-D_11MjVB.js} +13 -13
- package/dist/assets/{chat-display-DwRM41t_.js → chat-display-BCR_ySiV.js} +1 -1
- package/dist/assets/{chat-panel-DeTEdjxF.js → chat-panel-RPRvBsaU.js} +2 -2
- package/dist/assets/{chat-ui-D6XucD07.js → chat-ui-CGviVaRA.js} +3 -3
- package/dist/assets/column-preview-xe533-Cn.js +1 -0
- package/dist/assets/{command-palette-CA6yfNLb.js → command-palette-BB9bWdWp.js} +1 -1
- package/dist/assets/{common-B-ZYnO1x.js → common-CaeItbcI.js} +1 -1
- package/dist/assets/{components-vu2jLG08.js → components-G6frbw0c.js} +1 -1
- package/dist/assets/{datasource-BakEOWsf.js → datasource-deuXnlU-.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DCGB3_id.js → dependency-graph-panel-CIykLnJe.js} +2 -2
- package/dist/assets/{documentation-panel-CpGZ2Dfn.js → documentation-panel-2PQ8kQLa.js} +1 -1
- package/dist/assets/{download-CV1k4XQ4.js → download-dK52bLxZ.js} +1 -1
- package/dist/assets/{edit-page-By7n2M84.js → edit-page-C2phWLKH.js} +6 -6
- package/dist/assets/{error-entries-JgrZYMbe.js → error-entries-3GVk2KjS.js} +1 -1
- package/dist/assets/{error-panel-DS7HWph-.js → error-panel-CzDRGPeC.js} +1 -1
- package/dist/assets/{feedback-button-eYsTCK-g.js → feedback-button-C5NgWpFg.js} +1 -1
- package/dist/assets/feedback-button-CONwLFpw.js +1 -0
- package/dist/assets/{file-explorer-panel-BTLwwwD4.js → file-explorer-panel-B50xDnTZ.js} +1 -1
- package/dist/assets/{file-name-input-BaR4h2Ud.js → file-name-input-DqwMSgGU.js} +1 -1
- package/dist/assets/{floating-outline-Drd73Quo.js → floating-outline-ChmAuvxz.js} +1 -1
- package/dist/assets/{gallery-page-DXUD11Rm.js → gallery-page-CiL8aMS1.js} +1 -1
- package/dist/assets/{glide-data-editor-DqBBoQBl.js → glide-data-editor-CqRclBkp.js} +1 -1
- package/dist/assets/{home-page-pgFFsmga.js → home-page-BgZVl4dP.js} +1 -1
- package/dist/assets/{hooks-D25kP4Sr.js → hooks-DM5n7Osw.js} +1 -1
- package/dist/assets/index-vCxv6WVQ.js +30 -0
- package/dist/assets/{layout-DLHIGZTK.js → layout-BpP8nTjb.js} +4 -4
- package/dist/assets/{logs-panel-D5AAD2s9.js → logs-panel-B5eVAZtH.js} +1 -1
- package/dist/assets/{markdown-renderer--CwVm4kJ.js → markdown-renderer-BWUzqkJM.js} +1 -1
- package/dist/assets/{name-cell-input-BFKZaNI5.js → name-cell-input-DXW68gRW.js} +1 -1
- package/dist/assets/{outline-panel-qcwRnl0R.js → outline-panel-DPxJhJ_A.js} +1 -1
- package/dist/assets/{packages-panel-DnSryNcz.js → packages-panel-gBE2FWQr.js} +1 -1
- package/dist/assets/{panel-accordion-state-BwJpDMVt.js → panel-accordion-state-CCF5Gxnc.js} +1 -1
- package/dist/assets/{panels-Aqzwb3UR.js → panels-BQYMF6p_.js} +1 -1
- package/dist/assets/{process-output-DuUfHYSr.js → process-output-Cw9IDcKQ.js} +1 -1
- package/dist/assets/{readonly-code-display-BY-9I_Yk.js → readonly-code-display-DYcZQ28T.js} +1 -1
- package/dist/assets/{readonly-python-code-DSyr-yE2.js → readonly-python-code-RFuQP50v.js} +1 -1
- package/dist/assets/{reveal-component-C_Lb7Mom.js → reveal-component-yRrDmCQz.js} +2 -2
- package/dist/assets/{run-page-ZTw-kCU-.js → run-page-CY65WUOu.js} +1 -1
- package/dist/assets/{runtime-details-state-B7gpnuXx.js → runtime-details-state-pobtu6TO.js} +1 -1
- package/dist/assets/scratchpad-panel-H44_pvb_.js +1 -0
- package/dist/assets/session-panel-Bnd1iXkO.js +1 -0
- package/dist/assets/{snippets-panel-BNadUJLU.js → snippets-panel-DKApxKx9.js} +1 -1
- package/dist/assets/{state-BaYudep3.js → state-Bl9BiY_s.js} +1 -1
- package/dist/assets/{state-_1LTGJQM.js → state-Dv059B3A.js} +1 -1
- package/dist/assets/{tracing-DbTgS4u9.js → tracing-Clwy4SlW.js} +1 -1
- package/dist/assets/{tracing-panel-CjlrpXPy.js → tracing-panel-BwjLG_aC.js} +2 -2
- package/dist/assets/{tree-actions-C_KYlaSv.js → tree-actions-BZNTQ12N.js} +1 -1
- package/dist/assets/{useAddCell-C0J8quGE.js → useAddCell-Cjuqk2YR.js} +1 -1
- package/dist/assets/{useCellActionButton-CbkOj6BE.js → useCellActionButton-MoWLvuGe.js} +1 -1
- package/dist/assets/{useDeleteCell-DHkF5wgS.js → useDeleteCell-BC-MW7d1.js} +1 -1
- package/dist/assets/{useDependencyPanelTab--yXJW6X5.js → useDependencyPanelTab-CZU7i-B3.js} +1 -1
- package/dist/assets/{useNotebookActions-dJQalORg.js → useNotebookActions-wFxVdRXQ.js} +1 -1
- package/dist/assets/{useRunCells-C_IwVJR_.js → useRunCells-ByaWxoIt.js} +1 -1
- package/dist/assets/{useSplitCell-Ci4MEUj8.js → useSplitCell-YJIystwv.js} +1 -1
- package/dist/index.html +20 -20
- package/package.json +1 -1
- package/src/components/app-config/ai-config.tsx +1 -0
- package/src/components/chat/acp/blocks.tsx +2 -2
- package/src/components/chat/chat-components.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +6 -1
- package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
- 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/data-table/column-explorer-panel/column-explorer.tsx +1 -1
- package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
- package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
- package/src/components/datasources/column-preview.tsx +8 -2
- package/src/components/datasources/datasources.tsx +12 -14
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
- package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
- package/src/components/editor/cell/DeleteButton.tsx +1 -0
- package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
- package/src/components/editor/notebook-banner.tsx +1 -0
- 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 +6 -4
- package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
- package/src/components/mcp/mcp-status-indicator.tsx +1 -1
- package/src/components/scratchpad/scratchpad.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/components/slides/slide-form.tsx +1 -0
- 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/core/codemirror/language/panel/panel.tsx +1 -0
- package/src/plugins/impl/RefreshPlugin.tsx +1 -0
- package/src/plugins/impl/chat/chat-ui.tsx +2 -1
- package/src/stories/button.stories.tsx +1 -0
- package/dist/assets/column-preview-DXM8SqGY.js +0 -1
- package/dist/assets/feedback-button-Dgs_HOOC.js +0 -1
- package/dist/assets/index-DNG4qQ-E.js +0 -30
- package/dist/assets/scratchpad-panel-D2IHkKoG.js +0 -1
- package/dist/assets/session-panel-cOSO3WCX.js +0 -1
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { render } from "@testing-library/react";
|
|
4
|
-
import { Provider } from "jotai";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
5
|
import type React from "react";
|
|
6
6
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
7
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
8
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
9
|
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
9
10
|
import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
|
|
10
11
|
import { requestClientAtom } from "@/core/network/requests";
|
|
11
12
|
import { store } from "@/core/state/jotai";
|
|
12
13
|
import { DatasetColumnPreview } from "../column-preview";
|
|
13
14
|
|
|
15
|
+
const { createNewCell } = vi.hoisted(() => ({ createNewCell: vi.fn() }));
|
|
16
|
+
|
|
17
|
+
vi.mock("@/core/cells/cells", async (importOriginal) => ({
|
|
18
|
+
...(await importOriginal<typeof import("@/core/cells/cells")>()),
|
|
19
|
+
useCellActions: () => ({ createNewCell }),
|
|
20
|
+
}));
|
|
21
|
+
|
|
14
22
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
15
23
|
<Provider store={store}>{children}</Provider>
|
|
16
24
|
);
|
|
@@ -95,3 +103,56 @@ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
|
|
|
95
103
|
);
|
|
96
104
|
});
|
|
97
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("DatasetColumnPreview chart actions", () => {
|
|
108
|
+
it("identifies each column and inserts its corresponding chart", () => {
|
|
109
|
+
const store = createStore();
|
|
110
|
+
store.set(requestClientAtom, MockRequestClient.create());
|
|
111
|
+
const columns: DataTableColumn[] = [
|
|
112
|
+
{
|
|
113
|
+
name: "email",
|
|
114
|
+
type: "string",
|
|
115
|
+
external_type: "str",
|
|
116
|
+
sample_values: [],
|
|
117
|
+
},
|
|
118
|
+
{ name: "age", type: "integer", external_type: "int", sample_values: [] },
|
|
119
|
+
];
|
|
120
|
+
render(
|
|
121
|
+
<Provider store={store}>
|
|
122
|
+
<TooltipProvider>
|
|
123
|
+
{columns.map((column) => (
|
|
124
|
+
<DatasetColumnPreview
|
|
125
|
+
key={column.name}
|
|
126
|
+
table={{ ...table, source_type: "local", engine: null }}
|
|
127
|
+
column={column}
|
|
128
|
+
onAddColumnChart={vi.fn()}
|
|
129
|
+
preview={{
|
|
130
|
+
table_name: table.name,
|
|
131
|
+
column_name: column.name,
|
|
132
|
+
chart_code: `chart_${column.name}`,
|
|
133
|
+
chart_spec: null,
|
|
134
|
+
error: null,
|
|
135
|
+
missing_packages: null,
|
|
136
|
+
stats: {},
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
))}
|
|
140
|
+
</TooltipProvider>
|
|
141
|
+
</Provider>,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
for (const column of columns) {
|
|
145
|
+
fireEvent.click(
|
|
146
|
+
screen.getByRole("button", {
|
|
147
|
+
name: `Add chart to notebook for ${column.name}`,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
expect(createNewCell).toHaveBeenLastCalledWith({
|
|
151
|
+
code: `chart_${column.name}`,
|
|
152
|
+
before: false,
|
|
153
|
+
cellId: "__end__",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
expect(createNewCell).toHaveBeenCalledTimes(2);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -112,12 +112,16 @@ export const DatasetColumnPreview: React.FC<{
|
|
|
112
112
|
|
|
113
113
|
const addDataframeChart = preview.chart_code &&
|
|
114
114
|
table.source_type === "local" && (
|
|
115
|
-
<AddDataframeChart
|
|
115
|
+
<AddDataframeChart
|
|
116
|
+
chartCode={preview.chart_code}
|
|
117
|
+
columnName={column.name}
|
|
118
|
+
/>
|
|
116
119
|
);
|
|
117
120
|
|
|
118
121
|
const addSQLChart = table.source_type === "duckdb" && (
|
|
119
122
|
<Tooltip content="Add SQL cell" delayDuration={400}>
|
|
120
123
|
<Button
|
|
124
|
+
aria-label={`Add SQL cell for ${column.name}`}
|
|
121
125
|
variant="outline"
|
|
122
126
|
size="icon"
|
|
123
127
|
className="z-10 bg-background absolute right-1 -top-1"
|
|
@@ -238,7 +242,8 @@ export function renderChart(chartSpec: string, theme: Theme) {
|
|
|
238
242
|
|
|
239
243
|
export const AddDataframeChart: React.FC<{
|
|
240
244
|
chartCode: string;
|
|
241
|
-
|
|
245
|
+
columnName: string;
|
|
246
|
+
}> = ({ chartCode, columnName }) => {
|
|
242
247
|
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
243
248
|
const lastFocusedCellId = useLastFocusedCellId();
|
|
244
249
|
const { createNewCell } = useCellActions();
|
|
@@ -261,6 +266,7 @@ export const AddDataframeChart: React.FC<{
|
|
|
261
266
|
return (
|
|
262
267
|
<Tooltip content="Add chart to notebook" delayDuration={400}>
|
|
263
268
|
<Button
|
|
269
|
+
aria-label={`Add chart to notebook for ${columnName}`}
|
|
264
270
|
variant="outline"
|
|
265
271
|
size="icon"
|
|
266
272
|
className="z-10 bg-background absolute right-1 -top-0.5"
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
VisibilityToggleButton,
|
|
14
14
|
} from "@/components/editor/file-tree/tree-actions";
|
|
15
15
|
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
16
|
-
import { Button } from "@/components/ui/button";
|
|
16
|
+
import { Button, buttonVariants } from "@/components/ui/button";
|
|
17
17
|
import { Command, CommandInput, CommandItem } from "@/components/ui/command";
|
|
18
18
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
19
19
|
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
@@ -978,6 +978,7 @@ const DatasetTableItem: React.FC<{
|
|
|
978
978
|
{renderRowsByColumns()}
|
|
979
979
|
<Tooltip content="Add table to notebook" delayDuration={400}>
|
|
980
980
|
<Button
|
|
981
|
+
aria-label={`Add table ${table.name} to notebook`}
|
|
981
982
|
className="group-hover:inline-flex hidden"
|
|
982
983
|
variant="text"
|
|
983
984
|
size="icon"
|
|
@@ -1068,19 +1069,16 @@ const DatasetColumnItem: React.FC<{
|
|
|
1068
1069
|
{isIndexed &&
|
|
1069
1070
|
renderItemSubtext({ tooltipContent: "Indexed", content: "IDX" })}
|
|
1070
1071
|
</div>
|
|
1071
|
-
<
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
/>
|
|
1082
|
-
</Button>
|
|
1083
|
-
</Tooltip>
|
|
1072
|
+
<CopyClipboardIcon
|
|
1073
|
+
tooltip="Copy column name"
|
|
1074
|
+
value={column.name}
|
|
1075
|
+
ariaLabel={`Copy column name ${column.name}`}
|
|
1076
|
+
className="h-3 w-3"
|
|
1077
|
+
buttonClassName={cn(
|
|
1078
|
+
buttonVariants({ variant: "text", size: "icon" }),
|
|
1079
|
+
"group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-foreground",
|
|
1080
|
+
)}
|
|
1081
|
+
/>
|
|
1084
1082
|
<span className="text-xs text-muted-foreground">
|
|
1085
1083
|
{column.external_type}
|
|
1086
1084
|
</span>
|
|
@@ -162,6 +162,7 @@ export const DebuggerControls: React.FC<{
|
|
|
162
162
|
<div className="flex">
|
|
163
163
|
<Tooltip content="Next line">
|
|
164
164
|
<Button
|
|
165
|
+
aria-label="Next line"
|
|
165
166
|
variant="text"
|
|
166
167
|
size="icon"
|
|
167
168
|
data-testid="debugger-next-button"
|
|
@@ -173,6 +174,7 @@ export const DebuggerControls: React.FC<{
|
|
|
173
174
|
</Tooltip>
|
|
174
175
|
<Tooltip content="Continue execution">
|
|
175
176
|
<Button
|
|
177
|
+
aria-label="Continue execution"
|
|
176
178
|
variant="text"
|
|
177
179
|
size="icon"
|
|
178
180
|
data-testid="debugger-continue-button"
|
|
@@ -187,6 +189,7 @@ export const DebuggerControls: React.FC<{
|
|
|
187
189
|
</Tooltip>
|
|
188
190
|
<Tooltip content="Print stack trace">
|
|
189
191
|
<Button
|
|
192
|
+
aria-label="Print stack trace"
|
|
190
193
|
variant="text"
|
|
191
194
|
size="icon"
|
|
192
195
|
data-testid="debugger-stack-button"
|
|
@@ -198,6 +201,7 @@ export const DebuggerControls: React.FC<{
|
|
|
198
201
|
</Tooltip>
|
|
199
202
|
<Tooltip content="Help">
|
|
200
203
|
<Button
|
|
204
|
+
aria-label="Debugger help"
|
|
201
205
|
variant="text"
|
|
202
206
|
size="icon"
|
|
203
207
|
data-testid="debugger-help-button"
|
|
@@ -210,6 +214,7 @@ export const DebuggerControls: React.FC<{
|
|
|
210
214
|
{onClear && (
|
|
211
215
|
<Tooltip content="Quit and clear">
|
|
212
216
|
<Button
|
|
217
|
+
aria-label="Quit and clear debugger"
|
|
213
218
|
variant="text"
|
|
214
219
|
size="icon"
|
|
215
220
|
data-testid="debugger-clear-button"
|
|
@@ -216,11 +216,12 @@ export const GraphSelectionPanel: React.FC<{
|
|
|
216
216
|
</Button>
|
|
217
217
|
</Tooltip>
|
|
218
218
|
<ConnectionCellActionsDropdown cellId={selection.id}>
|
|
219
|
-
<Button variant="ghost" size="icon">
|
|
219
|
+
<Button aria-label="Cell actions" variant="ghost" size="icon">
|
|
220
220
|
<MoreVerticalIcon className="w-4 h-4" />
|
|
221
221
|
</Button>
|
|
222
222
|
</ConnectionCellActionsDropdown>
|
|
223
223
|
<Button
|
|
224
|
+
aria-label="Close cell details"
|
|
224
225
|
variant="text"
|
|
225
226
|
size="icon"
|
|
226
227
|
onClick={() => {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { act, render, screen, within } from "@testing-library/react";
|
|
3
|
+
import { act, fireEvent, render, screen, within } from "@testing-library/react";
|
|
4
4
|
import { createStore, Provider } from "jotai";
|
|
5
5
|
import { describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { cellId } from "@/__tests__/branded";
|
|
7
7
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
8
|
import { stagedAICellsAtom, visibleForTesting } from "@/core/ai/staged-cells";
|
|
9
|
+
import { scrollAndHighlightCell } from "../../../links/cell-link";
|
|
9
10
|
import { PendingAICells } from "../pending-ai-cells";
|
|
10
11
|
|
|
11
12
|
vi.mock("@/components/editor/cell/useDeleteCell", () => ({
|
|
@@ -29,6 +30,62 @@ vi.mock("@/components/editor/links/cell-link", () => ({
|
|
|
29
30
|
}));
|
|
30
31
|
|
|
31
32
|
describe("PendingAICells", () => {
|
|
33
|
+
it("navigates between pending cells through named controls", () => {
|
|
34
|
+
const store = createStore();
|
|
35
|
+
const first = cellId("first-pending-cell");
|
|
36
|
+
const second = cellId("second-pending-cell");
|
|
37
|
+
const third = cellId("third-pending-cell");
|
|
38
|
+
store.set(
|
|
39
|
+
stagedAICellsAtom,
|
|
40
|
+
new Map([
|
|
41
|
+
[first, { type: "add_cell" }],
|
|
42
|
+
[second, { type: "add_cell" }],
|
|
43
|
+
[third, { type: "add_cell" }],
|
|
44
|
+
]),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
render(
|
|
48
|
+
<Provider store={store}>
|
|
49
|
+
<TooltipProvider>
|
|
50
|
+
<PendingAICells />
|
|
51
|
+
</TooltipProvider>
|
|
52
|
+
</Provider>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const next = screen.getByRole("button", { name: "Next pending AI cell" });
|
|
56
|
+
const previous = screen.getByRole("button", {
|
|
57
|
+
name: "Previous pending AI cell",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
fireEvent.click(next);
|
|
61
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
|
|
62
|
+
expect(screen.getByText("1 / 3")).toBeInTheDocument();
|
|
63
|
+
|
|
64
|
+
fireEvent.click(next);
|
|
65
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(second, "focus");
|
|
66
|
+
expect(screen.getByText("2 / 3")).toBeInTheDocument();
|
|
67
|
+
|
|
68
|
+
fireEvent.click(previous);
|
|
69
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
|
|
70
|
+
expect(screen.getByText("1 / 3")).toBeInTheDocument();
|
|
71
|
+
|
|
72
|
+
fireEvent.click(next);
|
|
73
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(second, "focus");
|
|
74
|
+
expect(screen.getByText("2 / 3")).toBeInTheDocument();
|
|
75
|
+
|
|
76
|
+
fireEvent.click(next);
|
|
77
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(third, "focus");
|
|
78
|
+
expect(screen.getByText("3 / 3")).toBeInTheDocument();
|
|
79
|
+
|
|
80
|
+
fireEvent.click(next);
|
|
81
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
|
|
82
|
+
expect(screen.getByText("1 / 3")).toBeInTheDocument();
|
|
83
|
+
|
|
84
|
+
fireEvent.click(previous);
|
|
85
|
+
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(third, "focus");
|
|
86
|
+
expect(screen.getByText("3 / 3")).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
32
89
|
it("disables bulk actions while cells are still generating", () => {
|
|
33
90
|
const store = createStore();
|
|
34
91
|
const generationId = Symbol("staged-cell-generation");
|
|
@@ -79,7 +79,12 @@ export const PendingAICells: React.FC = () => {
|
|
|
79
79
|
<SparklesIcon className="h-4 w-4 text-primary" />
|
|
80
80
|
|
|
81
81
|
<div className="flex items-center">
|
|
82
|
-
<Button
|
|
82
|
+
<Button
|
|
83
|
+
aria-label="Previous pending AI cell"
|
|
84
|
+
variant="ghost"
|
|
85
|
+
size="icon"
|
|
86
|
+
onClick={() => clickNext("down")}
|
|
87
|
+
>
|
|
83
88
|
<ChevronUp className="h-3.5 w-3.5" />
|
|
84
89
|
</Button>
|
|
85
90
|
<span className="text-xs font-mono min-w-14 text-center">
|
|
@@ -87,7 +92,12 @@ export const PendingAICells: React.FC = () => {
|
|
|
87
92
|
? `${listStagedCells.length} pending`
|
|
88
93
|
: `${currentIndex + 1} / ${listStagedCells.length}`}
|
|
89
94
|
</span>
|
|
90
|
-
<Button
|
|
95
|
+
<Button
|
|
96
|
+
aria-label="Next pending AI cell"
|
|
97
|
+
variant="ghost"
|
|
98
|
+
size="icon"
|
|
99
|
+
onClick={() => clickNext("up")}
|
|
100
|
+
>
|
|
91
101
|
<ChevronDown className="h-3.5 w-3.5" />
|
|
92
102
|
</Button>
|
|
93
103
|
</div>
|
|
@@ -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}
|
|
@@ -158,6 +158,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
158
158
|
<SourceBadge source={packageAlert.source} />
|
|
159
159
|
</span>
|
|
160
160
|
<Button
|
|
161
|
+
aria-label="Dismiss missing packages alert"
|
|
161
162
|
variant="text"
|
|
162
163
|
data-testid="remove-banner-button"
|
|
163
164
|
size="icon"
|
|
@@ -171,12 +172,12 @@ export const PackageAlert: React.FC = () => {
|
|
|
171
172
|
<p>The following packages were not found:</p>
|
|
172
173
|
<table className="list-disc ml-2 mt-1">
|
|
173
174
|
<tbody>
|
|
174
|
-
{packageAlert.packages.map((pkg
|
|
175
|
+
{packageAlert.packages.map((pkg) => {
|
|
175
176
|
const parsed = parsePackageSpecifier(pkg);
|
|
176
177
|
const currentExtras = selectedExtras[pkg] || parsed.extras;
|
|
177
178
|
|
|
178
179
|
return (
|
|
179
|
-
<tr className="font-mono text-sm" key={
|
|
180
|
+
<tr className="font-mono text-sm" key={pkg}>
|
|
180
181
|
<td className="pr-2 py-1 align-middle">
|
|
181
182
|
<BoxIcon size="1rem" />
|
|
182
183
|
</td>
|
|
@@ -270,6 +271,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
270
271
|
<SourceBadge source={packageAlert.source} />
|
|
271
272
|
</span>
|
|
272
273
|
<Button
|
|
274
|
+
aria-label={`Dismiss ${title}`}
|
|
273
275
|
variant="text"
|
|
274
276
|
data-testid="remove-banner-button"
|
|
275
277
|
size="icon"
|
|
@@ -294,7 +296,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
294
296
|
</>
|
|
295
297
|
)}
|
|
296
298
|
<ul className="list-disc ml-2 mt-1">
|
|
297
|
-
{Object.entries(packageAlert.packages).map(([pkg, st]
|
|
299
|
+
{Object.entries(packageAlert.packages).map(([pkg, st]) => (
|
|
298
300
|
<li
|
|
299
301
|
className={cn(
|
|
300
302
|
"flex items-center gap-1 font-mono text-sm",
|
|
@@ -305,7 +307,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
305
307
|
status === "failed" &&
|
|
306
308
|
"text-muted-foreground",
|
|
307
309
|
)}
|
|
308
|
-
key={
|
|
310
|
+
key={pkg}
|
|
309
311
|
>
|
|
310
312
|
<ProgressIcon status={st} />
|
|
311
313
|
{pkg}
|
|
@@ -12,7 +12,13 @@ interface SidebarToggleProps {
|
|
|
12
12
|
export const SidebarToggle = ({ isOpen, toggle }: SidebarToggleProps) => {
|
|
13
13
|
return (
|
|
14
14
|
<div className="invisible lg:visible absolute top-[12px] right-[16px] z-20">
|
|
15
|
-
<Button
|
|
15
|
+
<Button
|
|
16
|
+
aria-label={isOpen ? "Collapse sidebar" : "Expand sidebar"}
|
|
17
|
+
onClick={toggle}
|
|
18
|
+
className="w-10 h-8"
|
|
19
|
+
variant="ghost"
|
|
20
|
+
size="icon"
|
|
21
|
+
>
|
|
16
22
|
<ChevronLeft
|
|
17
23
|
className={cn(
|
|
18
24
|
"h-5 w-5 transition-transform ease-in-out duration-700",
|
|
@@ -41,7 +41,7 @@ export const MCPStatusIndicator: React.FC = () => {
|
|
|
41
41
|
<Popover>
|
|
42
42
|
<Tooltip content="MCP Status">
|
|
43
43
|
<PopoverTrigger asChild={true}>
|
|
44
|
-
<Button variant="text" size="icon">
|
|
44
|
+
<Button aria-label="MCP status" variant="text" size="icon">
|
|
45
45
|
<PlugIcon
|
|
46
46
|
className={cn(
|
|
47
47
|
"h-4 w-4",
|
|
@@ -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>
|
|
@@ -450,6 +450,7 @@ export const SlideSidebar = ({
|
|
|
450
450
|
)}
|
|
451
451
|
<Tooltip content={isConfigOpen ? "Collapse panel" : "Expand panel"}>
|
|
452
452
|
<Button
|
|
453
|
+
aria-label={isConfigOpen ? "Collapse panel" : "Expand panel"}
|
|
453
454
|
variant="ghost"
|
|
454
455
|
size="icon"
|
|
455
456
|
className="h-7 w-7 text-muted-foreground hover:text-foreground"
|
|
@@ -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
|
},
|