Show each site's own icon; restore the native title bar

Both previous attempts asked a question about a domain, and a domain does
not know which product it is serving. Google's favicon service returned a
marketing site's icon for anything behind a login and nothing for a
private host; Simple Icons returned one flat brand mark where the real
one is multicoloured and, for Gmail, carries the unread count.

The page already holds the answer - fetched, authenticated, current. The
injected script now reads link[rel~="icon"] and reports the best one:
largest declared sizes wins, an Apple touch icon counts as 180, and an
.ico is penalised as usually the 16px tab icon. It rechecks on the same
tick as the unread count, which is when a site like Gmail redraws its
icon with a badge. The URL is stored, so the nav is right at launch
rather than blank until every page has loaded.

The custom frame is gone with it: ordinary macOS title bar, traffic
lights where every other window puts them, and the bar following the
app's Light/Dark choice through set_theme. A window that behaves like a
window beats one that looks bespoke.
This commit is contained in:
2026-09-01 15:03:09 +02:00
parent ffedb72b83
commit 17d13d2505
17 changed files with 186 additions and 358 deletions
+2 -11
View File
@@ -23,13 +23,6 @@ interface Props {
*/
const RAIL = 72;
const PANEL = 240;
/**
* The strip the traffic lights sit in. Draggable, since there is no title bar.
*
* Deep enough to give them room: with the window's own frame around it too,
* anything shallower leaves them crowding the corner.
*/
const TITLEBAR = 48;
export const navWidth = (collapsed: boolean) => (collapsed ? RAIL : PANEL);
@@ -110,7 +103,7 @@ export default function Nav({
}
>
{active && <span className="absolute left-0 h-5 w-[3px] rounded-full bg-sky-500" />}
<Favicon url={app.url} name={app.name} size={20} />
<Favicon icon={app.icon} name={app.name} size={20} />
{(unread[app.id] ?? 0) > 0 && (
<span
title={`${unread[app.id]} new since you last looked`}
@@ -126,7 +119,6 @@ export default function Nav({
return (
<aside className={`${shell} items-center`} style={{ width: RAIL }}>
<div data-tauri-drag-region style={{ height: TITLEBAR }} className="w-full shrink-0" />
{/* 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. */}
@@ -172,7 +164,7 @@ export default function Nav({
title={app.url}
className={`${row} ${app.id === activeId ? active : inactive}`}
>
<Favicon url={app.url} name={app.name} />
<Favicon icon={app.icon} name={app.name} />
<span className="truncate">{app.name}</span>
{badge(app.id, app.id === activeId)}
</button>
@@ -181,7 +173,6 @@ export default function Nav({
return (
<aside className={shell} style={{ width: PANEL }}>
<div data-tauri-drag-region style={{ height: TITLEBAR }} className="shrink-0" />
<header className="flex items-center gap-0.5 border-b border-slate-200 px-1.5 pb-2 dark:border-slate-800">
{controls}
<button