From 9a6d422d591044f326e459fc5671eaac2874dd46 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 17 Sep 2026 16:55:07 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20merged=20title=20bar=20=E2=80=94=20drag?= =?UTF-8?q?=20bands,=20owner's=20mark,=20title-width=20rename=20hit-box?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The system title bar disappears (titleBarStyle hiddenInset + trafficLightPosition {16,24}): the app's own 60px header bands become the window's drag surface, and the traffic lights float over the sidebar's mark band, vertically centered on the same line as the chat header's phase pill (pixel-verified: circle centers y 24–36.5pt, pill box center 30pt). - electron/main.cjs: hiddenInset chrome; dev shell (LH_DEV_URL) gets its own userData (-dev suffix) so it never fights the packaged app's single-instance lock — verified: the lock silently killed the dev shell while the packaged app was open (that also proved the lock works across separately-launched builds). - app/globals.css: .lh-electron-gated utilities — drag/no-drag bands, 92px traffic-light gutter (.lh-lights-gutter / .lh-closed-toggle: lights end ~75pt + the lights' own 16px edge inset as the gap), home's floating strip becomes a drag surface only in the shell. Everything is inert in the plain browser. - sidebar.tsx: SidebarProvider sets post-mount (UA detection; no hydration mismatch, absent in browser); LH wordmark text replaced by the owner's mark (components/lh-mark.tsx, inlined icon-no-padding.svg, 24px, home link kept); SidebarToggle gains the 92px margin when closed so it never slides under the static lights. - chat.tsx: the rename hit-box shrinks to the title text itself — flex-1 spanned the whole bar, so every click started a rename and the window lost its grip. Shrink-to-fit + a flex-1 drag spacer (chips stay right) + hover:bg-accent affordance (clear when hover renames vs drags; -mx/px cancel for zero shift). The renaming input keeps flex-1 for edit room. - home.tsx / new-session.tsx / phase-divider.tsx: drag bands + carve-outs. Live-verified in the dev shell (pixel analysis) and headless browser: lights/pill/mark centered, 17pt lights→mark gap, rename click→select-all →Escape roundtrip, browser layout untouched. Gates: typecheck, eslint, 500/500 tests, build. --- app/globals.css | 36 +++++++++++++++++++++++++++++++ components/chat.tsx | 15 ++++++++----- components/home.tsx | 2 +- components/lh-mark.tsx | 42 ++++++++++++++++++++++++++++++++++++ components/new-session.tsx | 2 +- components/phase-divider.tsx | 2 +- components/sidebar.tsx | 41 +++++++++++++++++++++++++++++------ electron/main.cjs | 16 ++++++++++++++ 8 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 components/lh-mark.tsx diff --git a/app/globals.css b/app/globals.css index 7ad08e1..12b4b32 100644 --- a/app/globals.css +++ b/app/globals.css @@ -305,3 +305,39 @@ body { animation: none; } } + +/* ───────────────────────────────────────────────────────────────────────── + Electron merged chrome (owner, 2026-09-17). The system title bar is + hidden (titleBarStyle: "hiddenInset" in electron/main.cjs); the app's + own 60px header bands are the drag surface. Everything here is inert + in the plain browser — it keys off , which + SidebarProvider sets after mount when it detects the Electron shell. + + - .lh-drag window-drag region (must sit on pointer-receiving els) + - .lh-no-drag carve-out for interactive elements inside a drag band + - .lh-lights-gutter the sidebar LH band's traffic-light gutter: lights + end at x≈68, the wordmark starts at 80px (5rem) + - .lh-closed-toggle same gutter for the sidebar toggle when the sidebar + is closed — the lights are static, the toggle moves + - .lh-float-drag the home header strip floats pointer-events-none over + the graph; only in Electron does it claim events and + become the drag surface for that page + ───────────────────────────────────────────────────────────────────────── */ +.lh-electron .lh-drag { + -webkit-app-region: drag; +} +.lh-electron .lh-no-drag { + -webkit-app-region: no-drag; +} +/* 92px = measured lights' right edge (~75pt) + the lights' own 16px + inset from the window edge — comfortable, symmetric air (owner). */ +.lh-electron .lh-lights-gutter { + padding-left: 92px; +} +.lh-electron .lh-closed-toggle { + margin-left: 92px; +} +.lh-electron .lh-float-drag { + pointer-events: auto; + -webkit-app-region: drag; +} diff --git a/components/chat.tsx b/components/chat.tsx index 2594701..446119c 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -637,7 +637,7 @@ export function Chat({ 60px is an ALIGNMENT, not just a size: it equals the sidebar's LH wordmark band, so the header's border-b continues that band's bottom edge as one line across the app (owner, round 6). */} -
+
{renaming ? ( void commitRename(titleDraft)} spellCheck={false} aria-label="Session title" - className="min-w-0 flex-1 bg-transparent font-title text-3xl tracking-tight caret-current outline-none" + className="lh-no-drag min-w-0 flex-1 bg-transparent font-title text-3xl tracking-tight caret-current outline-none" /> ) : ( + <> + + {/* flex-1 spacer: keeps the chips at the right edge AND stays a + drag surface (no carve-out) — the freed bar is grippable. */} +