lv 2 weken geleden
bovenliggende
commit
19e53a0269
11 gewijzigde bestanden met toevoegingen van 547 en 484 verwijderingen
  1. 26 0
      pnpm-lock.yaml
  2. 2 0
      pnpm-workspace.yaml
  3. 53 135
      src-tauri/Cargo.lock
  4. 3 2
      src-tauri/Cargo.toml
  5. 14 3
      src-tauri/capabilities/default.json
  6. 8 21
      src-tauri/src/capture.rs
  7. 23 4
      src-tauri/src/lib.rs
  8. 7 3
      src-tauri/tauri.conf.json
  9. 2 107
      src/App.css
  10. 405 206
      src/App.tsx
  11. 4 3
      src/mocks/tasks.ts

+ 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}"))
     }
 }

+ 23 - 4
src-tauri/src/lib.rs

@@ -16,11 +16,11 @@ 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);
+const WIN_FRAME: (f64, f64, f64, f64) = (2.0, 2.0, 2.0, 26.0);
 
 /// 子 webview 初始尺寸:1280×720 横屏
-const DEFAULT_CONTENT_W: f64 = 1280.0;
-const DEFAULT_CONTENT_H: f64 = 720.0;
+const DEFAULT_CONTENT_W: f64 = 1280.0 + 180.0;
+const DEFAULT_CONTENT_H: f64 = 720.0 + 34.0;
 
 /// 子 webview 初始 url(空白页占位,等用户从任务列表选)
 const INITIAL_URL: &str = "about:blank";
