From 5f3c84c924387c6bdf8b1bf2726df04e2e29b66b Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:33:26 +0200 Subject: [PATCH] feat: the pop-out title bar, done well, and a sidebar that answers sooner With vertical tabs and the title bar off, the terminal has the whole height and a bar drops over it within 16 px of the top, from the open sidebar's edge to the right, with the window buttons; it moves the window while down. The first pop-out bar could not be dragged because it slid with a CSS transform and Windows takes drag regions from the layout (measured with WM_NCHITTEST); it now slides by top, and the collapsed sidebar by left. The sidebar shows within 24 px of the left edge and both open after 60 ms. A strip above the tabs moves the window. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA --- .../specs/2026-10-11-vertical-tabs-design.md | 17 ++++ package-lock.json | 4 +- package.json | 2 +- src/main/edgeWatch.test.ts | 6 +- src/main/edgeWatch.ts | 6 +- src/renderer/src/App.tsx | 39 ++++---- src/renderer/src/components/HoverTitleBar.tsx | 62 +++++++++++++ src/renderer/src/components/TabSidebar.tsx | 14 ++- src/renderer/src/lib/hoverReveal.test.ts | 4 +- src/renderer/src/lib/hoverReveal.ts | 10 +- tools/e2e/run.mjs | 92 +++++++++++-------- 11 files changed, 181 insertions(+), 75 deletions(-) create mode 100644 src/renderer/src/components/HoverTitleBar.tsx diff --git a/docs/superpowers/specs/2026-10-11-vertical-tabs-design.md b/docs/superpowers/specs/2026-10-11-vertical-tabs-design.md index 4c9cce3..b6d4f6c 100644 --- a/docs/superpowers/specs/2026-10-11-vertical-tabs-design.md +++ b/docs/superpowers/specs/2026-10-11-vertical-tabs-design.md @@ -63,6 +63,23 @@ looked at on the way). holds the window buttons and moves the window. The sidebar's empty space no longer does. With the sidebar collapsed the row spans the window. +- **Final, the same night: the pop-out bar, done well** (owner: "a pop out title bar is best actually + we just need to do it well ... like zen browser's title bar ... when the sidebar is open the + titlebar drops down but only from the sidebar edge and all the way to the right ... make sure the + titlebar area is usable for content"; his screen recording of Zen, `Videos/2026-10-11 23-26-24.mp4`). + The terminal has the whole height; the bar drops over it within 16 px of the top, from the open + sidebar's edge to the right (the whole width when it is collapsed), with only the window buttons, + and moves the window while down. The clear bar is gone. + - **Why the first pop-out bar could not be dragged** (measured with WM_NCHITTEST): it slid with a CSS + transform, and Windows is told where a drag region is from the layout, so the region stayed where + the slide started, above the window. It now slides by `top`; the collapsed sidebar slides by + `left` for the same reason. + - The sidebar shows within 24 px of the left edge (was 8), the bar within 16 px of the top, both + after 60 ms (was 90) ("it reacts a bit too late ... could be better if it was like 20px further + into the window"). + - A 36 px strip above the tabs moves the window ("the tabs should start a bit further down on the + sidebar so that you can grab that area"). + ## 2. The indicators - **Vertical tabs have no new look for the indicators** (owner: "vertical tabs shouldnt have a new diff --git a/package-lock.json b/package-lock.json index c634d2b..0c7b6e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prism-terminal", - "version": "0.39.5", + "version": "0.39.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prism-terminal", - "version": "0.39.5", + "version": "0.39.6", "license": "MIT", "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index 76294ad..2c9cd91 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "prism-terminal", "productName": "Prism Terminal", - "version": "0.39.5", + "version": "0.39.6", "description": "A tabbed Windows terminal for AI CLIs.", "main": "./out/main/index.js", "author": "Max", diff --git a/src/main/edgeWatch.test.ts b/src/main/edgeWatch.test.ts index 01eaf23..d2bdf52 100644 --- a/src/main/edgeWatch.test.ts +++ b/src/main/edgeWatch.test.ts @@ -6,9 +6,9 @@ const box = { x: 100, y: 200, width: 800, height: 600 } describe('the pointer at the window edge (#162)', () => { it('is at the top inside the content band, and in the resize border above it', () => { expect(edgeNear({ x: 500, y: 200 }, box).top).toBe(true) - expect(edgeNear({ x: 500, y: 200 + EDGE.inside - 1 }, box).top).toBe(true) + expect(edgeNear({ x: 500, y: 200 + EDGE.top - 1 }, box).top).toBe(true) expect(edgeNear({ x: 500, y: 200 - EDGE.outside }, box).top).toBe(true) - expect(edgeNear({ x: 500, y: 200 + EDGE.inside }, box).top).toBe(false) + expect(edgeNear({ x: 500, y: 200 + EDGE.top }, box).top).toBe(false) expect(edgeNear({ x: 500, y: 200 - EDGE.outside - 1 }, box).top).toBe(false) }) @@ -20,7 +20,7 @@ describe('the pointer at the window edge (#162)', () => { it('is at the left edge the same way, along the window only', () => { expect(edgeNear({ x: 100, y: 500 }, box).left).toBe(true) expect(edgeNear({ x: 100 - EDGE.outside, y: 500 }, box).left).toBe(true) - expect(edgeNear({ x: 100 + EDGE.inside, y: 500 }, box).left).toBe(false) + expect(edgeNear({ x: 100 + EDGE.left, y: 500 }, box).left).toBe(false) expect(edgeNear({ x: 100, y: 800 + EDGE.outside }, box).left).toBe(false) }) diff --git a/src/main/edgeWatch.ts b/src/main/edgeWatch.ts index 95bec0f..a366bcf 100644 --- a/src/main/edgeWatch.ts +++ b/src/main/edgeWatch.ts @@ -17,7 +17,7 @@ import type { BrowserWindow, Point, Rectangle } from 'electron' /** How far inside the content edge counts as "at" it, and how far outside it * (the resize border's own band), in DIPs. */ -export const EDGE = { inside: 10, outside: 8, everyMs: 60 } as const +export const EDGE = { top: 16, left: 24, outside: 8, everyMs: 50 } as const export interface EdgeNear { top: boolean @@ -35,8 +35,8 @@ export function edgeNear(cursor: Point, content: Rectangle, zone = EDGE): EdgeNe const across = x >= -zone.outside && x < content.width + zone.outside const down = y >= -zone.outside && y < content.height + zone.outside return { - top: across && y >= -zone.outside && y < zone.inside, - left: down && x >= -zone.outside && x < zone.inside, + top: across && y >= -zone.outside && y < zone.top, + left: down && x >= -zone.outside && x < zone.left, x, y } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index d7ef133..1e9fc73 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -13,6 +13,8 @@ import TermFind from '@core/renderer/components/TermFind' import { TabStrip } from './components/TabStrip' import { TabSidebar } from './components/TabSidebar' import { useTabLayout } from './lib/tabLayoutPrefs' +import { useSidebarMode, useSidebarWidth } from './lib/sidebarPrefs' +import { HoverTitleBar } from './components/HoverTitleBar' import TitleBar, { AppButtons, TitleButtons, WindowButtons } from './components/TitleBar' import { useTitleBarMode } from './lib/titleBarPrefs' import { tabSwitch } from './lib/tabSwitchPrefs' @@ -165,6 +167,8 @@ export default function App(): JSX.Element { // the window buttons float at the top right. With no tab open there is no // sidebar, so the window keeps #91's top row to be moved by. const bare = vertical && titleBar === 'hidden' && state.tabs.length > 0 + const sidebarOpen = useSidebarMode() === 'open' + const sidebarWidth = useSidebarWidth() /** Which shell each tab was SPAWNED with. The Shell setting only names what * the next terminal launches, so a tab opened before it was changed still * speaks its old language, and the popup preselects by what is running. */ @@ -722,25 +726,6 @@ export default function App(): JSX.Element { /> )}
- {/* A TITLE BAR YOU CANNOT SEE (owner, 2026-10-11: "there should be a - title bar to drag from not the sidebar. how about making it - transparent"): with the tabs down the side and the title bar off, - the row over the terminal is the terminal's own ground, no line, no - name, and moves the window; the three window buttons (and the update - chip, when there is one) sit at its right. Over the terminal only, - so the sidebar keeps its tabs at the top; with the sidebar collapsed - that is the whole width. */} - {bare && ( -
- {chip} -
- -
-
- )}
+ {/* THE TITLE BAR THAT POPS OUT (owner, 2026-10-11: "a pop out title bar + is best actually we just need to do it well ... like zen browser's + title bar ... when the sidebar is open the titlebar drops down but + only from the sidebar edge and all the way to the right"). With the + tabs down the side and the title bar off, the terminal has the whole + height ("the titlebar area is usable for content"); the bar drops + over it at the top edge with the window's buttons, and moves the + window while it is down. */} + {bare && ( + + {chip} +
+ +
+
+ )} {termMenu && activeShell && termMenu.id === activeShell.id && (