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:
@@ -252,47 +252,34 @@ a number that survived would be a claim the app can no longer support.
|
||||
|
||||
## App marks
|
||||
|
||||
Each app is drawn as its brand glyph in white on a round tile of its own colour, from
|
||||
**Simple Icons** (CC0, ~3,400 marks). A host with no glyph gets its initial in the same
|
||||
tile, so a private tool sits in the row looking like it belongs.
|
||||
Each app shows **the icon its own page declares**, exactly as a browser would.
|
||||
|
||||
This replaced Google's favicon service, which was wrong as often as it was right: it
|
||||
returned a sign-in page's icon for anything behind a login, nothing at all for a private
|
||||
host, and cached both answers past any way of asking again. A "refresh icons" button could
|
||||
not fix that, because the staleness was not local.
|
||||
Two earlier attempts were wrong in the same way. Google's favicon service and Simple
|
||||
Icons both answer a question about a *domain*, and a domain does not know which product
|
||||
it is serving: the service returned a marketing site's icon for anything behind a login
|
||||
and nothing for a private host, and Simple Icons returned a single flat brand mark where
|
||||
the real one is multicoloured and, in Gmail's case, carries the unread count.
|
||||
|
||||
Each tile carries the **brand's own colour**, which Simple Icons publishes alongside the
|
||||
glyph — Gmail red, Drive blue, Chat green, Gemini violet. The glyph is drawn black or
|
||||
white depending on the tile's perceived brightness, because brand colours are chosen to
|
||||
look right rather than to carry a white mark: GitHub and Notion are near-black, Snapchat
|
||||
is pure yellow, and a fixed white glyph loses one end of that range.
|
||||
The page has the answer already — fetched, authenticated, and current. So the injected
|
||||
script 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 because it is usually the
|
||||
16px one drawn for a browser tab in 2005. It rechecks on the same tick as the unread
|
||||
count, because that is exactly when a site like Gmail redraws its icon with a badge on it.
|
||||
|
||||
A host with no mark falls back to a Tailwind 500 chosen by hashing the host, so it is
|
||||
still stable — you find things by their colour, and one that moved every launch would be
|
||||
worse than none.
|
||||
|
||||
The build turns Simple Icons' 15MB of SVG files into one 4.5MB map of slug to path and
|
||||
hex,
|
||||
written to `public/` so it is fetched once at runtime rather than parsed into the JS
|
||||
bundle at every start. The bundle stays at 233KB. The map is generated by `npm run icons`,
|
||||
which `npm run build` runs, so it is never committed and never stale.
|
||||
|
||||
Matching a host to a glyph tries the registrable name first — `example.odoo.com` is Odoo,
|
||||
not Aputure — because a self-hosted tool is nearly always on a subdomain of its vendor. A
|
||||
short table handles the ones a domain cannot answer, which is most of Google: `google.com`
|
||||
says only that it is Google, not which of a dozen products.
|
||||
The URL is stored in `apps.json`, so the nav is right the moment it opens rather than
|
||||
blank until every page has loaded. An app with nothing yet — or an icon that will not
|
||||
load — falls back to its initial in a tile of the same size, so the row never reflows.
|
||||
|
||||
## The window
|
||||
|
||||
There is no title bar and no toolbar, so in a normal window the shell keeps a 6px margin
|
||||
around itself, and the traffic lights are pushed in to sit within it rather than crowding
|
||||
the corner — which also sets the depth of the nav's drag strip.
|
||||
The window keeps its ordinary macOS title bar, with the traffic lights where every other
|
||||
window puts them. It follows the app's own Light or Dark choice through `set_theme`;
|
||||
"System" hands it back to the OS, which is what System means.
|
||||
|
||||
An app's corners are rounded on its own layer, not by the container around it: an app is a
|
||||
native view sitting on top rather than something the shell lays out, so a `rounded-xl` on
|
||||
its parent does nothing and it overhangs the curve. Only the right pair is rounded; the
|
||||
left edge butts against the nav, and rounding it would cut a notch out of the middle of
|
||||
the window.
|
||||
An earlier version drew its own frame instead — a hidden title bar, a margin to grab, and
|
||||
a corner radius applied to each app's layer because a native view sitting on top of the
|
||||
shell cannot be clipped by the CSS around it. It is gone. A window that behaves like a
|
||||
window is worth more than one that looks bespoke.
|
||||
|
||||
The margin That margin is the only part of the window that is not a web page, and
|
||||
therefore the only place left to grab it by. Full screen has no use for it and gets the
|
||||
|
||||
Reference in New Issue
Block a user