Skip to content

fix(desktop): show the canonical Maka icon in the permission guide - #3464

Merged
jackwener merged 1 commit into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/permission-overlay-canonical-icon
Aug 22, 2026
Merged

fix(desktop): show the canonical Maka icon in the permission guide#3464
jackwener merged 1 commit into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/permission-overlay-canonical-icon

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

Summary

Follow-up agreed in the #3455 review (M4n5ter's request-changes, Astro-Han's P2): the permission guide now shows the canonical Maka icon instead of a macOS file-type icon.

app.getFileIcon() was the wrong identity source — macOS reduces the path to its UTType and returns the generic application-bundle icon, never this particular app's own. The guide now loads the canonical 1024×1024 assets/icon.png (shipped as an extra resource since #3451, and the same PNG electron-builder stamps onto the bundle) via desktopAssetPath() + nativeImage.createFromPath(), resolved lazily at the card payload — its only consumer — so a non-darwin start() never pays the decode, and a missing asset degrades to the pre-existing no-icon path instead of caching the failure.

This lands the simplify-audit the review outlined: both app.getFileIcon() calls, BUNDLE_ICON_OPTIONS, loadNativeBundleIcon() and its packaged-only gate, the drag-time native fallback, and the unbounded icon await in start() are all gone. resolveAppBundle() remains solely responsible for the file being dragged. Dev builds now show the icon too, instead of the deliberate blank the #1920 gate imposed.

Since the PNG is now a load-bearing packaged resource, assertPackagedResources requires assets/icon.png — gated behind requireCanonicalIcon (same shape as requireDisclaimer) because the Windows upgrade-lifecycle baseline predates #3451; the lifecycle's post-upgrade re-verification still runs under the current contract and does require it.

Refs #3352.

Verification

Empirical, with this repo's Electron 43.2.0 on an Apple silicon Mac:

  • nativeImage.createFromPath(assets/icon.png) decodes 1024×1024; resize({64,64})toDataURL() yields a valid PNG data URL; a missing path returns an empty image without throwing (degrades to null, same as before).
  • The built dist/main/desktop-assets.js resolves the dev layout to apps/desktop/assets/icon.png (exists) and the packaged layout to <resourcesPath>/assets/icon.png — the call site is byte-identical to the existing one in main-window.ts.
  • webContents.startDrag({ file, icon: nativeImage.createEmpty() }) does not throw — Electron's 'icon' parameter is required guard fires only on a missing key, so the deleted drag fallback loses nothing.
  • assertPackagedResources fault-injection: current contract fails on a missing icon; requireCanonicalIcon: false (legacy baseline) passes — now pinned as two unit tests driven through the injectable requirePath (no packaging, no platform dependence).

Checks run locally (Node 24.18):

  • npm --workspace @maka/desktop run typecheck — pass
  • npm --workspace @maka/desktop run test — 1036 pass / 0 fail
  • node --test scripts/verify-packaged-app.test.mjs — 16 pass / 0 fail
  • biome check on changed files — clean; knip desktop workspace — clean

Not run: a full packaged DMG build (the packaged path split is covered by the existing desktop-assets unit tests plus the release verifiers this PR extends).

An independent adversarial review pass probed the change with fault-injection experiments before submission (path resolution from the built layout, start() payload regression, caching semantics, drag-fallback deletion, the assertPackagedResources caller matrix including the legacy-baseline gate, destructure coherence, comment truthfulness) — no defects; its two suggestions (lazy resolution at the consumer, the assertPackagedResources unit tests) are incorporated.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code implemented the maintainer-specified follow-up, ran the Electron-level verification, and wrote the tests; an independent adversarial review pass (also Claude) verified the change with running experiments. I reviewed and verified the result.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Adversarial review record (pre-submission)

An independent review pass probed this change with running experiments before submission. All seven vectors CONFIRMED-SAFE; the evidence was active, not read-only:

  1. Built-layout path resolution. Ran the built dist/main/desktop-assets.js under plain node: dev resolves to apps/desktop/assets/icon.png (exists, 745 KB), packaged to <resourcesPath>/assets/icon.png. import.meta.dirname is evaluated inside desktop-assets.js, so the caller's subdirectory is irrelevant; the call site is byte-identical to main-window.ts's existing one.
  2. start() regression. buildCardPayload already did its own resolveAppBundle — the one deleted from start() fed only the icon, so draggable is unaffected. The overlay page sets the <img> only if (payload.iconDataUrl); both null (the pre-existing dev behavior) and non-null render correctly.
  3. Caching. Single writer (??= at the payload), single reader; a null result retries next card instead of caching the failure. Measured cost: 16 ms cold, ~5 ms warm — once per session.
  4. Drag-fallback deletion. The renderer does not echo the payload icon — it drags a canvas composite of the card row, which stays valid with no icon. And startDrag({file, icon: nativeImage.createEmpty()}) does not throw: Electron's 'icon' parameter is required guard was proven (by driving all five argument shapes) to fire only on a missing key, never on emptiness. The deleted native fallback was near-unreachable and loses nothing.
  5. assertPackagedResources matrix. Fault-injected through the injectable requirePath: current contract fails on a missing icon; requireCanonicalIcon: false passes. Caller audit: macOS DMG verifier (defaults, fresh builds only), Windows x64 (contract-gated), the installer-lifecycle's legacy-baseline call is the only non-current site and its post-upgrade re-verification runs under the current contract. git log -S confirms assets/ began shipping only after v0.1.11, so the legacy gate is genuinely necessary. This matrix is now pinned as unit tests.
  6. Destructure coherence. No shadowing between the factory's and attachCardGestures' electron destructures; no dangling references to the deleted identifiers anywhere in the repo.
  7. Comment truthfulness. mac.icon/win.icon both point at the same assets/icon.png; measured 1024×1024, and the extraResources entry is platform-unscoped, so the updated builder-config comment is literal.

