detached f328a4f

feat(webui): add TypeScript types and ESLint Svelte plugin

commit
f328a4f28a8984d44d992817dc6b46b9c21fcb1c
author
brice · · 2026-01-24 14:19 UTC
parent
11eef0c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +518 -35
M webui/eslint.config.js +21 -0
@@ -1,9 +1,30 @@
11 import js from "@eslint/js";
2+import svelte from "eslint-plugin-svelte";
3+import globals from "globals";
24 import tseslint from "typescript-eslint";
5+import svelteConfig from "./svelte.config.js";
36
47 export default tseslint.config(
58 js.configs.recommended,
69 ...tseslint.configs.recommended,
10+ ...svelte.configs.recommended,
11+ {
12+ languageOptions: {
13+ globals: {
14+ ...globals.browser,
15+ },
16+ },
17+ },
18+ {
19+ files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
20+ languageOptions: {
21+ parserOptions: {
22+ extraFileExtensions: [".svelte"],
23+ parser: tseslint.parser,
24+ svelteConfig,
25+ },
26+ },
27+ },
728 {
829 files: ["**/*.{js,mjs,cjs,ts}"],
930 languageOptions: {
@@ -1,9 +1,30 @@
1 import js from "@eslint/js";1 import js from "@eslint/js";
2+import svelte from "eslint-plugin-svelte";
3+import globals from "globals";
2 import tseslint from "typescript-eslint";4 import tseslint from "typescript-eslint";
5+import svelteConfig from "./svelte.config.js";
3 6
4 export default tseslint.config(7 export default tseslint.config(
5 js.configs.recommended,8 js.configs.recommended,
6 ...tseslint.configs.recommended,9 ...tseslint.configs.recommended,
10+ ...svelte.configs.recommended,
11+ {
12+ languageOptions: {
13+ globals: {
14+ ...globals.browser,
15+ },
16+ },
17+ },
18+ {
19+ files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
20+ languageOptions: {
21+ parserOptions: {
22+ extraFileExtensions: [".svelte"],
23+ parser: tseslint.parser,
24+ svelteConfig,
25+ },
26+ },
27+ },
7 {28 {
8 files: ["**/*.{js,mjs,cjs,ts}"],29 files: ["**/*.{js,mjs,cjs,ts}"],
9 languageOptions: {30 languageOptions: {
M webui/package-lock.json +308 -7
@@ -25,6 +25,8 @@
2525 "@typescript-eslint/parser": "^7.0.0",
2626 "autoprefixer": "^10.4.20",
2727 "eslint": "^8.57.0",
28+ "eslint-plugin-svelte": "^3.14.0",
29+ "globals": "^17.1.0",
2830 "postcss": "^8.4.47",
2931 "svelte": "^5.0.0",
3032 "svelte-awesome-color-picker": "^4.1.0",
@@ -505,6 +507,22 @@
505507 "concat-map": "0.0.1"
506508 }
507509 },
510+ "node_modules/@eslint/eslintrc/node_modules/globals": {
511+ "version": "13.24.0",
512+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
513+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
514+ "dev": true,
515+ "license": "MIT",
516+ "dependencies": {
517+ "type-fest": "^0.20.2"
518+ },
519+ "engines": {
520+ "node": ">=8"
521+ },
522+ "funding": {
523+ "url": "https://github.com/sponsors/sindresorhus"
524+ }
525+ },
508526 "node_modules/@eslint/eslintrc/node_modules/minimatch": {
509527 "version": "3.1.2",
510528 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2356,6 +2374,19 @@
23562374 "node": ">= 8"
23572375 }
23582376 },
2377+ "node_modules/cssesc": {
2378+ "version": "3.0.0",
2379+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
2380+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
2381+ "dev": true,
2382+ "license": "MIT",
2383+ "bin": {
2384+ "cssesc": "bin/cssesc"
2385+ },
2386+ "engines": {
2387+ "node": ">=4"
2388+ }
2389+ },
23592390 "node_modules/debug": {
23602391 "version": "4.4.3",
23612392 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2583,6 +2614,53 @@
25832614 "url": "https://opencollective.com/eslint"
25842615 }
25852616 },
2617+ "node_modules/eslint-plugin-svelte": {
2618+ "version": "3.14.0",
2619+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.14.0.tgz",
2620+ "integrity": "sha512-Isw0GvaMm0yHxAj71edAdGFh28ufYs+6rk2KlbbZphnqZAzrH3Se3t12IFh2H9+1F/jlDhBBL4oiOJmLqmYX0g==",
2621+ "dev": true,
2622+ "license": "MIT",
2623+ "dependencies": {
2624+ "@eslint-community/eslint-utils": "^4.6.1",
2625+ "@jridgewell/sourcemap-codec": "^1.5.0",
2626+ "esutils": "^2.0.3",
2627+ "globals": "^16.0.0",
2628+ "known-css-properties": "^0.37.0",
2629+ "postcss": "^8.4.49",
2630+ "postcss-load-config": "^3.1.4",
2631+ "postcss-safe-parser": "^7.0.0",
2632+ "semver": "^7.6.3",
2633+ "svelte-eslint-parser": "^1.4.0"
2634+ },
2635+ "engines": {
2636+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2637+ },
2638+ "funding": {
2639+ "url": "https://github.com/sponsors/ota-meshi"
2640+ },
2641+ "peerDependencies": {
2642+ "eslint": "^8.57.1 || ^9.0.0",
2643+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
2644+ },
2645+ "peerDependenciesMeta": {
2646+ "svelte": {
2647+ "optional": true
2648+ }
2649+ }
2650+ },
2651+ "node_modules/eslint-plugin-svelte/node_modules/globals": {
2652+ "version": "16.5.0",
2653+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
2654+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
2655+ "dev": true,
2656+ "license": "MIT",
2657+ "engines": {
2658+ "node": ">=18"
2659+ },
2660+ "funding": {
2661+ "url": "https://github.com/sponsors/sindresorhus"
2662+ }
2663+ },
25862664 "node_modules/eslint-scope": {
25872665 "version": "7.2.2",
25882666 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -2624,6 +2702,22 @@
26242702 "concat-map": "0.0.1"
26252703 }
26262704 },
2705+ "node_modules/eslint/node_modules/globals": {
2706+ "version": "13.24.0",
2707+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
2708+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
2709+ "dev": true,
2710+ "license": "MIT",
2711+ "dependencies": {
2712+ "type-fest": "^0.20.2"
2713+ },
2714+ "engines": {
2715+ "node": ">=8"
2716+ },
2717+ "funding": {
2718+ "url": "https://github.com/sponsors/sindresorhus"
2719+ }
2720+ },
26272721 "node_modules/eslint/node_modules/minimatch": {
26282722 "version": "3.1.2",
26292723 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2962,16 +3056,13 @@
29623056 }
29633057 },
29643058 "node_modules/globals": {
2965- "version": "13.24.0",
2966- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
2967- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
3059+ "version": "17.1.0",
3060+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz",
3061+ "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==",
29683062 "dev": true,
29693063 "license": "MIT",
2970- "dependencies": {
2971- "type-fest": "^0.20.2"
2972- },
29733064 "engines": {
2974- "node": ">=8"
3065+ "node": ">=18"
29753066 },
29763067 "funding": {
29773068 "url": "https://github.com/sponsors/sindresorhus"
@@ -3207,6 +3298,13 @@
32073298 "node": ">=6"
32083299 }
32093300 },
3301+ "node_modules/known-css-properties": {
3302+ "version": "0.37.0",
3303+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
3304+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
3305+ "dev": true,
3306+ "license": "MIT"
3307+ },
32103308 "node_modules/levn": {
32113309 "version": "0.4.1",
32123310 "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3482,6 +3580,16 @@
34823580 "url": "https://opencollective.com/parcel"
34833581 }
34843582 },
3583+ "node_modules/lilconfig": {
3584+ "version": "2.1.0",
3585+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
3586+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
3587+ "dev": true,
3588+ "license": "MIT",
3589+ "engines": {
3590+ "node": ">=10"
3591+ }
3592+ },
34853593 "node_modules/locate-character": {
34863594 "version": "3.0.0",
34873595 "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@@ -3865,6 +3973,104 @@
38653973 "node": "^10 || ^12 || >=14"
38663974 }
38673975 },
3976+ "node_modules/postcss-load-config": {
3977+ "version": "3.1.4",
3978+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
3979+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
3980+ "dev": true,
3981+ "license": "MIT",
3982+ "dependencies": {
3983+ "lilconfig": "^2.0.5",
3984+ "yaml": "^1.10.2"
3985+ },
3986+ "engines": {
3987+ "node": ">= 10"
3988+ },
3989+ "funding": {
3990+ "type": "opencollective",
3991+ "url": "https://opencollective.com/postcss/"
3992+ },
3993+ "peerDependencies": {
3994+ "postcss": ">=8.0.9",
3995+ "ts-node": ">=9.0.0"
3996+ },
3997+ "peerDependenciesMeta": {
3998+ "postcss": {
3999+ "optional": true
4000+ },
4001+ "ts-node": {
4002+ "optional": true
4003+ }
4004+ }
4005+ },
4006+ "node_modules/postcss-safe-parser": {
4007+ "version": "7.0.1",
4008+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
4009+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
4010+ "dev": true,
4011+ "funding": [
4012+ {
4013+ "type": "opencollective",
4014+ "url": "https://opencollective.com/postcss/"
4015+ },
4016+ {
4017+ "type": "tidelift",
4018+ "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
4019+ },
4020+ {
4021+ "type": "github",
4022+ "url": "https://github.com/sponsors/ai"
4023+ }
4024+ ],
4025+ "license": "MIT",
4026+ "engines": {
4027+ "node": ">=18.0"
4028+ },
4029+ "peerDependencies": {
4030+ "postcss": "^8.4.31"
4031+ }
4032+ },
4033+ "node_modules/postcss-scss": {
4034+ "version": "4.0.9",
4035+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
4036+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
4037+ "dev": true,
4038+ "funding": [
4039+ {
4040+ "type": "opencollective",
4041+ "url": "https://opencollective.com/postcss/"
4042+ },
4043+ {
4044+ "type": "tidelift",
4045+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
4046+ },
4047+ {
4048+ "type": "github",
4049+ "url": "https://github.com/sponsors/ai"
4050+ }
4051+ ],
4052+ "license": "MIT",
4053+ "engines": {
4054+ "node": ">=12.0"
4055+ },
4056+ "peerDependencies": {
4057+ "postcss": "^8.4.29"
4058+ }
4059+ },
4060+ "node_modules/postcss-selector-parser": {
4061+ "version": "7.1.1",
4062+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
4063+ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
4064+ "dev": true,
4065+ "license": "MIT",
4066+ "dependencies": {
4067+ "cssesc": "^3.0.0",
4068+ "util-deprecate": "^1.0.2"
4069+ },
4070+ "engines": {
4071+ "node": ">=4"
4072+ }
4073+ },
38684074 "node_modules/postcss-value-parser": {
38694075 "version": "4.2.0",
38704076 "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -4253,6 +4459,84 @@
42534459 "typescript": ">=5.0.0"
42544460 }
42554461 },
4462+ "node_modules/svelte-eslint-parser": {
4463+ "version": "1.4.1",
4464+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz",
4465+ "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==",
4466+ "dev": true,
4467+ "license": "MIT",
4468+ "dependencies": {
4469+ "eslint-scope": "^8.2.0",
4470+ "eslint-visitor-keys": "^4.0.0",
4471+ "espree": "^10.0.0",
4472+ "postcss": "^8.4.49",
4473+ "postcss-scss": "^4.0.9",
4474+ "postcss-selector-parser": "^7.0.0"
4475+ },
4476+ "engines": {
4477+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0",
4478+ "pnpm": "10.24.0"
4479+ },
4480+ "funding": {
4481+ "url": "https://github.com/sponsors/ota-meshi"
4482+ },
4483+ "peerDependencies": {
4484+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
4485+ },
4486+ "peerDependenciesMeta": {
4487+ "svelte": {
4488+ "optional": true
4489+ }
4490+ }
4491+ },
4492+ "node_modules/svelte-eslint-parser/node_modules/eslint-scope": {
4493+ "version": "8.4.0",
4494+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
4495+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
4496+ "dev": true,
4497+ "license": "BSD-2-Clause",
4498+ "dependencies": {
4499+ "esrecurse": "^4.3.0",
4500+ "estraverse": "^5.2.0"
4501+ },
4502+ "engines": {
4503+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4504+ },
4505+ "funding": {
4506+ "url": "https://opencollective.com/eslint"
4507+ }
4508+ },
4509+ "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": {
4510+ "version": "4.2.1",
4511+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
4512+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
4513+ "dev": true,
4514+ "license": "Apache-2.0",
4515+ "engines": {
4516+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4517+ },
4518+ "funding": {
4519+ "url": "https://opencollective.com/eslint"
4520+ }
4521+ },
4522+ "node_modules/svelte-eslint-parser/node_modules/espree": {
4523+ "version": "10.4.0",
4524+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
4525+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
4526+ "dev": true,
4527+ "license": "BSD-2-Clause",
4528+ "dependencies": {
4529+ "acorn": "^8.15.0",
4530+ "acorn-jsx": "^5.3.2",
4531+ "eslint-visitor-keys": "^4.2.1"
4532+ },
4533+ "engines": {
4534+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4535+ },
4536+ "funding": {
4537+ "url": "https://opencollective.com/eslint"
4538+ }
4539+ },
42564540 "node_modules/svelte-toolbelt": {
42574541 "version": "0.10.6",
42584542 "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",
@@ -4729,6 +5013,13 @@
47295013 "punycode": "^2.1.0"
47305014 }
47315015 },
5016+ "node_modules/util-deprecate": {
5017+ "version": "1.0.2",
5018+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
5019+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
5020+ "dev": true,
5021+ "license": "MIT"
5022+ },
47325023 "node_modules/vite": {
47335024 "version": "5.4.21",
47345025 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -4843,6 +5134,16 @@
48435134 "dev": true,
48445135 "license": "ISC"
48455136 },
5137+ "node_modules/yaml": {
5138+ "version": "1.10.2",
5139+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
5140+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
5141+ "dev": true,
5142+ "license": "ISC",
5143+ "engines": {
5144+ "node": ">= 6"
5145+ }
5146+ },
48465147 "node_modules/yocto-queue": {
48475148 "version": "0.1.0",
48485149 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -25,6 +25,8 @@
25 "@typescript-eslint/parser": "^7.0.0",25 "@typescript-eslint/parser": "^7.0.0",
26 "autoprefixer": "^10.4.20",26 "autoprefixer": "^10.4.20",
27 "eslint": "^8.57.0",27 "eslint": "^8.57.0",
28+ "eslint-plugin-svelte": "^3.14.0",
29+ "globals": "^17.1.0",
28 "postcss": "^8.4.47",30 "postcss": "^8.4.47",
29 "svelte": "^5.0.0",31 "svelte": "^5.0.0",
30 "svelte-awesome-color-picker": "^4.1.0",32 "svelte-awesome-color-picker": "^4.1.0",
@@ -505,6 +507,22 @@
505 "concat-map": "0.0.1"507 "concat-map": "0.0.1"
506 }508 }
507 },509 },
510+ "node_modules/@eslint/eslintrc/node_modules/globals": {
511+ "version": "13.24.0",
512+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
513+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
514+ "dev": true,
515+ "license": "MIT",
516+ "dependencies": {
517+ "type-fest": "^0.20.2"
518+ },
519+ "engines": {
520+ "node": ">=8"
521+ },
522+ "funding": {
523+ "url": "https://github.com/sponsors/sindresorhus"
524+ }
525+ },
508 "node_modules/@eslint/eslintrc/node_modules/minimatch": {526 "node_modules/@eslint/eslintrc/node_modules/minimatch": {
509 "version": "3.1.2",527 "version": "3.1.2",
510 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",528 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2356,6 +2374,19 @@
2356 "node": ">= 8"2374 "node": ">= 8"
2357 }2375 }
2358 },2376 },
2377+ "node_modules/cssesc": {
2378+ "version": "3.0.0",
2379+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
2380+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
2381+ "dev": true,
2382+ "license": "MIT",
2383+ "bin": {
2384+ "cssesc": "bin/cssesc"
2385+ },
2386+ "engines": {
2387+ "node": ">=4"
2388+ }
2389+ },
2359 "node_modules/debug": {2390 "node_modules/debug": {
2360 "version": "4.4.3",2391 "version": "4.4.3",
2361 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",2392 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2583,6 +2614,53 @@
2583 "url": "https://opencollective.com/eslint"2614 "url": "https://opencollective.com/eslint"
2584 }2615 }
2585 },2616 },
2617+ "node_modules/eslint-plugin-svelte": {
2618+ "version": "3.14.0",
2619+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.14.0.tgz",
2620+ "integrity": "sha512-Isw0GvaMm0yHxAj71edAdGFh28ufYs+6rk2KlbbZphnqZAzrH3Se3t12IFh2H9+1F/jlDhBBL4oiOJmLqmYX0g==",
2621+ "dev": true,
2622+ "license": "MIT",
2623+ "dependencies": {
2624+ "@eslint-community/eslint-utils": "^4.6.1",
2625+ "@jridgewell/sourcemap-codec": "^1.5.0",
2626+ "esutils": "^2.0.3",
2627+ "globals": "^16.0.0",
2628+ "known-css-properties": "^0.37.0",
2629+ "postcss": "^8.4.49",
2630+ "postcss-load-config": "^3.1.4",
2631+ "postcss-safe-parser": "^7.0.0",
2632+ "semver": "^7.6.3",
2633+ "svelte-eslint-parser": "^1.4.0"
2634+ },
2635+ "engines": {
2636+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2637+ },
2638+ "funding": {
2639+ "url": "https://github.com/sponsors/ota-meshi"
2640+ },
2641+ "peerDependencies": {
2642+ "eslint": "^8.57.1 || ^9.0.0",
2643+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
2644+ },
2645+ "peerDependenciesMeta": {
2646+ "svelte": {
2647+ "optional": true
2648+ }
2649+ }
2650+ },
2651+ "node_modules/eslint-plugin-svelte/node_modules/globals": {
2652+ "version": "16.5.0",
2653+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
2654+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
2655+ "dev": true,
2656+ "license": "MIT",
2657+ "engines": {
2658+ "node": ">=18"
2659+ },
2660+ "funding": {
2661+ "url": "https://github.com/sponsors/sindresorhus"
2662+ }
2663+ },
2586 "node_modules/eslint-scope": {2664 "node_modules/eslint-scope": {
2587 "version": "7.2.2",2665 "version": "7.2.2",
2588 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",2666 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -2624,6 +2702,22 @@
2624 "concat-map": "0.0.1"2702 "concat-map": "0.0.1"
2625 }2703 }
2626 },2704 },
2705+ "node_modules/eslint/node_modules/globals": {
2706+ "version": "13.24.0",
2707+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
2708+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
2709+ "dev": true,
2710+ "license": "MIT",
2711+ "dependencies": {
2712+ "type-fest": "^0.20.2"
2713+ },
2714+ "engines": {
2715+ "node": ">=8"
2716+ },
2717+ "funding": {
2718+ "url": "https://github.com/sponsors/sindresorhus"
2719+ }
2720+ },
2627 "node_modules/eslint/node_modules/minimatch": {2721 "node_modules/eslint/node_modules/minimatch": {
2628 "version": "3.1.2",2722 "version": "3.1.2",
2629 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",2723 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2962,16 +3056,13 @@
2962 }3056 }
2963 },3057 },
2964 "node_modules/globals": {3058 "node_modules/globals": {
2965- "version": "13.24.0",3059+ "version": "17.1.0",
2966- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",3060+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz",
2967- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",3061+ "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==",
2968 "dev": true,3062 "dev": true,
2969 "license": "MIT",3063 "license": "MIT",
2970- "dependencies": {
2971- "type-fest": "^0.20.2"
2972- },
2973 "engines": {3064 "engines": {
2974- "node": ">=8"3065+ "node": ">=18"
2975 },3066 },
2976 "funding": {3067 "funding": {
2977 "url": "https://github.com/sponsors/sindresorhus"3068 "url": "https://github.com/sponsors/sindresorhus"
@@ -3207,6 +3298,13 @@
3207 "node": ">=6"3298 "node": ">=6"
3208 }3299 }
3209 },3300 },
3301+ "node_modules/known-css-properties": {
3302+ "version": "0.37.0",
3303+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
3304+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
3305+ "dev": true,
3306+ "license": "MIT"
3307+ },
3210 "node_modules/levn": {3308 "node_modules/levn": {
3211 "version": "0.4.1",3309 "version": "0.4.1",
3212 "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",3310 "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3482,6 +3580,16 @@
3482 "url": "https://opencollective.com/parcel"3580 "url": "https://opencollective.com/parcel"
3483 }3581 }
3484 },3582 },
3583+ "node_modules/lilconfig": {
3584+ "version": "2.1.0",
3585+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
3586+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
3587+ "dev": true,
3588+ "license": "MIT",
3589+ "engines": {
3590+ "node": ">=10"
3591+ }
3592+ },
3485 "node_modules/locate-character": {3593 "node_modules/locate-character": {
3486 "version": "3.0.0",3594 "version": "3.0.0",
3487 "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",3595 "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@@ -3865,6 +3973,104 @@
3865 "node": "^10 || ^12 || >=14"3973 "node": "^10 || ^12 || >=14"
3866 }3974 }
3867 },3975 },
3976+ "node_modules/postcss-load-config": {
3977+ "version": "3.1.4",
3978+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
3979+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
3980+ "dev": true,
3981+ "license": "MIT",
3982+ "dependencies": {
3983+ "lilconfig": "^2.0.5",
3984+ "yaml": "^1.10.2"
3985+ },
3986+ "engines": {
3987+ "node": ">= 10"
3988+ },
3989+ "funding": {
3990+ "type": "opencollective",
3991+ "url": "https://opencollective.com/postcss/"
3992+ },
3993+ "peerDependencies": {
3994+ "postcss": ">=8.0.9",
3995+ "ts-node": ">=9.0.0"
3996+ },
3997+ "peerDependenciesMeta": {
3998+ "postcss": {
3999+ "optional": true
4000+ },
4001+ "ts-node": {
4002+ "optional": true
4003+ }
4004+ }
4005+ },
4006+ "node_modules/postcss-safe-parser": {
4007+ "version": "7.0.1",
4008+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
4009+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
4010+ "dev": true,
4011+ "funding": [
4012+ {
4013+ "type": "opencollective",
4014+ "url": "https://opencollective.com/postcss/"
4015+ },
4016+ {
4017+ "type": "tidelift",
4018+ "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
4019+ },
4020+ {
4021+ "type": "github",
4022+ "url": "https://github.com/sponsors/ai"
4023+ }
4024+ ],
4025+ "license": "MIT",
4026+ "engines": {
4027+ "node": ">=18.0"
4028+ },
4029+ "peerDependencies": {
4030+ "postcss": "^8.4.31"
4031+ }
4032+ },
4033+ "node_modules/postcss-scss": {
4034+ "version": "4.0.9",
4035+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
4036+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
4037+ "dev": true,
4038+ "funding": [
4039+ {
4040+ "type": "opencollective",
4041+ "url": "https://opencollective.com/postcss/"
4042+ },
4043+ {
4044+ "type": "tidelift",
4045+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
4046+ },
4047+ {
4048+ "type": "github",
4049+ "url": "https://github.com/sponsors/ai"
4050+ }
4051+ ],
4052+ "license": "MIT",
4053+ "engines": {
4054+ "node": ">=12.0"
4055+ },
4056+ "peerDependencies": {
4057+ "postcss": "^8.4.29"
4058+ }
4059+ },
4060+ "node_modules/postcss-selector-parser": {
4061+ "version": "7.1.1",
4062+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
4063+ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
4064+ "dev": true,
4065+ "license": "MIT",
4066+ "dependencies": {
4067+ "cssesc": "^3.0.0",
4068+ "util-deprecate": "^1.0.2"
4069+ },
4070+ "engines": {
4071+ "node": ">=4"
4072+ }
4073+ },
3868 "node_modules/postcss-value-parser": {4074 "node_modules/postcss-value-parser": {
3869 "version": "4.2.0",4075 "version": "4.2.0",
3870 "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",4076 "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -4253,6 +4459,84 @@
4253 "typescript": ">=5.0.0"4459 "typescript": ">=5.0.0"
4254 }4460 }
4255 },4461 },
4462+ "node_modules/svelte-eslint-parser": {
4463+ "version": "1.4.1",
4464+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz",
4465+ "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==",
4466+ "dev": true,
4467+ "license": "MIT",
4468+ "dependencies": {
4469+ "eslint-scope": "^8.2.0",
4470+ "eslint-visitor-keys": "^4.0.0",
4471+ "espree": "^10.0.0",
4472+ "postcss": "^8.4.49",
4473+ "postcss-scss": "^4.0.9",
4474+ "postcss-selector-parser": "^7.0.0"
4475+ },
4476+ "engines": {
4477+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0",
4478+ "pnpm": "10.24.0"
4479+ },
4480+ "funding": {
4481+ "url": "https://github.com/sponsors/ota-meshi"
4482+ },
4483+ "peerDependencies": {
4484+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
4485+ },
4486+ "peerDependenciesMeta": {
4487+ "svelte": {
4488+ "optional": true
4489+ }
4490+ }
4491+ },
4492+ "node_modules/svelte-eslint-parser/node_modules/eslint-scope": {
4493+ "version": "8.4.0",
4494+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
4495+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
4496+ "dev": true,
4497+ "license": "BSD-2-Clause",
4498+ "dependencies": {
4499+ "esrecurse": "^4.3.0",
4500+ "estraverse": "^5.2.0"
4501+ },
4502+ "engines": {
4503+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4504+ },
4505+ "funding": {
4506+ "url": "https://opencollective.com/eslint"
4507+ }
4508+ },
4509+ "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": {
4510+ "version": "4.2.1",
4511+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
4512+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
4513+ "dev": true,
4514+ "license": "Apache-2.0",
4515+ "engines": {
4516+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4517+ },
4518+ "funding": {
4519+ "url": "https://opencollective.com/eslint"
4520+ }
4521+ },
4522+ "node_modules/svelte-eslint-parser/node_modules/espree": {
4523+ "version": "10.4.0",
4524+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
4525+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
4526+ "dev": true,
4527+ "license": "BSD-2-Clause",
4528+ "dependencies": {
4529+ "acorn": "^8.15.0",
4530+ "acorn-jsx": "^5.3.2",
4531+ "eslint-visitor-keys": "^4.2.1"
4532+ },
4533+ "engines": {
4534+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
4535+ },
4536+ "funding": {
4537+ "url": "https://opencollective.com/eslint"
4538+ }
4539+ },
4256 "node_modules/svelte-toolbelt": {4540 "node_modules/svelte-toolbelt": {
4257 "version": "0.10.6",4541 "version": "0.10.6",
4258 "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",4542 "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",
@@ -4729,6 +5013,13 @@
4729 "punycode": "^2.1.0"5013 "punycode": "^2.1.0"
4730 }5014 }
4731 },5015 },
5016+ "node_modules/util-deprecate": {
5017+ "version": "1.0.2",
5018+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
5019+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
5020+ "dev": true,
5021+ "license": "MIT"
5022+ },
4732 "node_modules/vite": {5023 "node_modules/vite": {
4733 "version": "5.4.21",5024 "version": "5.4.21",
4734 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",5025 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -4843,6 +5134,16 @@
4843 "dev": true,5134 "dev": true,
4844 "license": "ISC"5135 "license": "ISC"
4845 },5136 },
5137+ "node_modules/yaml": {
5138+ "version": "1.10.2",
5139+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
5140+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
5141+ "dev": true,
5142+ "license": "ISC",
5143+ "engines": {
5144+ "node": ">= 6"
5145+ }
5146+ },
4846 "node_modules/yocto-queue": {5147 "node_modules/yocto-queue": {
4847 "version": "0.1.0",5148 "version": "0.1.0",
4848 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",5149 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
M webui/package.json +2 -0
@@ -27,6 +27,8 @@
2727 "@typescript-eslint/parser": "^7.0.0",
2828 "autoprefixer": "^10.4.20",
2929 "eslint": "^8.57.0",
30+ "eslint-plugin-svelte": "^3.14.0",
31+ "globals": "^17.1.0",
3032 "postcss": "^8.4.47",
3133 "svelte": "^5.0.0",
3234 "svelte-awesome-color-picker": "^4.1.0",
@@ -27,6 +27,8 @@
27 "@typescript-eslint/parser": "^7.0.0",27 "@typescript-eslint/parser": "^7.0.0",
28 "autoprefixer": "^10.4.20",28 "autoprefixer": "^10.4.20",
29 "eslint": "^8.57.0",29 "eslint": "^8.57.0",
30+ "eslint-plugin-svelte": "^3.14.0",
31+ "globals": "^17.1.0",
30 "postcss": "^8.4.47",32 "postcss": "^8.4.47",
31 "svelte": "^5.0.0",33 "svelte": "^5.0.0",
32 "svelte-awesome-color-picker": "^4.1.0",34 "svelte-awesome-color-picker": "^4.1.0",
M webui/src/pages/homepage/messaging.ts +34 -27
@@ -23,8 +23,12 @@ import { homepageState } from './state.svelte';
2323 // REQUEST TRACKING
2424 // ═══════════════════════════════════════════════════════════════════════════════
2525
26-interface PendingRequest<T = unknown> {
27- resolve: (value: T) => void;
26+// Pending requests store resolvers for different response types.
27+// The resolve function accepts `unknown` since requests with different
28+// response types share the same map. Type safety is maintained at the
29+// sendMessage<T> API level.
30+interface PendingRequest {
31+ resolve: (value: unknown) => void;
2832 reject: (error: Error) => void;
2933 timeout: ReturnType<typeof setTimeout>;
3034 }
@@ -38,8 +42,8 @@ let callbacksInitialized = false;
3842 // WEBKIT BRIDGE UTILITIES
3943 // ═══════════════════════════════════════════════════════════════════════════════
4044
41-function getWebKitBridge(): { postMessage: (msg: unknown) => void } | null {
42- const bridge = (window as any).webkit?.messageHandlers?.dumber;
45+function getWebKitBridge(): DumberBridge | null {
46+ const bridge = window.webkit?.messageHandlers?.dumber;
4347 if (bridge && typeof bridge.postMessage === 'function') {
4448 return bridge;
4549 }
@@ -47,7 +51,7 @@ function getWebKitBridge(): { postMessage: (msg: unknown) => void } | null {
4751 }
4852
4953 function getWebViewId(): number {
50- return (window as any).__dumber_webview_id || 0;
54+ return window.__dumber_webview_id ?? 0;
5155 }
5256
5357 function generateRequestId(type: string): string {
@@ -62,9 +66,7 @@ function initializeCallbacks(): void {
6266 if (callbacksInitialized) return;
6367
6468 // Generic response handler - accepts a single Response object from Go backend
65- (window as any).__dumber_homepage_response = (
66- response: { requestId: string; success: boolean; data?: unknown; error?: string }
67- ) => {
69+ window.__dumber_homepage_response = (response) => {
6870 const request = pendingRequests.get(response.requestId);
6971 if (!request) return;
7072
@@ -74,61 +76,60 @@ function initializeCallbacks(): void {
7476 if (response.success) {
7577 request.resolve(response.data);
7678 } else {
77- request.reject(new Error(response.error || 'Unknown error'));
79+ request.reject(new Error(response.error ?? 'Unknown error'));
7880 }
7981 };
8082
8183 // Legacy callbacks for backward compatibility
8284 // These can receive either raw data or a Response object
83- (window as any).__dumber_history_timeline = (dataOrResponse: unknown) => {
85+ window.__dumber_history_timeline = (dataOrResponse) => {
8486 handleLegacyOrResponseCallback(dataOrResponse);
8587 };
8688
87- (window as any).__dumber_folders = (dataOrResponse: unknown) => {
89+ window.__dumber_folders = (dataOrResponse) => {
8890 handleLegacyOrResponseCallback(dataOrResponse);
8991 };
9092
91- (window as any).__dumber_tags = (dataOrResponse: unknown) => {
93+ window.__dumber_tags = (dataOrResponse) => {
9294 handleLegacyOrResponseCallback(dataOrResponse);
9395 };
9496
95- (window as any).__dumber_favorites = (dataOrResponse: unknown) => {
97+ window.__dumber_favorites = (dataOrResponse) => {
9698 handleLegacyOrResponseCallback(dataOrResponse);
9799 };
98100
99- (window as any).__dumber_analytics = (dataOrResponse: unknown) => {
101+ window.__dumber_analytics = (dataOrResponse) => {
100102 handleLegacyOrResponseCallback(dataOrResponse);
101103 };
102104
103- (window as any).__dumber_domain_stats = (dataOrResponse: unknown) => {
105+ window.__dumber_domain_stats = (dataOrResponse) => {
104106 handleLegacyOrResponseCallback(dataOrResponse);
105107 };
106108
107- (window as any).__dumber_history_search_results = (dataOrResponse: unknown) => {
109+ window.__dumber_history_search_results = (dataOrResponse) => {
108110 handleLegacyOrResponseCallback(dataOrResponse);
109111 };
110112
111- (window as any).__dumber_history_deleted = (dataOrResponse: unknown) => {
113+ window.__dumber_history_deleted = (dataOrResponse) => {
112114 handleLegacyOrResponseCallback(dataOrResponse);
113115 };
114116
115- (window as any).__dumber_history_cleared = (dataOrResponse: unknown) => {
117+ window.__dumber_history_cleared = (dataOrResponse) => {
116118 handleLegacyOrResponseCallback(dataOrResponse);
117119 };
118120
119- (window as any).__dumber_error = (errorOrResponse: unknown) => {
121+ window.__dumber_error = (errorOrResponse) => {
120122 // Handle both formats: direct error string or Response object with error
121- const response = errorOrResponse as { requestId?: string; error?: string } | string;
122- if (typeof response === 'string') {
123+ if (typeof errorOrResponse === 'string') {
123124 // Legacy format: direct error string
124- console.error('[messaging] Legacy error:', response);
125- } else if (response && typeof response === 'object') {
125+ console.error('[messaging] Legacy error:', errorOrResponse);
126+ } else if (errorOrResponse && typeof errorOrResponse === 'object') {
126127 // Response object format
127- const request = pendingRequests.get(response.requestId || 'default');
128+ const request = pendingRequests.get(errorOrResponse.requestId ?? 'default');
128129 if (request) {
129130 clearTimeout(request.timeout);
130- pendingRequests.delete(response.requestId || 'default');
131- request.reject(new Error(response.error || 'Unknown error'));
131+ pendingRequests.delete(errorOrResponse.requestId ?? 'default');
132+ request.reject(new Error(errorOrResponse.error ?? 'Unknown error'));
132133 }
133134 }
134135 };
@@ -188,7 +189,13 @@ async function sendMessage<T = unknown>(
188189 reject(new Error(`Request timeout: ${type}`));
189190 }, REQUEST_TIMEOUT_MS);
190191
191- pendingRequests.set(requestId, { resolve: resolve as any, reject, timeout });
192+ // Cast resolve to accept unknown - type safety is maintained at API level
193+ // since sendMessage<T> returns Promise<T> and callers expect typed responses
194+ pendingRequests.set(requestId, {
195+ resolve: resolve as (value: unknown) => void,
196+ reject,
197+ timeout,
198+ });
192199
193200 try {
194201 bridge.postMessage({
@@ -23,8 +23,12 @@ import { homepageState } from './state.svelte';
23 // REQUEST TRACKING23 // REQUEST TRACKING
24 // ═══════════════════════════════════════════════════════════════════════════════24 // ═══════════════════════════════════════════════════════════════════════════════
25 25
26-interface PendingRequest<T = unknown> {26+// Pending requests store resolvers for different response types.
27- resolve: (value: T) => void;27+// The resolve function accepts `unknown` since requests with different
28+// response types share the same map. Type safety is maintained at the
29+// sendMessage<T> API level.
30+interface PendingRequest {
31+ resolve: (value: unknown) => void;
28 reject: (error: Error) => void;32 reject: (error: Error) => void;
29 timeout: ReturnType<typeof setTimeout>;33 timeout: ReturnType<typeof setTimeout>;
30 }34 }
@@ -38,8 +42,8 @@ let callbacksInitialized = false;
38 // WEBKIT BRIDGE UTILITIES42 // WEBKIT BRIDGE UTILITIES
39 // ═══════════════════════════════════════════════════════════════════════════════43 // ═══════════════════════════════════════════════════════════════════════════════
40 44
41-function getWebKitBridge(): { postMessage: (msg: unknown) => void } | null {45+function getWebKitBridge(): DumberBridge | null {
42- const bridge = (window as any).webkit?.messageHandlers?.dumber;46+ const bridge = window.webkit?.messageHandlers?.dumber;
43 if (bridge && typeof bridge.postMessage === 'function') {47 if (bridge && typeof bridge.postMessage === 'function') {
44 return bridge;48 return bridge;
45 }49 }
@@ -47,7 +51,7 @@ function getWebKitBridge(): { postMessage: (msg: unknown) => void } | null {
47 }51 }
48 52
49 function getWebViewId(): number {53 function getWebViewId(): number {
50- return (window as any).__dumber_webview_id || 0;54+ return window.__dumber_webview_id ?? 0;
51 }55 }
52 56
53 function generateRequestId(type: string): string {57 function generateRequestId(type: string): string {
@@ -62,9 +66,7 @@ function initializeCallbacks(): void {
62 if (callbacksInitialized) return;66 if (callbacksInitialized) return;
63 67
64 // Generic response handler - accepts a single Response object from Go backend68 // Generic response handler - accepts a single Response object from Go backend
65- (window as any).__dumber_homepage_response = (69+ window.__dumber_homepage_response = (response) => {
66- response: { requestId: string; success: boolean; data?: unknown; error?: string }
67- ) => {
68 const request = pendingRequests.get(response.requestId);70 const request = pendingRequests.get(response.requestId);
69 if (!request) return;71 if (!request) return;
70 72
@@ -74,61 +76,60 @@ function initializeCallbacks(): void {
74 if (response.success) {76 if (response.success) {
75 request.resolve(response.data);77 request.resolve(response.data);
76 } else {78 } else {
77- request.reject(new Error(response.error || 'Unknown error'));79+ request.reject(new Error(response.error ?? 'Unknown error'));
78 }80 }
79 };81 };
80 82
81 // Legacy callbacks for backward compatibility83 // Legacy callbacks for backward compatibility
82 // These can receive either raw data or a Response object84 // These can receive either raw data or a Response object
83- (window as any).__dumber_history_timeline = (dataOrResponse: unknown) => {85+ window.__dumber_history_timeline = (dataOrResponse) => {
84 handleLegacyOrResponseCallback(dataOrResponse);86 handleLegacyOrResponseCallback(dataOrResponse);
85 };87 };
86 88
87- (window as any).__dumber_folders = (dataOrResponse: unknown) => {89+ window.__dumber_folders = (dataOrResponse) => {
88 handleLegacyOrResponseCallback(dataOrResponse);90 handleLegacyOrResponseCallback(dataOrResponse);
89 };91 };
90 92
91- (window as any).__dumber_tags = (dataOrResponse: unknown) => {93+ window.__dumber_tags = (dataOrResponse) => {
92 handleLegacyOrResponseCallback(dataOrResponse);94 handleLegacyOrResponseCallback(dataOrResponse);
93 };95 };
94 96
95- (window as any).__dumber_favorites = (dataOrResponse: unknown) => {97+ window.__dumber_favorites = (dataOrResponse) => {
96 handleLegacyOrResponseCallback(dataOrResponse);98 handleLegacyOrResponseCallback(dataOrResponse);
97 };99 };
98 100
99- (window as any).__dumber_analytics = (dataOrResponse: unknown) => {101+ window.__dumber_analytics = (dataOrResponse) => {
100 handleLegacyOrResponseCallback(dataOrResponse);102 handleLegacyOrResponseCallback(dataOrResponse);
101 };103 };
102 104
103- (window as any).__dumber_domain_stats = (dataOrResponse: unknown) => {105+ window.__dumber_domain_stats = (dataOrResponse) => {
104 handleLegacyOrResponseCallback(dataOrResponse);106 handleLegacyOrResponseCallback(dataOrResponse);
105 };107 };
106 108
107- (window as any).__dumber_history_search_results = (dataOrResponse: unknown) => {109+ window.__dumber_history_search_results = (dataOrResponse) => {
108 handleLegacyOrResponseCallback(dataOrResponse);110 handleLegacyOrResponseCallback(dataOrResponse);
109 };111 };
110 112
111- (window as any).__dumber_history_deleted = (dataOrResponse: unknown) => {113+ window.__dumber_history_deleted = (dataOrResponse) => {
112 handleLegacyOrResponseCallback(dataOrResponse);114 handleLegacyOrResponseCallback(dataOrResponse);
113 };115 };
114 116
115- (window as any).__dumber_history_cleared = (dataOrResponse: unknown) => {117+ window.__dumber_history_cleared = (dataOrResponse) => {
116 handleLegacyOrResponseCallback(dataOrResponse);118 handleLegacyOrResponseCallback(dataOrResponse);
117 };119 };
118 120
119- (window as any).__dumber_error = (errorOrResponse: unknown) => {121+ window.__dumber_error = (errorOrResponse) => {
120 // Handle both formats: direct error string or Response object with error122 // Handle both formats: direct error string or Response object with error
121- const response = errorOrResponse as { requestId?: string; error?: string } | string;123+ if (typeof errorOrResponse === 'string') {
122- if (typeof response === 'string') {
123 // Legacy format: direct error string124 // Legacy format: direct error string
124- console.error('[messaging] Legacy error:', response);125+ console.error('[messaging] Legacy error:', errorOrResponse);
125- } else if (response && typeof response === 'object') {126+ } else if (errorOrResponse && typeof errorOrResponse === 'object') {
126 // Response object format127 // Response object format
127- const request = pendingRequests.get(response.requestId || 'default');128+ const request = pendingRequests.get(errorOrResponse.requestId ?? 'default');
128 if (request) {129 if (request) {
129 clearTimeout(request.timeout);130 clearTimeout(request.timeout);
130- pendingRequests.delete(response.requestId || 'default');131+ pendingRequests.delete(errorOrResponse.requestId ?? 'default');
131- request.reject(new Error(response.error || 'Unknown error'));132+ request.reject(new Error(errorOrResponse.error ?? 'Unknown error'));
132 }133 }
133 }134 }
134 };135 };
@@ -188,7 +189,13 @@ async function sendMessage<T = unknown>(
188 reject(new Error(`Request timeout: ${type}`));189 reject(new Error(`Request timeout: ${type}`));
189 }, REQUEST_TIMEOUT_MS);190 }, REQUEST_TIMEOUT_MS);
190 191
191- pendingRequests.set(requestId, { resolve: resolve as any, reject, timeout });192+ // Cast resolve to accept unknown - type safety is maintained at API level
193+ // since sendMessage<T> returns Promise<T> and callers expect typed responses
194+ pendingRequests.set(requestId, {
195+ resolve: resolve as (value: unknown) => void,
196+ reject,
197+ timeout,
198+ });
192 199
193 try {200 try {
194 bridge.postMessage({201 bridge.postMessage({
M webui/src/pages/homepage/types.ts +3 -1
@@ -3,6 +3,8 @@
33 // Mirrors Go models from internal/db/models.go and related query files
44 // ═══════════════════════════════════════════════════════════════════════════════
55
6+import type { Component } from 'svelte';
7+
68 // ─────────────────────────────────────────────────────────────────────────────
79 // History Types
810 // ─────────────────────────────────────────────────────────────────────────────
@@ -110,7 +112,7 @@ export interface CommandPaletteItem {
110112 id: string;
111113 label: string;
112114 description?: string;
113- icon?: import('svelte').Component | string;
115+ icon?: Component | string;
114116 action: () => void;
115117 shortcut?: string;
116118 }
@@ -3,6 +3,8 @@
3 // Mirrors Go models from internal/db/models.go and related query files3 // Mirrors Go models from internal/db/models.go and related query files
4 // ═══════════════════════════════════════════════════════════════════════════════4 // ═══════════════════════════════════════════════════════════════════════════════
5 5
6+import type { Component } from 'svelte';
7+
6 // ─────────────────────────────────────────────────────────────────────────────8 // ─────────────────────────────────────────────────────────────────────────────
7 // History Types9 // History Types
8 // ─────────────────────────────────────────────────────────────────────────────10 // ─────────────────────────────────────────────────────────────────────────────
@@ -110,7 +112,7 @@ export interface CommandPaletteItem {
110 id: string;112 id: string;
111 label: string;113 label: string;
112 description?: string;114 description?: string;
113- icon?: import('svelte').Component | string;115+ icon?: Component | string;
114 action: () => void;116 action: () => void;
115 shortcut?: string;117 shortcut?: string;
116 }118 }
A webui/src/types/dumber.d.ts +150 -0
@@ -0,0 +1,150 @@
1+/**
2+ * Global type declarations for Dumber browser WebKit bridge
3+ */
4+
5+// ═══════════════════════════════════════════════════════════════════════════════
6+// WEBKIT BRIDGE TYPES
7+// ═══════════════════════════════════════════════════════════════════════════════
8+
9+interface DumberBridge {
10+ postMessage: (message: unknown) => void;
11+}
12+
13+interface DumberWebKit {
14+ messageHandlers?: {
15+ dumber?: DumberBridge;
16+ };
17+}
18+
19+// ═══════════════════════════════════════════════════════════════════════════════
20+// VIEW TRANSITION API (not yet in TypeScript DOM lib)
21+// ═══════════════════════════════════════════════════════════════════════════════
22+
23+interface ViewTransition {
24+ finished: Promise<void>;
25+ ready: Promise<void>;
26+ updateCallbackDone: Promise<void>;
27+}
28+
29+// ═══════════════════════════════════════════════════════════════════════════════
30+// THEME TYPES
31+// ═══════════════════════════════════════════════════════════════════════════════
32+
33+type ThemeMode = 'light' | 'dark';
34+
35+interface ColorSchemeManager {
36+ setUserPreference?: (theme: ThemeMode) => void;
37+}
38+
39+// ═══════════════════════════════════════════════════════════════════════════════
40+// HOMEPAGE MESSAGING TYPES
41+// ═══════════════════════════════════════════════════════════════════════════════
42+
43+/** Response from Go backend for homepage requests */
44+interface HomepageResponse {
45+ requestId: string;
46+ success: boolean;
47+ data?: unknown;
48+ error?: string;
49+}
50+
51+/** Error response format (can be string for legacy or object) */
52+type HomepageErrorResponse = string | { requestId?: string; error?: string };
53+
54+/** Callback type for homepage responses */
55+type HomepageResponseCallback = (response: HomepageResponse) => void;
56+
57+/** Callback type for legacy data responses (can receive raw data or Response object) */
58+type HomepageLegacyCallback = (dataOrResponse: unknown) => void;
59+
60+/** Callback type for error responses */
61+type HomepageErrorCallback = (errorOrResponse: HomepageErrorResponse) => void;
62+
63+// ═══════════════════════════════════════════════════════════════════════════════
64+// GLOBAL DECLARATIONS
65+// ═══════════════════════════════════════════════════════════════════════════════
66+
67+declare global {
68+ // Re-export bridge type for use in other modules
69+ type DumberBridge = {
70+ postMessage: (message: unknown) => void;
71+ };
72+ interface Window {
73+ // ─────────────────────────────────────────────────────────────────────────
74+ // Core WebKit Bridge
75+ // ─────────────────────────────────────────────────────────────────────────
76+ /** WebView ID assigned by the browser backend */
77+ __dumber_webview_id?: number;
78+ /** WebKit message handlers for native bridge */
79+ webkit?: DumberWebKit;
80+
81+ // ─────────────────────────────────────────────────────────────────────────
82+ // Config Page Callbacks
83+ // ─────────────────────────────────────────────────────────────────────────
84+ /** Callback invoked when config save succeeds */
85+ __dumber_config_saved?: (resp?: unknown) => void;
86+ /** Callback invoked when config save fails */
87+ __dumber_config_error?: (msg: unknown) => void;
88+
89+ // ─────────────────────────────────────────────────────────────────────────
90+ // Keybindings Callbacks
91+ // ─────────────────────────────────────────────────────────────────────────
92+ /** Callback invoked when keybindings are loaded */
93+ __dumber_keybindings_loaded?: (resp?: unknown) => void;
94+ /** Callback invoked when keybindings load fails */
95+ __dumber_keybindings_error?: (msg: unknown) => void;
96+ /** Callback invoked when a keybinding is set */
97+ __dumber_keybinding_set?: (resp?: unknown) => void;
98+ /** Callback invoked when keybinding set fails */
99+ __dumber_keybinding_set_error?: (msg: unknown) => void;
100+ /** Callback invoked when a keybinding is reset */
101+ __dumber_keybinding_reset?: (resp?: unknown) => void;
102+ /** Callback invoked when keybinding reset fails */
103+ __dumber_keybinding_reset_error?: (msg: unknown) => void;
104+ /** Callback invoked when all keybindings are reset */
105+ __dumber_keybindings_reset_all?: (resp?: unknown) => void;
106+ /** Callback invoked when reset all keybindings fails */
107+ __dumber_keybindings_reset_all_error?: (msg: unknown) => void;
108+
109+ // ─────────────────────────────────────────────────────────────────────────
110+ // Homepage Callbacks
111+ // ─────────────────────────────────────────────────────────────────────────
112+ /** Generic response handler for homepage requests */
113+ __dumber_homepage_response?: HomepageResponseCallback;
114+ /** History timeline data callback */
115+ __dumber_history_timeline?: HomepageLegacyCallback;
116+ /** Folders data callback */
117+ __dumber_folders?: HomepageLegacyCallback;
118+ /** Tags data callback */
119+ __dumber_tags?: HomepageLegacyCallback;
120+ /** Favorites data callback */
121+ __dumber_favorites?: HomepageLegacyCallback;
122+ /** Analytics data callback */
123+ __dumber_analytics?: HomepageLegacyCallback;
124+ /** Domain stats data callback */
125+ __dumber_domain_stats?: HomepageLegacyCallback;
126+ /** History search results callback */
127+ __dumber_history_search_results?: HomepageLegacyCallback;
128+ /** History deleted callback */
129+ __dumber_history_deleted?: HomepageLegacyCallback;
130+ /** History cleared callback */
131+ __dumber_history_cleared?: HomepageLegacyCallback;
132+ /** Error callback for homepage requests */
133+ __dumber_error?: HomepageErrorCallback;
134+
135+ // ─────────────────────────────────────────────────────────────────────────
136+ // Theme Management
137+ // ─────────────────────────────────────────────────────────────────────────
138+ /** Color scheme manager for theme switching */
139+ __dumber_color_scheme_manager?: ColorSchemeManager;
140+ /** Legacy theme setter */
141+ __dumber_setTheme?: (theme: ThemeMode) => void;
142+ }
143+
144+ interface Document {
145+ /** View Transition API */
146+ startViewTransition?: (callback: () => void | Promise<void>) => ViewTransition;
147+ }
148+}
149+
150+export {};
@@ -0,0 +1,150 @@
1+/**
2+ * Global type declarations for Dumber browser WebKit bridge
3+ */
4+
5+// ═══════════════════════════════════════════════════════════════════════════════
6+// WEBKIT BRIDGE TYPES
7+// ═══════════════════════════════════════════════════════════════════════════════
8+
9+interface DumberBridge {
10+ postMessage: (message: unknown) => void;
11+}
12+
13+interface DumberWebKit {
14+ messageHandlers?: {
15+ dumber?: DumberBridge;
16+ };
17+}
18+
19+// ═══════════════════════════════════════════════════════════════════════════════
20+// VIEW TRANSITION API (not yet in TypeScript DOM lib)
21+// ═══════════════════════════════════════════════════════════════════════════════
22+
23+interface ViewTransition {
24+ finished: Promise<void>;
25+ ready: Promise<void>;
26+ updateCallbackDone: Promise<void>;
27+}
28+
29+// ═══════════════════════════════════════════════════════════════════════════════
30+// THEME TYPES
31+// ═══════════════════════════════════════════════════════════════════════════════
32+
33+type ThemeMode = 'light' | 'dark';
34+
35+interface ColorSchemeManager {
36+ setUserPreference?: (theme: ThemeMode) => void;
37+}
38+
39+// ═══════════════════════════════════════════════════════════════════════════════
40+// HOMEPAGE MESSAGING TYPES
41+// ═══════════════════════════════════════════════════════════════════════════════
42+
43+/** Response from Go backend for homepage requests */
44+interface HomepageResponse {
45+ requestId: string;
46+ success: boolean;
47+ data?: unknown;
48+ error?: string;
49+}
50+
51+/** Error response format (can be string for legacy or object) */
52+type HomepageErrorResponse = string | { requestId?: string; error?: string };
53+
54+/** Callback type for homepage responses */
55+type HomepageResponseCallback = (response: HomepageResponse) => void;
56+
57+/** Callback type for legacy data responses (can receive raw data or Response object) */
58+type HomepageLegacyCallback = (dataOrResponse: unknown) => void;
59+
60+/** Callback type for error responses */
61+type HomepageErrorCallback = (errorOrResponse: HomepageErrorResponse) => void;
62+
63+// ═══════════════════════════════════════════════════════════════════════════════
64+// GLOBAL DECLARATIONS
65+// ═══════════════════════════════════════════════════════════════════════════════
66+
67+declare global {
68+ // Re-export bridge type for use in other modules
69+ type DumberBridge = {
70+ postMessage: (message: unknown) => void;
71+ };
72+ interface Window {
73+ // ─────────────────────────────────────────────────────────────────────────
74+ // Core WebKit Bridge
75+ // ─────────────────────────────────────────────────────────────────────────
76+ /** WebView ID assigned by the browser backend */
77+ __dumber_webview_id?: number;
78+ /** WebKit message handlers for native bridge */
79+ webkit?: DumberWebKit;
80+
81+ // ─────────────────────────────────────────────────────────────────────────
82+ // Config Page Callbacks
83+ // ─────────────────────────────────────────────────────────────────────────
84+ /** Callback invoked when config save succeeds */
85+ __dumber_config_saved?: (resp?: unknown) => void;
86+ /** Callback invoked when config save fails */
87+ __dumber_config_error?: (msg: unknown) => void;
88+
89+ // ─────────────────────────────────────────────────────────────────────────
90+ // Keybindings Callbacks
91+ // ─────────────────────────────────────────────────────────────────────────
92+ /** Callback invoked when keybindings are loaded */
93+ __dumber_keybindings_loaded?: (resp?: unknown) => void;
94+ /** Callback invoked when keybindings load fails */
95+ __dumber_keybindings_error?: (msg: unknown) => void;
96+ /** Callback invoked when a keybinding is set */
97+ __dumber_keybinding_set?: (resp?: unknown) => void;
98+ /** Callback invoked when keybinding set fails */
99+ __dumber_keybinding_set_error?: (msg: unknown) => void;
100+ /** Callback invoked when a keybinding is reset */
101+ __dumber_keybinding_reset?: (resp?: unknown) => void;
102+ /** Callback invoked when keybinding reset fails */
103+ __dumber_keybinding_reset_error?: (msg: unknown) => void;
104+ /** Callback invoked when all keybindings are reset */
105+ __dumber_keybindings_reset_all?: (resp?: unknown) => void;
106+ /** Callback invoked when reset all keybindings fails */
107+ __dumber_keybindings_reset_all_error?: (msg: unknown) => void;
108+
109+ // ─────────────────────────────────────────────────────────────────────────
110+ // Homepage Callbacks
111+ // ─────────────────────────────────────────────────────────────────────────
112+ /** Generic response handler for homepage requests */
113+ __dumber_homepage_response?: HomepageResponseCallback;
114+ /** History timeline data callback */
115+ __dumber_history_timeline?: HomepageLegacyCallback;
116+ /** Folders data callback */
117+ __dumber_folders?: HomepageLegacyCallback;
118+ /** Tags data callback */
119+ __dumber_tags?: HomepageLegacyCallback;
120+ /** Favorites data callback */
121+ __dumber_favorites?: HomepageLegacyCallback;
122+ /** Analytics data callback */
123+ __dumber_analytics?: HomepageLegacyCallback;
124+ /** Domain stats data callback */
125+ __dumber_domain_stats?: HomepageLegacyCallback;
126+ /** History search results callback */
127+ __dumber_history_search_results?: HomepageLegacyCallback;
128+ /** History deleted callback */
129+ __dumber_history_deleted?: HomepageLegacyCallback;
130+ /** History cleared callback */
131+ __dumber_history_cleared?: HomepageLegacyCallback;
132+ /** Error callback for homepage requests */
133+ __dumber_error?: HomepageErrorCallback;
134+
135+ // ─────────────────────────────────────────────────────────────────────────
136+ // Theme Management
137+ // ─────────────────────────────────────────────────────────────────────────
138+ /** Color scheme manager for theme switching */
139+ __dumber_color_scheme_manager?: ColorSchemeManager;
140+ /** Legacy theme setter */
141+ __dumber_setTheme?: (theme: ThemeMode) => void;
142+ }
143+
144+ interface Document {
145+ /** View Transition API */
146+ startViewTransition?: (callback: () => void | Promise<void>) => ViewTransition;
147+ }
148+}
149+
150+export {};

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close