Skip to content

R8a: replace the default Python title bar icon with a Graphlink app mark - #168

Merged
dovvnloading merged 1 commit into
mainfrom
r8a/app-icon
Jul 28, 2026
Merged

R8a: replace the default Python title bar icon with a Graphlink app mark#168
dovvnloading merged 1 commit into
mainfrom
r8a/app-icon

Conversation

@dovvnloading

@dovvnloading dovvnloading commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the Python interpreter icon shown in the application title bar with a purpose-built Graphlink mark, and wires the previously-orphaned icon asset into both the desktop window and the browser tab.

Problem

The desktop window displayed the Python interpreter's icon. pywebview's Windows backend extracts icon index 0 from sys.executable when no icon is supplied (webview/platforms/winforms.py:245-250), and graphlink_desktop.py never supplied one. The existing assets/graphlink.ico had no references anywhere in the codebase.

That asset was additionally unsuitable for the role: a single 1024×1024 frame in an .ico container with no 16/24/32 px variants, requiring Windows to downscale one large bitmap for every size it actually renders — title bar, taskbar, and Alt-Tab.

The browser tab was affected by the same omission. index.html declared no icon, so the browser's implicit /favicon.ico request fell through the SPA catch-all route and received index.html with Content-Type: text/html.

Changes

Icon generation. tools/build_app_icon.py produces the canonical SVG, the multi-frame .ico, and both favicon formats from a single geometry definition, preventing drift between vector and raster sources. The .ico contains eight frames (16–256 px), each rendered independently at its target size with per-size margin tuning rather than downscaled from one bitmap.

Stroke width is held constant across all sizes. Increasing stroke weight at small sizes — the conventional approach to pixel alignment — closes the aperture between the chevron arms and degrades legibility; margin is therefore the only per-size variable. Both constraints are documented in the script and in assets/branding/README.md.

Desktop window. graphlink_desktop.py passes icon= to webview.start(), guarded on file existence and logging a warning rather than raising if the asset is missing.

Browser tab. index.html declares an SVG icon with an .ico fallback, both served verbatim from web_ui/src/app/public/.

Design assets. Nine alternative concepts are retained under assets/branding/ for documentation, badges, and splash art, with the palette and canonical file recorded in the accompanying README.

assets/graphlink.ico decreases from 911 KB to 30 KB while gaining seven frames.

Verification

Check Result
Backend suite 1021 passed
Frontend tsc --noEmit / ESLint / tests / build clean / 0 errors / 794 passed / succeeds
.ico load via System.Drawing.Icon (the call pywebview makes) loads; 16/24/32/48 px resolve to exact frames
16 px frame vs. LANCZOS reduction of 256 px frame differs in 184/256 pixels, confirming per-size rendering
/favicon.svg 200 image/svg+xml
/favicon.ico 200 image/x-icon (previously returned HTML)

A 256 px request returns the 128 px frame, a known GDI+ constraint that does not affect title bar or taskbar rendering. All frames were inspected at 1:1 and magnified against both light and dark title bars.

Outstanding

The icon has not been confirmed in a running desktop window. The loading mechanism is verified end to end, but visual confirmation in the live title bar is recommended before merge.

The window showed the Python interpreter's icon. That was never a design
problem - it was a wiring one. pywebview's Windows backend falls back to
extracting icon index 0 from sys.executable when no icon is passed
(webview/platforms/winforms.py:245-250), and nothing in this repo ever
passed one. assets/graphlink.ico existed but was referenced from exactly
zero call sites.

That file was also malformed for the job: a single 1024x1024 bitmap in an
.ico container with no 16/24/32 frames, so every size Windows actually
renders - title bar, taskbar, Alt-Tab - was brute-force downscaling a
poster.

Adds a real mark: a stem forking into two directed branches, drawn at one
uniform stroke weight. Generated, not hand-drawn - tools/build_app_icon.py
emits the canonical SVG, the multi-frame .ico and both favicons from a
single geometry definition, so the vector and the bitmaps cannot drift
apart. Eight frames, each rendered at its own size with the margin tuned
per size rather than shrunk into place.

Two things learned while tuning, both documented in the script and the
branding README because they are counterintuitive:

- Fattening strokes at small sizes made legibility WORSE, not better. The
  binding constraint is the chevron's aperture, and a heavier stroke
  closes it into a blob. Stroke is constant across sizes; only the margin
  varies.
- The chevron angle has a narrow usable band. Too tight and the aperture
  closes; past ~80 degrees the arms splay and the pair reads as a flat
  zigzag instead of two arrows.

Also wires the favicons, which had the same root cause: index.html
declared no icon at all, so the browser's automatic /favicon.ico request
fell through the SPA catch-all and got index.html back as text/html.

The nine explored alternatives are kept under assets/branding/ as internal
assets for docs, badges and splash art, with the palette and the canonical
file documented so it does not become tribal knowledge.

Icon shrinks from 911KB to 30KB while gaining seven frames.
@dovvnloading
dovvnloading merged commit fb4d4a1 into main Jul 28, 2026
2 checks passed
@dovvnloading dovvnloading changed the title R8a: real app icon in the title bar, replacing the Python default R8a: replace the default Python title bar icon with a Graphlink app mark Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant