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

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 (93) hide show
  1. package/dist/assets/{CellStatus-C5QfWAQj.js → CellStatus-DlwC0A_k.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-B0tKPs5t.js → ConnectedDataExplorerComponent-C3aK452q.js} +1 -1
  3. package/dist/assets/{JsonOutput-NOfbbuUx.js → JsonOutput-C7Jn2Hoy.js} +9 -9
  4. package/dist/assets/{MarimoErrorOutput-usFICLUe.js → MarimoErrorOutput-Dtk0CQJu.js} +2 -2
  5. package/dist/assets/{RenderHTML-LxV1eecf.js → RenderHTML-DfrA7ZTv.js} +1 -1
  6. package/dist/assets/{add-cell-with-ai-CEKGThxQ.js → add-cell-with-ai-BYCrNUOQ.js} +12 -12
  7. package/dist/assets/{add-database-form-DQyrphh9.js → add-database-form-D-BVTDCT.js} +1 -1
  8. package/dist/assets/{agent-panel-CnTn12AW.js → agent-panel-CNIKszsQ.js} +6 -6
  9. package/dist/assets/{ai-model-dropdown-CxRols0L.js → ai-model-dropdown-CAhqhkRJ.js} +1 -1
  10. package/dist/assets/{app-config-button-DtMP2_j5.js → app-config-button-BhKhGiaG.js} +1 -1
  11. package/dist/assets/{cell-actions-DCZqhhwF.js → cell-actions-C8VD1BFf.js} +1 -1
  12. package/dist/assets/{cell-editor-DJ1tY3mU.js → cell-editor-bDu_ShRz.js} +7 -7
  13. package/dist/assets/cell-link-NwzNlJhp.js +1 -0
  14. package/dist/assets/{cells-DAR6V-0d.js → cells-xPjoZY0S.js} +46 -46
  15. package/dist/assets/{chat-components-CyY2c7cG.js → chat-components-CQFXyizy.js} +1 -1
  16. package/dist/assets/{chat-display-BgCCmGdz.js → chat-display-DqUXDG-o.js} +1 -1
  17. package/dist/assets/{chat-panel-ByuXM3gc.js → chat-panel-8iT2Wjh6.js} +2 -2
  18. package/dist/assets/client-Cbyg6uhC.js +4 -0
  19. package/dist/assets/{column-preview-DBqi4KCj.js → column-preview-BuNifqxS.js} +1 -1
  20. package/dist/assets/{command-BXWdrTY9.js → command-DIcQwElH.js} +1 -1
  21. package/dist/assets/command-palette-D_boTGwg.js +1 -0
  22. package/dist/assets/{common-D10A9aix.js → common-C6NCeVE8.js} +1 -1
  23. package/dist/assets/{config-D3ojpe3F.js → config-DWMmQmTt.js} +1 -1
  24. package/dist/assets/{datasource-CW2RMV2e.js → datasource-CyWtxjKq.js} +2 -2
  25. package/dist/assets/{dependency-graph-panel-CtlDdK0s.js → dependency-graph-panel-C01bMyQK.js} +3 -3
  26. package/dist/assets/{documentation-panel-Cup0ghBs.js → documentation-panel-BPUF-dp2.js} +1 -1
  27. package/dist/assets/{download-D3Uujsn_.js → download-B9UlWvRO.js} +1 -1
  28. package/dist/assets/edit-page-DFkV0LWx.js +13 -0
  29. package/dist/assets/{error-panel-DcRvXIwA.js → error-panel-ClGt4y8j.js} +1 -1
  30. package/dist/assets/{es-DCqUUmrq.js → es-1yDP0Xdp.js} +1 -1
  31. package/dist/assets/{file-explorer-panel-9Bt20tRi.js → file-explorer-panel-3SHWDl17.js} +1 -1
  32. package/dist/assets/{floating-outline-BTc4L5BF.js → floating-outline-C5wKHGsD.js} +1 -1
  33. package/dist/assets/{focus-6-St0ai2.js → focus-DPgrnlZ1.js} +1 -1
  34. package/dist/assets/{form-1WOcPtE8.js → form-B2Db87Zc.js} +2 -2
  35. package/dist/assets/{glide-data-editor-EdMWe-iv.js → glide-data-editor-CwVQm3Mn.js} +1 -1
  36. package/dist/assets/{globals-CJb4gcmW.js → globals-CC2GcyGk.js} +1 -1
  37. package/dist/assets/{home-page-Lqy9A89-.js → home-page-BdK-6xZH.js} +2 -2
  38. package/dist/assets/{index-CIoUDThG.js → index-CEHA_rtG.js} +9 -9
  39. package/dist/assets/index-G6ss-VDT.css +2 -0
  40. package/dist/assets/{kiosk-mode-C3lS9kUL.js → kiosk-mode-CsuyIDk3.js} +1 -1
  41. package/dist/assets/{layout-F63rlsGg.js → layout-DlHn_emH.js} +3 -3
  42. package/dist/assets/links-DR-r1Edb.js +1 -0
  43. package/dist/assets/{logs-panel-fnMYUf4-.js → logs-panel-BIW4BIAP.js} +1 -1
  44. package/dist/assets/{markdown-renderer-B9gYLzJX.js → markdown-renderer-B0tsGAgw.js} +1 -1
  45. package/dist/assets/{mermaid-BUMgiGGr.js → mermaid-BcA-uOhi.js} +1 -1
  46. package/dist/assets/{mode-D4GlAyEq.js → mode-Ii-fBazZ.js} +1 -1
  47. package/dist/assets/{name-cell-input-BMtblyo6.js → name-cell-input-03mNaWsC.js} +1 -1
  48. package/dist/assets/outline-panel-D2OKWKZx.js +1 -0
  49. package/dist/assets/{packages-panel-DyC7zUxG.js → packages-panel-CzdZ8dRE.js} +1 -1
  50. package/dist/assets/{panels-wYHy1fpG.js → panels-DTuGqWrW.js} +1 -1
  51. package/dist/assets/{process-output-D1GR6Ero.js → process-output-Dsqg6AQn.js} +1 -1
  52. package/dist/assets/{readonly-python-code-CGnOB_RP.js → readonly-python-code-CEN7uCAn.js} +1 -1
  53. package/dist/assets/{renderShortcut-CV_tqoEI.js → renderShortcut-DolwKyOI.js} +1 -1
  54. package/dist/assets/{run-page-BLnJcKtg.js → run-page-Bv4-nptx.js} +1 -1
  55. package/dist/assets/{scratchpad-panel-CTJDEy90.js → scratchpad-panel-CwWN2smX.js} +1 -1
  56. package/dist/assets/{session-panel-C3rDPyig.js → session-panel-C35Uxsn7.js} +1 -1
  57. package/dist/assets/{snippets-panel-s0p4CCoa.js → snippets-panel-VLyEWSgW.js} +1 -1
  58. package/dist/assets/{state-BK5o1KAL.js → state-CLVMeQ5K.js} +1 -1
  59. package/dist/assets/{state-DwSzcmzL.js → state-DfW1MJsR.js} +1 -1
  60. package/dist/assets/{switch-C8yab1pC.js → switch-DXLifdeN.js} +1 -1
  61. package/dist/assets/{terminal-CA6CqpuO.js → terminal-O_0qSqHX.js} +1 -1
  62. package/dist/assets/{textarea-DBFtOima.js → textarea-BibopRh1.js} +1 -1
  63. package/dist/assets/tracing-kd2E6r4-.js +1 -0
  64. package/dist/assets/{tracing-panel-C-o1Db4W.js → tracing-panel-CingJf_V.js} +2 -2
  65. package/dist/assets/{types-Bt3U-XJV.js → types-B_DyfI3j.js} +1 -1
  66. package/dist/assets/{useAddCell-CNI3pgez.js → useAddCell-CuNdAB3f.js} +1 -1
  67. package/dist/assets/{useBoolean-C_vQizET.js → useBoolean-DWRpatKF.js} +1 -1
  68. package/dist/assets/{useCellActionButton-BbfiHu5s.js → useCellActionButton-CLGSFvWy.js} +1 -1
  69. package/dist/assets/{useDeleteCell-B_jFiS1Y.js → useDeleteCell-DAgP4oYp.js} +1 -1
  70. package/dist/assets/{useNotebookActions-BeM5fet2.js → useNotebookActions-CLAi2J0h.js} +1 -1
  71. package/dist/assets/useRunCells-BHhExio0.js +1 -0
  72. package/dist/assets/{useSplitCell-COPg5dJw.js → useSplitCell-CF_E2d8x.js} +1 -1
  73. package/dist/assets/{useTheme-CuOCKnyR.js → useTheme-CrojuouA.js} +1 -1
  74. package/dist/assets/{utilities.esm-B7XtzwiJ.js → utilities.esm-lV-KjYEM.js} +2 -2
  75. package/dist/assets/{utils-Cxqw03y3.js → utils-B_xHF0y2.js} +1 -1
  76. package/dist/assets/{vega-component-arPQXQS8.js → vega-component-DTFVCfi1.js} +1 -1
  77. package/dist/index.html +37 -37
  78. package/package.json +1 -1
  79. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  80. package/src/components/editor/chrome/state.ts +30 -15
  81. package/src/components/editor/chrome/types.ts +67 -77
  82. package/src/components/editor/chrome/wrapper/app-chrome.tsx +148 -84
  83. package/src/components/editor/chrome/wrapper/sidebar.tsx +76 -43
  84. package/src/components/ui/reorderable-list.tsx +190 -31
  85. package/dist/assets/cell-link-DjST51KF.js +0 -1
  86. package/dist/assets/client-BQowSjqE.js +0 -4
  87. package/dist/assets/command-palette-CjFiCjws.js +0 -1
  88. package/dist/assets/edit-page-fJ4hHm67.js +0 -13
  89. package/dist/assets/index-D-BWugLn.css +0 -2
  90. package/dist/assets/links-ZVVAwqm7.js +0 -1
  91. package/dist/assets/outline-panel-Blq76m9b.js +0 -1
  92. package/dist/assets/tracing-VZVj-gEo.js +0 -1
  93. package/dist/assets/useRunCells-oeY0TrS1.js +0 -1
