@marimo-team/islands 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.
- package/dist/main.js +91 -39
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/editor/actions/useNotebookActions.tsx +1 -1
- package/src/components/editor/chrome/state.ts +30 -15
- package/src/components/editor/chrome/types.ts +67 -77
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +148 -84
- package/src/components/editor/chrome/wrapper/sidebar.tsx +76 -43
- package/src/components/ui/reorderable-list.tsx +190 -31
package/package.json
CHANGED
|
@@ -269,7 +269,7 @@ export function useNotebookActions() {
|
|
|
269
269
|
label: "Helper panel",
|
|
270
270
|
redundant: true,
|
|
271
271
|
handle: NOOP_HANDLER,
|
|
272
|
-
dropdown: PANELS.flatMap(({ id, Icon, hidden }) => {
|
|
272
|
+
dropdown: PANELS.flatMap(({ type: id, Icon, hidden }) => {
|
|
273
273
|
if (hidden) {
|
|
274
274
|
return [];
|
|
275
275
|
}
|
|
@@ -6,16 +6,41 @@ import { z } from "zod";
|
|
|
6
6
|
import { createReducerAndAtoms } from "@/utils/createReducer";
|
|
7
7
|
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
8
8
|
import { ZodLocalStorage } from "@/utils/storage/typed";
|
|
9
|
-
import type {
|
|
9
|
+
import type { PanelType } from "./types";
|
|
10
10
|
import { PANELS } from "./types";
|
|
11
11
|
|
|
12
12
|
export interface ChromeState {
|
|
13
13
|
selectedPanel: PanelType | undefined;
|
|
14
14
|
isSidebarOpen: boolean;
|
|
15
15
|
isDeveloperPanelOpen: boolean;
|
|
16
|
-
selectedDeveloperPanelTab:
|
|
16
|
+
selectedDeveloperPanelTab: PanelType;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Layout configuration for panels in sidebar and developer panel.
|
|
21
|
+
* Each array contains the ordered list of visible panel IDs for that section.
|
|
22
|
+
*/
|
|
23
|
+
export interface PanelLayout {
|
|
24
|
+
sidebar: PanelType[];
|
|
25
|
+
developerPanel: PanelType[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_PANEL_LAYOUT: PanelLayout = {
|
|
29
|
+
sidebar: PANELS.filter(
|
|
30
|
+
(p) => !p.hidden && p.defaultSection === "sidebar",
|
|
31
|
+
).map((p) => p.type),
|
|
32
|
+
developerPanel: PANELS.filter(
|
|
33
|
+
(p) => !p.hidden && p.defaultSection === "developer-panel",
|
|
34
|
+
).map((p) => p.type),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const panelLayoutAtom = atomWithStorage<PanelLayout>(
|
|
38
|
+
"marimo:panel-layout",
|
|
39
|
+
DEFAULT_PANEL_LAYOUT,
|
|
40
|
+
jotaiJsonStorage,
|
|
41
|
+
{ getOnInit: true },
|
|
42
|
+
);
|
|
43
|
+
|
|
19
44
|
const KEY = "marimo:sidebar";
|
|
20
45
|
const storage = new ZodLocalStorage<ChromeState>(
|
|
21
46
|
z.object({
|
|
@@ -29,7 +54,7 @@ const storage = new ZodLocalStorage<ChromeState>(
|
|
|
29
54
|
.string()
|
|
30
55
|
.optional()
|
|
31
56
|
.default("terminal")
|
|
32
|
-
.transform((v) => v as
|
|
57
|
+
.transform((v) => v as PanelType),
|
|
33
58
|
}),
|
|
34
59
|
initialState,
|
|
35
60
|
);
|
|
@@ -81,11 +106,11 @@ const {
|
|
|
81
106
|
...state,
|
|
82
107
|
isDeveloperPanelOpen: isOpen,
|
|
83
108
|
}),
|
|
84
|
-
setSelectedDeveloperPanelTab: (state, tab:
|
|
109
|
+
setSelectedDeveloperPanelTab: (state, tab: PanelType) => ({
|
|
85
110
|
...state,
|
|
86
111
|
selectedDeveloperPanelTab: tab,
|
|
87
112
|
}),
|
|
88
|
-
openDeveloperPanelTab: (state, tab:
|
|
113
|
+
openDeveloperPanelTab: (state, tab: PanelType) => ({
|
|
89
114
|
...state,
|
|
90
115
|
isDeveloperPanelOpen: true,
|
|
91
116
|
selectedDeveloperPanelTab: tab,
|
|
@@ -119,13 +144,3 @@ export const exportedForTesting = {
|
|
|
119
144
|
createActions,
|
|
120
145
|
initialState,
|
|
121
146
|
};
|
|
122
|
-
|
|
123
|
-
// TODO: probably merge this with the chrome state
|
|
124
|
-
export const sidebarOrderAtom = atomWithStorage<PanelType[]>(
|
|
125
|
-
"marimo:sidebar-order",
|
|
126
|
-
PANELS.filter(
|
|
127
|
-
(p) => !p.hidden && !p.defaultHidden && p.position === "sidebar",
|
|
128
|
-
).map((p) => p.id),
|
|
129
|
-
jotaiJsonStorage,
|
|
130
|
-
{ getOnInit: true },
|
|
131
|
-
);
|
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
41
|
-
|
|
42
|
-
tooltip: string;
|
|
43
|
-
position: "sidebar" | "footer";
|
|
57
|
+
/** Which section this panel belongs to by default */
|
|
58
|
+
defaultSection: PanelSection;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
68
|
+
type: "files",
|
|
62
69
|
Icon: FolderTreeIcon,
|
|
70
|
+
label: "Files",
|
|
63
71
|
tooltip: "View files",
|
|
64
|
-
|
|
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
|
-
|
|
75
|
+
type: "variables",
|
|
71
76
|
Icon: VariableIcon,
|
|
77
|
+
label: "Variables",
|
|
72
78
|
tooltip: "Explore variables and data sources",
|
|
73
|
-
|
|
79
|
+
defaultSection: "sidebar",
|
|
74
80
|
},
|
|
75
|
-
// Every notebook has a package environment that must
|
|
76
|
-
// be managed.
|
|
77
81
|
{
|
|
78
|
-
|
|
82
|
+
type: "packages",
|
|
79
83
|
Icon: BoxIcon,
|
|
84
|
+
label: "Packages",
|
|
80
85
|
tooltip: "Manage packages",
|
|
81
|
-
|
|
86
|
+
defaultSection: "sidebar",
|
|
82
87
|
},
|
|
83
|
-
// 2. "AI" panel.
|
|
84
|
-
//
|
|
85
|
-
// The AI panel holds both agents and in-editor chat.
|
|
86
88
|
{
|
|
87
|
-
|
|
89
|
+
type: "ai",
|
|
88
90
|
Icon: BotIcon,
|
|
91
|
+
label: "AI",
|
|
89
92
|
tooltip: "Chat & Agents",
|
|
90
|
-
|
|
93
|
+
defaultSection: "sidebar",
|
|
91
94
|
},
|
|
92
95
|
{
|
|
93
|
-
|
|
96
|
+
type: "snippets",
|
|
94
97
|
Icon: SquareDashedBottomCodeIcon,
|
|
98
|
+
label: "Snippets",
|
|
95
99
|
tooltip: "Snippets",
|
|
96
|
-
|
|
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
|
-
|
|
103
|
+
type: "outline",
|
|
108
104
|
Icon: ScrollTextIcon,
|
|
105
|
+
label: "Outline",
|
|
109
106
|
tooltip: "View outline",
|
|
110
|
-
|
|
107
|
+
defaultSection: "sidebar",
|
|
111
108
|
},
|
|
112
109
|
{
|
|
113
|
-
|
|
110
|
+
type: "documentation",
|
|
114
111
|
Icon: TextSearchIcon,
|
|
112
|
+
label: "Docs",
|
|
115
113
|
tooltip: "View live docs",
|
|
116
|
-
|
|
114
|
+
defaultSection: "sidebar",
|
|
117
115
|
},
|
|
118
116
|
{
|
|
119
|
-
|
|
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
|
-
|
|
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, {
|
|
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 {
|
|
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
|
-
<
|
|
285
|
-
value={
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
<
|
|
327
|
-
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
</
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
);
|