authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-18 15:44:06+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-18 15:44:06+02:00
logebe8afb1df357423a0e036b388af3e739d640205
tree50611ae39a4c88a08adb009841c1046dd8dddbbb
parentbf76535fe4da71d8de7b8bd5ffa0d2086b7af8d0
signature Signed by PGP key B5690EEEBB952194

Refresh splash screen and favicon branding (#4120)


10 files changed, 58 insertions(+), 3 deletions(-)

apps/web/public/apple-touch-icon.png
Binary files a/apps/web/public/apple-touch-icon.png and b/apps/web/public/apple-touch-icon.png differ
apps/web/public/favicon-16x16.png
Binary files a/apps/web/public/favicon-16x16.png and b/apps/web/public/favicon-16x16.png differ
apps/web/public/favicon-32x32.png
Binary files a/apps/web/public/favicon-32x32.png and b/apps/web/public/favicon-32x32.png differ
apps/web/public/favicon.ico
Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ
assets/README.md+1-1
......@@ -8,7 +8,7 @@ The three Icon Composer projects are the source of truth for full application ic
88
99Each project uses `text.svg` for the T3 mark and `background.svg` when the background is a vector layer. Additional layers use semantic names that describe their role and placement.
1010
11Run `vp run icons:export` from the repository root to regenerate the tracked iOS, Linux, Windows, and web assets. Run `vp run icons:check` to verify that those generated assets match their sources without changing files.
11Run `vp run icons:export` from the repository root to regenerate the tracked iOS, Linux, Windows, and web assets. The development web exports are also copied to `apps/web/public` for the browser favicon and splash screen. Run `vp run icons:check` to verify that the generated assets and public copies match their sources without changing files.
1212
1313Exporting requires Icon Composer 2 or newer on macOS. The script selects the newest compatible exporter from Xcode or a standalone Icon Composer installation and pins design generation 26. Set `ICON_COMPOSER_TOOL` to the full path of `Icon Composer.app/Contents/Executables/ictool` to override automatic discovery.
1414
scripts/build-desktop-artifact.test.ts+6
......@@ -30,6 +30,7 @@ import {
3030 resolveDesktopBuildIconAssets,
3131 resolveDesktopProductName,
3232 resolveDesktopUpdateChannel,
33 resolveDesktopWebAssetBrand,
3334 resolveGitHubPublishConfig,
3435 resolveMockUpdateServerPort,
3536 resolveMockUpdateServerUrl,
......@@ -102,6 +103,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
102103 });
103104 });
104105
106 it("switches the bundled splash and favicon branding for nightly versions", () => {
107 assert.equal(resolveDesktopWebAssetBrand("0.0.17"), "production");
108 assert.equal(resolveDesktopWebAssetBrand("0.0.17-nightly.20260413.42"), "nightly");
109 });
110
105111 it.effect("resolves GitHub desktop publish config from Effect config", () =>
106112 Effect.gen(function* () {
107113 const latestConfig = yield* resolveGitHubPublishConfig("latest").pipe(
scripts/build-desktop-artifact.ts+13-1
......@@ -10,7 +10,12 @@ import rootPackageJson from "../package.json" with { type: "json" };
1010import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
1111import serverPackageJson from "../apps/server/package.json" with { type: "json" };
1212
13import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
13import { applyWebBrandAssets } from "./apply-web-brand-assets.ts";
14import {
15 BRAND_ASSET_PATHS,
16 resolveWebAssetBrandForChannel,
17 type WebAssetBrand,
18} from "./lib/brand-assets.ts";
1419import { getDefaultBuildArch } from "./lib/build-target-arch.ts";
1520import { loadRepoEnv } from "./lib/public-config.ts";
1621import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
......@@ -1324,6 +1329,10 @@ export function resolveDesktopUpdateChannel(version: string): "latest" | "nightl
13241329 return /-nightly\.\d{8}\.\d+$/.test(version) ? "nightly" : "latest";
13251330}
13261331
1332export function resolveDesktopWebAssetBrand(version: string): WebAssetBrand {
1333 return resolveWebAssetBrandForChannel(resolveDesktopUpdateChannel(version));
1334}
1335
13271336export function resolveDesktopBuildIconAssets(version: string): DesktopBuildIconAssets {
13281337 if (resolveDesktopUpdateChannel(version) === "nightly") {
13291338 return {
......@@ -1665,6 +1674,9 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
16651674 });
16661675 }
16671676
1677 const webAssetBrand = resolveDesktopWebAssetBrand(appVersion);
1678 yield* applyWebBrandAssets(webAssetBrand, "apps/server/dist/client");
1679 yield* Effect.log(`[desktop-artifact] Applied ${webAssetBrand} web client branding.`);
16681680 yield* validateBundledClientAssets(path.dirname(bundledClientEntry));
16691681
16701682 yield* fs.makeDirectory(path.join(stageAppDir, "apps/desktop"), { recursive: true });
scripts/export-brand-icons.ts+11-1
......@@ -13,7 +13,7 @@ import * as Stream from "effect/Stream";
1313import { Command, Flag } from "effect/unstable/cli";
1414import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
1515
16import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
16import { BRAND_ASSET_PATHS, DEVELOPMENT_PUBLIC_ICON_OVERRIDES } from "./lib/brand-assets.ts";
1717import { encodePngIco, readPngDimensions, WINDOWS_ICON_SIZES } from "./lib/icon-export.ts";
1818
1919const DESIGN_GENERATION = 26;
......@@ -751,6 +751,16 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn
751751 }
752752 }
753753
754 for (const override of DEVELOPMENT_PUBLIC_ICON_OVERRIDES) {
755 const sourceContents = generated.get(override.sourceRelativePath);
756 if (sourceContents === undefined) {
757 return yield* Effect.die(
758 new Error(`Generated development web icon is missing: ${override.sourceRelativePath}`),
759 );
760 }
761 generated.set(override.targetRelativePath, sourceContents);
762 }
763
754764 if (checkOnly) {
755765 const stale = yield* Effect.filter(
756766 [...generated.entries()],
scripts/lib/brand-assets.test.ts+22
......@@ -3,6 +3,7 @@ import { describe, expect, it } from "vite-plus/test";
33import {
44 BRAND_ASSET_PATHS,
55 DEVELOPMENT_ICON_OVERRIDES,
6 DEVELOPMENT_PUBLIC_ICON_OVERRIDES,
67 PUBLISH_ICON_OVERRIDES,
78 resolveWebAssetBrandForChannel,
89 resolveWebIconOverrides,
......@@ -37,6 +38,27 @@ describe("brand-assets", () => {
3738 });
3839 });
3940
41 it("maps development web assets to the public splash and favicon files", () => {
42 expect(DEVELOPMENT_PUBLIC_ICON_OVERRIDES).toEqual([
43 {
44 sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFaviconIco,
45 targetRelativePath: "apps/web/public/favicon.ico",
46 },
47 {
48 sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon16Png,
49 targetRelativePath: "apps/web/public/favicon-16x16.png",
50 },
51 {
52 sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon32Png,
53 targetRelativePath: "apps/web/public/favicon-32x32.png",
54 },
55 {
56 sourceRelativePath: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng,
57 targetRelativePath: "apps/web/public/apple-touch-icon.png",
58 },
59 ]);
60 });
61
4062 it("can target hosted web dist directly", () => {
4163 expect(resolveWebIconOverrides("production", "apps/web/dist")).toContainEqual({
4264 sourceRelativePath: BRAND_ASSET_PATHS.productionWebAppleTouchIconPng,
scripts/lib/brand-assets.ts+5
......@@ -101,4 +101,9 @@ export function resolveWebIconOverrides(
101101
102102export const DEVELOPMENT_ICON_OVERRIDES = resolveWebIconOverrides("development", "dist/client");
103103
104export const DEVELOPMENT_PUBLIC_ICON_OVERRIDES = resolveWebIconOverrides(
105 "development",
106 "apps/web/public",
107);
108
104109export const PUBLISH_ICON_OVERRIDES = resolveWebIconOverrides("production", "dist/client");