@@ -21,7 +21,11 @@ import {
21
21
  import { getFeatureFlag } from "@/core/config/feature-flag";
22
22
  import { isWasm } from "@/core/wasm/utils";
23
23
 
24
+ /**
25
+ * Unified panel ID for all panels in sidebar and developer panel
26
+ */
24
27
  export type PanelType =
28
+ // Sidebar defaults
25
29
  | "files"
26
30
  | "variables"
27
31
  | "outline"
@@ -30,160 +34,146 @@ export type PanelType =
30
34
  | "documentation"
31
35
  | "snippets"
32
36
  | "ai"
37
+ // Developer panel defaults
38
+ | "errors"
39
+ | "scratchpad"
40
+ | "tracing"
41
+ | "secrets"
42
+ | "logs"
43
+ | "terminal"
33
44
  | "cache";
34
45
 
46
+ export type PanelSection = "sidebar" | "developer-panel";
47
+
35
48
  export interface PanelDescriptor {
36
- id: PanelType;
49
+ type: PanelType;
37
50
  Icon: LucideIcon;
51
+ /** Short label for developer panel tabs */
52
+ label: string;
53
+ /** Descriptive tooltip for sidebar icons */
54
+ tooltip: string;
38
55
  /** If true, the panel is completely unavailable */
39
56
  hidden?: boolean;
40
- /** If true, the panel is available but not shown by default */
41
- defaultHidden?: boolean;
42
- tooltip: string;
43
- position: "sidebar" | "footer";
57
+ /** Which section this panel belongs to by default */
58
+ defaultSection: PanelSection;
44
59
  }
45
60
 
46
- /* Panels are ordered in roughly decreasing order of importance as well as
47
- * logically grouped.
48
- *
49
- * 1. Must-have panels first.
50
- * 2. Panels that can add cells to the editor.
51
- * 3. Nice-to-have observability panels.
61
+ /**
62
+ * All panels in the application.
63
+ * Panels can be in either sidebar or developer panel, configurable by user.
52
64
  */
53
65
  export const PANELS: PanelDescriptor[] = [
54
- // 1. Must-have panels.
55
- //
56
- // The files panel is at the top to orient
57
- // users within their filesystem and give
58
- // them a quick glance at their project structure,
59
- // without having to leave their editor.
66
+ // Sidebar defaults
60
67
  {
61
- id: "files",
68
+ type: "files",
62
69
  Icon: FolderTreeIcon,
70
+ label: "Files",
63
71
  tooltip: "View files",
64
- position: "sidebar",
72
+ defaultSection: "sidebar",
65
73
  },
66
- // Because notebooks uniquely have data in RAM,
67
- // it's important to give humans visibility into
68
- // what that data is.
69
74
  {
70
- id: "variables",
75
+ type: "variables",
71
76
  Icon: VariableIcon,
77
+ label: "Variables",
72
78
  tooltip: "Explore variables and data sources",
73
- position: "sidebar",
79
+ defaultSection: "sidebar",
74
80
  },
75
- // Every notebook has a package environment that must
76
- // be managed.
77
81
  {
78
- id: "packages",
82
+ type: "packages",
79
83
  Icon: BoxIcon,
84
+ label: "Packages",
80
85
  tooltip: "Manage packages",
81
- position: "sidebar",
86
+ defaultSection: "sidebar",
82
87
  },
83
- // 2. "AI" panel.
84
- //
85
- // The AI panel holds both agents and in-editor chat.
86
88
  {
87
- id: "ai",
89
+ type: "ai",
88
90
  Icon: BotIcon,
91
+ label: "AI",
89
92
  tooltip: "Chat & Agents",
90
- position: "sidebar",
93
+ defaultSection: "sidebar",
91
94
  },
92
95
  {
93
- id: "snippets",
96
+ type: "snippets",
94
97
  Icon: SquareDashedBottomCodeIcon,
98
+ label: "Snippets",
95
99
  tooltip: "Snippets",
96
- position: "sidebar",
97
- defaultHidden: true,
100
+ defaultSection: "developer-panel",
98
101
  },
99
- // 3. Nice-to-have observability panels.
100
- //
101
- // Utility panels that provide observability
102
- // into the state or structure of the notebook. These
103
- // observability panels are less crucial than variables
104
- // or datasets, so they are positioned at the end of the
105
- // sidebar.
106
102
  {
107
- id: "outline",
103
+ type: "outline",
108
104
  Icon: ScrollTextIcon,
105
+ label: "Outline",
109
106
  tooltip: "View outline",
110
- position: "sidebar",
107
+ defaultSection: "sidebar",
111
108
  },
112
109
  {
113
- id: "documentation",
110
+ type: "documentation",
114
111
  Icon: TextSearchIcon,
112
+ label: "Docs",
115
113
  tooltip: "View live docs",
116
- position: "sidebar",
114
+ defaultSection: "sidebar",
117
115
  },
118
116
  {
119
- // TODO(akshayka): Consider making dependencies
120
- // default off; the minimap is a more effective
121
- // overview.
122
- id: "dependencies",
117
+ type: "dependencies",
123
118
  Icon: NetworkIcon,
119
+ label: "Dependencies",
124
120
  tooltip: "Explore dependencies",
125
- position: "sidebar",
121
+ defaultSection: "sidebar",
126
122
  },
127
- ];
128
-
129
- export const PANEL_MAP = new Map<PanelType, PanelDescriptor>(
130
- PANELS.map((p) => [p.id, p]),
131
- );
132
-
133
- export type DeveloperPanelTabType =
134
- | "errors"
135
- | "scratchpad"
136
- | "tracing"
137
- | "secrets"
138
- | "logs"
139
- | "terminal"
140
- | "cache";
141
-
142
- export interface DeveloperPanelTabDescriptor {
143
- type: DeveloperPanelTabType;
144
- Icon: LucideIcon;
145
- label: string;
146
- hidden?: boolean;
147
- }
148
-
149
- export const DEVELOPER_PANEL_TABS: DeveloperPanelTabDescriptor[] = [
123
+ // Developer panel defaults
150
124
  {
151
125
  type: "errors",
152
126
  Icon: XCircleIcon,
153
127
  label: "Errors",
128
+ tooltip: "View errors",
129
+ defaultSection: "developer-panel",
154
130
  },
155
131
  {
156
132
  type: "scratchpad",
157
133
  Icon: NotebookPenIcon,
158
134
  label: "Scratchpad",
135
+ tooltip: "Scratchpad",
136
+ defaultSection: "developer-panel",
159
137
  },
160
138
  {
161
139
  type: "tracing",
162
140
  Icon: ActivityIcon,
163
141
  label: "Tracing",
142
+ tooltip: "View tracing",
143
+ defaultSection: "developer-panel",
164
144
  },
165
145
  {
166
146
  type: "secrets",
167
147
  Icon: KeyRoundIcon,
168
148
  label: "Secrets",
149
+ tooltip: "Manage secrets",
150
+ defaultSection: "developer-panel",
169
151
  hidden: isWasm(),
170
152
  },
171
153
  {
172
154
  type: "logs",
173
155
  Icon: FileTextIcon,
174
156
  label: "Logs",
157
+ tooltip: "View logs",
158
+ defaultSection: "developer-panel",
175
159
  },
176
160
  {
177
161
  type: "terminal",
178
162
  Icon: TerminalSquareIcon,
179
163
  label: "Terminal",
164
+ tooltip: "Terminal",
165
+ defaultSection: "developer-panel",
180
166
  },
181
- // TODO(akshayka): The cache panel should not be default shown,
182
- // even when it's out of feature flag. (User config to turn it on.)
183
167
  {
184
168
  type: "cache",
185
169
  Icon: DatabaseZapIcon,
186
170
  label: "Cache",
171
+ tooltip: "View cache",
172
+ defaultSection: "developer-panel",
187
173
  hidden: !getFeatureFlag("cache_panel"),
188
174
  },
189
175
  ];
176
+
177
+ export const PANEL_MAP = new Map<PanelType, PanelDescriptor>(
178
+ PANELS.map((p) => [p.type, p]),
179
+ );
@@ -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,8 @@ 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 { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
30
+ import { PANEL_MAP, PANELS, type PanelDescriptor } from "../types";
25
31
  import { BackendConnectionStatus } from "./footer-items/backend-status";
26
32
  import { Minimap } from "./minimap";
27
33
  import { PanelsWrapper } from "./panels";
@@ -67,11 +73,67 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
67
73
  setIsSidebarOpen,
68
74
  setIsDeveloperPanelOpen,
69
75
  setSelectedDeveloperPanelTab,
76
+ openApplication,
70
77
  } = useChromeActions();
71
78
  const sidebarRef = React.useRef<ImperativePanelHandle>(null);
72
79
  const developerPanelRef = React.useRef<ImperativePanelHandle>(null);
73
80
  const { aiPanelTab, setAiPanelTab } = useAiPanelTab();
74
81
  const errorCount = useAtomValue(cellErrorCount);
82
+ const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
83
+
84
+ // Convert current developer panel items to PanelDescriptors
85
+ const devPanelItems = useMemo(() => {
86
+ return panelLayout.developerPanel.flatMap((id) => {
87
+ const panel = PANEL_MAP.get(id);
88
+ return panel ? [panel] : [];
89
+ });
90
+ }, [panelLayout.developerPanel]);
91
+
92
+ const handleSetDevPanelItems = (items: PanelDescriptor[]) => {
93
+ setPanelLayout((prev) => ({
94
+ ...prev,
95
+ developerPanel: items.map((item) => item.type),
96
+ }));
97
+ };
98
+
99
+ const handleDevPanelReceive = (item: PanelDescriptor, fromListId: string) => {
100
+ // Remove from the source list
101
+ if (fromListId === "sidebar") {
102
+ setPanelLayout((prev) => ({
103
+ ...prev,
104
+ sidebar: prev.sidebar.filter((id) => id !== item.type),
105
+ }));
106
+
107
+ // If the moved item was selected in sidebar, select the first remaining item
108
+ if (selectedPanel === item.type) {
109
+ const remainingSidebar = panelLayout.sidebar.filter(
110
+ (id) => id !== item.type,
111
+ );
112
+ if (remainingSidebar.length > 0) {
113
+ openApplication(remainingSidebar[0]);
114
+ }
115
+ }
116
+ }
117
+
118
+ // Select the dropped item in developer panel
119
+ setSelectedDeveloperPanelTab(item.type);
120
+ };
121
+
122
+ // Get panels available for developer panel context menu
123
+ // Only show panels that are NOT in the sidebar
124
+ const availableDevPanels = useMemo(() => {
125
+ const sidebarIds = new Set(panelLayout.sidebar);
126
+ return PANELS.filter((p) => {
127
+ if (p.hidden) {
128
+ return false;
129
+ }
130
+ // Exclude panels that are in the sidebar
131
+ if (sidebarIds.has(p.type)) {
132
+ return false;
133
+ }
134
+ return true;
135
+ });
136
+ }, [panelLayout.sidebar]);
75
137
 
76
138
  // sync sidebar
77
139
  useEffect(() => {
@@ -211,6 +273,18 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
211
273
  {selectedPanel === "documentation" && <LazyDocumentationPanel />}
212
274
  {selectedPanel === "snippets" && <LazySnippetsPanel />}
213
275
  {selectedPanel === "ai" && renderAiPanel()}
276
+ {selectedPanel === "errors" && <LazyErrorsPanel />}
277
+ {selectedPanel === "scratchpad" && <LazyScratchpadPanel />}
278
+ {selectedPanel === "tracing" && <LazyTracingPanel />}
279
+ {selectedPanel === "secrets" && <LazySecretsPanel />}
280
+ {selectedPanel === "logs" && <LazyLogsPanel />}
281
+ {selectedPanel === "terminal" && (
282
+ <LazyTerminal
283
+ visible={isSidebarOpen}
284
+ onClose={() => setIsSidebarOpen(false)}
285
+ />
286
+ )}
287
+ {selectedPanel === "cache" && <LazyCachePanel />}
214
288
  </TooltipProvider>
215
289
  </Suspense>
216
290
  </div>
@@ -281,36 +355,47 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
281
355
  <div className="flex flex-col h-full">
282
356
  {/* Panel header with tabs */}
283
357
  <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>
358
+ <ReorderableList<PanelDescriptor>
359
+ value={devPanelItems}
360
+ setValue={handleSetDevPanelItems}
361
+ getKey={(p) => p.type}
362
+ availableItems={availableDevPanels}
363
+ crossListDrag={{
364
+ dragType: "panels",
365
+ listId: "developer-panel",
366
+ onReceive: handleDevPanelReceive,
367
+ }}
368
+ getItemLabel={(panel) => (
369
+ <span className="flex items-center gap-2">
370
+ <panel.Icon className="w-4 h-4 text-muted-foreground" />
371
+ {panel.label}
372
+ </span>
373
+ )}
374
+ ariaLabel="Developer panel tabs"
375
+ className="flex flex-row gap-1"
376
+ minItems={0}
377
+ onAction={(panel) => setSelectedDeveloperPanelTab(panel.type)}
378
+ renderItem={(panel) => (
379
+ <div
380
+ className={cn(
381
+ "text-sm flex gap-2 px-2 pt-1 pb-0.5 items-center leading-none rounded-sm cursor-pointer",
382
+ selectedDeveloperPanelTab === panel.type
383
+ ? "bg-muted"
384
+ : "hover:bg-muted/50",
385
+ )}
386
+ >
387
+ <panel.Icon
388
+ className={cn(
389
+ "w-4 h-4",
390
+ panel.type === "errors" &&
391
+ errorCount > 0 &&
392
+ "text-destructive",
393
+ )}
394
+ />
395
+ {panel.label}
396
+ </div>
397
+ )}
398
+ />
314
399
  <div className="border-l border-border h-4 mx-1" />
315
400
  <BackendConnectionStatus />
316
401
  <div className="flex-1" />
@@ -323,60 +408,39 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
323
408
  </Button>
324
409
  </div>
325
410
  {/* 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 />}>
337
- <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 />}>
411
+ <Suspense fallback={<div />}>
412
+ <div className="flex-1 overflow-hidden">
413
+ {selectedDeveloperPanelTab === "files" && <LazyFileExplorerPanel />}
414
+ {selectedDeveloperPanelTab === "variables" && <LazySessionPanel />}
415
+ {selectedDeveloperPanelTab === "dependencies" && (
416
+ <LazyDependencyGraphPanel />
417
+ )}
418
+ {selectedDeveloperPanelTab === "packages" && <LazyPackagesPanel />}
419
+ {selectedDeveloperPanelTab === "outline" && <LazyOutlinePanel />}
420
+ {selectedDeveloperPanelTab === "documentation" && (
421
+ <LazyDocumentationPanel />
422
+ )}
423
+ {selectedDeveloperPanelTab === "snippets" && <LazySnippetsPanel />}
424
+ {selectedDeveloperPanelTab === "ai" && renderAiPanel()}
425
+ {selectedDeveloperPanelTab === "errors" && <LazyErrorsPanel />}
426
+ {selectedDeveloperPanelTab === "scratchpad" && (
427
+ <LazyScratchpadPanel />
428
+ )}
429
+ {selectedDeveloperPanelTab === "tracing" && <LazyTracingPanel />}
430
+ {selectedDeveloperPanelTab === "secrets" && <LazySecretsPanel />}
431
+ {selectedDeveloperPanelTab === "logs" && <LazyLogsPanel />}
432
+ {/* LazyMount needed for Terminal to avoid spurious connection */}
433
+ {selectedDeveloperPanelTab === "terminal" && (
434
+ <LazyMount isOpen={isDeveloperPanelOpen}>
365
435
  <LazyTerminal
366
436
  visible={isDeveloperPanelOpen}
367
437
  onClose={() => setIsDeveloperPanelOpen(false)}
368
438
  />
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>
439
+ </LazyMount>
440
+ )}
441
+ {selectedDeveloperPanelTab === "cache" && <LazyCachePanel />}
442
+ </div>
443
+ </Suspense>
380
444
  </div>
381
445
  </Panel>
382
446
  );
@@ -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}>