diff --git a/.github/workflows/icons-freshness-check.yml b/.github/workflows/icons-freshness-check.yml index b00c0e3e2a..b33229eb6b 100644 --- a/.github/workflows/icons-freshness-check.yml +++ b/.github/workflows/icons-freshness-check.yml @@ -1,13 +1,11 @@ name: Icon Assets Freshness -# Guard for the icon pipeline. The sources of truth are the nous-girl SVGs -# (assets/nous-girl-*.svg) and the per-platform backgrounds -# (assets/backgrounds/*.svg). Generated outputs are NOT committed — every -# consuming pipeline regenerates them via scripts/generate-icons.mjs — so -# this lane regenerates everything from a fresh checkout and runs --check, -# which asserts structural invariants (sizes, transparent squircle corners, -# container frame sets). A source change can never land broken. See -# scripts/generate_icons.py. +# The committed icon outputs must match what scripts/generate_icons.py +# renders from assets/nous-girl-*.svg + assets/backgrounds/*.svg. Regular +# builds and user installs consume the committed files and never render, so +# this lane is what keeps them honest: regenerate, structural --check, then +# fail on any byte difference. Fix a red run with +# `node scripts/generate-icons.mjs` on a Hermes runtime interpreter and commit. # # Byte-determinism note: resvg renders the same SVG to identical bytes on any # host for pure-shape artwork. If the master ever contains , font @@ -22,7 +20,7 @@ permissions: jobs: check: - name: Regenerate icons + structural check + name: Regenerate icons + diff against committed runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -36,7 +34,15 @@ jobs: cache-python: true prune-python-cache: true - - name: Regenerate every target, then verify structure + - name: Regenerate every target, verify structure, diff against committed + env: + HERMES_PAYLOAD_TAG: '' + HERMES_BUILD_COMMIT: '' run: | node scripts/generate-icons.mjs node scripts/generate-icons.mjs --check + if ! git diff --exit-code --stat || [ -n "$(git status --porcelain --untracked-files=all)" ]; then + git status --porcelain --untracked-files=all + echo "::error::Committed icons are stale. Run 'node scripts/generate-icons.mjs' and commit the result." + exit 1 + fi diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index 490fadf16d..f211530fd9 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -38,20 +38,6 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # tauri::generate_context! embeds the app icons, which are generated, - # not committed. The generator runs on a Hermes runtime interpreter; [] - # exports one as HERMES_PYTHON. - - uses: ./.github/actions/setup-pm - with: - toolchain: all - extras: '[]' - cache-python: true - prune-python-cache: true - - - name: Generate icons - working-directory: . - run: node scripts/generate-icons.mjs - # Tauri links against the system webkit2gtk on Linux, so the crate does # not compile without these even for `cargo test --lib`. - name: Install Tauri system dependencies diff --git a/.gitignore b/.gitignore index 86148b9ecd..56781d5f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -318,32 +318,4 @@ native/fts5_cjk/*.so # PM lockfile advisory lock; kept in place so waiting writers share its inode /pm/.lock.json.lock -# Generated icon assets (scripts/generate_icons.py). Regenerated on demand by -# every consuming pipeline; sources of truth: assets/nous-girl-*.svg + assets/backgrounds/. -assets/icon-master.svg -assets/icon-master-dark.svg -apps/desktop/assets/icon.png -apps/desktop/assets/icon.ico -apps/desktop/assets/icon.icns -apps/desktop/assets/icon-dark.png -apps/desktop/assets/icon-dark.ico -apps/desktop/assets/icon-dark.icns -apps/desktop/assets/appx/*.png -apps/desktop/public/apple-touch-icon.png -apps/desktop/public/nous-girl.png -apps/desktop/public/nous-girl-dark.png apps/desktop/.dist-build* -apps/bootstrap-installer/src-tauri/icons/*.png -apps/bootstrap-installer/src-tauri/icons/icon.ico -apps/bootstrap-installer/src-tauri/icons/icon.icns -apps/bootstrap-installer/public/nous-girl.png -website/static/img/logo.png -website/static/img/logo-dark.png -website/static/img/nous-logo.png -website/static/img/nous-logo-dark.png -website/static/img/favicon-16x16.png -website/static/img/favicon-32x32.png -website/static/img/apple-touch-icon.png -website/static/img/favicon.ico -website/static/img/favicon.svg -web/public/favicon.ico diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d361d3abd7..b0baa59b50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,7 +197,8 @@ Native desktop dependencies can also require the platform build toolchain. Logos and icons are generated from `assets/nous-girl-*.svg` and `assets/backgrounds/`. `node scripts/generate-icons.mjs` renders them with the Hermes runtime Python (`HERMES_PYTHON`, else `python` on PATH): Pillow and -resvg-py are core dependencies. Do not commit generated PNG/ICO/ICNS outputs. +resvg-py are core dependencies. Generated outputs are committed and CI fails if +they are stale; rerun the generator and commit after changing any source SVG. ### Run tests diff --git a/apps/bootstrap-installer/package.json b/apps/bootstrap-installer/package.json index 8817727e29..218b110f49 100644 --- a/apps/bootstrap-installer/package.json +++ b/apps/bootstrap-installer/package.json @@ -6,7 +6,6 @@ "type": "module", "scripts": { "dev": "vite --host 127.0.0.1 --port 5175", - "prebuild": "node ../../scripts/generate-icons.mjs --source ../.. --out ../..", "build": "tsc -b && vite build", "preview": "vite preview", "tauri": "tauri", diff --git a/apps/bootstrap-installer/public/nous-girl.png b/apps/bootstrap-installer/public/nous-girl.png new file mode 100644 index 0000000000..ef4a45ea8e Binary files /dev/null and b/apps/bootstrap-installer/public/nous-girl.png differ diff --git a/apps/bootstrap-installer/src-tauri/icons/128x128.png b/apps/bootstrap-installer/src-tauri/icons/128x128.png new file mode 100644 index 0000000000..3026d8c670 Binary files /dev/null and b/apps/bootstrap-installer/src-tauri/icons/128x128.png differ diff --git a/apps/bootstrap-installer/src-tauri/icons/128x128@2x.png b/apps/bootstrap-installer/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000..ef4a45ea8e Binary files /dev/null and b/apps/bootstrap-installer/src-tauri/icons/128x128@2x.png differ diff --git a/apps/bootstrap-installer/src-tauri/icons/32x32.png b/apps/bootstrap-installer/src-tauri/icons/32x32.png new file mode 100644 index 0000000000..3343785e69 Binary files /dev/null and b/apps/bootstrap-installer/src-tauri/icons/32x32.png differ diff --git a/apps/bootstrap-installer/src-tauri/icons/icon.icns b/apps/bootstrap-installer/src-tauri/icons/icon.icns new file mode 100644 index 0000000000..ec77c2a265 Binary files /dev/null and b/apps/bootstrap-installer/src-tauri/icons/icon.icns differ diff --git a/apps/bootstrap-installer/src-tauri/icons/icon.ico b/apps/bootstrap-installer/src-tauri/icons/icon.ico new file mode 100644 index 0000000000..0135e99927 Binary files /dev/null and b/apps/bootstrap-installer/src-tauri/icons/icon.ico differ diff --git a/apps/desktop/assets/appx/Square150x150Logo-dark.png b/apps/desktop/assets/appx/Square150x150Logo-dark.png new file mode 100644 index 0000000000..092167cc5f Binary files /dev/null and b/apps/desktop/assets/appx/Square150x150Logo-dark.png differ diff --git a/apps/desktop/assets/appx/Square150x150Logo.png b/apps/desktop/assets/appx/Square150x150Logo.png new file mode 100644 index 0000000000..7f843e4bde Binary files /dev/null and b/apps/desktop/assets/appx/Square150x150Logo.png differ diff --git a/apps/desktop/assets/appx/Square44x44Logo-dark.png b/apps/desktop/assets/appx/Square44x44Logo-dark.png new file mode 100644 index 0000000000..2de70696d7 Binary files /dev/null and b/apps/desktop/assets/appx/Square44x44Logo-dark.png differ diff --git a/apps/desktop/assets/appx/Square44x44Logo.png b/apps/desktop/assets/appx/Square44x44Logo.png new file mode 100644 index 0000000000..f158bb39bc Binary files /dev/null and b/apps/desktop/assets/appx/Square44x44Logo.png differ diff --git a/apps/desktop/assets/appx/StoreLogo-dark.png b/apps/desktop/assets/appx/StoreLogo-dark.png new file mode 100644 index 0000000000..a0fff35b03 Binary files /dev/null and b/apps/desktop/assets/appx/StoreLogo-dark.png differ diff --git a/apps/desktop/assets/appx/StoreLogo.png b/apps/desktop/assets/appx/StoreLogo.png new file mode 100644 index 0000000000..a4d0e24a1a Binary files /dev/null and b/apps/desktop/assets/appx/StoreLogo.png differ diff --git a/apps/desktop/assets/appx/Wide310x150Logo-dark.png b/apps/desktop/assets/appx/Wide310x150Logo-dark.png new file mode 100644 index 0000000000..4be20a1441 Binary files /dev/null and b/apps/desktop/assets/appx/Wide310x150Logo-dark.png differ diff --git a/apps/desktop/assets/appx/Wide310x150Logo.png b/apps/desktop/assets/appx/Wide310x150Logo.png new file mode 100644 index 0000000000..baf774f9a4 Binary files /dev/null and b/apps/desktop/assets/appx/Wide310x150Logo.png differ diff --git a/apps/desktop/assets/icon-dark.icns b/apps/desktop/assets/icon-dark.icns new file mode 100644 index 0000000000..a2d6c3961d Binary files /dev/null and b/apps/desktop/assets/icon-dark.icns differ diff --git a/apps/desktop/assets/icon-dark.ico b/apps/desktop/assets/icon-dark.ico new file mode 100644 index 0000000000..b32e2f3cf3 Binary files /dev/null and b/apps/desktop/assets/icon-dark.ico differ diff --git a/apps/desktop/assets/icon-dark.png b/apps/desktop/assets/icon-dark.png new file mode 100644 index 0000000000..b24855a5d0 Binary files /dev/null and b/apps/desktop/assets/icon-dark.png differ diff --git a/apps/desktop/assets/icon.icns b/apps/desktop/assets/icon.icns new file mode 100644 index 0000000000..ec77c2a265 Binary files /dev/null and b/apps/desktop/assets/icon.icns differ diff --git a/apps/desktop/assets/icon.ico b/apps/desktop/assets/icon.ico new file mode 100644 index 0000000000..2d6855e609 Binary files /dev/null and b/apps/desktop/assets/icon.ico differ diff --git a/apps/desktop/assets/icon.png b/apps/desktop/assets/icon.png new file mode 100644 index 0000000000..831c9797d4 Binary files /dev/null and b/apps/desktop/assets/icon.png differ diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c749e9c7a8..68a18e1e48 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -19,7 +19,7 @@ "clean:e2e": "tsc --build tsconfig.e2e.json --clean", "clean:renderer": "tsc --build tsconfig.json --clean ", "clean:electron": "tsc --build tsconfig.electron.json --clean", - "dev": "node ../../scripts/generate-icons.mjs && concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"", + "dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"", "dev:fake-boot": "cross-env HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=650 npm run dev", "dev:mock": "node ../../tests-js/scripts/mock-server.ts", "dev:renderer": "node scripts/assert-root-install.mjs && npm run clean:renderer && vite --host 127.0.0.1 --port 5174", diff --git a/apps/desktop/public/apple-touch-icon.png b/apps/desktop/public/apple-touch-icon.png new file mode 100644 index 0000000000..831c9797d4 Binary files /dev/null and b/apps/desktop/public/apple-touch-icon.png differ diff --git a/apps/desktop/public/nous-girl-dark.png b/apps/desktop/public/nous-girl-dark.png new file mode 100644 index 0000000000..489c67ba0b Binary files /dev/null and b/apps/desktop/public/nous-girl-dark.png differ diff --git a/apps/desktop/public/nous-girl.png b/apps/desktop/public/nous-girl.png new file mode 100644 index 0000000000..ef4a45ea8e Binary files /dev/null and b/apps/desktop/public/nous-girl.png differ diff --git a/apps/desktop/scripts/build.mjs b/apps/desktop/scripts/build.mjs index ad6317b083..0a93bff384 100644 --- a/apps/desktop/scripts/build.mjs +++ b/apps/desktop/scripts/build.mjs @@ -3,19 +3,15 @@ import { execFileSync } from 'node:child_process' import { cpSync } from 'node:fs' import { join, resolve } from 'node:path' import { parseArgs } from 'node:util' -import { generateIcons } from '../../../scripts/generate-icons.mjs' import { isMain, repoRoot } from '../../../scripts/build/frontend-common.mjs' -export function buildSourceDesktop({ source = repoRoot, icons, run = execFileSync, generate = generateIcons } = {}) { +export function buildSourceDesktop({ source = repoRoot, icons, run = execFileSync } = {}) { source = resolve(source) const app = join(source, 'apps/desktop') const step = (script, args = []) => run(process.execPath, [join(source, script), ...args], { cwd: app, stdio: 'inherit' }) step('apps/desktop/scripts/assert-root-install.mjs') - if (!icons) { - if (generate(['--source', source, '--out', source]) !== 0) throw new Error('Icon preparation failed') - icons = source - } - icons = resolve(icons) + // Default-brand icons are committed; only flavored release builds pass --icons. + icons = resolve(icons ?? source) if (icons !== source) { // electron-builder consumes packaging artwork in the workspace. Copy the // prepared pixels; do not create another Python environment to redraw them. diff --git a/assets/icon-master-dark.svg b/assets/icon-master-dark.svg new file mode 100644 index 0000000000..27fa292482 --- /dev/null +++ b/assets/icon-master-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icon-master.svg b/assets/icon-master.svg new file mode 100644 index 0000000000..04aa7f1e0f --- /dev/null +++ b/assets/icon-master.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/hermes_cli/source_build.py b/hermes_cli/source_build.py index dc73d4d2f8..a002bfa769 100644 --- a/hermes_cli/source_build.py +++ b/hermes_cli/source_build.py @@ -85,9 +85,8 @@ def build_source_tui(project_root: Path, *, env: dict) -> None: def build_source_web(project_root: Path, *, env: dict, icons: Path | None = None) -> None: - if icons is None: - icons = project_root - run_source_script(project_root, "scripts/generate-icons.mjs", env=env, label="Generating icons") + # Default-brand icons are committed; installs never render them. + icons = icons or project_root run_source_script(project_root, "scripts/build/web.mjs", "--source", str(project_root), "--icons", str(icons), "--out", str(project_root / "hermes_cli/web_dist"), env=env, label="Building the web UI") diff --git a/scripts/generate_icons.py b/scripts/generate_icons.py index ec3e8e8ebb..c6cc9dc0ee 100644 --- a/scripts/generate_icons.py +++ b/scripts/generate_icons.py @@ -36,13 +36,9 @@ She renders in front of the border, clipped only to the outer rounded silhouette Only nodes near her bottom edge extend to the border; the fitted face and hair stay fixed. Standalone wordmarks remain centered and have no border. -GENERATED OUTPUTS ARE NOT COMMITTED. Everything this script writes is -gitignored and regenerated on demand by the consuming pipelines (website -prebuild, desktop prebuild/predev, installer prebuild, web prebuild — via -scripts/generate-icons.mjs). The freshness lane (icons-freshness-check.yml) -runs --check, which regenerates in memory and asserts structural invariants -(sizes, transparency, container frame sets) — there are no committed bytes to -byte-compare against. +GENERATED OUTPUTS ARE COMMITTED. Regular builds and installs consume them and +never render; flavored release bundles (canary/commit) render to a product dir. +icons-freshness-check.yml regenerates, runs --check, and fails on any diff. Rendering: resvg (resvg-py) for SVG -> PNG fidelity at every size. Containers: Pillow for multi-size .ico and .icns. diff --git a/tests-js/desktop-builder.test.mjs b/tests-js/desktop-builder.test.mjs index 4931854d57..c1b4fd1d55 100644 --- a/tests-js/desktop-builder.test.mjs +++ b/tests-js/desktop-builder.test.mjs @@ -15,7 +15,6 @@ test('desktop development composition reuses prepared icon pixels instead of pro put(join(input.icons, 'apps/desktop/assets/icon.ico'), 'prepared packaging icon') const commands = [] buildSourceDesktop({ source: input.source, icons: input.icons, - generate: () => { throw new Error('prepared icons must not regenerate') }, run: (command, args) => commands.push([command, ...args]), }) expect(readFileSync(join(input.source, 'apps/desktop/assets/icon.ico'), 'utf8')).toBe('prepared packaging icon') diff --git a/tests/hermes_cli/test_source_build.py b/tests/hermes_cli/test_source_build.py index 698c900706..a623f1ad56 100644 --- a/tests/hermes_cli/test_source_build.py +++ b/tests/hermes_cli/test_source_build.py @@ -167,7 +167,7 @@ def source_products(source_checkout): encoding="utf-8", ) for script, step, output in [ - ("generate-icons.mjs", "icons", "web/public/favicon.ico"), + ("generate-icons.mjs", "icons", "never-rendered-at-install"), ("build/tui.mjs", "tui", "ui-tui/dist/entry.js"), ("build/web.mjs", "web", "hermes_cli/web_dist/index.html"), ]: @@ -188,6 +188,9 @@ def source_products(source_checkout): "if (!process.argv.includes('--icons')) await import('../generate-icons.mjs');\n", encoding="utf-8", ) + # Default-brand icons are committed; updates consume them without rendering. + (root / "web/public").mkdir(parents=True, exist_ok=True) + (root / "web/public/favicon.ico").write_bytes(b"committed icon") return root, acquired @@ -252,7 +255,7 @@ def test_update_builds_selected_products_after_one_union_preparation(source_prod app.write_text("previous app") build_update_products(root, desktop=desktop) steps = [event["step"] for event in _events(root)] - assert steps == ["deps", "tui", "icons", "web"] + (["desktop"] if desktop else []) + assert steps == ["deps", "tui", "web"] + (["desktop"] if desktop else []) assert acquired == ["npm"] assert (root / "ui-tui/dist/entry.js").read_text() == "tui" assert (root / "hermes_cli/web_dist/index.html").read_text() == "web" @@ -264,7 +267,7 @@ def test_update_builds_selected_products_after_one_union_preparation(source_prod @pytest.mark.platforms("linux") -@pytest.mark.parametrize("step", ["tui", "icons", "web", "desktop"]) +@pytest.mark.parametrize("step", ["tui", "web", "desktop"]) def test_update_failure_raises_without_retries_or_replacing_live_app(source_products, step): from hermes_cli.source_build import build_update_products @@ -277,7 +280,7 @@ def test_update_failure_raises_without_retries_or_replacing_live_app(source_prod build_update_products(root, desktop=True) assert app.read_text() == "previous app" assert not list((root / "apps/desktop").glob(".staging-*")) - order = ["deps", "tui", "icons", "web", "desktop"] + order = ["deps", "tui", "web", "desktop"] assert [event["step"] for event in _events(root)] == order[:order.index(step) + 1] assert acquired == ["npm"] assert not (Path(os.environ["HERMES_HOME"]) / "desktop-build-stamp.json").exists() diff --git a/web/package.json b/web/package.json index 5a0173a12f..50651b247a 100644 --- a/web/package.json +++ b/web/package.json @@ -4,9 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "predev": "node ../scripts/generate-icons.mjs --source .. --out ..", "dev": "vite", - "prebuild": "node ../scripts/generate-icons.mjs --source .. --out ..", "build": "node ../scripts/build/web.mjs", "lint": "eslint .", "lint:fix": "eslint . --fix", diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000..4562b156f1 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/website/scripts/prebuild.mjs b/website/scripts/prebuild.mjs index 0c3c2ca683..ef52a0e541 100644 --- a/website/scripts/prebuild.mjs +++ b/website/scripts/prebuild.mjs @@ -33,7 +33,6 @@ const repoRoot = resolve(websiteDir, ".."); const extractScript = join(scriptDir, "extract-skills.py"); const llmsScript = join(scriptDir, "generate-llms-txt.py"); const cronBlueprintsScript = join(scriptDir, "extract-automation-blueprints.py"); -const iconGenScript = join(repoRoot, "scripts", "generate-icons.mjs"); const pluginsScript = join(scriptDir, "extract-plugins.py"); const pluginStarsScript = join(scriptDir, "fetch-plugin-stars.py"); const outputFile = join(websiteDir, "static", "api", "skills.json"); @@ -125,19 +124,7 @@ async function ensureUnifiedIndex() { } } -// 0) Icon assets — the navbar logo, favicons, and apple-touch-icon are -// generated (not committed). This must fail loudly: a docs build without the -// logo ships a broken navbar. -console.log("[prebuild] generating icon assets…"); -{ - const r = spawnSync("node", [iconGenScript, "--source", repoRoot, "--out", repoRoot], { stdio: "inherit", cwd: repoRoot }); - if (r.status !== 0) { - console.error("[prebuild] icon generation failed — it needs a Hermes runtime Python (HERMES_PYTHON); see the output above"); - process.exit(1); - } -} - -// 0b) Pull unified index if we don't have a fresh one. +// 0) Pull unified index if we don't have a fresh one. await ensureUnifiedIndex(); // 1) skills.json — required for the Skills Hub page. diff --git a/website/static/img/apple-touch-icon.png b/website/static/img/apple-touch-icon.png new file mode 100644 index 0000000000..89521b85bd Binary files /dev/null and b/website/static/img/apple-touch-icon.png differ diff --git a/website/static/img/favicon-16x16.png b/website/static/img/favicon-16x16.png new file mode 100644 index 0000000000..79ddd34762 Binary files /dev/null and b/website/static/img/favicon-16x16.png differ diff --git a/website/static/img/favicon-32x32.png b/website/static/img/favicon-32x32.png new file mode 100644 index 0000000000..3343785e69 Binary files /dev/null and b/website/static/img/favicon-32x32.png differ diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico new file mode 100644 index 0000000000..4562b156f1 Binary files /dev/null and b/website/static/img/favicon.ico differ diff --git a/website/static/img/favicon.svg b/website/static/img/favicon.svg new file mode 100644 index 0000000000..04aa7f1e0f --- /dev/null +++ b/website/static/img/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/static/img/logo-dark.png b/website/static/img/logo-dark.png new file mode 100644 index 0000000000..9e1a47c124 Binary files /dev/null and b/website/static/img/logo-dark.png differ diff --git a/website/static/img/logo.png b/website/static/img/logo.png new file mode 100644 index 0000000000..1832d9860e Binary files /dev/null and b/website/static/img/logo.png differ diff --git a/website/static/img/nous-logo-dark.png b/website/static/img/nous-logo-dark.png new file mode 100644 index 0000000000..a01a03cbd1 Binary files /dev/null and b/website/static/img/nous-logo-dark.png differ diff --git a/website/static/img/nous-logo.png b/website/static/img/nous-logo.png new file mode 100644 index 0000000000..b658a9dc4a Binary files /dev/null and b/website/static/img/nous-logo.png differ