diff --git a/README.md b/README.md index be469df..f60e4b3 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ cd ../.. scripts/export-patches.sh # rewrite patches/ from those commits ``` +The icons are the one thing that is generated rather than written. Edit +`build/upstream/apps/app/icons/modrinth-enhanced.svg`, run `scripts/render-icons.py`, and every PNG, +`.ico` and `.icns` next to it is rewritten from that source; rendering the unchanged source again +reproduces the current files byte for byte. + Patches are applied with `git am --3way`, so small upstream movements around a hunk resolve by themselves. When one genuinely conflicts, `scripts/prepare.sh` stops and leaves the conflict staged in `build/upstream` to be resolved with `git am --continue`, after which `scripts/export-patches.sh` diff --git a/patches/0004-Remove-telemetry.patch b/patches/0004-Remove-telemetry.patch index 2de7247..3d4a3b6 100644 --- a/patches/0004-Remove-telemetry.patch +++ b/patches/0004-Remove-telemetry.patch @@ -3,7 +3,7 @@ From: Modrinth Enhanced Date: Mon, 14 Sep 2026 10:23:01 +0200 Subject: [PATCH] Remove telemetry -Product analytics, crash reporting and the user survey embed are all +Product analytics, crash reporting and the survey embeds are all removed, and the Modrinth analytics endpoints the launcher itself posts to are no longer called: @@ -13,9 +13,12 @@ to are no longer called: PostHog SDK is no longer part of the bundle at all. * `helpers/error-reporting.ts` no longer loads `@sentry/vue`; errors are logged locally, as they are in upstream development builds. -* The third-party Tally embed is dropped from `index.html` and the - survey list is no longer fetched, so the popup that carries the - signed-in Modrinth user id never appears. +* The third-party Tally embed is gone. It was loaded from `index.html` + on every start for the app's own survey popup, and injected again by + the Modrinth Hosting panel in `@modrinth/ui` when a server carries a + survey notice. Neither loads it now, and the app no longer fetches the + survey list, so the popup that carries the signed-in Modrinth user id + never appears. * `analytics/playtime` and `analytics/minecraft-server-play` are no longer posted from `app-lib`. The latter also removes a Mojang session handshake whose only purpose was to authenticate that report. @@ -38,7 +41,8 @@ for them. .../app-frontend/src/locales/en-US/index.json | 2 +- apps/app/tauri.conf.json | 6 +- packages/app-lib/src/api/instance/run.rs | 83 +++-------------- - 8 files changed, 50 insertions(+), 218 deletions(-) + .../layouts/wrapped/hosting/manage/root.vue | 8 +- + 9 files changed, 53 insertions(+), 223 deletions(-) diff --git a/apps/app-frontend/index.html b/apps/app-frontend/index.html index 6367788..9738bbc 100644 @@ -449,3 +453,22 @@ index 25626c1..d638128 100644 } else { Ok(()) }; +diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue +index 9ccd656..065aad6 100644 +--- a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue ++++ b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue +@@ -906,11 +906,9 @@ function showSurvey() { + } + + function loadTallyScript() { +- if (document.querySelector('script[src*="tally.so"]')) return +- const script = document.createElement('script') +- script.src = 'https://tally.so/widgets/embed.js' +- script.defer = true +- document.head.appendChild(script) ++ // Modrinth Enhanced does not load the third-party Tally embed. Without it ++ // `getTally()` stays undefined and `showSurvey()` above does nothing, so a ++ // survey notice on a hosted server is simply not turned into a popup. + } + + async function handleInstallationRetry() { diff --git a/scripts/render-icons.py b/scripts/render-icons.py new file mode 100755 index 0000000..92e2bd1 --- /dev/null +++ b/scripts/render-icons.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Render the Modrinth Enhanced icon set from its vector source. + +The source lives in the patched checkout as +`apps/app/icons/modrinth-enhanced.svg`; this rewrites every generated icon +next to it. Run it after editing that SVG, then commit the result in +build/upstream and re-export the patches. + +Needs `rsvg-convert` (librsvg). ImageMagick is not used: the ICNS and ICO +writers below are a few lines each and work the same everywhere, whereas +ImageMagick's ICNS support depends on how it was built. +""" + +import os +import shutil +import struct +import subprocess +import sys +import tempfile + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +ICONS = os.path.join( + os.environ.get("WORKTREE", os.path.join(REPO_ROOT, "build", "upstream")), + "apps", + "app", + "icons", +) +SVG = os.path.join(ICONS, "modrinth-enhanced.svg") + +# Every PNG the Tauri bundles reference, and the size it has to be. +PNGS = { + "icon.png": 512, + "128x128.png": 128, + "128x128@2x.png": 256, + "StoreLogo.png": 50, + "Square30x30Logo.png": 30, + "Square44x44Logo.png": 44, + "Square71x71Logo.png": 71, + "Square89x89Logo.png": 89, + "Square107x107Logo.png": 107, + "Square142x142Logo.png": 142, + "Square150x150Logo.png": 150, + "Square284x284Logo.png": 284, + "Square310x310Logo.png": 310, +} + +ICO_SIZES = [16, 24, 32, 48, 64, 256] +FAVICON_SIZES = [16, 24, 32, 64] + +# ICNS chunk type -> pixel size. All chunks carry PNG payloads, which macOS +# 10.7 and newer understand. +ICNS_TYPES = [ + (b"icp4", 16), + (b"icp5", 32), + (b"ic11", 32), + (b"ic12", 64), + (b"ic07", 128), + (b"ic13", 256), + (b"ic08", 256), + (b"ic14", 512), + (b"ic09", 512), + (b"ic10", 1024), +] + + +def render(size, path): + """Rasterise the source at exactly `size`, rather than downscaling one + large render, so the small sizes stay crisp.""" + subprocess.run( + ["rsvg-convert", "-w", str(size), "-h", str(size), SVG, "-o", path], + check=True, + ) + + +def build_ico(sizes, cache, path): + entries, blobs, offset = [], [], 6 + 16 * len(sizes) + for size in sizes: + with open(cache[size], "rb") as handle: + data = handle.read() + entries.append( + struct.pack( + "I", len(data) + 8) + data + with open(path, "wb") as handle: + handle.write(b"icns" + struct.pack(">I", len(chunks) + 8) + chunks) + + +def main(): + if not shutil.which("rsvg-convert"): + sys.exit("rsvg-convert is required (install librsvg)") + if not os.path.isfile(SVG): + sys.exit(f"No icon source at {SVG}. Run scripts/prepare.sh first.") + + with tempfile.TemporaryDirectory() as tmp: + cache = {} + needed = set(PNGS.values()) | set(ICO_SIZES) | set(FAVICON_SIZES) + needed |= {size for _, size in ICNS_TYPES} + for size in sorted(needed): + cache[size] = os.path.join(tmp, f"{size}.png") + render(size, cache[size]) + + for name, size in PNGS.items(): + shutil.copyfile(cache[size], os.path.join(ICONS, name)) + build_ico(ICO_SIZES, cache, os.path.join(ICONS, "icon.ico")) + build_ico(FAVICON_SIZES, cache, os.path.join(ICONS, "favicon.ico")) + build_icns(cache, os.path.join(ICONS, "icon.icns")) + + for name in sorted(os.listdir(ICONS)): + full = os.path.join(ICONS, name) + if os.path.isfile(full): + print(f"{name:24s} {os.path.getsize(full):>8d} bytes") + + +if __name__ == "__main__": + main()