@marimo-team/frontend 0.18.5-dev168 → 0.18.5-dev171

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 (140) hide show
  1. package/dist/assets/{CellStatus-C5QfWAQj.js → CellStatus-ClrYbUob.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-B0tKPs5t.js → ConnectedDataExplorerComponent-KlUs_Sz3.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-BYCO3_zw.js → ErrorBoundary-Drf1manw.js} +1 -1
  4. package/dist/assets/{ImperativeModal-7Thr73Oo.js → ImperativeModal-q6QlC2aZ.js} +1 -1
  5. package/dist/assets/{JsonOutput-NOfbbuUx.js → JsonOutput-CkeISXrs.js} +9 -9
  6. package/dist/assets/{LazyAnyLanguageCodeMirror-DIbHWNF6.js → LazyAnyLanguageCodeMirror-jpEDlD0M.js} +2 -2
  7. package/dist/assets/{MarimoErrorOutput-usFICLUe.js → MarimoErrorOutput-CO8cm8-0.js} +2 -2
  8. package/dist/assets/{RenderHTML-LxV1eecf.js → RenderHTML-D6fh5YLC.js} +1 -1
  9. package/dist/assets/{VisuallyHidden-BLp5kXtE.js → VisuallyHidden-BodIky8L.js} +1 -1
  10. package/dist/assets/{add-cell-with-ai-CEKGThxQ.js → add-cell-with-ai-B3V6Mgb9.js} +12 -12
  11. package/dist/assets/{add-database-form-DQyrphh9.js → add-database-form-C-K88iSC.js} +1 -1
  12. package/dist/assets/{agent-panel-CnTn12AW.js → agent-panel-BvLqBe3U.js} +6 -6
  13. package/dist/assets/{ai-model-dropdown-CxRols0L.js → ai-model-dropdown-D1nEKS8b.js} +1 -1
  14. package/dist/assets/{alert-dialog-B3T1GCoD.js → alert-dialog-k5KxevGr.js} +1 -1
  15. package/dist/assets/any-language-editor-DQu1Tt2N.js +3 -0
  16. package/dist/assets/{app-config-button-DtMP2_j5.js → app-config-button-BD5l-v4I.js} +1 -1
  17. package/dist/assets/{button-C9TEIJJh.js → button-DuYGqRtX.js} +1 -1
  18. package/dist/assets/{cache-panel-C-YLXsti.js → cache-panel-C1So4Zu3.js} +1 -1
  19. package/dist/assets/{capabilities-h9_pyvFv.js → capabilities-BmAOeMOK.js} +1 -1
  20. package/dist/assets/{capitalize-D4JsDDXI.js → capitalize-DQeWKRGx.js} +1 -1
  21. package/dist/assets/{cell-actions-DCZqhhwF.js → cell-actions-CJiaF9Tu.js} +1 -1
  22. package/dist/assets/cell-link-Hwo1QbqQ.js +1 -0
  23. package/dist/assets/{cells-DAR6V-0d.js → cells-rr1jjvJk.js} +33 -33
  24. package/dist/assets/{chat-components-CyY2c7cG.js → chat-components-DXJsXOD8.js} +1 -1
  25. package/dist/assets/{chat-display-BgCCmGdz.js → chat-display-C08N0rx5.js} +1 -1
  26. package/dist/assets/{chat-panel-ByuXM3gc.js → chat-panel-CNL6ANh8.js} +2 -2
  27. package/dist/assets/client-SMzMTiO_.js +4 -0
  28. package/dist/assets/{column-preview-DBqi4KCj.js → column-preview-BzvHnhPG.js} +1 -1
  29. package/dist/assets/command-palette-B_md1fzh.js +1 -0
  30. package/dist/assets/{command-BXWdrTY9.js → command-xU8ykulh.js} +1 -1
  31. package/dist/assets/{common-D10A9aix.js → common-_RAkDTpe.js} +1 -1
  32. package/dist/assets/{config-D3ojpe3F.js → config-cCkBQ_ER.js} +1 -1
  33. package/dist/assets/{copy-Bmkr29w4.js → copy-DRhpWiOq.js} +1 -1
  34. package/dist/assets/{copy-icon-qVgCqhII.js → copy-icon-B69c-352.js} +1 -1
  35. package/dist/assets/{createReducer-CaezzNVH.js → createReducer-DDa-hVe3.js} +1 -1
  36. package/dist/assets/{datasource-CW2RMV2e.js → datasource-DHbNHnua.js} +2 -2
  37. package/dist/assets/{dates-B2_O0-if.js → dates-CdsE1R40.js} +1 -1
  38. package/dist/assets/{dependency-graph-panel-CtlDdK0s.js → dependency-graph-panel-BGp5UzmZ.js} +3 -3
  39. package/dist/assets/{dialog-DIUTtzeB.js → dialog-DUEuLcT2.js} +1 -1
  40. package/dist/assets/{dist-vrNOUnFF.js → dist-DOFFh6Ii.js} +1 -1
  41. package/dist/assets/{documentation-panel-Cup0ghBs.js → documentation-panel-CuPlvIjJ.js} +1 -1
  42. package/dist/assets/{download-D3Uujsn_.js → download-DKRxBkYD.js} +1 -1
  43. package/dist/assets/edit-page-DAFshpgY.js +13 -0
  44. package/dist/assets/{error-banner-D2-5vgE4.js → error-banner-DU5Qb8a8.js} +1 -1
  45. package/dist/assets/{error-panel-DcRvXIwA.js → error-panel-CoYFpNUd.js} +1 -1
  46. package/dist/assets/{es-DCqUUmrq.js → es-YVwBDiDw.js} +1 -1
  47. package/dist/assets/{field-D2_0r7wT.js → field-DDKGFzpC.js} +1 -1
  48. package/dist/assets/{file-explorer-panel-9Bt20tRi.js → file-explorer-panel-BcpntT7Q.js} +1 -1
  49. package/dist/assets/{floating-outline-BTc4L5BF.js → floating-outline-CHprSkP6.js} +1 -1
  50. package/dist/assets/{focus-6-St0ai2.js → focus-D3ygI6Sy.js} +1 -1
  51. package/dist/assets/{form-1WOcPtE8.js → form-B_zRT4QS.js} +2 -2
  52. package/dist/assets/{formats-nh-sPUUZ.js → formats-B9CrBoaO.js} +1 -1
  53. package/dist/assets/{glide-data-editor-EdMWe-iv.js → glide-data-editor-Brckuic5.js} +1 -1
  54. package/dist/assets/{globals-CJb4gcmW.js → globals-D46Vbo5D.js} +1 -1
  55. package/dist/assets/{home-page-Lqy9A89-.js → home-page-DuPzZzUf.js} +2 -2
  56. package/dist/assets/hotkeys-uKX61F1_.js +1 -0
  57. package/dist/assets/{index-CIoUDThG.js → index-CQBtzy-J.js} +9 -9
  58. package/dist/assets/index-G6ss-VDT.css +2 -0
  59. package/dist/assets/{input-DP44ewsS.js → input-CaEtLL8p.js} +1 -1
  60. package/dist/assets/{kiosk-mode-C3lS9kUL.js → kiosk-mode-CTjr_Jn4.js} +1 -1
  61. package/dist/assets/{label-DJo0Eeb3.js → label-qwandMoh.js} +1 -1
  62. package/dist/assets/{layout-F63rlsGg.js → layout-CprHgyfx.js} +4 -4
  63. package/dist/assets/links-Cw9RjHIY.js +1 -0
  64. package/dist/assets/{logs-panel-fnMYUf4-.js → logs-panel-CFM-1lMP.js} +1 -1
  65. package/dist/assets/{maps-DELIOfTw.js → maps-s2pQkyf5.js} +1 -1
  66. package/dist/assets/{markdown-renderer-B9gYLzJX.js → markdown-renderer-DecoaRsV.js} +1 -1
  67. package/dist/assets/{mermaid-BUMgiGGr.js → mermaid-BPkO79lo.js} +1 -1
  68. package/dist/assets/{mode-D4GlAyEq.js → mode-BYulXE3t.js} +1 -1
  69. package/dist/assets/{multi-map-TecGBFLS.js → multi-map-fjX9ImVF.js} +1 -1
  70. package/dist/assets/{name-cell-input-BMtblyo6.js → name-cell-input-BGJDnv5Z.js} +1 -1
  71. package/dist/assets/{numbers-Ckcn6C-H.js → numbers-C9_R_vlY.js} +1 -1
  72. package/dist/assets/outline-panel-C-__7mIh.js +1 -0
  73. package/dist/assets/{packages-panel-DyC7zUxG.js → packages-panel-B4bQCG8Y.js} +1 -1
  74. package/dist/assets/{cell-editor-DJ1tY3mU.js → panel-context-Bee798AZ.js} +13 -13
  75. package/dist/assets/{panels-wYHy1fpG.js → panels-CuLZ4Xjj.js} +1 -1
  76. package/dist/assets/{process-output-D1GR6Ero.js → process-output-2gz8Uri2.js} +1 -1
  77. package/dist/assets/{readonly-python-code-CGnOB_RP.js → readonly-python-code-ihKi3Mrq.js} +1 -1
  78. package/dist/assets/{renderShortcut-CV_tqoEI.js → renderShortcut-D0Pei-OA.js} +1 -1
  79. package/dist/assets/{run-page-BLnJcKtg.js → run-page-Q1ifcEUY.js} +1 -1
  80. package/dist/assets/{runs-BbV5uZz6.js → runs-yuOchwkU.js} +1 -1
  81. package/dist/assets/scratchpad-panel-B75Qsjwz.js +1 -0
  82. package/dist/assets/{secrets-panel-K7EccfRa.js → secrets-panel-CDWmmmBS.js} +1 -1
  83. package/dist/assets/{select-BJ18Dxpd.js → select-D0g5GnIs.js} +1 -1
  84. package/dist/assets/{session-panel-C3rDPyig.js → session-panel-Bn5_JmbT.js} +1 -1
  85. package/dist/assets/{share-0dWXMs9M.js → share-CXQVxivL.js} +1 -1
  86. package/dist/assets/{slides-component-CZ76zal6.js → slides-component-DUIqQih1.js} +1 -1
  87. package/dist/assets/{snippets-panel-s0p4CCoa.js → snippets-panel-CVBBnX9H.js} +1 -1
  88. package/dist/assets/{spec-D9TFI5I_.js → spec-qp_XZeSS.js} +1 -1
  89. package/dist/assets/{state-DwSzcmzL.js → state-BHFBtWym.js} +1 -1
  90. package/dist/assets/{state-CGq6kn1k.js → state-CIznbe1J.js} +1 -1
  91. package/dist/assets/{state-BK5o1KAL.js → state-DNwec0Uj.js} +1 -1
  92. package/dist/assets/{switch-C8yab1pC.js → switch-iJj-D3dz.js} +1 -1
  93. package/dist/assets/{terminal-CA6CqpuO.js → terminal-CRIGvHBN.js} +1 -1
  94. package/dist/assets/{textarea-DBFtOima.js → textarea-DmNrZcLR.js} +1 -1
  95. package/dist/assets/{tooltip-BNPhCMFo.js → tooltip-CrRUCOBw.js} +1 -1
  96. package/dist/assets/tracing-D9h130fg.js +1 -0
  97. package/dist/assets/{tracing-panel-C-o1Db4W.js → tracing-panel-DaDvGMEN.js} +2 -2
  98. package/dist/assets/{type-drzC-SxF.js → type-BdyvjzTI.js} +1 -1
  99. package/dist/assets/{types-Bt3U-XJV.js → types-4-l_7Ws2.js} +1 -1
  100. package/dist/assets/{useAddCell-CNI3pgez.js → useAddCell-BpMe5DB-.js} +1 -1
  101. package/dist/assets/{useBoolean-C_vQizET.js → useBoolean-BDG41CyP.js} +1 -1
  102. package/dist/assets/{useCellActionButton-BbfiHu5s.js → useCellActionButton-Dh4wbVPA.js} +1 -1
  103. package/dist/assets/{useDeleteCell-B_jFiS1Y.js → useDeleteCell-DBzN8QcP.js} +1 -1
  104. package/dist/assets/{useIframeCapabilities-ZISlVepl.js → useIframeCapabilities-CU-WWxnz.js} +1 -1
  105. package/dist/assets/{useInstallPackage-CBvG269f.js → useInstallPackage-RldLPyJs.js} +1 -1
  106. package/dist/assets/{useLifecycle-BX7GmOQ5.js → useLifecycle-CmDXEyIC.js} +1 -1
  107. package/dist/assets/{useNotebookActions-BeM5fet2.js → useNotebookActions-DKyQBNlS.js} +1 -1
  108. package/dist/assets/useRunCells-BGzo-QMk.js +1 -0
  109. package/dist/assets/{useSplitCell-COPg5dJw.js → useSplitCell-CvKAuKQ_.js} +1 -1
  110. package/dist/assets/{useTheme-CuOCKnyR.js → useTheme-DfP1CWaW.js} +1 -1
  111. package/dist/assets/{utilities.esm-B7XtzwiJ.js → utilities.esm-CT3NbLA9.js} +2 -2
  112. package/dist/assets/{utils-Cxqw03y3.js → utils-CJJIceVn.js} +1 -1
  113. package/dist/assets/{vega-component-arPQXQS8.js → vega-component-CLrcy81y.js} +1 -1
  114. package/dist/assets/{write-secret-modal-CIbhcoWF.js → write-secret-modal-CLm48gMe.js} +1 -1
  115. package/dist/index.html +68 -68
  116. package/package.json +1 -1
  117. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  118. package/src/components/editor/chrome/panels/panel-context.tsx +34 -0
  119. package/src/components/editor/chrome/state.ts +30 -15
  120. package/src/components/editor/chrome/types.ts +67 -77
  121. package/src/components/editor/chrome/wrapper/app-chrome.tsx +216 -139
  122. package/src/components/editor/chrome/wrapper/sidebar.tsx +76 -43
  123. package/src/components/scratchpad/scratchpad.tsx +17 -4
  124. package/src/components/ui/reorderable-list.tsx +190 -31
  125. package/src/core/codemirror/cells/extensions.ts +7 -4
  126. package/src/core/hotkeys/__tests__/shortcuts.test.ts +61 -4
  127. package/src/core/hotkeys/shortcuts.ts +34 -2
  128. package/dist/assets/any-language-editor-CTAS1EXW.js +0 -3
  129. package/dist/assets/cell-link-DjST51KF.js +0 -1
  130. package/dist/assets/client-BQowSjqE.js +0 -4
  131. package/dist/assets/command-palette-CjFiCjws.js +0 -1
  132. package/dist/assets/edit-page-fJ4hHm67.js +0 -13
  133. package/dist/assets/hotkeys-D3ICc8RW.js +0 -1
  134. package/dist/assets/index-D-BWugLn.css +0 -2
  135. package/dist/assets/links-ZVVAwqm7.js +0 -1
  136. package/dist/assets/outline-panel-Blq76m9b.js +0 -1
  137. package/dist/assets/scratchpad-panel-CTJDEy90.js +0 -1
  138. package/dist/assets/tracing-VZVj-gEo.js +0 -1
  139. package/dist/assets/useRunCells-oeY0TrS1.js +0 -1
  140. /package/dist/assets/{cell-editor-Iey559K_.css → panel-context-Iey559K_.css} +0 -0
