diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png index e0e1b9659b8..3eed25ea6b7 100644 Binary files a/apps/web/public/apple-touch-icon.png and b/apps/web/public/apple-touch-icon.png differ diff --git a/apps/web/public/favicon-16x16.png b/apps/web/public/favicon-16x16.png index 673d8459998..a3431b8c6df 100644 Binary files a/apps/web/public/favicon-16x16.png and b/apps/web/public/favicon-16x16.png differ diff --git a/apps/web/public/favicon-32x32.png b/apps/web/public/favicon-32x32.png index 25bcc95d4aa..862f7629971 100644 Binary files a/apps/web/public/favicon-32x32.png and b/apps/web/public/favicon-32x32.png differ diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico index 36975b99782..750da22602e 100644 Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ diff --git a/assets/README.md b/assets/README.md index 8a7e3c5f20a..8c53266bfe7 100644 --- a/assets/README.md +++ b/assets/README.md @@ -8,7 +8,7 @@ The three Icon Composer projects are the source of truth for full application ic Each 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. -Run `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. +Run `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. Exporting 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. diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index c79fc4797be..da45376954f 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -30,6 +30,7 @@ import { resolveDesktopBuildIconAssets, resolveDesktopProductName, resolveDesktopUpdateChannel, + resolveDesktopWebAssetBrand, resolveGitHubPublishConfig, resolveMockUpdateServerPort, resolveMockUpdateServerUrl, @@ -102,6 +103,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { }); }); + it("switches the bundled splash and favicon branding for nightly versions", () => { + assert.equal(resolveDesktopWebAssetBrand("0.0.17"), "production"); + assert.equal(resolveDesktopWebAssetBrand("0.0.17-nightly.20260413.42"), "nightly"); + }); + it.effect("resolves GitHub desktop publish config from Effect config", () => Effect.gen(function* () { const latestConfig = yield* resolveGitHubPublishConfig("latest").pipe( diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index bac3ef3df85..8a1099a9ed0 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -10,7 +10,12 @@ import rootPackageJson from "../package.json" with { type: "json" }; import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; import serverPackageJson from "../apps/server/package.json" with { type: "json" }; -import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; +import { applyWebBrandAssets } from "./apply-web-brand-assets.ts"; +import { + BRAND_ASSET_PATHS, + resolveWebAssetBrandForChannel, + type WebAssetBrand, +} from "./lib/brand-assets.ts"; import { getDefaultBuildArch } from "./lib/build-target-arch.ts"; import { loadRepoEnv } from "./lib/public-config.ts"; import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; @@ -1324,6 +1329,10 @@ export function resolveDesktopUpdateChannel(version: string): "latest" | "nightl return /-nightly\.\d{8}\.\d+$/.test(version) ? "nightly" : "latest"; } +export function resolveDesktopWebAssetBrand(version: string): WebAssetBrand { + return resolveWebAssetBrandForChannel(resolveDesktopUpdateChannel(version)); +} + export function resolveDesktopBuildIconAssets(version: string): DesktopBuildIconAssets { if (resolveDesktopUpdateChannel(version) === "nightly") { return { @@ -1665,6 +1674,9 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( }); } + const webAssetBrand = resolveDesktopWebAssetBrand(appVersion); + yield* applyWebBrandAssets(webAssetBrand, "apps/server/dist/client"); + yield* Effect.log(`[desktop-artifact] Applied ${webAssetBrand} web client branding.`); yield* validateBundledClientAssets(path.dirname(bundledClientEntry)); yield* fs.makeDirectory(path.join(stageAppDir, "apps/desktop"), { recursive: true }); diff --git a/scripts/export-brand-icons.ts b/scripts/export-brand-icons.ts index c24169dc2e1..04b41f27858 100644 --- a/scripts/export-brand-icons.ts +++ b/scripts/export-brand-icons.ts @@ -13,7 +13,7 @@ import * as Stream from "effect/Stream"; import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; -import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; +import { BRAND_ASSET_PATHS, DEVELOPMENT_PUBLIC_ICON_OVERRIDES } from "./lib/brand-assets.ts"; import { encodePngIco, readPngDimensions, WINDOWS_ICON_SIZES } from "./lib/icon-export.ts"; const DESIGN_GENERATION = 26; @@ -751,6 +751,16 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn } } + for (const override of DEVELOPMENT_PUBLIC_ICON_OVERRIDES) { + const sourceContents = generated.get(override.sourceRelativePath); + if (sourceContents === undefined) { + return yield* Effect.die( + new Error(`Generated development web icon is missing: ${override.sourceRelativePath}`), + ); + } + generated.set(override.targetRelativePath, sourceContents); + } + if (checkOnly) { const stale = yield* Effect.filter( [...generated.entries()], diff --git a/scripts/lib/brand-assets.test.ts b/scripts/lib/brand-assets.test.ts index 6ab3c6a3a29..c830f3d1c64 100644 --- a/scripts/lib/brand-assets.test.ts +++ b/scripts/lib/brand-assets.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from "vite-plus/test"; import { BRAND_ASSET_PATHS, DEVELOPMENT_ICON_OVERRIDES, + DEVELOPMENT_PUBLIC_ICON_OVERRIDES, PUBLISH_ICON_OVERRIDES, resolveWebAssetBrandForChannel, resolveWebIconOverrides, @@ -37,6 +38,27 @@ describe("brand-assets", () => { }); }); + it("maps development web assets to the public splash and favicon files", () => { + expect(DEVELOPMENT_PUBLIC_ICON_OVERRIDES).toEqual([ + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFaviconIco, + targetRelativePath: "apps/web/public/favicon.ico", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon16Png, + targetRelativePath: "apps/web/public/favicon-16x16.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon32Png, + targetRelativePath: "apps/web/public/favicon-32x32.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng, + targetRelativePath: "apps/web/public/apple-touch-icon.png", + }, + ]); + }); + it("can target hosted web dist directly", () => { expect(resolveWebIconOverrides("production", "apps/web/dist")).toContainEqual({ sourceRelativePath: BRAND_ASSET_PATHS.productionWebAppleTouchIconPng, diff --git a/scripts/lib/brand-assets.ts b/scripts/lib/brand-assets.ts index fe02c975ea3..9ab72c2cd75 100644 --- a/scripts/lib/brand-assets.ts +++ b/scripts/lib/brand-assets.ts @@ -101,4 +101,9 @@ export function resolveWebIconOverrides( export const DEVELOPMENT_ICON_OVERRIDES = resolveWebIconOverrides("development", "dist/client"); +export const DEVELOPMENT_PUBLIC_ICON_OVERRIDES = resolveWebIconOverrides( + "development", + "apps/web/public", +); + export const PUBLISH_ICON_OVERRIDES = resolveWebIconOverrides("production", "dist/client");