authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 13:23:58-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 13:49:04-08:00
logcefe64158697f05e863b7347bc2bf797a264c78b
treecb615900d4d7c776e7ee841f18ee7aa01586b1e2
parent664451c2962dcc550e5bea54bb9c76536e3e6c94

Remove unused prompt dialog infrastructure

The inline rename replaces the prompt dialog entirely, so remove the promptDialog.ts file, IPC channels, bridge methods, and related tests. Context menu improvements (destructive, position) are kept. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

6 files changed, 0 insertions(+), 295 deletions(-)

apps/desktop/src/main.ts-16
...@@ -10,13 +10,11 @@ import type { MenuItemConstructorOptions } from "electron";...@@ -10,13 +10,11 @@ import type { MenuItemConstructorOptions } from "electron";
1010
11import { showDesktopConfirmDialog } from "./confirmDialog";11import { showDesktopConfirmDialog } from "./confirmDialog";
12import { fixPath } from "./fixPath";12import { fixPath } from "./fixPath";
13import { showDesktopPromptDialog } from "./promptDialog";
1413
15fixPath();14fixPath();
1615
17const PICK_FOLDER_CHANNEL = "desktop:pick-folder";16const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
18const CONFIRM_CHANNEL = "desktop:confirm";17const CONFIRM_CHANNEL = "desktop:confirm";
19const PROMPT_CHANNEL = "desktop:prompt";
20const CONTEXT_MENU_CHANNEL = "desktop:context-menu";18const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
21const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";19const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
22const MENU_ACTION_CHANNEL = "desktop:menu-action";20const MENU_ACTION_CHANNEL = "desktop:menu-action";
...@@ -436,20 +434,6 @@ function registerIpcHandlers(): void {...@@ -436,20 +434,6 @@ function registerIpcHandlers(): void {
436 return showDesktopConfirmDialog(message, owner);434 return showDesktopConfirmDialog(message, owner);
437 });435 });
438436
439 ipcMain.removeHandler(PROMPT_CHANNEL);
440 ipcMain.handle(PROMPT_CHANNEL, async (_event, message: unknown, defaultValue: unknown) => {
441 if (typeof message !== "string") {
442 return null;
443 }
444
445 const owner = BrowserWindow.getFocusedWindow() ?? mainWindow;
446 return showDesktopPromptDialog(
447 message,
448 typeof defaultValue === "string" ? defaultValue : "",
449 owner,
450 );
451 });
452
453 ipcMain.removeHandler(CONTEXT_MENU_CHANNEL);437 ipcMain.removeHandler(CONTEXT_MENU_CHANNEL);
454 ipcMain.handle(438 ipcMain.handle(
455 CONTEXT_MENU_CHANNEL,439 CONTEXT_MENU_CHANNEL,
apps/desktop/src/preload.ts-2
...@@ -3,7 +3,6 @@ import type { DesktopBridge } from "@t3tools/contracts";...@@ -3,7 +3,6 @@ import type { DesktopBridge } from "@t3tools/contracts";
33
4const PICK_FOLDER_CHANNEL = "desktop:pick-folder";4const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
5const CONFIRM_CHANNEL = "desktop:confirm";5const CONFIRM_CHANNEL = "desktop:confirm";
6const PROMPT_CHANNEL = "desktop:prompt";
7const CONTEXT_MENU_CHANNEL = "desktop:context-menu";6const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
8const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";7const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
9const MENU_ACTION_CHANNEL = "desktop:menu-action";8const MENU_ACTION_CHANNEL = "desktop:menu-action";
...@@ -13,7 +12,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {...@@ -13,7 +12,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {
13 getWsUrl: () => wsUrl,12 getWsUrl: () => wsUrl,
14 pickFolder: () => ipcRenderer.invoke(PICK_FOLDER_CHANNEL),13 pickFolder: () => ipcRenderer.invoke(PICK_FOLDER_CHANNEL),
15 confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),14 confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),
16 prompt: (message, defaultValue) => ipcRenderer.invoke(PROMPT_CHANNEL, message, defaultValue),
17 showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position),15 showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position),
18 openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url),16 openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url),
19 onMenuAction: (listener) => {17 onMenuAction: (listener) => {
apps/desktop/src/promptDialog.ts deleted-231
...@@ -1,231 +0,0 @@
1import { randomBytes } from "node:crypto";
2import { BrowserWindow, ipcMain } from "electron";
3
4const PROMPT_WIDTH = 420;
5const PROMPT_HEIGHT = 188;
6
7function createPromptHtml(input: {
8 message: string;
9 defaultValue: string;
10 submitChannel: string;
11 cancelChannel: string;
12}): string {
13 const scriptConfig = JSON.stringify(input);
14 return `<!doctype html>
15<html lang="en">
16 <head>
17 <meta charset="UTF-8" />
18 <meta name="viewport" content="width=device-width,initial-scale=1.0" />
19 <title>${input.message}</title>
20 <style>
21 :root {
22 color-scheme: light dark;
23 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24 --bg: #fff;
25 --fg: #1a1a1a;
26 --muted: #6b6b6b;
27 --border: #d4d4d4;
28 --input-bg: #fafafa;
29 --focus-ring: rgba(59, 130, 246, 0.5);
30 --btn-bg: #f5f5f5;
31 --btn-hover: #e8e8e8;
32 --btn-primary-bg: #171717;
33 --btn-primary-fg: #fff;
34 --btn-primary-hover: #333;
35 }
36 @media (prefers-color-scheme: dark) {
37 :root {
38 --bg: #1e1e1e;
39 --fg: #e4e4e4;
40 --muted: #999;
41 --border: #3a3a3a;
42 --input-bg: #2a2a2a;
43 --focus-ring: rgba(96, 165, 250, 0.5);
44 --btn-bg: #333;
45 --btn-hover: #404040;
46 --btn-primary-bg: #e4e4e4;
47 --btn-primary-fg: #1a1a1a;
48 --btn-primary-hover: #d0d0d0;
49 }
50 }
51 * { box-sizing: border-box; margin: 0; }
52 body {
53 padding: 16px;
54 background: var(--bg);
55 color: var(--fg);
56 -webkit-font-smoothing: antialiased;
57 }
58 p {
59 margin-bottom: 10px;
60 font-size: 13px;
61 font-weight: 500;
62 }
63 input {
64 width: 100%;
65 padding: 7px 10px;
66 font-size: 13px;
67 font-family: inherit;
68 color: var(--fg);
69 background: var(--input-bg);
70 border: 1px solid var(--border);
71 border-radius: 6px;
72 outline: none;
73 transition: border-color 0.15s, box-shadow 0.15s;
74 }
75 input:focus {
76 border-color: var(--focus-ring);
77 box-shadow: 0 0 0 2px var(--focus-ring);
78 }
79 .buttons {
80 display: flex;
81 justify-content: flex-end;
82 gap: 8px;
83 margin-top: 14px;
84 }
85 button {
86 min-width: 72px;
87 padding: 6px 14px;
88 font-size: 12px;
89 font-family: inherit;
90 font-weight: 500;
91 border: 1px solid var(--border);
92 border-radius: 6px;
93 cursor: pointer;
94 background: var(--btn-bg);
95 color: var(--fg);
96 transition: background 0.15s;
97 }
98 button:hover { background: var(--btn-hover); }
99 button:active { opacity: 0.85; }
100 button[type="submit"] {
101 background: var(--btn-primary-bg);
102 color: var(--btn-primary-fg);
103 border-color: var(--btn-primary-bg);
104 }
105 button[type="submit"]:hover { background: var(--btn-primary-hover); }
106 </style>
107 </head>
108 <body>
109 <p id="message"></p>
110 <form id="form">
111 <input id="value" autocomplete="off" />
112 <div class="buttons">
113 <button id="cancel" type="button">Cancel</button>
114 <button type="submit">Save</button>
115 </div>
116 </form>
117 <script>
118 const { ipcRenderer } = require("electron");
119 const config = ${scriptConfig};
120 const message = document.getElementById("message");
121 const form = document.getElementById("form");
122 const valueInput = document.getElementById("value");
123 const cancelButton = document.getElementById("cancel");
124
125 message.textContent = config.message;
126 valueInput.value = config.defaultValue;
127
128 function cancel() {
129 ipcRenderer.send(config.cancelChannel);
130 window.close();
131 }
132
133 form.addEventListener("submit", (event) => {
134 event.preventDefault();
135 ipcRenderer.send(config.submitChannel, valueInput.value);
136 window.close();
137 });
138 cancelButton.addEventListener("click", cancel);
139 window.addEventListener("keydown", (event) => {
140 if (event.key === "Escape") {
141 event.preventDefault();
142 cancel();
143 }
144 });
145 window.addEventListener("DOMContentLoaded", () => {
146 valueInput.focus();
147 valueInput.select();
148 });
149 </script>
150 </body>
151</html>`;
152}
153
154export async function showDesktopPromptDialog(
155 message: string,
156 defaultValue: string,
157 ownerWindow: BrowserWindow | null,
158): Promise<string | null> {
159 const normalizedMessage = message.trim();
160 if (normalizedMessage.length === 0) {
161 return null;
162 }
163
164 const token = randomBytes(12).toString("hex");
165 const submitChannel = `desktop:prompt-dialog:submit:${token}`;
166 const cancelChannel = `desktop:prompt-dialog:cancel:${token}`;
167
168 const promptWindow = new BrowserWindow({
169 width: PROMPT_WIDTH,
170 height: PROMPT_HEIGHT,
171 useContentSize: true,
172 show: false,
173 resizable: false,
174 minimizable: false,
175 maximizable: false,
176 fullscreenable: false,
177 autoHideMenuBar: true,
178 title: normalizedMessage,
179 ...(ownerWindow ? { parent: ownerWindow } : {}),
180 modal: ownerWindow !== null,
181 webPreferences: {
182 nodeIntegration: true,
183 contextIsolation: false,
184 },
185 });
186 promptWindow.removeMenu();
187
188 return new Promise<string | null>((resolve) => {
189 let settled = false;
190
191 const settle = (value: string | null) => {
192 if (settled) return;
193 settled = true;
194 cleanup();
195 resolve(value);
196 };
197
198 const cleanup = () => {
199 ipcMain.removeAllListeners(submitChannel);
200 ipcMain.removeAllListeners(cancelChannel);
201 if (!promptWindow.isDestroyed()) {
202 promptWindow.destroy();
203 }
204 };
205
206 ipcMain.once(submitChannel, (_event, value: unknown) => {
207 if (typeof value !== "string") {
208 settle(null);
209 return;
210 }
211 settle(value);
212 });
213 ipcMain.once(cancelChannel, () => settle(null));
214 promptWindow.once("closed", () => settle(null));
215
216 const html = createPromptHtml({
217 message: normalizedMessage,
218 defaultValue,
219 submitChannel,
220 cancelChannel,
221 });
222 void promptWindow
223 .loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`)
224 .then(() => {
225 if (!promptWindow.isDestroyed()) {
226 promptWindow.show();
227 }
228 })
229 .catch(() => settle(null));
230 });
231}
apps/web/src/wsNativeApi.test.ts-34
...@@ -68,7 +68,6 @@ beforeEach(() => {...@@ -68,7 +68,6 @@ beforeEach(() => {
68 subscribeMock.mockClear();68 subscribeMock.mockClear();
69 channelListeners.clear();69 channelListeners.clear();
70 Reflect.deleteProperty(getWindowForTest(), "desktopBridge");70 Reflect.deleteProperty(getWindowForTest(), "desktopBridge");
71 Reflect.deleteProperty(getWindowForTest(), "prompt");
72});71});
7372
74afterEach(() => {73afterEach(() => {
...@@ -377,37 +376,4 @@ describe("wsNativeApi", () => {...@@ -377,37 +376,4 @@ describe("wsNativeApi", () => {
377 );376 );
378 });377 });
379378
380 it("forwards prompt calls to desktop bridge", async () => {
381 const prompt = vi.fn().mockResolvedValue("renamed");
382 Object.defineProperty(getWindowForTest(), "desktopBridge", {
383 configurable: true,
384 writable: true,
385 value: {
386 prompt,
387 },
388 });
389
390 const { createWsNativeApi } = await import("./wsNativeApi");
391 const api = createWsNativeApi();
392 const value = await api.dialogs.prompt("Rename thread", "Old title");
393
394 expect(value).toBe("renamed");
395 expect(prompt).toHaveBeenCalledWith("Rename thread", "Old title");
396 });
397
398 it("uses browser prompt fallback when desktop bridge is unavailable", async () => {
399 const prompt = vi.fn().mockReturnValue("renamed");
400 Object.defineProperty(getWindowForTest(), "prompt", {
401 configurable: true,
402 writable: true,
403 value: prompt,
404 });
405
406 const { createWsNativeApi } = await import("./wsNativeApi");
407 const api = createWsNativeApi();
408 const value = await api.dialogs.prompt("Rename thread", "Old title");
409
410 expect(value).toBe("renamed");
411 expect(prompt).toHaveBeenCalledWith("Rename thread", "Old title");
412 });
413});379});
apps/web/src/wsNativeApi.ts-10
...@@ -124,16 +124,6 @@ export function createWsNativeApi(): NativeApi {...@@ -124,16 +124,6 @@ export function createWsNativeApi(): NativeApi {
124 }124 }
125 return window.confirm(message);125 return window.confirm(message);
126 },126 },
127 prompt: async (message, defaultValue) => {
128 if (window.desktopBridge) {
129 return window.desktopBridge.prompt(message, defaultValue);
130 }
131 try {
132 return window.prompt(message, defaultValue ?? "");
133 } catch {
134 return null;
135 }
136 },
137 },127 },
138 terminal: {128 terminal: {
139 open: (input) => transport.request(WS_METHODS.terminalOpen, input),129 open: (input) => transport.request(WS_METHODS.terminalOpen, input),
packages/contracts/src/ipc.ts-2
...@@ -47,7 +47,6 @@ export interface DesktopBridge {...@@ -47,7 +47,6 @@ export interface DesktopBridge {
47 getWsUrl: () => string | null;47 getWsUrl: () => string | null;
48 pickFolder: () => Promise<string | null>;48 pickFolder: () => Promise<string | null>;
49 confirm: (message: string) => Promise<boolean>;49 confirm: (message: string) => Promise<boolean>;
50 prompt: (message: string, defaultValue?: string) => Promise<string | null>;
51 showContextMenu: <T extends string>(50 showContextMenu: <T extends string>(
52 items: readonly ContextMenuItem<T>[],51 items: readonly ContextMenuItem<T>[],
53 position?: { x: number; y: number },52 position?: { x: number; y: number },
...@@ -60,7 +59,6 @@ export interface NativeApi {...@@ -60,7 +59,6 @@ export interface NativeApi {
60 dialogs: {59 dialogs: {
61 pickFolder: () => Promise<string | null>;60 pickFolder: () => Promise<string | null>;
62 confirm: (message: string) => Promise<boolean>;61 confirm: (message: string) => Promise<boolean>;
63 prompt: (message: string, defaultValue?: string) => Promise<string | null>;
64 };62 };
65 terminal: {63 terminal: {
66 open: (input: TerminalOpenInput) => Promise<TerminalSessionSnapshot>;64 open: (input: TerminalOpenInput) => Promise<TerminalSessionSnapshot>;