@@ -157,6 +157,8 @@ fn set_window_size(
 
 #[cfg_attr(mobile, tauri::mobile_entry_point)]
 pub fn run() {
+    use tauri_plugin_decorum::WebviewWindowExt; // adds helper methods to WebviewWindow
+
     tauri::Builder::default()
         .plugin(tauri_plugin_opener::init())
         .plugin(shortcuts::record_shortcut_plugin())
@@ -170,7 +172,22 @@ pub fn run() {
 
             // 在主窗口(也是 React UI 所在的 webview)所属的 Window 上挂一个 child webview
             // 注意:`add_child` 定义在 `Window` 上,不在 `WebviewWindow` 上,所以这里取 Window
-            let window = app.get_window("main").ok_or("主窗口未找到")?;
+
+            let main_window = app.get_webview_window("main").unwrap();
+            main_window.create_overlay_titlebar().unwrap();
+
+            // // Some macOS-specific helpers
+            // #[cfg(target_os = "macos")] {
+            // 	// Set a custom inset to the traffic lights
+            // 	window.set_traffic_lights_inset(12.0, 16.0).unwrap();
+
+            // 	// Make window transparent without privateApi
+            // 	window.make_transparent().unwrap()
+
+            // 	// Set window level
+            // 	// NSWindowLevel: https://developer.apple.com/documentation/appkit/nswindowlevel
+            // 	window.set_window_level(25).unwrap()
+            // }
 
             // 1) 先把窗口「客户区」调到目标值 = 左栏 + 工作区宽 × 工具栏高 + 工作区高
             //    必须放在 add_child 之前:否则 child webview 是在还很小的客户区里被创建,
@@ -185,6 +202,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| {

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

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

+ 2 - 107
src/App.css

@@ -1,116 +1,11 @@
 /* ===== Tailwind CSS v4 入口(必须放在最顶部)===== */
 @import "tailwindcss";
 
-/* ===== 主题 token:与 Ant Design 6 设计规范对齐 =====
- * 参考:https://ant-design.antgroup.com/docs/react/customize-theme-cn
- * 仅声明 Tailwind 用到的 utility 所需 token;JS 侧后续接入 antd ConfigProvider 时
- * 应保持 seed token 与此处一致(colorPrimary / colorSuccess / borderRadius 等)。
- */
-@theme {
-  /* ---- 字体栈(antd 默认 system font stack) ---- */
-  --font-sans:
-    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
-    Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
-    "Segoe UI Symbol", "Noto Color Emoji";
-
-  /* ---- 品牌色:蓝色 10 阶(antd Blue palette,主色 = blue-6 #1677ff) ---- */
-  --color-primary-1: #e6f4ff;
-  --color-primary-2: #bae0ff;
-  --color-primary-3: #91caff;
-  --color-primary-4: #69b1ff;
-  --color-primary-5: #4096ff;
-  --color-primary-6: #1677ff; /* colorPrimary */
-  --color-primary-7: #0958d9;
-  --color-primary-8: #003eb3;
-  --color-primary-9: #002c8c;
-  --color-primary-10: #001d66;
-  --color-primary: #1677ff;
-
-  /* ---- 状态色(与 antd seed token 完全一致) ---- */
-  --color-success: #52c41a;
-  --color-warning: #faad14;
-  --color-error: #ff4d4f;
-  --color-info: #1677ff;
-
-  /* ---- 中性灰:antd 13 级灰阶 ---- */
-  --color-gray-1: #ffffff;
-  --color-gray-2: #fafafa;
-  --color-gray-3: #f5f5f5;
-  --color-gray-4: #f0f0f0;
-  --color-gray-5: #d9d9d9;
-  --color-gray-6: #bfbfbf;
-  --color-gray-7: #8c8c8c;
-  --color-gray-8: #595959;
-  --color-gray-9: #434343;
-  --color-gray-10: #262626;
-  --color-gray-11: #1f1f1f;
-  --color-gray-12: #141414;
-  --color-gray-13: #000000;
-
-  /* ---- 字号(对齐 antd fontSize 系列;基础 14px) ----
-   * antd: SM=12 / base=14 / LG=16 / XL=20 / H3=24 / H2=30 / H1=38
-   */
-  --text-xs: 12px;
-  --text-xs--line-height: 1.66;
-  --text-sm: 14px;
-  --text-sm--line-height: 1.5714285714285714;
-  --text-base: 14px;
-  --text-base--line-height: 1.5714285714285714;
-  --text-lg: 16px;
-  --text-lg--line-height: 1.5;
-  --text-xl: 20px;
-  --text-xl--line-height: 1.4;
-  --text-2xl: 24px;
-  --text-2xl--line-height: 1.35;
-  --text-3xl: 30px;
-  --text-3xl--line-height: 1.3;
-  --text-4xl: 38px;
-  --text-4xl--line-height: 1.21;
-
-  /* ---- 间距:基于 4px step(Tailwind v4 默认即 4px,此处显式声明保持一致) ----
-   * 用法:p-1=4 / p-2=8 / p-3=12 / p-4=16 / p-5=20 / p-6=24 / p-8=32 / p-12=48
-   */
-  --spacing: 4px;
-
-  /* ---- 圆角:对齐 antd borderRadius 四档 ---- */
-  --radius-xs: 2px;
-  --radius-sm: 4px;
-  --radius-md: 6px; /* antd 基础 borderRadius */
-  --radius-lg: 8px;
-  --radius: 6px;
-
-  /* ---- 阴影:对齐 antd 三档 boxShadow ---- */
-  --shadow-sm:
-    0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02),
-    0 2px 4px 0 rgba(0, 0, 0, 0.02);
-  --shadow-md:
-    0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12),
-    0 9px 28px 8px rgba(0, 0, 0, 0.05);
-  --shadow-lg:
-    0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12),
-    0 9px 28px 8px rgba(0, 0, 0, 0.05);
-}
-
-/* ===== 全局基础样式 ===== */
-:root {
-  font-family:
-    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
-    Arial, "Noto Sans", sans-serif;
-  font-size: 14px;
-  line-height: 1.5714;
-  color: #1f1f1f;
-  background-color: #ffffff;
-
-  font-synthesis: none;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  -webkit-text-size-adjust: 100%;
-}
 
 html,
 body,
 #root {
   height: 100%;
   margin: 0;
-}
+  background-color: #000;
+}

+ 405 - 206
src/App.tsx

