diff --git a/NEWS.md b/NEWS.md index 7638f6aee..a84f22dc2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,14 @@ width: 128px; border-radius: 128px; " /> +## v1.12.5 + +- Fixes + - Fix "Last Read" column in table view crashing. + - Ignore dotfiles. +- Features + - Select by range in multi-select with shift+click. + ## v1.12.4 - Fixes diff --git a/bun.lock b/bun.lock index b13210932..1ea3be5b5 100644 --- a/bun.lock +++ b/bun.lock @@ -11,7 +11,7 @@ "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", "@vitest/eslint-plugin": "^1.6.17", - "eslint": "^10.3.0", + "eslint": "^10.4.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", "eslint-plugin-compat": "^7.0.2", @@ -24,6 +24,7 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", + "eslint-plugin-package-json": "^1.1.0", "eslint-plugin-perfectionist": "^5.9.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-promise": "^7.3.0", @@ -53,6 +54,8 @@ }, }, "packages": { + "@altano/repository-tools": ["@altano/repository-tools@2.0.3", "", {}, "sha512-cSR/ZYDF6Wp9OeAJMyLYYN1GenAAhV17W+w38ELP+3c5Ltsy9jkkCymi33nz/qnXyef3n6Fbr1h2yt3dvUN5sQ=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -77,7 +80,7 @@ "@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.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], + "@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=="], @@ -361,10 +364,12 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.3.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.5.5", "@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-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw=="], + "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-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], + "eslint-fix-utils": ["eslint-fix-utils@0.4.2", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-n7ZTcwwkP5scedlhvWMcqxED+O1NzXcj5Rxn/0kJQMP88k02vRcBfQ1qsk/JHb6Aw8bajFoetFCCBiNIcNCsvA=="], + "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" }, "optionalPeers": ["unrs-resolver"] }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="], "eslint-mdx": ["eslint-mdx@3.7.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "espree": "^9.6.1 || ^10.4.0", "estree-util-visit": "^2.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "unified-engine": "^11.2.2", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0", "remark-lint-file-extension": "*" }, "optionalPeers": ["remark-lint-file-extension"] }, "sha512-QpPdJ6EeFthHuIrfgnWneZgwwFNOLFj/nf2jg/tOTBoiUnqNTxUUpTGAn0ZFHYEh5htVVoe5kjvD02oKtxZGeA=="], @@ -391,6 +396,8 @@ "eslint-plugin-no-use-extend-native": ["eslint-plugin-no-use-extend-native@0.7.3", "", { "dependencies": { "is-get-set-prop": "^2.0.0", "is-js-type": "^3.0.0", "is-obj-prop": "^2.0.0", "is-proto-prop": "^3.0.1" }, "peerDependencies": { "eslint": "^9.3.0 || ^10.0.0" } }, "sha512-kYJhgZkiZIavu/wIwrO+n4GemQcMX53kWCNZNr7nGMkRD1aBFLkDpBivEYP7nIJINCo9fzPbFjrpeX5kr2Qbww=="], + "eslint-plugin-package-json": ["eslint-plugin-package-json@1.1.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.1", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.5.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^3.4.0" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-yFBkYxHBlGKBi3Av17pCkJie+G3/DN/XEy1KGkHnTLXCY8eX7rF2kGiFn8a3N4iIGV4NJzKA0HdpINwVxczX0Q=="], + "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.9.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.58.2", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-8TWzg02zmnBdZwCkWLi8jhzqXI+fE7Z/RwV8SL6xD45tJ8Bp3wGuYL2XtQgfe/Wd0eBqOUX+s6ey73IyszvKTA=="], "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.12" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw=="], @@ -479,7 +486,7 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], @@ -551,6 +558,8 @@ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "jsonc-eslint-parser": ["jsonc-eslint-parser@3.1.0", "", { "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^5.0.0", "semver": "^7.3.5" } }, "sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng=="], + "jsx-ast-utils-x": ["jsx-ast-utils-x@0.1.0", "", {}, "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -571,7 +580,7 @@ "lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "lru-cache": ["lru-cache@11.5.0", "", {}, "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -715,7 +724,7 @@ "npm-normalize-package-bin": ["npm-normalize-package-bin@3.0.1", "", {}, "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ=="], - "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="], + "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], "npm-pick-manifest": ["npm-pick-manifest@9.1.0", "", { "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "npm-package-arg": "^11.0.0", "semver": "^7.3.5" } }, "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA=="], @@ -731,6 +740,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-json-validator": ["package-json-validator@1.5.1", "", { "dependencies": { "npm-package-arg": "^13.0.2", "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0" } }, "sha512-5WpI9I6pMy/ueq7rkDcJXfVjcHX6OBVTQk4Z7ObVZzsgPv/sX3kUfFFfvobErl/HpgleLcW4egvNypyBwGlkpw=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse-json": ["parse-json@7.1.1", "", { "dependencies": { "@babel/code-frame": "^7.21.4", "error-ex": "^1.3.2", "json-parse-even-better-errors": "^3.0.0", "lines-and-columns": "^2.0.3", "type-fest": "^3.8.0" } }, "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw=="], @@ -769,7 +780,7 @@ "prettier-plugin-toml": ["prettier-plugin-toml@2.0.6", "", { "dependencies": { "@taplo/lib": "^0.5.0" }, "peerDependencies": { "prettier": "^3.0.3" } }, "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w=="], - "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], @@ -1037,7 +1048,7 @@ "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], - "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -1077,10 +1088,20 @@ "@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=="], + "@npmcli/config/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + + "@npmcli/git/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "@npmcli/git/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + "@npmcli/git/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "@npmcli/map-workspaces/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + "@npmcli/package-json/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "@npmcli/package-json/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -1121,10 +1142,18 @@ "is-builtin-module/builtin-modules": ["builtin-modules@5.1.0", "", {}, "sha512-c5JxaDrzwRjq3WyJkI1AGR5xy6Gr6udlt7sQPbl09+3ckB+Zo2qqQ2KhCTBr7Q8dHB43bENGYEk4xddrFH/b7A=="], + "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "npm-pick-manifest/npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1159,10 +1188,14 @@ "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "@npmcli/package-json/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + "eslint_d/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], + "eslint_d/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], "eslint_d/eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -1173,6 +1206,14 @@ "glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "npm-pick-manifest/npm-package-arg/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "npm-pick-manifest/npm-package-arg/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + + "npm-pick-manifest/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1184,5 +1225,7 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "npm-pick-manifest/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], } } diff --git a/cfg/eslint.config.base.js b/cfg/eslint.config.base.js index fb23ded7e..048393470 100644 --- a/cfg/eslint.config.base.js +++ b/cfg/eslint.config.base.js @@ -14,6 +14,7 @@ import * as eslintPluginMdx from "eslint-plugin-mdx"; import eslintPluginNoSecrets from "eslint-plugin-no-secrets"; import eslintPluginNoUnsanitized from "eslint-plugin-no-unsanitized"; import eslintPluginNoUseExtendNative from "eslint-plugin-no-use-extend-native"; +import eslintPluginPackageJson from "eslint-plugin-package-json"; import eslintPluginPerfectionist from "eslint-plugin-perfectionist"; import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; import eslintPluginPromise from "eslint-plugin-promise"; @@ -53,7 +54,6 @@ export const CONFIGS = { }, rules: { "@stylistic/multiline-comment-style": "off", // Multiple bugs with this rule - // "import-x/order": "off", "max-params": ["warn", 4], "no-console": "warn", "no-debugger": "warn", @@ -124,6 +124,9 @@ export default defineConfig([ ...eslintJson.configs.recommended, language: "json/json", }, + eslintPluginPackageJson.configs.recommended, + eslintPluginPackageJson.configs.stylistic, + eslintPluginPackageJson.configs["recommended-publishable"], { files: ["package.json"], languageOptions: { diff --git a/codex/librarian/fs/filters.py b/codex/librarian/fs/filters.py index 6fa2a50fd..f438ba348 100644 --- a/codex/librarian/fs/filters.py +++ b/codex/librarian/fs/filters.py @@ -1,6 +1,7 @@ """Filter files with regexes.""" import re +from contextlib import suppress from pathlib import Path from comicbox.box import Comicbox @@ -9,6 +10,53 @@ from codex.models.paths import CustomCover from codex.settings import CUSTOM_COVERS_DIR, CUSTOM_COVERS_GROUP_DIRS +# Component-level ignore registry consulted by the poller's walker and +# the watchfiles filter. Either constant can be extended to add new +# patterns without touching the walker/filter call sites: +# +# _IGNORED_BASENAMES — exact-match basenames (case-sensitive). Use +# this for OS / archive metadata trees that don't follow a prefix +# convention. Examples for future extension: ``"@eaDir"`` +# (Synology), ``"__MACOSX"`` (Mac archive metadata), ``"Thumbs.db"`` +# / ``"desktop.ini"`` (Windows), ``"#recycle"`` (recycle bins). +# +# _IGNORED_BASENAME_PREFIXES — prefix matches. Currently catches all +# hidden files / directories ("." prefix) so VCS metadata (.git), +# macOS spotlight (.Spotlight-V100), trash (.Trashes), and stray +# dotfiles (.DS_Store, .nomedia) are skipped wholesale. +# +# A path is ignored when *any* component (relative to its library +# root) matches either rule. The check is applied to every entry the +# walker sees and every event the watcher receives. +_IGNORED_BASENAMES: frozenset[str] = frozenset() +_IGNORED_BASENAME_PREFIXES: tuple[str, ...] = (".",) + + +def is_ignored_basename(name: str) -> bool: + """Return True when ``name`` matches any registered ignore rule.""" + return name in _IGNORED_BASENAMES or any( + name.startswith(prefix) for prefix in _IGNORED_BASENAME_PREFIXES + ) + + +def is_ignored_path(path: Path | str, root: Path | str | None = None) -> bool: + """ + Return True when any component (relative to ``root``) is ignored. + + The check is component-by-component so a hidden ancestor + (``.git/HEAD``) is caught even when the basename itself is + innocuous. When ``root`` is provided, components of the root are + skipped — a library whose path lives under a hidden parent (e.g. + ``/Users/aj/.archive/comics``) still polls its contents. + """ + ppath = path if isinstance(path, Path) else Path(path) + rel = ppath + if root is not None: + with suppress(ValueError): + rel = ppath.relative_to(root) + return any(is_ignored_basename(part) for part in rel.parts) + + _IMAGE_REGEX = r"\.(jpe?g|webp|png|gif|bmp)" _IMAGE_MATCHER: re.Pattern = re.compile(_IMAGE_REGEX, re.IGNORECASE) diff --git a/codex/librarian/fs/poller/snapshot.py b/codex/librarian/fs/poller/snapshot.py index a7eab224f..ea309cfcb 100644 --- a/codex/librarian/fs/poller/snapshot.py +++ b/codex/librarian/fs/poller/snapshot.py @@ -8,6 +8,7 @@ from django.db.models import Model from codex.librarian.fs.filters import ( + is_ignored_basename, match_comic, match_folder_cover, match_group_cover_image, @@ -121,6 +122,14 @@ def _init_walk(self): def _walk(self, root: str) -> None: """Walk the directory tree and populate lookups.""" for entry in os.scandir(root): + # Walking only the surviving children means the recursion + # never enters an ignored dir (``.git`` / ``.Trashes`` / + # ``@eaDir`` if registered / …), so a per-component check + # at each scandir level suffices — no need to re-check + # ancestors. See ``filters._IGNORED_BASENAMES`` / + # ``_IGNORED_BASENAME_PREFIXES`` for the registered rules. + if is_ignored_basename(entry.name): + continue path = Path(entry.path) is_dir = entry.is_dir(follow_symlinks=self._follow_symlinks) if is_dir: diff --git a/codex/librarian/fs/watcher/dirs.py b/codex/librarian/fs/watcher/dirs.py index 51ea19483..98017e9ed 100644 --- a/codex/librarian/fs/watcher/dirs.py +++ b/codex/librarian/fs/watcher/dirs.py @@ -7,6 +7,7 @@ from codex.librarian.fs.events import FSChange, FSEvent from codex.librarian.fs.filters import ( + is_ignored_basename, match_comic, match_folder_cover, match_group_cover_image, @@ -42,8 +43,15 @@ def expand_dir_added( if not root.is_dir(): return count = 0 - for dirpath, _dirnames, filenames in os.walk(root): + for dirpath, dirnames, filenames in os.walk(root): + # Prune ignored directories in place so ``os.walk`` never + # descends into them under a freshly-added tree. Rules come + # from the central registry in ``filters`` — extend that + # module to add more patterns. + dirnames[:] = [d for d in dirnames if not is_ignored_basename(d)] for filename in filenames: + if is_ignored_basename(filename): + continue file_path = Path(dirpath) / filename if event := _classify_added_file(file_path, covers_only=covers_only): batch.added.append((library_pk, event)) diff --git a/codex/librarian/fs/watcher/watcher.py b/codex/librarian/fs/watcher/watcher.py index 12433eff6..4205e61cd 100644 --- a/codex/librarian/fs/watcher/watcher.py +++ b/codex/librarian/fs/watcher/watcher.py @@ -8,6 +8,7 @@ from watchfiles import Change, watch from codex.librarian.fs.filters import ( + is_ignored_path, match_comic, match_folder_cover, match_group_cover_image, @@ -29,27 +30,37 @@ class CodexWatchFilter: """Watchfiles watcher class for both types of library.""" - def __init__(self, covers_only_paths: set[str]): - """Set covers_only_paths.""" + def __init__(self, library_paths: set[str], covers_only_paths: set[str]): + """Set library and covers_only paths.""" + self._library_paths = library_paths self._covers_only_paths = covers_only_paths + def _library_root_for(self, ppath: Path) -> str | None: + """Return the watched library root containing ``ppath``, if any.""" + for lib_path in self._library_paths: + if ppath.is_relative_to(lib_path): + return lib_path + return None + def __call__(self, change: Change, path: str) -> bool: """ Filter method. - Deleted paths can't be inspected on disk, so let them all through; - event processing filters by DB lookup and suffix matching instead. + Deleted paths can't be inspected on disk, but the ignore-path + check runs purely on the path string so events under registered + ignore patterns are suppressed without a stat. Non-deleted + events also fall through to the suffix / cover predicates that + the poller mirrors. """ + ppath = Path(path) + lib_root = self._library_root_for(ppath) + if lib_root is None or is_ignored_path(ppath, root=lib_root): + return False + if change == Change.deleted: return True - ppath = Path(path) - covers_only = False - for covers_only_path in self._covers_only_paths: - if ppath.is_relative_to(covers_only_path): - covers_only = True - break - + covers_only = lib_root in self._covers_only_paths if covers_only: return match_group_cover_image(ppath) return ppath.is_dir() or match_comic(ppath) or match_folder_cover(ppath) @@ -161,7 +172,6 @@ def _get_extant_paths(self, paths: list[str]) -> list[str]: def _watch_loop(self) -> None: """Run the watchfiles loop, restarting when paths change.""" - watch_filter = CodexWatchFilter(self._covers_only_paths) while not self._shutdown_event.is_set(): self._restart_event.clear() paths = list(self._library_paths.keys()) @@ -170,6 +180,14 @@ def _watch_loop(self) -> None: self._restart_event.wait(timeout=5.0) continue extant_paths = self._get_extant_paths(paths) + # Built per iteration so a ``restart()`` from + # ``_update_paths_from_db`` (which rebinds the path sets) + # immediately propagates new libraries / covers-only flags + # into the filter — the previous outer-scope binding froze + # the filter to the path sets present at process startup. + watch_filter = CodexWatchFilter( + set(self._library_paths.keys()), self._covers_only_paths + ) try: for changes in watch( *extant_paths, diff --git a/codex/templates/pwa/serviceworker.js b/codex/templates/pwa/serviceworker.js index dd700100e..706d79bb9 100644 --- a/codex/templates/pwa/serviceworker.js +++ b/codex/templates/pwa/serviceworker.js @@ -21,10 +21,12 @@ self.addEventListener("install", (event) => { self.addEventListener("activate", (event) => { event.waitUntil( Promise.all([ - // Pair with skipWaiting() so the new SW takes control of - // already-open pages immediately. Without this, replacing a - // stale SW (e.g. one with a stale CSP) would need a second - // reload to take effect. + /* + * Pair with skipWaiting() so the new SW takes control of + * already-open pages immediately. Without this, replacing a + * stale SW (e.g. one with a stale CSP) would need a second + * reload to take effect. + */ self.clients.claim(), caches.keys().then((cacheNames) => { return Promise.all( @@ -39,11 +41,13 @@ self.addEventListener("activate", (event) => { }); // Serve from Cache self.addEventListener("fetch", (event) => { - // Pass through non-GET and cross-origin requests so the browser - // handles them under the page's CSP rather than the SW's snapshot - // taken at install time. Vite HMR talks to a separate origin - // (5173) and would otherwise be blocked by a stale SW CSP that - // pre-dates the dev-only overlay. + /* + * Pass through non-GET and cross-origin requests so the browser + * handles them under the page's CSP rather than the SW's snapshot + * taken at install time. Vite HMR talks to a separate origin + * (5173) and would otherwise be blocked by a stale SW CSP that + * pre-dates the dev-only overlay. + */ const url = new URL(event.request.url); if (event.request.method !== "GET" || url.origin !== self.location.origin) { return; diff --git a/codex/views/browser/annotate/order.py b/codex/views/browser/annotate/order.py index 783b5b221..34b9c6a2a 100644 --- a/codex/views/browser/annotate/order.py +++ b/codex/views/browser/annotate/order.py @@ -227,15 +227,39 @@ def _annotate_page_count(self, qs): return qs def _annotate_bookmark_updated_at(self, qs) -> QuerySet: - if self.is_opds_acquisition or self.order_key == "bookmark_updated_at": - bmua_agg = self.get_max_bookmark_updated_at_aggregate( - qs.model, agg_func=self.order_agg_func - ) + # Aggregate triggers: + # - OPDS acquisition needs the per-entry "last read" timestamp. + # - Sorting by ``bookmark_updated_at`` needs it as the order key. + # - Group rows in table view display it as a column; the cell + # display path (``_emit_column`` → ``getattr``) reads the + # annotation directly, so without this branch the column would + # crash ``compute_group_intersections`` (the field can't be + # aggregated as a Comic-relative scalar without the user + # filter). Comic rows keep going through + # ``annotate_comic_extra_specials`` to avoid double-annotation. + primary = self.is_opds_acquisition or self.order_key == "bookmark_updated_at" + table_column = ( + qs.model is not Comic + and self.params.get("view_mode") == "table" + and self.order_key != "bookmark_updated_at" + ) + if not primary and not table_column: + return qs + agg_func = self.order_agg_func if primary else Max + bmua_agg = self.get_max_bookmark_updated_at_aggregate( + qs.model, agg_func=agg_func + ) + if primary: # `self.bmua_is_max` is read by `annotate.bookmark` to skip a # second aggregate, and by the serializer to compute mtime. - self.bmua_is_max = self.order_agg_func is Max - qs = qs.annotate(bookmark_updated_at=bmua_agg) - return qs + # Only set in the primary branch — that path annotates both + # group and Comic querysets, so the scalar exists on every + # serialized row. The table-column branch annotates only + # group rows; flipping the flag here would make + # ``get_mtime`` reach for a missing ``bookmark_updated_at`` + # on Comic books in the same response. + self.bmua_is_max = agg_func is Max + return qs.annotate(bookmark_updated_at=bmua_agg) def _annotate_search_scores(self, qs, *, for_cover: bool = False): """Annotate Search Scores.""" diff --git a/codex/views/browser/intersections.py b/codex/views/browser/intersections.py index 010bb0584..28d53b3e1 100644 --- a/codex/views/browser/intersections.py +++ b/codex/views/browser/intersections.py @@ -86,7 +86,11 @@ def _format_identifier(source_name: str | None, id_type: str, key: str) -> str: "metadata_mtime": "metadata_mtime", "created_at": "created_at", "updated_at": "updated_at", - "bookmark_updated_at": "bookmark_updated_at", + # ``bookmark_updated_at`` is intentionally absent: it's not a + # Comic field but a per-user-filtered ``Max(bookmark__updated_at)`` + # aggregate. The annotate pipeline attaches it directly to group + # rows in table view (see ``_annotate_bookmark_updated_at``); the + # cell display then falls through to ``getattr`` in ``_emit_column``. # FK-to-name columns (Comic FK → related model.name). "country": "country__name", "language": "language__name", diff --git a/frontend/bun.lock b/frontend/bun.lock index 723fc3e68..bfa5d8eaa 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -11,7 +11,7 @@ "eslint-import-resolver-oxc": "^0.15.0", "pinia": "^3.0.4", "pretty-bytes": "^7.1.0", - "text-case": "^1.2.10", + "text-case": "^1.2.11", "vue": "^3.5.34", "vue-pdf-embed": "^2.1.4", "vue-router": "^5.0.7", @@ -22,11 +22,11 @@ "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^25.8.0", + "@types/node": "^25.9.1", "@unhead/bundler": "^3.1.0", - "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.10", - "@vue/typescript-plugin": "^3.2.9", + "@vue/typescript-plugin": "^3.3.1", "happy-dom": "^20.9.0", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.99.0", @@ -35,10 +35,10 @@ "typeface-roboto": "^1.1.13", "vite": "^8.0.13", "vite-plugin-checker": "^0.13.0", - "vite-plugin-dynamic-base": "^1.4.0", + "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.0", "vite-plugin-vuetify": "^2.1.3", - "vitest": "^4.1.6", + "vitest": "^4.1.7", "vue-eslint-parser": "^10.4.0", }, }, @@ -324,7 +324,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], @@ -342,19 +342,19 @@ "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.7", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg=="], - "@vitest/expect": ["@vitest/expect@4.1.6", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg=="], + "@vitest/expect": ["@vitest/expect@4.1.7", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w=="], - "@vitest/mocker": ["@vitest/mocker@4.1.6", "", { "dependencies": { "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ=="], + "@vitest/mocker": ["@vitest/mocker@4.1.7", "", { "dependencies": { "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.6", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.7", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw=="], - "@vitest/runner": ["@vitest/runner@4.1.6", "", { "dependencies": { "@vitest/utils": "4.1.6", "pathe": "^2.0.3" } }, "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA=="], + "@vitest/runner": ["@vitest/runner@4.1.7", "", { "dependencies": { "@vitest/utils": "4.1.7", "pathe": "^2.0.3" } }, "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw=="], - "@vitest/spy": ["@vitest/spy@4.1.6", "", {}, "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg=="], + "@vitest/spy": ["@vitest/spy@4.1.7", "", {}, "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q=="], - "@vitest/utils": ["@vitest/utils@4.1.6", "", { "dependencies": { "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ=="], + "@vitest/utils": ["@vitest/utils@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw=="], "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], @@ -378,7 +378,7 @@ "@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="], - "@vue/language-core": ["@vue/language-core@3.2.9", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-ie0ojt/0fU/GfIogh+zgHbaYRPlt9S+cLOxcWwF7nTSFh897BVgnFKL2byT4kpp1mlqYWZ2psGwSniyE2xsxYw=="], + "@vue/language-core": ["@vue/language-core@3.3.1", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-NP8g6V7x81NVOXbLupUvYY6i6LqUkjkVowe2epRedmpgaFCOdjgWHE/rQBvEJ4r7koAYODIjGeBWEdt6n7jYXQ=="], "@vue/reactivity": ["@vue/reactivity@3.5.34", "", { "dependencies": { "@vue/shared": "3.5.34" } }, "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ=="], @@ -392,7 +392,7 @@ "@vue/test-utils": ["@vue/test-utils@2.4.10", "", { "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^3.0.0" }, "peerDependencies": { "@vue/compiler-dom": "3.x", "@vue/server-renderer": "3.x", "vue": "3.x" }, "optionalPeers": ["@vue/server-renderer"] }, "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA=="], - "@vue/typescript-plugin": ["@vue/typescript-plugin@3.2.9", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.9", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "vue-component-meta": "3.2.9" } }, "sha512-I3IQ+jbLlvSMyViV0yxbJgMG4em6UlSgfIVLk4KNMWddSyo4CFjrjm3BLm76vV/8snRb3dKmeYfQPm7axYlMuw=="], + "@vue/typescript-plugin": ["@vue/typescript-plugin@3.3.1", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.1", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "vue-component-meta": "3.3.1" } }, "sha512-j0OIAS1N420DOl77b7yO15yHY8v+M2fHi4ZoAm5CSPqsgev63/Gtx2jsnC88JR1blLWAfDzhVWfKw8xWKNG0pw=="], "@vuetify/loader-shared": ["@vuetify/loader-shared@2.1.2", "", { "dependencies": { "upath": "^2.0.1" }, "peerDependencies": { "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig=="], @@ -784,47 +784,47 @@ "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], - "text-camel-case": ["text-camel-case@1.2.10", "", { "dependencies": { "text-pascal-case": "1.2.10" } }, "sha512-KNrWeZzQT+gh73V1LnmgTkjK7V+tMRjLCc6VrGwkqbiRdnGVIWBUgIvVnvnaVCxIvZ/2Ke8DCmgPirlQcCqD3Q=="], + "text-camel-case": ["text-camel-case@1.2.11", "", { "dependencies": { "text-pascal-case": "^1.2.11" } }, "sha512-2ZsM/gOlB1tyza+8lGLvs6gtPuZ9qEYuKPa+gwo38m65wkY4k323SK4hT7ku8r5wIKyspUYIWSk1aB9/Jjxr7A=="], - "text-capital-case": ["text-capital-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-yvViUJKSSQcRO58je224bhPHg/Hij9MEY43zuKShtFzrPwW/fOAarUJ5UkTMSB81AOO1m8q+JiFdxMF4etKZbA=="], + "text-capital-case": ["text-capital-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-30A7B7+VUvevEmPE0xWK1Z2z0ncl/JTjSUBLfjpoXrkwuPpmNTVbjHShRTN3cX9GIuZn/P3jvR+TO9JiTZcl8A=="], - "text-case": ["text-case@1.2.10", "", { "dependencies": { "text-camel-case": "1.2.10", "text-capital-case": "1.2.10", "text-constant-case": "1.2.10", "text-dot-case": "1.2.10", "text-header-case": "1.2.10", "text-is-lower-case": "1.2.10", "text-is-upper-case": "1.2.10", "text-kebab-case": "1.2.10", "text-lower-case": "1.2.10", "text-lower-case-first": "1.2.10", "text-no-case": "1.2.10", "text-param-case": "1.2.10", "text-pascal-case": "1.2.10", "text-path-case": "1.2.10", "text-sentence-case": "1.2.10", "text-snake-case": "1.2.10", "text-swap-case": "1.2.10", "text-title-case": "1.2.10", "text-upper-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-5bY3Ks/u7OJ5YO69iyXrG5Xf2wUZeyko7U78nPUnYoSeuNeAfA5uAix5hTspfkl6smm3yCBObrex+kFvzeIcJg=="], + "text-case": ["text-case@1.2.11", "", { "dependencies": { "text-camel-case": "^1.2.11", "text-capital-case": "^1.2.11", "text-constant-case": "^1.2.11", "text-dot-case": "^1.2.11", "text-header-case": "^1.2.11", "text-is-lower-case": "^1.2.11", "text-is-upper-case": "^1.2.11", "text-kebab-case": "^1.2.11", "text-lower-case": "^1.2.11", "text-lower-case-first": "^1.2.11", "text-no-case": "^1.2.11", "text-param-case": "^1.2.11", "text-pascal-case": "^1.2.11", "text-path-case": "^1.2.11", "text-sentence-case": "^1.2.11", "text-snake-case": "^1.2.11", "text-swap-case": "^1.2.11", "text-title-case": "^1.2.11", "text-upper-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-LbdWNQeuXWbfav+pxBxvaefkziffMYeSA53BHp52cgJa9rjiC0dkjum9AKrH8iQWoQJ4InGPSGexLeerGFaZ1Q=="], - "text-constant-case": ["text-constant-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case": "1.2.10" } }, "sha512-/OfU798O2wrwKN9kQf71WhJeAlklGnbby0Tupp+Ez9NXymW+6oF9LWDRTkN+OreTmHucdvp4WQd6O5Rah5zj8A=="], + "text-constant-case": ["text-constant-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case": "^1.2.11" } }, "sha512-XnTBILsa7UpMWncUCchqIybZlg15FUcrlyNaWIJ8ybPy54qcoN513EXFswueyizuAgyJFXPCwwSFbSji6kw/Uw=="], - "text-dot-case": ["text-dot-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-vf4xguy5y6e39RlDZeWZFMDf2mNkR23VTSVb9e68dUSpfJscG9/1YWWpW3n8TinzQxBZlsn5sT5olL33MvvQXw=="], + "text-dot-case": ["text-dot-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-7SLKiT45KZO0qad0+p+GvC0+F+6pZ851HJcTcBJiSF88HsK/e1qErlGLtVBT6hkTHIaAj48WfSyQr4lZRv1xJQ=="], - "text-header-case": ["text-header-case@1.2.10", "", { "dependencies": { "text-capital-case": "1.2.10" } }, "sha512-sVb1NY9bwxtu+Z7CVyWbr+I0AkWtF0kEHL/Zz5V2u/WdkjK5tKBwl5nXf0NGy9da4ZUYTBb+TmQpOIqihzvFMQ=="], + "text-header-case": ["text-header-case@1.2.11", "", { "dependencies": { "text-capital-case": "^1.2.11" } }, "sha512-7OBHd2g7X+aH6rXMC3cANFh6yvhXjXkyumw2NaRwJRIk343pP2e1SQCTCfowPDmmi8wkZVqz1fdWNq5LwvcBOQ=="], - "text-is-lower-case": ["text-is-lower-case@1.2.10", "", {}, "sha512-dMTeTgrdWWfYf3fKxvjMkDPuXWv96cWbd1Uym6Zjv9H855S1uHxjkFsGbTYJ2tEK0NvAylRySTQlI6axlcMc4w=="], + "text-is-lower-case": ["text-is-lower-case@1.2.11", "", {}, "sha512-dBqPAkNmX7eTM7ZbS3D/UBCQ5i9EXt5tujF2wIGGbZ1+aN8bY7Qda4mDpxgd6Hbzf/z10uQWRNzupl99wFQ8CQ=="], - "text-is-upper-case": ["text-is-upper-case@1.2.10", "", {}, "sha512-PGD/cXoXECGAY1HVZxDdmpJUW2ZUAKQ6DTamDfCHC9fc/z4epOz0pB/ThBnjJA3fz+d2ApkMjAfZDjuZFcodzg=="], + "text-is-upper-case": ["text-is-upper-case@1.2.11", "", {}, "sha512-MZeUIYEYfKZ2FSeg0vnHCH4mHXLgGzes+iz2K+4BYnhnkEa2svKA1nNjQAqTUiVNHOPqPCuzmUr1LsyQZ73uyA=="], - "text-kebab-case": ["text-kebab-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-3XZJAApx5JQpUO7eXo7GQ2TyRcGw3OVbqxz6QJb2h+N8PbLLbz3zJVeXdGrhTkoUIbkSZ6PmHx6LRDaHXTdMcA=="], + "text-kebab-case": ["text-kebab-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-RIg9iN6VwH+JrX9dFdm1nd1efPGR9LjNc0CiQz496sQETeKGkDEzxES/ZzxbkerrAL2DFEMGdLXckzDz1OEDBQ=="], - "text-lower-case": ["text-lower-case@1.2.10", "", {}, "sha512-c9j5pIAN3ObAp1+4R7970e1bgtahTRF/5ZQdX2aJBuBngYTYZZIck0NwFXUKk5BnYpLGsre5KFHvpqvf4IYKgg=="], + "text-lower-case": ["text-lower-case@1.2.11", "", {}, "sha512-txTy6y0y8M23Lhf0mk8WcvXTqlf4OQ3AGnDsRB6o3uMNfIa0CJDol2s1PdKNa63rt5B2277zkZCCn6Xeq//big=="], - "text-lower-case-first": ["text-lower-case-first@1.2.10", "", {}, "sha512-Oro84jZPDLD9alfdZWmtFHYTvCaaSz2o4thPtjMsK4GAkTyVg9juYXWj0y0YFyjLYGH69muWsBe4/MR5S7iolw=="], + "text-lower-case-first": ["text-lower-case-first@1.2.11", "", {}, "sha512-QR483XLyuyIpq8tKu1ds3Q1jfsgfaa/p9rtoQKHe6Rv5ah9ic/SUzTGN0MQ7UIS9APADd8SUPn5TTh1Z2/ACyg=="], - "text-no-case": ["text-no-case@1.2.10", "", { "dependencies": { "text-lower-case": "1.2.10" } }, "sha512-4/m79pzQrywrwEG5lCULY1lQvFY+EKjhH9xSMT6caPK5plqzm9Y7rXyv+UXPd3s9qH6QODZnvsAYWW3M0JgxRA=="], + "text-no-case": ["text-no-case@1.2.11", "", { "dependencies": { "text-lower-case": "^1.2.11" } }, "sha512-wazS7FEq0Ct3aJzeE8MEMcSs0eW4+/X/fwdotv/rG66bLS+g1T0pa0gUsbBGjjLFs191AIXVIry+bYE0uaaBBQ=="], - "text-param-case": ["text-param-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-hkavcLsRRzZcGryPAshct1AwIOMj/FexYjMaLpGZCYYBn1lcZEeyMzJZPSckzkOYpq35LYSQr3xZto9XU5OAsw=="], + "text-param-case": ["text-param-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-3EMMAMLSz/mJXOnATNnrS+dZAvghpq09VhOVYDOkUnbm5zlYc6iU5AZOKVDpiAVVllQ9P1h5IKVZzsEYrdIRGw=="], - "text-pascal-case": ["text-pascal-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-/kynZD8vTYOmm/RECjIDaz3qYEUZc/N/bnC79XuAFxwXjdNVjj/jGovKJLRzqsYK/39N22XpGcVmGg7yIrbk6w=="], + "text-pascal-case": ["text-pascal-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-BNhQ1O/g/Q4dH5gPyLIJLDLDknl2dipBwV629ScsiZCKJaCLGXYhTXp23rp9Htg3O5OSSsiU3mqDKq+pBmwTSw=="], - "text-path-case": ["text-path-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-vbKdRCaVEeOaW6sm24QP9NbH7TS9S4ZQ3u19H8eylDox7m2HtFwYIBjAPv+v3z4I/+VjrMy9LB54lNP1uEqRHw=="], + "text-path-case": ["text-path-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-FsJU4BmMdtLtmnBK/XRJPqTwLF8yFiTEClHjxlQjSAG5Xt9R4p6D1WNaM1CI2dG5Lr4rsFM4jiVC620m0AsRbw=="], - "text-sentence-case": ["text-sentence-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-NO4MRlbfxFhl9QgQLuCL4xHmvE7PUWHVPWsZxQ5nzRtDjXOUllWvtsvl8CP5tBEvBmzg0kwfflxfhRtr5vBQGg=="], + "text-sentence-case": ["text-sentence-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-ApiVsvdLy+Wb8x7mZRVuoy8VO12jJ22G2djVM3ZZbUhXVIkqGgHxmiXRwdhRPoWGojK9n53m7jviJgBVNdRn+g=="], - "text-snake-case": ["text-snake-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-6ttMZ+B9jkHKun908HYr4xSvEtlbfJJ4MvpQ06JEKRGhwjMI0x8t2Wywp+MEzN6142O6E/zKhra18KyBL6cvXA=="], + "text-snake-case": ["text-snake-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-NOEQvjyyVABB41SS8dUx423Y6hWS+Z4TrAAJg1xzCkOD3q9y0JtdJjCvCA1FWI8oDu+HiIOp/N446uDM8j54XQ=="], - "text-swap-case": ["text-swap-case@1.2.10", "", {}, "sha512-vO3jwInIk0N77oEFakYZ2Hn/llTmRwf2c3RvkX/LfvmLWVp+3QcIc6bwUEtbqGQ5Xh2okjFhYrfkHZstVc3N4Q=="], + "text-swap-case": ["text-swap-case@1.2.11", "", {}, "sha512-PBmC5xvZdDZ4suikydpeXH0s4JV2XHelMj9/OEXEbA3oLpdV2A+B4BspVDWVw7C2Gi5eCareqk/7EE8I1/WwgQ=="], - "text-title-case": ["text-title-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-bqA+WWexUMWu9A3fdNar+3GXXW+c5xOvMyuK5hOx/w0AlqhyQptyCrMFjGB8Fd9dxbryBNmJ+5rWtC1OBDxlaA=="], + "text-title-case": ["text-title-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-V1GZy0XlqdkYUQm0tqm1jqtYlXJqFVMreBCTUReOaz8d/JbozTSpZrcakIeV8+1bN7LsvfhPFhA5zREiax6YIA=="], - "text-upper-case": ["text-upper-case@1.2.10", "", {}, "sha512-L1AtZ8R+jtSMTq0Ffma9R4Rzbrc3iuYW89BmWFH41AwnDfRmEBlBOllm1ZivRLQ/6pEu2p+3XKBHx9fsMl2CWg=="], + "text-upper-case": ["text-upper-case@1.2.11", "", {}, "sha512-BfTL7yB1YIRlVGNdZUvno013hOq2cRs07fDR2ApppOXRDuKrEmsLDEY82xXlDzQHELp0jexqkI+NeyPIl6MtMw=="], - "text-upper-case-first": ["text-upper-case-first@1.2.10", "", {}, "sha512-VXs7j7BbpKwvolDh5fwpYRmMrUHGkxbY8E90fhBzKUoKfadvWmPT/jFieoZ4UPLzr208pXvQEFbb2zO9Qzs9Fg=="], + "text-upper-case-first": ["text-upper-case-first@1.2.11", "", {}, "sha512-vgfbwKo8TEJbRsapR9LWWvIJRnv8u9aXVa6cyYOAQQmurCx54Cnt59x5fKdiq+hFaBJ51AbzCgMpbP3p65/pHQ=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -870,19 +870,19 @@ "vite-plugin-checker": ["vite-plugin-checker@0.13.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "chokidar": "^4.0.3", "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.4", "proper-lockfile": "^4.1.2", "tiny-invariant": "^1.3.3", "tinyglobby": "^0.2.15", "vscode-uri": "^3.1.0" }, "peerDependencies": { "@biomejs/biome": ">=1.7", "eslint": ">=9.39.4", "meow": "^13.2.0 || ^14.0.0", "optionator": "^0.9.4", "oxlint": ">=1", "stylelint": ">=16.26.1", "typescript": "*", "vite": ">=5.4.21", "vls": "*", "vti": "*", "vue-tsc": "~2.2.10 || ^3.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "meow", "optionator", "oxlint", "stylelint", "typescript", "vls", "vti", "vue-tsc"] }, "sha512-14EkOZmfinVZNxRmg2uCNDwtqGc/33lU/UEJansHgu27+ad+r6mMBf1Xtnq57jGZWiO/xzwtiEKPYsganw7ZFQ=="], - "vite-plugin-dynamic-base": ["vite-plugin-dynamic-base@1.4.0", "", { "dependencies": { "@swc/core": "^1.3.61", "node-html-parser": "^5.3.3" }, "peerDependencies": { "vite": ">= 2.9.5" } }, "sha512-iofx5cGkBqZTOyLAcGGT3CCxQxB0fFmnNSLVX7/VIFyZZsGbZ/Lzj9IrGISTKmftQke53eolJE3tmMzliPQhJQ=="], + "vite-plugin-dynamic-base": ["vite-plugin-dynamic-base@1.4.1", "", { "dependencies": { "@swc/core": "^1.3.61", "node-html-parser": "^5.3.3" }, "peerDependencies": { "vite": ">= 2.9.5" } }, "sha512-TvH5FNo8mp/jBZZN/agKnMIlf/OgFKr3vBPMniBAl8x5uFyIJzPQEMqIqwKQUxZUtWhVRWWBdW+9T/LU2Wg+Qg=="], "vite-plugin-run": ["vite-plugin-run@0.9.0", "", { "dependencies": { "@antfu/utils": "^9.3.0", "debug": "^4.4.3", "es-toolkit": "^1.45.1", "minimatch": "^10.2.5", "picocolors": "^1.1.1", "tinyexec": "^1.1.1" }, "peerDependencies": { "vite": "^8.0.8" } }, "sha512-Y2jaKIPtnAiQu46LJ798JV5gHRBRzGocOBX/RspfRsDbHZts/oYsvx2vQNxFLqxd0k69j4lQokYZ8dKDAqiTgA=="], "vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.3", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.2", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw=="], - "vitest": ["vitest@4.1.6", "", { "dependencies": { "@vitest/expect": "4.1.6", "@vitest/mocker": "4.1.6", "@vitest/pretty-format": "4.1.6", "@vitest/runner": "4.1.6", "@vitest/snapshot": "4.1.6", "@vitest/spy": "4.1.6", "@vitest/utils": "4.1.6", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.6", "@vitest/browser-preview": "4.1.6", "@vitest/browser-webdriverio": "4.1.6", "@vitest/coverage-istanbul": "4.1.6", "@vitest/coverage-v8": "4.1.6", "@vitest/ui": "4.1.6", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ=="], + "vitest": ["vitest@4.1.7", "", { "dependencies": { "@vitest/expect": "4.1.7", "@vitest/mocker": "4.1.7", "@vitest/pretty-format": "4.1.7", "@vitest/runner": "4.1.7", "@vitest/snapshot": "4.1.7", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.7", "@vitest/browser-preview": "4.1.7", "@vitest/browser-webdriverio": "4.1.7", "@vitest/coverage-istanbul": "4.1.7", "@vitest/coverage-v8": "4.1.7", "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA=="], "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], "vue": ["vue@3.5.34", "", { "dependencies": { "@vue/compiler-dom": "3.5.34", "@vue/compiler-sfc": "3.5.34", "@vue/runtime-dom": "3.5.34", "@vue/server-renderer": "3.5.34", "@vue/shared": "3.5.34" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA=="], - "vue-component-meta": ["vue-component-meta@3.2.9", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.9", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-18FIQDctfCwpuTrMmAZJXR7fM48wrXNVcWrrhKgvARz2Sr8i1gIQ7gZdUaQwC9hfTxa9S0wIauPSf1et2xp0vQ=="], + "vue-component-meta": ["vue-component-meta@3.3.1", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-XoU/EXBmbMyeorbGOblhtU9h8QiDZDt3HZgYS2/eyrthJaToIkBHyn2ssjdgvPIdBWnPyqopFWaIjR9APwGppw=="], "vue-component-type-helpers": ["vue-component-type-helpers@3.2.7", "", {}, "sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ=="], @@ -1004,8 +1004,6 @@ "vite/rolldown": ["rolldown@1.0.1", "", { "dependencies": { "@oxc-project/types": "=0.130.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.1", "@rolldown/binding-darwin-arm64": "1.0.1", "@rolldown/binding-darwin-x64": "1.0.1", "@rolldown/binding-freebsd-x64": "1.0.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", "@rolldown/binding-linux-arm64-gnu": "1.0.1", "@rolldown/binding-linux-arm64-musl": "1.0.1", "@rolldown/binding-linux-ppc64-gnu": "1.0.1", "@rolldown/binding-linux-s390x-gnu": "1.0.1", "@rolldown/binding-linux-x64-gnu": "1.0.1", "@rolldown/binding-linux-x64-musl": "1.0.1", "@rolldown/binding-openharmony-arm64": "1.0.1", "@rolldown/binding-wasm32-wasi": "1.0.1", "@rolldown/binding-win32-arm64-msvc": "1.0.1", "@rolldown/binding-win32-x64-msvc": "1.0.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ=="], - "vitest/vite": ["vite@8.0.12", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.0", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg=="], - "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.34", "", { "dependencies": { "@vue/compiler-core": "3.5.34", "@vue/shared": "3.5.34" } }, "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw=="], "vue/@vue/server-renderer": ["@vue/server-renderer@3.5.34", "", { "dependencies": { "@vue/compiler-ssr": "3.5.34", "@vue/shared": "3.5.34" }, "peerDependencies": { "vue": "3.5.34" } }, "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew=="], @@ -1130,8 +1128,6 @@ "vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0", "", {}, "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ=="], - "vitest/vite/rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="], - "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.1", "", { "dependencies": { "@vue/devtools-shared": "^8.1.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg=="], "vue-router/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], @@ -1162,40 +1158,6 @@ "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "vitest/vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.129.0", "", {}, "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg=="], - - "vitest/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA=="], - - "vitest/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew=="], - - "vitest/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ=="], - - "vitest/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0", "", { "os": "linux", "cpu": "arm" }, "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw=="], - - "vitest/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0", "", { "os": "none", "cpu": "arm64" }, "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg=="], - - "vitest/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow=="], - - "vitest/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg=="], - - "vitest/vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0", "", {}, "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ=="], - "vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="], "vue-router/@vue/devtools-api/@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], @@ -1207,9 +1169,5 @@ "vue/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue-macros/common/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], } } diff --git a/frontend/package.json b/frontend/package.json index d28e4f889..1162d4a30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "codex", - "version": "1.12.4", + "version": "1.12.5", "private": true, "description": "ui for codex api", "type": "module", @@ -22,7 +22,7 @@ "eslint-import-resolver-oxc": "^0.15.0", "pinia": "^3.0.4", "pretty-bytes": "^7.1.0", - "text-case": "^1.2.10", + "text-case": "^1.2.11", "vue": "^3.5.34", "vue-pdf-embed": "^2.1.4", "vue-router": "^5.0.7", @@ -33,11 +33,11 @@ "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^25.8.0", + "@types/node": "^25.9.1", "@unhead/bundler": "^3.1.0", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.10", - "@vue/typescript-plugin": "^3.2.9", + "@vue/typescript-plugin": "^3.3.1", "happy-dom": "^20.9.0", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.99.0", @@ -46,10 +46,10 @@ "typeface-roboto": "^1.1.13", "vite": "^8.0.13", "vite-plugin-checker": "^0.13.0", - "vite-plugin-dynamic-base": "^1.4.0", + "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.0", "vite-plugin-vuetify": "^2.1.3", - "vitest": "^4.1.6", + "vitest": "^4.1.7", "vue-eslint-parser": "^10.4.0" } } diff --git a/frontend/src/api/v3/admin.js b/frontend/src/api/v3/admin.js index 233538873..1954a4537 100644 --- a/frontend/src/api/v3/admin.js +++ b/frontend/src/api/v3/admin.js @@ -12,14 +12,16 @@ const makeAdminCRUD = (entity) => { }; }; -// One descriptor per admin table, keyed by the PascalCase table name -// the admin store / components pass to ``loadTable`` / ``createRow`` / -// ``updateRow`` / ``deleteRow``. Each descriptor exposes the subset of -// ``create`` / ``getAll`` / ``update`` / ``destroy`` HTTP ops the -// table actually supports plus the camelCase ``stateField`` the admin -// store stores the loaded rows under. Letting the API surface own the -// table names eliminates the IRREGULAR_PLURALS map + string-concat -// lookup the admin store used to need on the consumer side. +/* + * One descriptor per admin table, keyed by the PascalCase table name + * the admin store / components pass to ``loadTable`` / ``createRow`` / + * ``updateRow`` / ``deleteRow``. Each descriptor exposes the subset of + * ``create`` / ``getAll`` / ``update`` / ``destroy`` HTTP ops the + * table actually supports plus the camelCase ``stateField`` the admin + * store stores the loaded rows under. Letting the API surface own the + * table names eliminates the IRREGULAR_PLURALS map + string-concat + * lookup the admin store used to need on the consumer side. + */ export const TABLES = Object.freeze({ User: { ...makeAdminCRUD("user"), stateField: "users" }, Group: { ...makeAdminCRUD("group"), stateField: "groups" }, @@ -39,11 +41,13 @@ export const TABLES = Object.freeze({ HTTP.get("/admin/librarian/status", { params: { ts: Date.now() } }), stateField: "activeLibrarianStatuses", }, - // AgeRatingMetron is an effectively-static enum lookup — rows - // don't change at runtime — so we skip the ``serializeParams()`` - // cache-buster. Browser-cacheable; the admin store also keeps a - // sticky in-memory cache (``TABLE_TTL_MS = Infinity``) for the - // session, so a typical visitor hits this endpoint at most once. + /* + * AgeRatingMetron is an effectively-static enum lookup — rows + * don't change at runtime — so we skip the ``serializeParams()`` + * cache-buster. Browser-cacheable; the admin store also keeps a + * sticky in-memory cache (``TABLE_TTL_MS = Infinity``) for the + * session, so a typical visitor hits this endpoint at most once. + */ AgeRatingMetron: { getAll: () => HTTP.get("/admin/age-rating-metron"), stateField: "ageRatingMetrons", diff --git a/frontend/src/api/v3/auth.js b/frontend/src/api/v3/auth.js index 079561398..c4dbc2192 100644 --- a/frontend/src/api/v3/auth.js +++ b/frontend/src/api/v3/auth.js @@ -8,9 +8,11 @@ export const updateTimezone = () => timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone, }); -// Backend reads ``login`` (django-allauth field name); the form binds to -// ``username``. Spread instead of mutating so the caller's reactive form -// state doesn't grow a stray ``login`` field. +/* + * Backend reads ``login`` (django-allauth field name); the form binds to + * ``username``. Spread instead of mutating so the caller's reactive form + * state doesn't grow a stray ``login`` field. + */ export const register = (credentials) => HTTP.post("/auth/register/", { ...credentials, login: credentials.username }); diff --git a/frontend/src/api/v3/base.js b/frontend/src/api/v3/base.js index 7fe0a150c..f14e95879 100644 --- a/frontend/src/api/v3/base.js +++ b/frontend/src/api/v3/base.js @@ -12,17 +12,19 @@ const COOKIE_NAME = "csrftoken"; const CSRF_HEADER = "X-CSRFToken"; const CSRF_COOKIE_REGEX = RegExp("(?:^|;)\\s*" + COOKIE_NAME + "=([^;]*)"); -// Cookie-string cache: we re-run the regex only when -// ``document.cookie`` actually changes. The previous code matched -// the regex against the full cookie string on every single API -// request — a cheap operation per call, but multiplied across the -// many requests a single page navigation fires (browser page + -// available filters + per-field choices + mtime poll), it added up -// to noticeable redundant work. Caching the token string and the -// cookie snapshot it was extracted from lets us hit a constant-time -// string equality check in the common case while still picking up -// rotated tokens automatically (the cookie string changes whenever -// Django rotates the CSRF cookie). +/* + * Cookie-string cache: we re-run the regex only when + * ``document.cookie`` actually changes. The previous code matched + * the regex against the full cookie string on every single API + * request — a cheap operation per call, but multiplied across the + * many requests a single page navigation fires (browser page + + * available filters + per-field choices + mtime poll), it added up + * to noticeable redundant work. Caching the token string and the + * cookie snapshot it was extracted from lets us hit a constant-time + * string equality check in the common case while still picking up + * rotated tokens automatically (the cookie string changes whenever + * Django rotates the CSRF cookie). + */ let _cachedCookieSnapshot = ""; let _cachedToken = ""; diff --git a/frontend/src/api/v3/browser.js b/frontend/src/api/v3/browser.js index 8a23fb24e..7394008b2 100644 --- a/frontend/src/api/v3/browser.js +++ b/frontend/src/api/v3/browser.js @@ -29,9 +29,11 @@ export const getCoverSrc = ({ coverPk, coverCustomPk }, ts) => { return `${base}c/${coverPk}/cover.webp${query}`; }; -// Mirror of MISSING_COVER_NAME_MAP in codex/views/const.py. -// Root group "r" never appears as a card group, so it's intentionally -// absent — unknown letters fall back to the comic placeholder. +/* + * Mirror of MISSING_COVER_NAME_MAP in codex/views/const.py. + * Root group "r" never appears as a card group, so it's intentionally + * absent — unknown letters fall back to the comic placeholder. + */ const PLACEHOLDER_BY_GROUP = Object.freeze({ p: "publisher", i: "imprint", @@ -57,10 +59,8 @@ export const getAvailableFilterChoices = ( return HTTP.get(`/${group}/${pks}/choices_available`, { params, ...options }); }; -/* eslint-disable max-params */ export const getFilterChoices = ( - { group, pks }, - fieldName, + { group, pks, fieldName }, data, ts, options = {}, @@ -71,7 +71,6 @@ export const getFilterChoices = ( ...options, }); }; -/* eslint-enable max-params */ export const getBrowserPage = ( { group, pks, page }, @@ -84,11 +83,13 @@ export const getBrowserPage = ( }; export const getMetadata = ({ group, pks }, settings) => { - // Pull ``mtime`` out as the timestamp; ``serializeParams`` deep-clones - // the rest via ``_deepClone`` (which calls ``toRaw`` at every level), - // so we don't need ``structuredClone`` here — and can't safely use it, - // since the reactive filter arrays from the Pinia store don't always - // survive a structured clone (DataCloneError on ``[object Array]``). + /* + * Pull ``mtime`` out as the timestamp; ``serializeParams`` deep-clones + * the rest via ``_deepClone`` (which calls ``toRaw`` at every level), + * so we don't need ``structuredClone`` here — and can't safely use it, + * since the reactive filter arrays from the Pinia store don't always + * survive a structured clone (DataCloneError on ``[object Array]``). + */ const pkList = pks.join(","); const { mtime, ...data } = toRaw(settings) || {}; const params = serializeParams(data, mtime, false); @@ -109,10 +110,12 @@ export const resetSettings = () => HTTP.delete("/r/settings"); export const getGroupDownloadURL = ({ group, pks }, fn, settings, ts) => { const base = globalThis.CODEX.API_V3_PATH; - // Strip ``show`` without mutating the caller's settings object; - // the previous ``delete settings.show`` was a silent side-effect - // that broke any caller relying on its settings object surviving - // a download-URL build. + /* + * Strip ``show`` without mutating the caller's settings object; + * the previous ``delete settings.show`` was a silent side-effect + * that broke any caller relying on its settings object surviving + * a download-URL build. + */ const { show: _show, ...query } = settings; const { hrefPath, queryString } = getBrowserHrefPath({ group, @@ -127,8 +130,10 @@ export const getGroupDownloadURL = ({ group, pks }, fn, settings, ts) => { export const updateGroupBookmarks = ({ group, ids }, settings, updates) => { const params = serializeParams(settings); const queryString = new URLSearchParams(params).toString(); - // Backend rejects the JSON literal ``null`` for ``fitTo``; normalise - // without mutating the caller's reactive update payload. + /* + * Backend rejects the JSON literal ``null`` for ``fitTo``; normalise + * without mutating the caller's reactive update payload. + */ const body = updates.fitTo === null ? { ...updates, fitTo: "" } : updates; const pkList = ids.join(","); return HTTP.patch(`/${group}/${pkList}/bookmark?${queryString}`, body); diff --git a/frontend/src/api/v3/common.js b/frontend/src/api/v3/common.js index e7541d747..cc6ead205 100644 --- a/frontend/src/api/v3/common.js +++ b/frontend/src/api/v3/common.js @@ -61,11 +61,13 @@ const _addTimestamp = (params, ts) => { params.ts = ts; }; -// Public alias: deep-clone a Vue reactive (or plain) object/array, -// recursively unwrapping proxies via ``toRaw`` at every level. -// Prefer this over ``structuredClone`` when the source might be a -// Pinia / Vue reactive value — structuredClone trips ``DataCloneError`` -// on certain reactive Array proxies (see ``getMetadata`` history). +/* + * Public alias: deep-clone a Vue reactive (or plain) object/array, + * recursively unwrapping proxies via ``toRaw`` at every level. + * Prefer this over ``structuredClone`` when the source might be a + * Pinia / Vue reactive value — structuredClone trips ``DataCloneError`` + * on certain reactive Array proxies (see ``getMetadata`` history). + */ export const deepClone = (obj) => _deepClone(obj, false); export const serializeParams = (data, ts, filterEmpty = true) => { @@ -89,11 +91,13 @@ export const getDownloadIOSPWAFix = (href, filename) => { const blob = new Blob([response.data], { type: "application/octet-stream", }); - // ``createObjectURL`` returns a string ``blob:...`` URL; - // ``revokeObjectURL`` must be called on that same string, - // not on the underlying Blob. The original code passed the - // Blob, which silently no-op'd and leaked the object URL on - // every iOS PWA download. + /* + * ``createObjectURL`` returns a string ``blob:...`` URL; + * ``revokeObjectURL`` must be called on that same string, + * not on the underlying Blob. The original code passed the + * Blob, which silently no-op'd and leaked the object URL on + * every iOS PWA download. + */ const objectUrl = globalThis.URL.createObjectURL(blob); link.href = objectUrl; link.download = filename; diff --git a/frontend/src/api/v3/reader.js b/frontend/src/api/v3/reader.js index 1ef125007..1d0cff054 100644 --- a/frontend/src/api/v3/reader.js +++ b/frontend/src/api/v3/reader.js @@ -26,17 +26,19 @@ const _getReaderAPIPath = (pk) => globalThis.CODEX.API_V3_PATH + _getBookPath(pk); export const getComicPageSource = ({ pk, page, mtime, serve }) => { - // ``serve`` is the optional PDF serving-mode hint forwarded to the - // backend ``ReaderPageView``: ``auto`` (detector decides), - // ``image`` (always rasterize), or ``pdf`` (skip the detector and - // serve a single-page PDF blob). Ignored by the backend for - // non-PDF archives. Omitting the param keeps the URL identical to - // the legacy shape so HTTP caches don't fragment. - // - // The query name is ``serve`` rather than ``format`` because DRF - // reserves ``?format=`` (URL_FORMAT_OVERRIDE) as a renderer-format - // selector and raises NotFound for unknown values before the view - // dispatches. + /* + * ``serve`` is the optional PDF serving-mode hint forwarded to the + * backend ``ReaderPageView``: ``auto`` (detector decides), + * ``image`` (always rasterize), or ``pdf`` (skip the detector and + * serve a single-page PDF blob). Ignored by the backend for + * non-PDF archives. Omitting the param keeps the URL identical to + * the legacy shape so HTTP caches don't fragment. + * + * The query name is ``serve`` rather than ``format`` because DRF + * reserves ``?format=`` (URL_FORMAT_OVERRIDE) as a renderer-format + * selector and raises NotFound for unknown values before the view + * dispatches. + */ const bookAPIPath = _getReaderAPIPath(pk); let url = `${bookAPIPath}/${page}/page.jpg?ts=${mtime}`; if (serve && serve !== "auto") { @@ -46,8 +48,10 @@ export const getComicPageSource = ({ pk, page, mtime, serve }) => { }; export const getComicDownloadURL = ({ pk }, fn, ts) => { - // Consumed via ``HTTP.get`` (iOS-PWA download fix), so the ``baseURL`` - // prefix is added by xior — no leading slash here. + /* + * Consumed via ``HTTP.get`` (iOS-PWA download fix), so the ``baseURL`` + * prefix is added by xior — no leading slash here. + */ const bookPath = _getBookPath(pk); fn = fn ? encodeURIComponent(fn) : `comic-${pk}.cbz`; return `${bookPath}/download/${fn}?ts=${ts}`; @@ -60,9 +64,11 @@ export const getDownloadPageURL = ({ pk, page, mtime }) => { }; export const getPDFInBrowserURL = ({ pk, mtime }) => { - // Consumed by the "Read in Tab" link (``) — needs - // an absolute path so the browser doesn't resolve it relative to the - // current SPA route. + /* + * Consumed by the "Read in Tab" link (``) — needs + * an absolute path so the browser doesn't resolve it relative to the + * current SPA route. + */ const bookPath = _getBookPath(pk); return `/${bookPath}/book.pdf?ts=${mtime}`; }; diff --git a/frontend/src/components/admin/create-update-dialog/create-update-inputs-mixin.js b/frontend/src/components/admin/create-update-dialog/create-update-inputs-mixin.js index 375d37e7b..01eeaed5b 100644 --- a/frontend/src/components/admin/create-update-dialog/create-update-inputs-mixin.js +++ b/frontend/src/components/admin/create-update-dialog/create-update-inputs-mixin.js @@ -15,10 +15,12 @@ import { useAdminStore } from "@/stores/admin"; * watchers that sync row and oldRow and emit "change", * and the nameSet action from the admin store. */ -// ``deepClone`` (over ``structuredClone``) because ``oldRow`` rows -// from the admin store carry reactive nested arrays (``groups``, -// ``userSet``, ``librarySet``) that ``structuredClone`` can refuse -// to clone. +/* + * ``deepClone`` (over ``structuredClone``) because ``oldRow`` rows + * from the admin store carry reactive nested arrays (``groups``, + * ``userSet``, ``librarySet``) that ``structuredClone`` can refuse + * to clone. + */ export default { props: { oldRow: { diff --git a/frontend/src/components/auth/auth-form-mixin.js b/frontend/src/components/auth/auth-form-mixin.js index a49c6f3f0..3cb158e5f 100644 --- a/frontend/src/components/auth/auth-form-mixin.js +++ b/frontend/src/components/auth/auth-form-mixin.js @@ -19,8 +19,10 @@ import { useCommonStore } from "@/stores/common"; export default { setup() { - // Prevent keystrokes from leaking through dialogs to underlying views - // (e.g. reader keyboard shortcuts). + /* + * Prevent keystrokes from leaking through dialogs to underlying views + * (e.g. reader keyboard shortcuts). + */ useEventListener(globalThis, "keyup", (event) => { event.stopImmediatePropagation(); }); diff --git a/frontend/src/components/browser/card/card.vue b/frontend/src/components/browser/card/card.vue index 3a6429358..f0ae56953 100644 --- a/frontend/src/components/browser/card/card.vue +++ b/frontend/src/components/browser/card/card.vue @@ -15,7 +15,7 @@ class="cardCoverOverlay selectManyOverlay" :class="{ selected: checked }" :aria-label="linkLabel" - @click="toggleItem(item)" + @click="selectItemAt(item, { shift: $event.shiftKey })" /> reader; group rows -> drill in. Mirrors the * navigation contract of so the table behaves as * an alternate presentation, not an alternate router. When * select-many is active, plain row clicks toggle the selection - * instead of navigating — matches the cover view's overlay - * behavior. + * instead of navigating — and shift-click extends a range from + * the last anchor, matching the cover view. */ if (this.selectManyActive) { - this.toggleItem(row); + this.selectItemAt(row, { shift: Boolean(event?.shiftKey) }); return; } const group = row.group ?? "c"; diff --git a/frontend/src/plugins/router.js b/frontend/src/plugins/router.js index c063df27a..b4dc50898 100644 --- a/frontend/src/plugins/router.js +++ b/frontend/src/plugins/router.js @@ -66,8 +66,10 @@ const router = new createRouter({ }); router.afterEach((to) => { - // Strip the ts cache-busting query param from the visible URL. - // Vue Router's $route watcher already captured the value before this fires. + /* + * Strip the ts cache-busting query param from the visible URL. + * Vue Router's $route watcher already captured the value before this fires. + */ if (to.query?.ts !== undefined) { const { ts, ...query } = to.query; const cleanRoute = router.resolve({ @@ -80,9 +82,11 @@ router.afterEach((to) => { } }); -// Self-heal stale chunk references after a deploy: when a lazy-loaded route -// component fails to fetch (because its hashed filename no longer exists on -// the server), force a full page load so the browser pulls a fresh index.html. +/* + * Self-heal stale chunk references after a deploy: when a lazy-loaded route + * component fails to fetch (because its hashed filename no longer exists on + * the server), force a full page load so the browser pulls a fresh index.html. + */ const CHUNK_ERROR_PATTERNS = [ /Failed to fetch dynamically imported module/i, /error loading dynamically imported module/i, diff --git a/frontend/src/plugins/vuetify.js b/frontend/src/plugins/vuetify.js index af856ac44..1675259c6 100644 --- a/frontend/src/plugins/vuetify.js +++ b/frontend/src/plugins/vuetify.js @@ -32,13 +32,15 @@ const codexTheme = { }, }; -// CSP note: this block makes Vuetify inject a runtime