@marimo-team/islands 0.23.11-dev9 → 0.23.11

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 (82) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Z3RP7Vmm.js → ConnectedDataExplorerComponent-WqG-xX4l.js} +13 -13
  2. package/dist/{ErrorBoundary-DpbaKVv7.js → ErrorBoundary-BNx_OSVo.js} +2 -2
  3. package/dist/{any-language-editor-CAgFD4Kd.js → any-language-editor-rPSlOll9.js} +5 -5
  4. package/dist/assets/__vite-browser-external-DAm_YW43.js +1 -0
  5. package/dist/assets/{worker-CC0Oul9k.js → worker-DEDLIQQV.js} +2 -2
  6. package/dist/{button-C5K9fIPF.js → button-vQhauTmO.js} +20 -0
  7. package/dist/{capabilities-BceAxrAW.js → capabilities-BEHzIS99.js} +1 -1
  8. package/dist/{chat-ui-c1FdlK-C.js → chat-ui-k2kqhCv5.js} +135 -106
  9. package/dist/{check-BCaJeT-J.js → check-nrzHDi45.js} +1 -1
  10. package/dist/{code-visibility-0vPSjYnM.js → code-visibility-CFUW5L3i.js} +81 -68
  11. package/dist/{copy-UqRYxiOg.js → copy-UhDed7D4.js} +2 -2
  12. package/dist/{dist-Dk6PV_d3.js → dist-DYGLrbYQ.js} +2 -2
  13. package/dist/{error-banner-CJXYJ6Sb.js → error-banner-BHAkVFc2.js} +2 -2
  14. package/dist/{esm-BaH2eg5-.js → esm-Bqu9AE2K.js} +1 -1
  15. package/dist/{extends-D_hDsj6R.js → extends-9Yl5BEcg.js} +4 -4
  16. package/dist/{formats-C4wO47tk.js → formats-BV4bOfMI.js} +3 -3
  17. package/dist/{glide-data-editor-BPkCPs7L.js → glide-data-editor-BDTq6YUb.js} +9 -9
  18. package/dist/{html-to-image-D5-EpALB.js → html-to-image-C86pQALH.js} +16 -16
  19. package/dist/{input-OdWHkobi.js → input-AKkGXdyV.js} +6 -6
  20. package/dist/{label-CC4ytI1X.js → label-E3ZJXHu8.js} +2 -2
  21. package/dist/{loader-BWLPpjKK.js → loader-YPuQvn1Y.js} +1 -1
  22. package/dist/main.js +981 -978
  23. package/dist/{mermaid-BotvIKg9.js → mermaid-QFAR9YgY.js} +5 -5
  24. package/dist/{process-output-WDZE0cyS.js → process-output-nNw4OpSj.js} +3 -3
  25. package/dist/{reveal-component-BZpDHGsk.js → reveal-component-0u6v5UUq.js} +10 -10
  26. package/dist/{spec-X7FwLJni.js → spec-B45_YCNI.js} +4 -4
  27. package/dist/{strings-J57tzLr3.js → strings-Cq2s9_EQ.js} +4 -4
  28. package/dist/style.css +1 -1
  29. package/dist/{swiper-component-B2t5sN1q.js → swiper-component-BNa_4kh2.js} +2 -2
  30. package/dist/{toDate-d8RCRrRd.js → toDate-Do1xRzAo.js} +3 -3
  31. package/dist/{tooltip-DpcyNkQ2.js → tooltip-Bz3OAwrU.js} +3 -3
  32. package/dist/{types-ChtMFmZ2.js → types-D8gEGs4R.js} +1 -1
  33. package/dist/{useAsyncData-PonK__yh.js → useAsyncData-CL3o2p4i.js} +1 -1
  34. package/dist/{useDateFormatter-QB-3MpYr.js → useDateFormatter-BC6iSz9g.js} +2 -2
  35. package/dist/{useDeepCompareMemoize-D3NGWke6.js → useDeepCompareMemoize-BPx2MuOK.js} +1 -1
  36. package/dist/{useIframeCapabilities-C4JTXTIh.js → useIframeCapabilities-C6Ta3EyP.js} +1 -1
  37. package/dist/{useLifecycle-00mO3OSS.js → useLifecycle-C3Ec71q0.js} +3 -3
  38. package/dist/{useTheme-DEhDzATN.js → useTheme-ZhT6uIu3.js} +4 -3
  39. package/dist/{vega-component-DGPUhbDs.js → vega-component-C3AWYGAL.js} +10 -10
  40. package/dist/{zod-aLSua2NL.js → zod-DXqkaI_w.js} +1 -1
  41. package/package.json +1 -1
  42. package/src/components/ai/ai-model-dropdown.tsx +6 -0
  43. package/src/components/app-config/ai-config.tsx +73 -55
  44. package/src/components/app-config/data-form.tsx +33 -43
  45. package/src/components/app-config/is-overridden.tsx +125 -58
  46. package/src/components/app-config/user-config-form.tsx +178 -227
  47. package/src/components/chat/chat-display.tsx +5 -0
  48. package/src/components/chat/chat-panel.tsx +21 -8
  49. package/src/components/chat/tool-call/tool-call-view.tsx +4 -0
  50. package/src/components/chat/tool-call/tool-history-row.tsx +26 -5
  51. package/src/components/data-table/TableTopBar.tsx +4 -4
  52. package/src/components/data-table/__tests__/columns.test.tsx +20 -0
  53. package/src/components/data-table/__tests__/data-table.test.tsx +28 -0
  54. package/src/components/data-table/columns.tsx +16 -1
  55. package/src/components/data-table/data-table.tsx +3 -3
  56. package/src/components/data-table/filters/types.ts +1 -0
  57. package/src/components/data-table/renderers.tsx +5 -1
  58. package/src/components/datasources/__tests__/install-package-button.test.tsx +11 -0
  59. package/src/components/datasources/__tests__/utils.test.ts +127 -2
  60. package/src/components/datasources/datasources.tsx +44 -17
  61. package/src/components/datasources/install-package-button.tsx +3 -1
  62. package/src/components/datasources/utils.ts +45 -0
  63. package/src/components/editor/actions/useNotebookActions.tsx +62 -21
  64. package/src/components/editor/actions/useSetCodeVisibility.ts +57 -0
  65. package/src/components/editor/header/__tests__/status.test.tsx +0 -4
  66. package/src/components/editor/header/status.tsx +2 -3
  67. package/src/core/cells/__tests__/cells.test.ts +33 -0
  68. package/src/core/cells/cells.ts +1 -1
  69. package/src/core/config/config-schema.ts +6 -1
  70. package/src/core/edit-app.tsx +14 -0
  71. package/src/core/hotkeys/hotkeys.ts +20 -0
  72. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
  73. package/src/core/websocket/types.ts +0 -1
  74. package/src/core/websocket/useMarimoKernelConnection.tsx +0 -14
  75. package/src/css/app/print.css +8 -0
  76. package/src/css/md.css +12 -0
  77. package/src/plugins/impl/DataTablePlugin.tsx +12 -5
  78. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -1
  79. package/src/plugins/impl/chat/chat-ui.tsx +1 -0
  80. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
  81. package/dist/assets/__vite-browser-external-eshhtsgZ.js +0 -1
  82. package/src/components/editor/actions/useHideAllMarkdownCode.ts +0 -53
