1
0

2 Sitoutukset f95bd1a15a ... 46b2d18be3

Tekijä SHA1 Viesti Päivämäärä
  lv 46b2d18be3 uis 2 viikkoa sitten
  lv 19e53a0269 ui 2 viikkoa sitten

+ 26 - 0
pnpm-lock.yaml

@@ -668,66 +668,79 @@ packages:
     resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.60.3':
     resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.60.3':
     resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.60.3':
     resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-loong64-gnu@4.60.3':
     resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==}
     cpu: [loong64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-loong64-musl@4.60.3':
     resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==}
     cpu: [loong64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-ppc64-gnu@4.60.3':
     resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-ppc64-musl@4.60.3':
     resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==}
     cpu: [ppc64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-riscv64-gnu@4.60.3':
     resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-musl@4.60.3':
     resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==}
     cpu: [riscv64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-s390x-gnu@4.60.3':
     resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.60.3':
     resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.60.3':
     resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-openbsd-x64@4.60.3':
     resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==}
@@ -797,24 +810,28 @@ packages:
     engines: {node: '>= 20'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@tailwindcss/oxide-linux-arm64-musl@4.3.0':
     resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==}
     engines: {node: '>= 20'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@tailwindcss/oxide-linux-x64-gnu@4.3.0':
     resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==}
     engines: {node: '>= 20'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@tailwindcss/oxide-linux-x64-musl@4.3.0':
     resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==}
     engines: {node: '>= 20'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@tailwindcss/oxide-wasm32-wasi@4.3.0':
     resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==}
