@peers-app/peers-ui 0.19.12 → 0.19.13

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 (53) hide show
  1. package/README.md +7 -2
  2. package/dist/components/voice-playback.d.ts +15 -0
  3. package/dist/components/voice-playback.js +86 -0
  4. package/dist/components/voice-subscribe-events.d.ts +0 -4
  5. package/dist/index.d.ts +10 -2
  6. package/dist/index.js +23 -2
  7. package/dist/operator-console/operator-console-state.d.ts +49 -0
  8. package/dist/operator-console/operator-console-state.js +97 -0
  9. package/dist/operator-console/operator-console.d.ts +32 -0
  10. package/dist/operator-console/operator-console.js +88 -0
  11. package/dist/root-layout/global-overlays.d.ts +37 -0
  12. package/dist/root-layout/global-overlays.js +37 -0
  13. package/dist/root-layout/layout-picker.d.ts +9 -0
  14. package/dist/root-layout/layout-picker.js +22 -0
  15. package/dist/root-layout/layouts/full-screen-layout.d.ts +12 -0
  16. package/dist/root-layout/layouts/full-screen-layout.js +60 -0
  17. package/dist/root-layout/layouts/tabs-with-console-layout.d.ts +20 -0
  18. package/dist/root-layout/layouts/tabs-with-console-layout.js +158 -0
  19. package/dist/root-layout/root-layout-host.d.ts +19 -0
  20. package/dist/root-layout/root-layout-host.js +88 -0
  21. package/dist/root-layout/root-layout-registry.d.ts +15 -0
  22. package/dist/root-layout/root-layout-registry.js +50 -0
  23. package/dist/root-layout/root-layout-state.d.ts +23 -0
  24. package/dist/root-layout/root-layout-state.js +54 -0
  25. package/dist/root-layout/root-layout.d.ts +30 -0
  26. package/dist/root-layout/root-layout.js +12 -0
  27. package/dist/screens/network-viewer/connection-troubleshooter.js +7 -11
  28. package/dist/screens/settings/settings-page.js +2 -1
  29. package/dist/tabs-layout/tabs-layout.d.ts +48 -4
  30. package/dist/tabs-layout/tabs-layout.js +27 -53
  31. package/dist/tabs-layout/tabs-state.d.ts +10 -0
  32. package/dist/tabs-layout/tabs-state.js +33 -1
  33. package/package.json +3 -3
  34. package/src/components/voice-playback.tsx +97 -0
  35. package/src/components/voice-subscribe-events.ts +0 -4
  36. package/src/index.tsx +24 -2
  37. package/src/operator-console/operator-console-state.ts +119 -0
  38. package/src/operator-console/operator-console.tsx +233 -0
  39. package/src/root-layout/global-overlays.ts +50 -0
  40. package/src/root-layout/layout-picker.tsx +47 -0
  41. package/src/root-layout/layouts/full-screen-layout.tsx +144 -0
  42. package/src/root-layout/layouts/tabs-with-console-layout.tsx +260 -0
  43. package/src/root-layout/root-layout-host.tsx +113 -0
  44. package/src/root-layout/root-layout-registry.ts +51 -0
  45. package/src/root-layout/root-layout-state.ts +55 -0
  46. package/src/root-layout/root-layout.ts +32 -0
  47. package/src/screens/network-viewer/connection-troubleshooter.tsx +10 -14
  48. package/src/screens/settings/settings-page.tsx +7 -1
  49. package/src/tabs-layout/tabs-layout.tsx +28 -74
  50. package/src/tabs-layout/tabs-state.ts +31 -0
  51. package/dist/components/chat-overlay.d.ts +0 -14
  52. package/dist/components/chat-overlay.js +0 -477
  53. package/src/components/chat-overlay.tsx +0 -854
package/README.md CHANGED
@@ -27,15 +27,20 @@ Hooks that bridge `@peers-app/peers-sdk` observables to React 18 with concurrent
27
27
 
28
28
  ### Layout
29
29
 
