From 7c34df36cddf21bbce04277d55a5e94838f4cd06 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 14 Aug 2026 04:22:28 -0400 Subject: [PATCH] feat(ui): app icons on the house canvas; detail page gets the tile treatment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - alby-hub + phoenixd icons re-set with the standard 12% inner margin (they shipped edge-to-edge; every other icon carries whitespace). - scripts/normalize-app-icon.py: wraps any third-party SVG mark onto the house canvas — the system applies the tile plate (archy-app-icon) automatically but deliberately no runtime inset, so the margin must be baked; the guide now says exactly that. - MarketplaceAppDetails: the icon now carries archy-app-icon like the store tiles — the treatment no longer stops at the detail page. - v1.8.2 changelog: third curated bullet (the ceremony gate requires 3). Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 3 +- docs/app-developer-guide.md | 14 ++++ .../public/assets/img/app-icons/alby-hub.svg | 8 +- .../public/assets/img/app-icons/phoenixd.svg | 8 +- neode-ui/src/views/MarketplaceAppDetails.vue | 4 +- .../src/views/settings/AccountInfoSection.vue | 3 +- scripts/normalize-app-icon.py | 84 +++++++++++++++++++ 7 files changed, 116 insertions(+), 8 deletions(-) create mode 100755 scripts/normalize-app-icon.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd9a716..d4f57966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## v1.8.2-alpha (2026-08-13) - **An app that can't be shown inside the dashboard now becomes a tab app by itself.** A few apps refuse to render inside another page no matter what — they break out with their own code or insist on owning the whole browser window. Opening one used to mean staring at a grey pane. Now the dashboard notices, offers the app in its own tab, and remembers: from then on that app's button opens a tab directly (with the little launch icon that tab apps carry), first click, every time. If a later update makes the app embeddable after all, the dashboard notices that too and goes back to embedding it. -- **The screensaver and login emblem are back to their clean black finish.** A glossy paint experiment on the circle behind the logo shipped by mistake in the previous update; it's removed everywhere. +- **The logo emblem got its glossy black paint finish — properly this time.** The circle behind the A on the screensaver, intro, and login now wears a deep wet-paint look: warm light blooming from the top edge, fine grain so the dark tones stay smooth instead of banding, and no more ring border. (An earlier rougher version of this experiment briefly shipped by accident and then vanished depending on which screen you were on — this is the finished, deliberate one, everywhere.) +- **New app icons now match the store's look, on every screen.** Alby Hub and phoenixd arrived with edge-to-edge logos that ignored the breathing room every other app icon has, and the app detail page skipped the icon backdrop entirely. Both icons are re-set on the standard canvas, the detail page now applies the same icon treatment as the store tiles, and app developers get a one-command tool that puts any logo onto the house canvas automatically. ## v1.8.1-alpha (2026-08-13) diff --git a/docs/app-developer-guide.md b/docs/app-developer-guide.md index 5dead61e..11d61770 100644 --- a/docs/app-developer-guide.md +++ b/docs/app-developer-guide.md @@ -531,6 +531,20 @@ The broader app update policy for `1.8-alpha` is still being finalized. Until th ## App Icon +- **The tile plate (dark rounded background, border, sheen) is applied by + the system** — every surface renders your icon with the house + `archy-app-icon` treatment automatically. What the system deliberately + does NOT add is an inner margin (icons that already carry whitespace + would double-margin), so your file needs it baked in: **square canvas, + ~12% margin per side**. Edge-to-edge marks look wrong next to every + other tile. Don't hand-tune it: run your mark through the normalizer + and commit its output — + ```bash + scripts/normalize-app-icon.py your-mark.svg apps-icon-output.svg + ``` + It wraps any SVG (full-bleed upstream logos included) onto the house + canvas with the standard margin, preserving the original artwork + untouched inside. - Provide a URL to your app icon (PNG, WebP, or SVG) - Recommended size: 256x256 pixels - Square aspect ratio diff --git a/neode-ui/public/assets/img/app-icons/alby-hub.svg b/neode-ui/public/assets/img/app-icons/alby-hub.svg index 3649a900..a5ac1825 100644 --- a/neode-ui/public/assets/img/app-icons/alby-hub.svg +++ b/neode-ui/public/assets/img/app-icons/alby-hub.svg @@ -1,5 +1,9 @@ - + + + - \ No newline at end of file + + + diff --git a/neode-ui/public/assets/img/app-icons/phoenixd.svg b/neode-ui/public/assets/img/app-icons/phoenixd.svg index 2324e437..5122fcb8 100644 --- a/neode-ui/public/assets/img/app-icons/phoenixd.svg +++ b/neode-ui/public/assets/img/app-icons/phoenixd.svg @@ -1,7 +1,11 @@ - + + + - \ No newline at end of file + + + diff --git a/neode-ui/src/views/MarketplaceAppDetails.vue b/neode-ui/src/views/MarketplaceAppDetails.vue index cfbe6bdd..d856cfea 100644 --- a/neode-ui/src/views/MarketplaceAppDetails.vue +++ b/neode-ui/src/views/MarketplaceAppDetails.vue @@ -23,7 +23,7 @@ v-if="app.icon" :src="app.icon" :alt="app.title" - class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0" + class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0 archy-app-icon" @error="handleImageError" />
@@ -99,7 +99,7 @@ v-if="app.icon" :src="app.icon" :alt="app.title" - class="w-20 h-20 rounded-xl shadow-xl" + class="w-20 h-20 rounded-xl shadow-xl archy-app-icon" @error="handleImageError" />
diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index 0aad7e3a..9d43a625 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -370,7 +370,8 @@ init()