@@ -875,30 +892,35 @@ packages:
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@tauri-apps/cli-linux-arm64-musl@2.11.1':
     resolution: {integrity: sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@tauri-apps/cli-linux-riscv64-gnu@2.11.1':
     resolution: {integrity: sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw==}
     engines: {node: '>= 10'}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@tauri-apps/cli-linux-x64-gnu@2.11.1':
     resolution: {integrity: sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@tauri-apps/cli-linux-x64-musl@2.11.1':
     resolution: {integrity: sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@tauri-apps/cli-win32-arm64-msvc@2.11.1':
     resolution: {integrity: sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ==}
@@ -1098,24 +1120,28 @@ packages:
     engines: {node: '>= 12.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   lightningcss-linux-arm64-musl@1.32.0:
     resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
     engines: {node: '>= 12.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   lightningcss-linux-x64-gnu@1.32.0:
     resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
     engines: {node: '>= 12.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   lightningcss-linux-x64-musl@1.32.0:
     resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
     engines: {node: '>= 12.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   lightningcss-win32-arm64-msvc@1.32.0:
     resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}

+ 2 - 0
pnpm-workspace.yaml

@@ -0,0 +1,2 @@
+allowBuilds:
+  esbuild: set this to true or false

+ 53 - 135
src-tauri/Cargo.lock

@@ -222,8 +222,9 @@ dependencies = [
  "tokio",
  "url",
  "uuid",
- "webview2-com 0.34.0",
- "windows 0.58.0",
+ "webview2-com",
+ "widestring",
+ "windows",
 ]
 
 [[package]]
@@ -3335,7 +3336,7 @@ dependencies = [
  "tao-macros",
  "unicode-segmentation",
  "url",
- "windows 0.61.3",
+ "windows",
  "windows-core 0.61.2",
  "windows-version",
  "x11-dl",
@@ -3360,9 +3361,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
 
 [[package]]
 name = "tauri"
-version = "2.11.1"
+version = "2.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b93bd86d231f0a8138f11a02a584769fe4b703dc36ae133d783228dbc4801405"
+checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28"
 dependencies = [
  "anyhow",
  "bytes",
@@ -3405,16 +3406,16 @@ dependencies = [
  "tray-icon",
  "url",
  "webkit2gtk",
- "webview2-com 0.38.2",
+ "webview2-com",
  "window-vibrancy",
- "windows 0.61.3",
+ "windows",
 ]
 
 [[package]]
 name = "tauri-build"
-version = "2.6.1"
+version = "2.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a318b234cc2dea65f575467bafcfb76286bce228ebc3778e337d61d03213007"
+checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7"
 dependencies = [
  "anyhow",
  "cargo_toml",
@@ -3433,9 +3434,9 @@ dependencies = [
 
 [[package]]
 name = "tauri-codegen"
-version = "2.6.1"
+version = "2.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bd11644962add2549a60b7e7c6800f17d7020156e02f516021d8103e80cc528"
+checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9"
 dependencies = [
  "base64 0.22.1",
  "brotli",
@@ -3460,9 +3461,9 @@ dependencies = [
 
 [[package]]
 name = "tauri-macros"
-version = "2.6.1"
+version = "2.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed9d3742a37a355d2e47c9af924e9fbc112abb76f9835d35d4780e318419502"
+checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924"
 dependencies = [
  "heck 0.5.0",
  "proc-macro2",
@@ -3474,9 +3475,9 @@ dependencies = [
 
 [[package]]
 name = "tauri-plugin"
-version = "2.6.1"
+version = "2.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eefb2c18e8a605c23edb48fc56bb77381199e1a1e7f6ff0c9b970afe7b3cb8ee"
+checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e"
 dependencies = [
  "anyhow",
  "glob",
@@ -3521,15 +3522,15 @@ dependencies = [
  "tauri-plugin",
  "thiserror 2.0.18",
  "url",
- "windows 0.61.3",
+ "windows",
  "zbus",
 ]
 
 [[package]]
 name = "tauri-runtime"
-version = "2.11.1"
+version = "2.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fef478ba1d2ac21c2d528740b24d0cb315e1e8b1111aae53fafac34804371fc"
+checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef"
 dependencies = [
  "cookie",
  "dpi",
@@ -3546,15 +3547,15 @@ dependencies = [
  "thiserror 2.0.18",
  "url",
  "webkit2gtk",
- "webview2-com 0.38.2",
- "windows 0.61.3",
+ "webview2-com",
+ "windows",
 ]
 
 [[package]]
 name = "tauri-runtime-wry"
-version = "2.11.1"
+version = "2.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3989df2ae1c476404fe0a2e8ffc4cfbde97e51efd613c2bb5355fbc9ab52cf0"
+checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
 dependencies = [
  "gtk",
  "http",
@@ -3571,16 +3572,16 @@ dependencies = [
  "tauri-utils",
  "url",
  "webkit2gtk",
- "webview2-com 0.38.2",
- "windows 0.61.3",
+ "webview2-com",
+ "windows",
  "wry",
 ]
 
 [[package]]
 name = "tauri-utils"
-version = "2.9.1"
+version = "2.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d57200389a2f82b4b0a40ae29ca19b6978116e8f4d4e974c3234ce40c0ffbdec"
+checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95"
 dependencies = [
  "anyhow",
  "brotli",
@@ -3811,7 +3812,7 @@ dependencies = [
  "toml_datetime 1.1.1+spec-1.1.0",
  "toml_parser",
  "toml_writer",
- "winnow 1.0.2",
+ "winnow 1.0.3",
 ]
 
 [[package]]
@@ -3874,7 +3875,7 @@ dependencies = [
  "indexmap 2.14.0",
  "toml_datetime 1.1.1+spec-1.1.0",
  "toml_parser",
- "winnow 1.0.2",
+ "winnow 1.0.3",
 ]
 
 [[package]]
@@ -3883,7 +3884,7 @@ version = "1.1.2+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
 dependencies = [
- "winnow 1.0.2",
+ "winnow 1.0.3",
 ]
 
 [[package]]
@@ -4370,20 +4371,6 @@ dependencies = [
  "system-deps",
 ]
 
-[[package]]
-name = "webview2-com"
-version = "0.34.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "823e7ebcfaea51e78f72c87fc3b65a1e602c321f407a0b36dbb327d7bb7cd921"
-dependencies = [
- "webview2-com-macros",
- "webview2-com-sys 0.34.0",
- "windows 0.58.0",
- "windows-core 0.58.0",
- "windows-implement 0.58.0",
- "windows-interface 0.58.0",
-]
-
 [[package]]
 name = "webview2-com"
 version = "0.38.2"
@@ -4391,11 +4378,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
 dependencies = [
  "webview2-com-macros",
- "webview2-com-sys 0.38.2",
- "windows 0.61.3",
+ "webview2-com-sys",
+ "windows",
  "windows-core 0.61.2",
- "windows-implement 0.60.2",
- "windows-interface 0.59.3",
+ "windows-implement",
+ "windows-interface",
 ]
 
 [[package]]
@@ -4409,17 +4396,6 @@ dependencies = [
  "syn 2.0.117",
 ]
 
-[[package]]
-name = "webview2-com-sys"
-version = "0.34.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a82bce72db6e5ee83c68b5de1e2cd6ea195b9fbff91cb37df5884cbe3222df4"
-dependencies = [
- "thiserror 1.0.69",
- "windows 0.58.0",
- "windows-core 0.58.0",
-]
-
 [[package]]
 name = "webview2-com-sys"
 version = "0.38.2"
@@ -4427,10 +4403,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
 dependencies = [
  "thiserror 2.0.18",
- "windows 0.61.3",
+ "windows",
  "windows-core 0.61.2",
 ]
 
+[[package]]
+name = "widestring"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -4477,16 +4459,6 @@ dependencies = [
  "windows-version",
 ]
 
-[[package]]
-name = "windows"
-version = "0.58.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
-dependencies = [
- "windows-core 0.58.0",
- "windows-targets 0.52.6",
-]
-
 [[package]]
 name = "windows"
 version = "0.61.3"
@@ -4509,27 +4481,14 @@ dependencies = [
  "windows-core 0.61.2",
 ]
 
-[[package]]
-name = "windows-core"
-version = "0.58.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
-dependencies = [
- "windows-implement 0.58.0",
- "windows-interface 0.58.0",
- "windows-result 0.2.0",
- "windows-strings 0.1.0",
- "windows-targets 0.52.6",
-]
-
 [[package]]
 name = "windows-core"
 version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
 dependencies = [
- "windows-implement 0.60.2",
- "windows-interface 0.59.3",
+ "windows-implement",
+ "windows-interface",
  "windows-link 0.1.3",
  "windows-result 0.3.4",
  "windows-strings 0.4.2",
@@ -4541,8 +4500,8 @@ version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
 dependencies = [
- "windows-implement 0.60.2",
- "windows-interface 0.59.3",
+ "windows-implement",
+ "windows-interface",
  "windows-link 0.2.1",
  "windows-result 0.4.1",
  "windows-strings 0.5.1",
@@ -4559,17 +4518,6 @@ dependencies = [
  "windows-threading",
 ]
 
-[[package]]
-name = "windows-implement"
-version = "0.58.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
 [[package]]
 name = "windows-implement"
 version = "0.60.2"
@@ -4581,17 +4529,6 @@ dependencies = [
  "syn 2.0.117",
 ]
 
-[[package]]
-name = "windows-interface"
-version = "0.58.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
 [[package]]
 name = "windows-interface"
 version = "0.59.3"
@@ -4625,15 +4562,6 @@ dependencies = [
  "windows-link 0.1.3",
 ]
 
-[[package]]
-name = "windows-result"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
 [[package]]
 name = "windows-result"
 version = "0.3.4"
@@ -4652,16 +4580,6 @@ dependencies = [
  "windows-link 0.2.1",
 ]
 
-[[package]]
-name = "windows-strings"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
-dependencies = [
- "windows-result 0.2.0",
- "windows-targets 0.52.6",
-]
-
 [[package]]
 name = "windows-strings"
 version = "0.4.2"
@@ -4863,9 +4781,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
 
 [[package]]
 name = "winnow"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
 dependencies = [
  "memchr",
 ]
@@ -5017,8 +4935,8 @@ dependencies = [
  "url",
  "webkit2gtk",
  "webkit2gtk-sys",
- "webview2-com 0.38.2",
- "windows 0.61.3",
+ "webview2-com",
+ "windows",
  "windows-core 0.61.2",
  "windows-version",
  "x11-dl",
@@ -5120,7 +5038,7 @@ dependencies = [
  "uds_windows",
  "uuid",
  "windows-sys 0.61.2",
- "winnow 1.0.2",
+ "winnow 1.0.3",
  "zbus_macros",
  "zbus_names",
  "zvariant",
@@ -5148,7 +5066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
 dependencies = [
  "serde",
- "winnow 1.0.2",
+ "winnow 1.0.3",
  "zvariant",
 ]
 
@@ -5221,7 +5139,7 @@ dependencies = [
  "endi",
  "enumflags2",
  "serde",
- "winnow 1.0.2",
+ "winnow 1.0.3",
  "zvariant_derive",
  "zvariant_utils",
 ]
@@ -5249,5 +5167,5 @@ dependencies = [
  "quote",
  "serde",
  "syn 2.0.117",
- "winnow 1.0.2",
+ "winnow 1.0.3",
 ]

+ 3 - 2
src-tauri/Cargo.toml

@@ -36,6 +36,7 @@ tokio = { version = "1", features = ["time", "sync", "process", "io-util", "rt-m
 anyhow = "1"
 # 录制会话标识;不直接落盘的 raw 文件用 uuid 命名避免并发冲突
 uuid = { version = "1", features = ["v4"] }
+widestring = "1.2.1"
 
 # ------------------ Windows 平台专属 ------------------
 # 用 webview2-com 直接拿 ICoreWebView2 调 CallDevToolsProtocolMethod 跑 CDP(整页截图)。
@@ -43,8 +44,8 @@ uuid = { version = "1", features = ["v4"] }
 # 若 cargo check 报 ICoreWebView2 类型冲突,运行 `cargo tree -p webview2-com`
 # 查看实际版本并对齐这里。
 [target.'cfg(windows)'.dependencies]
-webview2-com = "0.34"
-windows = { version = "0.58", features = [
+webview2-com = "0.38"
+windows = { version = "0.61", features = [
     "Win32_Foundation",
     "Win32_System_Com",
 ] }

+ 14 - 3
src-tauri/capabilities/default.json

@@ -2,10 +2,21 @@
   "$schema": "../gen/schemas/desktop-schema.json",
   "identifier": "default",
   "description": "Capability for the main window",
-  "windows": ["main"],
+  "windows": [
+    "main"
+  ],
   "permissions": [
     "core:default",
     "opener:default",
-    "opener:allow-reveal-item-in-dir"
+    "opener:allow-reveal-item-in-dir",
+    "core:window:allow-close",
+    "core:window:allow-center",
+    "core:window:allow-minimize",
+    "core:window:allow-maximize",
+    "core:window:allow-set-size",
+    "core:window:allow-set-focus",
+    "core:window:allow-is-maximized",
+    "core:window:allow-start-dragging",
+    "core:window:allow-toggle-maximize"
   ]
-}
+}

+ 8 - 21
src-tauri/src/capture.rs

@@ -67,11 +67,7 @@ pub async fn trigger_auto_capture(app: AppHandle) {
 ///
 /// 成功返回保存路径(用于命令的 invoke 返回值);失败时同时 emit screenshot-failed
 /// 并返回 Err,便于前端通过两种路径感知失败。
-async fn perform_capture(
-    app: AppHandle,
-    task_id: String,
-    auto: bool,
-) -> Result<String, String> {
+async fn perform_capture(app: AppHandle, task_id: String, auto: bool) -> Result<String, String> {
     let target_path = paths::screenshot_path_for_task(&app, &task_id)?;
 
     let result = capture_to_png_bytes(&app).await;
@@ -148,10 +144,7 @@ mod windows_impl {
     use std::sync::{Arc, Mutex};
     use tauri::Webview;
     use tokio::sync::oneshot;
-    use webview2_com::{
-        CallDevToolsProtocolMethodCompletedHandler,
-        Microsoft::Web::WebView2::Win32::ICoreWebView2,
-    };
+    use webview2_com::CallDevToolsProtocolMethodCompletedHandler;
     use windows::core::HSTRING;
 
     /// 预热脚本:等懒加载内容、字体、图片加载完成;最大 8s 兜底超时。
@@ -254,9 +247,7 @@ mod windows_impl {
             .and_then(|v| v.as_str())
             .ok_or_else(|| anyhow!("CDP 响应缺少 data 字段: {shot_resp}"))?;
 
-        let bytes = STANDARD
-            .decode(b64)
-            .context("CDP 返回的 base64 解码失败")?;
+        let bytes = STANDARD.decode(b64).context("CDP 返回的 base64 解码失败")?;
         if bytes.is_empty() {
             bail!("CDP 截图返回了空数据");
         }
@@ -281,19 +272,18 @@ mod windows_impl {
                 // platform 在 Windows 上是 wry 的 webview2 包装,controller() 给 ICoreWebView2Controller
                 let result: Result<()> = (|| {
                     let controller = platform.controller();
-                    let core: ICoreWebView2 = unsafe { controller.CoreWebView2()? };
+                    let core = unsafe { controller.CoreWebView2()? };
 
                     let method_h: HSTRING = method_for_native.clone().into();
                     let params_h: HSTRING = params_str.clone().into();
-
                     let tx_handler = tx_for_native.clone();
                     let handler = CallDevToolsProtocolMethodCompletedHandler::create(Box::new(
                         move |hr, json_pcwstr| {
-                            let json_str = unsafe { json_pcwstr.to_string().unwrap_or_default() };
+                            let json_str = json_pcwstr.to_string();
                             let res = if hr.is_ok() {
                                 Ok(json_str)
                             } else {
-                                Err(format!("CDP HRESULT 错误: 0x{:08x}", hr.0))
+                                Err(format!("CDP HRESULT 错误: {}", hr.err().unwrap()))
                             };
                             if let Ok(mut guard) = tx_handler.lock() {
                                 if let Some(sender) = guard.take() {
@@ -321,11 +311,8 @@ mod windows_impl {
             })
             .map_err(|e| anyhow!("with_webview 调用失败: {e}"))?;
 
-        let raw = rx
-            .await
-            .map_err(|_| anyhow!("CDP oneshot 通道关闭"))??;
+        let raw = rx.await?.map_err(|_| anyhow!("CDP oneshot 通道关闭"))?;
 
-        serde_json::from_str(&raw)
-            .map_err(|e| anyhow!("CDP 响应不是合法 JSON: {e}; raw={raw}"))
+        serde_json::from_str(&raw).map_err(|e| anyhow!("CDP 响应不是合法 JSON: {e}; raw={raw}"))
     }
 }

+ 16 - 21
src-tauri/src/lib.rs

@@ -16,11 +16,9 @@ use recording::RecordingSession;
 /// 子 webview 的 label
 pub(crate) const CONTENT_WEBVIEW_LABEL: &str = "content";
 
-const WIN_FRAME: (f64, f64, f64, f64) = (0.0, 34.0, 0.0, 40.0);
-
 /// 子 webview 初始尺寸:1280×720 横屏
-const DEFAULT_CONTENT_W: f64 = 1280.0;
-const DEFAULT_CONTENT_H: f64 = 720.0;
+const DEFAULT_CONTENT_W: f64 = 1280.0f64;
+const DEFAULT_CONTENT_H: f64 = 720.0f64;
 
 /// 子 webview 初始 url(空白页占位,等用户从任务列表选)
 const INITIAL_URL: &str = "about:blank";
@@ -124,30 +122,26 @@ fn set_window_size(
     app: tauri::AppHandle,
     width: f64,
     height: f64,
+    content_x: f64,
+    content_y: f64,
     content_width: f64,
     content_height: f64,
 ) -> Result<(), String> {
     println!(
-        "===========:{},{},{},{}",
-        width, height, content_width, content_height
+        "===========:w{},h{},cx{},cy{},cw{},ch{}",
+        width, height, content_x, content_y, content_width, content_height
     );
     // 这里取底层 `Window`(而不是 `WebviewWindow`),与 setup 阶段保持一致
     let window = app
         .get_window("main")
         .ok_or_else(|| "找不到主窗口".to_string())?;
     window
-        .set_size(LogicalSize::new(
-            width + WIN_FRAME.0 + WIN_FRAME.2,
-            height + WIN_FRAME.1 + WIN_FRAME.3,
-        ))
+        .set_size(LogicalSize::new(width, height))
         .map_err(|e| e.to_string())?;
 
     let content = find_content_webview(&app)?;
     content
-        .set_position(LogicalPosition::new(
-            WIN_FRAME.0 + width - content_width,
-            WIN_FRAME.1 + height - content_height,
-        ))
+        .set_position(LogicalPosition::new(content_x, content_y))
         .map_err(|e| e.to_string())?;
     content
         .set_size(LogicalSize::new(content_width, content_height))
@@ -170,7 +164,6 @@ pub fn run() {
 
             // 在主窗口(也是 React UI 所在的 webview)所属的 Window 上挂一个 child webview
             // 注意:`add_child` 定义在 `Window` 上,不在 `WebviewWindow` 上,所以这里取 Window
-            let window = app.get_window("main").ok_or("主窗口未找到")?;
 
             // 1) 先把窗口「客户区」调到目标值 = 左栏 + 工作区宽 × 工具栏高 + 工作区高
             //    必须放在 add_child 之前:否则 child webview 是在还很小的客户区里被创建,
@@ -185,6 +178,8 @@ pub fn run() {
             //    - capture 内部会再做一次「CDP 撑大视口 + DOM 稳定」预热,详见 capture.rs
             let initial_url: url::Url = INITIAL_URL.parse()?;
             let app_handle_for_load = app.handle().clone();
+            let window = app.get_window("main").ok_or("主窗口未找到")?;
+
             let content = window.add_child(
                 WebviewBuilder::new(CONTENT_WEBVIEW_LABEL, WebviewUrl::External(initial_url))
                     .on_page_load(move |_webview, payload| {
@@ -199,14 +194,14 @@ pub fn run() {
                         });
                     }),
                 LogicalPosition::new(0, 0),
-                LogicalSize::new(DEFAULT_CONTENT_W, DEFAULT_CONTENT_H),
+                LogicalSize::new(
+                    DEFAULT_CONTENT_W + 180f64 + 4f64,
+                    DEFAULT_CONTENT_H + 32f64 + 24f64 + 4f64,
+                ),
             )?;
 
-            content.set_position(LogicalPosition::new(WIN_FRAME.0 + 180f64, WIN_FRAME.1))?;
-            content.set_size(LogicalSize::new(
-                DEFAULT_CONTENT_W + WIN_FRAME.0,
-                DEFAULT_CONTENT_W + WIN_FRAME.1,
-            ))?;
+            content.set_position(LogicalPosition::new(182f64, 34.0f64))?;
+            content.set_size(LogicalSize::new(DEFAULT_CONTENT_W, DEFAULT_CONTENT_W))?;
 
             Ok(())
         })

+ 8 - 3
src-tauri/tauri.conf.json

@@ -13,8 +13,13 @@
     "windows": [
       {
         "title": "auto-record",
-        "width": 1460,
-        "height": 768
+        "width": 1464,
+        "height": 780,
+        "decorations": false,
+        "maximizable": false,
+        "minimizable": true,
+        "closable": true,
+        "resizable": false
       }
     ],
     "withGlobalTauri": true,
@@ -39,4 +44,4 @@
       "icons/icon.ico"
     ]
   }
-}
+}

+ 5 - 3
src/App.css

@@ -19,7 +19,8 @@
   --color-primary-3: #91caff;
   --color-primary-4: #69b1ff;
   --color-primary-5: #4096ff;
-  --color-primary-6: #1677ff; /* colorPrimary */
+  --color-primary-6: #1677ff;
+  /* colorPrimary */
   --color-primary-7: #0958d9;
   --color-primary-8: #003eb3;
   --color-primary-9: #002c8c;
@@ -75,7 +76,8 @@
   /* ---- 圆角:对齐 antd borderRadius 四档 ---- */
   --radius-xs: 2px;
   --radius-sm: 4px;
-  --radius-md: 6px; /* antd 基础 borderRadius */
+  --radius-md: 6px;
+  /* antd 基础 borderRadius */
   --radius-lg: 8px;
   --radius: 6px;
 
@@ -113,4 +115,4 @@ body,
 #root {
   height: 100%;
   margin: 0;
-}
+}

+ 249 - 220
src/App.tsx

@@ -2,8 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
 import { invoke } from "@tauri-apps/api/core";
 import { listen, type UnlistenFn } from "@tauri-apps/api/event";
 import { openUrl, revealItemInDir } from "@tauri-apps/plugin-opener";
-import { Button, Divider, InputNumber, notification, Tooltip } from "antd";
-import { mockTasks, type Task } from "./mocks/tasks";
+import { Button, ConfigProvider, Divider, InputNumber, Layout, Menu, notification, Space, theme, Tooltip } from "antd";
+import { mockTasks } from "./mocks/tasks";
 import {
   EVT_RECORDING_FAILED,
   EVT_RECORDING_FINISHED,
@@ -28,15 +28,21 @@ import {
 } from "./lib/recorder";
 import "./App.css";
 import {
+  BankOutlined,
+  CloseOutlined,
   FileImageOutlined,
   FolderOpenOutlined,
+  LinkOutlined,
   LoadingOutlined,
+  MinusOutlined,
   PictureOutlined,
   PlayCircleFilled,
   StopOutlined,
   VideoCameraOutlined,
 } from "@ant-design/icons";
 
+const { Header, Content, Sider } = Layout;
+
 /**
  * 与 Rust 端常量保持一致(src-tauri/src/lib.rs):
  *   LEFT_PANEL_WIDTH = 180
@@ -44,14 +50,12 @@ import {
  *
  * 导出以避免 noUnusedLocals 误报;后续如有组件需要可直接 import。
  */
-export const LEFT_PANEL_WIDTH = 180;
-export const TOOLBAR_HEIGHT = 48;
 
 /** 工具栏右侧的尺寸预设(统一横屏:宽 × 高) */
 const SIZE_PRESETS = [
-  { label: "1920×1080", w: 1920, h: 1080 },
-  { label: "1024×768", w: 1024, h: 768 },
   { label: "1280×720", w: 1280, h: 720 },
+  { label: "1920×1080", w: 1920, h: 1080 },
+  { label: "1024×768", w: 1024, h: 768 }
 ];
 
 
@@ -78,8 +82,11 @@ function App() {
   const [activeId, setActiveId] = useState<string | null>(null);
 
   const [contentSize, setContentSize] = useState<{ w: number; h: number }>({ w: 1280, h: 720 });
+  const contentSizeRef = useRef(contentSize);
   const [customMode, setCustomMode] = useState(false);
-
+  const isCustomSize = useCallback(() => {
+    return !SIZE_PRESETS.find(({ w, h }) => w == contentSize.w && h == contentSize.h);
+  }, [contentSize])
   // 录制状态机(由 lib/recorder.ts 内部单例维护,这里订阅以驱动按钮)
   const [recordState, setRecordState] = useState<RecordState>(getRecordState());
 
@@ -96,7 +103,11 @@ function App() {
   // antd 6 notification 必须用 hook + contextHolder 才能正确取到主题
   const [notifyApi, notifyContext] = notification.useNotification();
 
-  function handleSelectTask(t: Task) {
+  function handleSelectTask({ key }: { key: string }) {
+    const t = mockTasks.find((v) => v.id == key);
+    if (!t) {
+      return;
+    }
     setActiveId(t.id);
     void loadInWebview(t.id, t.url);
   }
@@ -383,239 +394,257 @@ function App() {
   // 停止按钮仅在 recording 时可点
   const stopDisabled = recordState !== "recording";
 
-  const applyWorkAreaSize = useCallback(async (w: number, h: number) => {
+  const [collapsed, setCollapsed] = useState(false);
+
+  const applyWorkAreaSize = useCallback(async (w?: number, h?: number) => {
+    if (w && h) {
+      setContentSize({ w, h });
+      contentSizeRef.current = { w, h };
+    } else {
+      w = contentSize.w;
+      h = contentSize.h;
+    }
     try {
       // 先调主窗口,避免子 webview 越界
       await invoke("set_window_size", {
-        width: w + 180,
-        height: h + 46,
+        width: w + (collapsed ? 30 : 180) + 4,
+        height: h + 48 + 24 + 4,
+        contentX: (collapsed ? 30 : 180) + 2,
+        contentY: 50,
         contentWidth: w,
         contentHeight: h
       });
-      setContentSize({ w, h })
     } catch (e) {
-      console.error("调整尺寸失败:", e);
+      console.error("调整尺寸失败 ", e);
     }
-  }, [setContentSize]);
+  }, [contentSize, setContentSize]);
 
-  // 首次挂载时按默认 contentSize 调整窗口;依赖项故意为空(仅初始化用)。
   useEffect(() => {
-    applyWorkAreaSize(contentSize.w, contentSize.h);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
+    applyWorkAreaSize();
   }, []);
 
   // 录制状态机(RecordState 类型已抽到 src/types/ipc.ts),录制功能下一阶段接入
+  const handleCollaspsed = useCallback(async (c: boolean) => {
+    setCollapsed(c);
+    try {
+      // 先调主窗口,避免子 webview 越界
+      await invoke("set_window_size", {
+        width: contentSize.w + (collapsed ? 30 : 180),
+        height: contentSize.h + 60,
+        contentWidth: contentSize.w,
+        contentHeight: contentSize.h
+      });
+    } catch (e) {
+      console.error("调整尺寸失败:", e);
+    }
+  }, [setCollapsed]);
+  const {
+    token: { colorBgContainer },
+  } = theme.useToken();
 
+  const [statusColor, setStatusColor] = useState("#888");
+  const [statusText, setStatusText] = useState("请选择任务");
   return (
-    <div className="flex h-screen w-screen overflow-hidden select-none">
-      {/* antd notification 的渲染容器 —— 必须挂在树中 */}
-      {notifyContext}
-      {/* ===== 左栏:任务列表(180px) ===== */}
-      <aside className="w-[180px] shrink-0 border-r border-gray-4 bg-gray-2 h-full flex flex-col">
-        <div className="px-3 py-2 text-xs text-gray-7 border-b border-gray-4 select-none">
-          任务列表
-        </div>
-        <div className="flex-1 overflow-y-auto">
-          <ul>
-            {mockTasks.map((t) => {
-              const active = activeId === t.id;
-              return (
-                <li
-                  key={t.id}
-                  className={
-                    "flex items-center justify-between px-3 py-2 text-sm cursor-pointer transition-colors " +
-                    (active
-                      ? "bg-primary-1 text-primary-7"
-                      : "hover:bg-gray-3 text-gray-10")
-                  }
-                  onClick={() => handleSelectTask(t)}
-                >
-                  <span className="truncate">任务 {t.id}</span>
-                  <button
-                    type="button"
-                    title="在系统浏览器打开"
-                    className="ml-2 inline-flex items-center justify-center w-6 h-6 rounded-sm hover:bg-gray-4 text-gray-7 hover:text-primary-6"
-                    onClick={(e) => {
-                      e.stopPropagation();
-                      void openInBrowser(t.url);
-                    }}
-                  >
-                    {/* 外链图标(lucide external-link 同款 path,不引入新依赖) */}
-                    <svg
-                      width="14"
-                      height="14"
-                      viewBox="0 0 24 24"
-                      fill="none"
-                      stroke="currentColor"
-                      strokeWidth="2"
-                      strokeLinecap="round"
-                      strokeLinejoin="round"
-                    >
-                      <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
-                      <polyline points="15 3 21 3 21 9" />
-                      <line x1="10" y1="14" x2="21" y2="3" />
-                    </svg>
-                  </button>
-                </li>
-              );
-            })}
-          </ul>
-        </div>
-      </aside>
-
-      {/* ===== 右侧主区 ===== */}
-      <main className="flex-1 flex flex-col min-w-0">
-        {/* 工具栏(48px) */}
-        <header className="h-12 shrink-0 flex items-center justify-between gap-2 px-3 border-b border-gray-4 bg-gray-1">
-          {/* 左侧:操作按钮区 */}
-          <div className="flex items-center gap-2">
-            <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="top">
-              <Button
-                size="small"
-                icon={<PictureOutlined />}
-                disabled={!activeId}
-                onClick={() => void handleManualCapture()}
-              />
-            </Tooltip>
-            <Divider type="vertical" />
-            {/* 开始按钮:仅 idle + 已选任务时可点 */}
-            <Tooltip title={startConfig.tip} placement="top">
-              <Button
-                size="small"
-                icon={startConfig.icon}
-                disabled={startDisabled}
-                onClick={() => void handleStartRecord()}
-              />
-            </Tooltip>
-            {/* 停止按钮:仅 recording 时可用 */}
-            <Tooltip
-              title={
-                stopDisabled
-                  ? "无进行中的录制"
-                  : "停止录制并落盘 mp4(F11)"
-              }
-              placement="top"
-            >
-              <Button
-                size="small"
-                icon={<StopOutlined />}
-                disabled={stopDisabled}
-                onClick={() => void handleStopRecord()}
-              />
-            </Tooltip>
-            <Divider type="vertical" />
-            {/* 打开文件夹:reveal mp4 → png → 兜底 screenshots 目录 */}
-            <Tooltip
-              title={
-                !activeId
-                  ? "请先选择任务"
-                  : assets?.recording_exists
-                    ? "在文件管理器中显示录制视频"
-                    : assets?.screenshot_exists
-                      ? "在文件管理器中显示截图"
-                      : "打开截图目录"
-              }
-              placement="top"
-            >
-              <Button
-                size="small"
-                icon={<FolderOpenOutlined />}
-                disabled={!assets}
-                onClick={() => void handleOpenFolder()}
-              />
-            </Tooltip>
-            {/* 预览截图:仅当 png 存在 */}
-            <Tooltip
-              title={
-                !assets?.screenshot_exists
-                  ? "暂无截图可预览"
-                  : "在新窗口预览截图"
-              }
-              placement="top"
-            >
-              <Button
-                size="small"
-                icon={<FileImageOutlined />}
-                disabled={!assets?.screenshot_exists}
-                onClick={() => void handlePreviewImage()}
-              />
-            </Tooltip>
-            {/* 预览视频:仅当 mp4 存在 */}
-            <Tooltip
-              title={
-                !assets?.recording_exists
-                  ? "暂无录制视频可预览"
-                  : "在新窗口预览录制视频"
-              }
-              placement="top"
-            >
-              <Button
-                size="small"
-                icon={<VideoCameraOutlined />}
-                disabled={!assets?.recording_exists}
-                onClick={() => void handlePreviewVideo()}
-              />
-            </Tooltip>
-          </div>
+    <Layout className="m-0 p-0 h-full overflow-hidden">
+      <Header className="app-title h-8 select-none" style={{ paddingLeft: 8 }} >
+        <div className="flex h-full flex-row items-center">
+          <Space size={4}>
+            <Button className="app-close" icon={<CloseOutlined />} size="large" type="text" />
+            <Button icon={<BankOutlined />} disabled size="large" type="text" />
+            <Button className="app-minimize" icon={<MinusOutlined />} size="large" type="text" />
+          </Space>
+          <div className="w-32" />
+          <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="right">
+            <Button
+
+              shape="circle"
+              icon={<PictureOutlined />}
+              disabled={!activeId}
+              onClick={handleManualCapture}
+            />
+          </Tooltip>
+          <Divider orientation="vertical" />
+          {/* 开始按钮:仅 idle + 已选任务时可点 */}
+          <Tooltip title={startConfig.tip} placement="right">
+            <Button
+
+              shape="circle"
+              icon={startConfig.icon}
+              disabled={startDisabled}
+              onClick={() => void handleStartRecord()}
+            />
+          </Tooltip>
+          {/* 停止按钮:仅 recording 时可用 */}
+          <Tooltip
+            title={
+              stopDisabled
+                ? "无进行中的录制"
+                : "停止录制并落盘 mp4(F11)"
+            }
+            placement="right"
+          >
+            <Button
+
+              shape="circle"
+              icon={<StopOutlined />}
+              disabled={stopDisabled}
+              onClick={() => void handleStopRecord()}
+            />
+          </Tooltip>
+          <Divider orientation="vertical" />
+          {/* 打开文件夹:reveal mp4 → png → 兜底 screenshots 目录 */}
+          <Tooltip
+            title={
+              !activeId
+                ? "请先选择任务"
+                : assets?.recording_exists
+                  ? "在文件管理器中显示录制视频"
+                  : assets?.screenshot_exists
+                    ? "在文件管理器中显示截图"
+                    : "打开截图目录"
+            }
+            placement="right"
+          >
+            <Button
+
+              shape="circle"
+              icon={<FolderOpenOutlined />}
+              disabled={!assets}
+              onClick={() => void handleOpenFolder()}
+            />
+          </Tooltip>
+          {/* 预览截图:仅当 png 存在 */}
+          <Tooltip
+            title={
+              !assets?.screenshot_exists
+                ? "暂无截图可预览"
+                : "在新窗口预览截图"
+            }
+            placement="right"
+          >
+            <Button
+
+              shape="circle"
+              icon={<FileImageOutlined />}
+              disabled={!assets?.screenshot_exists}
+              onClick={() => void handlePreviewImage()}
+            />
+          </Tooltip>
+          {/* 预览视频:仅当 mp4 存在 */}
+          <Tooltip
+            title={
+              !assets?.recording_exists
+                ? "暂无录制视频可预览"
+                : "在新窗口预览录制视频"
+            }
+            placement="right"
+          >
+            <Button
+
+              shape="circle"
+              icon={<VideoCameraOutlined />}
+              disabled={!assets?.recording_exists}
+              onClick={() => void handlePreviewVideo()}
+            />
+          </Tooltip>
+          <div className="flex-1">
 
-          {/* 右侧:尺寸预设 / 自定义 */}
-          <div className="flex items-center gap-2">
-            {!customMode ? (
-              <>
-                {SIZE_PRESETS.map((p) => (
-                  <Button
-                    key={p.label}
-                    size="small"
-                    disabled={contentSize.w == p.w && contentSize.h == p.h}
-                    onClick={() => applyWorkAreaSize(p.w, p.h)}
-                  >
-                    {p.label}
-                  </Button>
-                ))}
-                <Button size="small" onClick={() => setCustomMode(true)}>
-                  自定义
-                </Button>
-              </>
-            ) : (
-              <>
-                <InputNumber
-                  size="small"
-                  min={200}
-                  max={3840}
-                  value={contentSize.w}
-                  onChange={(v) => setContentSize({ w: v || 0, h: contentSize.h })}
-                  style={{ width: 80 }}
-                />
-                <span className="text-gray-7 select-none">×</span>
-                <InputNumber
-                  size="small"
-                  min={200}
-                  max={2160}
-                  value={contentSize.h}
-                  onChange={(v) => setContentSize({ w: contentSize.w, h: v || 0 })}
-                  style={{ width: 80 }}
-                />
+          </div>
+          {!customMode ? (
+            <>
+              {SIZE_PRESETS.map((p) => (
                 <Button
-                  size="small"
-                  type="primary"
-                  onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
+                  key={p.label}
+                  type={contentSize.w == p.w && contentSize.h == p.h ? "primary" : undefined}
+
+                  onClick={contentSize.w != p.w || contentSize.h != p.h ? () => applyWorkAreaSize(p.w, p.h) as any : undefined}
                 >
-                  应用
-                </Button>
-                <Button size="small" onClick={() => setCustomMode(false)}>
-                  取消
+                  {p.label}
                 </Button>
-              </>
+              ))}
+              {isCustomSize() ?
+                <Button type="primary" onClick={() => setCustomMode(true)}>
+                  自定义({contentSize.w}x{contentSize.h})
+                </Button> : <Button onClick={() => setCustomMode(true)}>
+                  自定义
+                </Button>}
+            </>
+          ) : (
+            <>
+              <InputNumber
+
+                min={200}
+                max={3840}
+                value={contentSize.w}
+                onChange={(v) => setContentSize({ w: v || 0, h: contentSize.h })}
+                style={{ width: 80 }}
+              />
+              <span className="text-gray-7 select-none">×</span>
+              <InputNumber
+
+                min={200}
+                max={2160}
+                value={contentSize.h}
+                onChange={(v) => setContentSize({ w: contentSize.w, h: v || 0 })}
+                style={{ width: 80 }}
+              />
+              <Button
+                type="primary"
+                onClick={() => {
+                  setCustomMode(false);
+                  applyWorkAreaSize();
+
+                }}
+              >
+                应用
+              </Button>
+              <Button onClick={() => {
+                setContentSize(contentSizeRef.current)
+                setCustomMode(false);
+              }}>
+                取消
+              </Button>
+            </>
+          )}
+        </div>
+      </Header>
+      <Layout>
+        <Sider className="select-none" collapsible collapsed={collapsed} onCollapse={(c) => handleCollaspsed(c)} width={180} style={{ background: colorBgContainer }}>
+          <Menu
+            mode="inline"
+            defaultSelectedKeys={[activeId || '']}
+            selectedKeys={[activeId || '']}
+            style={{ height: '100%', borderInlineEnd: 0 }}
+            onClick={handleSelectTask}
+
+          >
+            {mockTasks.map((t) => <Menu.Item icon={<LinkOutlined />} key={t.id}>
+              {t.id}
+            </Menu.Item>
             )}
+          </Menu>
+        </Sider>
+        <Layout className="flex flex-row">
+          <Content className="w-full m-0 p-1 flex-1" />
+          <div className="w-full h-6 flex items-center bg-red-400 px-2 text-gray-3">
+            <div className="bg-gray-6 rounded-full w-3 h-3 m-2" style={{ backgroundColor: statusColor }} />
+            <div className="w-32">{statusText}</div>
+            <span className="flex-1">{notifyContext}</span>
           </div>
-        </header>
-
-        {/* 工作区占位:实际由 Rust child webview 覆盖在此区域之上 */}
-        <section className="flex-1 bg-gray-7" />
-        <section className="h-10 bg-gray-5 broder border-gray-7" />
-      </main>
-    </div>
+        </Layout>
+      </Layout>
+    </Layout >
   );
 }
 
-export default App;
+
+
+
+export default function Main() {
+  return <ConfigProvider theme={{
+    // 1. Use dark algorithm alone
+    algorithm: [theme.darkAlgorithm, theme.compactAlgorithm]
+  }}>
+    <App />
+  </ ConfigProvider>
+};

+ 4 - 3
src/mocks/tasks.ts

@@ -10,7 +10,8 @@ export interface Task {
   url: string;
 }
 
-export const mockTasks: Task[] = `https://www.awwwards.com/sites/reasonal
+export const mockTasks: Task[] = `https://www.baidu.com
+https://www.awwwards.com/sites/reasonal
 https://www.awwwards.com/sites/percare-mobile-visualizers
 https://www.awwwards.com/sites/edwin-le-portfolio
 https://www.awwwards.com/sites/funy-ai-video-image
@@ -45,7 +46,7 @@ https://www.awwwards.com/sites/wa-solutions
 https://www.awwwards.com/sites/patio
 https://www.awwwards.com/sites/pagegrid
 https://www.awwwards.com/sites/auger-dubord
-https://www.awwwards.com/sites/ulf-online`.split("\n").map((item,idx) => ({
-  id: idx+"",
+https://www.awwwards.com/sites/ulf-online`.split("\n").map((item, idx) => ({
+  id: idx + "",
   url: item.replace(/\s+/g, ''),
 }));