@wong2kim/wmux 1.0.0 → 1.0.1

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -157
  3. package/dist/cli/cli/client.js +1 -1
  4. package/dist/cli/cli/commands/browser.js +19 -19
  5. package/dist/cli/cli/index.js +58 -58
  6. package/dist/cli/shared/constants.js +17 -4
  7. package/dist/mcp/shared/constants.js +17 -4
  8. package/package.json +96 -84
  9. package/assets/icon.ico +0 -0
  10. package/assets/icon.svg +0 -6
  11. package/forge.config.ts +0 -61
  12. package/index.html +0 -12
  13. package/postcss.config.js +0 -6
  14. package/src/cli/client.ts +0 -76
  15. package/src/cli/commands/browser.ts +0 -128
  16. package/src/cli/commands/input.ts +0 -72
  17. package/src/cli/commands/notify.ts +0 -29
  18. package/src/cli/commands/pane.ts +0 -90
  19. package/src/cli/commands/surface.ts +0 -102
  20. package/src/cli/commands/system.ts +0 -95
  21. package/src/cli/commands/workspace.ts +0 -116
  22. package/src/cli/index.ts +0 -145
  23. package/src/cli/utils.ts +0 -44
  24. package/src/main/index.ts +0 -86
  25. package/src/main/ipc/handlers/clipboard.handler.ts +0 -20
  26. package/src/main/ipc/handlers/metadata.handler.ts +0 -56
  27. package/src/main/ipc/handlers/pty.handler.ts +0 -69
  28. package/src/main/ipc/handlers/session.handler.ts +0 -17
  29. package/src/main/ipc/handlers/shell.handler.ts +0 -11
  30. package/src/main/ipc/registerHandlers.ts +0 -31
  31. package/src/main/mcp/McpRegistrar.ts +0 -156
  32. package/src/main/metadata/MetadataCollector.ts +0 -58
  33. package/src/main/notification/ToastManager.ts +0 -32
  34. package/src/main/pipe/PipeServer.ts +0 -190
  35. package/src/main/pipe/RpcRouter.ts +0 -46
  36. package/src/main/pipe/handlers/_bridge.ts +0 -40
  37. package/src/main/pipe/handlers/browser.rpc.ts +0 -132
  38. package/src/main/pipe/handlers/input.rpc.ts +0 -120
  39. package/src/main/pipe/handlers/meta.rpc.ts +0 -59
  40. package/src/main/pipe/handlers/notify.rpc.ts +0 -53
  41. package/src/main/pipe/handlers/pane.rpc.ts +0 -39
  42. package/src/main/pipe/handlers/surface.rpc.ts +0 -43
  43. package/src/main/pipe/handlers/system.rpc.ts +0 -36
  44. package/src/main/pipe/handlers/workspace.rpc.ts +0 -52
  45. package/src/main/pty/AgentDetector.ts +0 -247
  46. package/src/main/pty/OscParser.ts +0 -81
  47. package/src/main/pty/PTYBridge.ts +0 -88
  48. package/src/main/pty/PTYManager.ts +0 -104
  49. package/src/main/pty/ShellDetector.ts +0 -63
  50. package/src/main/session/SessionManager.ts +0 -53
  51. package/src/main/updater/AutoUpdater.ts +0 -132
  52. package/src/main/window/createWindow.ts +0 -71
  53. package/src/mcp/README.md +0 -56
  54. package/src/mcp/index.ts +0 -153
  55. package/src/mcp/wmux-client.ts +0 -127
  56. package/src/preload/index.ts +0 -111
  57. package/src/preload/preload.ts +0 -108
  58. package/src/renderer/App.tsx +0 -5
  59. package/src/renderer/components/Browser/BrowserPanel.tsx +0 -219
  60. package/src/renderer/components/Browser/BrowserToolbar.tsx +0 -253
  61. package/src/renderer/components/Company/ApprovalDialog.tsx +0 -3
  62. package/src/renderer/components/Company/CompanyView.tsx +0 -7
  63. package/src/renderer/components/Company/MessageFeedPanel.tsx +0 -3
  64. package/src/renderer/components/Layout/AppLayout.tsx +0 -234
  65. package/src/renderer/components/Notification/NotificationPanel.tsx +0 -129
  66. package/src/renderer/components/Palette/CommandPalette.tsx +0 -409
  67. package/src/renderer/components/Palette/PaletteItem.tsx +0 -55
  68. package/src/renderer/components/Pane/Pane.tsx +0 -122
  69. package/src/renderer/components/Pane/PaneContainer.tsx +0 -41
  70. package/src/renderer/components/Pane/SurfaceTabs.tsx +0 -46
  71. package/src/renderer/components/Settings/SettingsPanel.tsx +0 -886
  72. package/src/renderer/components/Sidebar/MiniSidebar.tsx +0 -67
  73. package/src/renderer/components/Sidebar/Sidebar.tsx +0 -84
  74. package/src/renderer/components/Sidebar/WorkspaceItem.tsx +0 -241
  75. package/src/renderer/components/StatusBar/StatusBar.tsx +0 -93
  76. package/src/renderer/components/Terminal/SearchBar.tsx +0 -126
  77. package/src/renderer/components/Terminal/Terminal.tsx +0 -102
  78. package/src/renderer/components/Terminal/ViCopyMode.tsx +0 -104
  79. package/src/renderer/hooks/useKeyboard.ts +0 -310
  80. package/src/renderer/hooks/useNotificationListener.ts +0 -80
  81. package/src/renderer/hooks/useNotificationSound.ts +0 -75
  82. package/src/renderer/hooks/useRpcBridge.ts +0 -451
  83. package/src/renderer/hooks/useT.ts +0 -11
  84. package/src/renderer/hooks/useTerminal.ts +0 -349
  85. package/src/renderer/hooks/useViCopyMode.ts +0 -320
  86. package/src/renderer/i18n/index.ts +0 -69
  87. package/src/renderer/i18n/locales/en.ts +0 -157
  88. package/src/renderer/i18n/locales/ja.ts +0 -155
  89. package/src/renderer/i18n/locales/ko.ts +0 -155
  90. package/src/renderer/i18n/locales/zh.ts +0 -155
  91. package/src/renderer/index.tsx +0 -6
  92. package/src/renderer/stores/index.ts +0 -19
  93. package/src/renderer/stores/slices/notificationSlice.ts +0 -56
  94. package/src/renderer/stores/slices/paneSlice.ts +0 -141
  95. package/src/renderer/stores/slices/surfaceSlice.ts +0 -122
  96. package/src/renderer/stores/slices/uiSlice.ts +0 -247
  97. package/src/renderer/stores/slices/workspaceSlice.ts +0 -120
  98. package/src/renderer/styles/globals.css +0 -150
  99. package/src/renderer/themes.ts +0 -99
  100. package/src/shared/constants.ts +0 -53
  101. package/src/shared/electron.d.ts +0 -11
  102. package/src/shared/rpc.ts +0 -71
  103. package/src/shared/types.ts +0 -176
  104. package/tailwind.config.js +0 -11
  105. package/tsconfig.cli.json +0 -24
  106. package/tsconfig.json +0 -21
  107. package/tsconfig.mcp.json +0 -25
  108. package/vite.main.config.ts +0 -14
  109. package/vite.preload.config.ts +0 -9
  110. package/vite.renderer.config.ts +0 -6
