Skip to content

feat: use the product's own mark for the app icon and sidebar - #103

Merged
yujiezhang-ops merged 2 commits into
mainfrom
feat/adopt-brand-logo
Aug 7, 2026
Merged

feat: use the product's own mark for the app icon and sidebar#103
yujiezhang-ops merged 2 commits into
mainfrom
feat/adopt-brand-logo

Conversation

@yujiezhang-ops

Copy link
Copy Markdown
Collaborator

Closes #98.

Replaces the placeholder mark and the old app icon with the product's own logo, across every surface that had one.

What was wrong

  • Sidebar rendered lucide's generic <Boxes> glyph — a stock icon from the icon library standing in for a brand that did not exist yet.
  • build/appicon.png measured 1024×1024, 100% opaque, white in all four corners. macOS does not round app icons for you; the artwork has to carry its own squircle, so that square is what shipped.
  • Windows had no icon at all. The icon task passed -windowsfilename "" and no .ico existed anywhere in the repo, so released .exe files carried the default executable icon.

Approach

The mark is traced from the master raster in the site repo (public/images/brand/oneagent-logo.png) rather than redrawn, so the geometry stays the designer's. Verified by rasterising the traced path back and comparing coverage against the source: 33.7% both ways, with the ring, the inner form, and the break at the lower right all intact.

The app icon is rebuilt at 1024 from the bare mark rather than upscaled from the site's 512 plated PNG, using geometry measured off that asset: full-bleed plate, corner radius 20.5% of the edge, superellipse exponent ~1.75, mark at 71.3% centred. Result matches the reference at 4.3% transparent versus 4.4%.

It paints fill="currentColor" instead of the brand's #007AFF. That value equals --blue on the light theme exactly, but the dark theme sets --blue to #0a84ff, so a literal would be visibly off against every other blue in the window. Confirmed in the browser: #007aff on light, #0a84ff on dark.

The 16×16 problem

Scaling the mark down puts every feature under the pixel budget:

Target Ring stroke Ring→inner gap Inner stroke
38px (sidebar) 4.0px 4.8px 4.5px
32px 3.4px 4.0px 3.8px
16px 1.7px 2.0px 1.9px

At 16px the ring, the gap, and the inner form each land on roughly two pixels and blend into a solid block — the break in the ring disappears entirely. I confirmed this by extracting the entry wails3 generate icons produces and inspecting it.

Since 16×16 is what Explorer and the taskbar use, that entry is hand-drawn: the ring alone at a weight that survives, keeping the silhouette recognisable rather than preserving detail that cannot render. The other five sizes are scaled normally.

build/Taskfile.yml carries a comment explaining that regenerating the .ico wholesale discards it.

Two things found while wiring this up

A CSS selector that was already too broad. .brand-lockup span:last-child was meant for the tagline but matched any last-child span in the lockup — including the one now wrapping the mark — and painted it --text-secondary. It stayed hidden only while the mark was a Lucide <svg> rather than a <span>. Fixed the selector rather than piling specificity on the new rule.

The workflow does not use the Taskfile. build-artifacts.yml calls go build directly, so adding the .syso step only to build/windows/Taskfile.yml would have left every released .exe on the default icon while local builds looked correct. The step is in both places, with a comment saying why.

Compliance

The mark is first-party, so it deliberately does not go in asset-rights.json — that file tracks vendor artwork with a recorded source, licence, and SHA-256, and registering ours there would assert a third-party provenance it does not have. A test asserts it stays out.

NOTICE described the icon inventory as vendor marks from lobe-icons plus the OpenClaw derivative. Added a paragraph so a first-party mark is not read as covered by that. Worth flagging: the licence generator only diffs third_party/ and does not check this prose, so nothing would have caught the drift.

Not done, deliberately

No Linux icon. The release matrix in build-artifacts.yml builds macOS and Windows only, so a Linux icon would be an unused file.

