Skip to content

feat(tray): add cross-platform icon system with HiDPI support#54

Merged
flexiondotorg merged 13 commits intomainfrom
tray-icons
Mar 26, 2026
Merged

feat(tray): add cross-platform icon system with HiDPI support#54
flexiondotorg merged 13 commits intomainfrom
tray-icons

Conversation

@flexiondotorg
Copy link
Copy Markdown
Member

Summary

Implements a comprehensive cross-platform menu icon system for the tray with full HiDPI display support. Replaces glyph prefixes with platform-aware icon handling, generates light and dark variants for theme compatibility, and provides high-resolution representations for modern displays.

Changes

  • Add cross-platform menu icon helper function with platform-specific rendering
  • Replace glyph prefixes with icon property for Now Playing items
  • Generate menu icons in light and dark variants for theme switching
  • Add multi-representation artwork for HiDPI display support
  • Implement intermediate scale representations for album artwork
  • Optimise PNG icons and add apple/cat icon variants
  • Add high-DPI tray icon support with @2x scale representations
  • Create icon generation tooling for reproducible asset builds
  • Enhance mocks and add template inspection tests for menu icons
  • Add platform-specific icon assertion tests
  • Refresh menu icons on theme change detection

Testing

  • Unit tests verify platform-specific icon selection and rendering
  • Template inspection tests ensure correct icon property application
  • Assertion tests validate icon presence across light/dark variants
  • Manual testing on macOS, Linux and Windows confirms proper display at 1x and 2x scales

Related Issues

Closes the tray icon implementation feature request.

- Add 56 light theme icons (black, 1x and 2x) to
  assets/icons/tray/menu/light/
- Add 56 dark theme icons (white, 1x and 2x) to
  assets/icons/tray/menu/dark/
- Add `generate-menu-icons` recipe to justfile using rsvg-convert
- Covers 28 unique icons: backward-step, bell-slash, bell, bookmark,
  burst, circle-info, compact-disc, discord, eject, expand,
  forward-step, house-user, list, music, palette, parachute-box, pause,
  play, radio, record-vinyl, rotate, star, toggle-off, toggle-on,
  volume-high, volume-low, volume-off, volume

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Extend nativeImage mock with createFromPath and createFromNamedImage
- Convert Tray mock from function to class with setContextMenu,
  setToolTip, setImage, and on methods
- Expose Menu.buildFromTemplate to capture template arrays for
  inspection
- Add 15 tests covering template structure, Tray instantiation, and
  platform-specific behaviour (Linux glyph prefixes, Windows/macOS plain
  labels)
- Validate separator placement and disabled item handling across
  platforms

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Add menuIconFileMap and menuIconSFSymbolMap lookup tables for all 16
  tray actions
- Add isMacOSTahoeOrLater() helper to detect macOS 26.x+ via
  process.getSystemVersion()
- Export getMenuIcon(action) that returns icons based on platform:
  * Linux/Windows: nativeImage.createFromPath() with theme-aware PNG
    variants
  * macOS Tahoe+: nativeImage.createFromNamedImage() with SF Symbol
    names
  * Pre-Tahoe macOS: returns undefined
- Add comprehensive test coverage for all platform/version paths
- Mock process.getSystemVersion() in test setup

Establishes foundation for applying icons to menu items.

Signed-off-by: Martin Wimpress <code@wimpress.io>
Replace Unicode glyph prefixes in all top-level menu item labels with
platform-aware icon resolution via getMenuIcon(). Labels now display
plain translated text across all platforms, with icons determined by
OS and availability.

- Remove glyph prefixes from About, Start Page, Notifications, Discord,
  Style, Zoom, update states, Quit menu items
- Add icon property to each top-level menu item using getMenuIcon()
- Remove isLinux parameter from buildUpdateMenuItems() (glyph removal
  is now unconditional)
- Submenu radio items remain unchanged

Signed-off-by: Martin Wimpress <code@wimpress.io>
Extend test coverage to verify icon attachment across all platforms and
update states.