@@ -1,108 +0,0 @@
1
- import { contextBridge, ipcRenderer } from 'electron';
2
- import { IPC } from '../shared/constants';
3
-
4
- const electronAPI = {
5
- pty: {
6
- create: (options?: { shell?: string; cwd?: string; cols?: number; rows?: number }) =>
7
- ipcRenderer.invoke(IPC.PTY_CREATE, options),
8
- write: (id: string, data: string) =>
9
- ipcRenderer.invoke(IPC.PTY_WRITE, id, data),
10
- resize: (id: string, cols: number, rows: number) =>
11
- ipcRenderer.invoke(IPC.PTY_RESIZE, id, cols, rows),
12
- dispose: (id: string) =>
13
- ipcRenderer.invoke(IPC.PTY_DISPOSE, id),
14
- onData: (callback: (id: string, data: string) => void) => {
15
- const listener = (_event: Electron.IpcRendererEvent, id: string, data: string) => callback(id, data);
16
- ipcRenderer.on(IPC.PTY_DATA, listener);
17
- return () => { ipcRenderer.removeListener(IPC.PTY_DATA, listener); };
18
- },
19
- onExit: (callback: (id: string, exitCode: number) => void) => {
20
- const listener = (_event: Electron.IpcRendererEvent, id: string, exitCode: number) => callback(id, exitCode);
21
- ipcRenderer.on(IPC.PTY_EXIT, listener);
22
- return () => { ipcRenderer.removeListener(IPC.PTY_EXIT, listener); };
23
- },
24
- },
25
- shell: {
26
- list: () => ipcRenderer.invoke(IPC.SHELL_LIST) as Promise<{ name: string; path: string; args?: string[] }[]>,
27
- },
28
- session: {
29
- save: (data: unknown) => ipcRenderer.invoke(IPC.SESSION_SAVE, data),
30
- load: () => ipcRenderer.invoke(IPC.SESSION_LOAD),
31
- },
32
- settings: {
33
- setToastEnabled: (enabled: boolean) => ipcRenderer.send(IPC.TOAST_ENABLED, enabled),
34
- },
35
- notification: {
36
- onNew: (callback: (ptyId: string, data: { type: string; title: string; body: string }) => void) => {
37
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, data: { type: string; title: string; body: string }) =>
38
- callback(ptyId, data);
39
- ipcRenderer.on(IPC.NOTIFICATION, listener);
40
- return () => { ipcRenderer.removeListener(IPC.NOTIFICATION, listener); };
41
- },
42
- onCwdChanged: (callback: (ptyId: string, cwd: string) => void) => {
43
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, cwd: string) =>
44
- callback(ptyId, cwd);
45
- ipcRenderer.on(IPC.CWD_CHANGED, listener);
46
- return () => { ipcRenderer.removeListener(IPC.CWD_CHANGED, listener); };
47
- },
48
- },
49
- metadata: {
50
- request: (ptyId: string) =>
51
- ipcRenderer.invoke(IPC.METADATA_REQUEST, ptyId),
52
- onUpdate: (callback: (ptyId: string, data: { gitBranch?: string; cwd?: string; listeningPorts?: number[] }) => void) => {
53
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, data: { gitBranch?: string; cwd?: string; listeningPorts?: number[] }) =>
54
- callback(ptyId, data);
55
- ipcRenderer.on(IPC.METADATA_UPDATE, listener);
56
- return () => { ipcRenderer.removeListener(IPC.METADATA_UPDATE, listener); };
57
- },
58
- },
59
- rpc: {
60
- onCommand: (
61
- callback: (requestId: string, method: string, params: Record<string, unknown>) => void,
62
- ) => {
63
- const listener = (
64
- _event: Electron.IpcRendererEvent,
65
- requestId: string,
66
- method: string,
67
- params: Record<string, unknown>,
68
- ) => callback(requestId, method, params);
69
- ipcRenderer.on(IPC.RPC_COMMAND, listener);
70
- return () => { ipcRenderer.removeListener(IPC.RPC_COMMAND, listener); };
71
- },
72
- respond: (requestId: string, result: unknown) =>
73
- ipcRenderer.send(`${IPC.RPC_RESPONSE}:${requestId}`, result),
74
- },
75
- updater: {
76
- checkForUpdates: () =>
77
- ipcRenderer.invoke(IPC.UPDATE_CHECK) as Promise<{ status: string }>,
78
- installUpdate: () =>
79
- ipcRenderer.invoke(IPC.UPDATE_INSTALL),
80
- onUpdateAvailable: (callback: (data: { status: string; releaseName?: string }) => void) => {
81
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string; releaseName?: string }) =>
82
- callback(data);
83
- ipcRenderer.on(IPC.UPDATE_AVAILABLE, listener);
84
- return () => { ipcRenderer.removeListener(IPC.UPDATE_AVAILABLE, listener); };
85
- },
86
- onUpdateNotAvailable: (callback: (data: { status: string }) => void) => {
87
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string }) =>
88
- callback(data);
89
- ipcRenderer.on(IPC.UPDATE_NOT_AVAILABLE, listener);
90
- return () => { ipcRenderer.removeListener(IPC.UPDATE_NOT_AVAILABLE, listener); };
91
- },
92
- onUpdateError: (callback: (data: { status: string; message: string }) => void) => {
93
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string; message: string }) =>
94
- callback(data);
95
- ipcRenderer.on(IPC.UPDATE_ERROR, listener);
96
- return () => { ipcRenderer.removeListener(IPC.UPDATE_ERROR, listener); };
97
- },
98
- },
99
- };
100
-
101
- contextBridge.exposeInMainWorld('electronAPI', electronAPI);
102
-
103
- contextBridge.exposeInMainWorld('clipboardAPI', {
104
- writeText: (text: string) => ipcRenderer.invoke(IPC.CLIPBOARD_WRITE, text),
105
- readText: () => ipcRenderer.invoke(IPC.CLIPBOARD_READ) as Promise<string>,
106
- });
107
-
108
- export type ElectronAPI = typeof electronAPI;
@@ -1,5 +0,0 @@
1
- import AppLayout from './components/Layout/AppLayout';
2
-
3
- export default function App() {
4
- return <AppLayout />;
5
- }
@@ -1,219 +0,0 @@
1
- import { useRef, useState, useEffect, useCallback } from 'react';
2
- import BrowserToolbar from './BrowserToolbar';
3
- import { useT } from '../../hooks/useT';
4
-
5
- // ---------------------------------------------------------------------------
6
- // Declare the webview element for TypeScript
7
- // ---------------------------------------------------------------------------
8
-
9
- declare global {
10
- // eslint-disable-next-line @typescript-eslint/no-namespace
11
- namespace JSX {
12
- interface IntrinsicElements {
13
- webview: React.DetailedHTMLProps<
14
- React.HTMLAttributes<Electron.WebviewTag> & {
15
- src?: string;
16
- partition?: string;
17
- allowpopups?: string;
18
- disablewebsecurity?: string;
19
- preload?: string;
20
- useragent?: string;
21
- nodeintegration?: string;
22
- webpreferences?: string;
23
- },
24
- Electron.WebviewTag
25
- >;
26
- }
27
- }
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // Props
32
- // ---------------------------------------------------------------------------
33
-
34
- interface BrowserPanelProps {
35
- surfaceId: string;
36
- initialUrl: string;
37
- isActive: boolean;
38
- onClose: () => void;
39
- }
40
-
41
- // ---------------------------------------------------------------------------
42
- // Component
43
- // ---------------------------------------------------------------------------
44
-
45
- export default function BrowserPanel({ surfaceId, initialUrl, isActive, onClose }: BrowserPanelProps) {
46
- const t = useT();
47
- const webviewRef = useRef<Electron.WebviewTag>(null);
48
- const [currentUrl, setCurrentUrl] = useState(initialUrl);
49
- const [isLoading, setIsLoading] = useState(false);
50
- const [canGoBack, setCanGoBack] = useState(false);
51
- const [canGoForward, setCanGoForward] = useState(false);
52
- const [pageTitle, setPageTitle] = useState(() => t('browser.title'));
53
- const [isReady, setIsReady] = useState(false);
54
-
55
- // Update nav state from webview
56
- const updateNavState = useCallback(() => {
57
- const wv = webviewRef.current;
58
- if (!wv) return;
59
- try {
60
- setCanGoBack(wv.canGoBack());
61
- setCanGoForward(wv.canGoForward());
62
- } catch {
63
- // Webview may not be ready yet
64
- }
65
- }, []);
66
-
67
- // Attach webview event listeners once ready
68
- useEffect(() => {
69
- const wv = webviewRef.current;
70
- if (!wv) return;
71
-
72
- const onDomReady = () => {
73
- setIsReady(true);
74
- updateNavState();
75
- };
76
-
77
- const onStartLoading = () => {
78
- setIsLoading(true);
79
- };
80
-
81
- const onStopLoading = () => {
82
- setIsLoading(false);
83
- updateNavState();
84
- };
85
-
86
- const onDidNavigate = (e: Electron.DidNavigateEvent) => {
87
- setCurrentUrl(e.url);
88
- updateNavState();
89
- };
90
-
91
- const onDidNavigateInPage = (e: Electron.DidNavigateInPageEvent) => {
92
- setCurrentUrl(e.url);
93
- updateNavState();
94
- };
95
-
96
- const onTitleUpdated = (e: Electron.PageTitleUpdatedEvent) => {
97
- setPageTitle(e.title || t('browser.title'));
98
- };
99
-
100
- wv.addEventListener('dom-ready', onDomReady);
101
- wv.addEventListener('did-start-loading', onStartLoading);
102
- wv.addEventListener('did-stop-loading', onStopLoading);
103
- wv.addEventListener('did-navigate', onDidNavigate as EventListener);
104
- wv.addEventListener('did-navigate-in-page', onDidNavigateInPage as EventListener);
105
- wv.addEventListener('page-title-updated', onTitleUpdated as EventListener);
106
-
107
- return () => {
108
- wv.removeEventListener('dom-ready', onDomReady);
109
- wv.removeEventListener('did-start-loading', onStartLoading);
110
- wv.removeEventListener('did-stop-loading', onStopLoading);
111
- wv.removeEventListener('did-navigate', onDidNavigate as EventListener);
112
- wv.removeEventListener('did-navigate-in-page', onDidNavigateInPage as EventListener);
113
- wv.removeEventListener('page-title-updated', onTitleUpdated as EventListener);
114
- };
115
- }, [updateNavState]);
116
-
117
- // F12 opens DevTools for the webview
118
- useEffect(() => {
119
- if (!isActive) return;
120
- const handler = (e: KeyboardEvent) => {
121
- if (e.key === 'F12') {
122
- e.preventDefault();
123
- handleOpenDevTools();
124
- }
125
- };
126
- window.addEventListener('keydown', handler);
127
- return () => window.removeEventListener('keydown', handler);
128
- }, [isActive]);
129
-
130
- const handleNavigate = useCallback((url: string) => {
131
- const wv = webviewRef.current;
132
- if (!wv) return;
133
- if (isReady) {
134
- wv.loadURL(url);
135
- } else {
136
- // If not ready yet, just update src attribute
137
- wv.setAttribute('src', url);
138
- }
139
- setCurrentUrl(url);
140
- }, [isReady]);
141
-
142
- const handleBack = useCallback(() => {
143
- webviewRef.current?.goBack();
144
- }, []);
145
-
146
- const handleForward = useCallback(() => {
147
- webviewRef.current?.goForward();
148
- }, []);
149
-
150
- const handleRefresh = useCallback(() => {
151
- webviewRef.current?.reload();
152
- }, []);
153
-
154
- const handleOpenDevTools = useCallback(() => {
155
- try {
156
- webviewRef.current?.openDevTools();
157
- } catch {
158
- // May not be available in all contexts
159
- }
160
- }, []);
161
-
162
- return (
163
- <div
164
- className="flex flex-col h-full w-full overflow-hidden"
165
- style={{
166
- position: 'absolute',
167
- inset: 0,
168
- display: isActive ? 'flex' : 'none',
169
- }}
170
- >
171
- {/* Title bar strip showing page title */}
172
- <div
173
- className="flex items-center gap-2 px-3 py-0.5 shrink-0"
174
- style={{ backgroundColor: '#11111b', borderBottom: '1px solid var(--bg-base)' }}
175
- >
176
- {isLoading && (
177
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--accent-blue)] animate-pulse shrink-0" />
178
- )}
179
- <span
180
- className="text-xs text-[var(--text-subtle)] truncate"
181
- style={{ fontFamily: 'ui-monospace, monospace' }}
182
- title={pageTitle}
183
- >
184
- {pageTitle}
185
- </span>
186
- </div>
187
-
188
- {/* Toolbar */}
189
- <BrowserToolbar
190
- currentUrl={currentUrl}
191
- isLoading={isLoading}
192
- canGoBack={canGoBack}
193
- canGoForward={canGoForward}
194
- isActive={isActive}
195
- onNavigate={handleNavigate}
196
- onBack={handleBack}
197
- onForward={handleForward}
198
- onRefresh={handleRefresh}
199
- onOpenDevTools={handleOpenDevTools}
200
- onClose={onClose}
201
- />
202
-
203
- {/* WebView */}
204
- <div className="flex-1 relative overflow-hidden" style={{ backgroundColor: 'var(--bg-base)' }}>
205
- <webview
206
- ref={webviewRef as React.RefObject<Electron.WebviewTag>}
207
- src={initialUrl}
208
- partition="persist:browser"
209
- data-surface-id={surfaceId}
210
- style={{
211
- width: '100%',
212
- height: '100%',
213
- display: 'flex',
214
- }}
215
- />
216
- </div>
217
- </div>
218
- );
219
- }
@@ -1,253 +0,0 @@
1
- import { useRef, useState, useCallback, useEffect } from 'react';
2
- import { useT } from '../../hooks/useT';
3
-
4
- // ---------------------------------------------------------------------------
5
- // SVG Icon components
6
- // ---------------------------------------------------------------------------
7
-
8
- function IconBack() {
9
- return (
10
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
11
- <polyline points="9,2 4,7 9,12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
12
- </svg>
13
- );
14
- }
15
-
16
- function IconForward() {
17
- return (
18
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
19
- <polyline points="5,2 10,7 5,12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
20
- </svg>
21
- );
22
- }
23
-
24
- function IconRefresh() {
25
- return (
26
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
27
- <path d="M12 7A5 5 0 1 1 7 2" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
28
- <polyline points="7,0.5 9.5,2.5 7,4.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
29
- </svg>
30
- );
31
- }
32
-
33
- function IconDevTools() {
34
- return (
35
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
36
- <rect x="1" y="1" width="12" height="12" rx="1.5" stroke="currentColor" strokeWidth="1.2" />
37
- <line x1="1" y1="4.5" x2="13" y2="4.5" stroke="currentColor" strokeWidth="1.2" />
38
- <polyline points="3.5,7 5.5,9 3.5,11" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
39
- <line x1="7" y1="11" x2="10.5" y2="11" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
40
- </svg>
41
- );
42
- }
43
-
44
- function IconClose() {
45
- return (
46
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
47
- <line x1="2" y1="2" x2="10" y2="10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
48
- <line x1="10" y1="2" x2="2" y2="10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
49
- </svg>
50
- );
51
- }
52
-
53
- function IconLock() {
54
- return (
55
- <svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
56
- <rect x="1.5" y="4.5" width="8" height="5.5" rx="1" stroke="currentColor" strokeWidth="1.2" />
57
- <path d="M3.5 4.5V3a2 2 0 0 1 4 0v1.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
58
- </svg>
59
- );
60
- }
61
-
62
- // ---------------------------------------------------------------------------
63
- // BrowserToolbar props
64
- // ---------------------------------------------------------------------------
65
-
66
- interface BrowserToolbarProps {
67
- currentUrl: string;
68
- isLoading: boolean;
69
- canGoBack: boolean;
70
- canGoForward: boolean;
71
- isActive: boolean;
72
- onNavigate: (url: string) => void;
73
- onBack: () => void;
74
- onForward: () => void;
75
- onRefresh: () => void;
76
- onOpenDevTools: () => void;
77
- onClose: () => void;
78
- }
79
-
80
- // ---------------------------------------------------------------------------
81
- // Component
82
- // ---------------------------------------------------------------------------
83
-
84
- export default function BrowserToolbar({
85
- currentUrl,
86
- isLoading,
87
- canGoBack,
88
- canGoForward,
89
- isActive,
90
- onNavigate,
91
- onBack,
92
- onForward,
93
- onRefresh,
94
- onOpenDevTools,
95
- onClose,
96
- }: BrowserToolbarProps) {
97
- const t = useT();
98
- const [inputValue, setInputValue] = useState(currentUrl);
99
- const [isFocused, setIsFocused] = useState(false);
100
- const inputRef = useRef<HTMLInputElement>(null);
101
-
102
- // Sync display URL when not focused
103
- useEffect(() => {
104
- if (!isFocused) {
105
- setInputValue(currentUrl);
106
- }
107
- }, [currentUrl, isFocused]);
108
-
109
- // Ctrl+L focuses the URL bar — only register when this browser panel is active
110
- useEffect(() => {
111
- if (!isActive) return;
112
- const handler = (e: KeyboardEvent) => {
113
- if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key === 'l') {
114
- e.preventDefault();
115
- inputRef.current?.focus();
116
- inputRef.current?.select();
117
- }
118
- };
119
- window.addEventListener('keydown', handler);
120
- return () => window.removeEventListener('keydown', handler);
121
- }, [isActive]);
122
-
123
- const handleSubmit = useCallback((e: React.FormEvent) => {
124
- e.preventDefault();
125
- const raw = inputValue.trim();
126
- if (!raw) return;
127
- // Normalize: add protocol if missing
128
- let url = raw;
129
- if (!/^https?:\/\//i.test(url) && !/^about:/i.test(url)) {
130
- // If it looks like a domain, add https://; otherwise treat as search
131
- if (/^[\w-]+(\.[\w-]+)+([\/?#].*)?$/.test(url)) {
132
- url = `https://${url}`;
133
- } else {
134
- url = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
135
- }
136
- }
137
- setInputValue(url);
138
- onNavigate(url);
139
- inputRef.current?.blur();
140
- }, [inputValue, onNavigate]);
141
-
142
- const isSecure = currentUrl.startsWith('https://');
143
-
144
- const btnBase = 'flex items-center justify-center w-6 h-6 rounded transition-colors duration-100';
145
- const btnEnabled = `${btnBase} text-[var(--text-sub2)] hover:text-[var(--text-main)] hover:bg-[var(--bg-surface)] cursor-pointer`;
146
- const btnDisabled = `${btnBase} text-[var(--bg-overlay)] cursor-default`;
147
-
148
- return (
149
- <div
150
- className="flex items-center gap-1.5 px-2 py-1.5 shrink-0"
151
- style={{ backgroundColor: 'var(--bg-mantle)', borderBottom: '1px solid var(--bg-surface)' }}
152
- >
153
- {/* Back */}
154
- <button
155
- className={canGoBack ? btnEnabled : btnDisabled}
156
- onClick={canGoBack ? onBack : undefined}
157
- title={t('browser.back')}
158
- tabIndex={-1}
159
- >
160
- <IconBack />
161
- </button>
162
-
163
- {/* Forward */}
164
- <button
165
- className={canGoForward ? btnEnabled : btnDisabled}
166
- onClick={canGoForward ? onForward : undefined}
167
- title={t('browser.forward')}
168
- tabIndex={-1}
169
- >
170
- <IconForward />
171
- </button>
172
-
173
- {/* Refresh */}
174
- <button
175
- className={btnEnabled}
176
- onClick={onRefresh}
177
- title={t('browser.reload')}
178
- tabIndex={-1}
179
- >
180
- <span className={isLoading ? 'animate-spin' : ''}>
181
- <IconRefresh />
182
- </span>
183
- </button>
184
-
185
- {/* URL bar */}
186
- <form className="flex-1 min-w-0" onSubmit={handleSubmit}>
187
- <div
188
- className="flex items-center gap-1.5 px-2.5 py-1 rounded-md"
189
- style={{
190
- backgroundColor: isFocused ? 'var(--bg-base)' : '#11111b',
191
- border: `1px solid ${isFocused ? 'var(--accent-blue)' : 'var(--bg-surface)'}`,
192
- transition: 'border-color 0.15s',
193
- }}
194
- >
195
- {/* Lock icon */}
196
- <span className={isSecure ? 'text-[var(--accent-green)]' : 'text-[var(--text-muted)]'} style={{ flexShrink: 0 }}>
197
- <IconLock />
198
- </span>
199
-
200
- {/* Loading indicator */}
201
- {isLoading && (
202
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--accent-blue)] animate-pulse shrink-0" />
203
- )}
204
-
205
- <input
206
- ref={inputRef}
207
- type="text"
208
- value={inputValue}
209
- onChange={(e) => setInputValue(e.target.value)}
210
- onFocus={() => {
211
- setIsFocused(true);
212
- inputRef.current?.select();
213
- }}
214
- onBlur={() => {
215
- setIsFocused(false);
216
- setInputValue(currentUrl);
217
- }}
218
- onKeyDown={(e) => {
219
- if (e.key === 'Escape') {
220
- setInputValue(currentUrl);
221
- inputRef.current?.blur();
222
- }
223
- }}
224
- className="flex-1 min-w-0 bg-transparent text-[var(--text-main)] text-xs outline-none"
225
- style={{ fontFamily: 'ui-monospace, monospace' }}
226
- spellCheck={false}
227
- autoComplete="off"
228
- />
229
- </div>
230
- </form>
231
-
232
- {/* DevTools */}
233
- <button
234
- className={btnEnabled}
235
- onClick={onOpenDevTools}
236
- title={t('browser.devToolsTooltip')}
237
- tabIndex={-1}
238
- >
239
- <IconDevTools />
240
- </button>
241
-
242
- {/* Close */}
243
- <button
244
- className={`${btnBase} text-[var(--text-sub2)] hover:text-[var(--accent-red)] hover:bg-[#3b1e1e] cursor-pointer`}
245
- onClick={onClose}
246
- title={t('browser.close')}
247
- tabIndex={-1}
248
- >
249
- <IconClose />
250
- </button>
251
- </div>
252
- );
253
- }
@@ -1,3 +0,0 @@
1
- export default function ApprovalDialog() {
2
- return null;
3
- }
@@ -1,7 +0,0 @@
1
- interface CompanyViewProps {
2
- onClose: () => void;
3
- }
4
-
5
- export default function CompanyView({ onClose }: CompanyViewProps) {
6
- return null;
7
- }
@@ -1,3 +0,0 @@
1
- export default function MessageFeedPanel() {
2
- return null;
3
- }