Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/desktop-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,19 @@ jobs:
- name: Download and verify target Flashgrep binary
run: node scripts/prepare-flashgrep-resource.mjs --target "${{ matrix.platform.target }}"

- name: Select Xcode for macOS Icon Composer assets
if: runner.os == 'macOS'
shell: bash
run: |
test -d /Applications/Xcode_26.2.app/Contents/Developer
echo "DEVELOPER_DIR=/Applications/Xcode_26.2.app/Contents/Developer" >> "$GITHUB_ENV"

- name: Verify native macOS icon bundling
if: runner.os == 'macOS'
env:
OPENBITFUN_TEST_MACOS_BUNDLE: '1'
run: node --test scripts/macos-icon-bundle.test.mjs

- name: Build desktop app
run: ${{ matrix.platform.build_command }}

Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/nightly-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,19 @@ jobs:
CARGO_PROFILE_TEST_DEBUG: "0"
run: cargo test --locked -p openbitfun-cli --test terminal_process_contracts -- --test-threads=1

- name: Select Xcode for macOS Icon Composer assets
if: runner.os == 'macOS'
shell: bash
run: |
test -d /Applications/Xcode_26.2.app/Contents/Developer
echo "DEVELOPER_DIR=/Applications/Xcode_26.2.app/Contents/Developer" >> "$GITHUB_ENV"

- name: Verify native macOS icon bundling
if: runner.os == 'macOS'
env:
OPENBITFUN_TEST_MACOS_BUNDLE: '1'
run: node --test scripts/macos-icon-bundle.test.mjs

- name: Build desktop app
shell: bash
run: |
Expand Down
Binary file modified OpenBitFun-Installer/src-tauri/icons/openbitfun-app-icon.icns
Binary file not shown.
14 changes: 14 additions & 0 deletions OpenBitFun-Installer/src-tauri/tauri.macos.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://schema.tauri.app/config/2",
"build": {
"beforeBundleCommand": "pnpm --dir .. run check:macos-icons"
},
"bundle": {
"icon": [
"icons/openbitfun-app-icon.icns",
"icons/openbitfun-app-icon.ico",
"icons/openbitfun-app-icon.png",
"../../assets/brand/macos/openbitfun.icon"
]
}
}
87 changes: 79 additions & 8 deletions assets/brand/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,90 @@ The iOS App Store icon uses an opaque black square; iOS applies the corner mask.
mark, a light transparent mark, and the application icon. Fine-line mark exports
are rendered directly from the SVG with size-specific optical treatments at
small sizes. Application icons are resized from the preserved application artwork.
Windows ICO frames, Linux icons, and macOS PNG representations use the matching
size-specific application exports. Tauri encodes the legacy 16/32 px ICNS
representations from the same artwork.
Windows ICO frames and Linux icons use the matching size-specific application
exports. macOS owns a separate native resource pipeline described below; the
shared PNGs, Windows ICO, Linux, web, and mobile artwork are unchanged.

## macOS icons

`macos/openbitfun.icon` is the Icon Composer document for macOS 26 and newer,
including macOS 27. Its background is an opaque, full-bleed black fill and its
foreground is the transparent silver mark projected from the same brand master.
It has no baked outer mask, shadow, or Dock padding. macOS applies the enclosure
and appearance treatment through `Assets.car`. The source uses Icon Composer 1.2
features supported by Xcode 26.2; it does not require an Xcode 27-only document.
No language or region-specific icon variants are needed.

The committed ICNS is the static dev and older-macOS fallback. It is rendered
from that Composer document by Apple's `ictool`, on the separate authored
`macos/legacy-canvas.json` layout, and encoded with Apple's `iconutil`. That
legacy layout has an 824 px body on a 1024 px transparent canvas. This is our
legacy artwork geometry, **not a universal Apple padding requirement**. It never
alters the modern Composer source. Ten 1x/2x representations cover 16–1024 px.
There is no runtime OS-version scaling and no modification of ICNS chunks by
the shared brand generator; canonicalization only orders the native chunks.

