zcode-pro 1.2.1 → 1.3.1

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
@@ -286,20 +286,19 @@
286
286
  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",
287
287
  ntpDescSuffix: "\u63D2\u4EF6\u9762\u677F",
288
288
  ntpNeedSession: "\u5148\u6253\u5F00\u4E00\u4E2A\u4EFB\u52A1\u4F1A\u8BDD\uFF0C\u518D\u4ECE\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u63D2\u4EF6\u9762\u677F",
289
- featureStatusBar: "\u72B6\u6001\u680F\u7EDF\u8BA1",
290
289
  featureStatusBarDesc: "\u4E3B\u754C\u9762\u5E95\u90E8\u53F3\u4E0B\u89D2\u663E\u793A\u7EDF\u8BA1\u9879\uFF1A\u4E0A\u4E0B\u6587\u5360\u7528\u3001\u4F1A\u8BDD/\u4ECA\u65E5/\u7D2F\u8BA1 token\u3001\u4EFB\u52A1\u6570\u7B49\uFF1B\u5404\u9879\u7684\u987A\u5E8F\u4E0E\u663E\u9690\u5728\u201C\u6837\u5F0F \u2192 \u72B6\u6001\u680F\u201D\u91CC\u8C03\u6574\u3002",
291
290
  tabStylesPane: "\u6837\u5F0F\u8C03\u6574",
292
291
  tabStatusPane: "\u72B6\u6001\u680F",
293
292
  sbItemCtx: "\u4E0A\u4E0B\u6587\u5360\u7528",
294
293
  sbItemQuestions: "\u63D0\u95EE\u6B21\u6570",
294
+ sbMaster: "\u542F\u7528\u7EDF\u8BA1",
295
+ sbMasterDesc: "\u603B\u5F00\u5173\uFF1A\u5173\u95ED\u540E\u5E95\u90E8\u72B6\u6001\u680F\u4E0E\u6BCF\u8F6E\u56DE\u7B54\u7684\u7528\u91CF\u6807\u6CE8\u4E00\u8D77\u505C\u7528\u3002",
295
296
  sbGroupSession: "\u4F1A\u8BDD\u7EDF\u8BA1\uFF08\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\uFF09",
296
297
  sbGroupGlobal: "\u603B\u7EDF\u8BA1",
297
298
  sbGroupTurn: "\u6BCF\u8F6E\u7EDF\u8BA1\uFF08\u56DE\u7B54\u672B\u5C3E\uFF09",
298
299
  sbLabelIn: "\u8F93\u5165",
299
300
  sbLabelOut: "\u8F93\u51FA",
300
301
  sbLabelCache: "\u7F13\u5B58",
301
- featureTurnUsage: "\u6BCF\u8F6E\u56DE\u7B54 token \u7528\u91CF",
302
- featureTurnUsageDesc: "\u5728\u6BCF\u6761\u56DE\u7B54\u672B\u5C3E\u64CD\u4F5C\u6309\u94AE\u7684\u65F6\u95F4\u540E\u9762\u663E\u793A\u8BE5\u8F6E\u7684\u8F93\u5165/\u8F93\u51FA token\uFF08\u672C\u5730\u8BB0\u5F55\u53EA\u4FDD\u7559\u6700\u8FD1\u82E5\u5E72\u8F6E\uFF0C\u5BF9\u4E0D\u4E0A\u65F6\u95F4\u7684\u65E7\u56DE\u7B54\u4E0D\u663E\u793A\uFF09\u3002",
303
302
  turnUsageTitle: "\u672C\u8F6E token",
304
303
  sbLabelQuestions: "\u63D0\u95EE",
305
304
  sbItemTodayTokens: "\u4ECA\u65E5 token",
@@ -743,20 +742,19 @@
743
742
  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.',
744
743
  ntpDescSuffix: "plugin panel",
745
744
  ntpNeedSession: "Open a task session first, then open plugin panels from the new tab page",
746
- featureStatusBar: "Status bar statistics",
747
745
  featureStatusBarDesc: 'Shows statistics at the bottom-right of the main view: context usage, session/today/total tokens, task count, etc.; reorder or hide each item under "Styles \u2192 Status bar".',
748
746
  tabStylesPane: "Styles",
749
747
  tabStatusPane: "Status bar",
750
748
  sbItemCtx: "Context usage",
751
749
  sbItemQuestions: "Questions asked",