@@ -1,5 +1,10 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import React, { type PropsWithChildren, Suspense, useEffect } from "react";
2
+ import React, {
3
+ type PropsWithChildren,
4
+ Suspense,
5
+ useEffect,
6
+ useMemo,
7
+ } from "react";
3
8
  import {
4
9
  type ImperativePanelHandle,
5
10
  Panel,
@@ -10,9 +15,10 @@ import { Footer } from "./footer";
10
15
  import { Sidebar } from "./sidebar";
11
16
  import "./app-chrome.css";
12
17
  import { TooltipProvider } from "@radix-ui/react-tooltip";
13
- import { useAtomValue } from "jotai";
18
+ import { useAtom, useAtomValue } from "jotai";
14
19
  import { XIcon } from "lucide-react";
15
20
  import { Button } from "@/components/ui/button";
21
+ import { ReorderableList } from "@/components/ui/reorderable-list";
16
22
  import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
17
23
  import { LazyMount } from "@/components/utils/lazy-mount";
18
24
  import { cellErrorCount } from "@/core/cells/cells";
@@ -20,8 +26,9 @@ import { getFeatureFlag } from "@/core/config/feature-flag";
20
26
  import { cn } from "@/utils/cn";
21
27
  import { ErrorBoundary } from "../../boundary/ErrorBoundary";
22
28
  import { ContextAwarePanel } from "../panels/context-aware-panel/context-aware-panel";
23
- import { useChromeActions, useChromeState } from "../state";
24
- import { DEVELOPER_PANEL_TABS } from "../types";
29
+ import { PanelSectionProvider } from "../panels/panel-context";
30
+ import { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
31
+ import { PANEL_MAP, PANELS, type PanelDescriptor } from "../types";
25
32
  import { BackendConnectionStatus } from "./footer-items/backend-status";
26
33
  import { Minimap } from "./minimap";
27
34
  import { PanelsWrapper } from "./panels";
@@ -67,11 +74,67 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
67
74
  setIsSidebarOpen,
68
75
  setIsDeveloperPanelOpen,
69
76
  setSelectedDeveloperPanelTab,
77
+ openApplication,
70
78
  } = useChromeActions();
71
79
  const sidebarRef = React.useRef<ImperativePanelHandle>(null);
72
80
  const developerPanelRef = React.useRef<ImperativePanelHandle>(null);
73
81
  const { aiPanelTab, setAiPanelTab } = useAiPanelTab();
74
82
  const errorCount = useAtomValue(cellErrorCount);
83
+ const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
84
+
85
+ // Convert current developer panel items to PanelDescriptors
86
+ const devPanelItems = useMemo(() => {
87
+ return panelLayout.developerPanel.flatMap((id) => {
88
+ const panel = PANEL_MAP.get(id);
89
+ return panel ? [panel] : [];
90
+ });
91
+ }, [panelLayout.developerPanel]);
92
+
93
+ const handleSetDevPanelItems = (items: PanelDescriptor[]) => {
94
+ setPanelLayout((prev) => ({
95
+ ...prev,
96
+ developerPanel: items.map((item) => item.type),
97
+ }));
98
+ };
99
+
100
+ const handleDevPanelReceive = (item: PanelDescriptor, fromListId: string) => {
101
+ // Remove from the source list
102
+ if (fromListId === "sidebar") {
103
+ setPanelLayout((prev) => ({
104
+ ...prev,
105
+ sidebar: prev.sidebar.filter((id) => id !== item.type),
106
+ }));
107
+
108
+ // If the moved item was selected in sidebar, select the first remaining item
109
+ if (selectedPanel === item.type) {
110
+ const remainingSidebar = panelLayout.sidebar.filter(
111
+ (id) => id !== item.type,
112
+ );
113
+ if (remainingSidebar.length > 0) {
114
+ openApplication(remainingSidebar[0]);
115
+ }
116
+ }
117
+ }
118
+
119
+ // Select the dropped item in developer panel
120
+ setSelectedDeveloperPanelTab(item.type);
121
+ };
122
+
123
+ // Get panels available for developer panel context menu
124
+ // Only show panels that are NOT in the sidebar
125
+ const availableDevPanels = useMemo(() => {
126
+ const sidebarIds = new Set(panelLayout.sidebar);
127
+ return PANELS.filter((p) => {
128
+ if (p.hidden) {
129
+ return false;
130
+ }
131
+ // Exclude panels that are in the sidebar
132
+ if (sidebarIds.has(p.type)) {
133
+ return false;
134
+ }
135
+ return true;
136
+ });
137
+ }, [panelLayout.sidebar]);
75
138
 
76
139
  // sync sidebar
77
140
  useEffect(() => {
@@ -160,60 +223,74 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
160
223
 
161
224
  const helpPaneBody = (
162
225
  <ErrorBoundary>
163
- <div className="flex flex-col h-full flex-1 overflow-hidden mr-[-4px]">
164
- <div className="p-3 border-b flex justify-between items-center">
165
- {selectedPanel === "ai" && agentsEnabled ? (
166
- <Tabs
167
- value={aiPanelTab}
168
- onValueChange={(value) => {
169
- if (value === "chat" || value === "agents") {
170
- setAiPanelTab(value);
171
- }
172
- }}
226
+ <PanelSectionProvider value="sidebar">
227
+ <div className="flex flex-col h-full flex-1 overflow-hidden mr-[-4px]">
228
+ <div className="p-3 border-b flex justify-between items-center">
229
+ {selectedPanel === "ai" && agentsEnabled ? (
230
+ <Tabs
231
+ value={aiPanelTab}
232
+ onValueChange={(value) => {
233
+ if (value === "chat" || value === "agents") {
234
+ setAiPanelTab(value);
235
+ }
236
+ }}
237
+ >
238
+ <TabsList>
239
+ <TabsTrigger
240
+ value="chat"
241
+ className="py-0.5 text-xs uppercase tracking-wide font-bold"
242
+ >
243
+ Chat
244
+ </TabsTrigger>
245
+ <TabsTrigger
246
+ value="agents"
247
+ className="py-0.5 text-xs uppercase tracking-wide font-bold"
248
+ >
249
+ Agents
250
+ </TabsTrigger>
251
+ </TabsList>
252
+ </Tabs>
253
+ ) : (
254
+ <span className="text-sm text-(--slate-11) uppercase tracking-wide font-semibold flex-1">
255
+ {selectedPanel}
256
+ </span>
257
+ )}
258
+ <Button
259
+ data-testid="close-helper-pane"
260
+ className="m-0"
261
+ size="xs"
262
+ variant="text"
263
+ onClick={() => setIsSidebarOpen(false)}
173
264
  >
174
- <TabsList>
175
- <TabsTrigger
176
- value="chat"
177
- className="py-0.5 text-xs uppercase tracking-wide font-bold"
178
- >
179
- Chat
180
- </TabsTrigger>
181
- <TabsTrigger
182
- value="agents"
183
- className="py-0.5 text-xs uppercase tracking-wide font-bold"
184
- >
185
- Agents
186
- </TabsTrigger>
187
- </TabsList>
188
- </Tabs>
189
- ) : (
190
- <span className="text-sm text-(--slate-11) uppercase tracking-wide font-semibold flex-1">
191
- {selectedPanel}
192
- </span>
193
- )}
194
- <Button
195
- data-testid="close-helper-pane"
196
- className="m-0"
197
- size="xs"
198
- variant="text"
199
- onClick={() => setIsSidebarOpen(false)}
200
- >
201
- <XIcon className="w-4 h-4" />
202
- </Button>
265
+ <XIcon className="w-4 h-4" />
266
+ </Button>
267
+ </div>
268
+ <Suspense>
269
+ <TooltipProvider>
270
+ {selectedPanel === "files" && <LazyFileExplorerPanel />}
271
+ {selectedPanel === "variables" && <LazySessionPanel />}
272
+ {selectedPanel === "dependencies" && <LazyDependencyGraphPanel />}
273
+ {selectedPanel === "packages" && <LazyPackagesPanel />}
274
+ {selectedPanel === "outline" && <LazyOutlinePanel />}
275
+ {selectedPanel === "documentation" && <LazyDocumentationPanel />}
276
+ {selectedPanel === "snippets" && <LazySnippetsPanel />}
277
+ {selectedPanel === "ai" && renderAiPanel()}
278
+ {selectedPanel === "errors" && <LazyErrorsPanel />}
279
+ {selectedPanel === "scratchpad" && <LazyScratchpadPanel />}
280
+ {selectedPanel === "tracing" && <LazyTracingPanel />}
281
+ {selectedPanel === "secrets" && <LazySecretsPanel />}
282
+ {selectedPanel === "logs" && <LazyLogsPanel />}
283
+ {selectedPanel === "terminal" && (
284
+ <LazyTerminal
285
+ visible={isSidebarOpen}
286
+ onClose={() => setIsSidebarOpen(false)}
287
+ />
288
+ )}
289
+ {selectedPanel === "cache" && <LazyCachePanel />}
290
+ </TooltipProvider>
291
+ </Suspense>
203
292
  </div>
204
- <Suspense>
205
- <TooltipProvider>
206
- {selectedPanel === "files" && <LazyFileExplorerPanel />}
207
- {selectedPanel === "variables" && <LazySessionPanel />}
208
- {selectedPanel === "dependencies" && <LazyDependencyGraphPanel />}
209
- {selectedPanel === "packages" && <LazyPackagesPanel />}
210
- {selectedPanel === "outline" && <LazyOutlinePanel />}
211
- {selectedPanel === "documentation" && <LazyDocumentationPanel />}
212
- {selectedPanel === "snippets" && <LazySnippetsPanel />}
213
- {selectedPanel === "ai" && renderAiPanel()}
214
- </TooltipProvider>
215
- </Suspense>
216
- </div>
293
+ </PanelSectionProvider>
217
294
  </ErrorBoundary>
218
295
  );
219
296
 
@@ -281,36 +358,47 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
281
358
  <div className="flex flex-col h-full">
282
359
  {/* Panel header with tabs */}
283
360
  <div className="flex items-center justify-between border-b px-2 h-8 bg-background shrink-0">
284
- <Tabs
285
- value={selectedDeveloperPanelTab}
286
- onValueChange={(v) =>
287
- setSelectedDeveloperPanelTab(
288
- v as typeof selectedDeveloperPanelTab,
289
- )
290
- }
291
- >
292
- <TabsList className="bg-transparent p-0 gap-1">
293
- {DEVELOPER_PANEL_TABS.filter((tab) => !tab.hidden).map((tab) => (
294
- <TabsTrigger
295
- key={tab.type}
296
- value={tab.type}
297
- className="text-sm gap-2 px-2 pt-1 pb-0.5 items-center leading-none data-[state=active]:bg-muted"
298
- >
299
- {/* Color the Errors icon red when there are errors,
300
- so users see it when they open the developer panel */}
301
- <tab.Icon
302
- className={cn(
303
- "w-4 h-4",
304
- tab.type === "errors" &&
305
- errorCount > 0 &&
306
- "text-destructive",
307
- )}
308
- />
309
- {tab.label}
310
- </TabsTrigger>
311
- ))}
312
- </TabsList>
313
- </Tabs>
361
+ <ReorderableList<PanelDescriptor>
362
+ value={devPanelItems}
363
+ setValue={handleSetDevPanelItems}
364
+ getKey={(p) => p.type}
365
+ availableItems={availableDevPanels}
366
+ crossListDrag={{
367
+ dragType: "panels",
368
+ listId: "developer-panel",
369
+ onReceive: handleDevPanelReceive,
370
+ }}
371
+ getItemLabel={(panel) => (
372
+ <span className="flex items-center gap-2">
373
+ <panel.Icon className="w-4 h-4 text-muted-foreground" />
374
+ {panel.label}
375
+ </span>
376
+ )}
377
+ ariaLabel="Developer panel tabs"
378
+ className="flex flex-row gap-1"
379
+ minItems={0}
380
+ onAction={(panel) => setSelectedDeveloperPanelTab(panel.type)}
381
+ renderItem={(panel) => (
382
+ <div
383
+ className={cn(
384
+ "text-sm flex gap-2 px-2 pt-1 pb-0.5 items-center leading-none rounded-sm cursor-pointer",
385
+ selectedDeveloperPanelTab === panel.type
386
+ ? "bg-muted"
387
+ : "hover:bg-muted/50",
388
+ )}
389
+ >
390
+ <panel.Icon
391
+ className={cn(
392
+ "w-4 h-4",
393
+ panel.type === "errors" &&
394
+ errorCount > 0 &&
395
+ "text-destructive",
396
+ )}
397
+ />
398
+ {panel.label}
399
+ </div>
400
+ )}
401
+ />
314
402
  <div className="border-l border-border h-4 mx-1" />
315
403
  <BackendConnectionStatus />
316
404
  <div className="flex-1" />
@@ -323,60 +411,49 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
323
411
  </Button>
324
412
  </div>
325
413
  {/* Panel content */}
326
- <div className="flex-1 overflow-hidden">
327
- {selectedDeveloperPanelTab === "errors" && (
328
- <LazyMount isOpen={isDeveloperPanelOpen}>
329
- <Suspense fallback={<div />}>
330
- <LazyErrorsPanel />
331
- </Suspense>
332
- </LazyMount>
333
- )}
334
- {selectedDeveloperPanelTab === "scratchpad" && (
335
- <LazyMount isOpen={isDeveloperPanelOpen}>
336
- <Suspense fallback={<div />}>
414
+ <Suspense fallback={<div />}>
415
+ <PanelSectionProvider value="developer-panel">
416
+ <div className="flex-1 overflow-hidden">
417
+ {selectedDeveloperPanelTab === "files" && (
418
+ <LazyFileExplorerPanel />
419
+ )}
420
+ {selectedDeveloperPanelTab === "variables" && (
421
+ <LazySessionPanel />
422
+ )}
423
+ {selectedDeveloperPanelTab === "dependencies" && (
424
+ <LazyDependencyGraphPanel />
425
+ )}
426
+ {selectedDeveloperPanelTab === "packages" && (
427
+ <LazyPackagesPanel />
428
+ )}
429
+ {selectedDeveloperPanelTab === "outline" && <LazyOutlinePanel />}
430
+ {selectedDeveloperPanelTab === "documentation" && (
431
+ <LazyDocumentationPanel />
432
+ )}
433
+ {selectedDeveloperPanelTab === "snippets" && (
434
+ <LazySnippetsPanel />
435
+ )}
436
+ {selectedDeveloperPanelTab === "ai" && renderAiPanel()}
437
+ {selectedDeveloperPanelTab === "errors" && <LazyErrorsPanel />}
438
+ {selectedDeveloperPanelTab === "scratchpad" && (
337
439
  <LazyScratchpadPanel />
338
- </Suspense>
339
- </LazyMount>
340
- )}
341
- {selectedDeveloperPanelTab === "tracing" && (
342
- <LazyMount isOpen={isDeveloperPanelOpen}>
343
- <Suspense fallback={<div />}>
344
- <LazyTracingPanel />
345
- </Suspense>
346
- </LazyMount>
347
- )}
348
- {selectedDeveloperPanelTab === "secrets" && (
349
- <LazyMount isOpen={isDeveloperPanelOpen}>
350
- <Suspense fallback={<div />}>
351
- <LazySecretsPanel />
352
- </Suspense>
353
- </LazyMount>
354
- )}
355
- {selectedDeveloperPanelTab === "logs" && (
356
- <LazyMount isOpen={isDeveloperPanelOpen}>
357
- <Suspense fallback={<div />}>
358
- <LazyLogsPanel />
359
- </Suspense>
360
- </LazyMount>
361
- )}
362
- {selectedDeveloperPanelTab === "terminal" && (
363
- <LazyMount isOpen={isDeveloperPanelOpen}>
364
- <Suspense fallback={<div />}>
365
- <LazyTerminal
366
- visible={isDeveloperPanelOpen}
367
- onClose={() => setIsDeveloperPanelOpen(false)}
368
- />
369
- </Suspense>
370
- </LazyMount>
371
- )}
372
- {selectedDeveloperPanelTab === "cache" && (
373
- <LazyMount isOpen={isDeveloperPanelOpen}>
374
- <Suspense fallback={<div />}>
375
- <LazyCachePanel />
376
- </Suspense>
377
- </LazyMount>
378
- )}
379
- </div>
440
+ )}
441
+ {selectedDeveloperPanelTab === "tracing" && <LazyTracingPanel />}
442
+ {selectedDeveloperPanelTab === "secrets" && <LazySecretsPanel />}
443
+ {selectedDeveloperPanelTab === "logs" && <LazyLogsPanel />}
444
+ {/* LazyMount needed for Terminal to avoid spurious connection */}
445
+ {selectedDeveloperPanelTab === "terminal" && (
446
+ <LazyMount isOpen={isDeveloperPanelOpen}>
447
+ <LazyTerminal
448
+ visible={isDeveloperPanelOpen}
449
+ onClose={() => setIsDeveloperPanelOpen(false)}
450
+ />
451
+ </LazyMount>
452
+ )}
453
+ {selectedDeveloperPanelTab === "cache" && <LazyCachePanel />}
454
+ </div>
455
+ </PanelSectionProvider>
456
+ </Suspense>
380
457
  </div>
381
458
  </Panel>
382
459
  );
@@ -8,72 +8,105 @@ import { useMemo } from "react";
8
8
  import { ReorderableList } from "@/components/ui/reorderable-list";
9
9
  import { Tooltip } from "@/components/ui/tooltip";
10
10
  import { notebookQueuedOrRunningCountAtom } from "@/core/cells/cells";
11
- import { snippetsEnabledAtom } from "@/core/config/config";
12
11
  import { cn } from "@/utils/cn";
13
12
  import { FeedbackButton } from "../components/feedback-button";
14
- import { sidebarOrderAtom, useChromeActions, useChromeState } from "../state";
13
+ import { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
15
14
  import { PANEL_MAP, PANELS, type PanelDescriptor } from "../types";
16
15
 
17
16
  export const Sidebar: React.FC = () => {
18
- const { selectedPanel } = useChromeState();
19
- const { toggleApplication } = useChromeActions();
20
- const [sidebarOrder, setSidebarOrder] = useAtom(sidebarOrderAtom);
21
- const snippetsEnabled = useAtomValue(snippetsEnabledAtom);
17
+ const { selectedPanel, selectedDeveloperPanelTab } = useChromeState();
18
+ const { toggleApplication, setSelectedDeveloperPanelTab } =
19
+ useChromeActions();
20
+ const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
22
21
 
23
22
  const renderIcon = ({ Icon }: PanelDescriptor, className?: string) => {
24
23
  return <Icon className={cn("h-5 w-5", className)} />;
25
24
  };
26
25
 
27
- // Get all available sidebar panels
28
- // Panels with defaultHidden are only available if explicitly enabled (e.g., snippets)
29
- const availableSidebarPanels = useMemo(
30
- () =>
31
- PANELS.filter((p) => {
32
- if (p.hidden || p.position !== "sidebar") {
33
- return false;
34
- }
35
- // Show defaultHidden panels only if enabled via config
36
- if (p.defaultHidden && p.id === "snippets" && !snippetsEnabled) {
37
- return false;
38
- }
39
- return true;
40
- }),
41
- [snippetsEnabled],
42
- );
26
+ // Get panels available for sidebar context menu
27
+ // Only show panels that are NOT in the developer panel
28
+ const availableSidebarPanels = useMemo(() => {
29
+ const devPanelIds = new Set(panelLayout.developerPanel);
30
+ return PANELS.filter((p) => {
31
+ if (p.hidden) {
32
+ return false;
33
+ }
34
+ // Exclude panels that are in the developer panel
35
+ if (devPanelIds.has(p.type)) {
36
+ return false;
37
+ }
38
+ return true;
39
+ });
40
+ }, [panelLayout.developerPanel]);
41
+
42
+ // Convert current sidebar items to PanelDescriptors
43
+ const sidebarItems = useMemo(() => {
44
+ return panelLayout.sidebar.flatMap((id) => {
45
+ const panel = PANEL_MAP.get(id);
46
+ return panel ? [panel] : [];
47
+ });
48
+ }, [panelLayout.sidebar]);
49
+
50
+ const handleSetSidebarItems = (items: PanelDescriptor[]) => {
51
+ setPanelLayout((prev) => ({
52
+ ...prev,
53
+ sidebar: items.map((item) => item.type),
54
+ }));
55
+ };
56
+
57
+ const handleReceive = (item: PanelDescriptor, fromListId: string) => {
58
+ // Remove from the source list
59
+ if (fromListId === "developer-panel") {
60
+ setPanelLayout((prev) => ({
61
+ ...prev,
62
+ developerPanel: prev.developerPanel.filter((id) => id !== item.type),
63
+ }));
43
64
 
44
- const currentItems = sidebarOrder
45
- .map((id) => PANEL_MAP.get(id))
46
- .filter(Boolean);
65
+ // If the moved item was selected in dev panel, select the first remaining item
66
+ if (selectedDeveloperPanelTab === item.type) {
67
+ const remainingDevPanels = panelLayout.developerPanel.filter(
68
+ (id) => id !== item.type,
69
+ );
70
+ if (remainingDevPanels.length > 0) {
71
+ setSelectedDeveloperPanelTab(remainingDevPanels[0]);
72
+ }
73
+ }
74
+ }
47
75
 
48
- const handleSetValue = (panels: PanelDescriptor[]) => {
49
- setSidebarOrder(panels.map((p) => p.id));
76
+ // Select the dropped item in sidebar
77
+ toggleApplication(item.type);
50
78
  };
51
79
 
52
80
  return (
53
81
  <div className="h-full pt-4 pb-1 px-1 flex flex-col items-start text-muted-foreground text-md select-none no-print text-sm z-50 dark:bg-background print:hidden hide-on-fullscreen">
54
82
  <ReorderableList<PanelDescriptor>
55
- value={currentItems}
56
- setValue={handleSetValue}
83
+ value={sidebarItems}
84
+ setValue={handleSetSidebarItems}
85
+ getKey={(p) => p.type}
57
86
  availableItems={availableSidebarPanels}
87
+ crossListDrag={{
88
+ dragType: "panels",
89
+ listId: "sidebar",
90
+ onReceive: handleReceive,
91
+ }}
58
92
  getItemLabel={(panel) => (
59
- <span className="flex items-center gap-2 [">
93
+ <span className="flex items-center gap-2">
60
94
  {renderIcon(panel, "h-4 w-4 text-muted-foreground")}
61
- {panel.tooltip}
95
+ {panel.label}
62
96
  </span>
63
97
  )}
64
- ariaLabel="Reorderable sidebar panels"
98
+ ariaLabel="Sidebar panels"
65
99
  className="flex flex-col gap-0"
66
- onAction={(panel) => toggleApplication(panel.id)}
67
- renderItem={(panel) => {
68
- return (
69
- <SidebarItem
70
- tooltip={panel.tooltip}
71
- selected={selectedPanel === panel.id}
72
- >
73
- {renderIcon(panel)}
74
- </SidebarItem>
75
- );
76
- }}
100
+ minItems={0}
101
+ onAction={(panel) => toggleApplication(panel.type)}
102
+ renderItem={(panel) => (
103
+ <SidebarItem
104
+ tooltip={panel.tooltip}
105
+ selected={selectedPanel === panel.type}
106
+ >
107
+ {renderIcon(panel)}
108
+ </SidebarItem>
109
+ )}
77
110
  />
78
111
  <FeedbackButton>
79
112
  <SidebarItem tooltip="Send feedback!" selected={false}>
@@ -27,6 +27,10 @@ import { useTheme } from "@/theme/useTheme";
27
27
  import { cn } from "@/utils/cn";
28
28
  import { Functions } from "@/utils/functions";
29
29
  import { CellEditor } from "../editor/cell/code/cell-editor";
30
+ import {
31
+ usePanelOrientation,
32
+ usePanelSection,
33
+ } from "../editor/chrome/panels/panel-context";
30
34
  import { HideInKioskMode } from "../editor/kiosk-mode";
31
35
  import { OutputArea } from "../editor/Output";
32
36
  import { ConsoleOutput } from "../editor/output/console/ConsoleOutput";
@@ -53,6 +57,8 @@ export const ScratchPad: React.FC = () => {
53
57
  const lastFocusedCellId = useLastFocusedCellId();
54
58
  const { createNewCell, updateCellCode } = useCellActions();
55
59
  const { sendRunScratchpad } = useRequestClient();
60
+ const orientation = usePanelOrientation();
61
+ const section = usePanelSection();
56
62
 
57
63
  const cellId = SCRATCH_CELL_ID;
58
64
  const cellRuntime = notebookState.cellRuntime[cellId];
@@ -223,13 +229,15 @@ export const ScratchPad: React.FC = () => {
223
229
  </div>
224
230
  );
225
231
 
232
+ const isVertical = orientation === "vertical";
233
+
226
234
  return (
227
235
  <div
228
236
  className="flex flex-col h-full overflow-hidden"
229
237
  id={HTMLCellId.create(cellId)}
230
238
  >
231
- <PanelGroup direction="horizontal" className="h-full">
232
- {/* Left side: toolbar + editor */}
239
+ <PanelGroup key={section} direction={orientation} className="h-full">
240
+ {/* Editor panel */}
233
241
  <Panel defaultSize={40} minSize={20} maxSize={70}>
234
242
  <div className="h-full flex flex-col overflow-hidden relative">
235
243
  {renderToolbar()}
@@ -257,8 +265,13 @@ export const ScratchPad: React.FC = () => {
257
265
  {renderHistory()}
258
266
  </div>
259
267
  </Panel>
260
- <PanelResizeHandle className="w-1 bg-border hover:bg-primary/50 transition-colors" />
261
- {/* Right side: outputs */}
268
+ <PanelResizeHandle
269
+ className={cn(
270
+ "bg-border hover:bg-primary/50 transition-colors",
271
+ isVertical ? "h-1" : "w-1",
272
+ )}
273
+ />
274
+ {/* Output panel */}
262
275
  <Panel defaultSize={60} minSize={20}>
263
276
  <div className="h-full flex flex-col divide-y overflow-hidden">
264
277
  <div className="flex-1 overflow-auto">