@@ -2,7 +2,7 @@ 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 { Button, ConfigProvider, Divider, InputNumber, Layout, Menu, notification, Space, theme, Tooltip } from "antd";
 import { mockTasks, type Task } from "./mocks/tasks";
 import {
   EVT_RECORDING_FAILED,
@@ -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, Footer, Sider } = Layout;
+
 /**
  * 与 Rust 端常量保持一致(src-tauri/src/lib.rs):
  *   LEFT_PANEL_WIDTH = 180
@@ -96,7 +102,11 @@ function App() {
   // antd 6 notification 必须用 hook + contextHolder 才能正确取到主题
   const [notifyApi, notifyContext] = notification.useNotification();
 
-  function handleSelectTask(t: Task) {
+  function handleSelectTask(key: string) {
+    const t = mockTasks.find((v) => v.id == key);
+    if (!t) {
+      return;
+    }
     setActiveId(t.id);
     void loadInWebview(t.id, t.url);
   }
@@ -405,217 +415,406 @@ function App() {
   }, []);
 
   // 录制状态机(RecordState 类型已抽到 src/types/ipc.ts),录制功能下一阶段接入
-
+  const [collapsed, setCollapsed] = useState(false);
+  const {
+    token: { colorBgContainer },
+  } = theme.useToken();
   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)}
+    <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={8}>
+            <Button className="app-close" icon={<CloseOutlined />} size="large" />
+            <Button icon={<BankOutlined />} disabled size="large" />
+            <Button className="app-minimize" icon={<MinusOutlined />} size="large" />
+          </Space>
+          <div className="w-32" />
+          <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="top">
+            <Button
+
+              shape="circle"
+              icon={<PictureOutlined />}
+              disabled={!activeId}
+              onClick={() => void handleManualCapture()}
+            />
+          </Tooltip>
+          <Divider orientation="vertical" />
+          {/* 开始按钮:仅 idle + 已选任务时可点 */}
+          <Tooltip title={startConfig.tip} placement="top">
+            <Button
+
+              shape="circle"
+              icon={startConfig.icon}
+              disabled={startDisabled}
+              onClick={() => void handleStartRecord()}
+            />
+          </Tooltip>
+          {/* 停止按钮:仅 recording 时可用 */}
+          <Tooltip
+            title={
+              stopDisabled
+                ? "无进行中的录制"
+                : "停止录制并落盘 mp4(F11)"
+            }
+            placement="top"
+          >
+            <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="top"
+          >
+            <Button
+
+              shape="circle"
+              icon={<FolderOpenOutlined />}
+              disabled={!assets}
+              onClick={() => void handleOpenFolder()}
+            />
+          </Tooltip>
+          {/* 预览截图:仅当 png 存在 */}
+          <Tooltip
+            title={
+              !assets?.screenshot_exists
+                ? "暂无截图可预览"
+                : "在新窗口预览截图"
+            }
+            placement="top"
+          >
+            <Button
+
+              shape="circle"
+              icon={<FileImageOutlined />}
+              disabled={!assets?.screenshot_exists}
+              onClick={() => void handlePreviewImage()}
+            />
+          </Tooltip>
+          {/* 预览视频:仅当 mp4 存在 */}
+          <Tooltip
+            title={
+              !assets?.recording_exists
+                ? "暂无录制视频可预览"
+                : "在新窗口预览录制视频"
+            }
+            placement="top"
+          >
+            <Button
+
+              shape="circle"
+              icon={<VideoCameraOutlined />}
+              disabled={!assets?.recording_exists}
+              onClick={() => void handlePreviewVideo()}
+            />
+          </Tooltip>
+          <div className="flex-1">
+            {notifyContext}
+          </div>
+          {!customMode ? (
+            <>
+              {SIZE_PRESETS.map((p) => (
+                <Button
+                  key={p.label}
+
+                  disabled={contentSize.w == p.w && contentSize.h == p.h}
+                  onClick={() => applyWorkAreaSize(p.w, p.h)}
                 >
-                  <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()}
+                  {p.label}
+                </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 }}
               />
-            </Tooltip>
-            {/* 预览截图:仅当 png 存在 */}
-            <Tooltip
-              title={
-                !assets?.screenshot_exists
-                  ? "暂无截图可预览"
-                  : "在新窗口预览截图"
-              }
-              placement="top"
-            >
-              <Button
-                size="small"
-                icon={<FileImageOutlined />}
-                disabled={!assets?.screenshot_exists}
-                onClick={() => void handlePreviewImage()}
+              <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 }}
               />
-            </Tooltip>
-            {/* 预览视频:仅当 mp4 存在 */}
-            <Tooltip
-              title={
-                !assets?.recording_exists
-                  ? "暂无录制视频可预览"
-                  : "在新窗口预览录制视频"
-              }
-              placement="top"
-            >
               <Button
-                size="small"
-                icon={<VideoCameraOutlined />}
-                disabled={!assets?.recording_exists}
-                onClick={() => void handlePreviewVideo()}
-              />
-            </Tooltip>
-          </div>
 
