From fc36cc687466c718a1c842ceea526d2565fbed2d Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 26 May 2026 12:18:35 -0700 Subject: [PATCH 01/17] HtmlBlock: add vertical margin so blocks breathe in the chat stream --- app/src/renderer/hub/chat-v2/htmlBlock.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/renderer/hub/chat-v2/htmlBlock.css b/app/src/renderer/hub/chat-v2/htmlBlock.css index 3ff3fc6c..1abfc864 100644 --- a/app/src/renderer/hub/chat-v2/htmlBlock.css +++ b/app/src/renderer/hub/chat-v2/htmlBlock.css @@ -8,6 +8,7 @@ overflow: hidden; display: flex; flex-direction: column; + margin: 12px 0; } .chatv2-htmlblock--streaming { From 3aaa82e9a19dde2d667aefae607cb3593987b2ff Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 26 May 2026 12:19:30 -0700 Subject: [PATCH 02/17] shell:open-external IPC for opening http(s) URLs in the default browser --- app/src/main/index.ts | 11 +++++++++++ app/src/preload/shell.ts | 2 ++ app/src/renderer/globals.d.ts | 1 + 3 files changed, 14 insertions(+) diff --git a/app/src/main/index.ts b/app/src/main/index.ts index f261668c..51a97136 100644 --- a/app/src/main/index.ts +++ b/app/src/main/index.ts @@ -2113,6 +2113,17 @@ ipcMain.handle('shell:get-platform', () => { return process.platform; }); +// Open an absolute http(s) URL in the user's default browser. Used by the +// chat-v2 option picker's "View on {site}" button so the user can verify a +// source listing without leaving the app to switch tabs themselves. +ipcMain.handle('shell:open-external', async (_event, url: unknown) => { + if (typeof url !== 'string' || !/^https?:\/\//.test(url)) { + throw new Error('shell:open-external only accepts http(s) URLs'); + } + await shell.openExternal(url); + return { opened: true }; +}); + // Structured renderer log forwarding — see main/rendererLogIpc.ts and // renderer/shared/logger.ts. Registered alongside other preload-safe // channels so the bridge is ready before any window finishes loading. diff --git a/app/src/preload/shell.ts b/app/src/preload/shell.ts index 8cda0d26..cd8e84ba 100644 --- a/app/src/preload/shell.ts +++ b/app/src/preload/shell.ts @@ -41,6 +41,8 @@ contextBridge.exposeInMainWorld('electronAPI', { setOverlay: (active: boolean): void => { ipcRenderer.send('shell:set-overlay', active); }, + openExternal: (url: string): Promise<{ opened: boolean }> => + ipcRenderer.invoke('shell:open-external', url), }, pill: { toggle: (): Promise => ipcRenderer.invoke('pill:toggle'), diff --git a/app/src/renderer/globals.d.ts b/app/src/renderer/globals.d.ts index 7d7cb2b9..8500049b 100644 --- a/app/src/renderer/globals.d.ts +++ b/app/src/renderer/globals.d.ts @@ -204,6 +204,7 @@ interface ElectronShellAPI { platform: string; getPlatform: () => Promise; setOverlay: (active: boolean) => void; + openExternal: (url: string) => Promise<{ opened: boolean }>; } interface ElectronPillAPI { From 7109d7546347042542af07729afa24cee3a5c3a5 Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 26 May 2026 12:19:35 -0700 Subject: [PATCH 03/17] options block: require url+site, drop decorative images, default allowOther to false --- app/src/main/hl/engines/skillIndexPrompt.ts | 10 +- app/src/renderer/hub/chat-v2/htmlBlocks.ts | 40 ++++-- app/tests/unit/chat-v2/optionBlocks.test.ts | 144 +++++++++++++------- 3 files changed, 131 insertions(+), 63 deletions(-) diff --git a/app/src/main/hl/engines/skillIndexPrompt.ts b/app/src/main/hl/engines/skillIndexPrompt.ts index 3dbce394..8e760618 100644 --- a/app/src/main/hl/engines/skillIndexPrompt.ts +++ b/app/src/main/hl/engines/skillIndexPrompt.ts @@ -55,7 +55,8 @@ export function htmlBlockGuidanceLines(theme: 'light' | 'dark' = 'dark'): string return [ `UI THEME: ${theme}. When you emit a \`\`\`html block, use the active palette below. The full per-theme reference lives in the 'neobrutalist-html' interaction skill.`, `Active palette — card bg ${p.cardBg}, border ${p.border}, shadow ${p.shadow}, foreground ${p.fg}. Accents: ${p.accents}. Pick one bold accent + one secondary per artifact.`, - 'HTML blocks are an optional output channel — use them when layout helps the reader (plans, comparisons, status, timelines, diffs). Conversational replies, tool previews, and short answers should stay as plain markdown.', + 'HTML blocks are an optional output channel — use them when layout helps the reader (plans, comparisons, status, timelines, diffs). Also use them for dense, easily organized browser results or confirmations: shopping/cart/order summaries, delivery windows, addresses, prices, quantities, retailer/site names, reservation details, selected items, and next-step choices. Conversational replies, tool previews, and short answers should stay as plain markdown.', + 'Rule of thumb: if you have 3+ concrete facts from the page that naturally fit labeled rows, columns, cards, or a receipt-style summary, emit a compact ```html block instead of burying them in a paragraph.', 'When you emit an HTML block, keep it self-contained: inline styles or a single inline