**An app that can't be shown inside the dashboard now becomes a tab app by itself.** A few apps refuse to render inside another page no matter what — they break out with their own code or insist on owning the whole browser window. Opening one used to mean staring at a grey pane. Now the dashboard notices, offers the app in its own tab, and remembers: from then on that app's button opens a tab directly (with the little launch icon that tab apps carry), first click, every time. If a later update makes the app embeddable after all, the dashboard notices that too and goes back to embedding it.

-

**The screensaver and login emblem are back to their clean black finish.** A glossy paint experiment on the circle behind the logo shipped by mistake in the previous update; it's removed everywhere.

+

**The logo emblem got its glossy black paint finish — properly this time.** The circle behind the A on the screensaver, intro, and login now wears a deep wet-paint look: warm light blooming from the top edge, fine grain so the dark tones stay smooth instead of banding, and no more ring border. (An earlier rougher version of this experiment briefly shipped by accident and then vanished depending on which screen you were on — this is the finished, deliberate one, everywhere.)

+

**New app icons now match the store's look, on every screen.** Alby Hub and phoenixd arrived with edge-to-edge logos that ignored the breathing room every other app icon has, and the app detail page skipped the icon backdrop entirely. Both icons are re-set on the standard canvas, the detail page now applies the same icon treatment as the store tiles, and app developers get a one-command tool that puts any logo onto the house canvas automatically.

diff --git a/scripts/normalize-app-icon.py b/scripts/normalize-app-icon.py new file mode 100755 index 00000000..3c92aeee --- /dev/null +++ b/scripts/normalize-app-icon.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Normalize a third-party SVG mark onto the Archipelago app-icon canvas. + +Store tiles render icons as-is — there is deliberately no runtime inset +(it would double-margin every icon that already ships whitespace), so the +margin must be baked into the file. House icons carry it naturally; +third-party marks are usually full-bleed (Alby Hub's tile icon was the +first to land edge-to-edge in the store, 2026-08-14). This wraps any SVG +in a square canvas with a uniform inner margin, preserving the original +untouched as a nested . + +Usage: + scripts/normalize-app-icon.py in.svg out.svg [--margin 0.12] + +Margin is a fraction of the canvas per side (default 12%, the house look). +""" + +import argparse +import re +import sys + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("src") + ap.add_argument("dst") + ap.add_argument("--margin", type=float, default=0.12, + help="inner margin per side, fraction of canvas (default 0.12)") + args = ap.parse_args() + + svg = open(args.src, encoding="utf-8").read() + + m = re.search(r"]*>", svg, re.S) + if not m: + print("error: no root element found", file=sys.stderr) + return 1 + root = m.group(0) + + vb = re.search(r'viewBox\s*=\s*["\']([\d.eE+\s,-]+)["\']', root) + if vb: + nums = [float(x) for x in re.split(r"[\s,]+", vb.group(1).strip())] + if len(nums) != 4: + print("error: unparseable viewBox", file=sys.stderr) + return 1 + _, _, w, h = nums + inner_viewbox = vb.group(1).strip() + else: + wm = re.search(r'width\s*=\s*["\']([\d.]+)', root) + hm = re.search(r'height\s*=\s*["\']([\d.]+)', root) + if not (wm and hm): + print("error: no viewBox and no width/height to derive one", file=sys.stderr) + return 1 + w, h = float(wm.group(1)), float(hm.group(1)) + inner_viewbox = f"0 0 {w} {h}" + + # Square canvas fitting the larger dimension; the mark centers in it. + side = max(w, h) + canvas = 100.0 + margin = canvas * args.margin + avail = canvas - 2 * margin + scale_w = avail * (w / side) + scale_h = avail * (h / side) + x = (canvas - scale_w) / 2 + y = (canvas - scale_h) / 2 + + # Strip any XML prolog from the original; it nests inside the wrapper. + inner = re.sub(r"^\s*<\?xml[^>]*\?>\s*", "", svg) + + out = ( + f'\n' + f' \n' + f' \n' + f'{inner}\n' + f' \n' + f'\n' + ) + open(args.dst, "w", encoding="utf-8").write(out) + print(f"wrote {args.dst}: {canvas:g}x{canvas:g} canvas, mark {scale_w:.1f}x{scale_h:.1f} at {args.margin:.0%} margin") + return 0 + + +if __name__ == "__main__": + sys.exit(main())