The SVG is a traced polyline (320 points, 3.8KB) rather than curve-fitted. Faithful and verified, and small next to the 46KB CSS bundle, but a hand-authored vector from the designer would be smaller. Worth asking for if one exists.

Verification

frontend: 35 files, 252 tests passed   (8 new)
tsc --noEmit: clean
pnpm run build: clean
go test ./... and -race: clean
go vet: clean
scripts/check-docs.py: ok, 50 files
scripts/generate_third_party_licenses.py --check: exit 0

Windows resources confirmed embedded in a cross-compiled .exe — all six icon sizes present by byte signature, plus ProductName, CompanyName, and dpiAwareness. Sidebar mark verified in the browser in both themes.

🤖 Generated with Claude Code

@yujiezhang-ops
yujiezhang-ops force-pushed the feat/adopt-brand-logo branch from 7d0cccd to e57b570 Compare August 7, 2026 09:28
@yujiezhang-ops

Copy link
Copy Markdown
Collaborator Author

Rebased onto main after #102 landed the NSIS installer and macOS DMG.

Two conflicts, and the interesting one was not textual.

build/windows/icon.ico (add/add). #102 added an .ico too, generated from the old white-square appicon.png. Kept this branch's version — it is generated from the new mark and its 16×16 entry is hand-drawn (318 bytes vs their 603-byte mechanical downscale).

build/Taskfile.yml — a real conflict of intent. #102 added a generate:windows:icon task, and build/windows/Taskfile.yml's package task depends on it. So every local task package would have regenerated the .ico from appicon.png and silently discarded the hand-drawn 16×16, without failing. Scaling the full mark to 16px puts the ring stroke, its counter, and the inner form each under 2.4px, so they blend into a solid block and the break in the ring disappears — that is the size Explorer and the taskbar use.

Resolved by removing the generation task and keeping the .ico tracked, with a comment at both sites explaining why there is deliberately no such task. nsis/project.nsi reads ..\icon.ico directly (MUI_ICON / MUI_UNICON), so the installer picks up the tracked file with no task needed — verified the path resolves.

Everything else auto-merged correctly, and I checked rather than assumed:

  • The workflow step order is Compile Windows resourcesBuild binariesInstall NSISPackage Windows NSIS installer, so the .exe the installer wraps already carries the icon.
  • .gitignore kept both sides' additions.

Re-verified after the rebase: all six icon sizes plus ProductName and dpiAwareness embedded in a cross-compiled .exe; frontend 252 tests across 35 files; go vet, go test, and check-docs.py clean.

@yujiezhang-ops
yujiezhang-ops force-pushed the feat/adopt-brand-logo branch from 9c4f066 to 2c8096c Compare August 7, 2026 09:41
yujiezhang-ops and others added 2 commits August 7, 2026 17:44
The sidebar rendered lucide's generic <Boxes> glyph -- a stock icon standing in
for a brand that did not exist yet -- and build/appicon.png was a hard-edged
opaque square with white in all four corners. macOS does not round app icons,
so that square is what shipped.

The mark is traced from the master raster in the site repository rather than
redrawn, so the geometry stays the designer's. It paints fill="currentColor"
instead of the brand's #007AFF: that value equals --blue on the light theme
exactly, but the dark theme sets --blue to #0a84ff, so a literal would be
visibly off against every other blue in the window.

Windows was carrying the default executable icon. The previous icon task passed
-windowsfilename "" and no .ico existed at all, so this adds one, plus the
manifest and version info a .syso needs. The 16x16 entry is hand-drawn: scaling
the full mark down puts the ring stroke, its counter, and the inner form each
under 2.4px, which blends them into a solid block and loses the break in the
ring. That size is what Explorer and the taskbar use.

The syso step is repeated in build-artifacts.yml because that workflow calls
`go build` directly instead of going through the Taskfile. Doing it in only one
place would leave every released .exe on the default icon while local builds
looked correct.

Linux needs no icon: the release matrix builds macOS and Windows only.

