@marimo-team/frontend 0.25.3-dev94 → 0.25.3-dev96

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 (102) hide show
  1. package/dist/assets/{CellStatus-CplYFNTy.js → CellStatus-Dc8AR1al.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-CdGuz4a_.js → ErrorBoundary-DBQW23V4.js} +2 -2
  3. package/dist/assets/{JsonOutput-Csbov8Fx.js → JsonOutput-nLzWQ-0V.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-czclLlOn.js → MarimoErrorOutput-DAFgeK_G.js} +1 -1
  5. package/dist/assets/{RenderHTML-CNDedpS4.js → RenderHTML-DwolbpuI.js} +1 -1
  6. package/dist/assets/{RunButton-CRfdOxVd.js → RunButton-BN3_mnoU.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-BAIX7r0P.js → add-cell-with-ai-CRbG5cTT.js} +2 -2
  8. package/dist/assets/{add-connection-dialog-DrDuM-tI.js → add-connection-dialog-CzCmVM_b.js} +1 -1
  9. package/dist/assets/{agent-panel-CoXq_rlA.js → agent-panel-zpTxANfi.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-COvWn46z.js → ai-model-dropdown-DL56ImUU.js} +2 -2
  11. package/dist/assets/{app-config-button-CtxoYG4F.js → app-config-button-BRBtaFi_.js} +1 -1
  12. package/dist/assets/{blob-DCjrjZKW.js → blob-CuL-H1JC.js} +1 -1
  13. package/dist/assets/{cache-panel-CbWZcBu0.js → cache-panel-BV-c7ed0.js} +1 -1
  14. package/dist/assets/{cell-editor-BnL0Sorl.js → cell-editor-C6QIY1Q-.js} +2 -2
  15. package/dist/assets/{cell-link-BFbgKiRH.js → cell-link-C9tkvy6x.js} +1 -1
  16. package/dist/assets/{cells-yOs72eZm.js → cells-B23_t5uX.js} +1 -1
  17. package/dist/assets/{chat-display-DwRM41t_.js → chat-display-DSezZ1qo.js} +1 -1
  18. package/dist/assets/{chat-panel-DeTEdjxF.js → chat-panel-CocbwA2Q.js} +2 -2
  19. package/dist/assets/{chat-ui-D6XucD07.js → chat-ui-DXvDBccG.js} +2 -2
  20. package/dist/assets/column-preview-BUQ_YMaE.js +1 -0
  21. package/dist/assets/{command-palette-CA6yfNLb.js → command-palette-HbIlNTLD.js} +1 -1
  22. package/dist/assets/{common-B-ZYnO1x.js → common-XXWicvmo.js} +1 -1
  23. package/dist/assets/{components-vu2jLG08.js → components-D1a5GeNf.js} +1 -1
  24. package/dist/assets/{datasource-BakEOWsf.js → datasource-Qo_t5PqI.js} +1 -1
  25. package/dist/assets/{dependency-graph-panel-DCGB3_id.js → dependency-graph-panel-DHrm4LUL.js} +2 -2
  26. package/dist/assets/{documentation-panel-CpGZ2Dfn.js → documentation-panel-BoNI3vXT.js} +1 -1
  27. package/dist/assets/{download-CV1k4XQ4.js → download-Cm3InGht.js} +1 -1
  28. package/dist/assets/{edit-page-By7n2M84.js → edit-page-BVkC2MyU.js} +6 -6
  29. package/dist/assets/{error-entries-JgrZYMbe.js → error-entries-DEkB-8EY.js} +1 -1
  30. package/dist/assets/{error-panel-DS7HWph-.js → error-panel-D7GInT--.js} +1 -1
  31. package/dist/assets/feedback-button-Cz9BdS49.js +1 -0
  32. package/dist/assets/{feedback-button-eYsTCK-g.js → feedback-button-sC38i-D1.js} +1 -1
  33. package/dist/assets/{file-explorer-panel-BTLwwwD4.js → file-explorer-panel-2XFMubVM.js} +1 -1
  34. package/dist/assets/{file-name-input-BaR4h2Ud.js → file-name-input-DPs3KKM-.js} +1 -1
  35. package/dist/assets/{floating-outline-Drd73Quo.js → floating-outline-C7MSAu6q.js} +1 -1
  36. package/dist/assets/{gallery-page-DXUD11Rm.js → gallery-page-B3fJlerH.js} +1 -1
  37. package/dist/assets/{glide-data-editor-DqBBoQBl.js → glide-data-editor-DrhzXms_.js} +1 -1
  38. package/dist/assets/{home-page-DrRto2-v.js → home-page-CajAhF9Q.js} +1 -1
  39. package/dist/assets/{hooks-D25kP4Sr.js → hooks-DteA-neM.js} +1 -1
  40. package/dist/assets/{index-vBuAUWEe.js → index-YQHSwyF3.js} +6 -6
  41. package/dist/assets/{layout-DLHIGZTK.js → layout-DEFRD3k3.js} +3 -3
  42. package/dist/assets/{logs-panel-D5AAD2s9.js → logs-panel-BpM3pLdf.js} +1 -1
  43. package/dist/assets/{markdown-renderer--CwVm4kJ.js → markdown-renderer-C5aIPuRT.js} +1 -1
  44. package/dist/assets/{name-cell-input-BFKZaNI5.js → name-cell-input-Dd8oSXnd.js} +1 -1
  45. package/dist/assets/{outline-panel-qcwRnl0R.js → outline-panel-kFr3Fo2d.js} +1 -1
  46. package/dist/assets/{packages-panel-DnSryNcz.js → packages-panel-Ce_wBuIJ.js} +1 -1
  47. package/dist/assets/{panel-accordion-state-BwJpDMVt.js → panel-accordion-state-BO2rN4A5.js} +1 -1
  48. package/dist/assets/{panels-Aqzwb3UR.js → panels-Cy18qaTN.js} +1 -1
  49. package/dist/assets/{process-output-DuUfHYSr.js → process-output-CDhnnF3f.js} +1 -1
  50. package/dist/assets/{readonly-code-display-BY-9I_Yk.js → readonly-code-display-ChOK6mhE.js} +1 -1
  51. package/dist/assets/{readonly-python-code-DSyr-yE2.js → readonly-python-code-CVwB1OHZ.js} +1 -1
  52. package/dist/assets/{reveal-component-C_Lb7Mom.js → reveal-component-DE-4w0ik.js} +1 -1
  53. package/dist/assets/{run-page-ZTw-kCU-.js → run-page-C6qwGqj6.js} +1 -1
  54. package/dist/assets/{runtime-details-state-B7gpnuXx.js → runtime-details-state-BRSVs8um.js} +1 -1
  55. package/dist/assets/{scratchpad-panel-D2IHkKoG.js → scratchpad-panel--SxH2dnk.js} +1 -1
  56. package/dist/assets/session-panel-B0CkZ3wl.js +1 -0
  57. package/dist/assets/{snippets-panel-BNadUJLU.js → snippets-panel-CN-U1niV.js} +1 -1
  58. package/dist/assets/{state-BaYudep3.js → state-BRL89qqy.js} +1 -1
  59. package/dist/assets/{state-_1LTGJQM.js → state-BoRCcWOg.js} +1 -1
  60. package/dist/assets/{tracing-DbTgS4u9.js → tracing-CqxmObxN.js} +1 -1
  61. package/dist/assets/{tracing-panel-CjlrpXPy.js → tracing-panel-Bn8xJSPi.js} +2 -2
  62. package/dist/assets/{tree-actions-C_KYlaSv.js → tree-actions-DoPCJTpj.js} +1 -1
  63. package/dist/assets/{useAddCell-C0J8quGE.js → useAddCell-DKb7o70l.js} +1 -1
  64. package/dist/assets/{useCellActionButton-CbkOj6BE.js → useCellActionButton-CSNFulkR.js} +1 -1
  65. package/dist/assets/{useDeleteCell-DHkF5wgS.js → useDeleteCell-BPQ6Xcbj.js} +1 -1
  66. package/dist/assets/{useDependencyPanelTab--yXJW6X5.js → useDependencyPanelTab-4SMlwjEg.js} +1 -1
  67. package/dist/assets/{useNotebookActions-dJQalORg.js → useNotebookActions-B4kq9-P4.js} +1 -1
  68. package/dist/assets/{useRunCells-C_IwVJR_.js → useRunCells-BV6nl_Ok.js} +1 -1
  69. package/dist/assets/{useSplitCell-Ci4MEUj8.js → useSplitCell-Ca364Rrx.js} +1 -1
  70. package/dist/index.html +20 -20
  71. package/package.json +1 -1
  72. package/src/components/app-config/ai-config.tsx +1 -0
  73. package/src/components/chat/chat-components.tsx +2 -0
  74. package/src/components/chat/chat-panel.tsx +6 -1
  75. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  76. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  77. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  78. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  79. package/src/components/datasources/column-preview.tsx +8 -2
  80. package/src/components/datasources/datasources.tsx +12 -14
  81. package/src/components/debugger/debugger-code.tsx +5 -0
  82. package/src/components/dependency-graph/panels.tsx +2 -1
  83. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  84. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  85. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  86. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  87. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  88. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  89. package/src/components/editor/notebook-banner.tsx +1 -0
  90. package/src/components/editor/package-alert.tsx +2 -0
  91. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  92. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  93. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  94. package/src/components/pages/home-page.tsx +24 -3
  95. package/src/components/slides/slide-form.tsx +1 -0
  96. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  97. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  98. package/src/plugins/impl/chat/chat-ui.tsx +1 -0
  99. package/src/stories/button.stories.tsx +1 -0
  100. package/dist/assets/column-preview-DXM8SqGY.js +0 -1
  101. package/dist/assets/feedback-button-Dgs_HOOC.js +0 -1
  102. package/dist/assets/session-panel-cOSO3WCX.js +0 -1
