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";
1010
1111import { showDesktopConfirmDialog } from "./confirmDialog";
1212import { fixPath } from "./fixPath";
13import { showDesktopPromptDialog } from "./promptDialog";
1413
1514fixPath();
1615
1716const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
1817const CONFIRM_CHANNEL = "desktop:confirm";
19const PROMPT_CHANNEL = "desktop:prompt";
2018const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
2119const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
2220const MENU_ACTION_CHANNEL = "desktop:menu-action";
......@@ -436,20 +434,6 @@ function registerIpcHandlers(): void {
436434 return showDesktopConfirmDialog(message, owner);
437435 });
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
453437 ipcMain.removeHandler(CONTEXT_MENU_CHANNEL);
454438 ipcMain.handle(
455439 CONTEXT_MENU_CHANNEL,
apps/desktop/src/preload.ts-2
......@@ -3,7 +3,6 @@ import type { DesktopBridge } from "@t3tools/contracts";
33
44const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
55const CONFIRM_CHANNEL = "desktop:confirm";
6const PROMPT_CHANNEL = "desktop:prompt";
76const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
87const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
98const MENU_ACTION_CHANNEL = "desktop:menu-action";
......@@ -13,7 +12,6 @@ contextBridge.exposeInMainWorld("desktopBridge", {
1312 getWsUrl: () => wsUrl,
1413 pickFolder: () => ipcRenderer.invoke(PICK_FOLDER_CHANNEL),
1514 confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),
16 prompt: (message, defaultValue) => ipcRenderer.invoke(PROMPT_CHANNEL, message, defaultValue),
1715 showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position),
1816 openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url),
1917 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(() => {
6868 subscribeMock.mockClear();
6969 channelListeners.clear();
7070 Reflect.deleteProperty(getWindowForTest(), "desktopBridge");
71 Reflect.deleteProperty(getWindowForTest(), "prompt");
7271});
7372
7473afterEach(() => {
......@@ -377,37 +376,4 @@ describe("wsNativeApi", () => {
377376 );
378377 });
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 });
413379});
apps/web/src/wsNativeApi.ts-10
......@@ -124,16 +124,6 @@ export function createWsNativeApi(): NativeApi {
124124 }
125125 return window.confirm(message);
126126 },
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 },
137127 },
138128 terminal: {
139129 open: (input) => transport.request(WS_METHODS.terminalOpen, input),
packages/contracts/src/ipc.ts-2
......@@ -47,7 +47,6 @@ export interface DesktopBridge {
4747 getWsUrl: () => string | null;
4848 pickFolder: () => Promise<string | null>;
4949 confirm: (message: string) => Promise<boolean>;
50 prompt: (message: string, defaultValue?: string) => Promise<string | null>;
5150 showContextMenu: <T extends string>(
5251 items: readonly ContextMenuItem<T>[],
5352 position?: { x: number; y: number },
......@@ -60,7 +59,6 @@ export interface NativeApi {
6059 dialogs: {
6160 pickFolder: () => Promise<string | null>;
6261 confirm: (message: string) => Promise<boolean>;
63 prompt: (message: string, defaultValue?: string) => Promise<string | null>;
6462 };
6563 terminal: {
6664 open: (input: TerminalOpenInput) => Promise<TerminalSessionSnapshot>;