750
+ sbMaster: "Enable statistics",
751
+ sbMasterDesc: "Master switch: turning this off disables both the status bar and per-turn labels.",
752
752
  sbGroupSession: "Session statistics (follows current session)",
753
753
  sbGroupGlobal: "Global statistics",
754
754
  sbGroupTurn: "Per-turn statistics (answer rows)",
755
755
  sbLabelIn: "in",
756
756
  sbLabelOut: "out",
757
757
  sbLabelCache: "cache",
758
- featureTurnUsage: "Per-turn token usage",
759
- featureTurnUsageDesc: "Shows that turn's in/out tokens after the time in each answer's action row (local records keep only the most recent turns; older answers without a match stay unlabelled).",
760
758
  turnUsageTitle: "Turn tokens",
761
759
  sbLabelQuestions: "asks",
762
760
  sbItemTodayTokens: "Today tokens",
@@ -4163,16 +4161,6 @@
4163
4161
  if (await setFeature("newtabPanels", next)) f.newtabPanels = next;
4164
4162
  refreshRows();
4165
4163
  }),
4166
- settingRow(L.featureStatusBar, L.featureStatusBarDesc, f.statusBar !== false, async () => {
4167
- const next = !(f.statusBar !== false);
4168
- if (await setFeature("statusBar", next)) f.statusBar = next;
4169
- refreshRows();
4170
- }),
4171
- settingRow(L.featureTurnUsage, L.featureTurnUsageDesc, f.turnUsage !== false, async () => {
4172
- const next = !(f.turnUsage !== false);
4173
- if (await setFeature("turnUsage", next)) f.turnUsage = next;
4174
- refreshRows();
4175
- }),
4176
4164
  (() => {
4177
4165
  const repoLink = h("span", {
4178
4166
  class: "cursor-pointer underline-offset-4 hover:underline",
@@ -4717,8 +4705,30 @@
4717
4705
  renderGroup(sbGlobalList, STATUS_GLOBAL_IDS);
4718
4706
  };
4719
4707
  renderSbItems();
4708
+ const sbPaneRerender = () => {
4709
+ renderSbItems();
4710
+ };
4711
+ let masterRow = null;
4712
+ const renderMaster = () => {
4713
+ const next = masterRow ? !((config.features || {}).statusBar !== false) : (config.features || {}).statusBar !== false;
4714
+ const fresh = settingRow(L.sbMaster, L.sbMasterDesc, (config.features || {}).statusBar !== false, async () => {
4715
+ const to = !((config.features || {}).statusBar !== false);
4716
+ const res = await rpc("/config", { method: "POST", body: { features: { statusBar: to } } });
4717
+ clearConfigCache();
4718
+ if (res.ok) {
4719
+ config.features = { ...config.features || {}, statusBar: to };
4720
+ }
4721
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
4722
+ renderMaster();
4723
+ });
4724
+ fresh.setAttribute("data-zcodepro-sb-master", "1");
4725
+ if (masterRow) masterRow.replaceWith(fresh);
4726
+ masterRow = fresh;
4727
+ };
4728
+ renderMaster();
4720
4729
  paneStylesSub2.append(
4721
- h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.featureStatusBarDesc),
4730
+ masterRow,
4731
+ h("p", { class: "mt-2 text-ui-sm/relaxed text-foreground-subtle" }, L.featureStatusBarDesc),
4722
4732
  h("div", { class: "mt-3 mb-1 text-ui-sm font-medium" }, L.sbGroupSession),
4723
4733
  sbList,
4724
4734
  h("div", { class: "mt-3 mb-1 text-ui-sm font-medium" }, L.sbGroupGlobal),
@@ -7014,7 +7024,7 @@
7014
7024
  }
7015
7025
  var SEC_SPIN_CLASS = "zcodepro-ws-sec-spin";
7016
7026
  var anyActiveCache = { at: 0, value: null, busy: false };
7017
- var sectionActive = { task: false, project: false };
7027
+ var sectionActive = { task: false, project: false, pinned: false };
7018
7028
  var activeProjectPaths = /* @__PURE__ */ new Set();
7019
7029
  setInterval(() => {
7020
7030
  if (!anyActiveCache.busy && Date.now() - anyActiveCache.at >= 1e4) globalAnyActive();
@@ -7025,6 +7035,7 @@
7025
7035
  anyActiveCache.busy = true;
7026
7036
  sectionActive.task = false;
7027
7037
  sectionActive.project = false;
7038
+ sectionActive.pinned = false;
7028
7039
  activeProjectPaths = /* @__PURE__ */ new Set();
7029
7040
  void (async () => {
7030
7041
  let hit = null;
@@ -7092,8 +7103,29 @@
7092
7103
  }
7093
7104
  }
7094
7105
  const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
7095
- if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
7096
- if ([...byKind.project].some(live)) sectionActive.project = true;
7106
+ const taskLive = [...byKind.task].some(live);
7107
+ const projLive = [...byKind.project].some(live);
7108
+ if (taskLive) sectionActive.task = true;
7109
+ if (projLive) sectionActive.project = true;
7110
+ if (!taskDomRunning && taskLive && document.querySelector("[data-task-item-key]:not(section[data-project-id] *)")) {
7111
+ sectionActive.task = false;
7112
+ }
7113
+ for (const h3 of document.querySelectorAll("h3")) {
7114
+ const list = h3.nextElementSibling;
7115
+ if (!list) continue;
7116
+ for (const el of list.querySelectorAll("[data-task-item-key]")) {
7117
+ if (el.closest("section[data-project-id]")) continue;
7118
+ const k = el.getAttribute("data-task-item-key") || "";
7119
+ const id = k.slice(k.indexOf(":sess_") + 1);
7120
+ const base = k.slice(0, k.lastIndexOf(":")).replace(/[\/]+$/, "");
7121
+ const ids = byKindByPath.get(base) || [];
7122
+ if (ids.map(String).includes(id) && ids.some(live)) {
7123
+ sectionActive.pinned = true;
7124
+ break;
7125
+ }
7126
+ }
7127
+ if (sectionActive.pinned) break;
7128
+ }
7097
7129
  hit = sectionActive.task || sectionActive.project;
7098
7130
  }
7099
7131
  } catch {
@@ -7118,7 +7150,7 @@
7118
7150
  expanded = !list || list.offsetHeight > 0;
7119
7151
  }
7120
7152
  if (!kind) return;
7121
- const scoped = kind === "project" ? sectionActive.project : sectionActive.task;
7153
+ const scoped = head.tagName === "H3" ? sectionActive.pinned : kind === "project" ? sectionActive.project : sectionActive.task;
7122
7154
  const want = !expanded && scoped;
7123
7155
  const existing = head.querySelector(":scope > ." + SEC_SPIN_CLASS);
7124
7156
  if (want && !existing) {
@@ -8323,13 +8355,13 @@
8323
8355
  }
8324
8356
  function startTurnUsage() {
8325
8357
  void getConfig().then((cfg) => {
8326
- enabled5 = cfg.features ? cfg.features.turnUsage !== false : true;
8358
+ enabled5 = cfg.features ? cfg.features.statusBar !== false : true;
8327
8359
  }).catch(() => {
8328
8360
  });
8329
8361
  refreshTurnItems();
8330
8362
  window.addEventListener("zcodepro:config-changed", () => {
8331
8363
  void getConfig().then((cfg) => {
8332
- enabled5 = cfg.features ? cfg.features.turnUsage !== false : true;
8364
+ enabled5 = cfg.features ? cfg.features.statusBar !== false : true;
8333
8365
  }).catch(() => {
8334
8366
  });
8335
8367
  refreshTurnItems();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcode-pro",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "ZCode Pro — 为 ZCode 桌面版增加界面增强(不修改客户端文件,通过 ZCode Pro 快捷方式启动即可),核心功能:项目自定义别名",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@ import { reorderWorkspaceTasks, reorderGroupMembers } from './taskOrder.mjs';
17
17
  import { webdavRequest, webdavPutWithMkcol, webdavHttpText, parseWebdavListing, findCurl } from './webdav.mjs';
18
18
  import { resolveZcodeExecutable } from './paths.mjs';
19
19
 
20
- const VERSION = '1.2.1';
20
+ const VERSION = '1.3.1';
21
21
 
22
22
  // 全局提示词固定在用户主目录:官方加载器按 HOME/USERPROFILE 拼 .zcode/AGENTS.md,
23
23
  // 不读 ZCODE_DATA_BASE_DIR(数据根迁走时全局指令仍在原位)。
@@ -1829,8 +1829,12 @@ export function mergeAliases(localRaw, remoteRaw) {
1829
1829
  function applyFeaturesPartial(base, partial) {
1830
1830
  const out = (base && typeof base === 'object') ? base : {};
1831
1831
  if (!partial || typeof partial !== 'object') return out;
1832
- for (const key of Object.keys(defaultConfig().features)) {
1832
+ const known = new Set(Object.keys(defaultConfig().features));
1833
+ for (const key of Object.keys(partial)) {
1834
+ // 布尔特性一律可存(新特性未列入 defaultConfig 时不能被丢弃),
1835
+ // 非布尔值只在已知键内接受
1833
1836
  if (typeof partial[key] === 'boolean') out[key] = partial[key];
1837
+ else if (known.has(key)) out[key] = partial[key];
1834
1838
  }
1835
1839
  return out;
1836
1840
  }
@@ -296,20 +296,19 @@ const zh = {
296
296
  featureNewtabPanelsDesc: '官方“新标签页”只列官方面板与推荐的官方插件,此功能把第三方市场插件(headroom、rtk 等)的面板卡片加进去,点击在侧边面板打开。',
297
297
  ntpDescSuffix: '插件面板',
298
298
  ntpNeedSession: '先打开一个任务会话,再从新标签页打开插件面板',
299
- featureStatusBar: '状态栏统计',
300
299
  featureStatusBarDesc: '主界面底部右下角显示统计项:上下文占用、会话/今日/累计 token、任务数等;各项的顺序与显隐在“样式 → 状态栏”里调整。',
301
300
  tabStylesPane: '样式调整',
302
301
  tabStatusPane: '状态栏',
303
302
  sbItemCtx: '上下文占用',
304
303
  sbItemQuestions: '提问次数',
304
+ sbMaster: '启用统计',
305
+ sbMasterDesc: '总开关:关闭后底部状态栏与每轮回答的用量标注一起停用。',
305
306
  sbGroupSession: '会话统计(跟随当前会话)',
306
307
  sbGroupGlobal: '总统计',
307
308
  sbGroupTurn: '每轮统计(回答末尾)',
308
309
  sbLabelIn: '输入',
309
310
  sbLabelOut: '输出',
310
311
  sbLabelCache: '缓存',
311
- featureTurnUsage: '每轮回答 token 用量',
312
- featureTurnUsageDesc: '在每条回答末尾操作按钮的时间后面显示该轮的输入/输出 token(本地记录只保留最近若干轮,对不上时间的旧回答不显示)。',
313
312
  turnUsageTitle: '本轮 token',
314
313
  sbLabelQuestions: '提问',
315
314
  sbItemTodayTokens: '今日 token',
@@ -754,20 +753,19 @@ const en = {
754
753
  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, …) that open in the side pane.",
755
754
  ntpDescSuffix: 'plugin panel',
756
755
  ntpNeedSession: 'Open a task session first, then open plugin panels from the new tab page',
757
- featureStatusBar: 'Status bar statistics',
758
756
  featureStatusBarDesc: 'Shows statistics at the bottom-right of the main view: context usage, session/today/total tokens, task count, etc.; reorder or hide each item under "Styles → Status bar".',
759
757
  tabStylesPane: 'Styles',
760
758
  tabStatusPane: 'Status bar',
761
759
  sbItemCtx: 'Context usage',
762
760
  sbItemQuestions: 'Questions asked',
761
+ sbMaster: 'Enable statistics',
762
+ sbMasterDesc: 'Master switch: turning this off disables both the status bar and per-turn labels.',
763
763
  sbGroupSession: 'Session statistics (follows current session)',
764
764
  sbGroupGlobal: 'Global statistics',
765
765
  sbGroupTurn: 'Per-turn statistics (answer rows)',
766
766
  sbLabelIn: 'in',
767
767
  sbLabelOut: 'out',
768
768
  sbLabelCache: 'cache',
769
- featureTurnUsage: 'Per-turn token usage',
770
- featureTurnUsageDesc: "Shows that turn's in/out tokens after the time in each answer's action row (local records keep only the most recent turns; older answers without a match stay unlabelled).",
771
769
  turnUsageTitle: 'Turn tokens',
772
770
  sbLabelQuestions: 'asks',
773
771
  sbItemTodayTokens: 'Today tokens',
@@ -375,16 +375,6 @@ export function openSettingsDialog() {
375
375
  if (await setFeature('newtabPanels', next)) f.newtabPanels = next;
376
376
  refreshRows();
377
377
  }),
378
- settingRow(L.featureStatusBar, L.featureStatusBarDesc, f.statusBar !== false, async () => {
379
- const next = !(f.statusBar !== false);
380
- if (await setFeature('statusBar', next)) f.statusBar = next;
381
- refreshRows();
382
- }),
383
- settingRow(L.featureTurnUsage, L.featureTurnUsageDesc, f.turnUsage !== false, async () => {
384
- const next = !(f.turnUsage !== false);
385
- if (await setFeature('turnUsage', next)) f.turnUsage = next;
386
- refreshRows();
387
- }),
388
378
  (() => {
389
379
  // 标题 = “自动更新 duanluan/zcode-plugins 插件”,仓库名可点击跳转仓库
390
380
  const repoLink = h('span', {
@@ -840,8 +830,28 @@ export function openSettingsDialog() {
840
830
  renderGroup(sbGlobalList, STATUS_GLOBAL_IDS);
841
831
  };
842
832
  renderSbItems();
833
+ const sbPaneRerender = () => { renderSbItems(); };
834
+ // 总开关:与其它设置行完全同款 settingRow(自带开关样式);保存成功后
835
+ // 重建该行让开关视觉立即跟随
836
+ let masterRow = null;
837
+ const renderMaster = () => {
838
+ const next = masterRow ? !((config.features || {}).statusBar !== false) : ((config.features || {}).statusBar !== false);
839
+ const fresh = settingRow(L.sbMaster, L.sbMasterDesc, (config.features || {}).statusBar !== false, async () => {
840
+ const to = !((config.features || {}).statusBar !== false);
841
+ const res = await rpc('/config', { method: 'POST', body: { features: { statusBar: to } } });
842
+ clearConfigCache();
843
+ if (res.ok) { config.features = { ...(config.features || {}), statusBar: to }; }
844
+ window.dispatchEvent(new CustomEvent('zcodepro:config-changed'));
845
+ renderMaster();
846
+ });
847
+ fresh.setAttribute('data-zcodepro-sb-master', '1');
848
+ if (masterRow) masterRow.replaceWith(fresh);
849
+ masterRow = fresh;
850
+ };
851
+ renderMaster();
843
852
  paneStylesSub2.append(
844
- h('p', { class: 'text-ui-sm/relaxed text-foreground-subtle' }, L.featureStatusBarDesc),
853
+ masterRow,
854
+ h('p', { class: 'mt-2 text-ui-sm/relaxed text-foreground-subtle' }, L.featureStatusBarDesc),
845
855
  h('div', { class: 'mt-3 mb-1 text-ui-sm font-medium' }, L.sbGroupSession),
846
856
  sbList,
847
857
  h('div', { class: 'mt-3 mb-1 text-ui-sm font-medium' }, L.sbGroupGlobal),
@@ -178,10 +178,10 @@ async function tick() {
178
178
  }
179
179
 
180
180
  export function startTurnUsage() {
181
- void getConfig().then((cfg) => { enabled = cfg.features ? cfg.features.turnUsage !== false : true; }).catch(() => {});
181
+ void getConfig().then((cfg) => { enabled = cfg.features ? cfg.features.statusBar !== false : true; }).catch(() => {});
182
182
  refreshTurnItems();
183
183
  window.addEventListener('zcodepro:config-changed', () => {
184
- void getConfig().then((cfg) => { enabled = cfg.features ? cfg.features.turnUsage !== false : true; }).catch(() => {});
184
+ void getConfig().then((cfg) => { enabled = cfg.features ? cfg.features.statusBar !== false : true; }).catch(() => {});
185
185
  refreshTurnItems();
186
186
  });
187
187
  // 回答行出现即注入(去抖);数据 5 秒刷新一次
@@ -374,8 +374,10 @@ const SEC_SPIN_CLASS = 'zcodepro-ws-sec-spin';
374
374
  // 尾斜杠两种形式各查一次),任一非终态会话即“有任务在跑”。10 秒节流缓存;
375
375
  // 接口不可用(≤3.15.x)时无缓存,分区头退回 DOM 检测
376
376
  let anyActiveCache = { at: 0, value: null, busy: false };
377
- // 活跃按分区归属:task = “任务”分区(非项目工作区),project = “项目”分区
378
- const sectionActive = { task: false, project: false };
377
+ // 活跃按分区归属:task = “任务”分区(非项目工作区),project = “项目”分区,
378
+ // pinned = “已置顶”标题(置顶区任务,需单独判定——不与 task/project 混用,
379
+ // 否则别的分区有活跃时已置顶头也会亮)
380
+ const sectionActive = { task: false, project: false, pinned: false };
379
381
  // 权威扫描发现的有活跃任务的项目路径集合(全部 root);项目行折叠态判定用
380
382
  let activeProjectPaths = new Set();
381
383
  // 周期复查:缓存到期就无条件重扫(不看当前值)——只在 true 时扫会有两个缺口:
@@ -390,6 +392,7 @@ function globalAnyActive() {
390
392
  anyActiveCache.busy = true;
391
393
  sectionActive.task = false;
392
394
  sectionActive.project = false;
395
+ sectionActive.pinned = false;
393
396
  activeProjectPaths = new Set();
394
397
  void (async () => {
395
398
  let hit = null;
@@ -465,11 +468,42 @@ function globalAnyActive() {
465
468
  if (projName) for (const p2 of projectPaths.get(projName) || []) activeProjectPaths.add(p2);
466
469
  }
467
470
  }
468
- // 任务侧再加一道 DOM 确认:非项目任务行上必须真有 loader 在转——
469
- // 侧栏显示的就是真相,syncer 报告的活跃(尤其历史残留工作区)不足为凭
471
+ // 任务侧加一道 DOM 确认:非项目任务行上真有 loader 在转才亮——侧栏
472
+ // 显示的就是真相,syncer 报告的活跃(尤其历史残留工作区)不足为凭。
473
+ // 但“任务”分区折叠后行整体卸载(DOM 检测失效),此时退回只信权威扫描
474
+ //(否则分区折叠后头永远不亮——用户实测回归)
470
475
  const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
471
- if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
472
- if ([...byKind.project].some(live)) sectionActive.project = true;
476
+ const taskLive = [...byKind.task].some(live);
477
+ const projLive = [...byKind.project].some(live);
478
+ // 分区头亮灯各管各的:任务头只看非项目工作区,项目头只看项目工作区。
479
+ // 折叠后行不在场(DOM 检测失效),只信权威扫描;DOM 确认仅在非项目
480
+ // 任务行在场(分区展开着)时用来否决 syncer 卡滞误报
481
+ if (taskLive) sectionActive.task = true;
482
+ if (projLive) sectionActive.project = true;
483
+ if (!taskDomRunning && taskLive
484
+ && document.querySelector('[data-task-item-key]:not(section[data-project-id] *)')) {
485
+ // 非项目任务行在场但全没 loader(分区展开着)→ syncer 卡滞误报
486
+ sectionActive.task = false;
487
+ }
488
+ // 已置顶区单独判定:取“已置顶”标题(h3)旁边列表容器内的任务行
489
+ // (不在任何 data-project-id 分区里),它们的 id 有活跃才亮已置顶头
490
+ // ——跟 task/project 分区的活跃无关
491
+ for (const h3 of document.querySelectorAll('h3')) {
492
+ const list = h3.nextElementSibling;
493
+ if (!list) continue;
494
+ for (const el of list.querySelectorAll('[data-task-item-key]')) {
495
+ if (el.closest('section[data-project-id]')) continue;
496
+ const k = el.getAttribute('data-task-item-key') || '';
497
+ const id = k.slice(k.indexOf(':sess_') + 1);
498
+ const base = k.slice(0, k.lastIndexOf(':')).replace(/[\/]+$/, '');
499
+ const ids = byKindByPath.get(base) || [];
500
+ if (ids.map(String).includes(id) && ids.some(live)) {
501
+ sectionActive.pinned = true;
502
+ break;
503
+ }
504
+ }
505
+ if (sectionActive.pinned) break;
506
+ }
473
507
  hit = sectionActive.task || sectionActive.project;
474
508
  }
475
509
  } catch { /* 查询失败保持旧值 */ }
@@ -500,7 +534,9 @@ function applySectionSpin() {
500
534
  expanded = !list || list.offsetHeight > 0;
501
535
  }
502
536
  if (!kind) return; // 折叠后解析不到归属且无缓存:跳过(不动现有图标)
503
- const scoped = kind === 'project' ? sectionActive.project : sectionActive.task;
537
+ // 已置顶标题(h3)单独看 pinned scope,不复用 task 的
538
+ const scoped = head.tagName === 'H3' ? sectionActive.pinned
539
+ : kind === 'project' ? sectionActive.project : sectionActive.task;
504
540
  // 折叠态一律只信权威扫描:DOM 检测在折叠后不可靠(容器卸载后解析会落到
505
541
  // 别的分区,多目录项目的行匹配也套不上),且权威扫描周期覆盖刷新
506
542
  const want = !expanded && scoped;