- Replace glyph-prefix assertions with icon-property assertions
- Add 29 tests covering platform-specific icon variants (Linux PNG
  light/dark, Windows PNG, macOS Tahoe+ SF Symbols, pre-Tahoe macOS
  no icons) across update states (update-ready, update-available,
  up-to-date)
- All 272 tests pass (243 existing + 29 new)

Signed-off-by: Martin Wimpress <code@wimpress.io>
… items

- Remove Unicode glyph prefixes from artist, album, playback controls,
  and volume items
- Add icon property using getMenuIcon() helper to 7 Now Playing menu
  items
- Update 19 tests across 4 platform/version combinations to verify icon
  attachment
- Preserve artwork icon on track name item unchanged
- Labels now plain translated text on all platforms

This enables consistent icon rendering across Linux, Windows, and macOS
without relying on Unicode glyphs.

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Extend Linux nativeTheme.on('updated') handler to rebuild context
  menu, enabling menu icons to refresh when theme switches between
  light and dark modes
- Add equivalent Windows handler to rebuild menu for light/dark PNG
  variant switching
- macOS unchanged: SF Symbols inherit theme automatically
- Add 6 tests validating theme callbacks trigger menu rebuild on Linux
  and Windows, but not on macOS (including Tahoe+ and pre-Tahoe
  versions)

Signed-off-by: Martin Wimpress <code@wimpress.io>
Add conditional icon selection in buildNowPlayingMenuItems() to display
record-vinyl icon for albums with releaseDate <= 1981, compact-disc for
all others. Reflects historical accuracy of physical media formats.

- Add record-vinyl to menuIconFileMap and menuIconSFSymbolMap
- Use record.circle SF Symbol for vinyl records
- Add 3 tests covering vinyl/cd icon selection by release year

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Replace single-resolution NativeImage with multi-representation using
  createEmpty() + addRepresentation()
- Serve artwork at 1x (18px) on standard displays, 2x (36px) on HiDPI
- Move isEmpty() guard to check source before image creation
- Update test mocks to support createEmpty and addRepresentation methods

Improves album artwork clarity on Retina and other high-DPI displays by
leveraging cached 512×512 artwork source without changing the download
or cache pipeline.

Signed-off-by: Martin Wimpress <code@wimpress.io>
Add three scale representations for artwork NativeImage to support
Windows displays with fractional DPI scaling:
- 1.25x at 23px
- 1.5x at 27px
- 1.75x at 32px

These sit between existing 1.0x (18px) and 2.0x (36px) representations,
eliminating aliasing on intermediate scale factors while preserving the
existing download and cache pipeline.

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Optimize 124 existing tray menu icons with optipng (10-20% size
  reduction)
- Add apple and cat icon variants in light and dark PNG formats
- Add apple.svg and cat.svg source files
- Add optipng to flake.nix dev shell for reproducible optimization
- Integrate optipng into justfile generate-menu-icons recipe for
  automatic optimization of future generated icons

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Add `generate-tray-icon` task to build icon variants from
  sidra-mini.svg
- Generate platform-specific icons at 1x and 2x scales for HiDPI
  displays
- Create macOS template icons (16x16, 32x32) with transparent background
  - Create Linux/Windows light theme icons (24x24, 48x48) in black
  - Create Linux/Windows dark theme icons (24x24, 48x48) in white
  - Optimize all PNG files with optipng for minimal file size
  - Replace deprecated mono variants with unified mini icon source

Signed-off-by: Martin Wimpress <code@wimpress.io>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 138 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/tray.test.ts">

<violation number="1" location="test/tray.test.ts:275">
P2: Restore the `process.getSystemVersion` spy after each macOS test. Without cleanup, the mocked version can leak into later tests and make platform checks flaky.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Ensures consistent mock state between test runs by resetting
process.getSystemVersion to default value after each test.

Signed-off-by: Martin Wimpress <code@wimpress.io>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Significant feature addition (cross-platform icon system) with logic changes for theme switching and platform-specific rendering across 120+ files; requires human verification.

@flexiondotorg flexiondotorg merged commit 6cb500b into main Mar 26, 2026
12 checks passed
@flexiondotorg flexiondotorg deleted the tray-icons branch March 26, 2026 14:59
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