-          {/* 右侧:尺寸预设 / 自定义 */}
-          <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 }}
-                />
-                <Button
-                  size="small"
-                  type="primary"
-                  onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
-                >
-                  应用
-                </Button>
-                <Button size="small" onClick={() => setCustomMode(false)}>
-                  取消
-                </Button>
-              </>
+                type="primary"
+                onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
+              >
+                应用
+              </Button>
+              <Button onClick={() => setCustomMode(false)}>
+                取消
+              </Button>
+            </>
+          )}
+        </div>
+      </Header>
+      <Layout>
+        <Sider className="select-none" collapsible collapsed={collapsed} onCollapse={(c) => setCollapsed(c)} width={180} style={{ background: colorBgContainer }}>
+          <Menu
+            mode="inline"
+            defaultSelectedKeys={[activeId || '']}
+            selectedKeys={[activeId || '']}
+            style={{ height: '100%', borderInlineEnd: 0 }}
+            onClick={({ key }) => handleSelectTask(key)}
+
+          >
+            {mockTasks.map((t) => <Menu.Item icon={<LinkOutlined />} key={t.id}>
+              {t.id}
+            </Menu.Item>
             )}
-          </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>
+          </Menu>
+        </Sider>
+        <Layout>
+          <Content className="m-0 p-1 h-full w-full bg-indigo-950" />
+        </Layout>
+      </Layout>
+    </Layout>
   );
+  // <div data-tauri-decorum-tb className="flex h-screen w-screen overflow-hidden select-none">
+  //   {/* antd notification 的渲染容器 —— 必须挂在树中 */}
+  //   {notifyContext}
+  //   {/* ===== 左栏:任务列表(180px) ===== */}
+  //   <aside data-tauri-drag-region 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
+  //             
+  //             icon={<PictureOutlined />}
+  //             disabled={!activeId}
+  //             onClick={() => void handleManualCapture()}
+  //           />
+  //         </Tooltip>
+  //         <Divider type="vertical" />
+  //         {/* 开始按钮:仅 idle + 已选任务时可点 */}
+  //         <Tooltip title={startConfig.tip} placement="top">
+  //           <Button
+  //             
+  //             icon={startConfig.icon}
+  //             disabled={startDisabled}
+  //             onClick={() => void handleStartRecord()}
+  //           />
+  //         </Tooltip>
+  //         {/* 停止按钮:仅 recording 时可用 */}
+  //         <Tooltip
+  //           title={
+  //             stopDisabled
+  //               ? "无进行中的录制"
+  //               : "停止录制并落盘 mp4(F11)"
+  //           }
+  //           placement="top"
+  //         >
+  //           <Button
+  //             
+  //             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
+  //             
+  //             icon={<FolderOpenOutlined />}
+  //             disabled={!assets}
+  //             onClick={() => void handleOpenFolder()}
+  //           />
+  //         </Tooltip>
+  //         {/* 预览截图:仅当 png 存在 */}
+  //         <Tooltip
+  //           title={
+  //             !assets?.screenshot_exists
+  //               ? "暂无截图可预览"
+  //               : "在新窗口预览截图"
+  //           }
+  //           placement="top"
+  //         >
+  //           <Button
+  //             
+  //             icon={<FileImageOutlined />}
+  //             disabled={!assets?.screenshot_exists}
+  //             onClick={() => void handlePreviewImage()}
+  //           />
+  //         </Tooltip>
+  //         {/* 预览视频:仅当 mp4 存在 */}
+  //         <Tooltip
+  //           title={
+  //             !assets?.recording_exists
+  //               ? "暂无录制视频可预览"
+  //               : "在新窗口预览录制视频"
+  //           }
+  //           placement="top"
+  //         >
+  //           <Button
+  //             
+  //             icon={<VideoCameraOutlined />}
+  //             disabled={!assets?.recording_exists}
+  //             onClick={() => void handlePreviewVideo()}
+  //           />
+  //         </Tooltip>
+  //       </div>
+
+  //       {/* 右侧:尺寸预设 / 自定义 */}
+  //       <div className="flex items-center gap-2">
+  //         {!customMode ? (
+  //           <>
+  //             {SIZE_PRESETS.map((p) => (
+  //               <Button
+  //                 key={p.label}
+  //                 
+  //                 disabled={contentSize.w == p.w && contentSize.h == p.h}
+  //                 onClick={() => applyWorkAreaSize(p.w, p.h)}
+  //               >
+  //                 {p.label}
+  //               </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={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
+  //             >
+  //               应用
+  //             </Button>
+  //             <Button  onClick={() => setCustomMode(false)}>
+  //               取消
+  //             </Button>
+  //           </>
+  //         )}
+  //       </div>
+  //     </header>
+
+  //     {/* 工作区占位:实际由 Rust child webview 覆盖在此区域之上 */}
+  //     <section className="flex-1 bg-gray-7" />
+  //     <section className="h-6 bg-gray-5 broder border-gray-7" />
+  //   </main>
+  // </div>
+  // );
 }
 
-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, ''),
 }));