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
2 changes: 2 additions & 0 deletions .github/workflows/_ci-desktop-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Verify macOS icon representations
run: scripts/macos-icon.sh --check
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
with:
workspaces: desktop/src-tauri
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ jobs:
- 'scripts/normative-corpus.json'
- 'justfile'
desktop:
- 'scripts/generate-macos-icon.py'
- 'scripts/macos-icon.sh'
- 'bin/uv'
- 'bin/.uv-*.pkg'
- 'scripts/model-capabilities.json'
- 'scripts/normative-corpus.json'
- 'desktop/**'
Expand Down
1 change: 1 addition & 0 deletions bin/.uv-0.12.21.pkg
1 change: 1 addition & 0 deletions bin/uv
1 change: 1 addition & 0 deletions bin/uvx
Binary file modified desktop/src-tauri/icons/icon.icns
Binary file not shown.
13 changes: 11 additions & 2 deletions desktop/tests/e2e/image-attachment-gallery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,10 @@ test("hidden spoiler images are excluded from gallery navigation until revealed"
.last();
await expect(row).toBeVisible();

await row.locator(`img[src*="${SPOILER_VISIBLE_SHA}"]`).click();
const visibleImage = row.locator(`img[src*="${SPOILER_VISIBLE_SHA}"]`);
await visibleImage.scrollIntoViewIfNeeded();
await waitForAnimations(page);
await visibleImage.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
await expect(page.getByRole("button", { name: "Next image" })).toHaveCount(0);
Expand All @@ -723,7 +726,13 @@ test("hidden spoiler images are excluded from gallery navigation until revealed"
await spoiler.click();
await expect(spoiler).toHaveAttribute("data-revealed", "true");

await row.locator(`img[src*="${SPOILER_VISIBLE_SHA}"]`).click();
// The gallery snapshots visible images when opened; the state attribute
// changes before the spoiler image finishes its opacity transition.
await expect(spoiler.locator(`img[src*="${SPOILER_HIDDEN_SHA}"]`)).toHaveCSS(
"opacity",
"1",
);
await visibleImage.click();
await expect(dialog).toBeVisible();
await expect(page.getByRole("button", { name: "Next image" })).toBeVisible();
});
Expand Down
94 changes: 94 additions & 0 deletions scripts/generate-macos-icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Build the legacy macOS icon without relying on system corner masking.

Run: scripts/macos-icon.sh [--check]

The wrapper pins uv, Python and Pillow. Verification compares the complete set
of decoded RGBA representations, not PNG encoding or ICNS container bytes:
Apple's system iconutil may encode the same pixels differently across macOS.
The shared artwork and other platforms' icons are intentionally untouched.
"""

# /// script
# requires-python = "==3.14.3"
# dependencies = ["Pillow==12.2.0"]
# ///

import argparse
from pathlib import Path
import subprocess
import tempfile

from PIL import Image, ImageDraw


ROOT = Path(__file__).resolve().parent.parent
ICONS = ROOT / "desktop/src-tauri/icons"


def generate(output):
# An 824px tile on a 1024px canvas leaves the macOS optical inset.
# Work at 4x resolution to antialias the rounded edge at every icon size.
scale = 4
canvas = Image.new("RGBA", (1024 * scale, 1024 * scale))
with Image.open(ICONS / "buzz-source.png") as source:
tile = source.convert("RGBA").resize(
(824 * scale, 824 * scale), Image.Resampling.LANCZOS
)
mask = Image.new("L", tile.size)
ImageDraw.Draw(mask).rounded_rectangle(
(0, 0, tile.width - 1, tile.height - 1), radius=185 * scale, fill=255
)
canvas.paste(tile, (100 * scale, 100 * scale), mask)

with tempfile.TemporaryDirectory(prefix="buzz-macos-icon-") as temporary:
iconset = Path(temporary) / "Buzz.iconset"
iconset.mkdir()
for size in (16, 32, 128, 256, 512):
for density in (1, 2):
suffix = "@2x" if density == 2 else ""
canvas.resize(
(size * density, size * density), Image.Resampling.LANCZOS
).save(iconset / f"icon_{size}x{size}{suffix}.png")
subprocess.run(
["/usr/bin/iconutil", "-c", "icns", str(iconset), "-o", str(output)],
check=True,
)


def verify(candidate, reference):
"""Reject missing/extra representations and any decoded pixel drift."""
with tempfile.TemporaryDirectory(prefix="buzz-icon-check-") as temporary:
sets = []
for name, icon in (("candidate", candidate), ("reference", reference)):
iconset = Path(temporary) / f"{name}.iconset"
subprocess.run(
["/usr/bin/iconutil", "-c", "iconset", str(icon), "-o", str(iconset)],
check=True,
)
sets.append({p.name: p for p in iconset.glob("*.png")})
if sets[0].keys() != sets[1].keys():
raise ValueError("macOS icon representation set drifted")
for name in sorted(sets[0]):
with Image.open(sets[0][name]) as a, Image.open(sets[1][name]) as b:
if a.size != b.size or a.convert("RGBA").tobytes() != b.convert("RGBA").tobytes():
raise ValueError(f"macOS icon pixel drift: {name}")


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true", help="verify without modifying the icon")
parser.add_argument("--output", type=Path, default=ICONS / "icon.icns")
args = parser.parse_args()
if args.check:
with tempfile.TemporaryDirectory(prefix="buzz-icon-regenerate-") as temporary:
generated = Path(temporary) / "icon.icns"
generate(generated)
verify(args.output, generated)
print("macOS icon: all decoded representations match")
else:
generate(args.output)


if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions scripts/macos-icon.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# Hermit pins uv; inline script metadata pins Python and Pillow exactly.
# uv script environments are isolated from the caller's Python packages.
exec "$ROOT/bin/uv" run --managed-python --python 3.14.3 \
--script "$ROOT/scripts/generate-macos-icon.py" "$@"
Loading