Tauri CLI is pinned to **2.11.4** in both workspace apps. The macOS config overlay
adds `.icon` alongside ICNS; base and Windows/Linux icon selections stay intact.
Tauri dev reads ICNS through `NSApplication.applicationIconImage`. Bundled builds
use `CFBundleIconName` with `Assets.car`, plus `CFBundleIconFile` for the legacy
resource. `tauri.dev.conf.json` and the generated release config inherit the
platform icon list instead of overwriting it. The minimum application OS version
is unchanged; compiling the modern catalog for 26 does not raise that minimum.

Normal installs, dev builds, and non-Apple builds use the committed ICNS and need
no Icon Composer installation. After changing the application mark, Composer
document, or legacy layout, regenerate on macOS with **Icon Composer 1.2**:

```bash
# ICON_COMPOSER_APP can point to an alternate location for Icon Composer.app.
pnpm run generate:macos-icons
pnpm run generate-brand-assets
```

The native export manifest records the renderer version and source/output
hashes. Normal asset generation refuses stale native exports instead of silently
shipping a mismatched dev icon. Commit the Composer layer, manifest, and all
three generated ICNS copies together.

Packaging requires **full Xcode 26 or newer**, with `actool` and `assetutil`
selected by `DEVELOPER_DIR` or `xcode-select`. Command Line Tools alone are not
enough. Stable/beta and nightly macOS runners select **Xcode 26.2** on both Intel
and Apple Silicon. The pre-bundle check fails when modern tools are unavailable;
the build wrapper verifies the resulting catalog, both Info.plist icon keys,
and the exact fallback bytes before reporting success. No signed bundle is
modified by verification.

```bash
node --test scripts/generate-brand-assets.test.mjs scripts/macos-icon-bundle.test.mjs scripts/desktop-tauri-build.test.mjs
# Full Xcode: exercise actual Tauri + actool packaging in a disposable fixture.
OPENBITFUN_TEST_MACOS_BUNDLE=1 node --test scripts/macos-icon-bundle.test.mjs
# Inspect an existing bundle without launching it.
node scripts/macos-icon-bundle.mjs verify /path/to/OpenBitFun.app
```

These checks validate resources, native decoding, and package metadata. They do
not establish visual Dock equivalence on every supported OS or display. Remote
workspace, remote control, Peer Device Mode, and detached dispatch are not
exercised by these local packaging checks; their protocols and host logic are
unchanged.

