From b170b3cc3613456832f610aa2e7b3ad12dd0325d Mon Sep 17 00:00:00 2001 From: joaovictor91123 Date: Mon, 13 Jul 2026 13:42:08 +0400 Subject: [PATCH] feat(miner-extension): add a standard icon set to the manifest The miner extension's manifest had no icons defined at all. Adds the standard MV3 sizes (16/32/48/128), generated via generate-icons.mjs by resizing the repo's own favicon-512.png brand asset through sharp (already a devDependency) rather than drawing a separate/synthetic icon, so the extension's icon always matches the main site's mark. Wires the sizes into manifest.json's top-level icons and action.default_icon, and into build-miner-extension.mjs's PACKAGE_FILES so the packaged zip actually ships them. Closes #4862 --- apps/gittensory-miner-extension/README.md | 7 +++ .../icons/generate-icons.mjs | 23 ++++++++++ .../icons/icon-128.png | Bin 0 -> 2834 bytes .../icons/icon-16.png | Bin 0 -> 535 bytes .../icons/icon-32.png | Bin 0 -> 960 bytes .../icons/icon-48.png | Bin 0 -> 1491 bytes apps/gittensory-miner-extension/manifest.json | 14 +++++- scripts/build-miner-extension.mjs | 5 +++ test/unit/miner-extension-icons.test.ts | 40 ++++++++++++++++++ 9 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 apps/gittensory-miner-extension/icons/generate-icons.mjs create mode 100644 apps/gittensory-miner-extension/icons/icon-128.png create mode 100644 apps/gittensory-miner-extension/icons/icon-16.png create mode 100644 apps/gittensory-miner-extension/icons/icon-32.png create mode 100644 apps/gittensory-miner-extension/icons/icon-48.png create mode 100644 test/unit/miner-extension-icons.test.ts 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 0000000000000000000000000000000000000000..a686f070085162af874092f76e8baf8e9d72e4e7 GIT binary patch literal 2834 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9913979eda100facfa5a09c796d23d0065035acb GIT binary patch literal 535 zcmV+y0_gpTP)(RCt_?R8319K@hEk1a!BnyLTA82qNa>>bfq7r|4|__ywMF z&n-Vf2>}s7f?|+>LL!6&Z<2fNk{o1F5kW=WpdPY}Wg`HPiL+s;lZX z5mAZ_PixEP+A_4|>rf}jt?4q ZxB=1(ZBIN>O!@!-002ovPDHLkV1g!w>E!?b literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f9df7e1cc0a4d390f56a80db93aaca6f5e7d57f GIT binary patch literal 960 zcmV;x13&zUP)c_3MvYis3@g{#0N?ON)#-|oZA`fYg#%|7GE;$ zwDGTdVP;!=Oc4egT2X#qe8(aI!yE(}gTT(1Io z1_oaz4kq+L0PrYs0MMA!<0knkmn>a10h0aUlC23ZM{ej36+08`W)^w1hzo=erx5yi zV3Lh-Lw%Xvb^($-0xsE#(}RS;Py89SFGqKS zhI^VN_Cy=8JDiKIU?Un{h8|s4)+lurR7!66IM%x~Jk;UCp^;8}`zef*sSr-hWkA>O zK8JB&@F;ryT!|=U32;Q-?!)1D7tSn3a3U4NsrgV2NJMHmh695~l(m?!cLz8n|M)$E zuMqNgtc%M#=ciU~VcfK&2UiJ%f-i`gKK&z!*S(0>XrY{=>9=6=MAg3LevGlt2f z81_W|Lx4f#?&9Q+#{$@Y_W)kI)rQw^7vjd9HoV%mADj0Xs4@FM1p8EDD%B|3H+()D z#EE1OXBMM)`ARDSxUjBX!9B$uN2M`2@5y;5H#m4T?!N3bY(#38>y$mW$QQipKiY zcEpALz$}?-RklQWgIwxyS_`fH&_fHOvV_5_X@$&kN{g@%trq#ls!Xf1TA|e{z2)Sz i^1q~40Q4%r;{FR~1+;~Zt)e3U0000bi(VK)2Pr?H( zuCZ&m{6=tL90Ed+Wf(1!d|DpsiSFYWq4_v)J$oT}6QGrH@OV{lxfO61Wd`LGL}~T{ z@<5(J$uSkGs(sMZ9)yP0AT+e4$7?O8psZX-vG{*Z-<5L#x}Slo$P3LKA-FRUhUrH! zm|BWKe0e9PmSQly5`*hq=b_?^4#zysMdVa~?qf1!n%YA!cs~MnA4H*dG7SCk-O)Q4 zmSQ?@)xha9I(QW7NCe3#`630HF9#+0k+~@J$0PE7FZ7{^r5Jqw(|H+Alp6`)zyx46 zv08p)E&>BK$q$<7p9;gh#d>JG6oldu3?+Ur0utmg<`b45m<~H4KX^X^f6heU?tBzl zuY?Xxz)s6!*f=LYXbKp67=@P0C_u@-0As-#*=BGinLLsih==9XE(WF}F#0eGZEqZa z0GTS?Uc>T|JQCO@KRg?O$;EnUQM(wOkHW;?_3&!bDOp4ECLlq6JLZ$g56woTC$4^2 z39m%UZR28nIn-VZK&fALnYQ^j5`g(xF4&})w`7vIk<&l>su~FKz;TZ%t!W#_RY&9< z2|$UB0e@NMMov!yyZPUd0Bp3dLRLNJgF6#-vcjgzHVF@4 z_z;&@h66TgcAzo}Ue&nIxd53ii!faVrG@*Z(x=LVfOkKr1b4BKNze;eT{FQS5OAVg zz}aU?Ay}b15@A~aw!N>n24w@bTdv2y&#c7Yy^o#-LW97Wy8^aaf(KEUUXH=oLcM$zi&QEw42Nd%`7ktI3IQQr(0tCw z=S4t5M4u#{{yD7D!+%BPeM?73W-NS%Q}THekg@BG8z~s?n>#|Xu3%e|4fz8XfF2p1 zGw!izbGf~;vS&-)xq!CJS!gfv*mfjY-oHQcjs@Tbd?rI3uy6VOg$;Kq0LwdW^U6*N zxZd@W3?tkI+KYK4Z^nH4{uV6HRzRYW!*&9{9{kW<1MwvzMefz(V@gem`5cnxj=NDG zlb=3LKc&eHIQINlxOVIBu|Q$()U=gta!a0PvmcOO+!QK%7uCpCO2%fF> z$u8FJ28CxK!11R=V@*)G_1gz4 z(k0H7#*_mprYizOrGkFGH4WDb?+KAOcw)?jdsJPmPMoSqJ+Xo!jDsISImc0mV7*+h te+xyP3Hq1`HpB$|29E#{LW)x@|33}~(>0Z{lPCZH002ovPDHLkV1mpQoJ#-z literal 0 HcmV?d00001 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"); + }); +});