zcode-pro 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inject.js CHANGED
@@ -30,6 +30,18 @@
30
30
  function clearConfigCache() {
31
31
  configCache = { value: null, at: 0 };
32
32
  }
33
+ var DEFAULT_SHORTCUTS = {
34
+ switchLast: "z",
35
+ // 立即切到上次会话
36
+ switchPrev: "x",
37
+ // 弹窗中上移
38
+ switchNext: "c",
39
+ // 弹窗中下移
40
+ switchRecent: "e",
41
+ // 打开最近 10 条
42
+ switchAttention: "r"
43
+ // 打开运行中 / 未查看
44
+ };
33
45
  function zcodeAppVersion() {
34
46
  const v = liveBoot().zcodeVersion;
35
47
  return typeof v === "string" && v ? v : "";
@@ -178,7 +190,7 @@
178
190
  featureTaskOrder: "\u4FA7\u8FB9\u680F\u4F1A\u8BDD\u62D6\u52A8\u6392\u5E8F",
179
191
  featureTaskOrderDesc: "\u8BA9\u7F6E\u9876\u3001\u9879\u76EE\u4E0E\u5206\u7EC4\u4E2D\u7684\u4F1A\u8BDD\u62D6\u52A8\u540E\u8BB0\u4F4F\u987A\u5E8F\uFF0C\u5237\u65B0\u540E\u4FDD\u6301\u3002",
180
192
  featureSessionSwitch: "\u4F1A\u8BDD\u5FEB\u6377\u5207\u6362",
181
- featureSessionSwitchDesc: "alt+z \u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362\uFF1B\u6309\u4F4F alt \u518D\u6309 x/c \u5F39\u51FA\u6700\u8FD1\u4F1A\u8BDD\u5217\u8868\u524D\u540E\u9009\u62E9\uFF0C\u677E\u5F00 alt \u5207\u6362\uFF08\u7C7B alt+tab\uFF09\u3002",
193
+ featureSessionSwitchDesc: "{last} \u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362\uFF1B\u6309\u4F4F alt \u518D\u6309 {prev}/{next} \u5F39\u51FA\u6700\u8FD1\u4F1A\u8BDD\u5217\u8868\u524D\u540E\u9009\u62E9\uFF08\u521D\u59CB 10 \u6761\uFF0C{next} \u5230\u5E95\u518D\u52A0\u8F7D 5 \u6761\uFF09\uFF0C{recent} \u76F4\u63A5\u6253\u5F00\u6700\u8FD1 10 \u6761\uFF0C{attention} \u6253\u5F00\u8FD0\u884C\u4E2D\u4E0E\u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1\uFF1B\u65B9\u5411\u952E\u540C\u6837\u53EF\u9009\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362\uFF08\u7C7B alt+tab\uFF09",
182
194
  featureTitleLock: "\u91CD\u547D\u540D\u4F1A\u8BDD\u540D\u9501\u5B9A",
183
195
  featureTitleLockDesc: "\u624B\u52A8\u91CD\u547D\u540D\u8FC7\u7684\u4F1A\u8BDD\u4E0D\u518D\u88AB\u81EA\u52A8\u6807\u9898\u8986\u76D6\uFF1A\u4F1A\u8BDD\u6709\u65B0\u56DE\u7B54\u540E\u754C\u9762\u53EF\u80FD\u6539\u663E\u81EA\u52A8\u6807\u9898\uFF0C\u91CD\u542F\u540E\u53C8\u53D8\u56DE\uFF0C\u672C\u529F\u80FD\u8BA9\u4FA7\u680F\u59CB\u7EC8\u663E\u793A\u91CD\u547D\u540D\u540E\u7684\u540D\u5B57\u3002",
184
196
  tabTitleGen: "\u4EFB\u52A1\u540D\u63D0\u793A\u8BCD",
@@ -206,7 +218,7 @@
206
218
  titleGenTestOk: "\u751F\u6210\u7ED3\u679C\uFF1A",
207
219
  titleGenTestFailed: "\u751F\u6210\u5931\u8D25",
208
220
  featureWsRunning: "\u6298\u53E0\u65F6\u4EFB\u52A1\u8FD0\u884C\u4E2D\u63D0\u793A",
209
- featureWsRunningDesc: "\u6709\u4F1A\u8BDD\u5728\u8FD0\u884C\u65F6\uFF0C\u6298\u53E0\u7684\u9879\u76EE\u4E0E\u201C\u4EFB\u52A1\u201D\u201C\u5DF2\u7F6E\u9876\u201D\u7B49\u5206\u533A\u5934\u663E\u793A\u4E0E\u4EFB\u52A1\u8FD0\u884C\u4E2D\u540C\u6B3E\u7684\u52A0\u8F7D\u56FE\u6807\uFF0C\u5168\u90E8\u7ED3\u675F\u540E\u6D88\u5931\uFF1B3.16.1 \u4E4B\u524D\u6BCF 60 \u79D2\u4F1A\u5728\u540E\u53F0\u77ED\u6682\u5C55\u5F00\u9879\u76EE\u590D\u6838\u518D\u6536\u8D77\u3002",
221
+ featureWsRunningDesc: "\u6709\u4F1A\u8BDD\u5728\u8FD0\u884C\u65F6\uFF0C\u6298\u53E0\u7684\u9879\u76EE\u4E0E\u201C\u4EFB\u52A1\u201D\u201C\u5DF2\u7F6E\u9876\u201D\u7B49\u5206\u533A\u5934\u663E\u793A\u4E0E\u4EFB\u52A1\u8FD0\u884C\u4E2D\u540C\u6B3E\u7684\u52A0\u8F7D\u56FE\u6807\uFF0C\u5168\u90E8\u7ED3\u675F\u540E\u6D88\u5931\u3002",
210
222
  tabPerformance: "\u6027\u80FD",
211
223
  perfDesc: "ZCode \u4E3A\u6BCF\u4E2A\u6253\u5F00\u8FC7\u7684\u9879\u76EE\u5E38\u9A7B\u4E00\u4E2A\u4F1A\u8BDD\u8FDB\u7A0B\uFF0C\u5E94\u7528\u81EA\u8EAB\u4E0D\u4F1A\u56DE\u6536\uFF0C\u9879\u76EE\u591A\u4E86\u5185\u5B58\u5360\u7528\u5C31\u9AD8\u3002\u5F00\u542F\u540E\uFF0C\u957F\u65F6\u95F4\u672A\u4F7F\u7528\u4E14\u6CA1\u6709\u4EFB\u52A1\u5728\u8DD1\u7684\u9879\u76EE\u4F1A\u88AB\u81EA\u52A8\u91CA\u653E\uFF1A\u4F1A\u8BDD\u8BB0\u5F55\u4E0D\u53D7\u5F71\u54CD\uFF0C\u518D\u6B21\u6253\u5F00\u65F6\u81EA\u52A8\u6062\u590D\uFF0C\u4EC5\u9996\u6B21\u56DE\u590D\u6709\u51E0\u79D2\u7B49\u5F85\u3002\u6B63\u5728\u4F7F\u7528\u7684\u9879\u76EE\u3001\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u4E0E\u540E\u53F0\u547D\u4EE4\u3001\u5B9A\u65F6\u4EFB\u52A1\u90FD\u4E0D\u4F1A\u88AB\u56DE\u6536\u3002\u540E\u53F0\u547D\u4EE4\u68C0\u6D4B\u4F9D\u8D56\u7CFB\u7EDF\u8FDB\u7A0B\u4FE1\u606F\uFF08Linux / macOS / Windows \u5404\u8D70\u7CFB\u7EDF\u81EA\u5E26\u5DE5\u5177\uFF09\uFF0C\u68C0\u6D4B\u4E0D\u53EF\u7528\u65F6\u529F\u80FD\u81EA\u52A8\u505C\u7528\u3002",
212
224
  featureIdleReclaim: "\u7A7A\u95F2\u9879\u76EE\u5185\u5B58\u56DE\u6536",
@@ -234,10 +246,44 @@
234
246
  userFontDesc: "\u4F1A\u8BDD\u4E2D\u63D0\u95EE\u5185\u5BB9\u7684\u5B57\u4F53\uFF1B\u9009\u201C\u9ED8\u8BA4\u201D\u65F6\u8DDF\u968F\u754C\u9762\u5B57\u4F53\u3002",
235
247
  assistantFontName: "\u56DE\u7B54\u5B57\u4F53",
236
248
  assistantFontDesc: "\u4F1A\u8BDD\u4E2D\u56DE\u7B54\u6B63\u6587\u7684\u5B57\u4F53\uFF08\u4EE3\u7801\u5757\u4ECD\u7528\u7B49\u5BBD\u5B57\u4F53\uFF09\uFF1B\u9009\u201C\u9ED8\u8BA4\u201D\u65F6\u8DDF\u968F\u754C\u9762\u5B57\u4F53\u3002",
237
- switcherHint: "x / c \u9009\u62E9\uFF0C\u677E\u5F00 alt \u5207\u6362\uFF0CEsc \u53D6\u6D88",
249
+ switcherHint: "{prev} / {next} \u6216 \u2191 / \u2193 \u9009\u62E9\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362\uFF0CEsc \u53D6\u6D88",
238
250
  switcherCurrent: "\u5F53\u524D",
239
251
  switcherEmpty: "\u6682\u65E0\u4E0A\u6B21\u4F1A\u8BDD\uFF08\u5207\u6362\u8FC7\u4F1A\u8BDD\u540E\u53EF\u7528\uFF09",
240
252
  switcherFailed: "\u672A\u80FD\u5207\u6362\u5230\u8BE5\u4F1A\u8BDD\uFF08\u53EF\u80FD\u5DF2\u5220\u9664\uFF09",
253
+ switcherMore: "\u8FD8\u6709 {n} \u6761 \xB7 \u6309 c \u52A0\u8F7D",
254
+ switcherRunning: "\u8FD0\u884C\u4E2D",
255
+ switcherUnseen: "\u672A\u67E5\u770B",
256
+ switcherAttentionHint: "\u8FD0\u884C\u4E2D / \u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1 \xB7 {prev} / {next} \u6216 \u2191 / \u2193 \u9009\u62E9\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362",
257
+ switcherAttentionEmpty: "\u6682\u65E0\u8FD0\u884C\u4E2D\u6216\u672A\u67E5\u770B\u7684\u4EFB\u52A1",
258
+ featureNewtabPanels: "\u65B0\u6807\u7B7E\u9875\u63D2\u4EF6\u9762\u677F\u5165\u53E3",
259
+ featureNewtabPanelsDesc: "\u5B98\u65B9\u201C\u65B0\u6807\u7B7E\u9875\u201D\u53EA\u5217\u5B98\u65B9\u9762\u677F\u4E0E\u63A8\u8350\u7684\u5B98\u65B9\u63D2\u4EF6\uFF0C\u6B64\u529F\u80FD\u628A\u7B2C\u4E09\u65B9\u5E02\u573A\u63D2\u4EF6\uFF08headroom\u3001rtk \u7B49\uFF09\u7684\u9762\u677F\u5361\u7247\u52A0\u8FDB\u53BB\uFF0C\u70B9\u51FB\u5728\u4FA7\u8FB9\u9762\u677F\u6253\u5F00\u3002",
260
+ ntpDescSuffix: "\u63D2\u4EF6\u9762\u677F",
261
+ ntpNeedSession: "\u5148\u6253\u5F00\u4E00\u4E2A\u4EFB\u52A1\u4F1A\u8BDD\uFF0C\u518D\u4ECE\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u63D2\u4EF6\u9762\u677F",
262
+ tabShortcuts: "\u5FEB\u6377\u952E",
263
+ hkTitle: "\u4F1A\u8BDD\u5FEB\u6377\u5207\u6362",
264
+ hkAltZ: "\u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F7F\u7528\u7684\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362",
265
+ hkAltX: "\u5F39\u7A97\u4E2D\u4E0A\u79FB\u9AD8\u4EAE\uFF08\u66F4\u65E9\u7684\u4F1A\u8BDD\uFF09",
266
+ hkAltC: "\u5F39\u7A97\u4E2D\u4E0B\u79FB\u9AD8\u4EAE\uFF08\u66F4\u65B0\u7684\u4F1A\u8BDD\uFF09\uFF1B\u5230\u5E95\u4E14\u8FD8\u6709\u66F4\u591A\u65F6\u52A0\u8F7D 5 \u6761\u5E76\u9009\u4E2D\u65B0\u52A0\u8F7D\u7684\u7B2C\u4E00\u6761",
267
+ hkAltE: "\u6253\u5F00\u6700\u8FD1 10 \u6761\u4EFB\u52A1\u5217\u8868",
268
+ hkAltR: "\u6253\u5F00\u6B63\u5728\u8FD0\u884C\u4E0E\u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1\u5217\u8868",
269
+ hkArrow: "\u5F39\u7A97\u6253\u5F00\u65F6\u79FB\u52A8\u9AD8\u4EAE\uFF08\u4E0E alt + {prev} / alt + {next} \u540C\u89C4\u5219\uFF09",
270
+ hkEnter: "\u786E\u8BA4\u5207\u6362\u5230\u9AD8\u4EAE\u7684\u4F1A\u8BDD",
271
+ hkEsc: "\u53D6\u6D88\u5E76\u5173\u95ED\u5F39\u7A97",
272
+ hkRelease: "\u79FB\u52A8\u8FC7\u9AD8\u4EAE\u65F6\u677E\u5F00\u5373\u5207\u6362\uFF1B\u4ECE\u672A\u79FB\u52A8\u5219\u5F39\u7A97\u4FDD\u6301\u6253\u5F00\uFF0C\u7EE7\u7EED\u9009\u62E9",
273
+ hkReleaseKey: "\u677E\u5F00 alt",
274
+ hkModify: "\u4FEE\u6539",
275
+ hkRecording: "\u6309\u4E0B\u65B0\u952E\u2026",
276
+ hkInvalid: "\u5FEB\u6377\u952E\u53EA\u652F\u6301\u5355\u4E2A\u5B57\u6BCD\u6216\u6570\u5B57",
277
+ hkConflict: "alt+{k} \u5DF2\u7528\u4E8E\u201C{name}\u201D",
278
+ hkSaved: "\u5FEB\u6377\u952E\u5DF2\u66F4\u65B0",
279
+ hkNameLast: "\u5207\u6362\u4E0A\u6B21\u4F1A\u8BDD",
280
+ hkNamePrev: "\u4E0A\u79FB\u9AD8\u4EAE",
281
+ hkNameNext: "\u4E0B\u79FB\u9AD8\u4EAE",
282
+ hkNameRecent: "\u6700\u8FD1 10 \u6761",
283
+ hkNameAttention: "\u8FD0\u884C\u4E2D / \u672A\u67E5\u770B",
284
+ hkEditHint: "\u70B9\u51FB\u201C\u4FEE\u6539\u201D\u540E\u6309\u4E0B\u65B0\u6309\u952E\uFF08\u5B57\u6BCD\u6216\u6570\u5B57\uFF09\uFF0CEsc \u53D6\u6D88",
285
+ hkReset: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4",
286
+ hkResetDone: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u952E\u4F4D",
241
287
  featureFileActions: "\u6587\u4EF6\u83DC\u5355\u589E\u5F3A",
242
288
  featureFileActionsDesc: "\u5728\u4F1A\u8BDD\u4E2D\u6587\u4EF6\u94FE\u63A5\u7684\u53F3\u952E\u83DC\u5355\u91CC\u65B0\u589E\u201C\u9ED8\u8BA4\u5E94\u7528\u6253\u5F00\u201D\u4E0E\u201C\u6253\u5F00\u6240\u5728\u76EE\u5F55\u201D\u3002",
243
289
  featureImageCopy: "\u56FE\u7247\u53F3\u952E\u590D\u5236",
@@ -362,7 +408,7 @@
362
408
  headroomStoppedWarn: "\u4EE3\u7406\u5DF2\u505C\u6B62\uFF1B\u6307\u5411 127.0.0.1 \u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5",
363
409
  headroomBackend: "\u538B\u7F29\u8BBE\u5907",
364
410
  headroomBackendAuto: "\u81EA\u52A8 \xB7 \u663E\u5361\u4F18\u5148",
365
- headroomBackendCpu: "CPU \xB7 \u7701\u7535",
411
+ headroomBackendCpu: "\u7701\u7535 \xB7 CPU \u4F18\u5148",
366
412
  headroomBackendNative: "\uFF08\u539F\u751F\u503C\uFF09",
367
413
  headroomBackendDesc: "\u81EA\u52A8\uFF1Aheadroom \u9009\u6700\u5FEB\u8BBE\u5907\uFF08CUDA/MPS \u663E\u5361\u4F18\u5148\uFF09\uFF1BCPU\uFF1A\u5F3A\u5236 CPU \u538B\u7F29\uFF0C\u7701\u7535\u4E14\u4E0D\u5360\u663E\u5B58\u3002\u4E5F\u652F\u6301 headroom \u539F\u751F\u503C\uFF08onnx\u3001pytorch_mps \u7B49\uFF09\u3002\u5207\u6362\u4F1A\u91CD\u542F\u4EE3\u7406\u7ACB\u5373\u751F\u6548\u3002",
368
414
  headroomPower: "\u7701\u7535\u81EA\u52A8\u5207\u6362",
@@ -400,7 +446,7 @@
400
446
  aliasSaved: "\u522B\u540D\u5DF2\u66F4\u65B0",
401
447
  aliasCleared: "\u5DF2\u6062\u590D\u771F\u5B9E\u540D\u79F0",
402
448
  aliasHint: "\u78C1\u76D8\u76EE\u5F55\u540D\u4E0D\u53D8\uFF1A\u7EC8\u7AEF\u3001\u6587\u4EF6\u7BA1\u7406\u5668\u4E0E\u5176\u4ED6\u5F15\u7528\u771F\u5B9E\u8DEF\u5F84\u7684\u754C\u9762\u4ECD\u663E\u793A\u539F\u540D\u3002",
403
- featureProjectMenuDescNew: "\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u4E2D\u7684\u5207\u6362\u6587\u4EF6\u5939\u3001\u6253\u5F00\u6587\u4EF6\u5939\u4E0E\u590D\u5236\u8DEF\u5F84\u3002\u5B98\u65B9\u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF08\u7F16\u8F91\u9879\u76EE\uFF09\uFF0C\u81EA\u5B9A\u4E49\u522B\u540D\u5DF2\u505C\u7528\u3002",
449
+ featureProjectMenuDescNew: "\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u4E2D\u7684\u6253\u5F00\u6587\u4EF6\u5939\u4E0E\u590D\u5236\u8DEF\u5F84\u3002\u5B98\u65B9\u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF08\u7F16\u8F91\u9879\u76EE\uFF09\uFF0C\u81EA\u5B9A\u4E49\u522B\u540D\u5DF2\u505C\u7528\u3002",
404
450
  aliasMigrateTitle: "\u8FC1\u79FB\u9879\u76EE\u522B\u540D",
405
451
  aliasMigrateDesc: "\u5F53\u524D ZCode \u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF08\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355 \u2192 \u7F16\u8F91\u9879\u76EE\uFF09\uFF0C\u81EA\u5B9A\u4E49\u522B\u540D\u529F\u80FD\u5DF2\u505C\u7528\u3002\u53EF\u628A\u5DF2\u4FDD\u5B58\u7684\u522B\u540D\u5199\u5165\u5B98\u65B9\u9879\u76EE\u540D\u79F0\uFF0C\u52FE\u9009\u8981\u8FC1\u79FB\u7684\u9879\u76EE\uFF1B\u8FC1\u79FB\u53EA\u6539\u9879\u76EE\u540D\u79F0\uFF0C\u78C1\u76D8\u76EE\u5F55\u4E0D\u53D7\u5F71\u54CD\u3002",
406
452
  aliasMigrateItemHint: "\u8FC1\u79FB\u540E\u5C06\u4F5C\u4E3A\u9879\u76EE\u540D\u79F0\u4FDD\u5B58\uFF08\u53EF\u968F\u65F6\u5728\u201C\u7F16\u8F91\u9879\u76EE\u201D\u4E2D\u518D\u6539\uFF09\u3002\u4EC5\u65E0\u6807\u8BB0\u7684\u6761\u76EE\u53EF\u52FE\u9009\uFF1B\u5DF2\u8FC1\u79FB\u3001\u5DF2\u5E76\u5165\u3001\u9879\u76EE\u5DF2\u5220\u9664\u3001\u672C\u673A\u4E0D\u5B58\u5728\uFF08\u65E7\u673A\u5668\u8DEF\u5F84\uFF09\u4EE5\u53CA\u9879\u76EE\u5DF2\u91CD\u547D\u540D\u7684\u90FD\u4E0D\u53EF\u52FE\u9009\u2014\u2014\u8981\u6539\u8FD9\u4E9B\u9879\u76EE\u7684\u540D\u5B57\uFF0C\u76F4\u63A5\u7528\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u91CC\u7684\u201C\u7F16\u8F91\u9879\u76EE\u201D\u3002",
@@ -568,7 +614,7 @@
568
614
  featureTaskOrder: "Sidebar session drag ordering",
569
615
  featureTaskOrderDesc: "Makes session drags in Pinned, Projects and Groups persist across refreshes.",
570
616
  featureSessionSwitch: "Session quick switch",
571
- featureSessionSwitchDesc: "alt+z toggles between the current and the last session; hold alt and press x/c to move the highlight across recently used sessions, release alt to switch (like alt+tab).",
617
+ featureSessionSwitchDesc: "{last} toggles between the current and the last session; hold alt and press {prev}/{next} to move the highlight across recently used sessions (10 shown initially, pressing {next} at the bottom loads 5 more), {recent} opens the last 10 directly, {attention} opens running / finished-but-unseen tasks; arrow keys also move the highlight, Enter or release alt to switch (like alt+tab)",
572
618
  featureTitleLock: "Keep renamed session titles",
573
619
  featureTitleLockDesc: "Sessions you renamed keep their custom title: after new replies the app may show an auto-generated title instead, which reverts after a restart; this keeps the renamed title in the sidebar.",
574
620
  tabTitleGen: "Task name prompt",
@@ -596,7 +642,7 @@
596
642
  titleGenTestOk: "Result: ",
597
643
  titleGenTestFailed: "Generation failed",
598
644
  featureWsRunning: "Running indicator when collapsed",
599
- featureWsRunningDesc: "Shows the same spinning loader as running tasks on collapsed projects and on section headers (\u201CTasks\u201D, \u201CPinned\u201D) while any session is running; clears when they all finish. Before ZCode 3.16.1 the project is briefly re-expanded in the background every 60 s to re-check.",
645
+ featureWsRunningDesc: "Shows the same spinning loader as running tasks on collapsed projects and on section headers (\u201CTasks\u201D, \u201CPinned\u201D) while any session is running; clears when they all finish.",
600
646
  tabPerformance: "Performance",
601
647
  perfDesc: "ZCode keeps a session process resident for every project you have opened and never reclaims it, so memory grows with projects. When enabled, projects unused for a while with nothing running release their memory automatically: sessions are untouched and reload when reopened, with a few seconds wait on the first reply. The project in use, running sessions, background commands and scheduled tasks are never reclaimed. Background-command detection reads system process info via built-in tools (Linux / macOS / Windows); when unavailable, the feature disables itself.",
602
648
  featureIdleReclaim: "Idle project memory reclaim",
@@ -624,10 +670,44 @@
624
670
  userFontDesc: 'Font of question text in conversations; "default" follows the UI font.',
625
671
  assistantFontName: "Answer font",
626
672
  assistantFontDesc: 'Font of answer text in conversations (code blocks stay monospaced); "default" follows the UI font.',
627
- switcherHint: "x / c to choose, release alt to switch, Esc to cancel",
673
+ switcherHint: "{prev} / {next} or \u2191 / \u2193 to choose, Enter or release alt to switch, Esc to cancel",
628
674
  switcherCurrent: "current",
629
675
  switcherEmpty: "No previous session yet (available after you switch sessions)",
630
676
  switcherFailed: "Failed to switch to that session (it may have been deleted)",
677
+ switcherMore: "{n} more \xB7 press c to load",
678
+ switcherRunning: "running",
679
+ switcherUnseen: "unseen",
680
+ switcherAttentionHint: "Running / finished but unseen tasks \xB7 {prev} / {next} or \u2191 / \u2193 to choose, Enter or release alt to switch",
681
+ switcherAttentionEmpty: "No running or unseen tasks",
682
+ featureNewtabPanels: "Plugin panels on the new tab page",
683
+ featureNewtabPanelsDesc: 'The built-in "new tab page" only lists built-in panels and recommended official plugins; this adds cards for third-party marketplace plugin panels (headroom, rtk, \u2026) that open in the side pane.',
684
+ ntpDescSuffix: "plugin panel",
685
+ ntpNeedSession: "Open a task session first, then open plugin panels from the new tab page",
686
+ tabShortcuts: "Shortcuts",
687
+ hkTitle: "Session quick switch",
688
+ hkAltZ: "Toggle between the current and the last-used session",
689
+ hkAltX: "Move the highlight up (earlier sessions) in the popup",
690
+ hkAltC: "Move the highlight down (recent sessions); at the bottom it loads 5 more and highlights the first newly loaded entry",
691
+ hkAltE: "Open the last 10 tasks",
692
+ hkAltR: "Open running / finished-but-unseen tasks",
693
+ hkArrow: "Move the highlight while the popup is open (same rules as alt + {prev} / alt + {next})",
694
+ hkEnter: "Switch to the highlighted session",
695
+ hkEsc: "Cancel and close the popup",
696
+ hkRelease: "Releasing alt switches once the highlight has moved; otherwise the popup stays open for further selection",
697
+ hkReleaseKey: "release alt",
698
+ hkModify: "Change",
699
+ hkRecording: "press a key\u2026",
700
+ hkInvalid: "Shortcuts accept a single letter or digit only",
701
+ hkConflict: 'alt+{k} is already used by "{name}"',
702
+ hkSaved: "Shortcut updated",
703
+ hkNameLast: "last session",
704
+ hkNamePrev: "move up",
705
+ hkNameNext: "move down",
706
+ hkNameRecent: "last 10 tasks",
707
+ hkNameAttention: "running / unseen",
708
+ hkEditHint: 'Click "Change", press a new key (letter or digit); Esc cancels',
709
+ hkReset: "Reset to defaults",
710
+ hkResetDone: "Shortcuts reset to defaults",
631
711
  featureFileActions: "File menu actions",
632
712
  featureFileActionsDesc: 'Adds "Open with default app" and "Reveal in file manager" to the right-click menu of file links in chat.',
633
713
  featureImageCopy: "Image right-click copy",
@@ -752,7 +832,7 @@
752
832
  headroomStoppedWarn: "Proxy stopped; providers pointing at 127.0.0.1 cannot connect.",
753
833
  headroomBackend: "Compression device",
754
834
  headroomBackendAuto: "Auto \xB7 GPU first",
755
- headroomBackendCpu: "CPU \xB7 power saving",
835
+ headroomBackendCpu: "Power saving \xB7 CPU first",
756
836
  headroomBackendNative: " (native)",
757
837
  headroomBackendDesc: "Auto lets headroom pick the fastest device (CUDA/MPS); CPU forces CPU compression (power saving, no VRAM). Native headroom values (onnx, pytorch_mps, \u2026) also work. Switching restarts the proxy to apply.",
758
838
  headroomPower: "Power-save auto switch",
@@ -790,7 +870,7 @@
790
870
  aliasSaved: "Alias updated.",
791
871
  aliasCleared: "Real name restored.",
792
872
  aliasHint: "The directory name on disk is unchanged: terminals, file managers and other path-based UI still show the real name.",
793
- featureProjectMenuDescNew: 'Switch folder, open folder and copy path in the project "More" menu. Renaming is built in since ZCode 3.16.1 ("Edit project"), so the custom alias is retired.',
873
+ featureProjectMenuDescNew: 'Open folder and copy path in the project "More" menu. Renaming is built in since ZCode 3.16.1 ("Edit project"), so the custom alias is retired.',
794
874
  aliasMigrateTitle: "Migrate project aliases",
795
875
  aliasMigrateDesc: 'This ZCode supports renaming projects (project "More" menu \u2192 Edit project), so the custom alias feature is retired. Write your saved aliases into the official project names by picking the projects below; only the project name changes, folders on disk are untouched.',
796
876
  aliasMigrateItemHint: 'The alias becomes the project name (re-editable anytime via "Edit project"). Only unmarked entries can be checked; migrated, merged, deleted, not-on-this-machine and manually-renamed ones cannot \u2014 to rename those projects, use "Edit project" in the project "More" menu directly.',
@@ -1601,6 +1681,20 @@
1601
1681
  background-color: color-mix(in oklab, var(--color-primary, #111) 12%, transparent);
1602
1682
  color: var(--color-foreground-subtle, #666);
1603
1683
  }
1684
+ /* attention \u5F39\u7A97\uFF08alt+r\uFF09\u7684\u884C\u72B6\u6001\u6807\u7B7E\uFF1A\u8FD0\u884C\u4E2D\u7EFF\u3001\u672A\u67E5\u770B\u7425\u73C0 */
1685
+ .zcodepro-switcher-tag-run {
1686
+ background-color: color-mix(in oklab, var(--color-emerald-500, #10b981) 20%, transparent);
1687
+ }
1688
+ .zcodepro-switcher-tag-new {
1689
+ background-color: color-mix(in oklab, var(--color-amber-500, #f59e0b) 22%, transparent);
1690
+ }
1691
+ /* \u5217\u8868\u5E95\u90E8\u201C\u8FD8\u6709 N \u6761\u201D\u63D0\u793A\u884C */
1692
+ .zcodepro-switcher-more {
1693
+ padding: 6px 12px 2px;
1694
+ font-size: 11px;
1695
+ text-align: center;
1696
+ color: var(--color-muted-foreground, #888);
1697
+ }
1604
1698
  .zcodepro-switcher-ws {
1605
1699
  font-size: 11px;
1606
1700
  color: var(--color-muted-foreground, #888);
@@ -2756,10 +2850,10 @@
2756
2850
  function startImageMenu() {
2757
2851
  if (typeof window === "undefined") return;
2758
2852
  if (typeof navigator.clipboard?.write !== "function" || typeof window.ClipboardItem === "undefined") return;
2759
- let enabled6 = true;
2853
+ let enabled7 = true;
2760
2854
  const refresh = () => {
2761
2855
  void getConfig().then((c) => {
2762
- enabled6 = !(c.features && c.features.imageCopy === false);
2856
+ enabled7 = !(c.features && c.features.imageCopy === false);
2763
2857
  });
2764
2858
  };
2765
2859
  refresh();
@@ -2768,7 +2862,7 @@
2768
2862
  const img = copyTargetOf(e.target, e.clientX, e.clientY);
2769
2863
  if (!img) return;
2770
2864
  e.preventDefault();
2771
- if (enabled6) showMenu(e.clientX, e.clientY, img);
2865
+ if (enabled7) showMenu(e.clientX, e.clientY, img);
2772
2866
  refresh();
2773
2867
  }, true);
2774
2868
  }
@@ -3845,6 +3939,32 @@
3845
3939
  }
3846
3940
  pluginsStatusLine.textContent = n > 0 ? L.pluginsUpdatesFound.replaceAll("{n}", String(n)) : L.pluginsUpToDate;
3847
3941
  })();
3942
+ const HK_ACTIONS = [
3943
+ ["switchLast", "hkAltZ"],
3944
+ ["switchPrev", "hkAltX"],
3945
+ ["switchNext", "hkAltC"],
3946
+ ["switchRecent", "hkAltE"],
3947
+ ["switchAttention", "hkAltR"]
3948
+ ];
3949
+ const hkNames = () => ({
3950
+ switchLast: L.hkNameLast,
3951
+ switchPrev: L.hkNamePrev,
3952
+ switchNext: L.hkNameNext,
3953
+ switchRecent: L.hkNameRecent,
3954
+ switchAttention: L.hkNameAttention
3955
+ });
3956
+ const hkKeys = () => {
3957
+ const sc = config.shortcuts || {};
3958
+ const keys = {};
3959
+ for (const [a] of HK_ACTIONS) {
3960
+ keys[a] = /^[a-z0-9]$/.test(sc[a]) ? sc[a] : DEFAULT_SHORTCUTS[a];
3961
+ }
3962
+ return keys;
3963
+ };
3964
+ const sessionSwitchDesc = () => {
3965
+ const k = hkKeys();
3966
+ return L.featureSessionSwitchDesc.replaceAll("{last}", "alt+" + k.switchLast).replaceAll("{prev}", k.switchPrev).replaceAll("{next}", k.switchNext).replaceAll("{recent}", "alt+" + k.switchRecent).replaceAll("{attention}", "alt+" + k.switchAttention);
3967
+ };
3848
3968
  const refreshRows = () => {
3849
3969
  const f = config.features || {};
3850
3970
  rows.replaceChildren(
@@ -3860,7 +3980,7 @@
3860
3980
  if (await setFeature("taskOrder", next)) f.taskOrder = next;
3861
3981
  refreshRows();
3862
3982
  }),
3863
- settingRow(L.featureSessionSwitch, L.featureSessionSwitchDesc, f.sessionSwitch !== false, async () => {
3983
+ settingRow(L.featureSessionSwitch, sessionSwitchDesc(), f.sessionSwitch !== false, async () => {
3864
3984
  const next = !(f.sessionSwitch !== false);
3865
3985
  if (await setFeature("sessionSwitch", next)) f.sessionSwitch = next;
3866
3986
  refreshRows();
@@ -3890,6 +4010,11 @@
3890
4010
  if (await setFeature("wsRunningSpin", next)) f.wsRunningSpin = next;
3891
4011
  refreshRows();
3892
4012
  }),
4013
+ settingRow(L.featureNewtabPanels, L.featureNewtabPanelsDesc, f.newtabPanels !== false, async () => {
4014
+ const next = !(f.newtabPanels !== false);
4015
+ if (await setFeature("newtabPanels", next)) f.newtabPanels = next;
4016
+ refreshRows();
4017
+ }),
3893
4018
  (() => {
3894
4019
  const repoLink = h("span", {
3895
4020
  class: "cursor-pointer underline-offset-4 hover:underline",
@@ -3972,6 +4097,127 @@
3972
4097
  h("div", {}, rows),
3973
4098
  communityCards
3974
4099
  );
4100
+ let hkRecording = null;
4101
+ let hkKeyHandler = null;
4102
+ const hkCard = h("div", { class: "divide-y divide-border rounded-xl border border-border" });
4103
+ const hkCell = (keyLabel, desc, extra) => h(
4104
+ "div",
4105
+ { class: "flex items-start gap-3 px-3 py-2.5" },
4106
+ h("kbd", {
4107
+ class: "shrink-0 min-w-20 rounded-md border border-border px-2 py-0.5 text-center text-ui-xs text-foreground-subtle",
4108
+ style: "font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.5;"
4109
+ }, keyLabel),
4110
+ h("div", { class: "min-w-0 flex-1 text-ui-sm text-foreground-subtle" }, desc),
4111
+ extra ? h("div", { class: "ml-auto shrink-0 pl-3" }, extra) : null
4112
+ );
4113
+ const renderShortcuts = () => {
4114
+ const keys = hkKeys();
4115
+ const names = hkNames();
4116
+ hkCard.replaceChildren(
4117
+ ...HK_ACTIONS.map(([a, descKey]) => {
4118
+ const recording = hkRecording === a;
4119
+ return hkCell(
4120
+ recording ? L.hkRecording : "alt + " + keys[a],
4121
+ L[descKey],
4122
+ btnSmall(recording ? L.hkRecording : L.hkModify, () => {
4123
+ recording ? stopHkRecording() : startHkRecording(a);
4124
+ })
4125
+ );
4126
+ }),
4127
+ hkCell("\u2191 / \u2193", L.hkArrow.replaceAll("{prev}", keys.switchPrev).replaceAll("{next}", keys.switchNext)),
4128
+ hkCell("Enter", L.hkEnter),
4129
+ hkCell("Esc", L.hkEsc),
4130
+ hkCell(L.hkReleaseKey, L.hkRelease)
4131
+ );
4132
+ };
4133
+ const stopHkRecording = () => {
4134
+ hkRecording = null;
4135
+ if (hkKeyHandler) {
4136
+ document.removeEventListener("keydown", hkKeyHandler, true);
4137
+ hkKeyHandler = null;
4138
+ }
4139
+ renderShortcuts();
4140
+ };
4141
+ const startHkRecording = (action) => {
4142
+ hkRecording = action;
4143
+ if (!hkKeyHandler) {
4144
+ const myOverlay = document.querySelector(".zcodepro-overlay");
4145
+ hkKeyHandler = (e) => {
4146
+ if (!myOverlay || !myOverlay.isConnected) {
4147
+ stopHkRecording();
4148
+ return;
4149
+ }
4150
+ if (e.isComposing) return;
4151
+ e.preventDefault();
4152
+ e.stopImmediatePropagation();
4153
+ if (e.key === "Escape") {
4154
+ stopHkRecording();
4155
+ return;
4156
+ }
4157
+ const k = e.key.toLowerCase();
4158
+ if (!/^[a-z0-9]$/.test(k)) {
4159
+ showToast(L.hkInvalid, "error");
4160
+ return;
4161
+ }
4162
+ const keys = hkKeys();
4163
+ const names = hkNames();
4164
+ const conflict = HK_ACTIONS.find(([a2]) => a2 !== hkRecording && keys[a2] === k);
4165
+ if (conflict) {
4166
+ showToast(L.hkConflict.replaceAll("{k}", k).replaceAll("{name}", names[conflict[0]]), "error");
4167
+ return;
4168
+ }
4169
+ const a = hkRecording;
4170
+ stopHkRecording();
4171
+ void (async () => {
4172
+ const res = await rpc("/config", { method: "POST", body: { shortcuts: { [a]: k } } });
4173
+ clearConfigCache();
4174
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
4175
+ if (!res.ok) {
4176
+ showToast(L.failed + ": " + errText(res), "error");
4177
+ renderShortcuts();
4178
+ return;
4179
+ }
4180
+ if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
4181
+ showToast(L.hkSaved, "success");
4182
+ refreshRows();
4183
+ renderShortcuts();
4184
+ })();
4185
+ };
4186
+ document.addEventListener("keydown", hkKeyHandler, true);
4187
+ }
4188
+ renderShortcuts();
4189
+ };
4190
+ renderShortcuts();
4191
+ const resetShortcuts = async () => {
4192
+ stopHkRecording();
4193
+ const all = {};
4194
+ for (const [a] of HK_ACTIONS) all[a] = DEFAULT_SHORTCUTS[a];
4195
+ const res = await rpc("/config", { method: "POST", body: { shortcuts: all } });
4196
+ clearConfigCache();
4197
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
4198
+ if (!res.ok) {
4199
+ showToast(L.failed + ": " + errText(res), "error");
4200
+ return;
4201
+ }
4202
+ if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
4203
+ showToast(L.hkResetDone, "success");
4204
+ refreshRows();
4205
+ renderShortcuts();
4206
+ };
4207
+ const paneShortcuts = h(
4208
+ "div",
4209
+ { role: "tabpanel", class: "mt-4", style: "display:none" },
4210
+ h("div", { class: "mb-2 text-ui-sm font-medium" }, L.hkTitle),
4211
+ h(
4212
+ "div",
4213
+ { class: "mb-2 flex items-center gap-2" },
4214
+ h("div", { class: "min-w-0 flex-1 text-ui-xs/relaxed text-foreground-subtle" }, L.hkEditHint),
4215
+ h("div", { class: "shrink-0" }, btnSmall(L.hkReset, () => {
4216
+ void resetShortcuts();
4217
+ }))
4218
+ ),
4219
+ hkCard
4220
+ );
3975
4221
  const paneStyles = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3976
4222
  const panePerf = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3977
4223
  const paneAgents = h("div", { role: "tabpanel", class: "mt-2", style: "display:none" });
@@ -3982,9 +4228,10 @@
3982
4228
  const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3983
4229
  const paneTitleGen = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3984
4230
  const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3985
- const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
4231
+ const panes = { features: paneFeatures, shortcuts: paneShortcuts, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
3986
4232
  const tabDefs = [
3987
4233
  ["features", L.tabFeatures],
4234
+ ["shortcuts", L.tabShortcuts],
3988
4235
  ["agents", L.tabBehavior],
3989
4236
  ["styles", L.tabStyles],
3990
4237
  ["perf", L.tabPerformance],
@@ -4008,6 +4255,7 @@
4008
4255
  }, label)));
4009
4256
  const switchTab = (name) => {
4010
4257
  activeTab = name;
4258
+ if (name !== "shortcuts" && hkRecording) stopHkRecording();
4011
4259
  for (const [id, pane] of Object.entries(panes)) pane.style.display = id === name ? "" : "none";
4012
4260
  renderTabs();
4013
4261
  };
@@ -6077,6 +6325,7 @@
6077
6325
  statusLine,
6078
6326
  tablist,
6079
6327
  paneFeatures,
6328
+ paneShortcuts,
6080
6329
  paneAgents,
6081
6330
  paneStyles,
6082
6331
  panePerf,
@@ -6169,1318 +6418,1634 @@
6169
6418
  }
6170
6419
  }
6171
6420
 
6172
- // src/inject/features/session-switch.js
6173
- var MRU_LIMIT = 50;
6421
+ // src/inject/features/ws-running.js
6422
+ var installed2 = false;
6423
+ var enabled2 = true;
6174
6424
  var SCAN_DEBOUNCE_MS = 150;
6175
6425
  var SCAN_FALLBACK_MS = 5e3;
6176
- var mru = [];
6177
- var current = null;
6178
- var titles = /* @__PURE__ */ new Map();
6179
- var popupEl = null;
6180
- var aliasesCache = {};
6181
- var highlight = 0;
6182
- var scanDebounce = 0;
6183
- function firstLineOf(item) {
6184
- const tEl = item.querySelector('[class*="truncate"]');
6185
- return (tEl ? tEl.textContent : (item.innerText || "").split("\n")[0])?.trim() || "";
6426
+ var PEEK_FIRST_MS = 2e4;
6427
+ var PEEK_REPEAT_MS = 6e4;
6428
+ var PEEK_SETTLE_MS = 160;
6429
+ var PEEK_MAX_MS = 1500;
6430
+ var PEEK_UNMOUNT_WAIT_MS = 800;
6431
+ var CONFIG_POLL_MIN_MS = 5e3;
6432
+ var CONFIG_POLL_MAX_MS = 6e4;
6433
+ var TASK_SEL = "[data-task-item-key]";
6434
+ var WS_SEL = () => projectRowSelector();
6435
+ function findWsRow(ws) {
6436
+ return findProjectRow(ws);
6437
+ }
6438
+ var LOADER_FALLBACK_PATHS = [
6439
+ "M12 2v4",
6440
+ "m16.2 7.8 2.9-2.9",
6441
+ "M18 12h4",
6442
+ "m19.8 16.2-2.9 2.9",
6443
+ "M12 18v4",
6444
+ "m7.8 19.8-2.9-2.9",
6445
+ "M6 12H2",
6446
+ "m4.2 7.8 2.9-2.9"
6447
+ ];
6448
+ function makeLoaderSvg(baseClass) {
6449
+ const ns = "http://www.w3.org/2000/svg";
6450
+ const proto = document.querySelector("svg.lucide-loader");
6451
+ if (proto) {
6452
+ const clone = proto.cloneNode(true);
6453
+ const origClass = proto.getAttribute("class") || "";
6454
+ const keep = origClass.split(/\s+/).filter((c) => /^(size-|text-)/.test(c)).join(" ");
6455
+ clone.setAttribute("class", ((keep || baseClass) + " animate-spin zcodepro-ws-loader").trim());
6456
+ return clone;
6457
+ }
6458
+ const svg = document.createElementNS(ns, "svg");
6459
+ svg.setAttribute("viewBox", "0 0 24 24");
6460
+ svg.setAttribute("fill", "none");
6461
+ svg.setAttribute("stroke", "currentColor");
6462
+ svg.setAttribute("stroke-width", "2");
6463
+ svg.setAttribute("stroke-linecap", "round");
6464
+ svg.setAttribute("stroke-linejoin", "round");
6465
+ svg.setAttribute("class", (baseClass + " animate-spin zcodepro-ws-loader").trim());
6466
+ for (const d of LOADER_FALLBACK_PATHS) {
6467
+ const path = document.createElementNS(ns, "path");
6468
+ path.setAttribute("d", d);
6469
+ svg.append(path);
6470
+ }
6471
+ return svg;
6472
+ }
6473
+ function setRunningIcon(holder, on) {
6474
+ if (!holder) return;
6475
+ if (on) {
6476
+ if (holder.querySelector(":scope > svg.zcodepro-ws-loader")) return;
6477
+ const orig0 = holder.querySelector(":scope > svg:not(.zcodepro-ws-loader)");
6478
+ const base = (orig0 ? orig0.getAttribute("class") || "" : "size-4").replace(/lucide-[a-z-]*/g, "").trim() || "size-4";
6479
+ holder.append(makeLoaderSvg(base));
6480
+ } else {
6481
+ holder.querySelectorAll(":scope > svg.zcodepro-ws-loader").forEach((el) => el.remove());
6482
+ }
6483
+ }
6484
+ function iconHolderOf(row) {
6485
+ if (row.hasAttribute && row.hasAttribute("data-project-id")) {
6486
+ const toggle2 = row.querySelector('[data-testid="virtual-project-toggle"]');
6487
+ return toggle2 ? toggle2.querySelector(":scope > span") : null;
6488
+ }
6489
+ return row.firstElementChild && row.firstElementChild.querySelector(":scope > span");
6490
+ }
6491
+ function isRunningRow(el) {
6492
+ const scope = el.firstElementChild;
6493
+ if (!scope) return false;
6494
+ const svg = scope.querySelector('svg[class*="lucide-loader"]');
6495
+ return !!svg;
6496
+ }
6497
+ var runningByWs = /* @__PURE__ */ new Map();
6498
+ var peekTimers = /* @__PURE__ */ new Map();
6499
+ var activePeeks = /* @__PURE__ */ new Set();
6500
+ var peekedWs = /* @__PURE__ */ new Set();
6501
+ var wsWithRows = /* @__PURE__ */ new Set();
6502
+ var servicesRef = null;
6503
+ function findServices() {
6504
+ if (servicesRef) return servicesRef;
6505
+ try {
6506
+ const rootEl = document.getElementById("root");
6507
+ const containerKey = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
6508
+ if (!containerKey) return null;
6509
+ const seen = /* @__PURE__ */ new Set();
6510
+ let found = null;
6511
+ const scanObj = (o) => {
6512
+ if (!o || typeof o !== "object" || seen.has(o) || found) return;
6513
+ seen.add(o);
6514
+ const ts = o.zcodeTaskService;
6515
+ if (ts && typeof ts === "object" && typeof ts.releaseWorkspacePreparation === "function") found = o;
6516
+ };
6517
+ let visited = 0;
6518
+ const visit = (f) => {
6519
+ if (!f || found || visited > 6e4) return;
6520
+ visited++;
6521
+ for (const holder of [f.memoizedProps, f.memoizedState]) {
6522
+ if (!holder || typeof holder !== "object") continue;
6523
+ scanObj(holder);
6524
+ for (const v of Object.values(holder)) {
6525
+ if (v && typeof v === "object") scanObj(v);
6526
+ }
6527
+ }
6528
+ visit(f.child);
6529
+ visit(f.sibling);
6530
+ };
6531
+ visit(rootEl[containerKey]);
6532
+ if (found) servicesRef = found;
6533
+ return found;
6534
+ } catch {
6535
+ return null;
6536
+ }
6537
+ }
6538
+ async function authoritativeIdle(ws) {
6539
+ const services = findServices();
6540
+ const ts = services && services.zcodeTaskService;
6541
+ if (!ts || typeof ts.listActiveTaskIds !== "function") return null;
6542
+ try {
6543
+ const base = String(ws).replace(/[\\/]+$/, "");
6544
+ const a = await ts.listActiveTaskIds({ workspacePath: base });
6545
+ const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6546
+ const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6547
+ if (!Array.isArray(a) || !Array.isArray(b)) return null;
6548
+ if (!ids.length) return true;
6549
+ const st = await rpc("/task-statuses?ids=" + encodeURIComponent(ids.join(","))).catch(() => null);
6550
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6551
+ const live = (id) => {
6552
+ const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6553
+ if (rec === void 0) return true;
6554
+ if (!TERMINAL.has(rec.status)) return true;
6555
+ return Date.now() - rec.updatedAt < 3e5;
6556
+ };
6557
+ return !ids.some(live);
6558
+ } catch {
6559
+ return null;
6560
+ }
6186
6561
  }
6187
6562
  function workspaceOf(key) {
6188
6563
  const i = key.indexOf(":sess_");
6189
6564
  return i > 0 ? key.slice(0, i) : key;
6190
6565
  }
6191
- function findCurrent() {
6192
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6566
+ function wsHead(row) {
6567
+ return row.matches("[aria-expanded]") ? row : row.querySelector("[aria-expanded]");
6568
+ }
6569
+ function domRowsOf(ws) {
6570
+ return [...document.querySelectorAll(TASK_SEL)].filter((e) => workspaceOf(e.getAttribute("data-task-item-key") || "") === ws);
6571
+ }
6572
+ function currentTaskRow() {
6573
+ for (const el of document.querySelectorAll(TASK_SEL)) {
6193
6574
  if ((el.className + "").includes("bg-selected")) return el;
6194
6575
  }
6195
6576
  return null;
6196
6577
  }
6197
- function record(key) {
6198
- if (key && key !== current) {
6199
- current = key;
6200
- mru = mru.filter((k) => k !== key);
6201
- mru.unshift(key);
6202
- if (mru.length > MRU_LIMIT) mru.length = MRU_LIMIT;
6578
+ var synthClick = (el) => {
6579
+ try {
6580
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6581
+ } catch {
6203
6582
  }
6583
+ };
6584
+ function toggleNavSafe(head) {
6585
+ const cur = currentTaskRow();
6586
+ const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6587
+ synthClick(head);
6588
+ if (!backKey) return;
6589
+ const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL}[data-task-item-key="${CSS.escape(backKey)}"]`);
6590
+ if (back) synthClick(back);
6204
6591
  }
6205
6592
  function scan() {
6206
- const cur = findCurrent();
6207
- if (cur) record(cur.getAttribute("data-task-item-key"));
6208
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6209
- titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
6593
+ wsWithRows = /* @__PURE__ */ new Set();
6594
+ for (const el of document.querySelectorAll(TASK_SEL)) {
6595
+ const key = el.getAttribute("data-task-item-key") || "";
6596
+ const ws = workspaceOf(key);
6597
+ if (!ws) continue;
6598
+ wsWithRows.add(ws);
6599
+ const run = isRunningRow(el);
6600
+ let set = runningByWs.get(ws);
6601
+ if (run) {
6602
+ if (!set) {
6603
+ set = /* @__PURE__ */ new Set();
6604
+ runningByWs.set(ws, set);
6605
+ }
6606
+ set.add(key);
6607
+ } else if (set) {
6608
+ set.delete(key);
6609
+ }
6210
6610
  }
6611
+ syncPeeks();
6612
+ apply();
6211
6613
  }
6212
- var rowSel2 = "[data-task-item-key]";
6213
- var inTaskRow = (node) => node instanceof Element && !!node.closest && !!node.closest(rowSel2);
6214
- function taskRowMutations(muts) {
6215
- for (const m of muts) {
6216
- if (m.type === "attributes") {
6217
- if (inTaskRow(m.target)) return true;
6218
- } else if (m.type === "characterData") {
6219
- if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
6220
- } else if (inTaskRow(m.target)) {
6221
- return true;
6222
- } else {
6223
- for (const n of m.addedNodes) {
6224
- if (n instanceof Element && (n.matches(rowSel2) || !!n.querySelector(rowSel2))) return true;
6225
- }
6614
+ function handleRemovals(muts) {
6615
+ const removed = [];
6616
+ for (const m of muts) {
6617
+ if (m.type !== "childList") continue;
6618
+ for (const n of m.removedNodes) {
6619
+ if (!(n instanceof Element)) continue;
6620
+ if (n.matches(TASK_SEL)) removed.push(n);
6621
+ else for (const r of n.querySelectorAll(TASK_SEL)) removed.push(r);
6226
6622
  }
6227
6623
  }
6228
- return false;
6229
- }
6230
- function scheduleScan() {
6231
- if (scanDebounce) return;
6232
- scanDebounce = setTimeout(() => {
6233
- scanDebounce = 0;
6234
- scan();
6235
- }, SCAN_DEBOUNCE_MS);
6236
- }
6237
- async function switchTo(key) {
6238
- const find = () => document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
6239
- let item = find();
6240
- if (!item) {
6624
+ if (!removed.length) return;
6625
+ const infos = removed.map((el) => {
6626
+ const key = el.getAttribute("data-task-item-key") || "";
6241
6627
  const ws = workspaceOf(key);
6242
- const header = findProjectRow(ws);
6243
- const expander = header && header.querySelector("[aria-expanded]");
6244
- if (expander && expander.getAttribute("aria-expanded") === "false") {
6245
- expander.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6246
- for (let i = 0; i < 20 && !(item = find()); i++) await new Promise((r) => setTimeout(r, 100));
6628
+ return key && ws ? { key, ws, run: isRunningRow(el) } : null;
6629
+ }).filter(Boolean);
6630
+ if (!infos.length) return;
6631
+ setTimeout(() => {
6632
+ for (const info of infos) {
6633
+ const row = findWsRow(info.ws);
6634
+ const head = row && wsHead(row);
6635
+ const collapsed = !row || !head || head.getAttribute("aria-expanded") === "false";
6636
+ let set = runningByWs.get(info.ws);
6637
+ if (collapsed && info.run) {
6638
+ if (!set) {
6639
+ set = /* @__PURE__ */ new Set();
6640
+ runningByWs.set(info.ws, set);
6641
+ }
6642
+ set.add(info.key);
6643
+ } else if (set) {
6644
+ set.delete(info.key);
6645
+ }
6247
6646
  }
6248
- }
6249
- if (!item) return false;
6250
- item.scrollIntoView({ block: "nearest" });
6251
- item.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6252
- return true;
6647
+ syncPeeks();
6648
+ apply();
6649
+ }, 0);
6253
6650
  }
6254
- function closePopup(commit) {
6255
- if (!popupEl) return;
6256
- const idx = highlight;
6257
- popupEl.remove();
6258
- popupEl = null;
6259
- if (commit && idx > 0 && mru[idx] && mru[idx] !== current) {
6260
- void switchTo(mru[idx]).then((ok) => {
6261
- if (!ok) showToast(t().switcherFailed, "error");
6262
- });
6651
+ function apply() {
6652
+ const seen = /* @__PURE__ */ new Set();
6653
+ for (const row of document.querySelectorAll(WS_SEL())) {
6654
+ const path = rowPath(row);
6655
+ if (!path) continue;
6656
+ seen.add(path);
6657
+ const head = wsHead(row);
6658
+ const collapsed = !head || head.getAttribute("aria-expanded") === "false";
6659
+ const domRunning = !!(runningByWs.get(path) && runningByWs.get(path).size);
6660
+ const normP = (v) => String(v || "").replace(/[\\/]+$/, "");
6661
+ const running = enabled2 && collapsed && (activeProjectPaths.size ? [...activeProjectPaths].some((p2) => normP(p2) === normP(path)) : domRunning);
6662
+ const icon = iconHolderOf(row);
6663
+ icon?.classList.toggle("zcodepro-ws-running", running);
6664
+ setRunningIcon(icon, running);
6263
6665
  }
6666
+ applySectionSpin();
6667
+ for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
6264
6668
  }
6265
- function findTitle(key) {
6266
- const item = document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
6267
- if (item) titles.set(key, firstLineOf(item));
6268
- const id = key.slice(key.indexOf(":sess_") + 1);
6269
- return titles.get(key) || `sess ${id.slice(0, 8)}`;
6669
+ var headInfoCache = /* @__PURE__ */ new WeakMap();
6670
+ var PROJECT_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u9879\u76EE", "projects"]);
6671
+ var TASK_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u4EFB\u52A1", "tasks"]);
6672
+ function kindFromHeadText(head) {
6673
+ const text = (head.textContent || "").trim().toLowerCase();
6674
+ if (PROJECT_HEAD_TEXTS.has(text)) return "project";
6675
+ if (TASK_HEAD_TEXTS.has(text)) return "task";
6676
+ return null;
6270
6677
  }
6271
- function projLabelOf(key, aliases2) {
6272
- const ws = workspaceOf(key);
6273
- if (!ws) return "";
6274
- return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
6678
+ function sectionHeads() {
6679
+ const heads = [];
6680
+ for (const sv of document.querySelectorAll("svg[data-purpose-section-chevron]")) {
6681
+ const btn = sv.closest("button");
6682
+ if (btn) heads.push(btn);
6683
+ }
6684
+ for (const h3 of document.querySelectorAll("h3")) heads.push(h3);
6685
+ return heads.map((head) => {
6686
+ const cached = headInfoCache.get(head);
6687
+ if (cached) return { head, cont: cached.cont, kind: cached.kind };
6688
+ const textKind = head.tagName === "BUTTON" ? kindFromHeadText(head) : "task";
6689
+ let cont = head.parentElement;
6690
+ while (cont && !cont.querySelector("li[data-task-item-key]") && cont !== document.body) cont = cont.parentElement;
6691
+ const contOk = cont && cont !== document.body;
6692
+ const kind = textKind || (contOk ? cont.querySelector("section[data-project-id]") ? "project" : "task" : null);
6693
+ headInfoCache.set(head, { cont: contOk ? cont : null, kind });
6694
+ return { head, cont: contOk ? cont : null, kind };
6695
+ }).filter((x) => x.head);
6275
6696
  }
6276
- function renderPopup() {
6277
- if (!popupEl) return;
6278
- const L = t();
6279
- const list = popupEl.querySelector("[data-zcodepro-switcher-list]");
6280
- list.replaceChildren(...mru.map((key, i) => {
6281
- const proj = projLabelOf(key, aliasesCache);
6282
- return h(
6283
- "div",
6284
- {
6285
- class: "zcodepro-switcher-row",
6286
- "data-active": i === highlight ? "" : void 0,
6287
- onClick: () => {
6288
- highlight = i;
6289
- closePopup(true);
6697
+ var SEC_SPIN_CLASS = "zcodepro-ws-sec-spin";
6698
+ var anyActiveCache = { at: 0, value: null, busy: false };
6699
+ var sectionActive = { task: false, project: false };
6700
+ var activeProjectPaths = /* @__PURE__ */ new Set();
6701
+ setInterval(() => {
6702
+ if (!anyActiveCache.busy && Date.now() - anyActiveCache.at >= 1e4) globalAnyActive();
6703
+ }, 1e4);
6704
+ function globalAnyActive() {
6705
+ if (anyActiveCache.value !== null && Date.now() - anyActiveCache.at < 1e4) return anyActiveCache.value;
6706
+ if (anyActiveCache.busy) return anyActiveCache.value === true;
6707
+ anyActiveCache.busy = true;
6708
+ sectionActive.task = false;
6709
+ sectionActive.project = false;
6710
+ activeProjectPaths = /* @__PURE__ */ new Set();
6711
+ void (async () => {
6712
+ let hit = null;
6713
+ try {
6714
+ const res = await rpc("/projects-v2").catch(() => null);
6715
+ const services = findServices();
6716
+ const ts = services && services.zcodeTaskService;
6717
+ if (res && res.ok && res.available && ts && typeof ts.listActiveTaskIds === "function") {
6718
+ hit = false;
6719
+ const projPaths = /* @__PURE__ */ new Set();
6720
+ for (const proj of res.projects || []) {
6721
+ for (const r of proj.roots || []) {
6722
+ const b = String(r.path || "").replace(/[\\/]+$/, "");
6723
+ if (b) projPaths.add(b);
6724
+ }
6290
6725
  }
6291
- },
6292
- // 标题文本包 span:截断省略作用在文本节点上(flex 容器自身 ellipsis 无效)
6293
- h(
6294
- "div",
6295
- { class: "zcodepro-switcher-title" },
6296
- i === 0 ? h("span", { class: "zcodepro-switcher-tag" }, L.switcherCurrent) : null,
6297
- h("span", null, findTitle(key))
6298
- ),
6299
- // 无项目归属(如置顶)时不渲染空行
6300
- proj ? h("div", { class: "zcodepro-switcher-ws" }, proj) : null
6301
- );
6302
- }));
6303
- }
6304
- async function openPopup() {
6305
- if (popupEl) return;
6306
- const cfg = await getConfig().catch(() => null);
6307
- aliasesCache = cfg && cfg.aliases && typeof cfg.aliases === "object" ? cfg.aliases : {};
6308
- ensureStyle();
6309
- const L = t();
6310
- highlight = 0;
6311
- popupEl = h(
6312
- "div",
6313
- {
6314
- class: "zcodepro-switcher-overlay",
6315
- // 点弹窗外 = 取消(不切)
6316
- onMousedown: (e) => {
6317
- if (e.target === popupEl) closePopup(false);
6726
+ const taskSidePaths = /* @__PURE__ */ new Set();
6727
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
6728
+ const sec = el.closest("section[data-project-id]");
6729
+ if (sec) continue;
6730
+ const k = el.getAttribute("data-task-item-key") || "";
6731
+ const cut = k.lastIndexOf(":");
6732
+ if (cut > 1) taskSidePaths.add(k.slice(0, cut).replace(/[\\/]+$/, ""));
6733
+ }
6734
+ const scanPaths = /* @__PURE__ */ new Set([...projPaths, ...taskSidePaths]);
6735
+ const byKind = { task: /* @__PURE__ */ new Set(), project: /* @__PURE__ */ new Set() };
6736
+ const byKindByPath = /* @__PURE__ */ new Map();
6737
+ const rootToProject = /* @__PURE__ */ new Map();
6738
+ const projectPaths = /* @__PURE__ */ new Map();
6739
+ for (const proj of res.projects || []) {
6740
+ const names = [];
6741
+ for (const r of proj.roots || []) {
6742
+ const b = String(r.path || "").replace(/[\\/]+$/, "");
6743
+ if (b) {
6744
+ rootToProject.set(b, proj.name);
6745
+ names.push(b);
6746
+ }
6747
+ }
6748
+ projectPaths.set(proj.name, names);
6749
+ }
6750
+ for (const base of scanPaths) {
6751
+ const a = await ts.listActiveTaskIds({ workspacePath: base });
6752
+ const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6753
+ const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6754
+ if (!ids.length) continue;
6755
+ byKindByPath.set(base, ids);
6756
+ const kind = projPaths.has(base) ? "project" : "task";
6757
+ for (const id of ids) byKind[kind].add(id);
6758
+ }
6759
+ const allIds = [...byKind.task, ...byKind.project];
6760
+ const st = allIds.length ? await rpc("/task-statuses?ids=" + encodeURIComponent(allIds.join(","))).catch(() => null) : null;
6761
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6762
+ const live = (id) => {
6763
+ const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6764
+ if (rec === void 0) return true;
6765
+ if (!TERMINAL.has(rec.status)) return true;
6766
+ return Date.now() - rec.updatedAt < 3e5;
6767
+ };
6768
+ activeProjectPaths = /* @__PURE__ */ new Set();
6769
+ for (const [base, ids] of byKindByPath) {
6770
+ if (!projPaths.has(base)) continue;
6771
+ if (ids.some(live)) {
6772
+ const projName = rootToProject.get(base);
6773
+ if (projName) for (const p2 of projectPaths.get(projName) || []) activeProjectPaths.add(p2);
6774
+ }
6775
+ }
6776
+ const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
6777
+ if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
6778
+ if ([...byKind.project].some(live)) sectionActive.project = true;
6779
+ hit = sectionActive.task || sectionActive.project;
6318
6780
  }
6319
- },
6320
- h(
6321
- "div",
6322
- { class: "zcodepro-switcher" },
6323
- h("div", { class: "zcodepro-switcher-hint" }, L.switcherHint),
6324
- h("div", { "data-zcodepro-switcher-list": "1", class: "zcodepro-switcher-list" })
6325
- )
6326
- );
6327
- document.body.append(popupEl);
6328
- renderPopup();
6329
- }
6330
- function moveHighlight(delta) {
6331
- if (!popupEl || mru.length < 2) return;
6332
- highlight = (highlight + delta + mru.length) % mru.length;
6333
- renderPopup();
6334
- const row = popupEl.querySelector("[data-active]");
6335
- if (row) row.scrollIntoView({ block: "nearest" });
6781
+ } catch {
6782
+ }
6783
+ if (hit !== null) anyActiveCache = { at: Date.now(), value: hit, busy: false };
6784
+ else anyActiveCache.busy = false;
6785
+ apply();
6786
+ })();
6787
+ return anyActiveCache.value === true;
6336
6788
  }
6337
- function startSessionSwitch() {
6338
- scan();
6339
- new MutationObserver((muts) => {
6340
- if (taskRowMutations(muts)) scheduleScan();
6341
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
6342
- setInterval(() => {
6343
- if (!document.hidden) scan();
6344
- }, SCAN_FALLBACK_MS);
6345
- document.addEventListener("visibilitychange", () => {
6346
- if (!document.hidden) scan();
6347
- });
6348
- document.addEventListener("click", (e) => {
6349
- const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
6350
- if (item) record(item.getAttribute("data-task-item-key"));
6351
- }, true);
6352
- document.addEventListener("keydown", (e) => {
6353
- if (e.key === "Escape" && popupEl) {
6354
- e.preventDefault();
6355
- e.stopImmediatePropagation();
6356
- closePopup(false);
6357
- return;
6358
- }
6359
- if (!e.altKey || e.ctrlKey || e.metaKey || e.isComposing) return;
6360
- if (document.querySelector(".zcodepro-overlay")) return;
6361
- const k = e.key.toLowerCase();
6362
- if (k !== "z" && k !== "x" && k !== "c") return;
6363
- e.preventDefault();
6364
- e.stopImmediatePropagation();
6365
- void (async () => {
6366
- const cfg = await getConfig();
6367
- if (cfg.features && cfg.features.sessionSwitch === false) return;
6368
- if (k === "z") {
6369
- if (e.repeat) return;
6370
- closePopup(false);
6371
- const live = findCurrent();
6372
- if (live) record(live.getAttribute("data-task-item-key"));
6373
- const target = live ? mru[0] === current ? mru[1] : mru.find((mk) => mk !== current) : mru[0];
6374
- if (!target) {
6375
- showToast(t().switcherEmpty);
6376
- return;
6377
- }
6378
- void switchTo(target).then((ok) => {
6379
- if (!ok) showToast(t().switcherFailed, "error");
6380
- });
6381
- return;
6382
- }
6383
- if (mru.length < 2) {
6384
- showToast(t().switcherEmpty);
6385
- return;
6386
- }
6387
- if (!popupEl) await openPopup();
6388
- moveHighlight(k === "x" ? -1 : 1);
6389
- })();
6390
- }, true);
6391
- document.addEventListener("keyup", (e) => {
6392
- if (e.key === "Alt" && popupEl) closePopup(true);
6393
- }, true);
6394
- window.addEventListener("blur", () => closePopup(false));
6395
- }
6396
-
6397
- // src/inject/features/title-lock.js
6398
- var TASK_SEL = "[data-task-item-key]";
6399
- var SCAN_DEBOUNCE_MS2 = 150;
6400
- var SCAN_FALLBACK_MS2 = 5e3;
6401
- var BOOT_RETRY_MS = 5e3;
6402
- var RECHECK_DELAY_MS = 1e3;
6403
- var REVERIFY_MS = 3e3;
6404
- var MAP_REFRESH_MS = 6e4;
6405
- var installed2 = false;
6406
- var seenRows = /* @__PURE__ */ new WeakSet();
6407
- var enabled2 = true;
6408
- var pinned = null;
6409
- var resolved = false;
6410
- var fetching = null;
6411
- var pendingKeys = /* @__PURE__ */ new Set();
6412
- function sessIdOf(key) {
6413
- const i = key.indexOf(":sess_");
6414
- return i > 0 ? key.slice(i + 1) : key;
6415
- }
6416
- function titleElsOf(item) {
6417
- const copies = item.querySelectorAll("[data-task-title-copy]");
6418
- if (copies.length) return [...copies];
6419
- const t2 = item.querySelector('[class*="truncate"]');
6420
- return t2 ? [t2] : [];
6421
- }
6422
- function findRows(key) {
6423
- return [...document.querySelectorAll(`${TASK_SEL}[data-task-item-key="${CSS.escape(key)}"]`)];
6424
- }
6425
- async function fetchPinned() {
6426
- if (fetching) return fetching;
6427
- const p = (async () => {
6428
- const res = await rpc("/pinned-titles");
6429
- if (!res || !res.ok) return null;
6430
- resolved = true;
6431
- enabled2 = res.enabled !== false;
6432
- if (!enabled2) {
6433
- pinned = null;
6434
- return null;
6435
- }
6436
- pinned = new Map(Object.entries(res.titles || {}));
6437
- return pinned;
6438
- })();
6439
- fetching = p;
6440
- try {
6441
- return await p;
6442
- } finally {
6443
- if (fetching === p) fetching = null;
6444
- }
6445
- }
6446
- function isMismatch(item) {
6447
- if (!enabled2 || !pinned) return null;
6448
- const key = item.getAttribute("data-task-item-key") || "";
6449
- const want = pinned.get(sessIdOf(key));
6450
- if (want === void 0) return null;
6451
- const els = titleElsOf(item);
6452
- if (!els.length) return null;
6453
- if ((els[0].textContent || "").trim() === String(want).trim()) return null;
6454
- if (item.querySelector('input, textarea, [contenteditable="true"]')) return null;
6455
- return key;
6456
- }
6457
- function writeTitle(el, text) {
6458
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
6459
- const first = walker.nextNode();
6460
- if (first && !walker.nextNode()) first.nodeValue = text;
6461
- else el.textContent = text;
6462
- }
6463
- function scheduleEnforce(key, { immediate = false } = {}) {
6464
- if (immediate) {
6465
- if (enabled2 && pinned) {
6466
- const want = pinned.get(sessIdOf(key));
6467
- if (want !== void 0) {
6468
- for (const item of findRows(key)) {
6469
- for (const el of titleElsOf(item)) writeTitle(el, want);
6470
- }
6471
- }
6472
- }
6473
- }
6474
- if (pendingKeys.has(key)) return;
6475
- pendingKeys.add(key);
6476
- setTimeout(() => {
6477
- pendingKeys.delete(key);
6478
- void (async () => {
6479
- const map = await fetchPinned();
6480
- if (!map) return;
6481
- const want = map.get(sessIdOf(key));
6482
- if (want === void 0) return;
6483
- let wrote = false;
6484
- for (const item of findRows(key)) {
6485
- if (isMismatch(item) !== key) continue;
6486
- for (const el of titleElsOf(item)) writeTitle(el, want);
6487
- wrote = true;
6488
- }
6489
- if (wrote) setTimeout(() => {
6490
- void enforceKey(key);
6491
- }, REVERIFY_MS);
6492
- })();
6493
- }, RECHECK_DELAY_MS);
6494
- }
6495
- async function enforceKey(key) {
6496
- const map = await fetchPinned();
6497
- if (!map) return;
6498
- const want = map.get(sessIdOf(key));
6499
- if (want === void 0) return;
6500
- for (const item of findRows(key)) {
6501
- if (isMismatch(item) !== key) continue;
6502
- for (const el of titleElsOf(item)) writeTitle(el, want);
6503
- }
6504
- }
6505
- function scan2() {
6506
- if (!enabled2 || !pinned || pinned.size === 0) return;
6507
- for (const item of document.querySelectorAll(TASK_SEL)) {
6508
- const key = isMismatch(item);
6509
- if (!key) {
6510
- seenRows.add(item);
6511
- continue;
6512
- }
6513
- const fresh = !seenRows.has(item);
6514
- seenRows.add(item);
6515
- scheduleEnforce(key, { immediate: fresh });
6516
- }
6517
- }
6518
- var inTaskRow2 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL));
6519
- function taskRowMutations2(muts) {
6520
- for (const m of muts) {
6521
- if (m.type === "attributes") {
6522
- if (inTaskRow2(m.target)) return true;
6523
- } else if (m.type === "characterData") {
6524
- if (m.target.parentElement && inTaskRow2(m.target.parentElement)) return true;
6525
- } else if (inTaskRow2(m.target)) {
6526
- return true;
6527
- } else {
6528
- for (const n of m.addedNodes) {
6529
- if (n instanceof Element && (n.matches(TASK_SEL) || !!n.querySelector(TASK_SEL))) return true;
6530
- }
6531
- }
6532
- }
6533
- return false;
6534
- }
6535
- var scanDebounce2 = 0;
6536
- function scheduleScan2() {
6537
- if (scanDebounce2) return;
6538
- scanDebounce2 = setTimeout(() => {
6539
- scanDebounce2 = 0;
6540
- scan2();
6541
- }, SCAN_DEBOUNCE_MS2);
6542
- }
6543
- var mapRefreshTimer = 0;
6544
- function refreshMapSoon() {
6545
- if (mapRefreshTimer) return;
6546
- const tick2 = () => {
6547
- if (document.hidden) {
6548
- mapRefreshTimer = setTimeout(tick2, 1e4);
6549
- return;
6550
- }
6551
- mapRefreshTimer = 0;
6552
- if (enabled2 && resolved) void fetchPinned().then((m) => {
6553
- if (m) scan2();
6554
- });
6555
- };
6556
- mapRefreshTimer = setTimeout(tick2, MAP_REFRESH_MS);
6557
- }
6558
- var editingKey = null;
6559
- var bootDelay = 0;
6560
- function scheduleBootRetry() {
6561
- bootDelay = bootDelay ? Math.min(bootDelay * 2, MAP_REFRESH_MS) : BOOT_RETRY_MS;
6562
- setTimeout(() => {
6563
- if (resolved) return;
6564
- if (!document.hidden) void bootstrap();
6565
- scheduleBootRetry();
6566
- }, bootDelay);
6567
- }
6568
- async function bootstrap() {
6569
- const map = await fetchPinned();
6570
- if (map) scan2();
6571
- }
6572
- function setRowTitle(key, text) {
6573
- for (const item of findRows(key)) {
6574
- for (const el of titleElsOf(item)) writeTitle(el, text);
6575
- }
6576
- }
6577
- function refreshPinnedTitles() {
6578
- return fetchPinned().then((m) => {
6579
- if (m) scan2();
6580
- });
6581
- }
6582
- var RENAME_CTX_VALID_MS = 15e3;
6583
- var renameCtx = null;
6584
- var renamePrefilled = /* @__PURE__ */ new WeakSet();
6585
- function setNativeValue(el, text) {
6586
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
6587
- Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
6588
- } else {
6589
- el.textContent = text;
6590
- }
6591
- el.dispatchEvent(new Event("input", { bubbles: true }));
6592
- }
6593
- function prefillRenameDialog() {
6594
- if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
6595
- renameCtx = null;
6596
- return;
6597
- }
6598
- if (!enabled2 || !pinned) return;
6599
- const want = pinned.get(sessIdOf(renameCtx.key));
6600
- if (want === void 0) return;
6601
- for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
6602
- if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
6603
- if (el.closest(TASK_SEL) || el.closest(".zcodepro-overlay")) continue;
6604
- const cur = String(el.value ?? el.textContent ?? "").trim();
6605
- if (!cur) continue;
6606
- renamePrefilled.add(el);
6607
- if (cur !== String(want).trim()) setNativeValue(el, String(want));
6608
- renameCtx = null;
6789
+ function applySectionSpin() {
6790
+ if (!enabled2) {
6791
+ document.querySelectorAll("." + SEC_SPIN_CLASS).forEach((el) => el.remove());
6609
6792
  return;
6610
6793
  }
6611
- }
6612
- function startTitleLock() {
6613
- if (installed2 || typeof document === "undefined") return;
6614
- installed2 = true;
6615
- void bootstrap();
6616
- new MutationObserver((muts) => {
6617
- if (renameCtx) prefillRenameDialog();
6618
- if (!taskRowMutations2(muts)) return;
6619
- refreshMapSoon();
6620
- scheduleScan2();
6621
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
6622
- document.addEventListener("contextmenu", (e) => {
6623
- const row = e.target instanceof Element && e.target.closest(TASK_SEL);
6624
- renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
6625
- }, true);
6626
- document.addEventListener("click", (e) => {
6627
- if (!renameCtx || renameCtx.armed) return;
6628
- const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
6629
- if (item && /重命名|rename/i.test(item.textContent || "")) {
6630
- renameCtx.armed = true;
6631
- renameCtx.at = Date.now();
6632
- }
6633
- }, true);
6634
- scheduleBootRetry();
6635
- setInterval(() => {
6636
- if (!document.hidden) scan2();
6637
- }, SCAN_FALLBACK_MS2);
6638
- document.addEventListener("visibilitychange", () => {
6639
- if (!document.hidden) scan2();
6640
- });
6641
- window.addEventListener("zcodepro:config-changed", () => {
6642
- void bootstrap();
6643
- });
6644
- document.addEventListener("focusin", (e) => {
6645
- const el = e.target;
6646
- if (!(el instanceof Element)) return;
6647
- if (!el.matches('input, textarea, [contenteditable="true"]')) return;
6648
- const row = el.closest(TASK_SEL);
6649
- editingKey = row ? row.getAttribute("data-task-item-key") || "" : null;
6650
- });
6651
- document.addEventListener("focusout", () => {
6652
- if (editingKey === null) return;
6653
- editingKey = null;
6654
- void fetchPinned().then((m) => {
6655
- if (m) scan2();
6656
- });
6657
- });
6658
- }
6659
-
6660
- // src/inject/features/title-gen.js
6661
- var CONV2 = '[class*="@md/conversation"]';
6662
- var USER_ROW = '[class*="user-row"]';
6663
- var SCAN_DEBOUNCE_MS3 = 300;
6664
- var SCAN_FALLBACK_MS3 = 5e3;
6665
- var SELECT_WAIT_MS = 2e3;
6666
- var MSG_WAIT_MS = 1e4;
6667
- var QUIET_FALLBACK_MS = 12e4;
6668
- var COLLECT_MAX_MS = 6e5;
6669
- var installed3 = false;
6670
- var knownKeys = /* @__PURE__ */ new Set();
6671
- var attempted = /* @__PURE__ */ new Set();
6672
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
6673
- function sessIdOf2(key) {
6674
- const i = key.indexOf(":sess_");
6675
- return i > 0 ? key.slice(i + 1) : "";
6676
- }
6677
- function currentKey() {
6678
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6679
- if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
6794
+ for (const { head, cont, kind } of sectionHeads()) {
6795
+ const running = !!(cont && cont.querySelector('li[data-task-item-key] svg[class*="lucide-loader"]'));
6796
+ let expanded = true;
6797
+ if (head.tagName === "BUTTON") expanded = head.getAttribute("aria-expanded") !== "false";
6798
+ else {
6799
+ const list = head.nextElementSibling;
6800
+ expanded = !list || list.offsetHeight > 0;
6801
+ }
6802
+ if (!kind) return;
6803
+ const scoped = kind === "project" ? sectionActive.project : sectionActive.task;
6804
+ const want = !expanded && scoped;
6805
+ const existing = head.querySelector(":scope > ." + SEC_SPIN_CLASS);
6806
+ if (want && !existing) {
6807
+ const span = document.createElement("span");
6808
+ span.className = "flex size-4 shrink-0 items-center justify-center " + SEC_SPIN_CLASS;
6809
+ span.append(makeLoaderSvg("size-4"));
6810
+ head.prepend(span);
6811
+ } else if (!want && existing) {
6812
+ existing.remove();
6813
+ }
6680
6814
  }
6681
- return "";
6682
6815
  }
6683
- function userTexts() {
6684
- return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
6685
- const el = r.querySelector(".whitespace-pre-wrap");
6686
- return ((el ? el.textContent : r.innerText) || "").trim();
6687
- }).filter(Boolean);
6688
- }
6689
- var URL_RE = /https?:\/\/[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g;
6690
- function assistantText() {
6691
- const els = [...document.querySelectorAll('[class*="@md/conversation"] [class*="assistant-row"]')].filter((e) => !e.querySelector("[data-is-streaming]"));
6692
- const last = els[els.length - 1];
6693
- if (!last) return "";
6694
- const text = (last.innerText || "").replace(URL_RE, "").trim();
6695
- return text.slice(0, 600);
6816
+ function schedulePeek(ws, delay) {
6817
+ clearTimeout(peekTimers.get(ws));
6818
+ peekTimers.set(ws, setTimeout(() => {
6819
+ peekTimers.delete(ws);
6820
+ void peek(ws);
6821
+ }, delay));
6696
6822
  }
6697
- function linkDominant(t2) {
6698
- const raw = t2.replace(/\s+/g, "");
6699
- if (!raw) return true;
6700
- const stripped = raw.replace(URL_RE, "");
6701
- return stripped.length < raw.length / 3;
6823
+ function clearPeek(ws) {
6824
+ const t2 = peekTimers.get(ws);
6825
+ if (t2) {
6826
+ clearTimeout(t2);
6827
+ peekTimers.delete(ws);
6828
+ }
6702
6829
  }
6703
- async function onNewTaskRow(key) {
6704
- const selDeadline = Date.now() + SELECT_WAIT_MS;
6705
- while (currentKey() !== key && Date.now() < selDeadline) await sleep2(200);
6706
- if (currentKey() !== key) return;
6707
- const cfg = await getConfig().catch(() => null);
6708
- if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
6709
- const sessId = sessIdOf2(key);
6710
- if (!sessId || attempted.has(sessId)) return;
6711
- attempted.add(sessId);
6712
- let texts = [];
6713
- const msgDeadline = Date.now() + MSG_WAIT_MS;
6714
- while (Date.now() < msgDeadline) {
6715
- texts = userTexts();
6716
- if (texts.length) break;
6717
- await sleep2(400);
6830
+ function syncPeeks() {
6831
+ for (const [ws, set] of runningByWs) {
6832
+ if (!enabled2 || !set.size) {
6833
+ clearPeek(ws);
6834
+ continue;
6835
+ }
6836
+ const row = findWsRow(ws);
6837
+ const head = row && wsHead(row);
6838
+ if (!row || !head || head.getAttribute("aria-expanded") !== "false" || wsWithRows.has(ws)) {
6839
+ clearPeek(ws);
6840
+ continue;
6841
+ }
6842
+ if (!peekTimers.has(ws) && !activePeeks.has(ws)) schedulePeek(ws, peekedWs.has(ws) ? PEEK_REPEAT_MS : PEEK_FIRST_MS);
6718
6843
  }
6719
- if (!texts.length) return;
6720
- let assistantSeen = false;
6721
- const convObserver = new MutationObserver((muts) => {
6722
- if (currentKey() !== key) return;
6723
- for (const m of muts) {
6724
- const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
6725
- if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
6726
- for (const n of [t2, ...m.addedNodes]) {
6727
- if (n instanceof Element && n.closest && n.closest(CONV2)) {
6728
- assistantSeen = true;
6729
- return;
6844
+ }
6845
+ var peekChain = Promise.resolve();
6846
+ async function peek(ws, opts) {
6847
+ if (!opts?.manual) {
6848
+ const auth = await authoritativeIdle(ws);
6849
+ if (auth !== null) {
6850
+ if (auth) {
6851
+ runningByWs.delete(ws);
6852
+ } else {
6853
+ let set = runningByWs.get(ws);
6854
+ if (!set) {
6855
+ set = /* @__PURE__ */ new Set();
6856
+ runningByWs.set(ws, set);
6730
6857
  }
6858
+ set.add(ws + ":active");
6859
+ schedulePeek(ws, PEEK_REPEAT_MS);
6731
6860
  }
6861
+ apply();
6862
+ return true;
6732
6863
  }
6864
+ }
6865
+ const run = () => peekInner(ws, opts);
6866
+ const p = peekChain.then(run, run);
6867
+ peekChain = p.catch(() => {
6733
6868
  });
6734
- convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
6735
- const startAt = Date.now();
6736
- let lastCount = texts.length;
6737
- let lastChange = Date.now();
6869
+ return p;
6870
+ }
6871
+ async function peekInner(ws, opts) {
6872
+ if (!opts?.manual && !enabled2 || document.hidden || activePeeks.has(ws)) return false;
6873
+ const row = findWsRow(ws);
6874
+ const head = row && wsHead(row);
6875
+ if (!row || !head || head.getAttribute("aria-expanded") !== "false") return false;
6876
+ if (!currentTaskRow()) {
6877
+ schedulePeek(ws, PEEK_REPEAT_MS);
6878
+ return false;
6879
+ }
6880
+ activePeeks.add(ws);
6881
+ peekedWs.add(ws);
6882
+ const rect = row.getBoundingClientRect();
6883
+ const frozen = row.cloneNode(true);
6884
+ frozen.classList.add("zcodepro-ws-frozen");
6885
+ frozen.style.left = rect.left + "px";
6886
+ frozen.style.top = rect.top + "px";
6887
+ frozen.style.width = rect.width + "px";
6888
+ frozen.style.height = rect.height + "px";
6889
+ document.body.append(frozen);
6890
+ row.style.visibility = "hidden";
6891
+ const hider = installPeekHider(ws);
6892
+ let readOk = false;
6738
6893
  try {
6739
- while (Date.now() - startAt < COLLECT_MAX_MS) {
6740
- await sleep2(500);
6741
- if (currentKey() !== key) {
6742
- lastChange = Date.now();
6743
- continue;
6744
- }
6745
- texts = userTexts();
6746
- if (texts.length !== lastCount) {
6747
- lastCount = texts.length;
6748
- lastChange = Date.now();
6749
- }
6750
- if (assistantSeen) break;
6751
- if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
6894
+ toggleNavSafe(head);
6895
+ readOk = await settleAndRead(ws);
6896
+ const rowNow = findWsRow(ws) || row;
6897
+ const headNow = wsHead(rowNow);
6898
+ if (headNow && headNow.getAttribute("aria-expanded") === "true" && !userTouched(rowNow)) {
6899
+ toggleNavSafe(headNow);
6900
+ await waitForRowsGone(ws);
6752
6901
  }
6753
6902
  } finally {
6754
- convObserver.disconnect();
6755
- }
6756
- if (!texts.length) return;
6757
- if (linkDominant(texts.join("\n"))) {
6758
- const deadline = Date.now() + 6e4;
6759
- while (Date.now() < deadline) {
6760
- await sleep2(2e3);
6761
- if (currentKey() !== key) continue;
6762
- const a = assistantText();
6763
- if (a && a.length > 80) {
6764
- texts.push(`\uFF08\u9996\u8F6E\u56DE\u7B54\u6458\u8981\uFF09${a}`);
6765
- break;
6766
- }
6767
- }
6903
+ removePeekHider(hider);
6904
+ row.style.visibility = "";
6905
+ frozen.remove();
6906
+ activePeeks.delete(ws);
6768
6907
  }
6769
- const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
6770
- let res = await callTitleGen();
6771
- for (const delay of [5e3, 2e4]) {
6772
- if (res && res.ok) break;
6773
- await sleep2(delay);
6774
- res = await callTitleGen();
6908
+ apply();
6909
+ const set = runningByWs.get(ws);
6910
+ if (set && set.size && enabled2) schedulePeek(ws, PEEK_REPEAT_MS);
6911
+ return readOk;
6912
+ }
6913
+ function wsRunningSnapshot() {
6914
+ const curKey = currentTaskRow()?.getAttribute("data-task-item-key") || "";
6915
+ return {
6916
+ running: [...runningByWs.entries()].filter(([, set]) => set.size > 0).map(([ws]) => ws),
6917
+ current: curKey ? workspaceOf(curKey) || null : null
6918
+ };
6919
+ }
6920
+ function wsRunningKeys() {
6921
+ const keys = /* @__PURE__ */ new Set();
6922
+ for (const set of runningByWs.values()) for (const k of set) keys.add(k);
6923
+ return keys;
6924
+ }
6925
+ async function verifyWorkspaceNotRunning(ws) {
6926
+ if (!ws || document.hidden) return false;
6927
+ const row = findWsRow(ws);
6928
+ const head = row && wsHead(row);
6929
+ if (!row || !head) return false;
6930
+ if (head.getAttribute("aria-expanded") !== "false") {
6931
+ let run = false;
6932
+ for (const el of domRowsOf(ws)) if (isRunningRow(el)) run = true;
6933
+ return !run;
6775
6934
  }
6776
- if (res && res.ok && res.written && res.title) {
6777
- setRowTitle(key, res.title);
6778
- void refreshPinnedTitles();
6935
+ let verified = false;
6936
+ try {
6937
+ verified = await peek(ws, { manual: true }) === true;
6938
+ } catch {
6779
6939
  }
6940
+ if (!verified) return false;
6941
+ const set = runningByWs.get(ws);
6942
+ return !(set && set.size);
6943
+ }
6944
+ function settleAndRead(ws) {
6945
+ return new Promise((resolve) => {
6946
+ const t0 = Date.now();
6947
+ let lastCount = -1;
6948
+ let stableAt = 0;
6949
+ let zeroRows = false;
6950
+ const timer = setInterval(() => {
6951
+ const rows = domRowsOf(ws);
6952
+ const elapsed = Date.now() - t0;
6953
+ if (rows.length !== lastCount) {
6954
+ lastCount = rows.length;
6955
+ stableAt = Date.now();
6956
+ }
6957
+ const settled = rows.length > 0 && rows.length === lastCount && Date.now() - stableAt >= PEEK_SETTLE_MS;
6958
+ if (!settled && elapsed < PEEK_MAX_MS) return;
6959
+ clearInterval(timer);
6960
+ if (lastCount === 0) {
6961
+ zeroRows = true;
6962
+ } else {
6963
+ let set = runningByWs.get(ws);
6964
+ if (!set) {
6965
+ set = /* @__PURE__ */ new Set();
6966
+ runningByWs.set(ws, set);
6967
+ }
6968
+ for (const el of rows) {
6969
+ const key = el.getAttribute("data-task-item-key") || "";
6970
+ if (isRunningRow(el)) set.add(key);
6971
+ else set.delete(key);
6972
+ }
6973
+ }
6974
+ resolve(!zeroRows);
6975
+ }, 80);
6976
+ });
6977
+ }
6978
+ var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
6979
+ var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
6980
+ function installPeekHider(ws) {
6981
+ const rowSel3 = `${TASK_SEL}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
6982
+ for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
6983
+ const style = document.createElement("style");
6984
+ style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
6985
+ document.head.append(style);
6986
+ return { style, sel: rowSel3 };
6987
+ }
6988
+ function removePeekHider(hider) {
6989
+ hider.style.remove();
6990
+ for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
6991
+ }
6992
+ function waitForRowsGone(ws) {
6993
+ const sel = `${TASK_SEL}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
6994
+ return new Promise((resolve) => {
6995
+ const t0 = Date.now();
6996
+ const timer = setInterval(() => {
6997
+ if (!document.querySelector(sel) || Date.now() - t0 >= PEEK_UNMOUNT_WAIT_MS) {
6998
+ clearInterval(timer);
6999
+ resolve();
7000
+ }
7001
+ }, 40);
7002
+ });
6780
7003
  }
6781
- function scanForNewRows() {
6782
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6783
- const key = el.getAttribute("data-task-item-key") || "";
6784
- if (!key || knownKeys.has(key)) continue;
6785
- knownKeys.add(key);
6786
- void onNewTaskRow(key);
6787
- }
7004
+ var TOUCH_WINDOW_MS = 800;
7005
+ var wsTouchAt = /* @__PURE__ */ new WeakMap();
7006
+ function userTouched(row) {
7007
+ const at = wsTouchAt.get(row);
7008
+ return !!at && Date.now() - at < TOUCH_WINDOW_MS;
6788
7009
  }
6789
- var TASK_SEL2 = "[data-task-item-key]";
6790
- var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
6791
- function taskRowMutations3(muts) {
7010
+ function inSel(node, sel) {
7011
+ return !!(node instanceof Element && node.closest && node.closest(sel));
7012
+ }
7013
+ function relevant(muts) {
6792
7014
  for (const m of muts) {
6793
- if (m.type === "attributes") {
6794
- if (inTaskRow3(m.target)) return true;
6795
- } else if (m.type === "characterData") {
6796
- if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
6797
- } else if (inTaskRow3(m.target)) {
6798
- return true;
6799
- } else {
6800
- for (const n of m.addedNodes) {
6801
- if (n instanceof Element && (n.matches(TASK_SEL2) || !!n.querySelector(TASK_SEL2))) return true;
7015
+ if (inSel(m.target, TASK_SEL) || inSel(m.target, WS_SEL())) return true;
7016
+ for (const n of m.addedNodes) {
7017
+ if (n instanceof Element && (n.matches(TASK_SEL) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL) || n.querySelector(WS_SEL()))) return true;
7018
+ }
7019
+ for (const n of m.removedNodes) {
7020
+ if (n instanceof Element && (n.matches(TASK_SEL) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL))) return true;
7021
+ }
7022
+ }
7023
+ return false;
7024
+ }
7025
+ var pollMs = CONFIG_POLL_MIN_MS;
7026
+ async function refreshConfig() {
7027
+ try {
7028
+ const res = await rpc("/config");
7029
+ if (res && res.ok && res.config && res.config.features) {
7030
+ const next = res.config.features.wsRunningSpin !== false;
7031
+ if (next !== enabled2) {
7032
+ enabled2 = next;
7033
+ if (!next) {
7034
+ for (const ws of [...peekTimers.keys()]) clearPeek(ws);
7035
+ apply();
7036
+ }
7037
+ return true;
6802
7038
  }
6803
7039
  }
7040
+ } catch {
6804
7041
  }
6805
7042
  return false;
6806
7043
  }
6807
- var scanDebounce3 = 0;
6808
- function scheduleScan3() {
6809
- if (scanDebounce3) return;
6810
- scanDebounce3 = setTimeout(() => {
6811
- scanDebounce3 = 0;
6812
- scanForNewRows();
6813
- }, SCAN_DEBOUNCE_MS3);
7044
+ function pollConfig() {
7045
+ setTimeout(async () => {
7046
+ if (document.hidden) pollMs = CONFIG_POLL_MAX_MS;
7047
+ else pollMs = await refreshConfig() ? CONFIG_POLL_MIN_MS : Math.min(Math.round(pollMs * 1.5), CONFIG_POLL_MAX_MS);
7048
+ pollConfig();
7049
+ }, pollMs);
6814
7050
  }
6815
- function startTitleGen() {
6816
- if (installed3 || typeof document === "undefined") return;
6817
- installed3 = true;
6818
- scanForNewRows();
6819
- new MutationObserver((muts) => {
6820
- if (taskRowMutations3(muts)) scheduleScan3();
6821
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
7051
+ var scanDebounce = 0;
7052
+ function startWsRunningSpin() {
7053
+ if (installed2 || typeof document === "undefined") return;
7054
+ installed2 = true;
7055
+ void refreshConfig();
7056
+ pollConfig();
7057
+ const observer2 = new MutationObserver((muts) => {
7058
+ if (!relevant(muts)) return;
7059
+ handleRemovals(muts);
7060
+ if (!scanDebounce) {
7061
+ scanDebounce = setTimeout(() => {
7062
+ scanDebounce = 0;
7063
+ scan();
7064
+ }, SCAN_DEBOUNCE_MS);
7065
+ }
7066
+ });
7067
+ observer2.observe(document.body, { childList: true, subtree: true });
7068
+ scan();
6822
7069
  setInterval(() => {
6823
- if (!document.hidden) scanForNewRows();
6824
- }, SCAN_FALLBACK_MS3);
7070
+ if (!document.hidden && enabled2) scan();
7071
+ }, SCAN_FALLBACK_MS);
6825
7072
  document.addEventListener("visibilitychange", () => {
6826
- if (!document.hidden) scanForNewRows();
7073
+ if (!document.hidden) scan();
6827
7074
  });
7075
+ document.addEventListener("click", (e) => {
7076
+ if (!e.isTrusted) return;
7077
+ const row = e.target instanceof Element && e.target.closest(WS_SEL());
7078
+ if (row) wsTouchAt.set(row, Date.now());
7079
+ }, true);
6828
7080
  }
6829
7081
 
6830
- // src/inject/features/ws-running.js
6831
- var installed4 = false;
6832
- var enabled3 = true;
6833
- var SCAN_DEBOUNCE_MS4 = 150;
6834
- var SCAN_FALLBACK_MS4 = 5e3;
6835
- var PEEK_FIRST_MS = 2e4;
6836
- var PEEK_REPEAT_MS = 6e4;
6837
- var PEEK_SETTLE_MS = 160;
6838
- var PEEK_MAX_MS = 1500;
6839
- var PEEK_UNMOUNT_WAIT_MS = 800;
6840
- var CONFIG_POLL_MIN_MS = 5e3;
6841
- var CONFIG_POLL_MAX_MS = 6e4;
6842
- var TASK_SEL3 = "[data-task-item-key]";
6843
- var WS_SEL = () => projectRowSelector();
6844
- function findWsRow(ws) {
6845
- return findProjectRow(ws);
7082
+ // src/inject/features/session-switch.js
7083
+ var MRU_LIMIT = 50;
7084
+ var SCAN_DEBOUNCE_MS2 = 150;
7085
+ var SCAN_FALLBACK_MS2 = 5e3;
7086
+ var INITIAL_COUNT = 10;
7087
+ var PAGE_SIZE = 5;
7088
+ var UNSEEN_LIMIT = 50;
7089
+ var mru = [];
7090
+ var current = null;
7091
+ var titles = /* @__PURE__ */ new Map();
7092
+ var popupEl = null;
7093
+ var aliasesCache = {};
7094
+ var highlight = 0;
7095
+ var scanDebounce2 = 0;
7096
+ var popupMode = "mru";
7097
+ var visibleCount = INITIAL_COUNT;
7098
+ var unseen = /* @__PURE__ */ new Set();
7099
+ var lastRunKeys = null;
7100
+ var movedSinceOpen = false;
7101
+ var hotkeys = { ...DEFAULT_SHORTCUTS };
7102
+ function loadHotkeys() {
7103
+ return getConfig(true).then((cfg) => {
7104
+ const sc = cfg && cfg.shortcuts;
7105
+ if (sc && typeof sc === "object") {
7106
+ for (const k of Object.keys(DEFAULT_SHORTCUTS)) {
7107
+ if (typeof sc[k] === "string" && /^[a-z0-9]$/.test(sc[k])) hotkeys[k] = sc[k];
7108
+ }
7109
+ }
7110
+ }).catch(() => {
7111
+ });
6846
7112
  }
6847
- var LOADER_FALLBACK_PATHS = [
6848
- "M12 2v4",
6849
- "m16.2 7.8 2.9-2.9",
6850
- "M18 12h4",
6851
- "m19.8 16.2-2.9 2.9",
6852
- "M12 18v4",
6853
- "m7.8 19.8-2.9-2.9",
6854
- "M6 12H2",
6855
- "m4.2 7.8 2.9-2.9"
6856
- ];
6857
- function makeLoaderSvg(baseClass) {
6858
- const ns = "http://www.w3.org/2000/svg";
6859
- const proto = document.querySelector("svg.lucide-loader");
6860
- if (proto) {
6861
- const clone = proto.cloneNode(true);
6862
- const origClass = proto.getAttribute("class") || "";
6863
- const keep = origClass.split(/\s+/).filter((c) => /^(size-|text-)/.test(c)).join(" ");
6864
- clone.setAttribute("class", ((keep || baseClass) + " animate-spin zcodepro-ws-loader").trim());
6865
- return clone;
6866
- }
6867
- const svg = document.createElementNS(ns, "svg");
6868
- svg.setAttribute("viewBox", "0 0 24 24");
6869
- svg.setAttribute("fill", "none");
6870
- svg.setAttribute("stroke", "currentColor");
6871
- svg.setAttribute("stroke-width", "2");
6872
- svg.setAttribute("stroke-linecap", "round");
6873
- svg.setAttribute("stroke-linejoin", "round");
6874
- svg.setAttribute("class", (baseClass + " animate-spin zcodepro-ws-loader").trim());
6875
- for (const d of LOADER_FALLBACK_PATHS) {
6876
- const path = document.createElementNS(ns, "path");
6877
- path.setAttribute("d", d);
6878
- svg.append(path);
6879
- }
6880
- return svg;
7113
+ function firstLineOf(item) {
7114
+ const tEl = item.querySelector('[class*="truncate"]');
7115
+ return (tEl ? tEl.textContent : (item.innerText || "").split("\n")[0])?.trim() || "";
6881
7116
  }
6882
- function setRunningIcon(holder, on) {
6883
- if (!holder) return;
6884
- if (on) {
6885
- if (holder.querySelector(":scope > svg.zcodepro-ws-loader")) return;
6886
- const orig0 = holder.querySelector(":scope > svg:not(.zcodepro-ws-loader)");
6887
- const base = (orig0 ? orig0.getAttribute("class") || "" : "size-4").replace(/lucide-[a-z-]*/g, "").trim() || "size-4";
6888
- holder.append(makeLoaderSvg(base));
6889
- } else {
6890
- holder.querySelectorAll(":scope > svg.zcodepro-ws-loader").forEach((el) => el.remove());
7117
+ function workspaceOf2(key) {
7118
+ const i = key.indexOf(":sess_");
7119
+ return i > 0 ? key.slice(0, i) : key;
7120
+ }
7121
+ function findCurrent() {
7122
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7123
+ if ((el.className + "").includes("bg-selected")) return el;
6891
7124
  }
7125
+ return null;
6892
7126
  }
6893
- function iconHolderOf(row) {
6894
- if (row.hasAttribute && row.hasAttribute("data-project-id")) {
6895
- const toggle2 = row.querySelector('[data-testid="virtual-project-toggle"]');
6896
- return toggle2 ? toggle2.querySelector(":scope > span") : null;
7127
+ function record(key) {
7128
+ if (key) unseen.delete(key);
7129
+ if (key && key !== current) {
7130
+ current = key;
7131
+ mru = mru.filter((k) => k !== key);
7132
+ mru.unshift(key);
7133
+ if (mru.length > MRU_LIMIT) mru.length = MRU_LIMIT;
6897
7134
  }
6898
- return row.firstElementChild && row.firstElementChild.querySelector(":scope > span");
6899
7135
  }
6900
- function isRunningRow(el) {
6901
- const scope = el.firstElementChild;
6902
- if (!scope) return false;
6903
- const svg = scope.querySelector('svg[class*="lucide-loader"]');
6904
- return !!svg;
7136
+ function scan2() {
7137
+ const cur = findCurrent();
7138
+ if (cur) record(cur.getAttribute("data-task-item-key"));
7139
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7140
+ titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
7141
+ }
6905
7142
  }
6906
- var runningByWs = /* @__PURE__ */ new Map();
6907
- var peekTimers = /* @__PURE__ */ new Map();
6908
- var activePeeks = /* @__PURE__ */ new Set();
6909
- var peekedWs = /* @__PURE__ */ new Set();
6910
- var wsWithRows = /* @__PURE__ */ new Set();
6911
- var servicesRef = null;
6912
- function findServices() {
6913
- if (servicesRef) return servicesRef;
6914
- try {
6915
- const rootEl = document.getElementById("root");
6916
- const containerKey = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
6917
- if (!containerKey) return null;
6918
- const seen = /* @__PURE__ */ new Set();
6919
- let found = null;
6920
- const scanObj = (o) => {
6921
- if (!o || typeof o !== "object" || seen.has(o) || found) return;
6922
- seen.add(o);
6923
- const ts = o.zcodeTaskService;
6924
- if (ts && typeof ts === "object" && typeof ts.releaseWorkspacePreparation === "function") found = o;
6925
- };
6926
- let visited = 0;
6927
- const visit = (f) => {
6928
- if (!f || found || visited > 6e4) return;
6929
- visited++;
6930
- for (const holder of [f.memoizedProps, f.memoizedState]) {
6931
- if (!holder || typeof holder !== "object") continue;
6932
- scanObj(holder);
6933
- for (const v of Object.values(holder)) {
6934
- if (v && typeof v === "object") scanObj(v);
6935
- }
7143
+ var rowSel2 = "[data-task-item-key]";
7144
+ var inTaskRow = (node) => node instanceof Element && !!node.closest && !!node.closest(rowSel2);
7145
+ function taskRowMutations(muts) {
7146
+ for (const m of muts) {
7147
+ if (m.type === "attributes") {
7148
+ if (inTaskRow(m.target)) return true;
7149
+ } else if (m.type === "characterData") {
7150
+ if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
7151
+ } else if (inTaskRow(m.target)) {
7152
+ return true;
7153
+ } else {
7154
+ for (const n of m.addedNodes) {
7155
+ if (n instanceof Element && (n.matches(rowSel2) || !!n.querySelector(rowSel2))) return true;
6936
7156
  }
6937
- visit(f.child);
6938
- visit(f.sibling);
6939
- };
6940
- visit(rootEl[containerKey]);
6941
- if (found) servicesRef = found;
6942
- return found;
6943
- } catch {
6944
- return null;
7157
+ }
7158
+ }
7159
+ return false;
7160
+ }
7161
+ function scheduleScan() {
7162
+ if (scanDebounce2) return;
7163
+ scanDebounce2 = setTimeout(() => {
7164
+ scanDebounce2 = 0;
7165
+ scan2();
7166
+ }, SCAN_DEBOUNCE_MS2);
7167
+ }
7168
+ async function switchTo(key) {
7169
+ const find = () => document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
7170
+ let item = find();
7171
+ if (!item) {
7172
+ const ws = workspaceOf2(key);
7173
+ const header = findProjectRow(ws);
7174
+ const expander = header && header.querySelector("[aria-expanded]");
7175
+ if (expander && expander.getAttribute("aria-expanded") === "false") {
7176
+ expander.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
7177
+ for (let i = 0; i < 20 && !(item = find()); i++) await new Promise((r) => setTimeout(r, 100));
7178
+ }
6945
7179
  }
7180
+ if (!item) return false;
7181
+ item.scrollIntoView({ block: "nearest" });
7182
+ item.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
7183
+ return true;
6946
7184
  }
6947
- async function authoritativeIdle(ws) {
6948
- const services = findServices();
6949
- const ts = services && services.zcodeTaskService;
6950
- if (!ts || typeof ts.listActiveTaskIds !== "function") return null;
6951
- try {
6952
- const base = String(ws).replace(/[\\/]+$/, "");
6953
- const a = await ts.listActiveTaskIds({ workspacePath: base });
6954
- const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6955
- const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6956
- if (!Array.isArray(a) || !Array.isArray(b)) return null;
6957
- if (!ids.length) return true;
6958
- const st = await rpc("/task-statuses?ids=" + encodeURIComponent(ids.join(","))).catch(() => null);
6959
- const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6960
- const live = (id) => {
6961
- const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6962
- if (rec === void 0) return true;
6963
- if (!TERMINAL.has(rec.status)) return true;
6964
- return Date.now() - rec.updatedAt < 3e5;
6965
- };
6966
- return !ids.some(live);
6967
- } catch {
6968
- return null;
7185
+ function attentionList() {
7186
+ const run = wsRunningKeys();
7187
+ const listed = /* @__PURE__ */ new Set();
7188
+ const head = [];
7189
+ for (const k of mru) {
7190
+ if (run.has(k) || unseen.has(k)) {
7191
+ head.push(k);
7192
+ listed.add(k);
7193
+ }
7194
+ }
7195
+ const tail = [];
7196
+ for (const k of run) if (!listed.has(k)) {
7197
+ tail.push(k);
7198
+ listed.add(k);
6969
7199
  }
7200
+ for (const k of unseen) if (!listed.has(k)) {
7201
+ tail.push(k);
7202
+ listed.add(k);
7203
+ }
7204
+ return [...head, ...tail];
6970
7205
  }
6971
- function workspaceOf2(key) {
6972
- const i = key.indexOf(":sess_");
6973
- return i > 0 ? key.slice(0, i) : key;
7206
+ function activeList() {
7207
+ return popupMode === "attention" ? attentionList() : mru;
6974
7208
  }
6975
- function wsHead(row) {
6976
- return row.matches("[aria-expanded]") ? row : row.querySelector("[aria-expanded]");
7209
+ function closePopup(commit) {
7210
+ if (!popupEl) return;
7211
+ const idx = highlight;
7212
+ const list = activeList();
7213
+ popupEl.remove();
7214
+ popupEl = null;
7215
+ if (commit && list[idx] && list[idx] !== current) {
7216
+ void switchTo(list[idx]).then((ok) => {
7217
+ if (!ok) showToast(t().switcherFailed, "error");
7218
+ });
7219
+ }
6977
7220
  }
6978
- function domRowsOf(ws) {
6979
- return [...document.querySelectorAll(TASK_SEL3)].filter((e) => workspaceOf2(e.getAttribute("data-task-item-key") || "") === ws);
7221
+ function findTitle(key) {
7222
+ const item = document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
7223
+ if (item) titles.set(key, firstLineOf(item));
7224
+ const id = key.slice(key.indexOf(":sess_") + 1);
7225
+ return titles.get(key) || `sess ${id.slice(0, 8)}`;
6980
7226
  }
6981
- function currentTaskRow() {
6982
- for (const el of document.querySelectorAll(TASK_SEL3)) {
6983
- if ((el.className + "").includes("bg-selected")) return el;
6984
- }
6985
- return null;
7227
+ function projLabelOf(key, aliases2) {
7228
+ const ws = workspaceOf2(key);
7229
+ if (!ws) return "";
7230
+ return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
6986
7231
  }
6987
- var synthClick = (el) => {
6988
- try {
6989
- el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6990
- } catch {
7232
+ function renderPopup() {
7233
+ if (!popupEl) return;
7234
+ const L = t();
7235
+ const list = popupEl.querySelector("[data-zcodepro-switcher-list]");
7236
+ const full = activeList();
7237
+ const shown = full.slice(0, visibleCount);
7238
+ const run = popupMode === "attention" ? wsRunningKeys() : null;
7239
+ list.replaceChildren(...shown.map((key) => {
7240
+ const proj = projLabelOf(key, aliasesCache);
7241
+ return h(
7242
+ "div",
7243
+ {
7244
+ class: "zcodepro-switcher-row",
7245
+ "data-active": full[highlight] === key ? "" : void 0,
7246
+ onClick: () => {
7247
+ highlight = full.indexOf(key);
7248
+ closePopup(true);
7249
+ }
7250
+ },
7251
+ // 标题文本包 span:截断省略作用在文本节点上(flex 容器自身 ellipsis 无效)
7252
+ h(
7253
+ "div",
7254
+ { class: "zcodepro-switcher-title" },
7255
+ key === current ? h("span", { class: "zcodepro-switcher-tag" }, L.switcherCurrent) : null,
7256
+ run && run.has(key) ? h("span", { class: "zcodepro-switcher-tag zcodepro-switcher-tag-run" }, L.switcherRunning) : null,
7257
+ run && !run.has(key) ? h("span", { class: "zcodepro-switcher-tag zcodepro-switcher-tag-new" }, L.switcherUnseen) : null,
7258
+ h("span", null, findTitle(key))
7259
+ ),
7260
+ // 无项目归属(如置顶)时不渲染空行
7261
+ proj ? h("div", { class: "zcodepro-switcher-ws" }, proj) : null
7262
+ );
7263
+ }));
7264
+ if (full.length > shown.length) {
7265
+ list.append(h(
7266
+ "div",
7267
+ { class: "zcodepro-switcher-more" },
7268
+ L.switcherMore.replaceAll("{n}", String(full.length - shown.length))
7269
+ ));
6991
7270
  }
6992
- };
6993
- function toggleNavSafe(head) {
6994
- const cur = currentTaskRow();
6995
- const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6996
- synthClick(head);
6997
- if (!backKey) return;
6998
- const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL3}[data-task-item-key="${CSS.escape(backKey)}"]`);
6999
- if (back) synthClick(back);
7000
7271
  }
7001
- function scan3() {
7002
- wsWithRows = /* @__PURE__ */ new Set();
7003
- for (const el of document.querySelectorAll(TASK_SEL3)) {
7004
- const key = el.getAttribute("data-task-item-key") || "";
7005
- const ws = workspaceOf2(key);
7006
- if (!ws) continue;
7007
- wsWithRows.add(ws);
7008
- const run = isRunningRow(el);
7009
- let set = runningByWs.get(ws);
7010
- if (run) {
7011
- if (!set) {
7012
- set = /* @__PURE__ */ new Set();
7013
- runningByWs.set(ws, set);
7272
+ async function openPopup(mode = "mru") {
7273
+ if (popupEl) return;
7274
+ popupMode = mode;
7275
+ visibleCount = INITIAL_COUNT;
7276
+ highlight = 0;
7277
+ movedSinceOpen = false;
7278
+ const cfg = await getConfig().catch(() => null);
7279
+ aliasesCache = cfg && cfg.aliases && typeof cfg.aliases === "object" ? cfg.aliases : {};
7280
+ ensureStyle();
7281
+ const L = t();
7282
+ popupEl = h(
7283
+ "div",
7284
+ {
7285
+ class: "zcodepro-switcher-overlay",
7286
+ // 点弹窗外 = 取消(不切)
7287
+ onMousedown: (e) => {
7288
+ if (e.target === popupEl) closePopup(false);
7014
7289
  }
7015
- set.add(key);
7016
- } else if (set) {
7017
- set.delete(key);
7018
- }
7290
+ },
7291
+ h(
7292
+ "div",
7293
+ { class: "zcodepro-switcher" },
7294
+ // 提示行键位跟随当前配置(改键后新开的弹窗即用新键位)
7295
+ h(
7296
+ "div",
7297
+ { class: "zcodepro-switcher-hint" },
7298
+ (mode === "attention" ? L.switcherAttentionHint : L.switcherHint).replaceAll("{prev}", hotkeys.switchPrev).replaceAll("{next}", hotkeys.switchNext)
7299
+ ),
7300
+ h("div", { "data-zcodepro-switcher-list": "1", class: "zcodepro-switcher-list" })
7301
+ )
7302
+ );
7303
+ document.body.append(popupEl);
7304
+ renderPopup();
7305
+ }
7306
+ function scrollHighlight() {
7307
+ const row = popupEl && popupEl.querySelector("[data-active]");
7308
+ if (row) row.scrollIntoView({ block: "nearest" });
7309
+ }
7310
+ function moveUp() {
7311
+ const len = Math.min(activeList().length, visibleCount);
7312
+ if (len < 2) return;
7313
+ highlight = highlight > 0 ? highlight - 1 : len - 1;
7314
+ movedSinceOpen = true;
7315
+ renderPopup();
7316
+ scrollHighlight();
7317
+ }
7318
+ function moveDown() {
7319
+ const full = activeList();
7320
+ const len = Math.min(full.length, visibleCount);
7321
+ if (len < 2) return;
7322
+ if (highlight < len - 1) {
7323
+ highlight += 1;
7324
+ } else if (visibleCount < full.length) {
7325
+ visibleCount = Math.min(visibleCount + PAGE_SIZE, full.length);
7326
+ highlight = len;
7327
+ } else {
7328
+ highlight = 0;
7019
7329
  }
7020
- syncPeeks();
7021
- apply();
7330
+ movedSinceOpen = true;
7331
+ renderPopup();
7332
+ scrollHighlight();
7022
7333
  }
7023
- function handleRemovals(muts) {
7024
- const removed = [];
7025
- for (const m of muts) {
7026
- if (m.type !== "childList") continue;
7027
- for (const n of m.removedNodes) {
7028
- if (!(n instanceof Element)) continue;
7029
- if (n.matches(TASK_SEL3)) removed.push(n);
7030
- else for (const r of n.querySelectorAll(TASK_SEL3)) removed.push(r);
7334
+ function startSessionSwitch() {
7335
+ scan2();
7336
+ void loadHotkeys();
7337
+ window.addEventListener("zcodepro:config-changed", () => {
7338
+ void loadHotkeys();
7339
+ });
7340
+ new MutationObserver((muts) => {
7341
+ if (taskRowMutations(muts)) scheduleScan();
7342
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
7343
+ setInterval(() => {
7344
+ if (!document.hidden) scan2();
7345
+ }, SCAN_FALLBACK_MS2);
7346
+ document.addEventListener("visibilitychange", () => {
7347
+ if (!document.hidden) scan2();
7348
+ });
7349
+ setInterval(() => {
7350
+ if (document.hidden) return;
7351
+ const now = wsRunningKeys();
7352
+ if (lastRunKeys) {
7353
+ for (const k of lastRunKeys) {
7354
+ if (!now.has(k) && k !== current) unseen.add(k);
7355
+ }
7031
7356
  }
7032
- }
7033
- if (!removed.length) return;
7034
- const infos = removed.map((el) => {
7035
- const key = el.getAttribute("data-task-item-key") || "";
7036
- const ws = workspaceOf2(key);
7037
- return key && ws ? { key, ws, run: isRunningRow(el) } : null;
7038
- }).filter(Boolean);
7039
- if (!infos.length) return;
7040
- setTimeout(() => {
7041
- for (const info of infos) {
7042
- const row = findWsRow(info.ws);
7043
- const head = row && wsHead(row);
7044
- const collapsed = !row || !head || head.getAttribute("aria-expanded") === "false";
7045
- let set = runningByWs.get(info.ws);
7046
- if (collapsed && info.run) {
7047
- if (!set) {
7048
- set = /* @__PURE__ */ new Set();
7049
- runningByWs.set(info.ws, set);
7357
+ lastRunKeys = now;
7358
+ if (unseen.size > UNSEEN_LIMIT) unseen = new Set([...unseen].slice(-UNSEEN_LIMIT));
7359
+ }, 1e4);
7360
+ document.addEventListener("click", (e) => {
7361
+ const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
7362
+ if (item) record(item.getAttribute("data-task-item-key"));
7363
+ }, true);
7364
+ document.addEventListener("keydown", (e) => {
7365
+ if (e.key === "Escape" && popupEl) {
7366
+ e.preventDefault();
7367
+ e.stopImmediatePropagation();
7368
+ closePopup(false);
7369
+ return;
7370
+ }
7371
+ if (popupEl && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter")) {
7372
+ e.preventDefault();
7373
+ e.stopImmediatePropagation();
7374
+ if (e.key === "ArrowUp") moveUp();
7375
+ else if (e.key === "ArrowDown") moveDown();
7376
+ else if (!e.repeat) closePopup(true);
7377
+ return;
7378
+ }
7379
+ if (!e.altKey || e.ctrlKey || e.metaKey || e.isComposing) return;
7380
+ if (document.querySelector(".zcodepro-overlay")) return;
7381
+ const k = e.key.toLowerCase();
7382
+ const isHot = k === hotkeys.switchLast || k === hotkeys.switchPrev || k === hotkeys.switchNext || k === hotkeys.switchRecent || k === hotkeys.switchAttention;
7383
+ if (!isHot) return;
7384
+ e.preventDefault();
7385
+ e.stopImmediatePropagation();
7386
+ void (async () => {
7387
+ const cfg = await getConfig();
7388
+ if (cfg.features && cfg.features.sessionSwitch === false) return;
7389
+ if (k === hotkeys.switchLast) {
7390
+ if (e.repeat) return;
7391
+ closePopup(false);
7392
+ const live = findCurrent();
7393
+ if (live) record(live.getAttribute("data-task-item-key"));
7394
+ const target = live ? mru[0] === current ? mru[1] : mru.find((mk) => mk !== current) : mru[0];
7395
+ if (!target) {
7396
+ showToast(t().switcherEmpty);
7397
+ return;
7050
7398
  }
7051
- set.add(info.key);
7052
- } else if (set) {
7053
- set.delete(info.key);
7399
+ void switchTo(target).then((ok) => {
7400
+ if (!ok) showToast(t().switcherFailed, "error");
7401
+ });
7402
+ return;
7403
+ }
7404
+ if (k === hotkeys.switchAttention) {
7405
+ if (e.repeat) return;
7406
+ closePopup(false);
7407
+ if (!attentionList().length) {
7408
+ showToast(t().switcherAttentionEmpty);
7409
+ return;
7410
+ }
7411
+ await openPopup("attention");
7412
+ return;
7413
+ }
7414
+ if (mru.length < 2) {
7415
+ showToast(t().switcherEmpty);
7416
+ return;
7417
+ }
7418
+ if (k === hotkeys.switchRecent) {
7419
+ if (!e.repeat && !popupEl) await openPopup("mru");
7420
+ return;
7421
+ }
7422
+ if (!popupEl) await openPopup("mru");
7423
+ if (k === hotkeys.switchPrev) moveUp();
7424
+ else moveDown();
7425
+ })();
7426
+ }, true);
7427
+ document.addEventListener("keyup", (e) => {
7428
+ if (e.key === "Alt" && popupEl && movedSinceOpen) closePopup(true);
7429
+ }, true);
7430
+ window.addEventListener("blur", () => closePopup(false));
7431
+ }
7432
+
7433
+ // src/inject/features/newtab-panels.js
7434
+ var GRID_HINTS = ["\u8FD0\u884C\u547D\u4EE4\u548C\u67E5\u770B\u8F93\u51FA", "Run commands and view output"];
7435
+ var OFFICIAL_SUFFIX = "@zcode-plugins-official";
7436
+ var OPEN_FN_MARK = "\u6253\u5F00\u63D2\u4EF6 UI";
7437
+ var enabled3 = true;
7438
+ var injectTimer = 0;
7439
+ function eachFiber(visit) {
7440
+ const rootEl = document.getElementById("root");
7441
+ const key = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
7442
+ if (!key) return;
7443
+ let visited = 0;
7444
+ const walk = (f) => {
7445
+ if (!f || visited > 2e5) return;
7446
+ visited++;
7447
+ if (visit(f)) return true;
7448
+ return walk(f.child) || walk(f.sibling);
7449
+ };
7450
+ walk(rootEl[key]);
7451
+ }
7452
+ function findOpenSidePane() {
7453
+ let found = null;
7454
+ eachFiber((f) => {
7455
+ const et = f.elementType;
7456
+ if (et && typeof et === "object" && typeof et.$$typeof === "symbol" && String(et.$$typeof).includes("react.context")) {
7457
+ const val = f.memoizedProps && f.memoizedProps.value;
7458
+ if (typeof val === "function" && val.toString().includes(OPEN_FN_MARK)) {
7459
+ found = val;
7460
+ return true;
7054
7461
  }
7055
7462
  }
7056
- syncPeeks();
7057
- apply();
7058
- }, 0);
7463
+ return false;
7464
+ });
7465
+ return found;
7059
7466
  }
7060
- function apply() {
7467
+ function findBridge() {
7468
+ let holder = null;
7061
7469
  const seen = /* @__PURE__ */ new Set();
7062
- for (const row of document.querySelectorAll(WS_SEL())) {
7063
- const path = rowPath(row);
7064
- if (!path) continue;
7065
- seen.add(path);
7066
- const head = wsHead(row);
7067
- const collapsed = !head || head.getAttribute("aria-expanded") === "false";
7068
- const domRunning = !!(runningByWs.get(path) && runningByWs.get(path).size);
7069
- const normP = (v) => String(v || "").replace(/[\\/]+$/, "");
7070
- const running = enabled3 && collapsed && (activeProjectPaths.size ? [...activeProjectPaths].some((p2) => normP(p2) === normP(path)) : domRunning);
7071
- const icon = iconHolderOf(row);
7072
- icon?.classList.toggle("zcodepro-ws-running", running);
7073
- setRunningIcon(icon, running);
7470
+ const scan4 = (o) => {
7471
+ if (!o || typeof o !== "object" || seen.has(o) || holder) return;
7472
+ seen.add(o);
7473
+ if (typeof o.pluginUiBridgeService === "object" && o.pluginUiBridgeService) {
7474
+ holder = o.pluginUiBridgeService;
7475
+ return;
7476
+ }
7477
+ for (const v of Object.values(o)) if (v && typeof v === "object") scan4(v);
7478
+ };
7479
+ eachFiber((f) => {
7480
+ for (const h2 of [f.memoizedProps, f.memoizedState]) {
7481
+ if (!h2 || typeof h2 !== "object") continue;
7482
+ scan4(h2);
7483
+ if (holder) return true;
7484
+ }
7485
+ return false;
7486
+ });
7487
+ return holder;
7488
+ }
7489
+ function sessionScope() {
7490
+ const rows = [...document.querySelectorAll("[data-task-item-key]")];
7491
+ const cur = rows.find((e) => (e.className + "").includes("bg-selected")) || rows[rows.length - 1];
7492
+ const key = cur ? cur.getAttribute("data-task-item-key") : "";
7493
+ if (!key) return null;
7494
+ const i = key.indexOf(":sess_");
7495
+ return i > 0 ? { workspacePath: key.slice(0, i), sessionId: key.slice(i + 1) } : null;
7496
+ }
7497
+ async function thirdPartySurfaces(bridge, workspacePath) {
7498
+ try {
7499
+ const list = await bridge.listSurfaces({ workspacePath });
7500
+ return (list || []).filter((s) => !String(s.pluginId || "").endsWith(OFFICIAL_SUFFIX));
7501
+ } catch {
7502
+ return [];
7074
7503
  }
7075
- applySectionSpin();
7076
- for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
7077
7504
  }
7078
- var headInfoCache = /* @__PURE__ */ new WeakMap();
7079
- var PROJECT_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u9879\u76EE", "projects"]);
7080
- var TASK_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u4EFB\u52A1", "tasks"]);
7081
- function kindFromHeadText(head) {
7082
- const text = (head.textContent || "").trim().toLowerCase();
7083
- if (PROJECT_HEAD_TEXTS.has(text)) return "project";
7084
- if (TASK_HEAD_TEXTS.has(text)) return "task";
7085
- return null;
7505
+ function surfaceTitle(s, locale) {
7506
+ const title = s.title;
7507
+ const pick = (obj) => {
7508
+ const lang = (locale || "").split("-")[0];
7509
+ return obj[locale] || lang && obj[lang] || Object.entries(obj).find(([k]) => k.toLowerCase().startsWith(lang || "\0"))?.[1] || obj.en || Object.values(obj)[0] || "";
7510
+ };
7511
+ if (typeof title === "string") return title;
7512
+ if (title && typeof title === "object") {
7513
+ return pick(title) || s.pluginName || s.id;
7514
+ }
7515
+ return s.pluginName || s.id;
7516
+ }
7517
+ var PANEL_ICON_PATHS = ['<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>'];
7518
+ function findNewTabGrid() {
7519
+ const item = document.querySelector("[data-side-pane-open-tab-item]");
7520
+ if (!item || item.offsetParent === null) return null;
7521
+ const grid = item.parentElement;
7522
+ if (!grid || grid === document.body) return null;
7523
+ const text = grid.textContent || "";
7524
+ return GRID_HINTS.some((h2) => text.includes(h2)) ? grid : null;
7525
+ }
7526
+ async function injectPanels(grid) {
7527
+ if (grid.dataset.zcodeproPanels) return;
7528
+ const template = grid.querySelector("[data-side-pane-open-tab-item]");
7529
+ if (!template) return;
7530
+ const scope = sessionScope();
7531
+ if (!scope) return;
7532
+ const bridge = findBridge();
7533
+ if (!bridge) return;
7534
+ const surfaces = await thirdPartySurfaces(bridge, scope.workspacePath);
7535
+ if (!surfaces.length) {
7536
+ grid.dataset.zcodeproPanels = "1";
7537
+ return;
7538
+ }
7539
+ grid.dataset.zcodeproPanels = "1";
7540
+ const L = t();
7541
+ const locale = isZhLocale() ? "zh-CN" : "en-US";
7542
+ for (const s of surfaces) {
7543
+ const card = template.cloneNode(true);
7544
+ card.removeAttribute("data-side-pane-open-tab-item");
7545
+ card.setAttribute("data-zcodepro-panel", s.id);
7546
+ card.setAttribute("data-zcodepro-panel-name", s.pluginName || "");
7547
+ const title = surfaceTitle(s, locale);
7548
+ const desc = (s.pluginName || "") + " \xB7 " + L.ntpDescSuffix;
7549
+ const titleEl = card.querySelector("span.font-semibold");
7550
+ if (titleEl) titleEl.textContent = title;
7551
+ const descEl = card.querySelector("span[title]");
7552
+ if (descEl) {
7553
+ descEl.textContent = desc;
7554
+ descEl.setAttribute("title", desc);
7555
+ }
7556
+ const svg = card.querySelector("svg");
7557
+ if (svg) {
7558
+ svg.innerHTML = PANEL_ICON_PATHS[0];
7559
+ svg.setAttribute("viewBox", "0 0 24 24");
7560
+ }
7561
+ card.addEventListener("click", (ev) => {
7562
+ ev.stopPropagation();
7563
+ const sc = sessionScope();
7564
+ const open = findOpenSidePane();
7565
+ if (!sc || !open) {
7566
+ showToast(L.ntpNeedSession, "error");
7567
+ return;
7568
+ }
7569
+ try {
7570
+ open({
7571
+ parentSessionId: sc.sessionId,
7572
+ pluginId: s.pluginId,
7573
+ surfaceId: s.id,
7574
+ serverName: s.server,
7575
+ resourceUri: s.resourceUri,
7576
+ remoteSessionId: null,
7577
+ title,
7578
+ workspacePath: sc.workspacePath
7579
+ });
7580
+ } catch (e) {
7581
+ console.warn("[zcodepro] \u6253\u5F00\u63D2\u4EF6\u9762\u677F\u5931\u8D25", e);
7582
+ showToast(L.ntpNeedSession, "error");
7583
+ }
7584
+ });
7585
+ grid.append(card);
7586
+ }
7086
7587
  }
7087
- function sectionHeads() {
7088
- const heads = [];
7089
- for (const sv of document.querySelectorAll("svg[data-purpose-section-chevron]")) {
7090
- const btn = sv.closest("button");
7091
- if (btn) heads.push(btn);
7588
+ function tryInject() {
7589
+ if (!enabled3 || document.hidden) return;
7590
+ const grid = findNewTabGrid();
7591
+ if (!grid) return;
7592
+ if (grid.dataset.zcodeproPanels && !grid.querySelector("[data-zcodepro-panel]")) {
7593
+ delete grid.dataset.zcodeproPanels;
7092
7594
  }
7093
- for (const h3 of document.querySelectorAll("h3")) heads.push(h3);
7094
- return heads.map((head) => {
7095
- const cached = headInfoCache.get(head);
7096
- if (cached) return { head, cont: cached.cont, kind: cached.kind };
7097
- const textKind = head.tagName === "BUTTON" ? kindFromHeadText(head) : "task";
7098
- let cont = head.parentElement;
7099
- while (cont && !cont.querySelector("li[data-task-item-key]") && cont !== document.body) cont = cont.parentElement;
7100
- const contOk = cont && cont !== document.body;
7101
- const kind = textKind || (contOk ? cont.querySelector("section[data-project-id]") ? "project" : "task" : null);
7102
- headInfoCache.set(head, { cont: contOk ? cont : null, kind });
7103
- return { head, cont: contOk ? cont : null, kind };
7104
- }).filter((x) => x.head);
7595
+ void injectPanels(grid);
7105
7596
  }
7106
- var SEC_SPIN_CLASS = "zcodepro-ws-sec-spin";
7107
- var anyActiveCache = { at: 0, value: null, busy: false };
7108
- var sectionActive = { task: false, project: false };
7109
- var activeProjectPaths = /* @__PURE__ */ new Set();
7110
- setInterval(() => {
7111
- if (!anyActiveCache.busy && Date.now() - anyActiveCache.at >= 1e4) globalAnyActive();
7112
- }, 1e4);
7113
- function globalAnyActive() {
7114
- if (anyActiveCache.value !== null && Date.now() - anyActiveCache.at < 1e4) return anyActiveCache.value;
7115
- if (anyActiveCache.busy) return anyActiveCache.value === true;
7116
- anyActiveCache.busy = true;
7117
- sectionActive.task = false;
7118
- sectionActive.project = false;
7119
- activeProjectPaths = /* @__PURE__ */ new Set();
7120
- void (async () => {
7121
- let hit = null;
7122
- try {
7123
- const res = await rpc("/projects-v2").catch(() => null);
7124
- const services = findServices();
7125
- const ts = services && services.zcodeTaskService;
7126
- if (res && res.ok && res.available && ts && typeof ts.listActiveTaskIds === "function") {
7127
- hit = false;
7128
- const projPaths = /* @__PURE__ */ new Set();
7129
- for (const proj of res.projects || []) {
7130
- for (const r of proj.roots || []) {
7131
- const b = String(r.path || "").replace(/[\\/]+$/, "");
7132
- if (b) projPaths.add(b);
7133
- }
7134
- }
7135
- const taskSidePaths = /* @__PURE__ */ new Set();
7136
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
7137
- const sec = el.closest("section[data-project-id]");
7138
- if (sec) continue;
7139
- const k = el.getAttribute("data-task-item-key") || "";
7140
- const cut = k.lastIndexOf(":");
7141
- if (cut > 1) taskSidePaths.add(k.slice(0, cut).replace(/[\\/]+$/, ""));
7142
- }
7143
- const scanPaths = /* @__PURE__ */ new Set([...projPaths, ...taskSidePaths]);
7144
- const byKind = { task: /* @__PURE__ */ new Set(), project: /* @__PURE__ */ new Set() };
7145
- const byKindByPath = /* @__PURE__ */ new Map();
7146
- const rootToProject = /* @__PURE__ */ new Map();
7147
- const projectPaths = /* @__PURE__ */ new Map();
7148
- for (const proj of res.projects || []) {
7149
- const names = [];
7150
- for (const r of proj.roots || []) {
7151
- const b = String(r.path || "").replace(/[\\/]+$/, "");
7152
- if (b) {
7153
- rootToProject.set(b, proj.name);
7154
- names.push(b);
7155
- }
7156
- }
7157
- projectPaths.set(proj.name, names);
7158
- }
7159
- for (const base of scanPaths) {
7160
- const a = await ts.listActiveTaskIds({ workspacePath: base });
7161
- const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
7162
- const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
7163
- if (!ids.length) continue;
7164
- byKindByPath.set(base, ids);
7165
- const kind = projPaths.has(base) ? "project" : "task";
7166
- for (const id of ids) byKind[kind].add(id);
7167
- }
7168
- const allIds = [...byKind.task, ...byKind.project];
7169
- const st = allIds.length ? await rpc("/task-statuses?ids=" + encodeURIComponent(allIds.join(","))).catch(() => null) : null;
7170
- const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
7171
- const live = (id) => {
7172
- const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
7173
- if (rec === void 0) return true;
7174
- if (!TERMINAL.has(rec.status)) return true;
7175
- return Date.now() - rec.updatedAt < 3e5;
7176
- };
7177
- activeProjectPaths = /* @__PURE__ */ new Set();
7178
- for (const [base, ids] of byKindByPath) {
7179
- if (!projPaths.has(base)) continue;
7180
- if (ids.some(live)) {
7181
- const projName = rootToProject.get(base);
7182
- if (projName) for (const p2 of projectPaths.get(projName) || []) activeProjectPaths.add(p2);
7183
- }
7597
+ function startNewtabPanels() {
7598
+ ensureStyle();
7599
+ const refreshEnabled = () => {
7600
+ void getConfig().then((cfg) => {
7601
+ enabled3 = cfg.features ? cfg.features.newtabPanels !== false : true;
7602
+ }).catch(() => {
7603
+ });
7604
+ };
7605
+ refreshEnabled();
7606
+ window.addEventListener("zcodepro:config-changed", refreshEnabled);
7607
+ const observer2 = new MutationObserver(() => {
7608
+ if (injectTimer) return;
7609
+ injectTimer = setTimeout(() => {
7610
+ injectTimer = 0;
7611
+ tryInject();
7612
+ }, 300);
7613
+ });
7614
+ observer2.observe(document.body, { childList: true, subtree: true });
7615
+ setInterval(tryInject, 5e3);
7616
+ }
7617
+
7618
+ // src/inject/features/title-lock.js
7619
+ var TASK_SEL2 = "[data-task-item-key]";
7620
+ var SCAN_DEBOUNCE_MS3 = 150;
7621
+ var SCAN_FALLBACK_MS3 = 5e3;
7622
+ var BOOT_RETRY_MS = 5e3;
7623
+ var RECHECK_DELAY_MS = 1e3;
7624
+ var REVERIFY_MS = 3e3;
7625
+ var MAP_REFRESH_MS = 6e4;
7626
+ var installed3 = false;
7627
+ var seenRows = /* @__PURE__ */ new WeakSet();
7628
+ var enabled4 = true;
7629
+ var pinned = null;
7630
+ var resolved = false;
7631
+ var fetching = null;
7632
+ var pendingKeys = /* @__PURE__ */ new Set();
7633
+ function sessIdOf(key) {
7634
+ const i = key.indexOf(":sess_");
7635
+ return i > 0 ? key.slice(i + 1) : key;
7636
+ }
7637
+ function titleElsOf(item) {
7638
+ const copies = item.querySelectorAll("[data-task-title-copy]");
7639
+ if (copies.length) return [...copies];
7640
+ const t2 = item.querySelector('[class*="truncate"]');
7641
+ return t2 ? [t2] : [];
7642
+ }
7643
+ function findRows(key) {
7644
+ return [...document.querySelectorAll(`${TASK_SEL2}[data-task-item-key="${CSS.escape(key)}"]`)];
7645
+ }
7646
+ async function fetchPinned() {
7647
+ if (fetching) return fetching;
7648
+ const p = (async () => {
7649
+ const res = await rpc("/pinned-titles");
7650
+ if (!res || !res.ok) return null;
7651
+ resolved = true;
7652
+ enabled4 = res.enabled !== false;
7653
+ if (!enabled4) {
7654
+ pinned = null;
7655
+ return null;
7656
+ }
7657
+ pinned = new Map(Object.entries(res.titles || {}));
7658
+ return pinned;
7659
+ })();
7660
+ fetching = p;
7661
+ try {
7662
+ return await p;
7663
+ } finally {
7664
+ if (fetching === p) fetching = null;
7665
+ }
7666
+ }
7667
+ function isMismatch(item) {
7668
+ if (!enabled4 || !pinned) return null;
7669
+ const key = item.getAttribute("data-task-item-key") || "";
7670
+ const want = pinned.get(sessIdOf(key));
7671
+ if (want === void 0) return null;
7672
+ const els = titleElsOf(item);
7673
+ if (!els.length) return null;
7674
+ if ((els[0].textContent || "").trim() === String(want).trim()) return null;
7675
+ if (item.querySelector('input, textarea, [contenteditable="true"]')) return null;
7676
+ return key;
7677
+ }
7678
+ function writeTitle(el, text) {
7679
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
7680
+ const first = walker.nextNode();
7681
+ if (first && !walker.nextNode()) first.nodeValue = text;
7682
+ else el.textContent = text;
7683
+ }
7684
+ function scheduleEnforce(key, { immediate = false } = {}) {
7685
+ if (immediate) {
7686
+ if (enabled4 && pinned) {
7687
+ const want = pinned.get(sessIdOf(key));
7688
+ if (want !== void 0) {
7689
+ for (const item of findRows(key)) {
7690
+ for (const el of titleElsOf(item)) writeTitle(el, want);
7184
7691
  }
7185
- const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
7186
- if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
7187
- if ([...byKind.project].some(live)) sectionActive.project = true;
7188
- hit = sectionActive.task || sectionActive.project;
7189
7692
  }
7190
- } catch {
7191
7693
  }
7192
- if (hit !== null) anyActiveCache = { at: Date.now(), value: hit, busy: false };
7193
- else anyActiveCache.busy = false;
7194
- apply();
7195
- })();
7196
- return anyActiveCache.value === true;
7694
+ }
7695
+ if (pendingKeys.has(key)) return;
7696
+ pendingKeys.add(key);
7697
+ setTimeout(() => {
7698
+ pendingKeys.delete(key);
7699
+ void (async () => {
7700
+ const map = await fetchPinned();
7701
+ if (!map) return;
7702
+ const want = map.get(sessIdOf(key));
7703
+ if (want === void 0) return;
7704
+ let wrote = false;
7705
+ for (const item of findRows(key)) {
7706
+ if (isMismatch(item) !== key) continue;
7707
+ for (const el of titleElsOf(item)) writeTitle(el, want);
7708
+ wrote = true;
7709
+ }
7710
+ if (wrote) setTimeout(() => {
7711
+ void enforceKey(key);
7712
+ }, REVERIFY_MS);
7713
+ })();
7714
+ }, RECHECK_DELAY_MS);
7715
+ }
7716
+ async function enforceKey(key) {
7717
+ const map = await fetchPinned();
7718
+ if (!map) return;
7719
+ const want = map.get(sessIdOf(key));
7720
+ if (want === void 0) return;
7721
+ for (const item of findRows(key)) {
7722
+ if (isMismatch(item) !== key) continue;
7723
+ for (const el of titleElsOf(item)) writeTitle(el, want);
7724
+ }
7197
7725
  }
7198
- function applySectionSpin() {
7199
- if (!enabled3) {
7200
- document.querySelectorAll("." + SEC_SPIN_CLASS).forEach((el) => el.remove());
7201
- return;
7726
+ function scan3() {
7727
+ if (!enabled4 || !pinned || pinned.size === 0) return;
7728
+ for (const item of document.querySelectorAll(TASK_SEL2)) {
7729
+ const key = isMismatch(item);
7730
+ if (!key) {
7731
+ seenRows.add(item);
7732
+ continue;
7733
+ }
7734
+ const fresh = !seenRows.has(item);
7735
+ seenRows.add(item);
7736
+ scheduleEnforce(key, { immediate: fresh });
7202
7737
  }
7203
- for (const { head, cont, kind } of sectionHeads()) {
7204
- const running = !!(cont && cont.querySelector('li[data-task-item-key] svg[class*="lucide-loader"]'));
7205
- let expanded = true;
7206
- if (head.tagName === "BUTTON") expanded = head.getAttribute("aria-expanded") !== "false";
7207
- else {
7208
- const list = head.nextElementSibling;
7209
- expanded = !list || list.offsetHeight > 0;
7738
+ }
7739
+ var inTaskRow2 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
7740
+ function taskRowMutations2(muts) {
7741
+ for (const m of muts) {
7742
+ if (m.type === "attributes") {
7743
+ if (inTaskRow2(m.target)) return true;
7744
+ } else if (m.type === "characterData") {
7745
+ if (m.target.parentElement && inTaskRow2(m.target.parentElement)) return true;
7746
+ } else if (inTaskRow2(m.target)) {
7747
+ return true;
7748
+ } else {
7749
+ for (const n of m.addedNodes) {
7750
+ if (n instanceof Element && (n.matches(TASK_SEL2) || !!n.querySelector(TASK_SEL2))) return true;
7751
+ }
7210
7752
  }
7211
- if (!kind) return;
7212
- const scoped = kind === "project" ? sectionActive.project : sectionActive.task;
7213
- const want = !expanded && scoped;
7214
- const existing = head.querySelector(":scope > ." + SEC_SPIN_CLASS);
7215
- if (want && !existing) {
7216
- const span = document.createElement("span");
7217
- span.className = "flex size-4 shrink-0 items-center justify-center " + SEC_SPIN_CLASS;
7218
- span.append(makeLoaderSvg("size-4"));
7219
- head.prepend(span);
7220
- } else if (!want && existing) {
7221
- existing.remove();
7753
+ }
7754
+ return false;
7755
+ }
7756
+ var scanDebounce3 = 0;
7757
+ function scheduleScan2() {
7758
+ if (scanDebounce3) return;
7759
+ scanDebounce3 = setTimeout(() => {
7760
+ scanDebounce3 = 0;
7761
+ scan3();
7762
+ }, SCAN_DEBOUNCE_MS3);
7763
+ }
7764
+ var mapRefreshTimer = 0;
7765
+ function refreshMapSoon() {
7766
+ if (mapRefreshTimer) return;
7767
+ const tick2 = () => {
7768
+ if (document.hidden) {
7769
+ mapRefreshTimer = setTimeout(tick2, 1e4);
7770
+ return;
7222
7771
  }
7772
+ mapRefreshTimer = 0;
7773
+ if (enabled4 && resolved) void fetchPinned().then((m) => {
7774
+ if (m) scan3();
7775
+ });
7776
+ };
7777
+ mapRefreshTimer = setTimeout(tick2, MAP_REFRESH_MS);
7778
+ }
7779
+ var editingKey = null;
7780
+ var bootDelay = 0;
7781
+ function scheduleBootRetry() {
7782
+ bootDelay = bootDelay ? Math.min(bootDelay * 2, MAP_REFRESH_MS) : BOOT_RETRY_MS;
7783
+ setTimeout(() => {
7784
+ if (resolved) return;
7785
+ if (!document.hidden) void bootstrap();
7786
+ scheduleBootRetry();
7787
+ }, bootDelay);
7788
+ }
7789
+ async function bootstrap() {
7790
+ const map = await fetchPinned();
7791
+ if (map) scan3();
7792
+ }
7793
+ function setRowTitle(key, text) {
7794
+ for (const item of findRows(key)) {
7795
+ for (const el of titleElsOf(item)) writeTitle(el, text);
7223
7796
  }
7224
7797
  }
7225
- function schedulePeek(ws, delay) {
7226
- clearTimeout(peekTimers.get(ws));
7227
- peekTimers.set(ws, setTimeout(() => {
7228
- peekTimers.delete(ws);
7229
- void peek(ws);
7230
- }, delay));
7798
+ function refreshPinnedTitles() {
7799
+ return fetchPinned().then((m) => {
7800
+ if (m) scan3();
7801
+ });
7231
7802
  }
7232
- function clearPeek(ws) {
7233
- const t2 = peekTimers.get(ws);
7234
- if (t2) {
7235
- clearTimeout(t2);
7236
- peekTimers.delete(ws);
7803
+ var RENAME_CTX_VALID_MS = 15e3;
7804
+ var renameCtx = null;
7805
+ var renamePrefilled = /* @__PURE__ */ new WeakSet();
7806
+ function setNativeValue(el, text) {
7807
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
7808
+ Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
7809
+ } else {
7810
+ el.textContent = text;
7237
7811
  }
7812
+ el.dispatchEvent(new Event("input", { bubbles: true }));
7238
7813
  }
7239
- function syncPeeks() {
7240
- for (const [ws, set] of runningByWs) {
7241
- if (!enabled3 || !set.size) {
7242
- clearPeek(ws);
7243
- continue;
7244
- }
7245
- const row = findWsRow(ws);
7246
- const head = row && wsHead(row);
7247
- if (!row || !head || head.getAttribute("aria-expanded") !== "false" || wsWithRows.has(ws)) {
7248
- clearPeek(ws);
7249
- continue;
7814
+ function prefillRenameDialog() {
7815
+ if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
7816
+ renameCtx = null;
7817
+ return;
7818
+ }
7819
+ if (!enabled4 || !pinned) return;
7820
+ const want = pinned.get(sessIdOf(renameCtx.key));
7821
+ if (want === void 0) return;
7822
+ for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
7823
+ if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
7824
+ if (el.closest(TASK_SEL2) || el.closest(".zcodepro-overlay")) continue;
7825
+ const cur = String(el.value ?? el.textContent ?? "").trim();
7826
+ if (!cur) continue;
7827
+ renamePrefilled.add(el);
7828
+ if (cur !== String(want).trim()) setNativeValue(el, String(want));
7829
+ renameCtx = null;
7830
+ return;
7831
+ }
7832
+ }
7833
+ function startTitleLock() {
7834
+ if (installed3 || typeof document === "undefined") return;
7835
+ installed3 = true;
7836
+ void bootstrap();
7837
+ new MutationObserver((muts) => {
7838
+ if (renameCtx) prefillRenameDialog();
7839
+ if (!taskRowMutations2(muts)) return;
7840
+ refreshMapSoon();
7841
+ scheduleScan2();
7842
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
7843
+ document.addEventListener("contextmenu", (e) => {
7844
+ const row = e.target instanceof Element && e.target.closest(TASK_SEL2);
7845
+ renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
7846
+ }, true);
7847
+ document.addEventListener("click", (e) => {
7848
+ if (!renameCtx || renameCtx.armed) return;
7849
+ const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
7850
+ if (item && /重命名|rename/i.test(item.textContent || "")) {
7851
+ renameCtx.armed = true;
7852
+ renameCtx.at = Date.now();
7250
7853
  }
7251
- if (!peekTimers.has(ws) && !activePeeks.has(ws)) schedulePeek(ws, peekedWs.has(ws) ? PEEK_REPEAT_MS : PEEK_FIRST_MS);
7854
+ }, true);
7855
+ scheduleBootRetry();
7856
+ setInterval(() => {
7857
+ if (!document.hidden) scan3();
7858
+ }, SCAN_FALLBACK_MS3);
7859
+ document.addEventListener("visibilitychange", () => {
7860
+ if (!document.hidden) scan3();
7861
+ });
7862
+ window.addEventListener("zcodepro:config-changed", () => {
7863
+ void bootstrap();
7864
+ });
7865
+ document.addEventListener("focusin", (e) => {
7866
+ const el = e.target;
7867
+ if (!(el instanceof Element)) return;
7868
+ if (!el.matches('input, textarea, [contenteditable="true"]')) return;
7869
+ const row = el.closest(TASK_SEL2);
7870
+ editingKey = row ? row.getAttribute("data-task-item-key") || "" : null;
7871
+ });
7872
+ document.addEventListener("focusout", () => {
7873
+ if (editingKey === null) return;
7874
+ editingKey = null;
7875
+ void fetchPinned().then((m) => {
7876
+ if (m) scan3();
7877
+ });
7878
+ });
7879
+ }
7880
+
7881
+ // src/inject/features/title-gen.js
7882
+ var CONV2 = '[class*="@md/conversation"]';
7883
+ var USER_ROW = '[class*="user-row"]';
7884
+ var SCAN_DEBOUNCE_MS4 = 300;
7885
+ var SCAN_FALLBACK_MS4 = 5e3;
7886
+ var SELECT_WAIT_MS = 2e3;
7887
+ var MSG_WAIT_MS = 1e4;
7888
+ var QUIET_FALLBACK_MS = 12e4;
7889
+ var COLLECT_MAX_MS = 6e5;
7890
+ var installed4 = false;
7891
+ var knownKeys = /* @__PURE__ */ new Set();
7892
+ var attempted = /* @__PURE__ */ new Set();
7893
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
7894
+ function sessIdOf2(key) {
7895
+ const i = key.indexOf(":sess_");
7896
+ return i > 0 ? key.slice(i + 1) : "";
7897
+ }
7898
+ function currentKey() {
7899
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7900
+ if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
7252
7901
  }
7902
+ return "";
7253
7903
  }
7254
- var peekChain = Promise.resolve();
7255
- async function peek(ws, opts) {
7256
- if (!opts?.manual) {
7257
- const auth = await authoritativeIdle(ws);
7258
- if (auth !== null) {
7259
- if (auth) {
7260
- runningByWs.delete(ws);
7261
- } else {
7262
- let set = runningByWs.get(ws);
7263
- if (!set) {
7264
- set = /* @__PURE__ */ new Set();
7265
- runningByWs.set(ws, set);
7904
+ function userTexts() {
7905
+ return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
7906
+ const el = r.querySelector(".whitespace-pre-wrap");
7907
+ return ((el ? el.textContent : r.innerText) || "").trim();
7908
+ }).filter(Boolean);
7909
+ }
7910
+ var URL_RE = /https?:\/\/[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g;
7911
+ function assistantText() {
7912
+ const els = [...document.querySelectorAll('[class*="@md/conversation"] [class*="assistant-row"]')].filter((e) => !e.querySelector("[data-is-streaming]"));
7913
+ const last = els[els.length - 1];
7914
+ if (!last) return "";
7915
+ const text = (last.innerText || "").replace(URL_RE, "").trim();
7916
+ return text.slice(0, 600);
7917
+ }
7918
+ function linkDominant(t2) {
7919
+ const raw = t2.replace(/\s+/g, "");
7920
+ if (!raw) return true;
7921
+ const stripped = raw.replace(URL_RE, "");
7922
+ return stripped.length < raw.length / 3;
7923
+ }
7924
+ async function onNewTaskRow(key) {
7925
+ const selDeadline = Date.now() + SELECT_WAIT_MS;
7926
+ while (currentKey() !== key && Date.now() < selDeadline) await sleep2(200);
7927
+ if (currentKey() !== key) return;
7928
+ const cfg = await getConfig().catch(() => null);
7929
+ if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
7930
+ const sessId = sessIdOf2(key);
7931
+ if (!sessId || attempted.has(sessId)) return;
7932
+ attempted.add(sessId);
7933
+ let texts = [];
7934
+ const msgDeadline = Date.now() + MSG_WAIT_MS;
7935
+ while (Date.now() < msgDeadline) {
7936
+ texts = userTexts();
7937
+ if (texts.length) break;
7938
+ await sleep2(400);
7939
+ }
7940
+ if (!texts.length) return;
7941
+ let assistantSeen = false;
7942
+ const convObserver = new MutationObserver((muts) => {
7943
+ if (currentKey() !== key) return;
7944
+ for (const m of muts) {
7945
+ const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
7946
+ if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
7947
+ for (const n of [t2, ...m.addedNodes]) {
7948
+ if (n instanceof Element && n.closest && n.closest(CONV2)) {
7949
+ assistantSeen = true;
7950
+ return;
7266
7951
  }
7267
- set.add(ws + ":active");
7268
- schedulePeek(ws, PEEK_REPEAT_MS);
7269
7952
  }
7270
- apply();
7271
- return true;
7272
7953
  }
7273
- }
7274
- const run = () => peekInner(ws, opts);
7275
- const p = peekChain.then(run, run);
7276
- peekChain = p.catch(() => {
7277
7954
  });
7278
- return p;
7279
- }
7280
- async function peekInner(ws, opts) {
7281
- if (!opts?.manual && !enabled3 || document.hidden || activePeeks.has(ws)) return false;
7282
- const row = findWsRow(ws);
7283
- const head = row && wsHead(row);
7284
- if (!row || !head || head.getAttribute("aria-expanded") !== "false") return false;
7285
- if (!currentTaskRow()) {
7286
- schedulePeek(ws, PEEK_REPEAT_MS);
7287
- return false;
7288
- }
7289
- activePeeks.add(ws);
7290
- peekedWs.add(ws);
7291
- const rect = row.getBoundingClientRect();
7292
- const frozen = row.cloneNode(true);
7293
- frozen.classList.add("zcodepro-ws-frozen");
7294
- frozen.style.left = rect.left + "px";
7295
- frozen.style.top = rect.top + "px";
7296
- frozen.style.width = rect.width + "px";
7297
- frozen.style.height = rect.height + "px";
7298
- document.body.append(frozen);
7299
- row.style.visibility = "hidden";
7300
- const hider = installPeekHider(ws);
7301
- let readOk = false;
7955
+ convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
7956
+ const startAt = Date.now();
7957
+ let lastCount = texts.length;
7958
+ let lastChange = Date.now();
7302
7959
  try {
7303
- toggleNavSafe(head);
7304
- readOk = await settleAndRead(ws);
7305
- const rowNow = findWsRow(ws) || row;
7306
- const headNow = wsHead(rowNow);
7307
- if (headNow && headNow.getAttribute("aria-expanded") === "true" && !userTouched(rowNow)) {
7308
- toggleNavSafe(headNow);
7309
- await waitForRowsGone(ws);
7960
+ while (Date.now() - startAt < COLLECT_MAX_MS) {
7961
+ await sleep2(500);
7962
+ if (currentKey() !== key) {
7963
+ lastChange = Date.now();
7964
+ continue;
7965
+ }
7966
+ texts = userTexts();
7967
+ if (texts.length !== lastCount) {
7968
+ lastCount = texts.length;
7969
+ lastChange = Date.now();
7970
+ }
7971
+ if (assistantSeen) break;
7972
+ if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
7310
7973
  }
7311
7974
  } finally {
7312
- removePeekHider(hider);
7313
- row.style.visibility = "";
7314
- frozen.remove();
7315
- activePeeks.delete(ws);
7975
+ convObserver.disconnect();
7316
7976
  }
7317
- apply();
7318
- const set = runningByWs.get(ws);
7319
- if (set && set.size && enabled3) schedulePeek(ws, PEEK_REPEAT_MS);
7320
- return readOk;
7321
- }
7322
- function wsRunningSnapshot() {
7323
- const curKey = currentTaskRow()?.getAttribute("data-task-item-key") || "";
7324
- return {
7325
- running: [...runningByWs.entries()].filter(([, set]) => set.size > 0).map(([ws]) => ws),
7326
- current: curKey ? workspaceOf2(curKey) || null : null
7327
- };
7328
- }
7329
- async function verifyWorkspaceNotRunning(ws) {
7330
- if (!ws || document.hidden) return false;
7331
- const row = findWsRow(ws);
7332
- const head = row && wsHead(row);
7333
- if (!row || !head) return false;
7334
- if (head.getAttribute("aria-expanded") !== "false") {
7335
- let run = false;
7336
- for (const el of domRowsOf(ws)) if (isRunningRow(el)) run = true;
7337
- return !run;
7977
+ if (!texts.length) return;
7978
+ if (linkDominant(texts.join("\n"))) {
7979
+ const deadline = Date.now() + 6e4;
7980
+ while (Date.now() < deadline) {
7981
+ await sleep2(2e3);
7982
+ if (currentKey() !== key) continue;
7983
+ const a = assistantText();
7984
+ if (a && a.length > 80) {
7985
+ texts.push(`\uFF08\u9996\u8F6E\u56DE\u7B54\u6458\u8981\uFF09${a}`);
7986
+ break;
7987
+ }
7988
+ }
7338
7989
  }
7339
- let verified = false;
7340
- try {
7341
- verified = await peek(ws, { manual: true }) === true;
7342
- } catch {
7990
+ const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
7991
+ let res = await callTitleGen();
7992
+ for (const delay of [5e3, 2e4]) {
7993
+ if (res && res.ok) break;
7994
+ await sleep2(delay);
7995
+ res = await callTitleGen();
7996
+ }
7997
+ if (res && res.ok && res.written && res.title) {
7998
+ setRowTitle(key, res.title);
7999
+ void refreshPinnedTitles();
7343
8000
  }
7344
- if (!verified) return false;
7345
- const set = runningByWs.get(ws);
7346
- return !(set && set.size);
7347
- }
7348
- function settleAndRead(ws) {
7349
- return new Promise((resolve) => {
7350
- const t0 = Date.now();
7351
- let lastCount = -1;
7352
- let stableAt = 0;
7353
- let zeroRows = false;
7354
- const timer = setInterval(() => {
7355
- const rows = domRowsOf(ws);
7356
- const elapsed = Date.now() - t0;
7357
- if (rows.length !== lastCount) {
7358
- lastCount = rows.length;
7359
- stableAt = Date.now();
7360
- }
7361
- const settled = rows.length > 0 && rows.length === lastCount && Date.now() - stableAt >= PEEK_SETTLE_MS;
7362
- if (!settled && elapsed < PEEK_MAX_MS) return;
7363
- clearInterval(timer);
7364
- if (lastCount === 0) {
7365
- zeroRows = true;
7366
- } else {
7367
- let set = runningByWs.get(ws);
7368
- if (!set) {
7369
- set = /* @__PURE__ */ new Set();
7370
- runningByWs.set(ws, set);
7371
- }
7372
- for (const el of rows) {
7373
- const key = el.getAttribute("data-task-item-key") || "";
7374
- if (isRunningRow(el)) set.add(key);
7375
- else set.delete(key);
7376
- }
7377
- }
7378
- resolve(!zeroRows);
7379
- }, 80);
7380
- });
7381
- }
7382
- var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
7383
- var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7384
- function installPeekHider(ws) {
7385
- const rowSel3 = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
7386
- for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
7387
- const style = document.createElement("style");
7388
- style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
7389
- document.head.append(style);
7390
- return { style, sel: rowSel3 };
7391
- }
7392
- function removePeekHider(hider) {
7393
- hider.style.remove();
7394
- for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
7395
- }
7396
- function waitForRowsGone(ws) {
7397
- const sel = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
7398
- return new Promise((resolve) => {
7399
- const t0 = Date.now();
7400
- const timer = setInterval(() => {
7401
- if (!document.querySelector(sel) || Date.now() - t0 >= PEEK_UNMOUNT_WAIT_MS) {
7402
- clearInterval(timer);
7403
- resolve();
7404
- }
7405
- }, 40);
7406
- });
7407
- }
7408
- var TOUCH_WINDOW_MS = 800;
7409
- var wsTouchAt = /* @__PURE__ */ new WeakMap();
7410
- function userTouched(row) {
7411
- const at = wsTouchAt.get(row);
7412
- return !!at && Date.now() - at < TOUCH_WINDOW_MS;
7413
- }
7414
- function inSel(node, sel) {
7415
- return !!(node instanceof Element && node.closest && node.closest(sel));
7416
8001
  }
7417
- function relevant(muts) {
7418
- for (const m of muts) {
7419
- if (inSel(m.target, TASK_SEL3) || inSel(m.target, WS_SEL())) return true;
7420
- for (const n of m.addedNodes) {
7421
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL3) || n.querySelector(WS_SEL()))) return true;
7422
- }
7423
- for (const n of m.removedNodes) {
7424
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL3))) return true;
7425
- }
8002
+ function scanForNewRows() {
8003
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
8004
+ const key = el.getAttribute("data-task-item-key") || "";
8005
+ if (!key || knownKeys.has(key)) continue;
8006
+ knownKeys.add(key);
8007
+ void onNewTaskRow(key);
7426
8008
  }
7427
- return false;
7428
8009
  }
7429
- var pollMs = CONFIG_POLL_MIN_MS;
7430
- async function refreshConfig() {
7431
- try {
7432
- const res = await rpc("/config");
7433
- if (res && res.ok && res.config && res.config.features) {
7434
- const next = res.config.features.wsRunningSpin !== false;
7435
- if (next !== enabled3) {
7436
- enabled3 = next;
7437
- if (!next) {
7438
- for (const ws of [...peekTimers.keys()]) clearPeek(ws);
7439
- apply();
7440
- }
7441
- return true;
8010
+ var TASK_SEL3 = "[data-task-item-key]";
8011
+ var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL3));
8012
+ function taskRowMutations3(muts) {
8013
+ for (const m of muts) {
8014
+ if (m.type === "attributes") {
8015
+ if (inTaskRow3(m.target)) return true;
8016
+ } else if (m.type === "characterData") {
8017
+ if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
8018
+ } else if (inTaskRow3(m.target)) {
8019
+ return true;
8020
+ } else {
8021
+ for (const n of m.addedNodes) {
8022
+ if (n instanceof Element && (n.matches(TASK_SEL3) || !!n.querySelector(TASK_SEL3))) return true;
7442
8023
  }
7443
8024
  }
7444
- } catch {
7445
8025
  }
7446
8026
  return false;
7447
8027
  }
7448
- function pollConfig() {
7449
- setTimeout(async () => {
7450
- if (document.hidden) pollMs = CONFIG_POLL_MAX_MS;
7451
- else pollMs = await refreshConfig() ? CONFIG_POLL_MIN_MS : Math.min(Math.round(pollMs * 1.5), CONFIG_POLL_MAX_MS);
7452
- pollConfig();
7453
- }, pollMs);
7454
- }
7455
8028
  var scanDebounce4 = 0;
7456
- function startWsRunningSpin() {
8029
+ function scheduleScan3() {
8030
+ if (scanDebounce4) return;
8031
+ scanDebounce4 = setTimeout(() => {
8032
+ scanDebounce4 = 0;
8033
+ scanForNewRows();
8034
+ }, SCAN_DEBOUNCE_MS4);
8035
+ }
8036
+ function startTitleGen() {
7457
8037
  if (installed4 || typeof document === "undefined") return;
7458
8038
  installed4 = true;
7459
- void refreshConfig();
7460
- pollConfig();
7461
- const observer2 = new MutationObserver((muts) => {
7462
- if (!relevant(muts)) return;
7463
- handleRemovals(muts);
7464
- if (!scanDebounce4) {
7465
- scanDebounce4 = setTimeout(() => {
7466
- scanDebounce4 = 0;
7467
- scan3();
7468
- }, SCAN_DEBOUNCE_MS4);
7469
- }
7470
- });
7471
- observer2.observe(document.body, { childList: true, subtree: true });
7472
- scan3();
8039
+ scanForNewRows();
8040
+ new MutationObserver((muts) => {
8041
+ if (taskRowMutations3(muts)) scheduleScan3();
8042
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
7473
8043
  setInterval(() => {
7474
- if (!document.hidden && enabled3) scan3();
8044
+ if (!document.hidden) scanForNewRows();
7475
8045
  }, SCAN_FALLBACK_MS4);
7476
8046
  document.addEventListener("visibilitychange", () => {
7477
- if (!document.hidden) scan3();
8047
+ if (!document.hidden) scanForNewRows();
7478
8048
  });
7479
- document.addEventListener("click", (e) => {
7480
- if (!e.isTrusted) return;
7481
- const row = e.target instanceof Element && e.target.closest(WS_SEL());
7482
- if (row) wsTouchAt.set(row, Date.now());
7483
- }, true);
7484
8049
  }
7485
8050
 
7486
8051
  // src/inject/features/idle-reclaim.js
@@ -7614,7 +8179,7 @@
7614
8179
  var SCAN_FALLBACK_MS5 = 5e3;
7615
8180
  var ROW_SEL = "[data-task-item-key]";
7616
8181
  var MENU_SEL = '[data-testid="task-new-button"], [data-testid="automations-open"], [data-testid="plugin-store-sidebar-open"], [data-testid="sidebar-search-open"], [data-slot="command"], [data-testid="desktop-top-nav-back"]';
7617
- var enabled4 = true;
8182
+ var enabled5 = true;
7618
8183
  var rowEl = null;
7619
8184
  var origs = null;
7620
8185
  var syncTimer = 0;
@@ -7757,7 +8322,7 @@
7757
8322
  return scroll.getBoundingClientRect().width < NARROW_SIDEBAR_PX;
7758
8323
  }
7759
8324
  function sync() {
7760
- if (!enabled4) return;
8325
+ if (!enabled5) return;
7761
8326
  const parts = findParts();
7762
8327
  if (!parts) {
7763
8328
  if (rowEl) rowEl.style.display = "none";
@@ -7819,8 +8384,8 @@
7819
8384
  }
7820
8385
  async function refreshConfig2() {
7821
8386
  const cfg = await getConfig(true).catch(() => null);
7822
- enabled4 = !cfg || !cfg.features || cfg.features.toolbarIcons !== false;
7823
- if (enabled4) sync();
8387
+ enabled5 = !cfg || !cfg.features || cfg.features.toolbarIcons !== false;
8388
+ if (enabled5) sync();
7824
8389
  else teardown();
7825
8390
  }
7826
8391
  function startToolbarIcons() {
@@ -7846,7 +8411,7 @@
7846
8411
  var STORE_KEY = "zcodepro:pinned-collapsed";
7847
8412
  var SYNC_DEBOUNCE_MS2 = 300;
7848
8413
  var SCAN_FALLBACK_MS6 = 5e3;
7849
- var enabled5 = true;
8414
+ var enabled6 = true;
7850
8415
  var syncTimer2 = 0;
7851
8416
  function findPinnedHeader() {
7852
8417
  for (const h3 of document.querySelectorAll("h3")) {
@@ -7903,7 +8468,7 @@
7903
8468
  }
7904
8469
  h3.classList.add(HEAD_CLASS);
7905
8470
  h3.addEventListener("click", () => {
7906
- if (enabled5) toggle();
8471
+ if (enabled6) toggle();
7907
8472
  });
7908
8473
  const btn = h("button", {
7909
8474
  type: "button",
@@ -7912,7 +8477,7 @@
7912
8477
  "aria-expanded": collapsed ? "false" : "true",
7913
8478
  onClick: (e) => {
7914
8479
  e.stopPropagation();
7915
- if (enabled5) toggle();
8480
+ if (enabled6) toggle();
7916
8481
  }
7917
8482
  });
7918
8483
  btn.append(chevronSvg());
@@ -7920,7 +8485,7 @@
7920
8485
  return true;
7921
8486
  }
7922
8487
  function sync2() {
7923
- if (enabled5) apply2();
8488
+ if (enabled6) apply2();
7924
8489
  }
7925
8490
  function scheduleSync2() {
7926
8491
  if (syncTimer2) return;
@@ -7949,8 +8514,8 @@
7949
8514
  }
7950
8515
  async function refreshConfig3() {
7951
8516
  const cfg = await getConfig(true).catch(() => null);
7952
- enabled5 = !cfg || !cfg.features || cfg.features.pinnedCollapse !== false;
7953
- if (enabled5) sync2();
8517
+ enabled6 = !cfg || !cfg.features || cfg.features.pinnedCollapse !== false;
8518
+ if (enabled6) sync2();
7954
8519
  else teardown2();
7955
8520
  }
7956
8521
  function startPinnedCollapse() {
@@ -8069,6 +8634,10 @@
8069
8634
  startToolbarIcons();
8070
8635
  } catch {
8071
8636
  }
8637
+ try {
8638
+ startNewtabPanels();
8639
+ } catch {
8640
+ }
8072
8641
  try {
8073
8642
  startPinnedCollapse();
8074
8643
  } catch {