nexus-shell 0.2.9 → 0.2.10

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;
@@ -11,6 +11,16 @@ export interface ShellLayoutProps {
11
11
  * renders its `component` in the sidebar when selected.
12
12
  */
13
13
  panels?: ISidebarPanel[];
14
+ /**
15
+ * Right-hand panels — inspectors, properties, anything about the current
16
+ * selection rather than about navigation.
17
+ *
18
+ * Registered exactly like `panels`, against a separate store, so the two
19
+ * sides open and close independently: file tree on the left and properties on
20
+ * the right, both at once. The right-hand rail appears only once at least one
21
+ * is registered.
22
+ */
23
+ inspectorPanels?: ISidebarPanel[];
14
24
  /** Slash commands available in the chat pane. */
15
25
  slashCommands?: ISlashCommandConfig[];
16
26
  /** Menu structure, keyed by top-level menu name. Items dispatch by `commandId`. */
@@ -92,4 +102,4 @@ export interface ShellLayoutProps {
92
102
  * );
93
103
  * ```
94
104
  */
95
- export declare const ShellLayout: ({ panels, slashCommands, menuConfig, statusBarConfig, title, rightMenuBarContent, centerMenuBarContent, layoutModel, initialLayoutJson, disableLocalStorage, onLayoutChange, }: ShellLayoutProps) => import("react/jsx-runtime").JSX.Element;
105
+ export declare const ShellLayout: ({ panels, inspectorPanels, slashCommands, menuConfig, statusBarConfig, title, rightMenuBarContent, centerMenuBarContent, layoutModel, initialLayoutJson, disableLocalStorage, onLayoutChange, }: ShellLayoutProps) => import("react/jsx-runtime").JSX.Element;
@@ -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 {
@@ -24,17 +25,25 @@ export interface ActivityBarProps {
24
25
  * deselect it — is the caller's decision, not the component's.
25
26
  */
26
27
  onSelect?: (id: string) => void;
28
+ /**
29
+ * Which edge of the shell the rail is docked to. Defaults to `"left"`.
30
+ *
31
+ * The divider and the active-item indicator both mirror, so a right-hand rail
32
+ * marks its selection on the edge facing the content.
33
+ */
34
+ side?: PaneSide;
27
35
  /** Extra classes merged onto the root `<aside>`. */
28
36
  className?: string;
29
37
  /** Accessible label for the landmark. Defaults to `"Activity Bar"`. */
30
38
  'aria-label'?: string;
31
39
  }
32
40
  /**
33
- * The narrow vertical icon rail on the left edge of the shell, used to switch
34
- * which panel the sidebar shows.
41
+ * The narrow vertical icon rail at the edge of the shell, used to switch which
42
+ * panel the adjacent {@link SidebarPane} shows.
35
43
  *
36
- * Controlled — it holds no selection state. For the store-backed variant used
37
- * by `ShellLayout`, see `ConnectedActivityBar`.
44
+ * Controlled — it holds no selection state. `side` docks it left or right; for
45
+ * the store-backed variants used by `ShellLayout`, see `ConnectedActivityBar`
46
+ * and `ConnectedInspectorBar`.
38
47
  *
39
48
  * @example
40
49
  * ```tsx
@@ -45,4 +54,4 @@ export interface ActivityBarProps {
45
54
  * />
46
55
  * ```
47
56
  */
48
- export declare const ActivityBar: ({ items, bottomItems, activeId, onSelect, className, "aria-label": ariaLabel, }: ActivityBarProps) => import("react/jsx-runtime").JSX.Element;
57
+ 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';
3
5
  export interface SidebarPaneProps {
4
6
  /**
5
7
  * Heading text. Rendered uppercase with letter-spacing; pass it in natural
@@ -15,23 +17,40 @@ export interface SidebarPaneProps {
15
17
  onClose?: () => void;
16
18
  /** Fixed pane width, as a CSS length. Defaults to `"300px"`. */
17
19
  width?: string;
20
+ /**
21
+ * Which edge of the shell the pane is docked to. Defaults to `"left"`.
22
+ *
23
+ * Only the divider moves — a right-hand pane is the same container, so an
24
+ * inspector does not need a component of its own.
25
+ */
26
+ side?: PaneSide;
18
27
  /** Extra classes merged onto the root `<aside>`. */
19
28
  className?: string;
20
29
  }
21
30
  /**
22
- * The collapsible panel beside the {@link ActivityBar}: a titled header with an
23
- * optional close button, over a scrolling body.
31
+ * A docked side panel: a titled header with an optional close button, over a
32
+ * scrolling body.
24
33
  *
25
34
  * 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`.
35
+ * no state. Render whatever you like as `children`.
36
+ *
37
+ * `side` docks it left (beside the {@link ActivityBar}) or right, where it
38
+ * becomes an inspector. Both are the same component deliberately: before this
39
+ * prop existed every application hand-rolled its own right-hand
40
+ * `<div className="w-80 border-l …">`, and they all drifted apart.
41
+ *
42
+ * For the store-backed variants used by `ShellLayout`, see
43
+ * `ConnectedSidebarPane` and `ConnectedInspectorPane`.
29
44
  *
30
45
  * @example
31
46
  * ```tsx
32
47
  * <SidebarPane title="Explorer" onClose={() => setActive(null)}>
33
48
  * <TreeWidget data={files} />
34
49
  * </SidebarPane>
50
+ *
51
+ * <SidebarPane title="Properties" side="right" width="320px">
52
+ * <PropertyPanel subjects={selected} fields={fields} />
53
+ * </SidebarPane>
35
54
  * ```
36
55
  */
37
- export declare const SidebarPane: ({ title, children, onClose, width, className, }: SidebarPaneProps) => import("react/jsx-runtime").JSX.Element;
56
+ export declare const SidebarPane: ({ title, children, onClose, width, 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;
@@ -2,9 +2,20 @@ import { ActivityBarProps } from '../components/widgets/ActivityBar';
2
2
 
3
3
  export type ConnectedActivityBarProps = Omit<ActivityBarProps, 'items' | 'activeId' | 'onSelect'>;
4
4
  /**
5
- * {@link ActivityBar} bound to `useSidebarStore`.
5
+ * {@link ActivityBar} bound to `useSidebarStore`, docked left.
6
6
  *
7
7
  * Derives its items from the registered sidebar panels and toggles the active
8
8
  * panel on click — clicking the already-active item closes the sidebar.
9
9
  */
10
10
  export declare const ConnectedActivityBar: (props: ConnectedActivityBarProps) => import("react/jsx-runtime").JSX.Element;
11
+ /**
12
+ * {@link ActivityBar} bound to `useInspectorStore`, docked right.
13
+ *
14
+ * Rendered only when inspector panels are registered — an empty rail is a
15
+ * twelve-pixel strip of nothing, and a shell that grows one the moment you
16
+ * register a panel is easier to reason about than one you must switch on.
17
+ *
18
+ * It also defaults to no bottom group: settings live on the left rail, and two
19
+ * settings buttons would be a puzzle.
20
+ */
21
+ export declare const ConnectedInspectorBar: (props: ConnectedActivityBarProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -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;
@@ -2,11 +2,32 @@ import { SidebarPaneProps } from '../components/widgets/SidebarPane';
2
2
 
3
3
  export type ConnectedSidebarPaneProps = Omit<SidebarPaneProps, 'title' | 'children' | 'onClose'>;
4
4
  /**
5
- * {@link SidebarPane} bound to `useSidebarStore`.
5
+ * {@link SidebarPane} bound to `useSidebarStore`, docked left.
6
6
  *
7
7
  * Renders whichever panel is active, or nothing when the sidebar is closed. The
8
8
  * reserved `"settings"` panel id renders the built-in {@link SettingsPanel}
9
9
  * wired to `useThemeStore`; every other id resolves against the panels
10
10
  * registered in the store.
11
11
  */
12
- export declare const ConnectedSidebarPane: (props: ConnectedSidebarPaneProps) => import("react/jsx-runtime").JSX.Element | null;
12
+ export declare const ConnectedSidebarPane: (props: ConnectedSidebarPaneProps) => import("react/jsx-runtime").JSX.Element;
13
+ /**
14
+ * {@link SidebarPane} bound to `useInspectorStore`, docked right.
15
+ *
16
+ * The same pane on the other edge, resolving against a separate panel registry
17
+ * — so an inspector opens and closes independently of the explorer, which is
18
+ * how anyone actually works: file tree open on the left, properties on the
19
+ * right, both at once.
20
+ *
21
+ * Unlike the left pane there is no reserved `"settings"` id here; settings are
22
+ * navigation, and navigation belongs on the left.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <ShellLayout
27
+ * inspectorPanels={[
28
+ * { id: 'properties', label: 'Properties', icon: Sliders, component: Inspector },
29
+ * ]}
30
+ * />
31
+ * ```
32
+ */
33
+ export declare const ConnectedInspectorPane: (props: ConnectedSidebarPaneProps) => 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,13 @@
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 } from './panels';
13
+ export { ConnectedActivityBar, ConnectedInspectorBar, type ConnectedActivityBarProps, } from './ConnectedActivityBar';
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 { ConnectedInspectorPane, ConnectedSidebarPane, type ConnectedSidebarPaneProps, } from './ConnectedSidebarPane';
18
19
  export { ConnectedStatusBar, type ConnectedStatusBarProps } from './ConnectedStatusBar';
19
20
  export { ConnectedTerminalPane, type ConnectedTerminalPaneProps } from './ConnectedTerminalPane';
20
21
  export { ConnectedThemeSwitcher, type ConnectedThemeSwitcherProps } from './ConnectedThemeSwitcher';
@@ -0,0 +1,28 @@
1
+ import { CHAT_PANEL_ID, TERMINAL_PANEL_ID, ISidebarPanel } from '../core/services/SidebarService';
2
+
3
+ /**
4
+ * Ready-made panel descriptors for the shell's own pieces.
5
+ *
6
+ * Chat and terminal used to be nailed into fixed slots — chat on the right,
7
+ * terminal along the bottom — and there was no way to put them anywhere else.
8
+ * They are now ordinary registrations, so they go wherever you register them:
9
+ *
10
+ * ```tsx
11
+ * initializeShell({
12
+ * panels: [terminalPanel()], // left rail
13
+ * inspectorPanels: [chatPanel()], // right rail
14
+ * });
15
+ *
16
+ * // …or as a dockable tab, alongside everything else:
17
+ * componentRegistry.register('terminal', ConnectedTerminalPane);
18
+ * useLayoutStore.getState().addTab('terminal', 'Terminal');
19
+ * ```
20
+ *
21
+ * Each takes overrides, so the id, label or icon can be changed without
22
+ * rebuilding the descriptor by hand.
23
+ */
24
+ /** The chat pane, as a registerable panel. */
25
+ export declare const chatPanel: (overrides?: Partial<ISidebarPanel>) => ISidebarPanel;
26
+ /** The terminal, as a registerable panel. */
27
+ export declare const terminalPanel: (overrides?: Partial<ISidebarPanel>) => ISidebarPanel;
28
+ export { CHAT_PANEL_ID, TERMINAL_PANEL_ID };
@@ -10,6 +10,12 @@ export interface InitializeShellOptions {
10
10
  * renders its `component` in the sidebar when selected.
11
11
  */
12
12
  panels?: ISidebarPanel[];
13
+ /**
14
+ * Right-hand panels — inspectors and properties, as opposed to navigation.
15
+ * Registered against `useInspectorStore`, so they open and close
16
+ * independently of the left-hand sidebar.
17
+ */
18
+ inspectorPanels?: ISidebarPanel[];
13
19
  /** Menus keyed by top-level name. Items dispatch through their `commandId`. */
14
20
  menus?: Record<string, IMenuItemConfig[]>;
15
21
  /** Commands available to the palette, menus and keybindings. */
@@ -32,6 +38,18 @@ export interface InitializeShellOptions {
32
38
  }
33
39
  /** The command ids `initializeShell` registers unless told not to. */
34
40
  export declare const DEFAULT_COMMAND_IDS: readonly ["view.toggleTerminal", "view.toggleChat", "view.toggleSidebar", ...string[]];
41
+ /**
42
+ * Toggle a panel wherever it happens to be registered.
43
+ *
44
+ * Chat and terminal are no longer nailed to one edge, so a command that toggled
45
+ * a fixed slot would be wrong the moment you moved them. This looks the id up
46
+ * in the left rail, then the right, and finally opens it as a tab — which is
47
+ * also what makes `Control+\`` keep working when the terminal is a tab.
48
+ *
49
+ * Returns `false` when the id is registered nowhere, so a caller can tell the
50
+ * difference between "toggled" and "nothing to toggle".
51
+ */
52
+ export declare const togglePanel: (id: string) => boolean;
35
53
  /**
36
54
  * Boot the shell: register commands, menus, panels and status bar items into
37
55
  * the shell registries and stores.
@@ -1,17 +1,48 @@
1
+ import { StoreApi, UseBoundStore } from 'zustand';
1
2
  import { LucideIcon } from 'lucide-react';
2
3
 
4
+ /**
5
+ * The docked side panels, one store per edge of the shell.
6
+ *
7
+ * Both edges behave identically — a registry of panels, one of which is active,
8
+ * remembered across reloads — so they share an implementation rather than two
9
+ * that drift.
10
+ */
11
+ /**
12
+ * Ids the shell's own panels use by default.
13
+ *
14
+ * Declared here rather than beside the descriptors so `core` can resolve them
15
+ * without depending on `connected` — the dependency runs the other way.
16
+ */
17
+ export declare const CHAT_PANEL_ID = "chat";
18
+ export declare const TERMINAL_PANEL_ID = "terminal";
3
19
  export interface ISidebarPanel {
4
20
  id: string;
5
21
  label: string;
6
22
  icon: LucideIcon;
7
23
  component: React.ComponentType<any> | React.ReactNode;
8
24
  }
9
- interface SidebarState {
25
+ export interface SidebarState {
26
+ /** Id of the open panel, or `null` when the pane is collapsed. */
10
27
  activeSidebar: string | null;
11
28
  panels: ISidebarPanel[];
12
29
  setActiveSidebar: (id: string | null) => void;
30
+ /** Open the panel, or close it if it is already open. */
13
31
  toggleSidebar: (id: string) => void;
14
32
  setPanels: (panels: ISidebarPanel[]) => void;
15
33
  }
16
- export declare const useSidebarStore: import('zustand').UseBoundStore<import('zustand').StoreApi<SidebarState>>;
17
- export {};
34
+ export type SidebarStore = UseBoundStore<StoreApi<SidebarState>>;
35
+ /**
36
+ * A panel store bound to one storage key.
37
+ *
38
+ * Exported so an application with a third docked region — a bottom drawer, say
39
+ * — gets the same behaviour without copying it.
40
+ */
41
+ export declare const createSidebarStore: (storageKey: string) => SidebarStore;
42
+ /** The left-hand pane, beside the activity bar. */
43
+ export declare const useSidebarStore: SidebarStore;
44
+ /**
45
+ * The right-hand pane: inspectors, properties, chat transcripts — anything
46
+ * about the current selection rather than about navigation.
47
+ */
48
+ export declare const useInspectorStore: SidebarStore;
@@ -1,8 +1,13 @@
1
+ /**
2
+ * The terminal's scrollback.
3
+ *
4
+ * Visibility used to live here too, back when the terminal had exactly one
5
+ * place it could be. It is now registered like any other panel, so whatever
6
+ * hosts it owns whether it is showing — see `terminalPanel()` and
7
+ * `togglePanel()`.
8
+ */
1
9
  interface TerminalState {
2
- isOpen: boolean;
3
10
  history: string[];
4
- setOpen: (open: boolean) => void;
5
- toggle: () => void;
6
11
  addHistory: (line: string) => void;
7
12
  clearHistory: () => void;
8
13
  }
package/dist/index.d.ts CHANGED
@@ -23,7 +23,8 @@
23
23
  * to.
24
24
  */
25
25
  export { ShellLayout, ShellLayout as NexusWorkspaceShell, type ShellLayoutProps } from './components/layout/ShellLayout';
26
- export { initializeShell, DEFAULT_COMMAND_IDS, type InitializeShellOptions, } from './core/Boot';
26
+ export { initializeShell, togglePanel, DEFAULT_COMMAND_IDS, type InitializeShellOptions, } from './core/Boot';
27
+ export { PaneHostProvider, usePaneHost, useHostChrome, type IPaneHost, type PaneHostProviderProps, } from './components/layout/PaneHost';
27
28
  export { commandRegistry, CommandRegistry, type ICommand } from './core/registry/CommandRegistry';
28
29
  export { menuRegistry, MenuRegistry, type IMenuItemConfig } from './core/registry/MenuRegistry';
29
30
  export { pluginRegistry, type IPlugin, type PluginStatus } from './core/registry/PluginRegistry';
@@ -31,8 +32,7 @@ export { componentRegistry, type ComponentConstructor } from './core/registry/Co
31
32
  export { useChatStore, type ISlashCommandConfig } from './core/services/ChatService';
32
33
  export { useLayoutStore } from './core/services/LayoutService';
33
34
  export { useModalStore, type ModalType as ModalStoreType } from './core/services/ModalStoreService';
34
- export { useRightSidebarStore } from './core/services/RightSidebarService';
35
- export { useSidebarStore, type ISidebarPanel } from './core/services/SidebarService';
35
+ export { useSidebarStore, useInspectorStore, createSidebarStore, CHAT_PANEL_ID, TERMINAL_PANEL_ID, type ISidebarPanel, type SidebarState, type SidebarStore, } from './core/services/SidebarService';
36
36
  export { useStatusBarStore, type IStatusBarWidgetConfig } from './core/services/StatusBarService';
37
37
  export { useTerminalStore } from './core/services/TerminalService';
38
38
  export { useThemeStore, type ThemeType } from './core/services/ThemeService';
@@ -50,7 +50,7 @@ export { Modal, type ModalProps, type ModalType } from './components/widgets/Mod
50
50
  export { QuickSearch, type QuickSearchProps, type IQuickSearchResult } from './components/widgets/QuickSearch';
51
51
  export { SearchWidget, type SearchWidgetProps, type ISearchResult } from './components/widgets/SearchWidget';
52
52
  export { SettingsPanel, type SettingsPanelProps, type ISettingsThemeOption, DEFAULT_SETTINGS_THEMES } from './components/widgets/SettingsPanel';
53
- export { SidebarPane, type SidebarPaneProps } from './components/widgets/SidebarPane';
53
+ export { SidebarPane, type SidebarPaneProps, type PaneSide } from './components/widgets/SidebarPane';
54
54
  export { StatusBar, type StatusBarProps, type IStatusBarWidget } from './components/widgets/StatusBar';
55
55
  export { TerminalPane, type TerminalPaneProps } from './components/widgets/TerminalPane';
56
56
  export { ThemeSwitcher, type ThemeSwitcherProps, type IThemeOption, DEFAULT_THEME_OPTIONS } from './components/widgets/ThemeSwitcher';