Two things found while wiring this up:

  - `.brand-lockup span:last-child` was meant for the tagline but matched any
    last-child span in the lockup, including the one now wrapping the mark, and
    painted it --text-secondary. It stayed hidden only while the mark was an
    <svg> rather than a <span>.
  - NOTICE described the icon inventory as vendor marks from lobe-icons plus the
    OpenClaw derivative. A first-party mark must not be read as covered by that,
    and the licence generator only diffs third_party/ -- it does not check this
    prose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_every_shipped_image_asset_is_registered walked the assets directory and
required every image to appear in asset-rights.json. That gate is right about the
thing it was built for -- an unregistered third-party file ships with no source,
licence or hash, and --check still passes -- but it assumed everything in that
directory is third-party, which the product's own mark is not.

Satisfying it by adding the mark to `assets` would have been the wrong fix: that
entry means "here is the vendor, licence text, owner and hash", and our own
artwork has none of those to give. It is covered by the repository's own licence,
so a lobehub-shaped entry would assert a provenance that does not exist.

So the manifest now has a separate `firstPartyAssets` group, and the gate accepts
a file recorded in either. What it still refuses is a file in neither, which is
the actual failure it was written to catch. A second test keeps the two groups
from blurring: a first-party entry may not carry source, license, licenseSource,
copyrightOwner or sha256, and no file may appear in both.

collect_asset_dependencies reads only `assets`, so the new group cannot leak into
THIRD_PARTY_NOTICES.md as a dependency.

docs/distribution-compliance-policy.md needs no change -- it already lists
"OneAgent's own icons" as distributable and scopes the inventory requirement to
third-party files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yujiezhang-ops
yujiezhang-ops force-pushed the feat/adopt-brand-logo branch from 2c8096c to 7b38736 Compare August 7, 2026 09:45
@yujiezhang-ops
yujiezhang-ops merged commit cdb2fed into main Aug 7, 2026
4 checks passed
@yujiezhang-ops

Copy link
Copy Markdown
Collaborator Author

Rebased again onto main (a7bfee2 "display version in settings" and f4b22e1 "build wails in amd64"). All four checks green.

We had independently solved the same problem — both branches added wails.exe.manifest and a syso step. I kept their implementation and dropped mine, because theirs is better on three counts:

  • Their manifest declares trustInfo / requestedExecutionLevel asInvoker; mine omitted it.
  • Their .syso cleanup splits by platform (powershell Remove-Item on Windows hosts, rm -f elsewhere), where mine assumed a POSIX host.
  • GOARCH: amd64 on the generator step is a real fix I did not have — wails3 is a host tool, so go run would otherwise try to build it for arm64.

Kept from this branch: the tracked .ico with the hand-drawn 16×16, and -info build/windows/info.json so the binary carries version strings.

One change I did make to their code. Both the Taskfile and the workflow regenerated icon.ico from appicon.png immediately before compiling the .syso. That would have replaced the hand-drawn 16×16 on every build, including every release build — silently, without failing. Removed the regeneration in both places, with a comment at each site saying why the file is tracked instead.

Two things the automatic merge got wrong that were worth catching by hand:

  • build/windows/Taskfile.yml ended up with two generate:syso tasks (a duplicate YAML key), and they disagreed on the output filename: mine wrote rsrc_windows_*.syso while the cleanup step deleted wails_windows_*.syso, so a stale file would have been left in the package directory. Deleted mine.
  • I initially thought GOARCH=amd64 … -arch "${GOARCH}" meant the target arch got shadowed by the host override. It does not — the shell expands ${GOARCH} before the assignment applies, so the target arch is still passed correctly. Verified rather than assumed, and their line is right as written.

Re-verified after the rebase: the hand-drawn 16×16 (318 bytes) plus ProductName and asInvoker are present in a cross-compiled .exe; frontend 252 tests; go vet, go test, licence generator (7 tests), --check, and check-docs.py all clean.

@yujiezhang-ops
yujiezhang-ops deleted the feat/adopt-brand-logo branch August 7, 2026 09:52
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.

Adopt the finished O+A logo in the desktop app

1 participant