References: [Apple app icon guidance](https://developer.apple.com/design/human-interface-guidelines/app-icons),
[Icon Composer](https://developer.apple.com/documentation/xcode/creating-your-app-icon-using-icon-composer),
[NSApplication.applicationIconImage](https://developer.apple.com/documentation/appkit/nsapplication/applicationiconimage),
and [Tauri 2.11.4 macOS icon bundler](https://github.com/tauri-apps/tauri/blob/tauri-cli-v2.11.4/crates/tauri-bundler/src/bundle/macos/icon.rs).

Browser entry points select explicit 16/32 px favicons. The system tray reuses
the configured application/window icon on every desktop platform.
the macOS template mark on macOS and the configured application icon elsewhere.

Run `pnpm run generate-brand-assets` after changing either source master. The
generator is the single owner of the derived desktop, web, installer, Android,
iOS, and HarmonyOS files.
generator owns the shared desktop, web, installer, Android, iOS, and HarmonyOS
projections. Native macOS exports have the separate owner described above.

Verify generated dimensions, small-size rim contrast, favicon references, and
icon containers with `node --test scripts/generate-brand-assets.test.mjs`.
Verify generated dimensions, small-size rim contrast, favicon references,
platform selection, and icon containers with
`node --test scripts/generate-brand-assets.test.mjs`. macOS export and native
bundling checks also run `scripts/macos-icon-bundle.test.mjs`.

`source/release-letter-mascot.svg` preserves the authored character from
`openbitfun-letter.html`. Its named body, rigid head/rod, and eye parts are used
Expand Down
Binary file modified assets/brand/exports/openbitfun-app-icon.icns
Binary file not shown.
6 changes: 6 additions & 0 deletions assets/brand/macos/generated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"iconComposerVersion": "1.2",
"iconComposerBuild": "76",
"sourceSha256": "9b600c6605029a2b23d480ec5595b7634ffb94dd9c6a2cc91a98d645601f65f0",
"icnsSha256": "04ca39fd01030f7dcdf787670c3e56f006e33e8384eba14e793db75b6765af3e"
}
4 changes: 4 additions & 0 deletions assets/brand/macos/legacy-canvas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"canvasSize": 1024,
"artworkBounds": { "x": 100, "y": 100, "width": 824, "height": 824 }
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions assets/brand/macos/openbitfun.icon/icon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"fill": {
"solid": "srgb:0.00000,0.00000,0.00000,1.00000"
},
"groups": [
{
"name": "Application mark",
"layers": [
{
"image-name": "Mark.png",
"name": "Silver mark",
"glass": false
}
],
"shadow": {
"kind": "none",
"opacity": 0
},
"specular": false,
"translucency": {
"enabled": false,
"value": 0
}
}
],
"supported-platforms": {
"squares": [
"macOS"
]
}
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"agent-harness:generate": "node scripts/generate-agent-harness.mjs",
"agent-harness:check": "node scripts/generate-agent-harness.mjs --check",
"copy-monaco": "copyfiles -u 5 \"src/web-ui/node_modules/monaco-editor/min/vs/**/*\" src/web-ui/public/monaco-editor && node scripts/prune-monaco-nls.cjs",
"check:macos-icons": "node scripts/macos-icon-bundle.mjs preflight",
"generate:macos-icons": "node scripts/generate-macos-icons.mjs",
"generate-brand-assets": "node scripts/generate-brand-assets.mjs",
"copy-assets": "pnpm run copy-monaco && pnpm run generate-brand-assets",
"generate-version": "node scripts/generate-version.cjs --build-env production",
Expand Down Expand Up @@ -78,7 +80,7 @@
"check:core-boundaries": "pnpm run agent-harness:check && node scripts/check-core-boundaries.mjs",
"check:core-boundaries:test": "node --test scripts/check-core-boundaries.test.mjs",
"check:github-config": "pnpm --dir src/web-ui exec node ../../scripts/check-github-config.mjs && node --test scripts/check-github-config.test.mjs",
"test:release-packaging": "node --test scripts/release-channel.test.mjs scripts/desktop-tauri-build.test.mjs scripts/server-build.test.mjs scripts/version-generation.test.mjs scripts/generate-frontend-revision.test.mjs scripts/generate-brand-assets.test.mjs scripts/tauri-release-manifest.test.mjs OpenBitFun-Installer/scripts/build-installer.test.cjs",
"test:release-packaging": "node --test scripts/release-channel.test.mjs scripts/desktop-tauri-build.test.mjs scripts/server-build.test.mjs scripts/version-generation.test.mjs scripts/generate-frontend-revision.test.mjs scripts/generate-brand-assets.test.mjs scripts/macos-icon-bundle.test.mjs scripts/tauri-release-manifest.test.mjs OpenBitFun-Installer/scripts/build-installer.test.cjs",
"fmt:rs": "node scripts/format-changed-rust.mjs",
"lint:rs": "cargo clippy --workspace --exclude openbitfun-desktop --all-targets",
"lint:rs:desktop": "pnpm run prepare:mobile-web && cargo clippy -p openbitfun-desktop --all-targets",
Expand Down Expand Up @@ -174,7 +176,7 @@
},
"devDependencies": {
"@noble/hashes": "^2.0.1",
"@tauri-apps/cli": "^2.10.0",
"@tauri-apps/cli": "2.11.4",
"@vitejs/plugin-react": "^4.6.0",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
Expand Down
Loading
Loading