diff --git a/apps/gittensory-miner-extension/README.md b/apps/gittensory-miner-extension/README.md index 02506e396..f287babd7 100644 --- a/apps/gittensory-miner-extension/README.md +++ b/apps/gittensory-miner-extension/README.md @@ -28,6 +28,13 @@ The extension does not request the `unlimitedStorage` permission, so a paste is being parsed or saved once it exceeds a conservative size bound well under `chrome.storage.local`'s default 10 MiB quota, instead of silently failing to save or leaving storage partially written. +## Icons + +`icons/` holds the standard MV3 icon set (16/32/48/128) referenced from `manifest.json`'s top-level +`icons` and `action.default_icon`. Regenerate with `node icons/generate-icons.mjs` after the underlying +brand asset changes — it resizes `apps/gittensory-ui/public/favicon-512.png` via `sharp`, so the +extension's icon is always the same mark as the main site's favicon rather than a separately drawn asset. + ## Host permissions `manifest.json` grants `https://github.com/*` (for the issue-page content script) plus loopback host permissions — diff --git a/apps/gittensory-miner-extension/icons/generate-icons.mjs b/apps/gittensory-miner-extension/icons/generate-icons.mjs new file mode 100644 index 000000000..95b0bef75 --- /dev/null +++ b/apps/gittensory-miner-extension/icons/generate-icons.mjs @@ -0,0 +1,23 @@ +#!/usr/bin/env node +// Regenerates the MV3 icon set (#4862) from the repo's own brand asset -- never hand-drawn/synthesized +// geometry, so the extension's icon is always the same mark as apps/gittensory-ui's favicon. Standard +// Chrome/Firefox MV3 sizes: 16 (toolbar/favicon), 32 (Windows/high-DPI toolbar), 48 (extensions page), +// 128 (Chrome Web Store listing + install dialog). +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import sharp from "sharp"; + +const here = dirname(fileURLToPath(import.meta.url)); +const sourceIcon = resolve(here, "../../gittensory-ui/public/favicon-512.png"); +const sizes = [16, 32, 48, 128]; + +await Promise.all( + sizes.map((size) => + sharp(sourceIcon) + .resize(size, size) + .png() + .toFile(resolve(here, `icon-${size}.png`)), + ), +); + +console.log(`wrote icon-${sizes.join(".png, icon-")}.png from ${sourceIcon}`); diff --git a/apps/gittensory-miner-extension/icons/icon-128.png b/apps/gittensory-miner-extension/icons/icon-128.png new file mode 100644 index 000000000..a686f0700 Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-128.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-16.png b/apps/gittensory-miner-extension/icons/icon-16.png new file mode 100644 index 000000000..9913979ed Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-16.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-32.png b/apps/gittensory-miner-extension/icons/icon-32.png new file mode 100644 index 000000000..1f9df7e1c Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-32.png differ diff --git a/apps/gittensory-miner-extension/icons/icon-48.png b/apps/gittensory-miner-extension/icons/icon-48.png new file mode 100644 index 000000000..15442aee5 Binary files /dev/null and b/apps/gittensory-miner-extension/icons/icon-48.png differ diff --git a/apps/gittensory-miner-extension/manifest.json b/apps/gittensory-miner-extension/manifest.json index f27558bd7..fe6fc4923 100644 --- a/apps/gittensory-miner-extension/manifest.json +++ b/apps/gittensory-miner-extension/manifest.json @@ -3,6 +3,12 @@ "name": "LoopOver Miner Opportunity", "description": "Contributor-facing GitHub issue opportunity signals from a locally configured miner plane.", "version": "0.1.0", + "icons": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + }, "permissions": ["storage"], "host_permissions": ["https://github.com/*", "http://localhost/*", "http://127.0.0.1/*"], "background": { @@ -19,6 +25,12 @@ ], "options_page": "options.html", "action": { - "default_title": "LoopOver Miner" + "default_title": "LoopOver Miner", + "default_icon": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } } } diff --git a/scripts/build-miner-extension.mjs b/scripts/build-miner-extension.mjs index 67d4a4fd4..9d0fc7432 100644 --- a/scripts/build-miner-extension.mjs +++ b/scripts/build-miner-extension.mjs @@ -22,10 +22,15 @@ const PACKAGE_FILES = [ "options.js", "styles.css", "toolbar-badge.js", + "icons/icon-16.png", + "icons/icon-32.png", + "icons/icon-48.png", + "icons/icon-128.png", ]; rmSync(outDir, { recursive: true, force: true }); mkdirSync(outDir, { recursive: true }); +mkdirSync(resolve(outDir, "icons"), { recursive: true }); for (const file of PACKAGE_FILES) { cpSync(resolve(source, file), resolve(outDir, file)); diff --git a/test/unit/miner-extension-icons.test.ts b/test/unit/miner-extension-icons.test.ts new file mode 100644 index 000000000..fa280c82b --- /dev/null +++ b/test/unit/miner-extension-icons.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from "node:fs"; +import sharp from "sharp"; +import { describe, expect, it } from "vitest"; + +const EXTENSION_DIR = "apps/gittensory-miner-extension"; +const ICON_SIZES = [16, 32, 48, 128]; + +describe("miner extension icon set (#4862)", () => { + const manifest = JSON.parse(readFileSync(`${EXTENSION_DIR}/manifest.json`, "utf8")); + + it("wires every icon size into the manifest's top-level icons and action.default_icon", () => { + for (const size of ICON_SIZES) { + const path = `icons/icon-${size}.png`; + expect(manifest.icons[String(size)]).toBe(path); + expect(manifest.action.default_icon[String(size)]).toBe(path); + } + }); + + it("ships a real PNG at the declared size for every icon", async () => { + for (const size of ICON_SIZES) { + const metadata = await sharp(`${EXTENSION_DIR}/icons/icon-${size}.png`).metadata(); + expect(metadata.format).toBe("png"); + expect(metadata.width).toBe(size); + expect(metadata.height).toBe(size); + } + }); + + it("packages every icon file into the built zip's PACKAGE_FILES list", () => { + const buildScript = readFileSync("scripts/build-miner-extension.mjs", "utf8"); + for (const size of ICON_SIZES) { + expect(buildScript).toContain(`icons/icon-${size}.png`); + } + }); + + it("regenerates the icons from the repo's own brand asset, not a hand-drawn/synthesized source", () => { + const generator = readFileSync(`${EXTENSION_DIR}/icons/generate-icons.mjs`, "utf8"); + expect(generator).toContain("favicon-512.png"); + expect(generator).toContain("sharp"); + }); +});