The reviewer's two suggestions — resolving the icon lazily at its only consumer (sparing non-darwin start() the decode) and unit-testing the assertPackagedResources contract — are incorporated in this PR.

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review of exact head f82c0736acfc29de0854cbfbff7e5d9183ee3296 against main@f1f4e71a9540a4da23159052c924fee72202e989.

No actionable implementation issue found. The canonical PNG is resolved through the same packaged/dev asset authority already used by the main window; empty images degrade to the existing no-icon drag path; both getFileIcon crash/generic-icon paths are removed; and current macOS/Windows packaged verification requires the same assets/icon.png that extraResources ships. The legacy Windows baseline alone opts out, while its post-upgrade current-contract verification still requires the icon.

Required conclusions:

  1. Optimal for the actual problem: yes; this deletes the wrong native identity path and reuses the canonical packaged asset.
  2. Production code to delete: the PR already deletes BUNDLE_ICON_OPTIONS, loadNativeBundleIcon, both native lookups, and the redundant drag fallback; none further identified.
  3. Tests to delete/replace: none identified. The fault-injected packaged-resource tests protect the regression #3451 left uncovered.
  4. Deeper refactor: no.
  5. Ready to merge: not yet: no required hosted test result or independent human approval is present on the current PR state.
  6. Residual risks: packaged visual/drag behavior still benefits from a human macOS smoke check; no additional code gap was found.

This changes user-visible permission-guide presentation. Independent human review is required; this automated review is not approval.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Packaged macOS smoke — this PR's head, real device

Addressing the residual risk from the automated review ("packaged visual/drag behavior still benefits from a human macOS smoke check"): I built this exact head into a packaged app locally and drove the permission guide on real hardware.

Setup. f82c0736abuild:with-depselectron-builder --mac --arm64 (local unsigned config) → ad-hoc codesign → launched on an Apple silicon Mac (the crash report's hardware class) with an isolated HOME/--user-data-dir and --remote-debugging-port=0, driven over CDP — the same technique the release verifier uses.

Observed, in order:

  1. Permission snapshot read fine; accessibility was not_determined for the ad-hoc build, so the guide takes the real card path.
  2. window.maka.permissions.startDragOnboarding('accessibility'){ ok: true }. The process stayed alive — on 0.1.11 this exact call was the packaged EXC_BREAKPOINT crash (bug(desktop): packaged macOS permission guide crashes while loading the app icon #3352); with this head the whole native icon path it crashed in no longer exists.
  3. The overlay card appeared and its <img id="icon"> rendered a 64×64 PNG data URL (7,710 chars).
  4. Identity proof, byte-for-byte: the data URL captured from the live card equals the packaged Resources/assets/icon.png run through the exact production transform (createFromPathresize({64,64})toDataURL) — both 7,710 chars, both sha256/16 = b19dfb59a413a066, strict string equality true. The card is showing the canonical Maka icon, not a UTType-generic one, and could not have come from any other source.

Not covered by this smoke: the physical drag gesture into System Settings needs a human hand on an unmanaged pointer, so I did not exercise the drop itself. The drag replica, however, is composited from the same verified <img> (the renderer canvas path — the drag image never takes a separate icon source in this head), and the underlying webContents.startDrag file/icon contract is unchanged by this PR.

Happy to re-run any variation of this if it helps the human review.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this removes the crash-prone native icon lookup and reuses the canonical packaged assets/icon.png path through the existing desktop asset seam. I reviewed exact head f82c0736acfc29de0854cbfbff7e5d9183ee3296; the main-process bundle authority, renderer drag-image boundary, current/legacy resource contracts, and cleanup are consistent. I found no P0–P3 issues, and both current-head test and package checks are green.

AI-assisted review disclosure: OpenAI Codex performed an independent exact-head review. I verified the source and packaged-resource paths, cross-platform scope, tests, live reviews, mergeability, and current-head CI, and I independently made the approval decision.

app.getFileIcon() was the wrong identity source: macOS reduces the path
to its UTType and returns the generic application icon, never this
app's own. Load the canonical 1024px assets/icon.png (shipped since the
window-icon fix) through desktopAssetPath + nativeImage.createFromPath
instead, resolved lazily at the card payload so non-darwin starts never
pay the decode.

This retires the whole native icon chain: both getFileIcon() calls,
BUNDLE_ICON_OPTIONS, loadNativeBundleIcon() and its packaged-only gate,
the drag-time native fallback, and the unbounded icon await in start().
Dev builds now show the icon too instead of a deliberate blank.

The PNG is now a load-bearing packaged resource, so
assertPackagedResources requires it on current builds, gated off for
the legacy Windows upgrade baseline that predates it.

Implements the follow-up agreed in apache#3455 review. Refs apache#3352.

Generated-by: Claude Code
@jackwener
jackwener force-pushed the fix/permission-overlay-canonical-icon branch from f82c073 to 3aa9043 Compare August 22, 2026 13:04

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First-principles: app.getFileIcon() is the wrong identity source on macOS (UTType generic app icon, and 'large' SIGTRAPs packaged builds). Loading the canonical assets/icon.png through the existing desktopAssetPath seam is the minimal fix, and it deletes the native icon chain.

Rebased onto current main. Packaged-resource contract now requires the icon on current builds and still forbids bundled Git; the legacy Windows baseline opts out of both.

Approve.

@jackwener
jackwener merged commit 56d03b4 into apache:main Aug 22, 2026
2 checks passed
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.

3 participants