Size the rail to the traffic lights instead of guessing

The collapsed rail was a fixed 72px, which left the cluster with more
room on its left than its right. The width now comes from the buttons
themselves - the close button's own inset plus the zoom button's right
edge - so the margin matches on both sides. That inset is macOS's to
choose and has changed between releases, so it is asked for rather than
assumed.

Measured at startup rather than on demand. The answer arrives on the main
thread, and a command waiting for it there deadlocks until the timeout
and silently returns the fallback - which is what the first attempt did.
This commit is contained in:
2026-09-02 10:07:32 +02:00
parent 0ae5746a30
commit e1ac3d7509
7 changed files with 99 additions and 10 deletions
+3
View File
@@ -27,6 +27,7 @@ export default function App() {
opaque bar was only hiding that. With the bar painted the nav's colour, the
shell has to keep clear of it or the nav lands on the traffic lights. */
const [chrome, setChrome] = useState(0);
const [rail, setRail] = useState(72);
const [offer, setOffer] = useState<PasswordOffer | null>(null);
const [saved, setSaved] = useState<string | null>(null);
@@ -42,6 +43,7 @@ export default function App() {
useEffect(() => {
void api.chromeHeight().then(setChrome);
void api.railWidth().then(setRail);
}, []);
useEffect(() => {
@@ -215,6 +217,7 @@ export default function App() {
onReload={() => activeId && api.historyGo(activeId, 0)}
unread={unread}
chrome={chrome}
rail={rail}
/>
{/* The hole an app's native webview is positioned into. It stays empty
+1
View File
@@ -66,3 +66,4 @@ export const setWindowChrome = (dark: boolean) =>
export const chromeHeight = () => invoke<number>("chrome_height");
export const setCountNotifications = (enabled: boolean) =>
invoke<Config>("set_count_notifications", { enabled });
export const railWidth = () => invoke<number>("rail_width");
+8 -9
View File
@@ -17,21 +17,20 @@ interface Props {
onReload: () => void;
/** Height of the title bar the window's content runs underneath. */
chrome: number;
/**
* How wide the collapsed rail has to be for the traffic lights to keep the
* same margin on their right as macOS gave them on their left. Measured from
* the buttons themselves, since that inset is macOS's to choose.
*/
rail: number;
}
/**
* Wide enough that the macOS traffic lights fit inside the rail rather than
* spilling over the page. Everything else about the rail follows from that.
*/
const RAIL = 72;
const PANEL = 240;
export const navWidth = (collapsed: boolean) => (collapsed ? RAIL : PANEL);
export default function Nav({
config, activeId, collapsed, unread,
onSelect, onToggleCollapse, onOpenSettings, onToggleGroup,
onBack, onForward, onReload, chrome,
onBack, onForward, onReload, chrome, rail,
}: Props) {
const groups = [...config.groups].sort((a, b) => a.order - b.order);
const inGroup = (id: string | null) =>
@@ -125,7 +124,7 @@ export default function Nav({
};
return (
<aside className={`${shell} items-center`} style={{ width: RAIL }}>
<aside className={`${shell} items-center`} style={{ width: rail }}>
<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