@@ -83,6 +83,8 @@ import {
83
83
  areSchemasResolved,
84
84
  areTablesResolved,
85
85
  isSchemaless,
86
+ schemaSubtreeMatchesSearch,
87
+ shouldExpandDatabaseForSearch,
86
88
  sqlCode,
87
89
  tableUniqueId,
88
90
  } from "./utils";
@@ -360,7 +362,6 @@ export const DataSources: React.FC = () => {
360
362
  key={database.name}
361
363
  connection={connection}
362
364
  database={database}
363
- hasSearch={hasSearch}
364
365
  searchValue={searchValue}
365
366
  />
366
367
  ))}
@@ -435,7 +436,6 @@ interface DataSourceTree {
435
436
  dialect: string;
436
437
  engineName: string;
437
438
  databaseName: string;
438
- hasSearch: boolean;
439
439
  searchValue?: string;
440
440
  }
441
441
 
@@ -470,9 +470,8 @@ function buildSqlTableContext(
470
470
  const DatabaseTree: React.FC<{
471
471
  connection: DataSourceConnection;
472
472
  database: Database;
473
- hasSearch: boolean;
474
473
  searchValue?: string;
475
- }> = ({ connection, database, hasSearch, searchValue }) => {
474
+ }> = ({ connection, database, searchValue }) => {
476
475
  const tree = React.useMemo<DataSourceTree>(
477
476
  () => ({
478
477
  engineName: connection.name,
@@ -480,7 +479,6 @@ const DatabaseTree: React.FC<{
480
479
  dialect: connection.dialect,
481
480
  defaultSchema: connection.default_schema,
482
481
  defaultDatabase: connection.default_database,
483
- hasSearch,
484
482
  searchValue,
485
483
  }),
486
484
  [
@@ -489,7 +487,6 @@ const DatabaseTree: React.FC<{
489
487
  connection.default_schema,
490
488
  connection.default_database,
491
489
  database.name,
492
- hasSearch,
493
490
  searchValue,
494
491
  ],
495
492
  );
@@ -498,7 +495,7 @@ const DatabaseTree: React.FC<{
498
495
  <DatabaseItem
499
496
  engineName={connection.name}
500
497
  database={database}
501
- hasSearch={hasSearch}
498
+ searchValue={searchValue}
502
499
  >
503
500
  <DataSourceTreeContext.Provider value={tree}>
504
501
  <SchemaList
@@ -513,18 +510,30 @@ const DatabaseTree: React.FC<{
513
510
  };
514
511
 
515
512
  const DatabaseItem: React.FC<{
516
- hasSearch: boolean;
513
+ searchValue?: string;
517
514
  engineName: string;
518
515
  database: Database;
519
516
  children: React.ReactNode;
520
- }> = ({ hasSearch, engineName, database, children }) => {
521
- const [isExpanded, setIsExpanded] = React.useState(false);
517
+ }> = ({ searchValue, engineName, database, children }) => {
518
+ // Auto-expand during search only when a loaded schema under this database
519
+ // contains a matching table.
520
+ const expandForSearch = shouldExpandDatabaseForSearch(database, searchValue);
521
+ const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
522
522
  const [isSelected, setIsSelected] = React.useState(false);
523
- const [prevHasSearch, setPrevHasSearch] = React.useState(hasSearch);
524
-
525
- if (prevHasSearch !== hasSearch) {
526
- setPrevHasSearch(hasSearch);
527
- setIsExpanded(hasSearch);
523
+ const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
524
+ const [prevExpandForSearch, setPrevExpandForSearch] =
525
+ React.useState(expandForSearch);
526
+
527
+ // Re-sync when the query changes or when newly resolved data turns this
528
+ // branch into a match under the same query (deferred subtrees resolve
529
+ // asynchronously, so the decision can flip without the query changing).
530
+ if (
531
+ prevSearchValue !== searchValue ||
532
+ prevExpandForSearch !== expandForSearch
533
+ ) {
534
+ setPrevSearchValue(searchValue);
535
+ setPrevExpandForSearch(expandForSearch);
536
+ setIsExpanded(expandForSearch);
528
537
  }
529
538
 
530
539
  return (
@@ -657,12 +666,30 @@ interface SchemaNodeProps {
657
666
  const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
658
667
  const { schema, schemaPath, depth } = props;
659
668
  const tree = useDataSourceTree();
660
- const { databaseName, hasSearch, searchValue } = tree;
661
- const [isExpanded, setIsExpanded] = React.useState(hasSearch);
669
+ const { databaseName, searchValue } = tree;
670
+ // Auto-expand during search only when this schema's loaded subtree contains a
671
+ // matching table.
672
+ const expandForSearch = schemaSubtreeMatchesSearch(schema, searchValue);
673
+ const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
662
674
  const [isSelected, setIsSelected] = React.useState(false);
675
+ const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
676
+ const [prevExpandForSearch, setPrevExpandForSearch] =
677
+ React.useState(expandForSearch);
663
678
  const uniqueValue = `${databaseName}:${schemaPath.join(".")}`;
664
679
  const childSchemas = schema.child_schemas ?? [];
665
680
 
681
+ // Re-sync when the query changes or when newly resolved data turns this
682
+ // subtree into a match under the same query (deferred tables/child schemas
683
+ // resolve asynchronously, so the decision can flip without the query changing).
684
+ if (
685
+ prevSearchValue !== searchValue ||
686
+ prevExpandForSearch !== expandForSearch
687
+ ) {
688
+ setPrevSearchValue(searchValue);
689
+ setPrevExpandForSearch(expandForSearch);
690
+ setIsExpanded(expandForSearch);
691
+ }
692
+
666
693
  return (
667
694
  <>
668
695
  <CommandItem
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React from "react";
4
4
  import { Button } from "@/components/ui/button";
5
+ import { useInstallAllowed } from "@/core/mode";
5
6
  import { useInstallPackages } from "@/core/packages/useInstallPackage";
6
7
  import { cn } from "@/utils/cn";
7
8
 
@@ -23,8 +24,9 @@ export const InstallPackageButton: React.FC<InstallPackageButtonProps> = ({
23
24
  onInstall,
24
25
  }) => {
25
26
  const { handleInstallPackages } = useInstallPackages();
27
+ const installAllowed = useInstallAllowed();
26
28
 
27
- if (!packages || packages.length === 0) {
29
+ if (!packages || packages.length === 0 || !installAllowed) {
28
30
  return null;
29
31
  }
30
32
 
@@ -52,6 +52,51 @@ export function areChildSchemasResolved(schema: DatabaseSchema): boolean {
52
52
  return schema.child_schemas_resolved !== false;
53
53
  }
54
54
 
55
+ /**
56
+ * Whether a loaded schema subtree contains a table whose name matches
57
+ * `searchValue`. Deferred (not-yet-fetched) tables and child schemas are
58
+ * treated as non-matching, so searching never triggers a lazy catalog fetch on
59
+ * large/remote connections.
60
+ */
61
+ export function schemaSubtreeMatchesSearch(
62
+ schema: DatabaseSchema,
63
+ searchValue: string | undefined,
64
+ ): boolean {
65
+ const query = searchValue?.trim().toLowerCase();
66
+ if (!query) {
67
+ return false;
68
+ }
69
+ if (
70
+ areTablesResolved(schema) &&
71
+ schema.tables.some((table) => table.name.toLowerCase().includes(query))
72
+ ) {
73
+ return true;
74
+ }
75
+ if (areChildSchemasResolved(schema)) {
76
+ return (schema.child_schemas ?? []).some((child) =>
77
+ schemaSubtreeMatchesSearch(child, query),
78
+ );
79
+ }
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Auto-expand a database row during search only when one of its loaded schemas
85
+ * contains a matching table. Returns false when the schema list itself is
86
+ * deferred.
87
+ */
88
+ export function shouldExpandDatabaseForSearch(
89
+ database: Database,
90
+ searchValue: string | undefined,
91
+ ): boolean {
92
+ if (!searchValue?.trim() || !areSchemasResolved(database)) {
93
+ return false;
94
+ }
95
+ return database.schemas.some((schema) =>
96
+ schemaSubtreeMatchesSearch(schema, searchValue),
97
+ );
98
+ }
99
+
55
100
  interface SqlCodeFormatter {
56
101
  /**
57
102
  * Format the table path based on dialect-specific rules
@@ -14,6 +14,7 @@ import {
14
14
  DownloadIcon,
15
15
  EditIcon,
16
16
  ExternalLinkIcon,
17
+ EyeIcon,
17
18
  EyeOffIcon,
18
19
  FastForwardIcon,
19
20
  FileIcon,
@@ -41,7 +42,10 @@ import {
41
42
  YoutubeIcon,
42
43
  ZapIcon,
43
44
  } from "lucide-react";
44
- import { settingDialogAtom } from "@/components/app-config/state";
45
+ import {
46
+ settingDialogAtom,
47
+ useOpenSettingsToTab,
48
+ } from "@/components/app-config/state";
45
49
  import { MarkdownIcon } from "@/components/editor/cell/code/icons";
46
50
  import { MarimoPlusIcon } from "@/components/icons/marimo-icons";
47
51
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
@@ -57,7 +61,8 @@ import {
57
61
  useCellActions,
58
62
  } from "@/core/cells/cells";
59
63
  import { disabledCellIds } from "@/core/cells/utils";
60
- import { useResolvedMarimoConfig } from "@/core/config/config";
64
+ import { capabilitiesAtom } from "@/core/config/capabilities";
65
+ import { aiEnabledAtom, useResolvedMarimoConfig } from "@/core/config/config";
61
66
  import { Constants } from "@/core/constants";
62
67
  import {
63
68
  updateCellOutputsWithScreenshots,
@@ -86,7 +91,7 @@ import { Strings } from "@/utils/strings";
86
91
  import { newNotebookURL } from "@/utils/urls";
87
92
  import { useRunAllCells } from "../cell/useRunCells";
88
93
  import { useChromeActions, useChromeState } from "../chrome/state";
89
- import { PANELS } from "../chrome/types";
94
+ import { isPanelHidden, PANELS } from "../chrome/types";
90
95
  import { AddConnectionDialogContent } from "../connections/add-connection-dialog";
91
96
  import { keyboardShortcutsAtom } from "../controls/keyboard-shortcuts";
92
97
  import { commandPaletteAtom } from "../controls/state";
@@ -95,8 +100,8 @@ import { LAYOUT_TYPES } from "../renderers/types";
95
100
  import { runServerSidePDFDownload } from "./pdf-export";
96
101
  import type { ActionButton } from "./types";
97
102
  import { useCopyNotebook } from "./useCopyNotebook";
98
- import { useHideAllMarkdownCode } from "./useHideAllMarkdownCode";
99
103
  import { useRestartKernel } from "./useRestartKernel";
104
+ import { useSetCodeVisibility } from "./useSetCodeVisibility";
100
105
 
101
106
  const NOOP_HANDLER = (event?: Event) => {
102
107
  event?.preventDefault();
@@ -110,8 +115,10 @@ export function useNotebookActions() {
110
115
  const { selectedPanel } = useChromeState();
111
116
  const [viewState] = useAtom(viewStateAtom);
112
117
  const kioskMode = useAtomValue(kioskModeAtom);
113
- const hideAllMarkdownCode = useHideAllMarkdownCode();
118
+ const setCodeVisibility = useSetCodeVisibility();
114
119
  const [resolvedConfig] = useResolvedMarimoConfig();
120
+ const capabilities = useAtomValue(capabilitiesAtom);
121
+ const aiEnabled = useAtomValue(aiEnabledAtom);
115
122
 
116
123
  const {
117
124
  updateCellConfig,
@@ -126,6 +133,7 @@ export function useNotebookActions() {
126
133
  const copyNotebook = useCopyNotebook(filename);
127
134
  const setCommandPaletteOpen = useSetAtom(commandPaletteAtom);
128
135
  const setSettingsDialogOpen = useSetAtom(settingDialogAtom);
136
+ const { handleClick: openSettings } = useOpenSettingsToTab();
129
137
  const setKeyboardShortcutsOpen = useSetAtom(keyboardShortcutsAtom);
130
138
  const {
131
139
  exportAsIPYNB,
@@ -407,20 +415,31 @@ export function useNotebookActions() {
407
415
  label: "Helper panel",
408
416
  redundant: true,
409
417
  handle: NOOP_HANDLER,
410
- dropdown: PANELS.flatMap(
411
- ({ type: id, Icon, hidden, additionalKeywords }) => {
412
- if (hidden) {
413
- return [];
414
- }
415
- return {
416
- label: Strings.startCase(id),
417
- rightElement: renderCheckboxElement(selectedPanel === id),
418
- icon: <Icon size={14} strokeWidth={1.5} />,
419
- handle: () => toggleApplication(id),
420
- additionalKeywords,
421
- };
422
- },
423
- ),
418
+ dropdown: PANELS.flatMap((panel) => {
419
+ // Still show the AI panel in the command palette so users can try AI
420
+ // features. When AI is disabled, open settings instead of the panel.
421
+ const openAiSettingsWhenDisabled = panel.type === "ai" && !aiEnabled;
422
+ if (
423
+ isPanelHidden({ panel, capabilities, aiEnabled }) &&
424
+ !openAiSettingsWhenDisabled
425
+ ) {
426
+ return [];
427
+ }
428
+ const { type: id, Icon, additionalKeywords } = panel;
429
+ return {
430
+ label: Strings.startCase(id),
431
+ rightElement: renderCheckboxElement(selectedPanel === id),
432
+ icon: <Icon size={14} strokeWidth={1.5} />,
433
+ handle: () => {
434
+ if (openAiSettingsWhenDisabled) {
435
+ openSettings("ai", "ai-features");
436
+ return;
437
+ }
438
+ toggleApplication(id);
439
+ },
440
+ additionalKeywords,
441
+ };
442
+ }),
424
443
  },
425
444
 
426
445
  {
@@ -560,11 +579,33 @@ export function useNotebookActions() {
560
579
  clearAllCellOutputs();
561
580
  },
562
581
  },
582
+ {
583
+ icon: <EyeIcon size={14} strokeWidth={1.5} />,
584
+ label: "Show all code",
585
+ hotkey: "global.showAllCode",
586
+ handle: () => setCodeVisibility(false, "code"),
587
+ redundant: true,
588
+ },
589
+ {
590
+ icon: <EyeOffIcon size={14} strokeWidth={1.5} />,
591
+ label: "Hide all code",
592
+ hotkey: "global.hideAllCode",
593
+ handle: () => setCodeVisibility(true, "code"),
594
+ redundant: true,
595
+ },
596
+ {
597
+ icon: <EyeIcon size={14} strokeWidth={1.5} />,
598
+ label: "Show all markdown code",
599
+ hotkey: "global.showAllMarkdownCode",
600
+ handle: () => setCodeVisibility(false, "markdown"),
601
+ redundant: true,
602
+ },
563
603
  {
564
604
  icon: <EyeOffIcon size={14} strokeWidth={1.5} />,
565
605
  label: "Hide all markdown code",
566
- handle: hideAllMarkdownCode,
567
- redundant: true, // hidden by default
606
+ hotkey: "global.hideAllMarkdownCode",
607
+ handle: () => setCodeVisibility(true, "markdown"),
608
+ redundant: true,
568
609
  },
569
610
  {
570
611
  icon: <ChevronRightCircleIcon size={14} strokeWidth={1.5} />,
@@ -0,0 +1,57 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useCallback } from "react";
4
+ import { getNotebook, useCellActions } from "@/core/cells/cells";
5
+ import type { CellId } from "@/core/cells/ids";
6
+ import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
7
+ import { useRequestClient } from "@/core/network/requests";
8
+ import type { CellConfig } from "@/core/network/types";
9
+ import { Objects } from "@/utils/objects";
10
+
11
+ const markdownAdapter = new MarkdownLanguageAdapter();
12
+
13
+ /** The kind of cell a visibility change applies to. */
14
+ export type CellCodeKind = "code" | "markdown";
15
+
16
+ const cellCodeKind = (code: string): CellCodeKind =>
17
+ markdownAdapter.isSupported(code) ? "markdown" : "code";
18
+
19
+ /**
20
+ * Hook returning a callback that hides or shows the code editor for every
21
+ * cell of the given kind.
22
+ */
23
+ export const useSetCodeVisibility = () => {
24
+ const { updateCellConfig } = useCellActions();
25
+ const { saveCellConfig } = useRequestClient();
26
+
27
+ return useCallback(
28
+ async (hidden: boolean, kind: CellCodeKind) => {
29
+ const notebook = getNotebook();
30
+
31
+ const newConfigs: Record<CellId, Partial<CellConfig>> = {};
32
+
33
+ for (const cellId of notebook.cellIds.inOrderIds) {
34
+ const cell = notebook.cellData[cellId];
35
+ if (cell === undefined || cell.config.hide_code === hidden) {
36
+ continue;
37
+ }
38
+ if (cellCodeKind(cell.code) !== kind) {
39
+ continue;
40
+ }
41
+ newConfigs[cellId] = { hide_code: hidden };
42
+ }
43
+
44
+ const entries = Objects.entries(newConfigs);
45
+ if (entries.length === 0) {
46
+ return;
47
+ }
48
+
49
+ await saveCellConfig({ configs: newConfigs });
50
+
51
+ for (const [cellId, config] of entries) {
52
+ updateCellConfig({ cellId, config });
53
+ }
54
+ },
55
+ [updateCellConfig, saveCellConfig],
56
+ );
57
+ };
@@ -67,10 +67,6 @@ describe("StatusOverlay disconnect indicator", () => {
67
67
  });
68
68
 
69
69
  it.each([
70
- [
71
- WebSocketClosedReason.MALFORMED_QUERY,
72
- "the kernel did not recognize a request; please file a bug with marimo",
73
- ],
74
70
  [
75
71
  WebSocketClosedReason.KERNEL_STARTUP_ERROR,
76
72
  "Failed to start kernel sandbox",
@@ -22,9 +22,8 @@ export const StatusOverlay: React.FC<{
22
22
  const { mode } = useAtomValue(viewStateAtom);
23
23
  const isClosed = connection.state === WebSocketState.CLOSED;
24
24
  const isOpen = connection.state === WebSocketState.OPEN;
25
- // Only KERNEL_DISCONNECTED is recoverable by a retry. Other terminal
26
- // reasons (MALFORMED_QUERY, KERNEL_STARTUP_ERROR) would deterministically
27
- // fail the same way.
25
+ // Only KERNEL_DISCONNECTED is recoverable by a retry. KERNEL_STARTUP_ERROR
26
+ // would deterministically fail the same way.
28
27
  const canReconnect =
29
28
  isClosed && connection.code === WebSocketClosedReason.KERNEL_DISCONNECTED;
30
29
 
@@ -2920,6 +2920,39 @@ describe("cell reducer", () => {
2920
2920
  expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2921
2921
  });
2922
2922
 
2923
+ it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
2924
+ // Markdown cells are created with hideCode and non-empty default code
2925
+ // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
2926
+ // should be shown until first blur.
2927
+ actions.createNewCell({
2928
+ cellId: "__end__",
2929
+ before: false,
2930
+ code: 'mo.md(r"""\n""")',
2931
+ hideCode: true,
2932
+ autoFocus: true,
2933
+ });
2934
+
2935
+ const newCellId =
2936
+ state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2937
+ expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2938
+ });
2939
+
2940
+ it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
2941
+ // Cells created by the kernel (e.g. via code_mode) carry code and
2942
+ // autoFocus=false; their hide_code must take effect immediately.
2943
+ actions.createNewCell({
2944
+ cellId: "__end__",
2945
+ before: false,
2946
+ code: "x = 1",
2947
+ hideCode: true,
2948
+ autoFocus: false,
2949
+ });
2950
+
2951
+ const newCellId =
2952
+ state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2953
+ expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2954
+ });
2955
+
2923
2956
  it("does not add cell to untouchedNewCells when hideCode is false", () => {
2924
2957
  actions.createNewCell({
2925
2958
  cellId: "__end__",
@@ -276,7 +276,7 @@ const {
276
276
  },
277
277
  scrollKey: autoFocus ? newCellId : null,
278
278
  untouchedNewCells:
279
- hideCode && !code
279
+ hideCode && autoFocus
280
280
  ? new Set([...state.untouchedNewCells, newCellId])
281
281
  : state.untouchedNewCells,
282
282
  };
@@ -45,7 +45,12 @@ export const DEFAULT_AI_MODEL = "openai/gpt-4o";
45
45
  const AUTO_DOWNLOAD_FORMATS = ["html", "markdown", "ipynb"] as const;
46
46
 
47
47
  export type CopilotMode = NonNullable<schemas["AiConfig"]["mode"]>;
48
- export const COPILOT_MODES: CopilotMode[] = ["manual", "ask", "agent"];
48
+ export const COPILOT_MODES: CopilotMode[] = [
49
+ "manual",
50
+ "ask",
51
+ "agent",
52
+ "code_mode",
53
+ ];
49
54
 
50
55
  const AiConfigSchema = z
51
56
  .object({
@@ -20,6 +20,7 @@ import {
20
20
  useRunAllCells,
21
21
  useRunStaleCells,
22
22
  } from "../components/editor/cell/useRunCells";
23
+ import { useSetCodeVisibility } from "../components/editor/actions/useSetCodeVisibility";
23
24
  import { CellArray } from "../components/editor/renderers/cell-array";
24
25
  import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
25
26
  import { useHotkey } from "../hooks/useHotkey";
@@ -112,6 +113,7 @@ export const EditApp: React.FC<AppProps> = ({
112
113
  const runStaleCells = useRunStaleCells();
113
114
  const runAllCells = useRunAllCells();
114
115
  const togglePresenting = useTogglePresenting();
116
+ const setCodeVisibility = useSetCodeVisibility();
115
117
 
116
118
  // HOTKEYS
117
119
  useHotkey("global.runStale", () => {
@@ -126,6 +128,18 @@ export const EditApp: React.FC<AppProps> = ({
126
128
  useHotkey("global.runAll", () => {
127
129
  runAllCells();
128
130
  });
131
+ useHotkey("global.showAllCode", () => {
132
+ setCodeVisibility(false, "code");
133
+ });
134
+ useHotkey("global.hideAllCode", () => {
135
+ setCodeVisibility(true, "code");
136
+ });
137
+ useHotkey("global.showAllMarkdownCode", () => {
138
+ setCodeVisibility(false, "markdown");
139
+ });
140
+ useHotkey("global.hideAllMarkdownCode", () => {
141
+ setCodeVisibility(true, "markdown");
142
+ });
129
143
  useHotkey("global.collapseAllSections", () => {
130
144
  collapseAllCells();
131
145
  });
@@ -370,6 +370,26 @@ const DEFAULT_HOT_KEY = {
370
370
  group: "Other",
371
371
  key: "Mod-j",
372
372
  },
373
+ "global.showAllCode": {
374
+ name: "Show all code",
375
+ group: "Editing",
376
+ key: NOT_SET,
377
+ },
378
+ "global.hideAllCode": {
379
+ name: "Hide all code",
380
+ group: "Editing",
381
+ key: NOT_SET,
382
+ },
383
+ "global.showAllMarkdownCode": {
384
+ name: "Show all markdown code",
385
+ group: "Editing",
386
+ key: NOT_SET,
387
+ },
388
+ "global.hideAllMarkdownCode": {
389
+ name: "Hide all markdown code",
390
+ group: "Editing",
391
+ key: NOT_SET,
392
+ },
373
393
  "global.collapseAllSections": {
374
394
  name: "Collapse all sections",
375
395
  group: "Editing",
@@ -32,7 +32,6 @@ describe("classifyCloseEvent", () => {
32
32
 
33
33
  describe("terminal closes (server-initiated)", () => {
34
34
  it.each([
35
- "MARIMO_WRONG_KERNEL_ID",
36
35
  "MARIMO_NO_FILE_KEY",
37
36
  "MARIMO_NO_SESSION_ID",
38
37
  "MARIMO_NO_SESSION",
@@ -49,18 +48,6 @@ describe("classifyCloseEvent", () => {
49
48
  }
50
49
  });
51
50
 
52
- it("MARIMO_MALFORMED_QUERY → terminal but does NOT close transport", () => {
53
- const decision = classify("MARIMO_MALFORMED_QUERY");
54
- expect(decision.kind).toBe("terminal");
55
- expect(decision.status).toMatchObject({
56
- state: WebSocketState.CLOSED,
57
- code: WebSocketClosedReason.MALFORMED_QUERY,
58
- });
59
- if (decision.kind === "terminal") {
60
- expect(decision.closeTransport).toBe(false);
61
- }
62
- });
63
-
64
51
  it("MARIMO_KERNEL_STARTUP_ERROR → terminal + closeTransport", () => {
65
52
  const decision = classify("MARIMO_KERNEL_STARTUP_ERROR");
66
53
  expect(decision.kind).toBe("terminal");
@@ -14,7 +14,6 @@ export type WebSocketState =
14
14
 
15
15
  export const WebSocketClosedReason = {
16
16
  KERNEL_DISCONNECTED: "KERNEL_DISCONNECTED",
17
- MALFORMED_QUERY: "MALFORMED_QUERY",
18
17
  KERNEL_STARTUP_ERROR: "KERNEL_STARTUP_ERROR",
19
18
  } as const;
20
19
 
@@ -82,12 +82,10 @@ const SUPPORTS_LAZY_KERNELS = true;
82
82
  // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
83
83
  // the backend literals.
84
84
  export type CloseReason =
85
- | "MARIMO_WRONG_KERNEL_ID"
86
85
  | "MARIMO_NO_FILE_KEY"
87
86
  | "MARIMO_NO_SESSION_ID"
88
87
  | "MARIMO_NO_SESSION"
89
88
  | "MARIMO_SHUTDOWN"
90
- | "MARIMO_MALFORMED_QUERY"
91
89
  | "MARIMO_KERNEL_STARTUP_ERROR"
92
90
  | typeof TRANSPORT_EXHAUSTED_REASON;
93
91
 
@@ -107,7 +105,6 @@ export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
107
105
  reason: "kernel not found",
108
106
  },
109
107
  };
110
- case "MARIMO_WRONG_KERNEL_ID":
111
108
  case "MARIMO_NO_FILE_KEY":
112
109
  case "MARIMO_NO_SESSION_ID":
113
110
  case "MARIMO_NO_SESSION":
@@ -121,17 +118,6 @@ export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
121
118
  },
122
119
  closeTransport: true,
123
120
  };
124
- case "MARIMO_MALFORMED_QUERY":
125
- return {
126
- kind: "terminal",
127
- status: {
128
- state: WebSocketState.CLOSED,
129
- code: WebSocketClosedReason.MALFORMED_QUERY,
130
- reason:
131
- "the kernel did not recognize a request; please file a bug with marimo",
132
- },
133
- closeTransport: false,
134
- };
135
121
  case "MARIMO_KERNEL_STARTUP_ERROR":
136
122
  return {
137
123
  kind: "terminal",
@@ -16,6 +16,10 @@ body.printing #App {
16
16
  height: fit-content !important;
17
17
  overflow-y: visible !important;
18
18
  overflow-x: hidden !important;
19
+
20
+ /* With @page margin:0 the themed background reaches the paper edge;
21
+ this padding insets printed content from all four edges. */
22
+ padding: 2rem !important;
19
23
  }
20
24
 
21
25
  h1,
@@ -69,4 +73,8 @@ body.printing #App {
69
73
  padding-right: 0 !important;
70
74
  max-height: none !important;
71
75
  }
76
+
77
+ @page {
78
+ margin: 0;
79
+ }
72
80
  }
package/src/css/md.css CHANGED
@@ -49,6 +49,18 @@
49
49
  .markdown h6 {
50
50
  font-weight: inherit;
51
51
  font-family: var(--heading-font);
52
+
53
+ /*
54
+ Anchor heading spacing to GitHub's markdown rhythm (Primer): a uniform
55
+ 24px top / 16px bottom margin and 1.25 line-height across every level,
56
+ letting font-size — not margin — carry the hierarchy. Tailwind Typography's
57
+ prose defaults scale heading margins per-level up to ~2em (≈48px on h2),
58
+ which is tuned for long-form articles and reads as too airy in a notebook.
59
+ Ref: https://github.com/sindresorhus/github-markdown-css
60
+ */
61
+ margin-top: 1.5rem;
62
+ margin-bottom: 1rem;
63
+ line-height: 1.25;
52
64
  }
53
65
 
54
66
  .markdown hr {