30
- - **`TabsLayout`** — Multi-tab application shell used by the Peers desktop and PWA clients
30
+ - **`RootLayoutHost`** — The single component a client mounts. Owns bootstrapping (user gate, context init, route loading), the always-available command palette, and the welcome modal, then renders the selected root layout body. `TabsLayoutApp` is a backwards-compatible alias for it.
31
+ - **Root layout system** — Swappable top-level layouts. `getRootLayouts()` / `getRootLayout(id)` expose the static registry, `selectedRootLayoutId` (per-device pvar) holds the choice, `setRootLayout(id)` changes it, and `<LayoutPicker />` is the Settings UI. The default `tabs` layout is `TabsLayoutInternal`.
32
+ - **`TabsLayoutInternal`** — The tabs UI body (tab strip + active tab content); the default root layout. Pass `fillContainer` to size it to its parent instead of the viewport (used when embedding it in another layout).
33
+ - **Tabs + Console layout** (`tabs-with-console`) — Splits the tab UI and the Operator Console into two real, resizable panels (dockable bottom/left/right) rather than an overlay. Selectable via `<LayoutPicker />`.
34
+ - **Full-screen layout** (`fullscreen`) — Shows one screen at a time with no tab strip; navigation is via the always-mounted command palette (Cmd/Ctrl+K) plus a Home button. While active it enables `setSingleTabMode(true)` so opening an app replaces the current screen instead of accumulating tabs (non-destructive — tabs from other layouts are left untouched).
35
+ - **`OperatorConsolePanel`** — The presentational console chrome (header toolbar + body) that fills its container; the host layout owns sizing, the resize divider, the collapsed strip, and placement. Toggle the console with Ctrl+` while on the Tabs + Console layout.
31
36
  - **`SortableList`** — Drag-and-drop list powered by SortableJS
32
37
  - **`InverseLazyList`** — Bottom-anchored lazy-loading list for chat-style UIs
33
38
  - **`Tabs`** / **`ScreenTabBody`** — Tab strip and content container
34
39
 
35
40
  ### Communication
36
41
 
37
- - **`ChatOverlay`** — Floating chat panel for AI assistant conversations
38
42
  - **`VoiceIndicator`** — Visual indicator for active voice sessions
43
+ - **`VoicePlayback`** — Headless component that plays browser-side voice TTS output
39
44
 
40
45
  ### Tab Management
41
46
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Voice Playback Component
3
+ *
4
+ * Headless (renders nothing) component that hosts the browser-side voice TTS
5
+ * playback handlers. The voice server (peers-electron `tts-service`) emits
6
+ * `voice:speakText` / `voice:playAudio` / `voice:stopSpeaking` events and relies
7
+ * on a mounted renderer to actually produce sound and report completion via
8
+ * `voiceNotifyPlaybackComplete`. Mount this once near the app root so voice
9
+ * output works regardless of which (if any) voice UI is visible.
10
+ */
11
+ /**
12
+ * Subscribes to voice TTS events and plays them in the browser. Renders nothing.
13
+ */
14
+ export declare const VoicePlayback: React.FC;
15
+ export default VoicePlayback;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Voice Playback Component
4
+ *
5
+ * Headless (renders nothing) component that hosts the browser-side voice TTS
6
+ * playback handlers. The voice server (peers-electron `tts-service`) emits
7
+ * `voice:speakText` / `voice:playAudio` / `voice:stopSpeaking` events and relies
8
+ * on a mounted renderer to actually produce sound and report completion via
9
+ * `voiceNotifyPlaybackComplete`. Mount this once near the app root so voice
10
+ * output works regardless of which (if any) voice UI is visible.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.VoicePlayback = void 0;
14
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
15
+ const react_1 = require("react");
16
+ /**
17
+ * Subscribes to voice TTS events and plays them in the browser. Renders nothing.
18
+ */
19
+ const VoicePlayback = () => {
20
+ // Track the current cloud-TTS audio element so it can be stopped on demand.
21
+ const currentAudioRef = (0, react_1.useRef)(null);
22
+ (0, react_1.useEffect)(() => {
23
+ const speakHandler = (0, peers_sdk_1.subscribe)("voice:speakText", (event) => {
24
+ const { text, voice, rate } = event.data;
25
+ if ("speechSynthesis" in window) {
26
+ const utterance = new SpeechSynthesisUtterance(text);
27
+ if (voice) {
28
+ const voices = speechSynthesis.getVoices();
29
+ const selectedVoice = voices.find((v) => v.name === voice);
30
+ if (selectedVoice)
31
+ utterance.voice = selectedVoice;
32
+ }
33
+ if (rate)
34
+ utterance.rate = rate;
35
+ utterance.onend = () => {
36
+ peers_sdk_1.rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => { });
37
+ };
38
+ utterance.onerror = () => {
39
+ peers_sdk_1.rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => { });
40
+ };
41
+ speechSynthesis.speak(utterance);
42
+ }
43
+ else {
44
+ peers_sdk_1.rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => { });
45
+ }
46
+ });
47
+ const stopHandler = (0, peers_sdk_1.subscribe)("voice:stopSpeaking", () => {
48
+ if ("speechSynthesis" in window) {
49
+ speechSynthesis.cancel();
50
+ }
51
+ if (currentAudioRef.current) {
52
+ currentAudioRef.current.pause();
53
+ currentAudioRef.current = null;
54
+ }
55
+ });
56
+ const playHandler = (0, peers_sdk_1.subscribe)("voice:playAudio", async (event) => {
57
+ const { audioBase64, mimeType } = event.data;
58
+ try {
59
+ const audioData = Uint8Array.from(atob(audioBase64), (c) => c.charCodeAt(0));
60
+ const blob = new Blob([audioData], { type: mimeType });
61
+ const url = URL.createObjectURL(blob);
62
+ const audio = new Audio(url);
63
+ currentAudioRef.current = audio;
64
+ audio.onended = () => {
65
+ URL.revokeObjectURL(url);
66
+ currentAudioRef.current = null;
67
+ peers_sdk_1.rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => { });
68
+ };
69
+ await audio.play();
70
+ }
71
+ catch (e) {
72
+ console.error("Failed to play audio:", e);
73
+ currentAudioRef.current = null;
74
+ peers_sdk_1.rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => { });
75
+ }
76
+ });
77
+ return () => {
78
+ speakHandler.unsubscribe();
79
+ stopHandler.unsubscribe();
80
+ playHandler.unsubscribe();
81
+ };
82
+ }, []);
83
+ return null;
84
+ };
85
+ exports.VoicePlayback = VoicePlayback;
86
+ exports.default = exports.VoicePlayback;
@@ -1,11 +1,7 @@
1
- import type { IMessage } from "@peers-app/peers-sdk";
2
1
  /** Shape passed to `subscribe` handlers for voice / chat IPC events */
3
2
  export type VoiceSubscribeEvent<T> = {
4
3
  data: T;
5
4
  };
6
- export type ChatOpenWithMessagePayload = {
7
- message?: IMessage;
8
- };
9
5
  export type VoiceStatePayload = {
10
6
  state: string;
11
7
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * from "./components/chat-overlay";
2
1
  export * from "./components/inverse-lazy-list";
3
2
  export type { IEditorEffects, IMarkdownEditorProps } from "./components/markdown-editor/editor";
4
3
  export { MarkdownEditor } from "./components/markdown-editor/editor";
@@ -8,7 +7,16 @@ export * from "./components/sortable-list";
8
7
  export * from "./components/tabs";
9
8
  export { Typeahead, type TypeaheadItem } from "./components/typeahead";
10
9
  export * from "./components/voice-indicator";
11
- export { mainContentPath } from "./globals";
10
+ export * from "./components/voice-playback";
11
+ export { isDesktop, mainContentPath } from "./globals";
12
12
  export * from "./hooks";
13
+ export * from "./operator-console/operator-console";
14
+ export * from "./operator-console/operator-console-state";
15
+ export { type GlobalOverlay, globalOverlays, registerGlobalOverlay, unregisterGlobalOverlay, } from "./root-layout/global-overlays";
16
+ export { LayoutPicker } from "./root-layout/layout-picker";
17
+ export type { RootLayout } from "./root-layout/root-layout";
18
+ export { RootLayoutHost, RootLayoutHost as TabsLayoutApp, } from "./root-layout/root-layout-host";
19
+ export { DEFAULT_ROOT_LAYOUT_ID, getRootLayout, getRootLayouts, } from "./root-layout/root-layout-registry";
20
+ export { selectedRootLayoutId, setRootLayout } from "./root-layout/root-layout-state";
13
21
  export * from "./tabs-layout/tabs-layout";
14
22
  export { activeTabId, activeTabs, closeCurrentTab, goToTabPath, TabState, updateActiveTabTitle, } from "./tabs-layout/tabs-state";
package/dist/index.js CHANGED
@@ -14,8 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.updateActiveTabTitle = exports.goToTabPath = exports.closeCurrentTab = exports.activeTabs = exports.activeTabId = exports.mainContentPath = exports.Typeahead = exports.MarkdownEditor = void 0;
18
- __exportStar(require("./components/chat-overlay"), exports);
17
+ exports.updateActiveTabTitle = exports.goToTabPath = exports.closeCurrentTab = exports.activeTabs = exports.activeTabId = exports.setRootLayout = exports.selectedRootLayoutId = exports.getRootLayouts = exports.getRootLayout = exports.DEFAULT_ROOT_LAYOUT_ID = exports.TabsLayoutApp = exports.RootLayoutHost = exports.LayoutPicker = exports.unregisterGlobalOverlay = exports.registerGlobalOverlay = exports.globalOverlays = exports.mainContentPath = exports.isDesktop = exports.Typeahead = exports.MarkdownEditor = void 0;
19
18
  __exportStar(require("./components/inverse-lazy-list"), exports);
20
19
  var editor_1 = require("./components/markdown-editor/editor");
21
20
  Object.defineProperty(exports, "MarkdownEditor", { enumerable: true, get: function () { return editor_1.MarkdownEditor; } });
@@ -26,9 +25,31 @@ __exportStar(require("./components/tabs"), exports);
26
25
  var typeahead_1 = require("./components/typeahead");
27
26
  Object.defineProperty(exports, "Typeahead", { enumerable: true, get: function () { return typeahead_1.Typeahead; } });
28
27
  __exportStar(require("./components/voice-indicator"), exports);
28
+ __exportStar(require("./components/voice-playback"), exports);
29
29
  var globals_1 = require("./globals");
30
+ Object.defineProperty(exports, "isDesktop", { enumerable: true, get: function () { return globals_1.isDesktop; } });
30
31
  Object.defineProperty(exports, "mainContentPath", { enumerable: true, get: function () { return globals_1.mainContentPath; } });
31
32
  __exportStar(require("./hooks"), exports);
33
+ __exportStar(require("./operator-console/operator-console"), exports);
34
+ __exportStar(require("./operator-console/operator-console-state"), exports);
35
+ var global_overlays_1 = require("./root-layout/global-overlays");
36
+ Object.defineProperty(exports, "globalOverlays", { enumerable: true, get: function () { return global_overlays_1.globalOverlays; } });
37
+ Object.defineProperty(exports, "registerGlobalOverlay", { enumerable: true, get: function () { return global_overlays_1.registerGlobalOverlay; } });
38
+ Object.defineProperty(exports, "unregisterGlobalOverlay", { enumerable: true, get: function () { return global_overlays_1.unregisterGlobalOverlay; } });
39
+ var layout_picker_1 = require("./root-layout/layout-picker");
40
+ Object.defineProperty(exports, "LayoutPicker", { enumerable: true, get: function () { return layout_picker_1.LayoutPicker; } });
41
+ var root_layout_host_1 = require("./root-layout/root-layout-host");
42
+ Object.defineProperty(exports, "RootLayoutHost", { enumerable: true, get: function () { return root_layout_host_1.RootLayoutHost; } });
43
+ // Compat alias: the desktop/PWA/RN clients historically mounted `TabsLayoutApp`.
44
+ // The host now owns bootstrapping; the tabs UI is just the default layout body.
45
+ Object.defineProperty(exports, "TabsLayoutApp", { enumerable: true, get: function () { return root_layout_host_1.RootLayoutHost; } });
46
+ var root_layout_registry_1 = require("./root-layout/root-layout-registry");
47
+ Object.defineProperty(exports, "DEFAULT_ROOT_LAYOUT_ID", { enumerable: true, get: function () { return root_layout_registry_1.DEFAULT_ROOT_LAYOUT_ID; } });
48
+ Object.defineProperty(exports, "getRootLayout", { enumerable: true, get: function () { return root_layout_registry_1.getRootLayout; } });
49
+ Object.defineProperty(exports, "getRootLayouts", { enumerable: true, get: function () { return root_layout_registry_1.getRootLayouts; } });
50
+ var root_layout_state_1 = require("./root-layout/root-layout-state");
51
+ Object.defineProperty(exports, "selectedRootLayoutId", { enumerable: true, get: function () { return root_layout_state_1.selectedRootLayoutId; } });
52
+ Object.defineProperty(exports, "setRootLayout", { enumerable: true, get: function () { return root_layout_state_1.setRootLayout; } });
32
53
  __exportStar(require("./tabs-layout/tabs-layout"), exports);
33
54
  var tabs_state_1 = require("./tabs-layout/tabs-state");
34
55
  Object.defineProperty(exports, "activeTabId", { enumerable: true, get: function () { return tabs_state_1.activeTabId; } });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Operator Console — panel state
3
+ *
4
+ * Persistent, per-device state for the Operator Console shell plus the control
5
+ * helpers and the global keyboard shortcut that toggles it. The panel itself
6
+ * ({@link OperatorConsolePanel}) reads this state reactively via `useObservable`.
7
+ */
8
+ /** Edge the console panel is docked to. */
9
+ export type ConsoleDock = "bottom" | "left" | "right";
10
+ /** Persisted layout/visibility state for the Operator Console panel. */
11
+ export interface ConsolePanelState {
12
+ /** Whether the panel is expanded (open) or collapsed to its edge strip. */
13
+ expanded: boolean;
14
+ /** Which screen edge the panel is docked to. */
15
+ dock: ConsoleDock;
16
+ /** Panel height in pixels when docked to the bottom. */
17
+ bottomHeight: number;
18
+ /** Panel width in pixels when docked to the left or right. */
19
+ sideWidth: number;
20
+ /** Whether the panel is maximized to fill the layout body (hiding the tabs panel). */
21
+ maximized: boolean;
22
+ }
23
+ /** Default panel state: collapsed, docked to the bottom. */
24
+ export declare const DEFAULT_CONSOLE_PANEL_STATE: ConsolePanelState;
25
+ /**
26
+ * Per-device persisted state for the Operator Console panel. Layout/visibility
27
+ * is a per-device preference, so this uses `deviceVar` (not synced across devices).
28
+ */
29
+ export declare const operatorConsolePanel: import("@peers-app/peers-sdk").PersistentVar<ConsolePanelState>;
30
+ /** Toggle the console between expanded and collapsed. */
31
+ export declare function toggleConsole(): void;
32
+ /** Expand (open) the console. */
33
+ export declare function openConsole(): void;
34
+ /** Collapse (close) the console. */
35
+ export declare function closeConsole(): void;
36
+ /** Dock the console to the given edge. */
37
+ export declare function setConsoleDock(dock: ConsoleDock): void;
38
+ /** Set whether the console is maximized (fills the layout body, hiding the tabs panel). */
39
+ export declare function setConsoleMaximized(maximized: boolean): void;
40
+ /**
41
+ * Persist a new panel size. Use `bottomHeight` when docked to the bottom and
42
+ * `sideWidth` when docked to a side.
43
+ */
44
+ export declare function setConsoleSize(size: {
45
+ bottomHeight?: number;
46
+ sideWidth?: number;
47
+ }): void;
48
+ /** Install the global Ctrl+` listener and register the palette command (idempotent). */
49
+ export declare function initializeOperatorConsole(): void;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Operator Console — panel state
4
+ *
5
+ * Persistent, per-device state for the Operator Console shell plus the control
6
+ * helpers and the global keyboard shortcut that toggles it. The panel itself
7
+ * ({@link OperatorConsolePanel}) reads this state reactively via `useObservable`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.operatorConsolePanel = exports.DEFAULT_CONSOLE_PANEL_STATE = void 0;
11
+ exports.toggleConsole = toggleConsole;
12
+ exports.openConsole = openConsole;
13
+ exports.closeConsole = closeConsole;
14
+ exports.setConsoleDock = setConsoleDock;
15
+ exports.setConsoleMaximized = setConsoleMaximized;
16
+ exports.setConsoleSize = setConsoleSize;
17
+ exports.initializeOperatorConsole = initializeOperatorConsole;
18
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
19
+ const command_palette_1 = require("../command-palette/command-palette");
20
+ /** Default panel state: collapsed, docked to the bottom. */
21
+ exports.DEFAULT_CONSOLE_PANEL_STATE = {
22
+ expanded: false,
23
+ dock: "bottom",
24
+ bottomHeight: 300,
25
+ sideWidth: 420,
26
+ maximized: false,
27
+ };
28
+ /**
29
+ * Per-device persisted state for the Operator Console panel. Layout/visibility
30
+ * is a per-device preference, so this uses `deviceVar` (not synced across devices).
31
+ */
32
+ exports.operatorConsolePanel = (0, peers_sdk_1.deviceVar)("operatorConsole:panel", {
33
+ defaultValue: exports.DEFAULT_CONSOLE_PANEL_STATE,
34
+ });
35
+ function update(patch) {
36
+ const current = (0, exports.operatorConsolePanel)() ?? exports.DEFAULT_CONSOLE_PANEL_STATE;
37
+ (0, exports.operatorConsolePanel)({ ...current, ...patch });
38
+ }
39
+ /** Toggle the console between expanded and collapsed. */
40
+ function toggleConsole() {
41
+ const current = (0, exports.operatorConsolePanel)() ?? exports.DEFAULT_CONSOLE_PANEL_STATE;
42
+ update({ expanded: !current.expanded });
43
+ }
44
+ /** Expand (open) the console. */
45
+ function openConsole() {
46
+ update({ expanded: true });
47
+ }
48
+ /** Collapse (close) the console. */
49
+ function closeConsole() {
50
+ update({ expanded: false });
51
+ }
52
+ /** Dock the console to the given edge. */
53
+ function setConsoleDock(dock) {
54
+ update({ dock });
55
+ }
56
+ /** Set whether the console is maximized (fills the layout body, hiding the tabs panel). */
57
+ function setConsoleMaximized(maximized) {
58
+ update({ maximized });
59
+ }
60
+ /**
61
+ * Persist a new panel size. Use `bottomHeight` when docked to the bottom and
62
+ * `sideWidth` when docked to a side.
63
+ */
64
+ function setConsoleSize(size) {
65
+ update(size);
66
+ }
67
+ /**
68
+ * Global keyboard handler for the console toggle shortcut. Ctrl+` on all
69
+ * platforms (matching VS Code / Cursor). Intentionally not Cmd on macOS.
70
+ */
71
+ function handleKeyDown(event) {
72
+ if (event.ctrlKey && !event.metaKey && !event.altKey && event.code === "Backquote") {
73
+ event.preventDefault();
74
+ toggleConsole();
75
+ }
76
+ }
77
+ let initialized = false;
78
+ /** Install the global Ctrl+` listener and register the palette command (idempotent). */
79
+ function initializeOperatorConsole() {
80
+ if (initialized || typeof window === "undefined")
81
+ return;
82
+ initialized = true;
83
+ document.addEventListener("keydown", handleKeyDown);
84
+ (0, command_palette_1.registerCommand)({
85
+ id: "toggle-operator-console",
86
+ label: "Toggle Console",
87
+ description: "Show or hide the Operator Console panel",
88
+ shortcut: "Ctrl+`",
89
+ iconClassName: "bi-terminal",
90
+ category: "View",
91
+ action: () => {
92
+ (0, command_palette_1.closeCommandPalette)();
93
+ toggleConsole();
94
+ },
95
+ });
96
+ }
97
+ initializeOperatorConsole();
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Operator Console — presentational panel
3
+ *
4
+ * The console's visual chrome only: a header toolbar (dock buttons, maximize/restore,
5
+ * collapse) plus the body region. It fills its container (`height/width: 100%`) and has
6
+ * no positioning of its own — the host layout (e.g. the `tabs-with-console` root layout)
7
+ * owns the split sizing, the resize handle, the collapsed edge strip, and where the panel
8
+ * is placed. Command input, output blocks, and the engine arrive later.
9
+ */
10
+ import type React from "react";
11
+ /** Resolved color palette for the console chrome. */
12
+ export interface ConsoleTheme {
13
+ bg: string;
14
+ headerBg: string;
15
+ border: string;
16
+ text: string;
17
+ muted: string;
18
+ hover: string;
19
+ accent: string;
20
+ }
21
+ /**
22
+ * Resolve the console color palette for the active color mode. Shared by the panel and by
23
+ * the host layout (so its collapsed strip and resize divider match the panel chrome).
24
+ */
25
+ export declare function getConsoleTheme(isDark: boolean): ConsoleTheme;
26
+ /**
27
+ * The Operator Console panel chrome. Renders as an in-flow flex column that fills its
28
+ * container; the parent layout decides the panel's size and placement. Reads dock and
29
+ * maximized state from `operatorConsolePanel` to drive the header toolbar.
30
+ */
31
+ export declare const OperatorConsolePanel: React.FC;
32
+ export default OperatorConsolePanel;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OperatorConsolePanel = void 0;
4
+ exports.getConsoleTheme = getConsoleTheme;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const hooks_1 = require("../hooks");
7
+ const color_mode_dropdown_1 = require("../screens/settings/color-mode-dropdown");
8
+ const operator_console_state_1 = require("./operator-console-state");
9
+ /**
10
+ * Resolve the console color palette for the active color mode. Shared by the panel and by
11
+ * the host layout (so its collapsed strip and resize divider match the panel chrome).
12
+ */
13
+ function getConsoleTheme(isDark) {
14
+ return {
15
+ bg: isDark ? "#1e1e1e" : "#ffffff",
16
+ headerBg: isDark ? "#252526" : "#f3f3f3",
17
+ border: isDark ? "#3c3c3c" : "#dee2e6",
18
+ text: isDark ? "#cccccc" : "#1e1e1e",
19
+ muted: isDark ? "#8c8c8c" : "#6c757d",
20
+ hover: isDark ? "#2a2d2e" : "#e8e8e8",
21
+ accent: "#0d6efd",
22
+ };
23
+ }
24
+ /**
25
+ * The Operator Console panel chrome. Renders as an in-flow flex column that fills its
26
+ * container; the parent layout decides the panel's size and placement. Reads dock and
27
+ * maximized state from `operatorConsolePanel` to drive the header toolbar.
28
+ */
29
+ const OperatorConsolePanel = () => {
30
+ const [state] = (0, hooks_1.useObservable)(operator_console_state_1.operatorConsolePanel);
31
+ const [mode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
32
+ const { dock, maximized } = state;
33
+ const theme = getConsoleTheme(mode === "dark");
34
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
35
+ width: "100%",
36
+ height: "100%",
37
+ backgroundColor: theme.bg,
38
+ color: theme.text,
39
+ display: "flex",
40
+ flexDirection: "column",
41
+ overflow: "hidden",
42
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
43
+ display: "flex",
44
+ alignItems: "center",
45
+ justifyContent: "space-between",
46
+ height: "34px",
47
+ flex: "0 0 auto",
48
+ padding: "0 8px",
49
+ backgroundColor: theme.headerBg,
50
+ borderBottom: `1px solid ${theme.border}`,
51
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", fontSize: "12px" }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-terminal" }), (0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 500 }, children: "Console" })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "2px" }, children: [(0, jsx_runtime_1.jsx)(DockButton, { dock: "left", active: dock === "left", disabled: maximized, theme: theme, icon: "bi-arrow-bar-left", label: "Dock left" }), (0, jsx_runtime_1.jsx)(DockButton, { dock: "bottom", active: dock === "bottom", disabled: maximized, theme: theme, icon: "bi-arrow-bar-down", label: "Dock bottom" }), (0, jsx_runtime_1.jsx)(DockButton, { dock: "right", active: dock === "right", disabled: maximized, theme: theme, icon: "bi-arrow-bar-right", label: "Dock right" }), (0, jsx_runtime_1.jsx)("span", { style: { width: "1px", height: "18px", backgroundColor: theme.border, margin: "0 4px" } }), (0, jsx_runtime_1.jsx)(ToolbarButton, { theme: theme, icon: maximized ? "bi-fullscreen-exit" : "bi-arrows-fullscreen", label: maximized ? "Restore" : "Maximize", onClick: () => (0, operator_console_state_1.setConsoleMaximized)(!maximized) }), (0, jsx_runtime_1.jsx)(ToolbarButton, { theme: theme, icon: "bi-chevron-down", label: "Collapse (Ctrl+`)", onClick: operator_console_state_1.toggleConsole })] })] }), (0, jsx_runtime_1.jsx)("div", { style: {
52
+ flex: "1 1 auto",
53
+ overflow: "auto",
54
+ display: "flex",
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ color: theme.muted,
58
+ fontSize: "13px",
59
+ }, children: (0, jsx_runtime_1.jsxs)("div", { style: { textAlign: "center" }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-terminal", style: { fontSize: "24px", display: "block", marginBottom: "6px" } }), "Operator Console"] }) })] }));
60
+ };
61
+ exports.OperatorConsolePanel = OperatorConsolePanel;
62
+ /** A small, icon-only button used in the console header toolbar. */
63
+ function ToolbarButton({ theme, icon, label, onClick, active, disabled }) {
64
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", title: label, "aria-label": label, disabled: disabled, onClick: onClick, style: {
65
+ display: "flex",
66
+ alignItems: "center",
67
+ justifyContent: "center",
68
+ width: "28px",
69
+ height: "26px",
70
+ border: "none",
71
+ borderRadius: "4px",
72
+ background: active ? theme.hover : "transparent",
73
+ color: active ? theme.text : theme.muted,
74
+ cursor: disabled ? "not-allowed" : "pointer",
75
+ opacity: disabled ? 0.4 : 1,
76
+ fontSize: "14px",
77
+ }, onMouseEnter: (e) => {
78
+ if (!disabled)
79
+ e.currentTarget.style.backgroundColor = theme.hover;
80
+ }, onMouseLeave: (e) => {
81
+ e.currentTarget.style.backgroundColor = active ? theme.hover : "transparent";
82
+ }, children: (0, jsx_runtime_1.jsx)("i", { className: icon }) }));
83
+ }
84
+ /** Header button that docks the console to a given edge. */
85
+ function DockButton({ dock, active, disabled, theme, icon, label }) {
86
+ return ((0, jsx_runtime_1.jsx)(ToolbarButton, { theme: theme, icon: icon, label: label, active: active, disabled: disabled, onClick: () => (0, operator_console_state_1.setConsoleDock)(dock) }));
87
+ }
88
+ exports.default = exports.OperatorConsolePanel;
@@ -0,0 +1,37 @@
1
+ import type React from "react";
2
+ /**
3
+ * A globally-mounted overlay component contributed by a package (or peers-ui itself).
4
+ *
5
+ * Overlays are rendered by {@link RootLayoutHost} above every root layout (alongside the
6
+ * command palette), so they remain visible regardless of which tab/app is active. Each
7
+ * overlay is responsible for deciding its own visibility (e.g. returning `null` when it
8
+ * should be hidden); the registry only controls whether the component is mounted.
9
+ */
10
+ export interface GlobalOverlay {
11
+ /** Stable id. Re-registering with the same id replaces the prior entry. */
12
+ id: string;
13
+ /** The component rendered by the host. It owns its own visibility and positioning. */
14
+ Component: React.ComponentType;
15
+ }
16
+ /**
17
+ * Reactive registry of global overlays rendered by {@link RootLayoutHost}.
18
+ *
19
+ * This mirrors the command-palette registry pattern: a package's UI bundle calls
20
+ * {@link registerGlobalOverlay} when it loads, and the host re-renders because this is an
21
+ * observable. Because peers-core (and other packages) externalize `@peers-app/peers-ui`
22
+ * to the shared `window.PeersUI` singleton, they register into this exact instance.
23
+ */
24
+ export declare const globalOverlays: import("@peers-app/peers-sdk").Observable<GlobalOverlay[]>;
25
+ /**
26
+ * Register a global overlay component, replacing any existing overlay with the same id.
27
+ *
28
+ * @param id Stable identifier for the overlay (used for replacement/removal).
29
+ * @param Component The React component to mount globally.
30
+ */
31
+ export declare function registerGlobalOverlay(id: string, Component: React.ComponentType): void;
32
+ /**
33
+ * Remove a previously-registered global overlay by id. No-op if the id is not registered.
34
+ *
35
+ * @param id The id passed to {@link registerGlobalOverlay}.
36
+ */
37
+ export declare function unregisterGlobalOverlay(id: string): void;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.globalOverlays = void 0;
4
+ exports.registerGlobalOverlay = registerGlobalOverlay;
5
+ exports.unregisterGlobalOverlay = unregisterGlobalOverlay;
6
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
7
+ /**
8
+ * Reactive registry of global overlays rendered by {@link RootLayoutHost}.
9
+ *
10
+ * This mirrors the command-palette registry pattern: a package's UI bundle calls
11
+ * {@link registerGlobalOverlay} when it loads, and the host re-renders because this is an
12
+ * observable. Because peers-core (and other packages) externalize `@peers-app/peers-ui`
13
+ * to the shared `window.PeersUI` singleton, they register into this exact instance.
14
+ */
15
+ exports.globalOverlays = (0, peers_sdk_1.observable)([]);
16
+ /**
17
+ * Register a global overlay component, replacing any existing overlay with the same id.
18
+ *
19
+ * @param id Stable identifier for the overlay (used for replacement/removal).
20
+ * @param Component The React component to mount globally.
21
+ */
22
+ function registerGlobalOverlay(id, Component) {
23
+ const next = (0, exports.globalOverlays)().filter((o) => o.id !== id);
24
+ next.push({ id, Component });
25
+ (0, exports.globalOverlays)(next);
26
+ }
27
+ /**
28
+ * Remove a previously-registered global overlay by id. No-op if the id is not registered.
29
+ *
30
+ * @param id The id passed to {@link registerGlobalOverlay}.
31
+ */
32
+ function unregisterGlobalOverlay(id) {
33
+ const current = (0, exports.globalOverlays)();
34
+ const next = current.filter((o) => o.id !== id);
35
+ if (next.length !== current.length)
36
+ (0, exports.globalOverlays)(next);
37
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Layout picker — Settings entry point for choosing the root layout.
3
+ *
4
+ * Lists every registered {@link RootLayout} and writes the selection to
5
+ * `selectedRootLayoutId` (per-device). `RootLayoutHost` subscribes to that pvar, so the
6
+ * layout swaps live on select. Mirrors the `ColorModeDropdown` pattern.
7
+ */
8
+ /** Dropdown that selects the active root layout. */
9
+ export declare const LayoutPicker: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LayoutPicker = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * Layout picker — Settings entry point for choosing the root layout.
7
+ *
8
+ * Lists every registered {@link RootLayout} and writes the selection to
9
+ * `selectedRootLayoutId` (per-device). `RootLayoutHost` subscribes to that pvar, so the
10
+ * layout swaps live on select. Mirrors the `ColorModeDropdown` pattern.
11
+ */
12
+ const hooks_1 = require("../hooks");
13
+ const root_layout_registry_1 = require("./root-layout-registry");
14
+ const root_layout_state_1 = require("./root-layout-state");
15
+ /** Dropdown that selects the active root layout. */
16
+ const LayoutPicker = () => {
17
+ const [selectedId] = (0, hooks_1.useObservable)(root_layout_state_1.selectedRootLayoutId);
18
+ const layouts = (0, root_layout_registry_1.getRootLayouts)();
19
+ const active = layouts.find((layout) => layout.id === selectedId);
20
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dropdown", children: [(0, jsx_runtime_1.jsxs)("button", { className: "btn btn-secondary dropdown-toggle", type: "button", id: "layoutPickerDropdown", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: ["Layout: ", active?.name ?? selectedId] }), (0, jsx_runtime_1.jsx)("ul", { className: "dropdown-menu", "aria-labelledby": "layoutPickerDropdown", children: layouts.map((layout) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "dropdown-item", onClick: () => (0, root_layout_state_1.setRootLayout)(layout.id), children: [(0, jsx_runtime_1.jsx)("i", { className: `${layout.iconClassName} me-2` }), layout.name, " ", layout.id === selectedId && "✓", (0, jsx_runtime_1.jsx)("div", { className: "small text-muted ms-4", children: layout.description })] }) }, layout.id))) })] }));
21
+ };
22
+ exports.LayoutPicker = LayoutPicker;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Full-screen root layout — one screen at a time, no tab strip.
3
+ *
4
+ * Renders only the active tab's content under a minimal header. All navigation flows
5
+ * through the command palette (Cmd/Ctrl+K), which {@link RootLayoutHost} always mounts,
6
+ * plus a Home button back to the app launcher. While mounted, this layout enables
7
+ * single-tab mode (see `setSingleTabMode`) so opening an app replaces the current screen
8
+ * instead of accumulating tabs. This is non-destructive: any tabs opened under a multi-tab
9
+ * layout are left untouched and reappear if the user switches back.
10
+ */
11
+ /** The full-screen layout body: minimal header + the single active screen. */
12
+ export declare function FullScreenLayout(): import("react/jsx-runtime").JSX.Element;