@@ -8,6 +8,7 @@ import {
8
8
  import { fireEvent, render, screen, within } from "@testing-library/react";
9
9
  import { beforeAll, describe, expect, it, vi } from "vitest";
10
10
  import { TooltipProvider } from "@/components/ui/tooltip";
11
+ import type { PreviewColumn } from "@/plugins/impl/DataTablePlugin";
11
12
  import { ColumnExplorerPanel } from "../column-explorer-panel/column-explorer";
12
13
  import type { FieldTypesWithExternalType } from "../types";
13
14
 
@@ -36,11 +37,13 @@ const TEST_COLUMNS: ColumnDef<Row>[] = [
36
37
  interface HarnessProps {
37
38
  totalColumns?: number;
38
39
  initiallyHidden?: readonly string[];
40
+ previewColumn?: PreviewColumn;
39
41
  }
40
42
 
41
43
  function PanelHarness({
42
44
  totalColumns = 3,
43
45
  initiallyHidden = EMPTY_HIDDEN_COLUMNS,
46
+ previewColumn = vi.fn().mockResolvedValue({}),
44
47
  }: HarnessProps) {
45
48
  const table = useReactTable<Row>({
46
49
  data: [],
@@ -55,7 +58,7 @@ function PanelHarness({
55
58
  });
56
59
  return (
57
60
  <ColumnExplorerPanel
58
- previewColumn={vi.fn().mockResolvedValue({})}
61
+ previewColumn={previewColumn}
59
62
  fieldTypes={FIELD_TYPES}
60
63
  totalRows={3}
61
64
  totalColumns={totalColumns}
@@ -124,6 +127,29 @@ describe("ColumnExplorerPanel search", () => {
124
127
  });
125
128
  });
126
129
 
130
+ describe("ColumnExplorerPanel chart actions", () => {
131
+ it("names chart actions for each expanded column", async () => {
132
+ const previewColumn = vi.fn<PreviewColumn>().mockResolvedValue({
133
+ chart_code: "chart_code",
134
+ chart_spec: null,
135
+ error: null,
136
+ missing_packages: null,
137
+ stats: null,
138
+ });
139
+ renderPanel({ previewColumn });
140
+ fireEvent.click(screen.getByText("cust_age"));
141
+
142
+ for (const column of ["customer_name", "cust_age"]) {
143
+ expect(
144
+ await screen.findByRole("button", {
145
+ name: `Add chart to notebook for ${column}`,
146
+ }),
147
+ ).toBeInTheDocument();
148
+ expect(previewColumn).toHaveBeenCalledWith({ column });
149
+ }
150
+ });
151
+ });
152
+
127
153
  describe("ColumnExplorerPanel header counts", () => {
128
154
  it("uses rendered-subset total when a clipped column is hidden", () => {
129
155
  // Dataset has 100 columns server-side; only 3 are rendered into the
@@ -345,7 +345,7 @@ const ColumnPreview = ({
345
345
  const chart = chart_spec && renderChart(chart_spec, theme);
346
346
 
347
347
  const addDataframeChart = chart_code && (
348
- <AddDataframeChart chartCode={chart_code} />
348
+ <AddDataframeChart chartCode={chart_code} columnName={columnName} />
349
349
  );
350
350
 
351
351
  return (
@@ -0,0 +1,97 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import { createStore, Provider } from "jotai";
11
+ import { beforeAll, describe, expect, it, vi } from "vitest";
12
+ import { MockRequestClient } from "@/__mocks__/requests";
13
+ import { Command, CommandList } from "@/components/ui/command";
14
+ import { TooltipProvider } from "@/components/ui/tooltip";
15
+ import type { DataTable } from "@/core/kernel/messages";
16
+ import { requestClientAtom } from "@/core/network/requests";
17
+ import { copyToClipboard } from "@/utils/copy";
18
+ import { TableList } from "../datasources";
19
+
20
+ vi.mock("@/utils/copy", () => ({
21
+ copyToClipboard: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ beforeAll(() => {
25
+ HTMLElement.prototype.scrollIntoView = vi.fn();
26
+ });
27
+
28
+ const table: DataTable = {
29
+ name: "users",
30
+ columns: [
31
+ {
32
+ name: "email",
33
+ type: "string",
34
+ external_type: "VARCHAR",
35
+ sample_values: [],
36
+ },
37
+ {
38
+ name: "name",
39
+ type: "string",
40
+ external_type: "VARCHAR",
41
+ sample_values: [],
42
+ },
43
+ ],
44
+ source: "memory",
45
+ source_type: "local",
46
+ type: "table",
47
+ engine: null,
48
+ indexes: null,
49
+ num_columns: 2,
50
+ num_rows: null,
51
+ variable_name: null,
52
+ primary_keys: null,
53
+ };
54
+
55
+ describe("Column actions", () => {
56
+ it("copies each column through one named button without expanding its preview", async () => {
57
+ const store = createStore();
58
+ const client = MockRequestClient.create();
59
+ store.set(requestClientAtom, client);
60
+ render(
61
+ <Provider store={store}>
62
+ <TooltipProvider>
63
+ <Command>
64
+ <CommandList>
65
+ <TableList tables={[table]} />
66
+ </CommandList>
67
+ </Command>
68
+ </TooltipProvider>
69
+ </Provider>,
70
+ );
71
+
72
+ fireEvent.click(await screen.findByText("users"));
73
+
74
+ for (const column of table.columns) {
75
+ const copyButton = await screen.findByRole("button", {
76
+ name: `Copy column name ${column.name}`,
77
+ });
78
+ const row = screen.getByRole("option", {
79
+ name: `${column.name} ${column.external_type}`,
80
+ });
81
+ expect(within(row).getAllByRole("button")).toEqual([copyButton]);
82
+ expect(copyButton.querySelector("button")).toBeNull();
83
+
84
+ fireEvent.focus(copyButton);
85
+ expect(
86
+ await screen.findByRole("tooltip", { name: "Copy column name" }),
87
+ ).toBeInTheDocument();
88
+
89
+ fireEvent.click(copyButton);
90
+ await waitFor(() => {
91
+ expect(copyToClipboard).toHaveBeenLastCalledWith(column.name);
92
+ });
93
+ expect(client.previewDatasetColumn).not.toHaveBeenCalled();
94
+ }
95
+ expect(copyToClipboard).toHaveBeenCalledTimes(2);
96
+ });
97
+ });
@@ -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 chartCode={preview.chart_code} />
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
- }> = ({ chartCode }) => {
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
- <Tooltip content="Copy column name" delayDuration={400}>
1072
- <Button
1073
- variant="text"
1074
- size="icon"
1075
- className="group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-foreground"
1076
- >
1077
- <CopyClipboardIcon
1078
- tooltip={false}
1079
- value={column.name}
1080
- className="h-3 w-3"
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={() => {
@@ -408,6 +408,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
408
408
  </div>
409
409
  </Tooltip>
410
410
  <Button
411
+ aria-label="Discard AI completion"
411
412
  data-testid="decline-completion-button"
412
413
  variant="text"
413
414
  size="icon"
@@ -51,6 +51,7 @@ export const StartupLogsAlert: React.FC = () => {
51
51
  {isRunning ? "Initializing..." : "Initialized"}
52
52
  </span>
53
53
  <Button
54
+ aria-label="Dismiss startup logs"
54
55
  variant="text"
55
56
  data-testid="remove-startup-logs-button"
56
57
  size="icon"
@@ -37,6 +37,7 @@ export const DeleteButton = (props: {
37
37
  return (
38
38
  <Tooltip content={tooltipMsg} usePortal={false}>
39
39
  <Button
40
+ aria-label="Delete cell"
40
41
  variant="ghost"
41
42
  size="icon"
42
43
  onClick={onClick}
@@ -104,6 +104,7 @@ const CachePanel = () => {
104
104
  {/* Header with Refresh Button */}
105
105
  <div className="flex items-center justify-end">
106
106
  <Button
107
+ aria-label="Refresh cache"
107
108
  variant="ghost"
108
109
  size="icon"
109
110
  className="h-6 w-6"
@@ -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 variant="ghost" size="icon" onClick={() => clickNext("up")}>
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 variant="ghost" size="icon" onClick={() => clickNext("down")}>
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>
@@ -41,6 +41,7 @@ export const NotebookBanner: React.FC<Props> = ({ width }) => {
41
41
  {banner.title}
42
42
  </span>
43
43
  <Button
44
+ aria-label={`Dismiss ${banner.title}`}
44
45
  data-testid="remove-banner-button"
45
46
  variant="text"
46
47
  size="icon"
@@ -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"
@@ -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"
@@ -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 onClick={toggle} className="w-10 h-8" variant="ghost" size="icon">
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",