diff --git a/bun.lock b/bun.lock index c58076c0..5e68c537 100644 --- a/bun.lock +++ b/bun.lock @@ -18,11 +18,12 @@ "ws": "^8.19.0", }, "devDependencies": { - "@types/node": "^25.3.0", + "@types/node": "^24.10.0", "@types/ws": "^8.18.1", - "electron": "39.8.6", + "electron": "42.2.0", "electron-builder": "26.8.2", "esbuild": "^0.25.12", + "eslint": "^10.4.0", "prettier": "^3.8.1", "typescript": "^5.9.3", }, @@ -52,7 +53,7 @@ "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], - "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + "@electron/get": ["@electron/get@5.0.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^3.0.0", "graceful-fs": "^4.2.11", "progress": "^2.0.3", "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "optionalDependencies": { "undici": "^7.24.4" } }, "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA=="], "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], @@ -116,10 +117,34 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], + + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], + "@fontsource-variable/geist": ["@fontsource-variable/geist@5.2.8", "", {}, "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw=="], "@fontsource-variable/geist-mono": ["@fontsource-variable/geist-mono@5.2.7", "", {}, "sha512-ZKlZ5sjtalb2TwXKs400mAGDlt/+2ENLNySPx0wTz3bP3mWARCsUW+rpxzZc7e05d2qGch70pItt3K4qttbIYA=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], @@ -166,15 +191,21 @@ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], @@ -194,6 +225,10 @@ "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], @@ -294,6 +329,8 @@ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], @@ -326,7 +363,7 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron": ["electron@39.8.6", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-uWX6Jh5LmwL13VwOSKBjebI+ck+03GOwc8V2Sgbmr9pJVJ/cHfli/PkjXuRDr+hq+SLHQuT9mGHSIfScebApRA=="], + "electron": ["electron@42.2.0", "", { "dependencies": { "@electron/get": "^5.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-b2Tc7sIKiZEl0tBVwFM5GJ+FT5KYhmy9QJHjx8BGVZPVW2SctXWEvrE959ElB56qw7H05dBkhlikDA1DmpaAMw=="], "electron-builder": ["electron-builder@26.8.2", "", { "dependencies": { "app-builder-lib": "26.8.2", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-ieiiXPdgH3qrG6lcvy2mtnI5iEmAopmLuVRMSJ5j40weU0tgpNx0OAk9J5X5nnO0j9+KIkxHzwFZVUDk1U3aGw=="], @@ -344,7 +381,7 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], @@ -364,6 +401,22 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "eslint": ["eslint@10.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ=="], + + "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], @@ -374,12 +427,22 @@ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], @@ -404,6 +467,8 @@ "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -442,6 +507,8 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -450,8 +517,12 @@ "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], @@ -472,6 +543,8 @@ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], @@ -486,8 +559,12 @@ "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "libsql": ["libsql@0.5.28", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.28", "@libsql/darwin-x64": "0.5.28", "@libsql/linux-arm-gnueabihf": "0.5.28", "@libsql/linux-arm-musleabihf": "0.5.28", "@libsql/linux-arm64-gnu": "0.5.28", "@libsql/linux-arm64-musl": "0.5.28", "@libsql/linux-x64-gnu": "0.5.28", "@libsql/linux-x64-musl": "0.5.28", "@libsql/win32-x64-msvc": "0.5.28" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-wKqx9FgtPcKHdPfR/Kfm0gejsnbuf8zV+ESPmltFvsq5uXwdeN9fsWn611DmqrdXj1e94NkARcMA2f1syiAqOg=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.18.0", "", {}, "sha512-l1mfj2atMqndAHI3ls7XqPxEjV2J9ZkcNyHpoZA3r2T1LLwDB69jgkMWh71YKwhBbK0G2f4WSn05ahmQXVxupA=="], "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], @@ -540,6 +617,8 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "node-abi": ["node-abi@4.28.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g=="], @@ -560,16 +639,22 @@ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -588,6 +673,8 @@ "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], @@ -700,13 +787,15 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], + "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], @@ -726,6 +815,8 @@ "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -748,14 +839,12 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "@discordjs/rest/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - - "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], @@ -764,6 +853,8 @@ "@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], @@ -774,6 +865,20 @@ "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@types/cacheable-request/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/fs-extra/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/keyv/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/plist/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/responselike/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/ws/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/yauzl/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], @@ -786,8 +891,6 @@ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "electron/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], - "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -800,22 +903,36 @@ "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - - "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/fs-extra/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/keyv/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/plist/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/responselike/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/ws/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "@types/yauzl/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -826,8 +943,6 @@ "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "electron/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], diff --git a/changes/electron-42-runtime.md b/changes/electron-42-runtime.md new file mode 100644 index 00000000..16b1982c --- /dev/null +++ b/changes/electron-42-runtime.md @@ -0,0 +1,4 @@ +type: changed +area: runtime + +- Updated the bundled Electron runtime from 39.8.6 to 42.2.0, moving SubMiner back onto a supported Electron release line. diff --git a/changes/fix-hyprland-overlay-unfullscreen.md b/changes/fix-hyprland-overlay-unfullscreen.md new file mode 100644 index 00000000..833e294b --- /dev/null +++ b/changes/fix-hyprland-overlay-unfullscreen.md @@ -0,0 +1,4 @@ +type: fixed +area: overlay + +- Refreshed Linux overlay placement after leaving mpv fullscreen so Hyprland keeps the visible overlay aligned to the player. diff --git a/changes/fix-hyprland-overlay-zorder.md b/changes/fix-hyprland-overlay-zorder.md new file mode 100644 index 00000000..533adfef --- /dev/null +++ b/changes/fix-hyprland-overlay-zorder.md @@ -0,0 +1,5 @@ +type: fixed +area: overlay + +- Kept the Hyprland visible overlay stacked above mpv after mpv receives focus from clicks or overlay movement. +- Suspended the visible overlay while the in-player stats window is open, then restored it mouse-passive after stats closes. diff --git a/launcher/config/args-normalizer.test.ts b/launcher/config/args-normalizer.test.ts index fa8de87b..2cf4f26f 100644 --- a/launcher/config/args-normalizer.test.ts +++ b/launcher/config/args-normalizer.test.ts @@ -3,40 +3,13 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; +import { withProcessExitIntercept } from '../test-support/exit-intercept.js'; import { applyInvocationsToArgs, applyRootOptionsToArgs, createDefaultArgs, } from './args-normalizer.js'; -class ExitSignal extends Error { - code: number; - - constructor(code: number) { - super(`exit:${code}`); - this.code = code; - } -} - -function withProcessExitIntercept(callback: () => void): ExitSignal { - const originalExit = process.exit; - try { - process.exit = ((code?: number) => { - throw new ExitSignal(code ?? 0); - }) as typeof process.exit; - callback(); - } catch (error) { - if (error instanceof ExitSignal) { - return error; - } - throw error; - } finally { - process.exit = originalExit; - } - - throw new Error('expected process.exit'); -} - function withTempDir(fn: (dir: string) => T): T { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-launcher-args-')); try { @@ -106,6 +79,7 @@ test('applyRootOptionsToArgs rejects unsupported targets', () => { assert.equal(error.code, 1); assert.match(error.message, /exit:1/); + assert.match(error.stderr, /Not a file, directory, or supported URL/); }); test('applyInvocationsToArgs maps config and jellyfin invocation state', () => { @@ -231,6 +205,7 @@ test('applyInvocationsToArgs fails when config invocation has no action', () => }); assert.equal(error.code, 1); + assert.match(error.stderr, /Unknown config action: \(none\)/); }); test('applyInvocationsToArgs maps texthooker browser-open request', () => { diff --git a/launcher/mpv.test.ts b/launcher/mpv.test.ts index 18ca0ecc..ea62a4e5 100644 --- a/launcher/mpv.test.ts +++ b/launcher/mpv.test.ts @@ -7,6 +7,7 @@ import net from 'node:net'; import { EventEmitter } from 'node:events'; import type { Args } from './types'; import { getAppControlSocketPath } from '../src/shared/app-control'; +import { withProcessExitIntercept } from './test-support/exit-intercept.js'; import { buildConfiguredMpvDefaultArgs, buildMpvBackendArgs, @@ -28,34 +29,6 @@ import { } from './mpv'; import * as mpvModule from './mpv'; -class ExitSignal extends Error { - code: number; - - constructor(code: number) { - super(`exit:${code}`); - this.code = code; - } -} - -function withProcessExitIntercept(callback: () => void): ExitSignal { - const originalExit = process.exit; - try { - process.exit = ((code?: number) => { - throw new ExitSignal(code ?? 0); - }) as typeof process.exit; - callback(); - } catch (error) { - if (error instanceof ExitSignal) { - return error; - } - throw error; - } finally { - process.exit = originalExit; - } - - throw new Error('expected process.exit'); -} - function createTempSocketPath(): { dir: string; socketPath: string } { const baseDir = path.join(process.cwd(), '.tmp', 'launcher-mpv-tests'); fs.mkdirSync(baseDir, { recursive: true }); @@ -393,6 +366,7 @@ test('launchTexthookerOnly exits non-zero when app binary cannot be spawned', () }); assert.equal(error.code, 1); + assert.match(error.stderr, /Failed to launch texthooker mode/); }); test('launchTexthookerOnly forwards browser-open request to app command', () => { diff --git a/launcher/parse-args.test.ts b/launcher/parse-args.test.ts index 0e2d5f8d..3c74229c 100644 --- a/launcher/parse-args.test.ts +++ b/launcher/parse-args.test.ts @@ -1,34 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { parseArgs } from './config'; - -class ExitSignal extends Error { - code: number; - - constructor(code: number) { - super(`exit:${code}`); - this.code = code; - } -} - -function withProcessExitIntercept(callback: () => void): ExitSignal { - const originalExit = process.exit; - try { - process.exit = ((code?: number) => { - throw new ExitSignal(code ?? 0); - }) as typeof process.exit; - callback(); - } catch (error) { - if (error instanceof ExitSignal) { - return error; - } - throw error; - } finally { - process.exit = originalExit; - } - - throw new Error('expected parseArgs to exit'); -} +import { withProcessExitIntercept } from './test-support/exit-intercept.js'; test('parseArgs captures passthrough args for app subcommand', () => { const parsed = parseArgs(['app', '--anilist', '--log-level', 'debug'], 'subminer', {}); @@ -131,7 +104,9 @@ test('parseArgs rejects removed config open and launch actions', () => { }); assert.equal(openExit.code, 1); + assert.match(openExit.stderr, /Unknown config action: open/); assert.equal(exit.code, 1); + assert.match(exit.stderr, /Unknown config action: launch/); }); test('parseArgs requires an explicit action for the config subcommand', () => { @@ -140,6 +115,7 @@ test('parseArgs requires an explicit action for the config subcommand', () => { }); assert.equal(exit.code, 1); + assert.match(exit.stderr, /Unknown config action: \(none\)/); }); test('parseArgs maps mpv idle action', () => { @@ -180,6 +156,7 @@ test('parseArgs rejects conflicting dictionary candidate and selection modes', ( }); assert.equal(exit.code, 1); + assert.match(exit.stderr, /Dictionary --candidates and --select cannot be combined/); }); test('parseArgs maps stats command and log-level override', () => { @@ -243,6 +220,7 @@ test('parseArgs rejects cleanup-only stats flags without cleanup action', () => assert.equal(error.code, 1); assert.match(error.message, /exit:1/); + assert.match(error.stderr, /Stats --vocab and --lifetime flags require the cleanup action/); }); test('parseArgs maps stats rebuild action to cleanup lifetime mode', () => { diff --git a/launcher/test-support/exit-intercept.ts b/launcher/test-support/exit-intercept.ts new file mode 100644 index 00000000..a5a3f7c7 --- /dev/null +++ b/launcher/test-support/exit-intercept.ts @@ -0,0 +1,47 @@ +export class ExitSignal extends Error { + code: number; + stderr: string; + + constructor(code: number, stderr: string) { + super(`exit:${code}`); + this.code = code; + this.stderr = stderr; + } +} + +function stderrChunkToString(chunk: string | Uint8Array, encoding?: BufferEncoding): string { + if (typeof chunk === 'string') return chunk; + return Buffer.from(chunk).toString(encoding); +} + +export function withProcessExitIntercept(callback: () => void): ExitSignal { + const originalExit = process.exit; + const originalStderrWrite = process.stderr.write; + let stderr = ''; + + try { + process.stderr.write = ((chunk: string | Uint8Array, ...args: unknown[]) => { + const encoding = typeof args[0] === 'string' ? (args[0] as BufferEncoding) : undefined; + stderr += stderrChunkToString(chunk, encoding); + const writeCallback = args.find((arg): arg is (error?: Error | null) => void => { + return typeof arg === 'function'; + }); + writeCallback?.(); + return true; + }) as typeof process.stderr.write; + process.exit = ((code?: number) => { + throw new ExitSignal(code ?? 0, stderr); + }) as typeof process.exit; + callback(); + } catch (error) { + if (error instanceof ExitSignal) { + return error; + } + throw error; + } finally { + process.stderr.write = originalStderrWrite; + process.exit = originalExit; + } + + throw new Error('expected process.exit'); +} diff --git a/package.json b/package.json index 5dded349..ec223a59 100644 --- a/package.json +++ b/package.json @@ -121,11 +121,12 @@ "ws": "^8.19.0" }, "devDependencies": { - "@types/node": "^25.3.0", + "@types/node": "^24.10.0", "@types/ws": "^8.18.1", - "electron": "39.8.6", + "electron": "42.2.0", "electron-builder": "26.8.2", "esbuild": "^0.25.12", + "eslint": "^10.4.0", "prettier": "^3.8.1", "typescript": "^5.9.3" }, diff --git a/src/core/services/hyprland-window-placement.test.ts b/src/core/services/hyprland-window-placement.test.ts index 7904c52b..2951e67a 100644 --- a/src/core/services/hyprland-window-placement.test.ts +++ b/src/core/services/hyprland-window-placement.test.ts @@ -60,7 +60,10 @@ test('buildHyprlandPlacementDispatches floats tiled overlay windows without pinn floating: false, pinned: false, }), - [['dispatch', 'setfloating', 'address:0xabc']], + [ + ['dispatch', 'setfloating', 'address:0xabc'], + ['dispatch', 'alterzorder', 'top,address:0xabc'], + ], ); }); @@ -87,6 +90,7 @@ test('buildHyprlandPlacementDispatches force-aligns floating overlay windows to ['dispatch', 'setprop', 'address:0xabc no_shadow 1'], ['dispatch', 'setprop', 'address:0xabc no_blur 1'], ['dispatch', 'setprop', 'address:0xabc decorate 0'], + ['dispatch', 'alterzorder', 'top,address:0xabc'], ], ); }); @@ -98,7 +102,7 @@ test('buildHyprlandPlacementDispatches does not pin already floating overlay win floating: true, pinned: false, }), - [], + [['dispatch', 'alterzorder', 'top,address:0xabc']], ); }); @@ -109,7 +113,10 @@ test('buildHyprlandPlacementDispatches unpins previously pinned overlay windows' floating: true, pinned: true, }), - [['dispatch', 'pin', 'address:0xabc']], + [ + ['dispatch', 'pin', 'address:0xabc'], + ['dispatch', 'alterzorder', 'top,address:0xabc'], + ], ); }); @@ -146,6 +153,7 @@ test('ensureHyprlandWindowFloatingByTitle dispatches float-only placement for ma [ ['-j', 'clients'], ['dispatch', 'setfloating', 'address:0xmatch'], + ['dispatch', 'alterzorder', 'top,address:0xmatch'], ], ); }); @@ -195,6 +203,7 @@ test('ensureHyprlandWindowFloatingByTitle dispatches exact Hyprland geometry whe ['dispatch', 'setprop', 'address:0xmatch no_shadow 1'], ['dispatch', 'setprop', 'address:0xmatch no_blur 1'], ['dispatch', 'setprop', 'address:0xmatch decorate 0'], + ['dispatch', 'alterzorder', 'top,address:0xmatch'], ], ); }); diff --git a/src/core/services/hyprland-window-placement.ts b/src/core/services/hyprland-window-placement.ts index 5782933c..3512597e 100644 --- a/src/core/services/hyprland-window-placement.ts +++ b/src/core/services/hyprland-window-placement.ts @@ -95,6 +95,7 @@ export function buildHyprlandPlacementDispatches( dispatches.push(['dispatch', 'setprop', `${windowAddress} no_blur 1`]); dispatches.push(['dispatch', 'setprop', `${windowAddress} decorate 0`]); } + dispatches.push(['dispatch', 'alterzorder', `top,${windowAddress}`]); return dispatches; } diff --git a/src/core/services/overlay-visibility.test.ts b/src/core/services/overlay-visibility.test.ts index bed34537..6948a97a 100644 --- a/src/core/services/overlay-visibility.test.ts +++ b/src/core/services/overlay-visibility.test.ts @@ -197,6 +197,53 @@ test('tracked non-macOS overlay stays hidden while tracker is not ready', () => assert.ok(!calls.includes('osd')); }); +test('suspended visible overlay hides without refreshing bounds or z-order', () => { + const { window, calls } = createMainWindowRecorder(); + const tracker: WindowTrackerStub = { + isTracking: () => true, + getGeometry: () => ({ x: 0, y: 0, width: 1280, height: 720 }), + }; + + window.show(); + calls.length = 0; + + updateVisibleOverlayVisibility({ + visibleOverlayVisible: true, + suspendVisibleOverlay: true, + mainWindow: window as never, + windowTracker: tracker as never, + trackerNotReadyWarningShown: false, + setTrackerNotReadyWarningShown: () => {}, + updateVisibleOverlayBounds: () => { + calls.push('update-bounds'); + }, + ensureOverlayWindowLevel: () => { + calls.push('ensure-level'); + }, + syncPrimaryOverlayWindowLayer: () => { + calls.push('sync-layer'); + }, + enforceOverlayLayerOrder: () => { + calls.push('enforce-order'); + }, + syncOverlayShortcuts: () => { + calls.push('sync-shortcuts'); + }, + isMacOSPlatform: false, + isWindowsPlatform: false, + } as never); + + assert.ok(calls.includes('mouse-ignore:true:forward')); + assert.ok(calls.includes('always-on-top:false')); + assert.ok(calls.includes('hide')); + assert.ok(calls.includes('sync-shortcuts')); + assert.ok(!calls.includes('update-bounds')); + assert.ok(!calls.includes('ensure-level')); + assert.ok(!calls.includes('enforce-order')); + assert.ok(!calls.includes('show')); + assert.ok(!calls.includes('focus')); +}); + test('untracked non-macOS overlay keeps fallback visible behavior when no tracker exists', () => { const { window, calls } = createMainWindowRecorder(); let trackerWarning = false; diff --git a/src/core/services/overlay-visibility.ts b/src/core/services/overlay-visibility.ts index aef9003b..69f2f3d9 100644 --- a/src/core/services/overlay-visibility.ts +++ b/src/core/services/overlay-visibility.ts @@ -64,6 +64,7 @@ export function updateVisibleOverlayVisibility(args: { visibleOverlayVisible: boolean; modalActive?: boolean; forceMousePassthrough?: boolean; + suspendVisibleOverlay?: boolean; overlayInteractionActive?: boolean; mainWindow: BrowserWindow | null; windowTracker: BaseWindowTracker | null; @@ -103,6 +104,18 @@ export function updateVisibleOverlayVisibility(args: { return; } + if (args.suspendVisibleOverlay) { + if (args.isWindowsPlatform) { + clearPendingWindowsOverlayReveal(mainWindow); + setOverlayWindowOpacity(mainWindow, 0); + } + mainWindow.setIgnoreMouseEvents(true, { forward: true }); + releaseOverlayWindowLevel(mainWindow); + mainWindow.hide(); + args.syncOverlayShortcuts(); + return; + } + const showPassiveVisibleOverlay = (): boolean => { const forceMousePassthrough = args.forceMousePassthrough === true; const wasVisible = mainWindow.isVisible(); diff --git a/src/core/services/stats-window-runtime.ts b/src/core/services/stats-window-runtime.ts index 73945717..f08760a3 100644 --- a/src/core/services/stats-window-runtime.ts +++ b/src/core/services/stats-window-runtime.ts @@ -7,6 +7,8 @@ export const STATS_WINDOW_TITLE = 'SubMiner Stats'; type StatsWindowLevelController = Pick & Partial>; +type VisibleStatsWindowLevelController = StatsWindowLevelController & + Pick; type StatsWindowBoundsController = Pick; type StatsWindowPresentationController = Pick & @@ -106,6 +108,22 @@ export function promoteStatsWindowLevel( window.moveTop(); } +export function promoteVisibleStatsWindowAboveOverlay( + window: VisibleStatsWindowLevelController, + options: { + platform?: NodeJS.Platform; + promoteHyprlandWindow?: () => void; + } = {}, +): boolean { + if (window.isDestroyed() || !window.isVisible()) { + return false; + } + + promoteStatsWindowLevel(window, options.platform); + options.promoteHyprlandWindow?.(); + return true; +} + export function presentStatsWindow( window: StatsWindowPresentationController, platform: NodeJS.Platform = process.platform, diff --git a/src/core/services/stats-window.test.ts b/src/core/services/stats-window.test.ts index da2e0dd3..befb88a2 100644 --- a/src/core/services/stats-window.test.ts +++ b/src/core/services/stats-window.test.ts @@ -4,6 +4,7 @@ import { buildStatsWindowLoadFileOptions, buildStatsWindowOptions, presentStatsWindow, + promoteVisibleStatsWindowAboveOverlay, promoteStatsWindowLevel, resolveStatsWindowOuterBoundsForContent, shouldHideStatsWindowForInput, @@ -232,6 +233,47 @@ test('promoteStatsWindowLevel raises stats above overlay level on Windows', () = assert.deepEqual(calls, ['always-on-top:true:screen-saver:2', 'move-top']); }); +test('promoteVisibleStatsWindowAboveOverlay reasserts stats above overlay on Hyprland', () => { + const calls: string[] = []; + const promoted = promoteVisibleStatsWindowAboveOverlay( + { + isDestroyed: () => false, + isVisible: () => true, + setAlwaysOnTop: (flag: boolean, level?: string, relativeLevel?: number) => { + calls.push(`always-on-top:${flag}:${level ?? 'none'}:${relativeLevel ?? 0}`); + }, + moveTop: () => { + calls.push('move-top'); + }, + } as never, + { + platform: 'linux', + promoteHyprlandWindow: () => calls.push('hyprland-top'), + }, + ); + + assert.equal(promoted, true); + assert.deepEqual(calls, ['always-on-top:true:none:0', 'move-top', 'hyprland-top']); +}); + +test('promoteVisibleStatsWindowAboveOverlay skips hidden stats windows', () => { + const calls: string[] = []; + const promoted = promoteVisibleStatsWindowAboveOverlay( + { + isDestroyed: () => false, + isVisible: () => false, + setAlwaysOnTop: () => calls.push('always-on-top'), + moveTop: () => calls.push('move-top'), + } as never, + { + promoteHyprlandWindow: () => calls.push('hyprland-top'), + }, + ); + + assert.equal(promoted, false); + assert.deepEqual(calls, []); +}); + test('presentStatsWindow shows inactive on macOS to stay on the fullscreen mpv Space', () => { const calls: string[] = []; diff --git a/src/core/services/stats-window.ts b/src/core/services/stats-window.ts index 70a3057c..58c17e20 100644 --- a/src/core/services/stats-window.ts +++ b/src/core/services/stats-window.ts @@ -7,6 +7,7 @@ import { buildStatsWindowOptions, presentStatsWindow, promoteStatsWindowLevel, + promoteVisibleStatsWindowAboveOverlay, resolveStatsWindowOuterBoundsForContent, shouldHideStatsWindowForInput, STATS_WINDOW_TITLE, @@ -58,7 +59,19 @@ function showStatsWindow(window: BrowserWindow, options: StatsWindowOptions): vo placementBounds = syncStatsWindowBounds(window, bounds) ?? placementBounds; } options.onVisibilityChanged?.(true); - promoteStatsWindowLevel(window); + promoteStatsOverlayAbovePlayback(); +} + +export function promoteStatsOverlayAbovePlayback(): boolean { + if (!statsWindow) { + return false; + } + + return promoteVisibleStatsWindowAboveOverlay(statsWindow, { + promoteHyprlandWindow: () => { + ensureHyprlandWindowFloatingByTitle({ title: STATS_WINDOW_TITLE }); + }, + }); } /** @@ -104,7 +117,7 @@ export function toggleStatsOverlay(options: StatsWindowOptions): void { if (!statsWindow || statsWindow.isDestroyed() || !statsWindow.isVisible()) { return; } - promoteStatsWindowLevel(statsWindow); + promoteStatsOverlayAbovePlayback(); }); } else if (statsWindow.isVisible()) { statsWindow.hide(); diff --git a/src/main.ts b/src/main.ts index 629600a8..c0f51430 100644 --- a/src/main.ts +++ b/src/main.ts @@ -351,8 +351,12 @@ import { import { resolveYoutubePlaybackUrl } from './core/services/youtube/playback-resolve'; import { probeYoutubeTracks } from './core/services/youtube/track-probe'; import { startStatsServer } from './core/services/stats-server'; -import { registerStatsOverlayToggle, destroyStatsWindow } from './core/services/stats-window.js'; -import { toggleStatsOverlay as toggleStatsOverlayWindow } from './core/services/stats-window.js'; +import { + destroyStatsWindow, + promoteStatsOverlayAbovePlayback, + registerStatsOverlayToggle, + toggleStatsOverlay as toggleStatsOverlayWindow, +} from './core/services/stats-window.js'; import { createFirstRunSetupService, getFirstRunSetupCompletionMessage, @@ -495,6 +499,7 @@ import { } from './main/jlpt-runtime'; import { createMediaRuntimeService } from './main/media-runtime'; import { createOverlayVisibilityRuntimeService } from './main/overlay-visibility-runtime'; +import { createStatsOverlayVisibilityChangeHandler } from './main/runtime/stats-overlay-visibility'; import { createDiscordPresenceRuntime } from './main/runtime/discord-presence-runtime'; import { createCharacterDictionaryRuntimeService } from './main/character-dictionary-runtime'; import { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/character-dictionary-auto-sync'; @@ -2232,6 +2237,7 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService( getModalActive: () => overlayModalInputState.getModalInputExclusive(), getVisibleOverlayVisible: () => overlayManager.getVisibleOverlayVisible(), getForceMousePassthrough: () => appState.statsOverlayVisible, + getSuspendVisibleOverlay: () => appState.statsOverlayVisible, getOverlayInteractionActive: () => visibleOverlayInteractionActive, getWindowTracker: () => appState.windowTracker, getLastKnownWindowsForegroundProcessName: () => lastWindowsVisibleOverlayForegroundProcessName, @@ -2289,6 +2295,17 @@ let lastWindowsVisibleOverlayForegroundProcessName: string | null = null; let lastWindowsVisibleOverlayBlurredAtMs = 0; let visibleOverlayInteractionActive = false; +const handleStatsOverlayVisibilityChanged = createStatsOverlayVisibilityChangeHandler({ + setStatsOverlayVisibleState: (visible) => { + appState.statsOverlayVisible = visible; + }, + resetVisibleOverlayInteraction: () => { + visibleOverlayInteractionActive = false; + }, + getMainWindow: () => overlayManager.getMainWindow(), + updateVisibleOverlayVisibility: () => overlayVisibilityRuntime.updateVisibleOverlayVisibility(), +}); + function clearVisibleOverlayBlurRefreshTimeouts(): void { for (const timeout of visibleOverlayBlurRefreshTimeouts) { clearTimeout(timeout); @@ -3837,8 +3854,7 @@ const immersionTrackerStartupMainDeps: Parameters< getToggleKey: () => getResolvedConfig().stats.toggleKey, resolveBounds: () => getCurrentOverlayGeometry(), onVisibilityChanged: (visible) => { - appState.statsOverlayVisible = visible; - overlayVisibilityRuntime.updateVisibleOverlayVisibility(); + handleStatsOverlayVisibilityChanged(visible); }, }); } @@ -4628,7 +4644,12 @@ const updateVisibleOverlayBounds = createUpdateVisibleOverlayBoundsHandler( const buildEnsureOverlayWindowLevelMainDepsHandler = createBuildEnsureOverlayWindowLevelMainDepsHandler({ + shouldSuppressOverlayWindowLevel: (window) => + appState.statsOverlayVisible && window === overlayManager.getMainWindow(), ensureOverlayWindowLevelCore: (window) => ensureOverlayWindowLevelCore(window as BrowserWindow), + afterEnsureOverlayWindowLevel: () => { + promoteStatsOverlayAbovePlayback(); + }, }); const ensureOverlayWindowLevelMainDeps = buildEnsureOverlayWindowLevelMainDepsHandler(); const ensureOverlayWindowLevel = createEnsureOverlayWindowLevelHandler( @@ -5349,8 +5370,7 @@ async function dispatchSessionAction(request: SessionActionDispatchRequest): Pro getToggleKey: () => getResolvedConfig().stats.toggleKey, resolveBounds: () => getCurrentOverlayGeometry(), onVisibilityChanged: (visible) => { - appState.statsOverlayVisible = visible; - overlayVisibilityRuntime.updateVisibleOverlayVisibility(); + handleStatsOverlayVisibilityChanged(visible); }, }), toggleVisibleOverlay: () => toggleVisibleOverlay(), diff --git a/src/main/overlay-visibility-runtime.ts b/src/main/overlay-visibility-runtime.ts index b1f101e7..0adffe51 100644 --- a/src/main/overlay-visibility-runtime.ts +++ b/src/main/overlay-visibility-runtime.ts @@ -11,6 +11,7 @@ export interface OverlayVisibilityRuntimeDeps { getModalActive: () => boolean; getVisibleOverlayVisible: () => boolean; getForceMousePassthrough: () => boolean; + getSuspendVisibleOverlay?: () => boolean; getOverlayInteractionActive?: () => boolean; getWindowTracker: () => BaseWindowTracker | null; getLastKnownWindowsForegroundProcessName?: () => string | null; @@ -43,6 +44,7 @@ export function createOverlayVisibilityRuntimeService( updateVisibleOverlayVisibility(): void { const visibleOverlayVisible = deps.getVisibleOverlayVisible(); const forceMousePassthrough = deps.getForceMousePassthrough(); + const suspendVisibleOverlay = deps.getSuspendVisibleOverlay?.() ?? false; const windowTracker = deps.getWindowTracker(); const mainWindow = deps.getMainWindow(); @@ -50,6 +52,7 @@ export function createOverlayVisibilityRuntimeService( visibleOverlayVisible, modalActive: deps.getModalActive(), forceMousePassthrough, + suspendVisibleOverlay, overlayInteractionActive: deps.getOverlayInteractionActive?.() ?? false, mainWindow, windowTracker, diff --git a/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts b/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts index c4343b4d..8fd204bb 100644 --- a/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts +++ b/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts @@ -50,7 +50,7 @@ test('linux mpv fullscreen overlay refresh burst schedules overlay refresh work } }); -test('linux mpv fullscreen overlay refresh update cancels burst when fullscreen exits', async () => { +test('linux mpv fullscreen overlay refresh update schedules a fresh burst when fullscreen exits', async () => { const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform'); Object.defineProperty(process, 'platform', { configurable: true, @@ -82,8 +82,11 @@ test('linux mpv fullscreen overlay refresh update cancels burst when fullscreen await new Promise((resolve) => setTimeout(resolve, 80)); - assert.equal(nextCancel, null); - assert.deepEqual(calls, []); + assert.equal(typeof nextCancel, 'function'); + assert.ok(calls.includes('updateVisibleOverlayVisibility')); + assert.ok(calls.includes('hide')); + assert.ok(calls.includes('showInactive')); + assert.ok(calls.includes('ensureOverlayWindowLevel')); } finally { clearLinuxMpvFullscreenOverlayRefreshTimeouts(); if (originalPlatformDescriptor) { diff --git a/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts b/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts index 419faed7..90d09709 100644 --- a/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts +++ b/src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts @@ -68,14 +68,11 @@ export function scheduleLinuxVisibleOverlayFullscreenRefreshBurst( } export function updateLinuxMpvFullscreenOverlayRefreshBurst( - isFullscreen: boolean, + _isFullscreen: boolean, deps: LinuxMpvFullscreenOverlayRefreshDeps, cancelCurrentBurst: CancelLinuxMpvFullscreenOverlayRefreshBurst | null, ): CancelLinuxMpvFullscreenOverlayRefreshBurst | null { cancelCurrentBurst?.(); - if (!isFullscreen) { - return null; - } return scheduleLinuxVisibleOverlayFullscreenRefreshBurst(deps); } diff --git a/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts b/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts index 624d9ca3..f15bb400 100644 --- a/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts +++ b/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts @@ -15,6 +15,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb getModalActive: () => true, getVisibleOverlayVisible: () => true, getForceMousePassthrough: () => true, + getSuspendVisibleOverlay: () => true, getOverlayInteractionActive: () => true, getWindowTracker: () => tracker, getLastKnownWindowsForegroundProcessName: () => 'mpv', @@ -41,6 +42,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb assert.equal(deps.getModalActive(), true); assert.equal(deps.getVisibleOverlayVisible(), true); assert.equal(deps.getForceMousePassthrough(), true); + assert.equal(deps.getSuspendVisibleOverlay?.(), true); assert.equal(deps.getOverlayInteractionActive?.(), true); assert.equal(deps.getLastKnownWindowsForegroundProcessName?.(), 'mpv'); assert.equal(deps.getWindowsOverlayProcessName?.(), 'subminer'); diff --git a/src/main/runtime/overlay-visibility-runtime-main-deps.ts b/src/main/runtime/overlay-visibility-runtime-main-deps.ts index 525728a6..2445256d 100644 --- a/src/main/runtime/overlay-visibility-runtime-main-deps.ts +++ b/src/main/runtime/overlay-visibility-runtime-main-deps.ts @@ -10,6 +10,7 @@ export function createBuildOverlayVisibilityRuntimeMainDepsHandler( getModalActive: () => deps.getModalActive(), getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(), getForceMousePassthrough: () => deps.getForceMousePassthrough(), + getSuspendVisibleOverlay: () => deps.getSuspendVisibleOverlay?.() ?? false, getOverlayInteractionActive: () => deps.getOverlayInteractionActive?.() ?? false, getWindowTracker: () => deps.getWindowTracker(), getLastKnownWindowsForegroundProcessName: () => diff --git a/src/main/runtime/overlay-window-layout-main-deps.test.ts b/src/main/runtime/overlay-window-layout-main-deps.test.ts index 967854b1..fa5464eb 100644 --- a/src/main/runtime/overlay-window-layout-main-deps.test.ts +++ b/src/main/runtime/overlay-window-layout-main-deps.test.ts @@ -15,9 +15,16 @@ test('overlay window layout main deps builders map callbacks', () => { visible.setOverlayWindowBounds({ x: 0, y: 0, width: 1, height: 1 }); const level = createBuildEnsureOverlayWindowLevelMainDepsHandler({ + shouldSuppressOverlayWindowLevel: () => { + calls.push('ensure-suppressed-check'); + return false; + }, ensureOverlayWindowLevelCore: () => calls.push('ensure'), + afterEnsureOverlayWindowLevel: () => calls.push('ensure-after'), })(); + assert.equal(level.shouldSuppressOverlayWindowLevel?.({}), false); level.ensureOverlayWindowLevelCore({}); + level.afterEnsureOverlayWindowLevel?.({}); const order = createBuildEnforceOverlayLayerOrderMainDepsHandler({ enforceOverlayLayerOrderCore: () => calls.push('order'), @@ -34,5 +41,12 @@ test('overlay window layout main deps builders map callbacks', () => { assert.deepEqual(order.getMainWindow(), { kind: 'main' }); order.ensureOverlayWindowLevel({}); - assert.deepEqual(calls, ['visible', 'ensure', 'order', 'order-level']); + assert.deepEqual(calls, [ + 'visible', + 'ensure-suppressed-check', + 'ensure', + 'ensure-after', + 'order', + 'order-level', + ]); }); diff --git a/src/main/runtime/overlay-window-layout-main-deps.ts b/src/main/runtime/overlay-window-layout-main-deps.ts index f8e70ebc..51402a75 100644 --- a/src/main/runtime/overlay-window-layout-main-deps.ts +++ b/src/main/runtime/overlay-window-layout-main-deps.ts @@ -23,7 +23,11 @@ export function createBuildEnsureOverlayWindowLevelMainDepsHandler( deps: EnsureOverlayWindowLevelMainDeps, ) { return (): EnsureOverlayWindowLevelMainDeps => ({ + shouldSuppressOverlayWindowLevel: (window: unknown) => + deps.shouldSuppressOverlayWindowLevel?.(window) ?? false, ensureOverlayWindowLevelCore: (window: unknown) => deps.ensureOverlayWindowLevelCore(window), + afterEnsureOverlayWindowLevel: (window: unknown) => + deps.afterEnsureOverlayWindowLevel?.(window), }); } diff --git a/src/main/runtime/overlay-window-layout.test.ts b/src/main/runtime/overlay-window-layout.test.ts index 84a7be43..ff326905 100644 --- a/src/main/runtime/overlay-window-layout.test.ts +++ b/src/main/runtime/overlay-window-layout.test.ts @@ -36,9 +36,28 @@ test('ensure overlay window level handler delegates to core', () => { const calls: string[] = []; const ensureLevel = createEnsureOverlayWindowLevelHandler({ ensureOverlayWindowLevelCore: () => calls.push('core'), + afterEnsureOverlayWindowLevel: () => calls.push('after'), }); ensureLevel({}); - assert.deepEqual(calls, ['core']); + assert.deepEqual(calls, ['core', 'after']); +}); + +test('ensure overlay window level handler skips while top reassertion is suppressed', () => { + const calls: string[] = []; + const window = {}; + const ensureLevel = createEnsureOverlayWindowLevelHandler({ + shouldSuppressOverlayWindowLevel: (nextWindow) => { + assert.equal(nextWindow, window); + calls.push('suppress-check'); + return true; + }, + ensureOverlayWindowLevelCore: () => calls.push('core'), + afterEnsureOverlayWindowLevel: () => calls.push('after'), + }); + + ensureLevel(window); + + assert.deepEqual(calls, ['suppress-check']); }); test('enforce overlay layer order handler forwards resolved state', () => { diff --git a/src/main/runtime/overlay-window-layout.ts b/src/main/runtime/overlay-window-layout.ts index d1d0330a..9d306fa7 100644 --- a/src/main/runtime/overlay-window-layout.ts +++ b/src/main/runtime/overlay-window-layout.ts @@ -11,10 +11,16 @@ export function createUpdateVisibleOverlayBoundsHandler(deps: { } export function createEnsureOverlayWindowLevelHandler(deps: { + shouldSuppressOverlayWindowLevel?: (window: unknown) => boolean; ensureOverlayWindowLevelCore: (window: unknown) => void; + afterEnsureOverlayWindowLevel?: (window: unknown) => void; }) { return (window: unknown): void => { + if (deps.shouldSuppressOverlayWindowLevel?.(window) === true) { + return; + } deps.ensureOverlayWindowLevelCore(window); + deps.afterEnsureOverlayWindowLevel?.(window); }; } diff --git a/src/main/runtime/stats-overlay-visibility.test.ts b/src/main/runtime/stats-overlay-visibility.test.ts new file mode 100644 index 00000000..b8e7cddb --- /dev/null +++ b/src/main/runtime/stats-overlay-visibility.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createStatsOverlayVisibilityChangeHandler } from './stats-overlay-visibility'; + +test('stats overlay visibility handler makes overlay mouse-passive before opening stats', () => { + const calls: string[] = []; + const handler = createStatsOverlayVisibilityChangeHandler({ + setStatsOverlayVisibleState: (visible) => calls.push(`state:${visible}`), + resetVisibleOverlayInteraction: () => calls.push('reset-interaction'), + getMainWindow: () => + ({ + isDestroyed: () => false, + isVisible: () => true, + setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => { + calls.push(`mouse-ignore:${ignore}:${options?.forward === true ? 'forward' : 'plain'}`); + }, + }) as never, + updateVisibleOverlayVisibility: () => calls.push('update-visible'), + }); + + handler(true); + + assert.deepEqual(calls, [ + 'state:true', + 'reset-interaction', + 'mouse-ignore:true:forward', + 'update-visible', + ]); +}); + +test('stats overlay visibility handler restores overlay then leaves mpv mouse-responsive after close', () => { + const calls: string[] = []; + const handler = createStatsOverlayVisibilityChangeHandler({ + setStatsOverlayVisibleState: (visible) => calls.push(`state:${visible}`), + resetVisibleOverlayInteraction: () => calls.push('reset-interaction'), + getMainWindow: () => + ({ + isDestroyed: () => false, + isVisible: () => true, + setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => { + calls.push(`mouse-ignore:${ignore}:${options?.forward === true ? 'forward' : 'plain'}`); + }, + }) as never, + updateVisibleOverlayVisibility: () => calls.push('update-visible'), + }); + + handler(false); + + assert.deepEqual(calls, [ + 'state:false', + 'reset-interaction', + 'update-visible', + 'mouse-ignore:true:forward', + ]); +}); diff --git a/src/main/runtime/stats-overlay-visibility.ts b/src/main/runtime/stats-overlay-visibility.ts new file mode 100644 index 00000000..c7a76b26 --- /dev/null +++ b/src/main/runtime/stats-overlay-visibility.ts @@ -0,0 +1,33 @@ +type StatsOverlayVisibilityWindow = { + isDestroyed: () => boolean; + isVisible: () => boolean; + setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => void; +}; + +function makeOverlayMousePassive(window: StatsOverlayVisibilityWindow | null): void { + if (!window || window.isDestroyed() || !window.isVisible()) { + return; + } + window.setIgnoreMouseEvents(true, { forward: true }); +} + +export function createStatsOverlayVisibilityChangeHandler(deps: { + setStatsOverlayVisibleState: (visible: boolean) => void; + resetVisibleOverlayInteraction: () => void; + getMainWindow: () => StatsOverlayVisibilityWindow | null; + updateVisibleOverlayVisibility: () => void; +}) { + return (visible: boolean): void => { + deps.setStatsOverlayVisibleState(visible); + deps.resetVisibleOverlayInteraction(); + + if (visible) { + makeOverlayMousePassive(deps.getMainWindow()); + deps.updateVisibleOverlayVisibility(); + return; + } + + deps.updateVisibleOverlayVisibility(); + makeOverlayMousePassive(deps.getMainWindow()); + }; +} diff --git a/src/window-trackers/hyprland-tracker.test.ts b/src/window-trackers/hyprland-tracker.test.ts index 6df5e8ce..aaeace24 100644 --- a/src/window-trackers/hyprland-tracker.test.ts +++ b/src/window-trackers/hyprland-tracker.test.ts @@ -1,6 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { + HyprlandWindowTracker, isHyprlandGeometryEvent, parseHyprctlClients, parseHyprctlMonitors, @@ -177,3 +178,22 @@ test('resolveHyprlandWindowGeometry uses monitor bounds for client-requested ful height: 1080, }); }); + +test('HyprlandWindowTracker re-emits focus callback on active window events for z-order refresh', () => { + const calls: string[] = []; + const tracker = new HyprlandWindowTracker(); + const privateTracker = tracker as unknown as { + handleSocketEvent: (event: string) => void; + pollGeometry: () => void; + }; + privateTracker.pollGeometry = () => { + calls.push('poll'); + }; + tracker.onWindowFocusChange = (focused) => { + calls.push(`focus:${focused}`); + }; + + privateTracker.handleSocketEvent('activewindowv2>>0xmpv'); + + assert.deepEqual(calls, ['poll', 'focus:false']); +}); diff --git a/src/window-trackers/hyprland-tracker.ts b/src/window-trackers/hyprland-tracker.ts index 867b757e..664db2fc 100644 --- a/src/window-trackers/hyprland-tracker.ts +++ b/src/window-trackers/hyprland-tracker.ts @@ -295,8 +295,12 @@ export class HyprlandWindowTracker extends BaseWindowTracker { const data = rawData.trim(); if (name === 'activewindowv2') { + const wasFocused = this.isTargetWindowFocused(); this.activeWindowAddress = data || null; this.pollGeometry(); + if (this.isTargetWindowFocused() === wasFocused) { + this.onWindowFocusChange?.(this.isTargetWindowFocused()); + } return; } @@ -336,9 +340,12 @@ export class HyprlandWindowTracker extends BaseWindowTracker { const mpvWindow = this.findTargetWindow(clients); if (mpvWindow) { + const focused = !this.activeWindowAddress || mpvWindow.address === this.activeWindowAddress; this.updateGeometry( resolveHyprlandWindowGeometry(mpvWindow, this.getHyprlandMonitors(mpvWindow)), + focused, ); + this.updateTargetWindowFocused(focused); } else { this.updateGeometry(null); }