@marimo-team/islands 0.23.11-dev9 → 0.23.12-dev0
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/{ConnectedDataExplorerComponent-Z3RP7Vmm.js → ConnectedDataExplorerComponent-WqG-xX4l.js} +13 -13
- package/dist/{ErrorBoundary-DpbaKVv7.js → ErrorBoundary-BNx_OSVo.js} +2 -2
- package/dist/{any-language-editor-CAgFD4Kd.js → any-language-editor-rPSlOll9.js} +5 -5
- package/dist/assets/__vite-browser-external-DAm_YW43.js +1 -0
- package/dist/assets/{worker-CC0Oul9k.js → worker-DEDLIQQV.js} +2 -2
- package/dist/{button-C5K9fIPF.js → button-vQhauTmO.js} +20 -0
- package/dist/{capabilities-BceAxrAW.js → capabilities-BEHzIS99.js} +1 -1
- package/dist/{chat-ui-c1FdlK-C.js → chat-ui-k2kqhCv5.js} +135 -106
- package/dist/{check-BCaJeT-J.js → check-nrzHDi45.js} +1 -1
- package/dist/{code-visibility-0vPSjYnM.js → code-visibility-BGJLYDS-.js} +81 -68
- package/dist/{copy-UqRYxiOg.js → copy-UhDed7D4.js} +2 -2
- package/dist/{dist-Dk6PV_d3.js → dist-DYGLrbYQ.js} +2 -2
- package/dist/{error-banner-CJXYJ6Sb.js → error-banner-BHAkVFc2.js} +2 -2
- package/dist/{esm-BaH2eg5-.js → esm-Bqu9AE2K.js} +1 -1
- package/dist/{extends-D_hDsj6R.js → extends-9Yl5BEcg.js} +4 -4
- package/dist/{formats-C4wO47tk.js → formats-BV4bOfMI.js} +3 -3
- package/dist/{glide-data-editor-BPkCPs7L.js → glide-data-editor-BDTq6YUb.js} +9 -9
- package/dist/{html-to-image-D5-EpALB.js → html-to-image-C86pQALH.js} +16 -16
- package/dist/{input-OdWHkobi.js → input-AKkGXdyV.js} +6 -6
- package/dist/{label-CC4ytI1X.js → label-E3ZJXHu8.js} +2 -2
- package/dist/{loader-BWLPpjKK.js → loader-YPuQvn1Y.js} +1 -1
- package/dist/main.js +981 -978
- package/dist/{mermaid-BotvIKg9.js → mermaid-QFAR9YgY.js} +5 -5
- package/dist/{process-output-WDZE0cyS.js → process-output-nNw4OpSj.js} +3 -3
- package/dist/{reveal-component-BZpDHGsk.js → reveal-component-B2YCxhmm.js} +10 -10
- package/dist/{spec-X7FwLJni.js → spec-B45_YCNI.js} +4 -4
- package/dist/{strings-J57tzLr3.js → strings-Cq2s9_EQ.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-B2t5sN1q.js → swiper-component-BNa_4kh2.js} +2 -2
- package/dist/{toDate-d8RCRrRd.js → toDate-Do1xRzAo.js} +3 -3
- package/dist/{tooltip-DpcyNkQ2.js → tooltip-Bz3OAwrU.js} +3 -3
- package/dist/{types-ChtMFmZ2.js → types-D8gEGs4R.js} +1 -1
- package/dist/{useAsyncData-PonK__yh.js → useAsyncData-CL3o2p4i.js} +1 -1
- package/dist/{useDateFormatter-QB-3MpYr.js → useDateFormatter-BC6iSz9g.js} +2 -2
- package/dist/{useDeepCompareMemoize-D3NGWke6.js → useDeepCompareMemoize-BPx2MuOK.js} +1 -1
- package/dist/{useIframeCapabilities-C4JTXTIh.js → useIframeCapabilities-C6Ta3EyP.js} +1 -1
- package/dist/{useLifecycle-00mO3OSS.js → useLifecycle-C3Ec71q0.js} +3 -3
- package/dist/{useTheme-DEhDzATN.js → useTheme-ZhT6uIu3.js} +4 -3
- package/dist/{vega-component-DGPUhbDs.js → vega-component-C3AWYGAL.js} +10 -10
- package/dist/{zod-aLSua2NL.js → zod-DXqkaI_w.js} +1 -1
- package/package.json +1 -1
- package/src/components/ai/ai-model-dropdown.tsx +6 -0
- package/src/components/app-config/ai-config.tsx +73 -55
- package/src/components/app-config/data-form.tsx +33 -43
- package/src/components/app-config/is-overridden.tsx +125 -58
- package/src/components/app-config/user-config-form.tsx +178 -227
- package/src/components/chat/chat-display.tsx +5 -0
- package/src/components/chat/chat-panel.tsx +21 -8
- package/src/components/chat/tool-call/tool-call-view.tsx +4 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +26 -5
- package/src/components/data-table/TableTopBar.tsx +4 -4
- package/src/components/data-table/__tests__/columns.test.tsx +20 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +28 -0
- package/src/components/data-table/columns.tsx +16 -1
- package/src/components/data-table/data-table.tsx +3 -3
- package/src/components/data-table/filters/types.ts +1 -0
- package/src/components/data-table/renderers.tsx +5 -1
- package/src/components/datasources/__tests__/install-package-button.test.tsx +11 -0
- package/src/components/datasources/__tests__/utils.test.ts +127 -2
- package/src/components/datasources/datasources.tsx +44 -17
- package/src/components/datasources/install-package-button.tsx +3 -1
- package/src/components/datasources/utils.ts +45 -0
- package/src/components/editor/actions/useNotebookActions.tsx +62 -21
- package/src/components/editor/actions/useSetCodeVisibility.ts +57 -0
- package/src/components/editor/header/__tests__/status.test.tsx +0 -4
- package/src/components/editor/header/status.tsx +2 -3
- package/src/core/cells/__tests__/cells.test.ts +33 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/config/config-schema.ts +6 -1
- package/src/core/edit-app.tsx +14 -0
- package/src/core/hotkeys/hotkeys.ts +20 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
- package/src/core/websocket/types.ts +0 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +0 -14
- package/src/css/app/print.css +8 -0
- package/src/css/md.css +12 -0
- package/src/plugins/impl/DataTablePlugin.tsx +12 -5
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -1
- package/src/plugins/impl/chat/chat-ui.tsx +1 -0
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
- package/dist/assets/__vite-browser-external-eshhtsgZ.js +0 -1
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
513
|
+
searchValue?: string;
|
|
517
514
|
engineName: string;
|
|
518
515
|
database: Database;
|
|
519
516
|
children: React.ReactNode;
|
|
520
|
-
}> = ({
|
|
521
|
-
|
|
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 [
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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,
|
|
661
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
567
|
-
|
|
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.
|
|
26
|
-
//
|
|
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__",
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -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[] = [
|
|
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({
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -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");
|
|
@@ -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",
|
package/src/css/app/print.css
CHANGED
|
@@ -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 {
|