From c8609596314f6a546ff690e5e87247d795598938 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 29 Apr 2026 11:05:10 -0400 Subject: [PATCH] =?UTF-8?q?Min=20width=20=3D=201=20column,=20add=20header?= =?UTF-8?q?=20minimize,=20Claude=20=E2=86=92=20PowerShell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Width: drop the header-fits-too floor; popup width is now max(COLUMN_WIDTH, columns-required, tabs-required). The brand title in the header is allowed to ellipsize at the 216px minimum (full name preserved in tray hover and h1 title attr). - Minimize: new -/▢ button in the header next to pin. Toggling hides the tab strip + body, locks width at COLUMN_WIDTH, and lets the existing ResizeObserver collapse height to just the header. State persists in config.minimized so reopening the popup remembers it. Add-column + button is hidden while minimized. - Claude launcher: now spawns powershell.exe -NoExit -Command claude in the wt new-tab (was cmd.exe /K claude). Cmd-fallback updated to also start powershell directly when the trailing args target it. --- electron/launchers.js | Bin 10671 -> 11234 bytes electron/main.js | 10 +++++ electron/preload.js | 1 + src/App.jsx | 11 +++++ src/components/Popup.jsx | 89 ++++++++++++++++----------------------- 5 files changed, 59 insertions(+), 52 deletions(-) diff --git a/electron/launchers.js b/electron/launchers.js index 1029c1bbe538412c93123995f8e877eb7f1f3d80..d65891d41828f3b373e7a447beefe8b52c43864c 100644 GIT binary patch delta 543 zcmZ8eJxc>Y6s!~ss6j;$L`G3^fq1d?;|d8DHXLbQ1-@4cCMGy8n}b^7vHy=yir%_dG1OS4q8!BktjLP&+qDK^Bn zkzT?ibOYmtWRS~}lujtiLb@gdg(i**H%`b?wK7(4f~z7u+Fu*BoeI3s;2*kk=Dcr@m_7sTh(@K3V48u|4bI)Mk zrWWk(T4nWTvoS;ioD6euxHOF_sNq+8Ym4@MV%jdRP6R-|?(t42Ni!Z6LeXU~Y$-B@ z5`Nr&{jA=PEo~gi{p-}=iYOmsH4?2Y=7U+c>V9GnXUqf?F>YVxd+Yx^H&7c=)%>$1 NI~^wY>()Yd<{Ofywdw!> delta 70 zcmaD9zCL)vY=OzCf{vSC3w5#z=zA+ { activeTabId: getActiveTabId(), buttonVisibility: getButtonVisibility(), autoStart: getAutoStart(), + minimized: !!cfg.minimized, }; }); @@ -712,6 +715,13 @@ ipcMain.handle('run-redeploy', (_event, p) => launchers.runRedeploy(p)); ipcMain.handle('copy-path', (_event, text) => launchers.copyPath(text)); ipcMain.handle('is-claude-available', () => launchers.isClaudeAvailable()); +ipcMain.handle('set-minimized', (_event, value) => { + const next = !!value; + writeConfig({ minimized: next }); + broadcastConfig(); + return next; +}); + ipcMain.handle('set-pinned', (_event, value) => { pinned = !!value; writeConfig({ pinned }); diff --git a/electron/preload.js b/electron/preload.js index 77987d1..550ef21 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -40,6 +40,7 @@ contextBridge.exposeInMainWorld('bookmarks', { // Window setPinned: (value) => ipcRenderer.invoke('set-pinned', value), + setMinimized: (value) => ipcRenderer.invoke('set-minimized', value), setPopupHeight: (height) => ipcRenderer.invoke('set-popup-height', height), setPopupWidth: (width) => ipcRenderer.invoke('set-popup-width', width), diff --git a/src/App.jsx b/src/App.jsx index 1d8e46a..2b12b7a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ export default function App() { const [columns, setColumns] = useState([]); const [bookmarks, setBookmarks] = useState([]); const [pinned, setPinned] = useState(false); + const [minimized, setMinimized] = useState(false); const [loading, setLoading] = useState(true); const [claudeAvailable, setClaudeAvailable] = useState(true); const [buttonVisibility, setButtonVisibility] = useState({ claude: true, terminal: true, redeploy: true }); @@ -26,6 +27,7 @@ export default function App() { setColumns(Array.isArray(cfg.columns) ? cfg.columns : []); setBookmarks(Array.isArray(cfg.bookmarks) ? cfg.bookmarks : []); setPinned(!!cfg.pinned); + setMinimized(!!cfg.minimized); setClaudeAvailable(!!hasClaude); if (cfg.buttonVisibility) { setButtonVisibility({ @@ -56,6 +58,7 @@ export default function App() { }); } if (typeof payload.autoStart === 'boolean') setAutoStart(payload.autoStart); + if (typeof payload.minimized === 'boolean') setMinimized(payload.minimized); }); const offShown = window.bookmarks.onPopupShown(() => { setInspectRevision((r) => r + 1); @@ -159,6 +162,12 @@ export default function App() { setPinned(!!next); }, [pinned]); + const handleToggleMinimized = useCallback(async () => { + setMinimized((prev) => !prev); + const next = await window.bookmarks.setMinimized(!minimized); + if (typeof next === 'boolean') setMinimized(next); + }, [minimized]); + const handleSetButtonVisibility = useCallback(async (patch) => { // Optimistic update so the checkbox flips immediately even before main responds. setButtonVisibility((prev) => ({ ...prev, ...patch })); @@ -211,6 +220,8 @@ export default function App() { onSetButtonVisibility={handleSetButtonVisibility} autoStart={autoStart} onSetAutoStart={handleSetAutoStart} + minimized={minimized} + onToggleMinimized={handleToggleMinimized} onTogglePin={handleTogglePin} onAdd={handleAdd} onEdit={handleEdit} diff --git a/src/components/Popup.jsx b/src/components/Popup.jsx index dbed37b..7ce7e30 100644 --- a/src/components/Popup.jsx +++ b/src/components/Popup.jsx @@ -22,6 +22,8 @@ export default function Popup({ onSetButtonVisibility, autoStart, onSetAutoStart, + minimized, + onToggleMinimized, onTogglePin, onAdd, onEdit, @@ -42,14 +44,11 @@ export default function Popup({ const heightDebounceRef = useRef(null); const settingsRef = useRef(null); const tabStripRef = useRef(null); - const brandRef = useRef(null); - const actionsRef = useRef(null); const [creatingColumn, setCreatingColumn] = useState(false); const [draftColumnName, setDraftColumnName] = useState(''); const [settingsOpen, setSettingsOpen] = useState(false); const [confirmRequest, setConfirmRequest] = useState(null); const [tabStripScrollWidth, setTabStripScrollWidth] = useState(0); - const [headerMinWidth, setHeaderMinWidth] = useState(0); const requestConfirm = useCallback((opts) => { setConfirmRequest(opts); @@ -90,34 +89,6 @@ export default function Popup({ }; }, []); - // Measure the popup header's intrinsic width so the brand never gets - // ellipsized. The h1 has overflow:hidden + text-overflow:ellipsis to keep - // it from forcing the popup wider than its content; we ask for that width - // explicitly through scrollWidth (which reports the full text width even - // when the element is clipped). - useEffect(() => { - if (!brandRef.current || !actionsRef.current) return undefined; - const update = () => { - const brand = brandRef.current; - const actions = actionsRef.current; - if (!brand || !actions) return; - const iconEl = brand.querySelector('img'); - const titleEl = brand.querySelector('h1'); - const iconW = iconEl ? iconEl.offsetWidth : 0; - const titleW = titleEl ? titleEl.scrollWidth : 0; - const brandW = iconW + (titleW > 0 ? 8 + titleW : 0); - const actionsW = actions.scrollWidth; - // 28px = popup-inner horizontal padding (14 each side); 8px = gap - // between brand and actions inside popup-header. - setHeaderMinWidth(brandW + 8 + actionsW + 28); - }; - update(); - const ro = new ResizeObserver(update); - ro.observe(brandRef.current); - ro.observe(actionsRef.current); - return () => ro.disconnect(); - }, []); - // Watch the tab strip's scrollWidth so renames or new tabs widen the popup. // ResizeObserver picks up both layout changes (window width changes) and // content changes (a tab rename growing the strip). @@ -142,10 +113,17 @@ export default function Popup({ // to the screen's work area, so wide tab strips either fit (most monitors) // or scroll horizontally inside the popup once they would push it off-screen. useEffect(() => { + if (minimized) { + window.bookmarks.setPopupWidth(COLUMN_WIDTH); + return; + } const colsWidth = Math.max(1, visibleColumns.length || 1) * COLUMN_WIDTH; const tabsWidth = tabStripScrollWidth > 0 ? tabStripScrollWidth + TAB_STRIP_PADDING : 0; - window.bookmarks.setPopupWidth(Math.max(colsWidth, tabsWidth, headerMinWidth)); - }, [visibleColumns.length, tabStripScrollWidth, headerMinWidth]); + // Floor at COLUMN_WIDTH (one column) — the long brand title in the header + // is allowed to ellipsize when the popup is at this minimum; the full + // name is preserved in the tray hover. + window.bookmarks.setPopupWidth(Math.max(COLUMN_WIDTH, colsWidth, tabsWidth)); + }, [visibleColumns.length, tabStripScrollWidth, minimized]); const submitNewColumn = async () => { const name = draftColumnName.trim(); @@ -184,19 +162,19 @@ export default function Popup({
-
+
-

Trailhead Path Bookmarker

+

Trailhead Path Bookmarker

-
- {!creatingColumn ? ( +
+ {!minimized && !creatingColumn ? ( - ) : ( + ) : !minimized && creatingColumn ? (
×
- )} + ) : null} +
- + {!minimized && ( + + )} - {hasNoTabs ? ( + {minimized ? null : hasNoTabs ? (
No tabs yet — click + in the tab bar to create your first.