Drop the title bar; drag the window by the nav

The page now runs to the very top of the window: titleBarStyle Overlay
gives the full size content view, so there is no reserved band above the
apps and the nav reaches the top edge with the traffic lights floating
over it.

That costs the old drag strip. A native webview swallows mouse events,
so the shell cannot offer a drag region anywhere over a page — once the
page starts at y=0 the only surface left is the nav.

So the nav becomes the handle, all of it, at either width. Every
container carries the attribute and only the buttons do not, which keeps
them clickable: a drag region is decided by the element under the
cursor. The scrolling app list is included, and loses nothing by it —
mousedown is all a drag region takes, never the wheel or a trackpad
swipe.

Verified by dragging from bare points in both the collapsed rail and the
expanded panel: the window follows the delta exactly, and clicking an
app still switches to it rather than moving the window.
This commit is contained in:
2026-09-03 22:19:17 +02:00
parent 9d69038cac
commit 9aac28c0cf
3 changed files with 51 additions and 19 deletions
+2 -1
View File
@@ -19,7 +19,8 @@
"minWidth": 900,
"minHeight": 600,
"center": true,
"hiddenTitle": true
"hiddenTitle": true,
"titleBarStyle": "Overlay"
}
],
"security": {
+6 -2
View File
@@ -265,9 +265,13 @@ export default function App() {
/>
{/* The hole an app's native webview is positioned into. It stays empty
on purpose — anything drawn here would be painted over. */}
on purpose — anything drawn here would be painted over.
It runs to the very top of the window: there is no title bar to sit
under. Nothing draggable can go here either, for the same reason the
hole is empty — the webview would swallow it. Dragging lives in the
nav, which is the one column the shell still owns. */}
<div className="flex min-w-0 flex-1 flex-col">
<div data-tauri-drag-region className="shrink-0" style={{ height: chrome }} />
<div
ref={stageRef}
className="relative min-h-0 flex-1 overflow-hidden bg-slate-100 dark:bg-slate-950"
+43 -16
View File
@@ -127,34 +127,52 @@ export default function Nav({
};
return (
<aside className={`${shell} items-center`} style={{ width: rail }}>
<aside
data-tauri-drag-region
className={`${shell} items-center`}
style={{ width: rail }}
>
{/* With no title bar left, the nav is the only surface the shell
still owns, so the whole of it drags the window — every container
here carries the attribute, and only the buttons do not. Costs
nothing on the scrolling list: a drag region intercepts mousedown,
never the wheel or a trackpad swipe. */}
<div data-tauri-drag-region className="w-full shrink-0" style={{ height: chrome }} />
{/* Only the expander survives the rail's header. Back and forward are
a two-finger swipe and reload is ⌘R, so a toolbar here would be
clutter standing in for something nobody asked for. */}
<div className="flex w-full flex-col items-center border-b border-slate-200 py-2.5 dark:border-slate-800">
<div
data-tauri-drag-region
className="flex w-full flex-col items-center border-b border-slate-200 py-2.5 dark:border-slate-800"
>
<button onClick={onToggleCollapse} title="Expand" className={ICON_CHROME}>
<Collapse open={false} />
</button>
</div>
<nav className="flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto py-3">
<nav
data-tauri-drag-region
className="flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto py-3"
>
{ungrouped.map(railBtn)}
{ungrouped.length > 0 && groups.length > 0 && (
<span className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />
<span data-tauri-drag-region className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />
)}
{groups.map((g, i) => {
const apps = inGroup(g.id);
if (apps.length === 0) return null;
return (
<div key={g.id} className="flex flex-col items-center gap-1">
{i > 0 && <span className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />}
<div key={g.id} data-tauri-drag-region className="flex flex-col items-center gap-1">
{i > 0 && <span data-tauri-drag-region className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />}
{apps.map(railBtn)}
</div>
);
})}
</nav>
<Downloads items={downloads} collapsed onDismiss={onDismissDownload} />
<div className="flex w-full flex-col items-center border-t border-slate-200 py-2 dark:border-slate-800">
<div
data-tauri-drag-region
className="flex w-full flex-col items-center border-t border-slate-200 py-2 dark:border-slate-800"
>
{settingsButton}
</div>
</aside>
@@ -169,7 +187,7 @@ export default function Nav({
const active = "bg-slate-900 text-white dark:bg-white dark:text-slate-900";
const appRow = (app: WorkApp) => (
<li key={app.id}>
<li key={app.id} data-tauri-drag-region>
<button
onClick={() => onSelect(app.id)}
title={app.url}
@@ -183,9 +201,12 @@ export default function Nav({
);
return (
<aside className={shell} style={{ width: PANEL }}>
<aside data-tauri-drag-region className={shell} style={{ width: PANEL }}>
<div data-tauri-drag-region className="shrink-0" style={{ height: chrome }} />
<header className="flex items-center gap-0.5 border-b border-slate-200 px-1.5 py-2.5 dark:border-slate-800">
<header
data-tauri-drag-region
className="flex items-center gap-0.5 border-b border-slate-200 px-1.5 py-2.5 dark:border-slate-800"
>
{controls}
<button
onClick={onToggleCollapse}
@@ -196,13 +217,13 @@ export default function Nav({
</button>
</header>
<nav className="min-h-0 flex-1 overflow-y-auto px-2 py-3">
{ungrouped.length > 0 && <ul className="space-y-0.5">{ungrouped.map(appRow)}</ul>}
<nav data-tauri-drag-region className="min-h-0 flex-1 overflow-y-auto px-2 py-3">
{ungrouped.length > 0 && <ul data-tauri-drag-region className="space-y-0.5">{ungrouped.map(appRow)}</ul>}
{groups.map((g) => {
const apps = inGroup(g.id);
return (
<section key={g.id} className="pt-3 first:pt-0">
<section key={g.id} data-tauri-drag-region className="pt-3 first:pt-0">
<button
onClick={() => onToggleGroup(g)}
className={`${GROUP_LABEL} flex w-full cursor-pointer items-center gap-1 px-2 pb-1
@@ -218,13 +239,16 @@ export default function Nav({
<span className="truncate">{g.name}</span>
<span className="ml-auto font-mono text-[10px] opacity-60">{apps.length}</span>
</button>
{!g.collapsed && <ul className="space-y-0.5">{apps.map(appRow)}</ul>}
{!g.collapsed && <ul data-tauri-drag-region className="space-y-0.5">{apps.map(appRow)}</ul>}
</section>
);
})}
{config.apps.length === 0 && (
<p className="px-2 py-3 text-[11px] leading-snug text-slate-500 dark:text-slate-400">
<p
data-tauri-drag-region
className="px-2 py-3 text-[11px] leading-snug text-slate-500 dark:text-slate-400"
>
No apps yet. Open Settings to add the first one.
</p>
)}
@@ -232,7 +256,10 @@ export default function Nav({
<Downloads items={downloads} collapsed={false} onDismiss={onDismissDownload} />
<footer className="flex items-center gap-1 border-t border-slate-200 px-1.5 py-2 dark:border-slate-800">
<footer
data-tauri-drag-region
className="flex items-center gap-1 border-t border-slate-200 px-1.5 py-2 dark:border-slate-800"
>
{settingsButton}
</footer>
</aside>