nexus-shell 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,8 +28,10 @@ import { Boxes, Files } from 'lucide-react';
28
28
  import {
29
29
  AppTitle,
30
30
  ShellLayout,
31
+ chatPanel,
31
32
  componentRegistry,
32
33
  initializeShell,
34
+ terminalPanel,
33
35
  useLayoutStore,
34
36
  } from 'nexus-shell';
35
37
  import 'nexus-shell/style.css';
@@ -38,7 +40,13 @@ import 'nexus-shell/style.css';
38
40
  componentRegistry.register('editor', Editor);
39
41
 
40
42
  initializeShell({
41
- panels: [{ id: 'files', label: 'Explorer', icon: Files, component: FileExplorer }],
43
+ panels: [
44
+ { id: 'files', label: 'Explorer', icon: Files, component: FileExplorer },
45
+ terminalPanel(),
46
+ ],
47
+ // Chat on the right rail. Move it to `panels` for the left, or register
48
+ // the component as a tab — nothing about it is bound to one edge.
49
+ inspectorPanels: [chatPanel()],
42
50
  commands: [
43
51
  { id: 'file.save', label: 'File: Save', keybinding: 'Control+s', execute: save },
44
52
  ],
@@ -56,6 +64,23 @@ createRoot(document.getElementById('root')!).render(
56
64
  That is a working IDE: dockable tabs, a command palette on `Cmd/Ctrl+Shift+P`, a
57
65
  terminal, a chat pane, and a themable frame.
58
66
 
67
+ ## Where things go
68
+
69
+ The shell has four places a view can live, and nothing is nailed to one of
70
+ them. Chat and the terminal are ordinary registrations, so they go wherever you
71
+ put them — including into a dockable tab, alongside your own views.
72
+
73
+ | Place | How |
74
+ | :-- | :-- |
75
+ | Left rail | `panels: [...]` |
76
+ | Right rail | `inspectorPanels: [...]` |
77
+ | Dockable tab | `componentRegistry.register(id, Component)` + `addTab(id)` |
78
+ | Bottom of the workspace | A tab in the bottom row of your `initialLayoutJson` |
79
+
80
+ Anything hosted in a pane or a tab is told so, and drops its own title bar —
81
+ `SidebarPane` and the tab strip already draw one. Pass `chrome` explicitly to
82
+ force it either way.
83
+
59
84
  ## Growing an app
60
85
 
61
86
  You add capability by **registering** it. None of this changes the layout, which
@@ -67,6 +92,8 @@ is what lets plugins and distant code contribute to the shell.
67
92
  | An action or hotkey | `commandRegistry.registerCommand({ id, label, keybinding, execute })` — appears in the palette automatically |
68
93
  | A menu entry | `menuRegistry.registerMenu('File', { id, label, commandId })` |
69
94
  | A sidebar panel | Add it to `panels`; it becomes an activity bar icon |
95
+ | An inspector or properties panel | Add it to `inspectorPanels`; it docks right, with its own rail |
96
+ | Chat or a terminal, anywhere | `chatPanel()` / `terminalPanel()` in `panels` or `inspectorPanels`, or register the component as a tab |
70
97
  | A status bar item | `useStatusBarStore.getState().addWidget(...)` |
71
98
  | A dialog from anywhere | `await useModalStore.getState().openConfirm('Sure?')` |
72
99
  | An unsaved-changes guard | `useLayoutStore.getState().setTabDirty(tabId, true)` |
@@ -114,8 +141,8 @@ shell's own frame and keep every registration working.
114
141
 
115
142
  | Component | What it is |
116
143
  | :-- | :-- |
117
- | `ActivityBar` | Vertical icon rail for switching sidebar panels |
118
- | `ChatPane` | Docked chat transcript with slash-command autocomplete |
144
+ | `ActivityBar` | Vertical icon rail for switching side panels, either edge |
145
+ | `ChatPane` | Chat transcript with slash-command autocomplete; fills any host |
119
146
  | `CommandPalette` | Filterable, keyboard-navigable command list |
120
147
  | `ContextMenu` | Floating menu positioned at viewport coordinates |
121
148
  | `DataGrid` | Sortable, filterable table with optional virtualisation |
@@ -124,9 +151,10 @@ shell's own frame and keep every registration working.
124
151
  | `QuickSearch` | Compact search field with grouped results, for the menu bar |
125
152
  | `SearchWidget` | Full-height search panel with autocomplete, for the sidebar |
126
153
  | `SettingsPanel` | Theme picker body for the settings sidebar |
127
- | `SidebarPane` | Titled, closable container for sidebar content |
154
+ | `SidebarPane` | Titled, closable side panel; `side` docks it left or right |
128
155
  | `StatusBar` | Footer with left / center / right item groups |
129
- | `TerminalPane` | Bottom-docked terminal log and input |
156
+ | `TerminalPane` | Terminal log and input; fills any host |
157
+ | `PaneHostProvider` | Tells hosted components a title bar is already drawn |
130
158
  | `ThemeSwitcher` | Segmented theme control |
131
159
  | `TreeWidget` | Virtualised tree for any hierarchy, with drag-to-move and a data-driven menu |
132
160
  | `GraphCanvas` | Infinite pannable, zoomable field for node-and-edge editing |
@@ -0,0 +1,56 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * What a component can learn about the thing hosting it.
5
+ *
6
+ * A pane, a dockable tab and a bare `<div>` give a component very different
7
+ * surroundings, and the difference that matters is whether a title bar has
8
+ * already been drawn. Without this, a chat pane inside a `SidebarPane` renders
9
+ * two headers and two close buttons.
10
+ */
11
+ export interface IPaneHost {
12
+ /**
13
+ * The host already draws a title bar and a close affordance, so a hosted
14
+ * component should not draw its own.
15
+ */
16
+ chrome: boolean;
17
+ /** Where the host sits, when it knows. Useful for choosing a layout. */
18
+ placement?: 'left' | 'right' | 'bottom' | 'tab';
19
+ }
20
+ /**
21
+ * Read the surrounding host, or `null` when there isn't one.
22
+ *
23
+ * Prefer {@link useHostChrome}, which folds in an explicit prop.
24
+ */
25
+ export declare const usePaneHost: () => IPaneHost | null;
26
+ /**
27
+ * Whether this component should draw its own title bar.
28
+ *
29
+ * The host provides the *default*; an explicit prop always wins. That ordering
30
+ * is deliberate — auto-detection keeps the common case free of configuration,
31
+ * but a component that can only be told implicitly is one you cannot unit-test
32
+ * without building a host around it, and cannot force when the detection is
33
+ * wrong.
34
+ *
35
+ * @param explicit The component's own `chrome` prop, if the caller set one.
36
+ */
37
+ export declare const useHostChrome: (explicit?: boolean) => boolean;
38
+ export interface PaneHostProviderProps extends IPaneHost {
39
+ /** Everything that should see this host. */
40
+ children: ReactNode;
41
+ }
42
+ /**
43
+ * Declare that everything inside is hosted.
44
+ *
45
+ * `SidebarPane` and the shell's tab factory do this for you. Wrap your own
46
+ * container in it if you draw a title bar and want hosted components to defer.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * <PaneHostProvider chrome placement="bottom">
51
+ * <MyTitleBar />
52
+ * <ConnectedTerminalPane />
53
+ * </PaneHostProvider>
54
+ * ```
55
+ */
56
+ export declare const PaneHostProvider: ({ children, ...host }: PaneHostProviderProps) => import("react/jsx-runtime").JSX.Element;
@@ -2,15 +2,22 @@ import { ReactNode } from 'react';
2
2
  import { Model, IJsonModel } from 'flexlayout-react';
3
3
  import { IMenuItemConfig } from '../../core/registry/MenuRegistry';
4
4
  import { ISlashCommandConfig } from '../../core/services/ChatService';
5
- import { ISidebarPanel } from '../../core/services/SidebarService';
5
+ import { IPanel } from '../../core/services/PaneService';
6
6
  import { IStatusBarWidgetConfig } from '../../core/services/StatusBarService';
7
7
 
8
8
  export interface ShellLayoutProps {
9
9
  /**
10
- * Sidebar panels to register. Each becomes an icon in the activity bar and
11
- * renders its `component` in the sidebar when selected.
10
+ * Panels to register, for every edge.
11
+ *
12
+ * Each panel's `side` decides where it docks — `"left"`, `"right"` or
13
+ * `"bottom"`, defaulting to left — and each edge keeps its own open panel, so
14
+ * a file tree, an inspector and a terminal can all be showing at once. A rail
15
+ * appears only once its edge has a panel.
16
+ *
17
+ * Placement is data on the panel rather than a separate prop per side, so
18
+ * moving something is a one-word change.
12
19
  */
13
- panels?: ISidebarPanel[];
20
+ panels?: IPanel[];
14
21
  /** Slash commands available in the chat pane. */
15
22
  slashCommands?: ISlashCommandConfig[];
16
23
  /** Menu structure, keyed by top-level menu name. Items dispatch by `commandId`. */
@@ -51,8 +58,8 @@ export interface ShellLayoutProps {
51
58
  onLayoutChange?: (layout: IJsonModel) => void;
52
59
  }
53
60
  /**
54
- * The complete application shell: menu bar, activity bar, sidebar, a
55
- * `flexlayout-react` docking workspace, terminal, chat pane and status bar.
61
+ * The complete application shell: menu bar, a docked pane and rail on each
62
+ * edge, a `flexlayout-react` docking workspace, and a status bar.
56
63
  *
57
64
  * **This is where an application starts.** Call {@link initializeShell} once to
58
65
  * register your commands, menus and panels, render this component, and add
@@ -81,7 +88,11 @@ export interface ShellLayoutProps {
81
88
  * componentRegistry.register('editor', Editor);
82
89
  *
83
90
  * initializeShell({
84
- * panels: [{ id: 'files', label: 'Explorer', icon: Files, component: FileTree }],
91
+ * panels: [
92
+ * { id: 'files', label: 'Explorer', icon: Files, component: FileTree },
93
+ * chatPanel({ side: 'right' }),
94
+ * terminalPanel({ side: 'bottom' }),
95
+ * ],
85
96
  * commands: [{ id: 'file.save', label: 'File: Save', keybinding: 'Control+s', execute: save }],
86
97
  * menus: { File: [{ id: 'save', label: 'Save', commandId: 'file.save' }] },
87
98
  * statusBar: [{ id: 'branch', label: 'main', alignment: 'left' }],
@@ -1,4 +1,5 @@
1
1
  import { LucideIcon } from 'lucide-react';
2
+ import { PaneSide } from './SidebarPane';
2
3
 
3
4
  /** A single icon button in the {@link ActivityBar}. */
4
5
  export interface IActivityBarItem {
@@ -13,8 +14,12 @@ export interface ActivityBarProps {
13
14
  /** Items rendered in the main (top) group, in order. */
14
15
  items?: IActivityBarItem[];
15
16
  /**
16
- * Items rendered in the bottom group, below the flex spacer. Defaults to a
17
- * single Settings item. Pass `[]` to render no bottom group.
17
+ * Items rendered in the bottom group, below the flex spacer. Empty by
18
+ * default.
19
+ *
20
+ * This used to default to a Settings item, which meant every rail grew one
21
+ * whether the app had settings or not. Settings is now an ordinary panel with
22
+ * `align: 'end'` — see `settingsPanel()`.
18
23
  */
19
24
  bottomItems?: IActivityBarItem[];
20
25
  /** Id of the currently active item, or `null` when nothing is selected. */
@@ -24,17 +29,25 @@ export interface ActivityBarProps {
24
29
  * deselect it — is the caller's decision, not the component's.
25
30
  */
26
31
  onSelect?: (id: string) => void;
32
+ /**
33
+ * Which edge of the shell the rail is docked to. Defaults to `"left"`.
34
+ *
35
+ * The divider and the active-item indicator both mirror, so a right-hand rail
36
+ * marks its selection on the edge facing the content.
37
+ */
38
+ side?: Exclude<PaneSide, 'bottom'>;
27
39
  /** Extra classes merged onto the root `<aside>`. */
28
40
  className?: string;
29
41
  /** Accessible label for the landmark. Defaults to `"Activity Bar"`. */
30
42
  'aria-label'?: string;
31
43
  }
32
44
  /**
33
- * The narrow vertical icon rail on the left edge of the shell, used to switch
34
- * which panel the sidebar shows.
45
+ * The narrow vertical icon rail at the edge of the shell, used to switch which
46
+ * panel the adjacent {@link SidebarPane} shows.
35
47
  *
36
- * Controlled — it holds no selection state. For the store-backed variant used
37
- * by `ShellLayout`, see `ConnectedActivityBar`.
48
+ * Controlled — it holds no selection state. `side` docks it left or right; for
49
+ * the store-backed variants used by `ShellLayout`, see `ConnectedActivityBar`
50
+ * and `ConnectedInspectorBar`.
38
51
  *
39
52
  * @example
40
53
  * ```tsx
@@ -45,4 +58,4 @@ export interface ActivityBarProps {
45
58
  * />
46
59
  * ```
47
60
  */
48
- export declare const ActivityBar: ({ items, bottomItems, activeId, onSelect, className, "aria-label": ariaLabel, }: ActivityBarProps) => import("react/jsx-runtime").JSX.Element;
61
+ export declare const ActivityBar: ({ items, bottomItems, activeId, onSelect, side, className, "aria-label": ariaLabel, }: ActivityBarProps) => import("react/jsx-runtime").JSX.Element;
@@ -38,17 +38,31 @@ export interface ChatPaneProps {
38
38
  placeholder?: string;
39
39
  /** Shown in place of the transcript when `messages` is empty. */
40
40
  emptyState?: React.ReactNode;
41
+ /**
42
+ * Draw the title bar.
43
+ *
44
+ * Left unset, this follows the host: inside a `SidebarPane` or a dockable tab
45
+ * the title is already drawn, so the pane omits its own. Set it explicitly to
46
+ * force either way — which is also what lets the component be tested without
47
+ * a host around it.
48
+ */
49
+ chrome?: boolean;
41
50
  /** Extra classes merged onto the root element. */
42
51
  className?: string;
43
52
  }
44
53
  /**
45
- * A docked chat panel: a scrolling transcript over a composer with slash-command
54
+ * A chat panel: a scrolling transcript over a composer with slash-command
46
55
  * autocomplete.
47
56
  *
48
57
  * Fully controlled — it owns only the in-progress input and the suggestion
49
58
  * highlight. Messages, sending and visibility belong to the caller, so the same
50
59
  * component works against a local array, a websocket, or an LLM endpoint. For
51
- * the store-backed variant used by `ShellLayout`, see `ConnectedChatPane`.
60
+ * the store-backed variant, see `ConnectedChatPane`.
61
+ *
62
+ * **It fills whatever hosts it.** Register it as a sidebar panel, an inspector
63
+ * panel or a dockable tab and it takes that shape; it carries no width or edge
64
+ * of its own. Inside a host that draws a title bar it omits its own header —
65
+ * see `chrome` to override.
52
66
  *
53
67
  * @example
54
68
  * ```tsx
@@ -60,4 +74,4 @@ export interface ChatPaneProps {
60
74
  * />
61
75
  * ```
62
76
  */
63
- export declare const ChatPane: ({ messages, onSend, slashCommands, onSlashCommand, onClose, title, placeholder, emptyState, className, }: ChatPaneProps) => import("react/jsx-runtime").JSX.Element;
77
+ export declare const ChatPane: ({ messages, onSend, slashCommands, onSlashCommand, onClose, title, placeholder, emptyState, chrome, className, }: ChatPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
+ /** Which edge of the shell a pane or rail is docked to. */
4
+ export type PaneSide = 'left' | 'right' | 'bottom';
3
5
  export interface SidebarPaneProps {
4
6
  /**
5
7
  * Heading text. Rendered uppercase with letter-spacing; pass it in natural
@@ -13,25 +15,49 @@ export interface SidebarPaneProps {
13
15
  * entirely — useful when the pane is always visible.
14
16
  */
15
17
  onClose?: () => void;
16
- /** Fixed pane width, as a CSS length. Defaults to `"300px"`. */
18
+ /** Fixed pane width, as a CSS length. Ignored when `side` is `"bottom"`. Defaults to `"300px"`. */
17
19
  width?: string;
20
+ /** Fixed pane height, as a CSS length. Only used when `side` is `"bottom"`. Defaults to `"240px"`. */
21
+ height?: string;
22
+ /**
23
+ * Which edge of the shell the pane is docked to. Defaults to `"left"`.
24
+ *
25
+ * Only the divider moves — a right-hand pane is the same container, so an
26
+ * inspector does not need a component of its own.
27
+ */
28
+ side?: PaneSide;
18
29
  /** Extra classes merged onto the root `<aside>`. */
19
30
  className?: string;
20
31
  }
21
32
  /**
22
- * The collapsible panel beside the {@link ActivityBar}: a titled header with an
23
- * optional close button, over a scrolling body.
33
+ * A docked side panel: a titled header with an optional close button, over a
34
+ * scrolling body.
24
35
  *
25
36
  * A pure container — it decides nothing about which panel is showing and holds
26
- * no state. Render whatever you like as `children`. For the store-backed
27
- * variant that resolves the active panel from `useSidebarStore`, see
28
- * `ConnectedSidebarPane`.
37
+ * no state. Render whatever you like as `children`.
38
+ *
39
+ * `side` docks it left (beside the {@link ActivityBar}) or right, where it
40
+ * becomes an inspector. Both are the same component deliberately: before this
41
+ * prop existed every application hand-rolled its own right-hand
42
+ * `<div className="w-80 border-l …">`, and they all drifted apart.
43
+ *
44
+ * `bottom` docks it below the workspace, where `width` gives way to `height`.
45
+ *
46
+ * For the store-backed variant used by `ShellLayout`, see `ConnectedPane`.
29
47
  *
30
48
  * @example
31
49
  * ```tsx
32
50
  * <SidebarPane title="Explorer" onClose={() => setActive(null)}>
33
51
  * <TreeWidget data={files} />
34
52
  * </SidebarPane>
53
+ *
54
+ * <SidebarPane title="Properties" side="right" width="320px">
55
+ * <PropertyPanel subjects={selected} fields={fields} />
56
+ * </SidebarPane>
57
+ *
58
+ * <SidebarPane title="Terminal" side="bottom" height="240px">
59
+ * <ConnectedTerminalPane />
60
+ * </SidebarPane>
35
61
  * ```
36
62
  */
37
- export declare const SidebarPane: ({ title, children, onClose, width, className, }: SidebarPaneProps) => import("react/jsx-runtime").JSX.Element;
63
+ export declare const SidebarPane: ({ title, children, onClose, width, height, side, className, }: SidebarPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -12,8 +12,22 @@ export interface TerminalPaneProps {
12
12
  onCommand?: (command: string) => void;
13
13
  /** Called when the collapse or close button is pressed. */
14
14
  onClose?: () => void;
15
- /** Height of the pane, as a CSS length. Defaults to `"250px"`. */
15
+ /**
16
+ * Height of the pane, as a CSS length.
17
+ *
18
+ * Leave it unset inside a host that sizes its own children — a side pane or a
19
+ * dockable tab — and the terminal fills the space instead. Defaults to
20
+ * `"250px"` only when standalone.
21
+ */
16
22
  height?: string;
23
+ /**
24
+ * Draw the title bar.
25
+ *
26
+ * Left unset, this follows the host: inside a `SidebarPane` or a dockable tab
27
+ * the title is already drawn, so the pane omits its own. Set it explicitly to
28
+ * force either way.
29
+ */
30
+ chrome?: boolean;
17
31
  /** Prompt string shown before the input. Defaults to `"$"`. */
18
32
  prompt?: string;
19
33
  /** Title shown in the pane header. Defaults to `"Terminal"`. */
@@ -22,14 +36,17 @@ export interface TerminalPaneProps {
22
36
  className?: string;
23
37
  }
24
38
  /**
25
- * A bottom-docked terminal pane: a scrolling output log over a single-line
26
- * input.
39
+ * A terminal pane: a scrolling output log over a single-line input.
27
40
  *
28
41
  * Presentational and fully controlled — it owns only the in-progress input
29
42
  * text. Visibility, history and command execution all belong to the caller.
30
43
  * For the store-backed variant with built-in `clear`/`help` handling, see
31
44
  * `ConnectedTerminalPane`.
32
45
  *
46
+ * **It fills whatever hosts it.** Bottom of the workspace, a side pane, or a
47
+ * dockable tab — register it wherever it belongs. Inside a host that draws a
48
+ * title bar it omits its own header and its own fixed height.
49
+ *
33
50
  * @example
34
51
  * ```tsx
35
52
  * const [history, setHistory] = useState<string[]>([]);
@@ -40,4 +57,4 @@ export interface TerminalPaneProps {
40
57
  * />
41
58
  * ```
42
59
  */
43
- export declare const TerminalPane: ({ history, onCommand, onClose, height, prompt, title, className, }: TerminalPaneProps) => import("react/jsx-runtime").JSX.Element;
60
+ export declare const TerminalPane: ({ history, onCommand, onClose, height, prompt, title, chrome, className, }: TerminalPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -9,10 +9,13 @@ export type ConnectedChatPaneProps = Omit<ChatPaneProps, 'messages' | 'slashComm
9
9
  onSend?: (text: string) => void;
10
10
  };
11
11
  /**
12
- * {@link ChatPane} bound to `useChatStore` and `useRightSidebarStore`.
12
+ * {@link ChatPane} bound to `useChatStore` and `useChatVisibilityStore`.
13
13
  *
14
- * Renders nothing while the right sidebar reports chat closed. Appends each
15
- * submission to the transcript, then either dispatches a registered slash
16
- * command's `execute` or hands the text to `onSend`.
14
+ * Appends each submission to the transcript, then either dispatches a
15
+ * registered slash command's `execute` or hands the text to `onSend`.
16
+ *
17
+ * Visibility belongs to whatever hosts it — a pane closes by closing the pane,
18
+ * a tab by closing the tab. Register it with `chatPanel()`, or as a tab
19
+ * component, and put it wherever it belongs.
17
20
  */
18
- export declare const ConnectedChatPane: ({ onSend, ...props }: ConnectedChatPaneProps) => import("react/jsx-runtime").JSX.Element | null;
21
+ export declare const ConnectedChatPane: ({ onSend, ...props }: ConnectedChatPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { SidebarPaneProps, PaneSide } from '../components/widgets/SidebarPane';
2
+
3
+ export type ConnectedPaneProps = Omit<SidebarPaneProps, 'title' | 'children' | 'onClose'> & {
4
+ /** Which edge to render. Defaults to `"left"`. */
5
+ side?: PaneSide;
6
+ };
7
+ /**
8
+ * {@link SidebarPane} bound to the pane store for one edge.
9
+ *
10
+ * Renders whichever panel is open on that edge, or nothing when the pane is
11
+ * collapsed. Every edge resolves against its own registry, so the left pane,
12
+ * the right pane and the bottom drawer open and close independently — a file
13
+ * tree, an inspector and a terminal all at once is the normal case, not a
14
+ * special one.
15
+ *
16
+ * There is nothing edge-specific in here. A panel decides where it lives
17
+ * through its `side`, and this renders whatever arrived.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <ConnectedPane side="right" width="320px" />
22
+ * ```
23
+ */
24
+ export declare const ConnectedPane: ({ side, ...props }: ConnectedPaneProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,25 @@
1
+ import { ActivityBarProps } from '../components/widgets/ActivityBar';
2
+ import { PaneSide } from '../components/widgets/SidebarPane';
3
+
4
+ export type ConnectedPaneRailProps = Omit<ActivityBarProps, 'items' | 'bottomItems' | 'activeId' | 'onSelect' | 'side'> & {
5
+ /** Which edge to render. Defaults to `"left"`. */
6
+ side?: Exclude<PaneSide, 'bottom'>;
7
+ };
8
+ /**
9
+ * {@link ActivityBar} bound to the pane store for one edge.
10
+ *
11
+ * Derives its items from the panels registered on that edge and toggles the
12
+ * open one — clicking the already-open item closes the pane. Items with
13
+ * `align: 'end'` go to the far group, which is how settings ends up at the
14
+ * bottom without the rail knowing what settings is.
15
+ *
16
+ * Renders nothing when no panels are registered for the edge. An empty rail is
17
+ * a twelve-pixel strip of nothing, and both edges behave the same way — the
18
+ * left rail used to render regardless, which made the two sides subtly
19
+ * different for no reason anyone had chosen.
20
+ *
21
+ * There is no bottom rail: a bottom drawer is toggled from the status bar, a
22
+ * command or a keybinding, and a horizontal strip of icons under the workspace
23
+ * costs more room than it earns.
24
+ */
25
+ export declare const ConnectedPaneRail: ({ side, ...props }: ConnectedPaneRailProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ import { SettingsPanelProps } from '../components/widgets/SettingsPanel';
2
+
3
+ export type ConnectedSettingsPanelProps = Omit<SettingsPanelProps, 'theme' | 'onThemeChange'>;
4
+ /**
5
+ * {@link SettingsPanel} bound to `useThemeStore`.
6
+ *
7
+ * This used to be reached through a reserved `"settings"` panel id that the
8
+ * pane special-cased. Making it an ordinary connected component means the pane
9
+ * has no privileged ids left, and both rails behave identically — register it
10
+ * with `settingsPanel()`, or don't, and nothing else changes.
11
+ */
12
+ export declare const ConnectedSettingsPanel: (props: ConnectedSettingsPanelProps) => import("react/jsx-runtime").JSX.Element;
@@ -11,8 +11,11 @@ export type ConnectedTerminalPaneProps = Omit<TerminalPaneProps, 'history' | 'on
11
11
  /**
12
12
  * {@link TerminalPane} bound to `useTerminalStore`.
13
13
  *
14
- * Renders nothing while the store reports the terminal closed. Echoes each
15
- * command into history and handles `help` and `clear` internally; anything else
16
- * is offered to `onCommand` before falling back to a not-found message.
14
+ * Echoes each command into history and handles `help` and `clear` internally;
15
+ * anything else is offered to `onCommand` before falling back to a not-found
16
+ * message.
17
+ *
18
+ * Visibility belongs to whatever hosts it. Register it with `terminalPanel()`
19
+ * for a side pane, or as a tab component for the docking area.
17
20
  */
18
- export declare const ConnectedTerminalPane: ({ onCommand, ...props }: ConnectedTerminalPaneProps) => import("react/jsx-runtime").JSX.Element | null;
21
+ export declare const ConnectedTerminalPane: ({ onCommand, ...props }: ConnectedTerminalPaneProps) => import("react/jsx-runtime").JSX.Element;
@@ -9,12 +9,14 @@
9
9
  * Reach for these when you want the shell's behavior. Reach for the plain
10
10
  * components when you want control.
11
11
  */
12
- export { ConnectedActivityBar, type ConnectedActivityBarProps } from './ConnectedActivityBar';
12
+ export { chatPanel, terminalPanel, settingsPanel } from './panels';
13
+ export { ConnectedPaneRail, type ConnectedPaneRailProps } from './ConnectedPaneRail';
13
14
  export { ConnectedChatPane, type ConnectedChatPaneProps } from './ConnectedChatPane';
14
15
  export { ConnectedCommandPalette, type ConnectedCommandPaletteProps } from './ConnectedCommandPalette';
15
16
  export { ConnectedMenuBar, type ConnectedMenuBarProps } from './ConnectedMenuBar';
16
17
  export { ConnectedModal, type ConnectedModalProps } from './ConnectedModal';
17
- export { ConnectedSidebarPane, type ConnectedSidebarPaneProps } from './ConnectedSidebarPane';
18
+ export { ConnectedPane, type ConnectedPaneProps } from './ConnectedPane';
19
+ export { ConnectedSettingsPanel, type ConnectedSettingsPanelProps, } from './ConnectedSettingsPanel';
18
20
  export { ConnectedStatusBar, type ConnectedStatusBarProps } from './ConnectedStatusBar';
19
21
  export { ConnectedTerminalPane, type ConnectedTerminalPaneProps } from './ConnectedTerminalPane';
20
22
  export { ConnectedThemeSwitcher, type ConnectedThemeSwitcherProps } from './ConnectedThemeSwitcher';
@@ -0,0 +1,36 @@
1
+ import { IPanel } from '../core/services/PaneService';
2
+
3
+ /**
4
+ * Ready-made panel descriptors for the shell's own pieces.
5
+ *
6
+ * Chat, terminal and settings were each nailed into a fixed place — chat on the
7
+ * right, terminal along the bottom, settings behind a reserved panel id the
8
+ * pane special-cased. They are now ordinary registrations with no privileges,
9
+ * so they go wherever you put them:
10
+ *
11
+ * ```tsx
12
+ * initializeShell({
13
+ * panels: [
14
+ * { id: 'files', label: 'Explorer', icon: Files, component: FileTree },
15
+ * chatPanel({ side: 'right' }),
16
+ * terminalPanel({ side: 'bottom' }),
17
+ * settingsPanel(),
18
+ * ],
19
+ * });
20
+ * ```
21
+ *
22
+ * Each takes overrides, so the id, label, icon or side can change without
23
+ * rebuilding the descriptor by hand.
24
+ */
25
+ /** The chat pane, as a registerable panel. */
26
+ export declare const chatPanel: (overrides?: Partial<IPanel>) => IPanel;
27
+ /** The terminal, as a registerable panel. */
28
+ export declare const terminalPanel: (overrides?: Partial<IPanel>) => IPanel;
29
+ /**
30
+ * The theme picker, as a registerable panel.
31
+ *
32
+ * `align: 'end'` pins it to the far group of its rail, which is where a
33
+ * settings icon conventionally sits — and is now the only reason it ends up
34
+ * there, rather than the pane knowing the string `"settings"`.
35
+ */
36
+ export declare const settingsPanel: (overrides?: Partial<IPanel>) => IPanel;
@@ -1,7 +1,7 @@
1
1
  import { ICommand } from './registry/CommandRegistry';
2
2
  import { IMenuItemConfig } from './registry/MenuRegistry';
3
3
  import { ISlashCommandConfig } from './services/ChatService';
4
- import { ISidebarPanel } from './services/SidebarService';
4
+ import { IPanel } from './services/PaneService';
5
5
  import { IStatusBarWidgetConfig } from './services/StatusBarService';
6
6
 
7
7
  export interface InitializeShellOptions {
@@ -9,7 +9,7 @@ export interface InitializeShellOptions {
9
9
  * Sidebar panels to register. Each becomes an icon in the activity bar and
10
10
  * renders its `component` in the sidebar when selected.
11
11
  */
12
- panels?: ISidebarPanel[];
12
+ panels?: IPanel[];
13
13
  /** Menus keyed by top-level name. Items dispatch through their `commandId`. */
14
14
  menus?: Record<string, IMenuItemConfig[]>;
15
15
  /** Commands available to the palette, menus and keybindings. */
@@ -32,6 +32,18 @@ export interface InitializeShellOptions {
32
32
  }
33
33
  /** The command ids `initializeShell` registers unless told not to. */
34
34
  export declare const DEFAULT_COMMAND_IDS: readonly ["view.toggleTerminal", "view.toggleChat", "view.toggleSidebar", ...string[]];
35
+ /**
36
+ * Toggle a panel wherever it happens to be registered.
37
+ *
38
+ * Chat and terminal are no longer nailed to one edge, so a command that toggled
39
+ * a fixed slot would be wrong the moment you moved them. This looks the id up
40
+ * in the left rail, then the right, and finally opens it as a tab — which is
41
+ * also what makes `Control+\`` keep working when the terminal is a tab.
42
+ *
43
+ * Returns `false` when the id is registered nowhere, so a caller can tell the
44
+ * difference between "toggled" and "nothing to toggle".
45
+ */
46
+ export declare const togglePanel: (id: string) => boolean;
35
47
  /**
36
48
  * Boot the shell: register commands, menus, panels and status bar items into
37
49
  * the shell registries and stores.
@@ -54,7 +66,10 @@ export declare const DEFAULT_COMMAND_IDS: readonly ["view.toggleTerminal", "view
54
66
  * componentRegistry.register('editor', Editor);
55
67
  *
56
68
  * initializeShell({
57
- * panels: [{ id: 'files', label: 'Explorer', icon: Files, component: FileTree }],
69
+ * panels: [
70
+ * { id: 'files', label: 'Explorer', icon: Files, component: FileTree },
71
+ * chatPanel({ side: 'right' }),
72
+ * ],
58
73
  * commands: [{ id: 'file.save', label: 'File: Save', keybinding: 'Control+s', execute: save }],
59
74
  * menus: { File: [{ id: 'save', label: 'Save', commandId: 'file.save' }] },
60
75
  * statusBar: [{ id: 'branch', label: 'main', alignment: 'left' }],