From 79d4c5ec95a58b6f3706d7c9195a7128cd861fbe Mon Sep 17 00:00:00 2001 From: AUDO Date: Fri, 7 Aug 2026 00:06:07 +0800 Subject: [PATCH 1/4] chore(lint): extend ESLint to electron and build scripts, fix existing errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run lint` covered only src/ and the two root config files: the flat config's single block matched `**/*.{js,jsx}`, so every `electron/*.cjs` file — main, preload, and the VRoid Hub modules — was parsed with zero rules applied, and `scripts/*.mjs` was never matched at all. Linting was also red on main, so it could not be used as a gate. Config: - Scope the renderer block to `src/` (browser globals + React rules). - Add an `electron/**/*.cjs` block: commonjs, Node globals, recommended. The suites need no extra globals — they require('node:test'). - Add blocks for `*.config.js` and `scripts/**` with Node globals. - Move `--max-warnings=0` into the `lint` script so local and CI agree. Fixes surfaced by the wider net: - VoicePanel destructured an unused `audioFile`; the file input is uncontrolled and only needs the setter. Dropped the prop at the call site too. - VrmAvatar read `group.current` in effect cleanup. Capture the group once and use it for both attach and detach, so a swap detaches from the group it attached to. - make-icons dropped a dead `installerAssets` array that duplicated the sizes already inlined in the PowerShell block — a trap if someone edited one and not the other. Coverage: 40 → 55 files. lint, build, and the 26 electron tests pass. Co-Authored-By: Claude Opus 5 --- avatar/eslint.config.js | 38 ++++++++++++++++++++- avatar/package.json | 2 +- avatar/scripts/make-icons.mjs | 6 +--- avatar/src/components/AvatarStage.jsx | 1 - avatar/src/components/avatar/VrmAvatar.jsx | 12 ++++--- avatar/src/components/panels/VoicePanel.jsx | 1 - 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/avatar/eslint.config.js b/avatar/eslint.config.js index cee1e2c..7113822 100644 --- a/avatar/eslint.config.js +++ b/avatar/eslint.config.js @@ -7,7 +7,8 @@ import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ globalIgnores(['dist']), { - files: ['**/*.{js,jsx}'], + // Renderer — browser globals, React rules. + files: ['src/**/*.{js,jsx}'], extends: [ js.configs.recommended, reactHooks.configs['recommended-latest'], @@ -26,4 +27,39 @@ export default defineConfig([ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], }, }, + { + // Electron main / preload — CommonJS, Node globals. Previously matched no + // config block at all, so main.cjs and the VRoid Hub modules were parsed + // but had zero rules applied. + files: ['electron/**/*.cjs'], + extends: [js.configs.recommended], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'commonjs', + globals: globals.node, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, + { + // Build tooling — Node globals. The test suites need nothing extra: they + // require('node:test') rather than relying on injected globals. + files: ['*.config.js', 'scripts/**/*.{js,mjs}'], + extends: [js.configs.recommended], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: globals.node, + }, + }, + { + files: ['scripts/**/*.cjs'], + extends: [js.configs.recommended], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'commonjs', + globals: globals.node, + }, + }, ]) diff --git a/avatar/package.json b/avatar/package.json index 661e900..30f67d1 100644 --- a/avatar/package.json +++ b/avatar/package.json @@ -17,7 +17,7 @@ "icons": "node scripts/make-icons.mjs", "test": "node --test electron/*.test.cjs", "dist:win": "npm run icons && cross-env AVATAR_SHIP=1 vite build && cross-env CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win nsis", - "lint": "eslint .", + "lint": "eslint . --max-warnings=0", "preview": "vite preview" }, "dependencies": { diff --git a/avatar/scripts/make-icons.mjs b/avatar/scripts/make-icons.mjs index 6ff0442..bd69787 100644 --- a/avatar/scripts/make-icons.mjs +++ b/avatar/scripts/make-icons.mjs @@ -70,11 +70,7 @@ console.log('wrote build/icon.ico', ico.length, 'bytes'); // NSIS assets: copy public PNGs into build/ and emit the BMPs electron-builder expects. // Sizes are already NSIS-correct (top 150×57, side 164×314); keep them unversioned. -const installerAssets = [ - { name: 'installer_top', width: 150, height: 57 }, - { name: 'installer_side', width: 164, height: 314 }, -]; - +// The authoritative list is the $assets array inside the PowerShell block below. const installerPs = ` Add-Type -AssemblyName System.Drawing $public = '${path.join(root, 'public').replace(/'/g, "''")}' diff --git a/avatar/src/components/AvatarStage.jsx b/avatar/src/components/AvatarStage.jsx index 62ca295..beebb3b 100644 --- a/avatar/src/components/AvatarStage.jsx +++ b/avatar/src/components/AvatarStage.jsx @@ -747,7 +747,6 @@ export function AvatarStage() { { @@ -58,7 +62,7 @@ export function VrmAvatar({ VRMUtils.rotateVRM0(vrmData); loaded = vrmData; - group.current?.add(vrmData.scene); + container?.add(vrmData.scene); setVrm(vrmData); onLoaded?.(); }, @@ -72,9 +76,9 @@ export function VrmAvatar({ return () => { disposed = true; setVrm(null); - if (group.current) { - while (group.current.children.length > 0) { - group.current.remove(group.current.children[0]); + if (container) { + while (container.children.length > 0) { + container.remove(container.children[0]); } } // Detaching a scene leaves its geometries, materials and textures on the diff --git a/avatar/src/components/panels/VoicePanel.jsx b/avatar/src/components/panels/VoicePanel.jsx index ed37754..38fa903 100644 --- a/avatar/src/components/panels/VoicePanel.jsx +++ b/avatar/src/components/panels/VoicePanel.jsx @@ -6,7 +6,6 @@ import { PanelSelect } from '../ui/PanelPrimitives'; export function VoicePanel({ audioSourceId, setAudioSourceId, - audioFile, setAudioFile, windowSourceId, setWindowSourceId, From a82a97dae8d3678274071e68fba2bc5158527a84 Mon Sep 17 00:00:00 2001 From: AUDO Date: Fri, 7 Aug 2026 00:14:23 +0800 Subject: [PATCH 2/4] ci: run lint, tests, and production build on PRs and main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #4. Label sync was the only workflow, so renderer and Electron regressions could land with no automated signal. - Node 22 (Vite 7 needs ^20.19.0 || >=22.12.0), npm cache keyed on avatar/package-lock.json. - Runs `npm test` as well as lint and build: the 26 Electron unit tests existed but nothing ever ran them. - Skips the Electron binary download — the tested modules deliberately avoid require('electron'), so ~100 MB is dead weight here. - No dist:win: Windows-specific and slow, deferred as the issue suggests. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e9169f9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Lint, test, build + runs-on: ubuntu-latest + defaults: + run: + working-directory: avatar + env: + # Nothing here launches Electron: the unit-tested modules deliberately + # avoid require('electron'), so the ~100 MB binary is dead weight. + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + # Vite 7 needs ^20.19.0 || >=22.12.0. + node-version: 22 + cache: npm + cache-dependency-path: avatar/package-lock.json + + - name: Install + run: npm ci + + - name: Lint + run: npm run lint + + - name: Test + run: npm test + + - name: Build + run: npm run build From 370a3e7361dae0fd52946418b695ce10bfad21c6 Mon Sep 17 00:00:00 2001 From: AUDO Date: Fri, 7 Aug 2026 10:43:00 +0800 Subject: [PATCH 3/4] ci: bump checkout and setup-node to v5 v4 declares the node20 runtime, which GitHub now force-runs on Node 24 and warns about on every run. v5 declares node24 natively. No input changes: node-version, cache, and cache-dependency-path are unchanged between v4 and v5. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9169f9..5cec3d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,10 +25,10 @@ jobs: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: # Vite 7 needs ^20.19.0 || >=22.12.0. node-version: 22 From c65ceebee43b2bc76dbbd30cb6c1293d6f4a6e07 Mon Sep 17 00:00:00 2001 From: AUDO Date: Fri, 7 Aug 2026 11:04:27 +0800 Subject: [PATCH 4/4] docs: add CI status badge to the README badge row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows whether main currently passes lint, tests, and build without having to open the Actions tab. Uses the shields.io dynamic endpoint rather than GitHub's native badge.svg so the style matches the four flat-square badges already in that row; the trade-off is that the colour is status-driven (green or red) and cannot be a pastel like its neighbours. `?branch=main` is required — without it the badge reflects the most recent run on any ref, including in-review PRs, which makes it meaningless as a signal about main. Co-Authored-By: Claude Opus 5 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0d9691b..e2f1513 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ License MIT BOOTH VRM VRoid Hub + CI