@lesliechan721/aw 0.1.0-beta.31 → 0.1.0-beta.33

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.
Files changed (50) hide show
  1. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  2. package/dist/authoring/scripts/validate-plugin.mjs +5 -2
  3. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  4. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  5. package/dist/src/commands/aw-capabilities.js +2 -0
  6. package/dist/src/commands/aw-capabilities.js.map +1 -1
  7. package/dist/src/tui/catalog-sources/session.d.ts +5 -1
  8. package/dist/src/tui/catalog-sources/session.js +6 -1
  9. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  10. package/dist/src/tui/catalog-sources/types.d.ts +1 -0
  11. package/dist/src/tui/clipboard.d.ts +13 -0
  12. package/dist/src/tui/clipboard.js +95 -0
  13. package/dist/src/tui/clipboard.js.map +1 -0
  14. package/dist/src/tui/components.d.ts +21 -11
  15. package/dist/src/tui/components.js +69 -56
  16. package/dist/src/tui/components.js.map +1 -1
  17. package/dist/src/tui/config-value-presentation.d.ts +19 -0
  18. package/dist/src/tui/config-value-presentation.js +77 -0
  19. package/dist/src/tui/config-value-presentation.js.map +1 -0
  20. package/dist/src/tui/console-app-v4.d.ts +1 -1
  21. package/dist/src/tui/console-app-v4.js +1205 -1377
  22. package/dist/src/tui/console-app-v4.js.map +1 -1
  23. package/dist/src/tui/controller-runtime.js +5 -0
  24. package/dist/src/tui/controller-runtime.js.map +1 -1
  25. package/dist/src/tui/controller.d.ts +12 -1
  26. package/dist/src/tui/controller.js +171 -46
  27. package/dist/src/tui/controller.js.map +1 -1
  28. package/dist/src/tui/diagnostics.d.ts +2 -1
  29. package/dist/src/tui/diagnostics.js +8 -1
  30. package/dist/src/tui/diagnostics.js.map +1 -1
  31. package/dist/src/tui/input-display.d.ts +1 -0
  32. package/dist/src/tui/input-display.js +13 -0
  33. package/dist/src/tui/input-display.js.map +1 -1
  34. package/dist/src/tui/model.d.ts +5 -3
  35. package/dist/src/tui/model.js +17 -33
  36. package/dist/src/tui/model.js.map +1 -1
  37. package/dist/src/tui/operation-diagnostic-screens.d.ts +6 -3
  38. package/dist/src/tui/operation-diagnostic-screens.js +31 -9
  39. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -1
  40. package/dist/src/tui/operation-history.js +5 -4
  41. package/dist/src/tui/operation-history.js.map +1 -1
  42. package/dist/src/tui/router.d.ts +5 -7
  43. package/dist/src/tui/router.js.map +1 -1
  44. package/dist/src/tui/screen-registry.d.ts +13 -22
  45. package/dist/src/tui/screen-registry.js +3 -4
  46. package/dist/src/tui/screen-registry.js.map +1 -1
  47. package/dist/src/tui/viewport.d.ts +1 -0
  48. package/dist/src/tui/viewport.js +23 -0
  49. package/dist/src/tui/viewport.js.map +1 -1
  50. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { presentConfigValue, safeConfigText } from './config-value-presentation.js';
2
3
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
4
  import { useEffect, useRef, useState } from 'react';
4
5
  import path from 'node:path';
@@ -11,21 +12,22 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
11
12
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
12
13
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
13
14
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
14
- import { ActionRow, ObjectCardRow, objectCardRowLayout, StatusBadge, diagnosticDetailLines, Callout, ChoiceRow, ContentViewport, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, PinnedActionBar, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
15
+ import { ActionRow, ObjectCardRow, objectCardRowLayout, pluginListRowLayout, StatusBadge, diagnosticDetailLines, diagnosticDetailText, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
15
16
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
16
17
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
17
18
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
18
19
  import { catalogSourceFreshnessLabel, catalogSourceListPresentation, } from './catalog-sources/screens.js';
19
- import { operationDiagnosticLineCount, operationHistoryMeta, operationHistoryState, operationReportLineCount, OperationDiagnosticScreen, OperationReportPreviewScreen, } from './operation-diagnostic-screens.js';
20
+ import { operationDiagnosticLineCount, operationDiagnosticText, operationHistoryMeta, operationHistoryState, operationReportLineCount, OperationDiagnosticScreen, OperationReportPreviewScreen, } from './operation-diagnostic-screens.js';
21
+ import { copyToClipboard } from './clipboard.js';
20
22
  import { resolveTuiSizePolicy } from './layout.js';
21
23
  import { buildDependencyGraphProjection, buildUnifiedDependencyGraphProjection, } from './dependency-graph.js';
22
24
  import { platformLabel, scopeLabel } from './glossary.js';
23
25
  import { InputDisplay, removeLastGrapheme } from './input-display.js';
24
26
  import { catalogRefPolicyForPreset } from './source-ref-presets.js';
25
- import { buildPlatformActions, buildPluginDetailActions, configObjectHasSensitiveChild, hasInstalledPlacement, configValuesEqual, } from './model.js';
27
+ import { buildPlatformActions, configObjectHasSensitiveChild, hasInstalledPlacement, configValuesEqual, } from './model.js';
26
28
  import { resolveTuiGlobalKeyAction, resolveTuiSessionNavigationKey } from './keys.js';
27
29
  import { tuiTheme } from './theme.js';
28
- import { contentRowBudget, rowViewport } from './viewport.js';
30
+ import { contentRowBudget, rowViewport, wrapDisplayText } from './viewport.js';
29
31
  import { resolveRouteScreenId, routeDefinition, TUI_SCREEN_IDS, unreachableTuiRoute, } from './screen-registry.js';
30
32
  export const CONSOLE_V4_SCREEN_IDS = TUI_SCREEN_IDS;
31
33
  const PLUGIN_FILTERS = [
@@ -40,22 +42,6 @@ function pluginFilterLabel(filter) {
40
42
  attention: '需处理',
41
43
  }[filter];
42
44
  }
43
- function paginationActions(options) {
44
- return [
45
- {
46
- actionId: 'pagination-previous',
47
- label: 'p 上一页',
48
- disabled: options.offset <= 0 ? '已在第一页' : undefined,
49
- run: () => options.move(-1),
50
- },
51
- {
52
- actionId: 'pagination-next',
53
- label: 'n 下一页',
54
- disabled: options.offset + options.pageRows >= options.total ? '已在最后一页' : undefined,
55
- run: () => options.move(1),
56
- },
57
- ];
58
- }
59
45
  function sourceOperationBlockLabel(reason) {
60
46
  switch (reason) {
61
47
  case 'configDraft': return '未保存的插件设置';
@@ -175,17 +161,17 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
175
161
  if (route.name === 'pluginList') {
176
162
  rows.push(['←/→ · [ / ]', '切换来源']);
177
163
  rows.push(['/', '进入搜索']);
178
- if (route.name === 'pluginList')
179
- rows.push(['f', '切换插件状态']);
164
+ rows.push(['f', '切换插件状态']);
165
+ rows.push(['m', '原位选择多个插件']);
180
166
  }
181
167
  if (route.name === 'portableSkillSelect') {
182
168
  rows.push(['Space', '切换当前 Skill']);
183
- rows.push(['a', '全选 Skills']);
169
+ rows.push(['a', '定位下方操作 / 回到原项']);
184
170
  }
185
171
  if (route.name === 'batchSelection') {
186
172
  rows.push(['Space', '切换当前插件']);
187
- rows.push(['a', '全选当前筛选结果']);
188
- rows.push(['c', '继续选择位置和平台']);
173
+ rows.push(['a', '定位下方操作 / 回到原项']);
174
+ rows.push(['c', '继续选择任务或查看汇总']);
189
175
  }
190
176
  if (route.name === 'configFields' && route.layer.scope !== 'effective') {
191
177
  rows.push(['s', '查看保存汇总']);
@@ -305,12 +291,16 @@ function pluginUpdateDescription(plugin) {
305
291
  }
306
292
  return '当前启用目录已验证;可直接进入更新确认。';
307
293
  }
294
+ function pluginPlatformUnknown(plugin, platform) {
295
+ const placements = plugin.installedPlacements.filter((entry) => entry.platform === platform);
296
+ return plugin.sourceUnavailable || placements.length === 0
297
+ || placements.some((entry) => entry.state === 'installed' && entry.sharedNative?.state === 'unobserved');
298
+ }
308
299
  function pluginPlatformBadges(plugin) {
309
300
  const items = plugin.supportedPlatforms.map((platform) => {
310
301
  const placements = plugin.installedPlacements.filter((entry) => entry.platform === platform);
311
302
  const installed = placements.some((entry) => entry.state === 'installed' || entry.state === 'scopeConflict');
312
- const unknown = plugin.sourceUnavailable || placements.length === 0
313
- || placements.some((entry) => entry.state === 'installed' && entry.sharedNative?.state === 'unobserved');
303
+ const unknown = pluginPlatformUnknown(plugin, platform);
314
304
  const attention = placements.some((entry) => entry.state === 'blocked' || entry.state === 'scopeConflict'
315
305
  || entry.state === 'installed' && (entry.health !== 'ready' || entry.blockingReason !== undefined));
316
306
  const update = plugin.update.targets.some((target) => target.platform === platform
@@ -390,7 +380,7 @@ function moveFocus(actions, current, direction) {
390
380
  return 0;
391
381
  for (let offset = 1; offset <= actions.length; offset += 1) {
392
382
  const next = (current + direction * offset + actions.length) % actions.length;
393
- if (!actions[next]?.disabled)
383
+ if (!actions[next]?.disabled && actions[next]?.role !== 'navigation')
394
384
  return next;
395
385
  }
396
386
  return current;
@@ -431,6 +421,31 @@ function wrapConsoleText(value, columns) {
431
421
  lines.push(line);
432
422
  return lines;
433
423
  }
424
+ function sourceRefSummary(value) {
425
+ if (!value)
426
+ return '未知';
427
+ if (/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/iu.test(value))
428
+ return value.slice(0, 8);
429
+ return value.replace(/^refs\/(?:heads|tags)\//u, '');
430
+ }
431
+ function relativeCheckTime(value, now = Date.now()) {
432
+ if (!value)
433
+ return '尚未检查';
434
+ const checkedAt = Date.parse(value);
435
+ if (!Number.isFinite(checkedAt))
436
+ return '时间未知';
437
+ const elapsed = Math.max(0, now - checkedAt);
438
+ if (elapsed < 60_000)
439
+ return '刚刚';
440
+ if (elapsed < 3_600_000)
441
+ return `${Math.floor(elapsed / 60_000)} 分钟前`;
442
+ if (elapsed < 86_400_000)
443
+ return `${Math.floor(elapsed / 3_600_000)} 小时前`;
444
+ return `${Math.floor(elapsed / 86_400_000)} 天前`;
445
+ }
446
+ function parseConfigNumberInput(input) {
447
+ return input.trim().length === 0 ? undefined : Number(input);
448
+ }
434
449
  function changeDetailLines(changes, columns) {
435
450
  const valueLines = (label, value) => [
436
451
  label,
@@ -440,8 +455,19 @@ function changeDetailLines(changes, columns) {
440
455
  `修改项 · ${change.title}`,
441
456
  ...valueLines('修改前', change.before),
442
457
  ...valueLines('修改后', change.after),
458
+ ...(change.children ?? []).flatMap((child) => [child.property, ...valueLines('修改前', child.before), ...valueLines('修改后', child.after)]),
443
459
  ]);
444
460
  }
461
+ function changeDetailText(changes) {
462
+ return changes.flatMap((change) => [
463
+ `修改项 · ${change.title}`,
464
+ `修改前\n ${change.before ?? '未设置'}`,
465
+ `修改后\n ${change.after ?? '未设置'}`,
466
+ ...(change.children ?? []).flatMap((child) => [
467
+ child.property, `修改前\n ${child.before}`, `修改后\n ${child.after}`,
468
+ ]),
469
+ ]).join('\n');
470
+ }
445
471
  function operationRiskWarnings(review) {
446
472
  return [...new Set([
447
473
  ...(review.sharedImpact.warning ? [review.sharedImpact.warning] : []),
@@ -464,29 +490,12 @@ function operationRiskLines(review, columns) {
464
490
  }
465
491
  function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
466
492
  return rowViewport({
467
- rowHeights: actions.map((action) => compact || (!action.description && !action.disabled) ? 1 : 2),
493
+ rowHeights: actions.map((action) => action.role === 'navigation' ? 0 : compact || (!action.description && !action.disabled) ? 1 : 2),
468
494
  focus,
469
495
  requestedOffset,
470
496
  rowBudget: lineBudget,
471
497
  });
472
498
  }
473
- function objectCardActionViewport(actions, width, compact, focus, requestedOffset, lineBudget) {
474
- return rowViewport({
475
- rowHeights: actions.map((action) => objectCardRowLayout({
476
- width,
477
- description: action.disabled ?? action.description ?? '',
478
- meta: action.meta,
479
- hasTags: action.tags !== undefined,
480
- compact,
481
- }).height),
482
- focus,
483
- requestedOffset,
484
- rowBudget: lineBudget,
485
- });
486
- }
487
- function portableSkillCandidateViewport(actions, width, compact, focus, requestedOffset, rowBudget) {
488
- return objectCardActionViewport(actions, width, compact, focus, requestedOffset, rowBudget);
489
- }
490
499
  function prefixRowLimit(rowHeights, rowBudget) {
491
500
  let used = 0;
492
501
  let visible = 0;
@@ -662,17 +671,40 @@ function configUnavailableDiagnostic(route) {
662
671
  }
663
672
  function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
664
673
  const unavailable = state === 'unavailable' || state === 'error';
674
+ if (generation?.errorCode === 'catalogLogicalIdentityChanged') {
675
+ return {
676
+ severity: 'error',
677
+ title: '来源发布身份已变化',
678
+ userMessage: '待检查内容与当前来源不是同一发布身份。当前状态不能据此判定为健康。',
679
+ impact: [
680
+ { state: 'blocked', message: '不能把待检查内容作为当前来源的更新。' },
681
+ { state: 'degraded', message: '旧 Active 是否仍可用需要单独验证。' },
682
+ ],
683
+ nextStep: '检查来源地址和发布身份,然后编辑来源,或移除后重新添加。',
684
+ technical: {
685
+ code: 'catalogLogicalIdentityChanged',
686
+ stage: 'compare-source-identity',
687
+ sourceId,
688
+ checks: ['核对来源地址、ref 和 logical identity。', '确认后编辑来源,或移除后重新添加。'],
689
+ },
690
+ };
691
+ }
665
692
  if (generation?.compatibility?.state === 'unsupported') {
666
693
  const { requiredCapabilities, missingCapabilities } = generation.compatibility;
694
+ const missingKnown = missingCapabilities.length > 0;
667
695
  return {
668
696
  severity: 'error',
669
697
  title: '当前目录版本不兼容',
670
- userMessage: `当前 AW 缺少 ${missingCapabilities.length} 项所需功能支持。`,
698
+ userMessage: missingKnown
699
+ ? `当前 AW 缺少 ${missingCapabilities.length} 项所需功能支持。`
700
+ : '当前目录版本所需功能无法确认。',
671
701
  impact: [
672
702
  { state: 'blocked', message: '这个目录版本不能用于浏览、安装或同步。' },
673
703
  { state: 'preserved', message: '当前启用版本和已下载内容保持不变。' },
674
704
  ],
675
- nextStep: '升级 AW 到支持所需能力的版本,或修改来源后重新启用。',
705
+ nextStep: missingKnown
706
+ ? '升级 AW 到支持所需能力的版本,或修改来源后重新启用。'
707
+ : '检查来源内容和 capability 诊断,再决定修复来源或更新 AW。',
676
708
  technical: {
677
709
  code: 'awCapabilityUnsupported',
678
710
  stage: 'resolve-source',
@@ -681,7 +713,9 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
681
713
  key: 'compatibility',
682
714
  value: `required: ${requiredCapabilities.join('、')}; missing: ${missingCapabilities.join('、')}`,
683
715
  }],
684
- checks: ['检查所需和缺少的 Catalog capability。', '升级 AW 后重新加载来源。'],
716
+ checks: missingKnown
717
+ ? ['检查所需和缺少的 Catalog capability。', '升级 AW 后重新加载来源。']
718
+ : ['检查来源的 capability 声明和验证错误。', '确认原因后重新加载来源。'],
685
719
  },
686
720
  };
687
721
  }
@@ -704,18 +738,24 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
704
738
  };
705
739
  }
706
740
  if (freshness?.state === 'incompatible') {
707
- const hasActive = generation?.active !== undefined;
741
+ const activeHealthy = generation?.state === 'ready' && generation.active !== undefined;
742
+ const missingCount = freshness.missingCapabilities?.length ?? 0;
743
+ const missingSummary = missingCount && missingCount > 0
744
+ ? `缺少 ${missingCount} 项待检查版本所需功能`
745
+ : '待检查版本所需功能无法确认';
708
746
  return {
709
747
  severity: 'error',
710
748
  title: '待检查版本不兼容',
711
- userMessage: hasActive
712
- ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能。普通更新已阻止,当前目录版本保持不变。`
713
- : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能。这个来源没有可用目录版本。`,
749
+ userMessage: activeHealthy
750
+ ? `当前 AW ${missingSummary}。普通更新已阻止;已验证的当前目录版本保持不变。`
751
+ : `当前 AW ${missingSummary}。这个来源没有已验证可用的目录版本。`,
714
752
  impact: [
715
753
  { state: 'blocked', message: '不能直接切换到这个待检查版本。' },
716
- ...(hasActive ? [{ state: 'preserved', message: '当前目录版本和插件浏览仍可用。' }] : []),
754
+ ...(activeHealthy ? [{ state: 'preserved', message: '已验证的当前目录版本和插件浏览仍可用。' }] : []),
717
755
  ],
718
- nextStep: '升级 AW 到支持待检查版本所需功能的版本,或编辑来源后重新验证。',
756
+ nextStep: missingCount > 0
757
+ ? '升级 AW 到支持待检查版本所需功能的版本,或编辑来源后重新验证。'
758
+ : '检查来源的 capability 声明和安全诊断,再决定修正来源或升级 AW。',
719
759
  technical: {
720
760
  code: safeDiagnosticCode(freshness.errorCode, 'catalogincompatible'),
721
761
  stage: 'compare-candidate',
@@ -723,7 +763,9 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
723
763
  ...(freshness.missingCapabilities?.length ? {
724
764
  context: [{ key: 'compatibility', value: freshness.missingCapabilities.join('、') }],
725
765
  } : {}),
726
- checks: ['检查缺少的 Catalog capability。', '升级 AW 后执行完整验证。'],
766
+ checks: missingCount > 0
767
+ ? ['检查缺少的 Catalog capability。', '升级 AW 后执行完整验证。']
768
+ : ['检查来源的 capability 声明。', '查看安全错误代码后重新验证。'],
727
769
  },
728
770
  };
729
771
  }
@@ -813,6 +855,8 @@ function SemanticRows(props) {
813
855
  ? props.actions.slice(offset)
814
856
  : props.actions.slice(offset, offset + props.limit);
815
857
  return (_jsx(Box, { flexDirection: "column", children: actions.map((action, visibleIndex) => {
858
+ if (action.role === 'navigation')
859
+ return null;
816
860
  const index = visibleIndex + offset;
817
861
  const rowProps = {
818
862
  title: action.label,
@@ -824,7 +868,7 @@ function SemanticRows(props) {
824
868
  compact: props.compact,
825
869
  };
826
870
  if (props.cards)
827
- return _jsx(ObjectCardRow, { width: props.columns ?? 58, title: action.label, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: index === props.focus, compact: props.compact }, `${action.label}:${index}`);
871
+ return _jsx(ObjectCardRow, { width: props.columns ?? 58, title: action.label, description: action.disabled ?? action.description ?? '', meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows, tags: action.tags, focused: index === props.focus, compact: props.compact }, `${action.label}:${index}`);
828
872
  if (props.kind === 'section')
829
873
  return _jsx(SectionRow, { ...rowProps }, `${action.label}:${index}`);
830
874
  if (props.kind === 'choice') {
@@ -838,9 +882,36 @@ function SemanticRows(props) {
838
882
  return _jsx(TaskRow, { ...rowProps }, `${action.label}:${index}`);
839
883
  }) }));
840
884
  }
885
+ function SequenceRows(props) {
886
+ const configLines = (action) => [
887
+ ...wrapConsoleText(`${action.label}${action.meta ? ` · ${action.meta}` : ''}`, Math.max(1, props.columns - 2)),
888
+ ...(action.description ? wrapConsoleText(action.description, Math.max(1, props.columns - 2)) : []),
889
+ ];
890
+ const viewport = rowViewport({
891
+ rowHeights: props.actions.map((action, index) => action.role === 'navigation' ? 0 : index < props.itemCount
892
+ ? props.wrapValues ? configLines(action).length : props.cards ? objectCardRowLayout({
893
+ width: props.columns, description: action.disabled ?? action.description ?? '',
894
+ meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows,
895
+ hasTags: action.tags !== undefined, compact: props.compact,
896
+ }).height : action.description ? 2 : 1
897
+ : 1),
898
+ focus: props.focus, requestedOffset: props.scrollOffset, rowBudget: props.rowBudget,
899
+ });
900
+ return _jsx(ContentViewport, { children: props.actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => {
901
+ if (action.role === 'navigation')
902
+ return null;
903
+ const index = viewport.offset + visibleIndex;
904
+ const focused = !props.inactive && index === props.focus;
905
+ if (index < props.itemCount && props.wrapValues)
906
+ return _jsx(Box, { flexDirection: "column", children: configLines(action).map((line, lineIndex) => _jsxs(Text, { bold: focused && lineIndex === 0, color: action.status === 'error' ? 'red' : undefined, children: [lineIndex === 0 ? focused ? '› ' : ' ' : ' ', line] }, lineIndex)) }, action.configKey ?? action.label);
907
+ return index < props.itemCount && props.cards
908
+ ? _jsx(ObjectCardRow, { width: props.columns, title: `${action.selected === undefined ? '' : action.selected ? '[x] ' : '[ ] '}${action.label}`, description: action.disabled ?? action.description ?? '', meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows, tags: action.tags, focused: focused, compact: props.compact }, action.actionId ?? action.pluginId ?? action.label)
909
+ : _jsx(SemanticRows, { actions: [action], focus: focused ? 0 : -1, kind: index < props.itemCount ? 'section' : 'action', compact: index >= props.itemCount }, action.actionId ?? action.label);
910
+ }) });
911
+ }
841
912
  function PluginRows(props) {
842
913
  return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
843
- .map((row, visibleIndex) => row.menuAction ? (_jsx(TaskRow, { title: row.name, description: row.description, meta: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, compact: true }, row.pluginId)) : (_jsx(PluginListRow, { name: row.name, sourceLabel: row.sourceLabel, description: row.description, attention: row.attention, placementSummary: row.placementSummary, tags: row.tags, statusLabel: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, anchored: !props.active && props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
914
+ .map((row, visibleIndex) => row.menuAction ? (_jsx(TaskRow, { title: row.name, description: row.description, meta: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, compact: true }, row.pluginId)) : (_jsx(PluginListRow, { width: props.columns, name: row.name, sourceLabel: row.sourceLabel, description: row.description, attention: row.attention, placementSummary: row.placementSummary, tags: row.tags, statusLabel: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, anchored: !props.active && props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
844
915
  }
845
916
  function IssueTechnicalDetails(props) {
846
917
  const visible = props.details.slice(0, props.compact ? 1 : 2);
@@ -868,9 +939,10 @@ function SourceTabs(props) {
868
939
  const state = 'state' in tab ? tab.state : 'ready';
869
940
  return `${tab.id === props.current ? '[' : ''}${tab.label}${badge(state, tab.count)}${tab.id === props.current ? ']' : ''} `;
870
941
  }).join('')}`;
871
- if (props.compact || terminalTextWidth(fullText) > props.columns) {
942
+ if (props.compact || terminalTextWidth(`${fullText}←→ 切换`) > props.columns) {
872
943
  const tab = tabs[index];
873
- return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
944
+ const neighbour = tabs[(index + 1) % tabs.length];
945
+ return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " \u2190\u2192 \u5207\u6362 ", tabs.length > 1 ? neighbour?.label : ''] });
874
946
  }
875
947
  return (_jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", tabs.map((tab) => {
876
948
  const active = tab.id === props.current;
@@ -879,7 +951,7 @@ function SourceTabs(props) {
879
951
  : state === 'error' ? tuiTheme.statusColors.error
880
952
  : state === 'update' || state === 'recovery' ? tuiTheme.statusColors.warning
881
953
  : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, badge(state, tab.count), active ? ']' : '', ' '] }, tab.id));
882
- })] }));
954
+ }), "\u2190\u2192 \u5207\u6362"] }));
883
955
  }
884
956
  function configTabs(page) {
885
957
  return [
@@ -987,9 +1059,6 @@ function dependencyReferenceUsageLabel(reference, consumerName) {
987
1059
  : reference.consumer.project === 'current' ? '当前项目' : '其他项目';
988
1060
  return `${consumerName} · ${location} · ${platformLabel(reference.consumer.platform)}`;
989
1061
  }
990
- function dependencySummaryText(summary) {
991
- return `${summary.directCount} 个直接 · ${summary.totalCount} 个总计`;
992
- }
993
1062
  function dependencyGraphRowStatus(row) {
994
1063
  if (row.nodeState === 'ready')
995
1064
  return { value: '正常', status: 'success' };
@@ -1064,14 +1133,13 @@ export function ConsoleAppV4(props) {
1064
1133
  const [overlayFocus, setOverlayFocus] = useState(0);
1065
1134
  const [operationConfirmation, setOperationConfirmation] = useState();
1066
1135
  const [sourceTransportOverlay, setSourceTransportOverlay] = useState();
1067
- const [pluginListChooser, setPluginListChooser] = useState();
1068
1136
  const [projectReminderOpen, setProjectReminderOpen] = useState(false);
1069
1137
  const [discardForExit, setDiscardForExit] = useState(false);
1070
1138
  const [discardForSessionNavigation, setDiscardForSessionNavigation] = useState();
1071
1139
  const [discardDraftConfirmation, setDiscardDraftConfirmation] = useState();
1072
1140
  const [focus, setFocus] = useState(0);
1073
- const [focusZone, setFocusZone] = useState('content');
1074
- const [pinnedActionFocus, setPinnedActionFocus] = useState(0);
1141
+ const actionJumpAnchor = useRef(undefined);
1142
+ const [searchCursor, setSearchCursor] = useState(0);
1075
1143
  const [scrollOffset, setScrollOffset] = useState(0);
1076
1144
  const [searchFocus, setSearchFocus] = useState(false);
1077
1145
  const [query, setQuery] = useState(snapshot.router.current.context.query ?? '');
@@ -1081,6 +1149,7 @@ export function ConsoleAppV4(props) {
1081
1149
  const [scope, setScope] = useState('user');
1082
1150
  const [selectedTargets, setSelectedTargets] = useState([]);
1083
1151
  const [installPlatformScopes, setInstallPlatformScopes] = useState({});
1152
+ const [installPlatformIndex, setInstallPlatformIndex] = useState(0);
1084
1153
  const [uninstallTargetKeys, setUninstallTargetKeys] = useState([]);
1085
1154
  const [technicalRouteKey, setTechnicalRouteKey] = useState();
1086
1155
  const [exitAfterBusy, setExitAfterBusy] = useState(false);
@@ -1098,12 +1167,12 @@ export function ConsoleAppV4(props) {
1098
1167
  const [inputValue, setInputValue] = useState('');
1099
1168
  const [inputError, setInputError] = useState();
1100
1169
  const [configActionError, setConfigActionError] = useState();
1101
- const [configGroupChooserOpen, setConfigGroupChooserOpen] = useState(false);
1102
- const [configGroupChoiceFocus, setConfigGroupChoiceFocus] = useState(0);
1103
1170
  const [actionError, setActionError] = useState();
1171
+ const [copyFeedback, setCopyFeedback] = useState();
1104
1172
  const [configResetAvailability, setConfigResetAvailability] = useState({});
1105
1173
  const [sourceDraft, setSourceDraft] = useState();
1106
1174
  const [batchDraft, setBatchDraft] = useState();
1175
+ const batchSelectionAnchor = useRef(undefined);
1107
1176
  const [batchTargetOffset, setBatchTargetOffset] = useState(0);
1108
1177
  const [portableResultDetailOpen, setPortableResultDetailOpen] = useState(false);
1109
1178
  const [portableResultDetailOffset, setPortableResultDetailOffset] = useState(0);
@@ -1134,6 +1203,7 @@ export function ConsoleAppV4(props) {
1134
1203
  const [draftOwner, setDraftOwner] = useState();
1135
1204
  const dynamicMenuFocus = useRef(undefined);
1136
1205
  const pluginListFocusId = useRef(undefined);
1206
+ const listFocusAnchor = useRef(undefined);
1137
1207
  const dependencyGraphContexts = useRef(new Map());
1138
1208
  const selectedDependencyReferenceIndex = useRef(0);
1139
1209
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
@@ -1159,10 +1229,6 @@ export function ConsoleAppV4(props) {
1159
1229
  ...(configDrafts.user?.updates ?? {}),
1160
1230
  ...(configDrafts.project?.updates ?? {}),
1161
1231
  };
1162
- const draftRemoves = [...new Set([
1163
- ...(configDrafts.user?.removes ?? []),
1164
- ...(configDrafts.project?.removes ?? []),
1165
- ])];
1166
1232
  const definition = routeDefinition(route);
1167
1233
  const rendererFamily = definition.rendererFamily;
1168
1234
  const routeKey = JSON.stringify(route);
@@ -1378,10 +1444,6 @@ export function ConsoleAppV4(props) {
1378
1444
  setInputValue('');
1379
1445
  }
1380
1446
  }, [route.name]);
1381
- useEffect(() => {
1382
- if (route.name !== 'configFields')
1383
- setConfigGroupChooserOpen(false);
1384
- }, [route.name]);
1385
1447
  const configRoute = route.name === 'configFields'
1386
1448
  || route.name === 'configFieldEditor'
1387
1449
  || route.name === 'configObjectPropertyEditor'
@@ -1607,42 +1669,6 @@ export function ConsoleAppV4(props) {
1607
1669
  return;
1608
1670
  props.controller.updatePageContext({ configGroupId: tabs[0].id });
1609
1671
  }, [props.controller, route.name, currentConfigPage, snapshot.router.current.context.configGroupId]);
1610
- useEffect(() => {
1611
- let count;
1612
- if (route.name === 'sourceList')
1613
- count = props.controller.listSources().length;
1614
- else if (route.name === 'issueList') {
1615
- const issueCount = props.controller.listUserIssues().length;
1616
- count = issueCount || 2;
1617
- }
1618
- else if (route.name === 'operationHistory') {
1619
- count = snapshot.operationHistorySummaries.length + 1;
1620
- }
1621
- else if (route.name === 'pluginList') {
1622
- const items = props.controller.listPlugins({
1623
- mode: route.mode,
1624
- ...(route.sourceId ? { sourceId: route.sourceId } : {}),
1625
- ...(query ? { query } : {}),
1626
- installFilter,
1627
- });
1628
- const sourceUnavailable = route.sourceId !== undefined
1629
- && props.controller.getSourceOverview(route.sourceId).health?.state === 'unavailable';
1630
- count = pluginListRowIds(route.mode, items, query, sourceUnavailable).length || 1;
1631
- }
1632
- if (count === undefined)
1633
- return;
1634
- setFocus((value) => Math.min(value, Math.max(0, count - 1)));
1635
- setScrollOffset((value) => Math.min(value, Math.max(0, count - 1)));
1636
- }, [
1637
- policy.mode,
1638
- props.controller,
1639
- query,
1640
- installFilter,
1641
- route.name,
1642
- route.name === 'pluginList' ? route.mode : '',
1643
- route.name === 'pluginList' ? route.sourceId : '',
1644
- snapshot.viewModel,
1645
- ]);
1646
1672
  useEffect(() => {
1647
1673
  if (route.name !== 'pluginWriteTask' || route.goal !== 'update')
1648
1674
  return;
@@ -1800,6 +1826,11 @@ export function ConsoleAppV4(props) {
1800
1826
  layer: configRoute.layer,
1801
1827
  }, snapshot.router.current.context);
1802
1828
  }, [props.controller, configRoute, currentConfigPage, activeField, snapshot.router.current.context]);
1829
+ useEffect(() => {
1830
+ if (route.name === 'configFieldAction' && activeField) {
1831
+ props.controller.replace({ ...route, name: 'configFieldEditor', editor: activeField.type });
1832
+ }
1833
+ }, [props.controller, route, activeField]);
1803
1834
  const activeObjectKey = activeField?.type === 'object' && configRoute
1804
1835
  ? `${configOwnerKey(configRoute)}\0${activeField.key}`
1805
1836
  : undefined;
@@ -1938,18 +1969,24 @@ export function ConsoleAppV4(props) {
1938
1969
  let description = '为 Codex 和 Claude Code 添加、设置或移除开发能力。';
1939
1970
  let breadcrumb = '首页';
1940
1971
  let context;
1941
- let help = '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出';
1972
+ let help = route.name === 'home'
1973
+ ? '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出'
1974
+ : '↑↓ 移动 · Enter 选择 · Esc 返回 · ? 帮助';
1942
1975
  let content;
1943
1976
  let actions = [];
1977
+ let readingText;
1978
+ let confirmationText;
1944
1979
  let referenceDetailPagination;
1945
1980
  let confirmationPagination;
1946
1981
  let pluginRows = [];
1947
- let pinnedActions = [];
1982
+ let bottomActions = [];
1983
+ let listQueryActions = [];
1948
1984
  let selectAllPortableCandidates;
1949
1985
  let togglePluginListFilter;
1950
1986
  let movePluginListSource;
1951
1987
  let choosePluginListFilter;
1952
1988
  let choosePluginListSource;
1989
+ let returnToPreviousInstallPlatform;
1953
1990
  let visibleConfigTabs = [];
1954
1991
  let activeConfigTabId;
1955
1992
  const dynamicMenu = plugin && route.name === 'platform'
@@ -1971,11 +2008,14 @@ export function ConsoleAppV4(props) {
1971
2008
  : focus;
1972
2009
  const pageActionBudget = (staticRows = 0) => contentRowBudget({
1973
2010
  terminalRows: size.rows,
1974
- description: Boolean(description),
2011
+ description: Boolean(actionError ?? description),
1975
2012
  compact: policy.mode === 'compact',
1976
- staticRows,
2013
+ staticRows: staticRows
2014
+ + Math.max(0, wrapConsoleText(actionError ?? description ?? '', size.columns - 2).length - 1)
2015
+ + Math.max(0, wrapConsoleText(title, size.columns - 2).length - 1),
1977
2016
  });
1978
- const dependencyPlacementStaticRows = policy.mode === 'compact' ? 3 : 10;
2017
+ const dependencyPlacementStaticRows = (policy.mode === 'compact' ? 3 : 10)
2018
+ + (snapshot.installPlacementDraft?.exactRootTargets?.length ?? 0);
1979
2019
  const actionViewportStaticRows = route.name === 'dependencyPlacement'
1980
2020
  ? dependencyPlacementStaticRows
1981
2021
  : route.name === 'pluginDetail' ? policy.mode === 'compact' ? 6 : 8
@@ -1988,12 +2028,42 @@ export function ConsoleAppV4(props) {
1988
2028
  const base = policy.mode === 'compact' ? compactRows : standardRows;
1989
2029
  return base + Math.max(0, size.rows - baseline);
1990
2030
  };
2031
+ const operationDetailPageRows = responsiveBudget(3, 6);
2032
+ const operationReportPageRows = responsiveBudget(3, 4);
1991
2033
  const sourceTechnicalPageRows = Math.max(1, contentRowBudget({
1992
2034
  terminalRows: size.rows, description: false, compact: policy.mode === 'compact', staticRows: 6,
1993
2035
  }));
1994
2036
  const resolveSourceTechnicalDiagnostic = (sourceId) => {
1995
2037
  const overview = props.controller.getSourceOverview(sourceId);
1996
- return sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), props.controller.getCatalogFreshness(sourceId)) ?? {
2038
+ const freshness = props.controller.getCatalogFreshness(sourceId);
2039
+ const diagnostic = sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), freshness);
2040
+ const evidenceContext = [
2041
+ { key: 'sourcePolicy', value: overview.detail?.type === 'path'
2042
+ ? 'path' : overview.detail?.refPolicy === 'explicit' ? 'explicit' : 'cliChannel' },
2043
+ ...(freshness?.checkedAt
2044
+ ? [{ key: 'freshness', value: freshness.checkedAt }]
2045
+ : []),
2046
+ ...(freshness?.candidate
2047
+ ? [{ key: 'candidate', value: JSON.stringify({
2048
+ catalogVersion: freshness.candidate.catalogVersion,
2049
+ requestedRef: freshness.candidate.requestedRef,
2050
+ resolvedCommit: freshness.candidate.resolvedCommit,
2051
+ }) }]
2052
+ : []),
2053
+ ];
2054
+ if (diagnostic)
2055
+ return {
2056
+ ...diagnostic,
2057
+ ...(diagnostic.technical ? { technical: {
2058
+ ...diagnostic.technical,
2059
+ ...(diagnostic.technical.requestedRef || !overview.detail?.requestedRef
2060
+ ? {} : { requestedRef: overview.detail.requestedRef }),
2061
+ ...(diagnostic.technical.resolvedCommit || !overview.detail?.resolvedCommit
2062
+ ? {} : { resolvedCommit: overview.detail.resolvedCommit }),
2063
+ context: [...(diagnostic.technical.context ?? []), ...evidenceContext],
2064
+ } } : {}),
2065
+ };
2066
+ return {
1997
2067
  severity: 'warning',
1998
2068
  title: '来源解析详情',
1999
2069
  userMessage: '来源当前可用。',
@@ -2005,6 +2075,7 @@ export function ConsoleAppV4(props) {
2005
2075
  sourceId,
2006
2076
  ...(overview.detail?.requestedRef ? { requestedRef: overview.detail.requestedRef } : {}),
2007
2077
  ...(overview.detail?.resolvedCommit ? { resolvedCommit: overview.detail.resolvedCommit } : {}),
2078
+ context: evidenceContext,
2008
2079
  },
2009
2080
  };
2010
2081
  };
@@ -2026,12 +2097,11 @@ export function ConsoleAppV4(props) {
2026
2097
  sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
2027
2098
  focusedEntityId: route.name === 'pluginList'
2028
2099
  ? pluginRows[focus]?.pluginId
2029
- : route.name === 'sourceList' ? props.controller.listSources()[focus]?.id : undefined,
2100
+ : route.name === 'batchSelection' ? actions[focus]?.pluginId
2101
+ : route.name === 'sourceList' ? props.controller.listSources()[focus]?.id : undefined,
2030
2102
  });
2031
2103
  props.controller.push(next);
2032
2104
  setFocus(0);
2033
- setFocusZone('content');
2034
- setPinnedActionFocus(0);
2035
2105
  setScrollOffset(0);
2036
2106
  setSearchFocus(false);
2037
2107
  if (next.name === 'pluginList') {
@@ -2040,6 +2110,19 @@ export function ConsoleAppV4(props) {
2040
2110
  setShowTechnical(false);
2041
2111
  setActionError(undefined);
2042
2112
  };
2113
+ const closeBatchSelectionTools = () => {
2114
+ if (!batchDraft)
2115
+ return;
2116
+ const candidates = batchCandidates(batchDraft);
2117
+ const anchor = batchSelectionAnchor.current;
2118
+ const retained = anchor?.frameId === snapshot.router.currentFrameId
2119
+ ? candidates.findIndex((candidate) => candidate.pluginId === anchor.pluginId) : -1;
2120
+ const nextFocus = retained >= 0 ? retained
2121
+ : Math.min(anchor?.index ?? 0, Math.max(0, candidates.length - 1));
2122
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
2123
+ setFocus(nextFocus);
2124
+ setScrollOffset(Math.min(anchor?.scrollOffset ?? 0, nextFocus));
2125
+ };
2043
2126
  const toggleTechnical = () => {
2044
2127
  if (portableResultDetailOpen)
2045
2128
  setFocus(portableResultDetailReturnFocus.current);
@@ -2072,6 +2155,12 @@ export function ConsoleAppV4(props) {
2072
2155
  ? restoredEntityIndex
2073
2156
  : Math.min(restoredFocus, Math.max(0, rowIds.length - 1));
2074
2157
  }
2158
+ else if (restoredFrame.route.name === 'batchSelection' && batchDraft) {
2159
+ const candidates = batchCandidates(batchDraft);
2160
+ const entityIndex = candidates.findIndex((candidate) => candidate.pluginId === restored.focusedEntityId);
2161
+ itemCount = candidates.length + 3;
2162
+ restoredFocus = entityIndex >= 0 ? entityIndex : Math.min(restoredFocus, Math.max(0, itemCount - 1));
2163
+ }
2075
2164
  else if (restoredFrame.route.name === 'sourceList') {
2076
2165
  const sources = props.controller.listSources();
2077
2166
  itemCount = sources.length + 3;
@@ -2139,8 +2228,6 @@ export function ConsoleAppV4(props) {
2139
2228
  else
2140
2229
  props.controller.back();
2141
2230
  restorePageContext();
2142
- setFocusZone('content');
2143
- setPinnedActionFocus(0);
2144
2231
  setActionError(undefined);
2145
2232
  };
2146
2233
  const acceptSourceFieldAndBack = () => {
@@ -2237,8 +2324,7 @@ export function ConsoleAppV4(props) {
2237
2324
  showSelected: false,
2238
2325
  });
2239
2326
  setDirty(true);
2240
- setFocus(0);
2241
- setScrollOffset(0);
2327
+ setFocus(Math.min(focus, Math.max(0, pluginRows.length - 1)));
2242
2328
  setSearchFocus(false);
2243
2329
  };
2244
2330
  const batchCandidates = (draft) => {
@@ -2433,7 +2519,13 @@ export function ConsoleAppV4(props) {
2433
2519
  };
2434
2520
  const beginPortable = async (pluginId) => {
2435
2521
  try {
2436
- await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
2522
+ if (route.name === 'portableBlocker' && snapshot.portableDraft?.pluginId === pluginId) {
2523
+ await props.controller.refreshPortableInstallDraft(pluginId);
2524
+ props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
2525
+ }
2526
+ else {
2527
+ await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
2528
+ }
2437
2529
  setFocus(0);
2438
2530
  setPortableSelectionView('all');
2439
2531
  setScrollOffset(0);
@@ -2443,8 +2535,13 @@ export function ConsoleAppV4(props) {
2443
2535
  if (error instanceof Error && error.name === 'AbortError')
2444
2536
  return;
2445
2537
  const blocker = extractPortableError(error);
2446
- const target = { name: 'portableBlocker', pluginId, ...blocker };
2447
- if (props.controller.snapshot.router.current.route.name === 'portableBlocker') {
2538
+ const operationRecordId = props.controller.recordUiFailure(error, {
2539
+ code: blocker.diagnosticCode,
2540
+ stage: 'prepare-portable',
2541
+ });
2542
+ const target = { name: 'portableBlocker', pluginId, ...blocker, operationRecordId };
2543
+ if (props.controller.snapshot.router.current.route.name === 'portableBlocker'
2544
+ || props.controller.snapshot.router.current.route.name === 'portablePreparing') {
2448
2545
  props.controller.replace(target);
2449
2546
  }
2450
2547
  else {
@@ -2476,7 +2573,11 @@ export function ConsoleAppV4(props) {
2476
2573
  if (error instanceof Error && error.name === 'AbortError')
2477
2574
  return false;
2478
2575
  const blocker = extractPortableError(error);
2479
- const target = { name: 'portableBlocker', pluginId, ...blocker };
2576
+ const operationRecordId = props.controller.recordUiFailure(error, {
2577
+ code: blocker.diagnosticCode,
2578
+ stage: 'prepare-portable',
2579
+ });
2580
+ const target = { name: 'portableBlocker', pluginId, ...blocker, operationRecordId };
2480
2581
  if (route.name === 'result')
2481
2582
  leaveResult(target);
2482
2583
  else
@@ -2735,43 +2836,14 @@ export function ConsoleAppV4(props) {
2735
2836
  const configLayerLabel = (layer) => layer.scope === 'effective' ? '有效设置'
2736
2837
  : layer.scope === 'user' ? '个人默认'
2737
2838
  : '当前项目';
2738
- const objectSummary = (field, value) => {
2739
- if (field.sensitive)
2740
- return '已设置';
2741
- if (configObjectHasSensitiveChild(field.definition)) {
2742
- const count = Object.keys(value).length;
2743
- return `已设置 · ${count === 0 ? '空对象' : `${count} 项`}`;
2744
- }
2745
- const preset = field.choices?.find((choice) => configValuesEqual(choice.value, value));
2746
- const keys = Object.keys(value).sort((left, right) => left.localeCompare(right, 'en'));
2747
- const count = keys.length === 0
2748
- ? '空对象'
2749
- : `${keys.length} 项(${keys.slice(0, 2).join('、')}${keys.length > 2 ? '…' : ''})`;
2750
- return preset ? `方案“${preset.title}” · ${count}` : `自定义 · ${count}`;
2751
- };
2752
- const scalarSummary = (field, value) => {
2753
- if (field.sensitive)
2754
- return '将更新';
2755
- const preset = field.choices?.find((choice) => configValuesEqual(choice.value, value));
2756
- if (preset)
2757
- return preset.title;
2758
- if (field.type === 'boolean')
2759
- return value ? '开启' : '关闭';
2760
- return `${String(value)}${field.unit ? ` ${field.unit}` : ''}`;
2761
- };
2762
- const currentFieldDisplay = (field) => {
2763
- if (draftRemoves.includes(field.key))
2764
- return '恢复继承';
2765
- if (Object.hasOwn(draftUpdates, field.key)) {
2766
- const value = draftUpdates[field.key];
2767
- return typeof value === 'object'
2768
- ? objectSummary(field, value)
2769
- : scalarSummary(field, value);
2770
- }
2771
- if (field.type === 'boolean' && field.value !== undefined)
2772
- return field.value ? '开启' : '关闭';
2773
- return `${field.displayValue}${field.unit && field.displayValue !== '未设置' ? ` ${field.unit}` : ''}`;
2774
- };
2839
+ const fieldValueText = (field, value, full = false, configured = value !== undefined) => presentConfigValue({
2840
+ definition: field.definition,
2841
+ value,
2842
+ configured,
2843
+ privateValue: field.sensitive,
2844
+ summary: field.summary,
2845
+ full,
2846
+ }).text;
2775
2847
  const effectiveFieldDisplay = (field) => {
2776
2848
  if (field.type === 'boolean' && field.effectiveValue !== undefined) {
2777
2849
  return field.effectiveValue ? '开启' : '关闭';
@@ -2789,7 +2861,7 @@ export function ConsoleAppV4(props) {
2789
2861
  const value = draft.updates[field.key];
2790
2862
  if (field.sensitive)
2791
2863
  return '将更新';
2792
- return typeof value === 'object' ? objectSummary(field, value) : scalarSummary(field, value);
2864
+ return fieldValueText(field, value);
2793
2865
  };
2794
2866
  const pendingFieldTargets = (field) => ['project', 'user'].flatMap((scope) => {
2795
2867
  const value = pendingFieldDisplay(field, scope);
@@ -3020,7 +3092,7 @@ export function ConsoleAppV4(props) {
3020
3092
  return '已设置';
3021
3093
  if (typeof value === 'boolean')
3022
3094
  return value ? '开启' : '关闭';
3023
- return String(value);
3095
+ return value === '' ? '(空字符串)' : safeConfigText(String(value));
3024
3096
  };
3025
3097
  const updatedChildren = beforeObject && afterObject
3026
3098
  ? [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])]
@@ -3059,13 +3131,11 @@ export function ConsoleAppV4(props) {
3059
3131
  : updatedChildren;
3060
3132
  const after = draft.removes.includes(key)
3061
3133
  ? `恢复继承(${field?.fallbackDisplayValue ?? '未设置'} · ${field?.fallbackSourceLabel ?? '未设置'})`
3062
- : updatedValue && typeof updatedValue === 'object'
3063
- ? objectSummary(field, updatedValue)
3064
- : scalarSummary(field, updatedValue);
3134
+ : fieldValueText(field, updatedValue, true);
3065
3135
  return {
3066
3136
  key,
3067
3137
  title: field?.title ?? key,
3068
- before: field?.displayValue ?? '未设置',
3138
+ before: field ? fieldValueText(field, field.draftValue ?? field.value, true, field.configured) : '未设置',
3069
3139
  after,
3070
3140
  ...(children && children.length > 0 ? { children } : {}),
3071
3141
  };
@@ -3184,34 +3254,8 @@ export function ConsoleAppV4(props) {
3184
3254
  const projectIntentIssue = snapshot.projectIntentIssue;
3185
3255
  const projectPendingCount = (projectReminder?.actionableTargets.length ?? 0)
3186
3256
  + (projectReminder?.blockedTargets.length ?? 0);
3187
- const projectTargetCount = projectPendingCount + (projectReminder?.unconfirmedTargets.length ?? 0);
3188
3257
  context = '当前项目';
3189
3258
  actions = [
3190
- ...(projectIntentIssue ? [{
3191
- actionId: 'project-intent-issue',
3192
- label: projectIntentIssue.state === 'invalid'
3193
- ? '本项目插件声明无效'
3194
- : '无法读取本项目插件声明',
3195
- description: projectIntentIssue.state === 'invalid'
3196
- ? '本项目插件声明无效;修复后重新加载'
3197
- : '无法读取本项目插件声明;检查项目位置或权限后重新加载',
3198
- status: 'warning',
3199
- run: () => go({ name: 'projectStatus' }),
3200
- }] : []),
3201
- ...(projectTargetCount > 0 ? [{
3202
- label: '项目插件',
3203
- description: projectReminder.actionableTargets.length > 0
3204
- ? `${projectReminder.actionableTargets.length} 个位置可同步;`
3205
- + `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3206
- + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3207
- : projectReminder.blockedTargets.length > 0
3208
- ? `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3209
- + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3210
- : `${projectReminder.unconfirmedTargets.length} 个位置未确认`,
3211
- meta: String(projectTargetCount),
3212
- status: 'warning',
3213
- run: () => go({ name: 'projectStatus' }),
3214
- }] : []),
3215
3259
  { label: '插件', description: '浏览、安装和管理所有插件', meta: `${home.installedPlugins} 已安装`, status: home.pluginUpdateCount > 0 ? 'warning' : undefined, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3216
3260
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
3217
3261
  {
@@ -3224,18 +3268,12 @@ export function ConsoleAppV4(props) {
3224
3268
  run: () => go({ name: 'sourceList' }),
3225
3269
  },
3226
3270
  ];
3227
- const homeViewport = rowViewport({
3228
- rowHeights: actions.map((action) => showTechnical ? 1 : objectCardRowLayout({
3229
- description: action.description ?? '', meta: action.meta, width: size.columns - 2,
3230
- compact: policy.mode === 'compact',
3231
- }).height), focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(showTechnical ? 7 : 2),
3232
- });
3233
3271
  content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[\u5DF2\u5B89\u88C5 ", home.installedPlugins, "] [\u53EF\u66F4\u65B0 ", home.pluginUpdateCount, "]", home.unavailableSourceCount > 0 ? ` [不可用来源 ${home.unavailableSourceCount}]` : ''] }), _jsx(Text, { color: tuiTheme.colors.muted, children: projectIntentIssue ? '! 本项目插件声明需要检查'
3234
3272
  : projectReminder && projectReminder.unconfirmedTargets.length > 0
3235
3273
  ? '? 部分项目插件状态无法确认' : projectPendingCount > 0
3236
3274
  ? `! ${projectPendingCount} 个位置待处理`
3237
3275
  : snapshot.projectDeclarationAbsent
3238
- ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", cards: !showTechnical, columns: size.columns - 2, compact: policy.mode === 'compact', offset: homeViewport.offset, limit: homeViewport.limit })] });
3276
+ ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", compact: false })] });
3239
3277
  }
3240
3278
  else if (route.name === 'projectStatus') {
3241
3279
  const reminder = snapshot.projectIntentReminder;
@@ -3321,7 +3359,7 @@ export function ConsoleAppV4(props) {
3321
3359
  : () => go({ name: 'pluginDetail', pluginId: target.pluginId }),
3322
3360
  };
3323
3361
  });
3324
- pinnedActions = [
3362
+ bottomActions = [
3325
3363
  ...(actionable.length > 0 ? [{
3326
3364
  actionId: 'project-sync-actionable',
3327
3365
  label: `同步明确的 ${actionable.length} 个位置`,
@@ -3348,56 +3386,41 @@ export function ConsoleAppV4(props) {
3348
3386
  description: '查看现有安全错误代码和诊断,不执行写操作',
3349
3387
  run: () => setShowTechnical((value) => !value),
3350
3388
  }] : []),
3351
- {
3352
- actionId: 'project-back-home',
3353
- label: '返回首页',
3354
- run: back,
3355
- },
3356
3389
  ];
3357
3390
  help = showTechnical
3358
- ? 'p/n 或 PgUp/PgDn 阅读 · d 返回项目位置 · Tab 固定操作 · Esc 返回'
3359
- : focusZone === 'actions'
3360
- ? '←→ 移动固定操作 · Enter 选择 · Tab 返回项目位置 · Esc 返回'
3361
- : '↑↓ 移动 · Enter 查看 · Tab 固定操作 · Esc 返回';
3362
- const viewport = rowViewport({
3363
- rowHeights: actions.map((action) => objectCardRowLayout({
3364
- description: action.description ?? '', meta: action.meta,
3365
- width: size.columns - 2, hasTags: true, compact: policy.mode === 'compact',
3366
- }).height),
3367
- focus,
3368
- requestedOffset: scrollOffset,
3369
- rowBudget: pageActionBudget(showTechnical ? 5
3370
- : 1 + (projectIntentIssue || unconfirmed.length > 0 ? 1 : 0)
3371
- + (actionable.length > 0 && blocked.length + unconfirmed.length > 0 ? 1 : 0)),
3372
- });
3373
- const technicalLines = [
3391
+ ? '←→ 或 PgUp/PgDn 阅读 · d 返回项目位置 · Esc 返回'
3392
+ : '↑↓ 移动 · Enter 查看 · Esc 返回';
3393
+ const projectItemCount = showTechnical ? 0 : actions.length;
3394
+ actions = [...(showTechnical ? [] : actions), ...bottomActions];
3395
+ const technicalFields = [
3374
3396
  ...(projectIntentIssue ? [
3375
3397
  `项目声明 · ${safeDiagnosticCode(projectIntentIssue.code, 'projectIntentUnavailable')}`,
3376
- ...wrapConsoleText(`说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`, Math.max(20, size.columns - 8)),
3398
+ `说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`,
3377
3399
  ] : []),
3378
3400
  ...allTargets.flatMap(({ target, state }) => [
3379
- ...wrapConsoleText(`${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, Math.max(20, size.columns - 8)),
3401
+ `${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3380
3402
  ...(target.reasonCodes.length > 0
3381
- ? target.reasonCodes.flatMap((code) => wrapConsoleText(`原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`, Math.max(20, size.columns - 8)))
3403
+ ? target.reasonCodes.map((code) => `原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`)
3382
3404
  : [state === 'actionable' ? '原因 · confirmedDifference'
3383
3405
  : state === 'blocked' ? '原因 · blocked' : '原因 · unconfirmed']),
3384
3406
  ]),
3385
3407
  ];
3408
+ const technicalLines = technicalFields.flatMap((field) => wrapConsoleText(field, Math.max(20, size.columns - 8)));
3409
+ if (showTechnical)
3410
+ readingText = technicalFields.join('\n');
3411
+ if (showTechnical)
3412
+ description = undefined;
3386
3413
  const technicalPageRows = responsiveBudget(4, 8);
3387
3414
  const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
3388
3415
  const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
3389
3416
  referenceDetailPagination = showTechnical && technicalLines.length > 0
3390
3417
  ? { total: technicalLines.length, pageRows: technicalPageRows }
3391
3418
  : undefined;
3392
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
3393
- id: action.actionId ?? `${action.label}:${index}`,
3394
- label: action.label,
3395
- disabled: Boolean(action.disabled),
3396
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), projectIntentIssue || unconfirmed.length > 0 ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, actionable.length > 0 && blocked.length + unconfirmed.length > 0
3419
+ content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical && (projectIntentIssue || unconfirmed.length > 0) ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, !showTechnical && actionable.length > 0 && blocked.length + unconfirmed.length > 0
3397
3420
  ? _jsx(StaticRow, { status: "info", title: `只同步明确项;保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置。`, compact: true })
3398
3421
  : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u72B6\u6001\u5B89\u5168\u8BE6\u60C5", compact: true, children: [technicalOffset > 0
3399
3422
  ? _jsx(StaticRow, { title: `↑ 前面还有 ${technicalOffset} 行`, status: "muted", compact: true }) : null, visibleTechnicalLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${technicalOffset + index}:${line}`)), technicalOffset + visibleTechnicalLines.length < technicalLines.length
3400
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
3423
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(SequenceRows, { actions: actions, itemCount: projectItemCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2), compact: policy.mode === 'compact', cards: true }), showTechnical ? _jsx(SemanticRows, { actions: actions, offset: projectItemCount, focus: focus, kind: "action", compact: true }) : null] });
3401
3424
  }
3402
3425
  else if (route.name === 'pluginList') {
3403
3426
  const sourceId = route.sourceId ?? 'all';
@@ -3416,12 +3439,10 @@ export function ConsoleAppV4(props) {
3416
3439
  }).length,
3417
3440
  ]));
3418
3441
  breadcrumb = '插件';
3419
- title = '插件';
3420
- description = '浏览、安装和管理所有插件。选择会跨来源、搜索和筛选保留。';
3442
+ title = `插件 · ${items.length} 个 [m] 多选`;
3443
+ description = undefined;
3421
3444
  context = `${items.length} 个插件`;
3422
- help = focusZone === 'actions'
3423
- ? '←→/↑↓ 操作 · Enter 执行 · Tab 插件列表 · Esc 返回'
3424
- : '↑↓ 插件 · Enter 查看 · Tab 操作栏 · m 多选 · / 搜索 · f 状态 · Esc 返回';
3445
+ help = '↑↓ 移动 · Enter 查看 · Esc 返回';
3425
3446
  const sourceError = sourceId !== 'all'
3426
3447
  && props.controller.getSourceOverview(sourceId).health?.state === 'unavailable';
3427
3448
  actions = sourceError
@@ -3439,7 +3460,7 @@ export function ConsoleAppV4(props) {
3439
3460
  const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
3440
3461
  setInstallFilter(nextFilter);
3441
3462
  props.controller.updatePageContext({ installFilter: nextFilter });
3442
- setFocus(retained >= 0 ? retained : 0);
3463
+ setFocus(retained >= 0 ? retained : Math.min(focus, Math.max(0, nextRowIds.length - 1)));
3443
3464
  setScrollOffset(0);
3444
3465
  };
3445
3466
  togglePluginListFilter = () => {
@@ -3462,7 +3483,7 @@ export function ConsoleAppV4(props) {
3462
3483
  props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
3463
3484
  const retainedFocus = retained >= 0
3464
3485
  ? retained
3465
- : nextItems[0] ? nextRowIds.indexOf(nextItems[0].pluginId) : 0;
3486
+ : Math.min(focus, Math.max(0, nextRowIds.length - 1));
3466
3487
  setFocus(retainedFocus);
3467
3488
  setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
3468
3489
  };
@@ -3504,32 +3525,24 @@ export function ConsoleAppV4(props) {
3504
3525
  }] : []),
3505
3526
  {
3506
3527
  actionId: 'plugin-list-multi',
3507
- label: query ? 'm 当前结果多选' : 'm 多选',
3528
+ label: '选择多个插件 [m]',
3508
3529
  description: '先选择插件,再选择安装、更新、恢复、卸载或维护任务',
3509
3530
  status: 'info',
3510
3531
  run: () => openBatchSelection(),
3511
3532
  },
3512
3533
  ];
3513
- const toolActions = [
3514
- {
3515
- actionId: 'plugin-list-filter',
3516
- label: `筛选:${pluginFilterLabel(installFilter)}`,
3517
- run: () => setPluginListChooser({
3518
- kind: 'status',
3519
- focus: Math.max(0, PLUGIN_FILTERS.indexOf(installFilter)),
3520
- }),
3521
- },
3522
- { actionId: 'plugin-list-search', label: '/ 搜索', run: () => setSearchFocus(true) },
3523
- {
3524
- actionId: 'plugin-list-source',
3525
- label: `来源:${sourceId === 'all' ? '全部' : sourceId}`,
3526
- run: () => setPluginListChooser({
3527
- kind: 'source',
3528
- focus: Math.max(0, ['all', ...props.controller.listSources().map((source) => source.id)]
3529
- .indexOf(sourceId)),
3530
- }),
3534
+ const searchAction = {
3535
+ actionId: 'plugin-list-search', label: '搜索 [/]',
3536
+ run: () => {
3537
+ setSearchCursor([...new Intl.Segmenter(undefined, { granularity: 'grapheme' })
3538
+ .segment(query)].length);
3539
+ setSearchFocus(true);
3531
3540
  },
3532
- ];
3541
+ };
3542
+ const filterAction = {
3543
+ actionId: 'plugin-list-filter', label: '切换筛选 [f]', run: togglePluginListFilter,
3544
+ };
3545
+ listQueryActions = [searchAction, filterAction];
3533
3546
  const pluginRowsById = new Map([
3534
3547
  ...items.map((item) => [item.pluginId, {
3535
3548
  pluginId: item.pluginId,
@@ -3600,38 +3613,32 @@ export function ConsoleAppV4(props) {
3600
3613
  } } : { disabled: '没有可打开的插件' }),
3601
3614
  }];
3602
3615
  }
3603
- pinnedActions = dedupeActions([
3616
+ bottomActions = dedupeActions([
3604
3617
  ...(sourceError ? actions : []),
3605
- ...(pluginRows.length > 0 ? menuActions : actions),
3606
- ...toolActions,
3618
+ ...actions,
3619
+ ...menuActions,
3607
3620
  ]);
3608
- const pluginListStaticRows = 4;
3609
- const viewport = pluginRows.length === 0
3610
- ? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
3611
- : rowViewport({
3612
- rowHeights: pluginRows.map((row) => row.attention ? 6 : 5),
3613
- focus,
3614
- requestedOffset: scrollOffset,
3615
- rowBudget: pageActionBudget(pluginListStaticRows),
3616
- });
3617
- if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
3618
- context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
3619
- }
3620
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
3621
- id: action.actionId ?? `${action.label}:${index}`,
3622
- label: action.label,
3623
- disabled: Boolean(action.disabled),
3624
- })), active: !pluginListChooser && focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3625
- ? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3626
- : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " \u5168\u90E8 ", filterCounts.all, " \u00B7 \u5DF2\u88C5 ", filterCounts.installed, " \u00B7 \u672A\u88C5 ", filterCounts.uninstalled, " \u00B7 \u66F4\u65B0 ", filterCounts.updateAvailable, " \u00B7 \u5904\u7406 ", filterCounts.attention, " f \u5207\u6362"] }), sourceError
3621
+ actions = [...pluginRows.map((row) => ({
3622
+ actionId: `plugin:${row.pluginId}`, pluginId: row.pluginId, label: row.name,
3623
+ run: () => go({ name: 'pluginDetail', pluginId: row.pluginId }),
3624
+ })), ...bottomActions];
3625
+ const viewport = rowViewport({
3626
+ rowHeights: actions.map((_, index) => index < pluginRows.length
3627
+ ? pluginListRowLayout({
3628
+ ...pluginRows[index], width: size.columns - 2,
3629
+ hasTags: Boolean(pluginRows[index].tags),
3630
+ }).height : 1),
3631
+ focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(3),
3632
+ });
3633
+ content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 [/] ", searchFocus
3634
+ ? _jsx(InputDisplay, { value: query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3635
+ : _jsxs(_Fragment, { children: [query || '输入名称或用途', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " [f] \u5207\u6362"] }), sourceError
3627
3636
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
3628
3637
  : route.mode === 'installed' && items.length === 0 && !query && sourceId === 'all'
3629
3638
  ? installFilter === 'updateAvailable'
3630
3639
  ? _jsx(Callout, { title: "\u6CA1\u6709\u53EF\u66F4\u65B0\u63D2\u4EF6", description: "\u5F53\u524D\u7B5B\u9009\u8303\u56F4\u5185\u6CA1\u6709\u7ECF\u8FC7\u7248\u672C\u8BC1\u636E\u786E\u8BA4\u7684\u66F4\u65B0\u3002" })
3631
3640
  : _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
3632
- : null, _jsx(ContentViewport, { children: pluginRows.length > 0
3633
- ? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport, active: !pluginListChooser && focusZone === 'content' })
3634
- : _jsx(SemanticRows, { actions: actions, focus: !pluginListChooser && focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] }));
3641
+ : null, _jsxs(ContentViewport, { children: [_jsx(PluginRows, { columns: size.columns - 2, rows: pluginRows, focus: focus, viewport: viewport, active: !searchFocus }), _jsx(SemanticRows, { actions: actions, focus: searchFocus ? -1 : focus, kind: "action", compact: true, offset: Math.max(pluginRows.length, viewport.offset), limit: Math.max(0, viewport.offset + viewport.limit - Math.max(pluginRows.length, viewport.offset)) })] })] }));
3635
3642
  }
3636
3643
  else if (route.name === 'batchSelection') {
3637
3644
  breadcrumb = '插件 / 选择多个';
@@ -3639,12 +3646,12 @@ export function ConsoleAppV4(props) {
3639
3646
  : batchDraft?.stage === 'maintenance' ? '其他维护方式'
3640
3647
  : '选择插件';
3641
3648
  description = batchDraft?.stage === 'select'
3642
- ? 'Space 切换选择,Enter 查看插件。选择会跨来源、搜索和筛选保留。'
3649
+ ? undefined
3643
3650
  : batchDraft?.stage === 'task'
3644
3651
  ? '更新和恢复沿用已经添加的位置;移除会逐项显示已有位置。'
3645
3652
  : '同步、重新安装和普通清理使用所选插件的已有位置。';
3646
3653
  if (!batchDraft) {
3647
- actions = [{ label: '返回插件列表', run: back }];
3654
+ actions = [{ role: 'navigation', label: '返回插件列表', run: back }];
3648
3655
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
3649
3656
  }
3650
3657
  else if (batchDraft.stage === 'select') {
@@ -3658,8 +3665,10 @@ export function ConsoleAppV4(props) {
3658
3665
  const plugin = candidate.plugin;
3659
3666
  return {
3660
3667
  label: plugin
3661
- ? `${plugin.displayName} · ${plugin.sourceLabel}`
3668
+ ? plugin.displayName
3662
3669
  : `${candidate.pluginId}(已不可用,可取消选择)`,
3670
+ sourceLabel: plugin?.sourceLabel,
3671
+ maxDescriptionRows: 2,
3663
3672
  description: plugin
3664
3673
  ? `${plugin.description} · ${blockReason
3665
3674
  ? `${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
@@ -3695,9 +3704,11 @@ export function ConsoleAppV4(props) {
3695
3704
  }
3696
3705
  moveBatchStage('task');
3697
3706
  };
3707
+ const continueLabel = batchDraft.recommendedGoal === 'update' ? '查看升级汇总'
3708
+ : batchDraft.recommendedGoal ? '查看恢复汇总' : '继续选择任务';
3698
3709
  const continueAction = {
3699
3710
  actionId: 'batch-selection-continue',
3700
- label: `查看确认(${batchDraft.selectedPluginIds.length})`,
3711
+ label: `${continueLabel}(${batchDraft.selectedPluginIds.length}) [c]`,
3701
3712
  description: batchDraft.selectedPluginIds.length > 0
3702
3713
  ? batchDraft.recommendedGoal ? '重新读取更新信息并显示操作汇总' : '继续选择任务和确认范围'
3703
3714
  : '先选择至少一个插件',
@@ -3715,19 +3726,13 @@ export function ConsoleAppV4(props) {
3715
3726
  } : current);
3716
3727
  setFocus(0);
3717
3728
  setScrollOffset(0);
3718
- setFocusZone('content');
3719
3729
  },
3720
3730
  };
3721
3731
  const selectionToolActions = [
3722
3732
  {
3723
- label: '返回插件选择',
3733
+ role: 'navigation', label: '返回插件选择',
3724
3734
  description: '保留当前已选插件',
3725
- run: () => {
3726
- setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
3727
- setFocus(0);
3728
- setScrollOffset(0);
3729
- setFocusZone('content');
3730
- },
3735
+ run: closeBatchSelectionTools,
3731
3736
  },
3732
3737
  {
3733
3738
  label: '全选当前结果',
@@ -3755,35 +3760,25 @@ export function ConsoleAppV4(props) {
3755
3760
  label: batchDraft.showSelectionTools ? '返回插件选择' : '选择工具',
3756
3761
  description: '全选、取消当前结果或清空全部选择',
3757
3762
  run: () => {
3758
- setBatchDraft((current) => current ? {
3759
- ...current,
3760
- showSelectionTools: !current.showSelectionTools,
3761
- } : current);
3763
+ if (batchDraft.showSelectionTools) {
3764
+ closeBatchSelectionTools();
3765
+ return;
3766
+ }
3767
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: true } : current);
3762
3768
  setFocus(0);
3763
3769
  setScrollOffset(0);
3764
- setFocusZone('content');
3765
3770
  },
3766
3771
  };
3767
- actions = batchDraft.showSelectionTools ? selectionToolActions : candidateActions;
3768
- pinnedActions = [continueAction, selectedSetAction, selectionToolsAction];
3772
+ bottomActions = [continueAction, selectedSetAction, selectionToolsAction];
3773
+ actions = [...(batchDraft.showSelectionTools ? selectionToolActions : candidateActions), ...bottomActions];
3769
3774
  const batchFocus = Math.min(focus, Math.max(0, actions.length - 1));
3770
3775
  context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
3771
3776
  help = batchDraft.showSelectionTools
3772
- ? '↑↓ 移动 · Enter 执行 · Tab 操作栏 · Esc 返回选择'
3773
- : '↑↓ 移动 · Space 选择 · Enter 查看 · Tab 操作栏 · c 查看确认 · ←→ 来源 · / 搜索 · f 状态 · Esc 返回';
3774
- const cardWidth = Math.max(20, size.columns - 2);
3775
- const viewport = batchDraft.showSelectionTools
3776
- ? actionViewport(actions, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)))
3777
- : objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)));
3778
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
3779
- id: action.actionId ?? action.label,
3780
- label: action.label,
3781
- disabled: Boolean(action.disabled),
3782
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3783
- ? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3784
- : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(batchDraft.installFilter), "]"] }), " f \u5207\u6362"] }), _jsx(ContentViewport, { children: batchDraft.showSelectionTools
3785
- ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? batchFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })
3786
- : _jsx(Box, { flexDirection: "column", children: actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => (_jsx(ObjectCardRow, { width: cardWidth, title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: focusZone === 'content' && viewport.offset + visibleIndex === batchFocus, anchored: focusZone !== 'content' && viewport.offset + visibleIndex === batchFocus, compact: policy.mode === 'compact' }, action.pluginId ?? action.label))) }) })] });
3777
+ ? '↑↓ 移动 · Enter 执行 · Esc 返回选择'
3778
+ : '↑↓ 移动 · Space 选择 · Enter 查看 · Esc 返回';
3779
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 [/] ", searchFocus
3780
+ ? _jsx(InputDisplay, { value: batchDraft.query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3781
+ : _jsxs(_Fragment, { children: [batchDraft.query || '输入名称或用途', batchDraft.query ? ' / 编辑' : ''] })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(batchDraft.installFilter), "]"] }), " [f] \u5207\u6362"] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u5DF2\u9009 ", selected.size, " \u00B7 \u9690\u85CF\u5DF2\u9009 ", hiddenSelected, " [c] ", continueLabel] }), _jsx(SequenceRows, { actions: actions, itemCount: batchDraft.showSelectionTools ? 0 : candidateActions.length, focus: batchFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(4), compact: policy.mode === 'compact', cards: true, inactive: searchFocus })] });
3787
3782
  }
3788
3783
  else {
3789
3784
  const selectedIds = batchDraft.selectedPluginIds;
@@ -3824,7 +3819,7 @@ export function ConsoleAppV4(props) {
3824
3819
  { label: '重新安装', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
3825
3820
  { label: '清理遗留内容', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个已核验位置;保留不属于插件或被共用的内容`, run: () => startExistingBatch('cleanup') },
3826
3821
  {
3827
- label: '返回任务选择',
3822
+ role: 'navigation', label: '返回任务选择',
3828
3823
  run: () => moveBatchStage('task'),
3829
3824
  },
3830
3825
  ];
@@ -3866,7 +3861,7 @@ export function ConsoleAppV4(props) {
3866
3861
  description: '同步、重新安装和普通清理',
3867
3862
  run: () => moveBatchStage('maintenance'),
3868
3863
  },
3869
- { label: '返回调整选择', run: () => moveBatchStage('select') },
3864
+ { role: 'navigation', label: '返回调整选择', run: () => moveBatchStage('select') },
3870
3865
  ];
3871
3866
  }
3872
3867
  context = `${selectedIds.length} 个插件 · ${syncTargets.length} 个已保存或现场目标`;
@@ -3905,7 +3900,7 @@ export function ConsoleAppV4(props) {
3905
3900
  };
3906
3901
  const confirmPlatforms = {
3907
3902
  actionId: 'batch-install-platforms-confirm',
3908
- label: '确定所选平台',
3903
+ label: '确定所选平台 [c]',
3909
3904
  description: `${[...selectedPlatforms].map(platformLabel).join('、')} · 继续选择目标`,
3910
3905
  run: () => {
3911
3906
  props.controller.push({ name: 'batchInstallScope' });
@@ -3930,9 +3925,9 @@ export function ConsoleAppV4(props) {
3930
3925
  },
3931
3926
  confirmPlatforms,
3932
3927
  ];
3933
- pinnedActions = [confirmPlatforms];
3934
- help = '↑↓ 移动 · Enter 选择/确定 · c 确定所选平台 · Esc 返回任务选择';
3935
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: [{ id: confirmPlatforms.actionId, label: confirmPlatforms.label }], active: focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focusZone === 'content' ? focus : -1, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: true, offset: 2 })] })] });
3928
+ bottomActions = [confirmPlatforms];
3929
+ help = '↑↓ 移动 · Space/Enter 切换 · c 确定所选平台 · Esc 返回任务选择';
3930
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: 2 })] }) });
3936
3931
  }
3937
3932
  else if (route.name === 'batchInstallScope' || route.name === 'batchPlacement') {
3938
3933
  breadcrumb = route.name === 'batchInstallScope'
@@ -3960,7 +3955,7 @@ export function ConsoleAppV4(props) {
3960
3955
  let installPlacementActions = [];
3961
3956
  let maintenanceTargetCount = 0;
3962
3957
  if (!batchDraft?.action && !batchDraft?.maintenanceGoal) {
3963
- actions = [{ label: '返回批量选择', run: back }];
3958
+ actions = [{ role: 'navigation', label: '返回批量选择', run: back }];
3964
3959
  }
3965
3960
  else if (maintenanceMode) {
3966
3961
  const currentTargets = batchDraft.maintenanceGoal
@@ -4014,9 +4009,9 @@ export function ConsoleAppV4(props) {
4014
4009
  actions = [
4015
4010
  ...targetActions,
4016
4011
  confirmMaintenance,
4017
- { label: '返回任务选择', run: back },
4012
+ { role: 'navigation', label: '返回任务选择', run: back },
4018
4013
  ];
4019
- pinnedActions = [confirmMaintenance];
4014
+ bottomActions = [confirmMaintenance];
4020
4015
  }
4021
4016
  else {
4022
4017
  const projectIntent = batchDraft.selection === 'currentProjectIntentRoots';
@@ -4106,7 +4101,7 @@ export function ConsoleAppV4(props) {
4106
4101
  if (route.name === 'batchInstallScope') {
4107
4102
  const confirmPlacement = installPlacementActions.find((action) => action.actionId === 'batch-placement-confirm');
4108
4103
  if (confirmPlacement)
4109
- pinnedActions = [confirmPlacement];
4104
+ bottomActions = [confirmPlacement];
4110
4105
  }
4111
4106
  if (batchDraft.showPlacementTargets) {
4112
4107
  actions = [
@@ -4118,7 +4113,7 @@ export function ConsoleAppV4(props) {
4118
4113
  : target.state === 'blocked' ? 'warning' : 'muted',
4119
4114
  })),
4120
4115
  {
4121
- label: '返回位置选择',
4116
+ role: 'navigation', label: '返回位置选择',
4122
4117
  run: () => {
4123
4118
  setBatchDraft((current) => current ? { ...current, showPlacementTargets: false } : current);
4124
4119
  restoreActionFocus('batch-placement-details');
@@ -4137,35 +4132,26 @@ export function ConsoleAppV4(props) {
4137
4132
  help = batchDraft?.showPlacementTargets
4138
4133
  ? '↑↓ 查看全部位置 · Esc 返回位置选择'
4139
4134
  : maintenanceMode
4140
- ? '↑↓ 移动 · Enter 选择 · Tab 固定操作 · c 查看确认 · Esc 返回修改'
4135
+ ? '↑↓ 移动 · Enter 选择· c 查看确认 · Esc 返回修改'
4141
4136
  : '↑↓ 移动 · Enter 选择 · Esc 返回修改';
4142
4137
  const placementDetail = Boolean(batchDraft?.showPlacementTargets);
4143
- const viewport = actionViewport(actions, placementDetail ? false : policy.mode === 'compact', focus, scrollOffset, pageActionBudget(maintenanceMode ? 2 : 1));
4138
+ const viewport = actionViewport(actions, placementDetail ? false : policy.mode === 'compact', focus, scrollOffset, pageActionBudget(maintenanceMode ? 4 : 1));
4144
4139
  if (maintenanceMode) {
4145
4140
  const targetOffset = Math.min(viewport.offset, maintenanceTargetCount);
4146
4141
  const targetLimit = Math.max(0, Math.min(maintenanceTargetCount, viewport.offset + viewport.limit) - targetOffset);
4147
4142
  const actionOffset = Math.max(0, viewport.offset - maintenanceTargetCount);
4148
4143
  const actionLimit = Math.max(0, Math.min(actions.length - maintenanceTargetCount, viewport.offset + viewport.limit - maintenanceTargetCount) - actionOffset);
4149
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: [{
4150
- id: pinnedActions[0].actionId,
4151
- label: pinnedActions[0].label,
4152
- disabled: Boolean(pinnedActions[0].disabled),
4153
- }], active: focusZone === 'actions', focus: pinnedActionFocus }), _jsx(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${maintenanceSelection.size} 个位置`, compact: true, children: targetLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(0, maintenanceTargetCount), focus: focus, kind: "toggle", compact: true, offset: targetOffset, limit: targetLimit }) : null }), actionLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(maintenanceTargetCount), focus: focus - maintenanceTargetCount, kind: "action", compact: policy.mode === 'compact', offset: actionOffset, limit: actionLimit }) : null] });
4144
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${maintenanceSelection.size} 个位置`, compact: true, children: targetLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(0, maintenanceTargetCount), focus: focus, kind: "toggle", compact: true, offset: targetOffset, limit: targetLimit }) : null }), actionLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(maintenanceTargetCount), focus: focus - maintenanceTargetCount, kind: "action", compact: policy.mode === 'compact', offset: actionOffset, limit: actionLimit }) : null] });
4154
4145
  }
4155
4146
  else if (!placementDetail && batchDraft?.action === 'install') {
4156
- content = _jsxs(Box, { flexDirection: "column", children: [route.name === 'batchInstallScope' && pinnedActions[0]
4157
- ? _jsx(PinnedActionBar, { actions: [{
4158
- id: pinnedActions[0].actionId, label: pinnedActions[0].label,
4159
- disabled: Boolean(pinnedActions[0].disabled),
4160
- }], active: focusZone === 'actions', focus: pinnedActionFocus })
4161
- : null, _jsx(ContentViewport, { children: installPlacementGroups.map((group) => {
4162
- const groupOffset = installPlacementActions.findIndex((action) => action.actionId === group.actions[0]?.actionId);
4163
- const localOffset = Math.max(0, viewport.offset - groupOffset);
4164
- const localLimit = Math.max(0, Math.min(group.actions.length - localOffset, viewport.offset + viewport.limit - groupOffset - localOffset));
4165
- if (localLimit === 0)
4166
- return null;
4167
- return _jsxs(Box, { flexDirection: "column", children: [group.title ? _jsx(Text, { color: tuiTheme.colors.muted, children: group.title }) : null, _jsx(SemanticRows, { actions: group.actions, focus: focusZone === 'content' ? focus - groupOffset : -1, kind: group.kind, compact: group.kind === 'choice' || policy.mode === 'compact', offset: localOffset, limit: localLimit })] }, group.title ?? 'actions');
4168
- }) })] });
4147
+ content = _jsx(Box, { flexDirection: "column", children: _jsx(ContentViewport, { children: installPlacementGroups.map((group) => {
4148
+ const groupOffset = installPlacementActions.findIndex((action) => action.actionId === group.actions[0]?.actionId);
4149
+ const localOffset = Math.max(0, viewport.offset - groupOffset);
4150
+ const localLimit = Math.max(0, Math.min(group.actions.length - localOffset, viewport.offset + viewport.limit - groupOffset - localOffset));
4151
+ if (localLimit === 0)
4152
+ return null;
4153
+ return _jsxs(Box, { flexDirection: "column", children: [group.title ? _jsx(Text, { color: tuiTheme.colors.muted, children: group.title }) : null, _jsx(SemanticRows, { actions: group.actions, focus: focus - groupOffset, kind: group.kind, compact: group.kind === 'choice' || policy.mode === 'compact', offset: localOffset, limit: localLimit })] }, group.title ?? 'actions');
4154
+ }) }) });
4169
4155
  }
4170
4156
  else
4171
4157
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: placementDetail ? 'action' : 'choice', compact: placementDetail ? false : policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
@@ -4193,11 +4179,7 @@ export function ConsoleAppV4(props) {
4193
4179
  meta: `${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件'
4194
4180
  : candidate.relation === 'direct' ? '直接依赖插件' : '间接依赖插件'}`,
4195
4181
  tags: _jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", candidate.availableScopes.map(scopeLabel).join('、') || '范围待确认', "]", candidate.dynamic ? ' [按设置生成]' : ''] }),
4196
- run: () => go({
4197
- name: 'portableSkillSummary',
4198
- pluginId: plugin.pluginId,
4199
- skillName: candidate.name,
4200
- }),
4182
+ run: () => props.controller.togglePortableSkill(candidate.name),
4201
4183
  }));
4202
4184
  selectAllPortableCandidates = { actionId: 'portable-selection-select-all', label: '全选当前结果', run: () => {
4203
4185
  for (const candidate of visibleCandidates) {
@@ -4221,63 +4203,45 @@ export function ConsoleAppV4(props) {
4221
4203
  setScrollOffset(0);
4222
4204
  } },
4223
4205
  ...(plugin.dependencySummary?.totalCount ? [{ actionId: 'portable-selection-dependencies', label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
4224
- { actionId: 'portable-selection-tools-back', label: '返回 Skills 列表', run: () => {
4206
+ { role: 'navigation', actionId: 'portable-selection-tools-back', label: '返回 Skills 列表', run: () => {
4225
4207
  setPortableToolsOpen(false);
4226
4208
  setFocus(0);
4227
4209
  setScrollOffset(0);
4228
4210
  } },
4229
4211
  ];
4230
- const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount})`, run: continuePortableSelection };
4212
+ const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount}) [c]`, run: continuePortableSelection };
4231
4213
  const openToolsAction = { actionId: 'portable-selection-tools', label: '选择工具', run: () => {
4232
4214
  setPortableToolsOpen(true);
4233
- setFocusZone('content');
4234
4215
  setFocus(0);
4235
4216
  setScrollOffset(0);
4236
4217
  } };
4237
- pinnedActions = [continueAction, openToolsAction];
4238
- actions = portableToolsOpen ? selectionToolActions : candidateActions;
4239
- const candidateViewport = portableSkillCandidateViewport(candidateActions, Math.max(20, size.columns - 2), policy.mode === 'compact', focus, scrollOffset, pageActionBudget(2 + (portableVariantCandidates.length > 0 ? 3 : 0)));
4240
- const viewport = showTechnical
4241
- ? { offset: focus < visibleCandidates.length ? focus : candidateViewport.offset, limit: 1 }
4242
- : candidateViewport;
4243
- const visibleCandidateEntries = visibleCandidates
4244
- .map((candidate, index) => ({ candidate, action: candidateActions[index], index }))
4245
- .slice(viewport.offset, viewport.offset + viewport.limit);
4218
+ bottomActions = [continueAction, openToolsAction];
4219
+ actions = portableToolsOpen ? selectionToolActions : [...candidateActions, ...bottomActions];
4220
+ const visibleCandidateEntries = visibleCandidates.map((candidate, index) => ({ candidate, index }));
4246
4221
  const variantCandidates = portableVariantCandidates;
4247
4222
  const focusedVariant = variantCandidates.find((candidate) => candidate.name === visibleCandidates[focus]?.name);
4248
4223
  const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
4249
4224
  const technicalVariant = focusedVariant ?? visibleVariant ?? variantCandidates[0];
4250
- help = portableToolsOpen
4251
- ? '↑↓ 移动 · Enter 选择 · Tab 返回固定栏 · Esc 返回'
4252
- : focusZone === 'actions'
4253
- ? '←→/↑↓ 移动操作 · Enter 选择 · Tab 返回列表 · c 继续 · Esc 返回'
4254
- : '↑↓ 移动 · Space 切换 · Enter 查看摘要 · Tab 固定栏 · c 继续 · Esc 返回';
4255
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4256
- id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4257
- })), active: !portableEmptySelectionOpen && focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [portableToolsOpen
4258
- ? _jsx(SemanticRows, { actions: selectionToolActions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact' })
4259
- : visibleCandidateEntries.map(({ action, index }) => _jsx(ObjectCardRow, { width: Math.max(20, size.columns - 2), title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.description ?? '', meta: action.meta, tags: action.tags, focused: !portableEmptySelectionOpen && focusZone === 'content' && focus === index, anchored: !portableEmptySelectionOpen && focusZone !== 'content' && focus === index, muted: portableEmptySelectionOpen, compact: policy.mode === 'compact' }, String(action.label))), !portableToolsOpen && variantCandidates.length > 0
4260
- ? showTechnical
4261
- ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4262
- : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
4263
- : null, !portableToolsOpen && visibleCandidates.length === 0
4264
- ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
4265
- : null, !portableToolsOpen && !draft.canContinue
4266
- ? _jsx(StaticRow, { title: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A Skill", status: "warning", compact: true })
4267
- : null, !portableToolsOpen && showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] })] });
4225
+ help = portableToolsOpen ? '↑↓ 移动 · Enter 选择 · Esc 返回'
4226
+ : '↑↓ 移动 · Space/Enter 切换 · Esc 返回';
4227
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SequenceRows, { actions: actions, itemCount: portableToolsOpen ? 0 : candidateActions.length, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget((variantCandidates.length > 0 ? 3 : 0) + (!draft.canContinue ? 1 : 0)
4228
+ + (showTechnical && technicalVariant ? 5 : 0)), compact: policy.mode === 'compact', cards: true, inactive: portableEmptySelectionOpen }), !portableToolsOpen && variantCandidates.length > 0
4229
+ ? showTechnical
4230
+ ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4231
+ : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
4232
+ : null, !portableToolsOpen && visibleCandidates.length === 0
4233
+ ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
4234
+ : null, !portableToolsOpen && !draft.canContinue
4235
+ ? _jsx(StaticRow, { title: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A Skill", status: "warning", compact: true })
4236
+ : null, !portableToolsOpen && showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] }) });
4268
4237
  }
4269
- else if (route.name === 'portableSkillSummary' && plugin && snapshot.portableDraft) {
4270
- const candidate = snapshot.portableDraft.candidates.find((entry) => entry.name === route.skillName);
4271
- breadcrumb = `${plugin.displayName} / 通用 Skills / 内容摘要`;
4272
- context = candidate?.providerDisplayName ?? plugin.displayName;
4273
- title = candidate?.name ?? 'Skill 不再可用';
4274
- description = candidate?.description ?? '候选已经变化。请返回重新选择。';
4275
- actions = [
4276
- { label: '返回 Skill 选择', run: back },
4277
- { label: '继续选择 Agent', disabled: snapshot.portableDraft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
4278
- ];
4279
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5185\u5BB9\u6458\u8981", children: [_jsx(StaticRow, { title: "\u6240\u5C5E\u63D2\u4EF6", meta: candidate?.providerDisplayName ?? '未知', compact: true }), _jsx(StaticRow, { title: "\u751F\u6210\u65B9\u5F0F", meta: candidate?.dynamic ? '按当前插件设置生成' : '目录中的静态 Skill', compact: true }), _jsx(StaticRow, { title: "\u53EF\u7528\u8303\u56F4", meta: candidate?.availableScopes.map(scopeLabel).join('、') ?? '未确认', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4280
- help = '↑↓ 移动 · Enter 选择 · Esc 返回';
4238
+ else if (route.name === 'portablePreparing' && plugin) {
4239
+ breadcrumb = `${plugin.displayName} / 通用 Skills`;
4240
+ context = '只读检查';
4241
+ title = '正在准备可安装的 Skills';
4242
+ description = '正在检查当前项目和所有项目范围的完整依赖。此步骤不会写入文件或调用 Skills CLI。';
4243
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(StatusCard, { status: "info", title: "\u51C6\u5907\u5019\u9009", children: [_jsx(StaticRow, { title: "\u25CF \u68C0\u67E5\u5F53\u524D\u9879\u76EE\u8303\u56F4", compact: true }), _jsx(StaticRow, { title: "\u25CF \u68C0\u67E5\u6240\u6709\u9879\u76EE\u8303\u56F4", compact: true }), _jsx(StaticRow, { title: "\u25CB \u5408\u5E76\u53EF\u9009 Skill \u4E0E Agent \u4FE1\u606F", status: "muted", compact: true })] }) });
4244
+ help = 'Esc 取消检查并返回目标平台';
4281
4245
  }
4282
4246
  else if (route.name === 'portableDependencies' && plugin && snapshot.portableDraft) {
4283
4247
  const overview = dependencyOverview(plugin);
@@ -4286,14 +4250,20 @@ export function ConsoleAppV4(props) {
4286
4250
  title = '生成这些候选 Skills 需要的插件';
4287
4251
  description = '插件依赖用于生成,不能在这里取消。';
4288
4252
  actions = [
4289
- { label: '返回 Skill 选择', run: back },
4253
+ { role: 'navigation', label: '返回 Skill 选择', run: back },
4290
4254
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4291
4255
  ];
4292
4256
  const dependencies = overview?.dependencies ?? [];
4293
- const visibleDependencies = dependencies.slice(0, showTechnical ? 1 : responsiveBudget(2, 3));
4294
- const technicalDependencies = dependencies.slice(0, 1);
4257
+ readingText = [plugin.displayName, ...dependencies.map((entry) => `${entry.displayName} · ${entry.pluginId} · ${entry.relation === 'direct' ? '直接依赖' : '间接依赖'} · ${entry.requiredBy.map((parent) => parent.displayName).join('、')} 需要它`)].join('\n');
4258
+ const dependencyLimit = showTechnical ? 1 : responsiveBudget(2, 3);
4259
+ const dependencyOffset = Math.min(batchTargetOffset, Math.max(0, dependencies.length - dependencyLimit));
4260
+ const visibleDependencies = dependencies.slice(dependencyOffset, dependencyOffset + dependencyLimit);
4261
+ const technicalDependencies = dependencies.slice(dependencyOffset, dependencyOffset + 1);
4295
4262
  const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
4296
- content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56\u63D2\u4EF6", meta: `${dependencies.length} 个`, children: [visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '当前插件直接需要' : '由依赖插件继续需要'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > visibleDependencies.length ? _jsx(StaticRow, { title: `另有 ${dependencies.length - visibleDependencies.length} 个依赖插件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: `${entry.pluginId}${compactTechnicalSummary && dependencies.length > technicalDependencies.length ? ` · 另有 ${dependencies.length - technicalDependencies.length} 个依赖` : ''}`, compact: true }, entry.pluginId)) }) : null] });
4263
+ help = dependencies.length > dependencyLimit
4264
+ ? '↑↓ 移动 · PageUp/PageDown 或 ←→ 阅读依赖 · Enter 选择 · Esc 返回'
4265
+ : '↑↓ 移动 · Enter 选择 · Esc 返回';
4266
+ content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56\u63D2\u4EF6", meta: `${dependencies.length} 个`, children: [dependencyOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${dependencyOffset} 个依赖插件`, status: "muted", compact: true }) : null, visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '当前插件直接需要' : '由依赖插件继续需要'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > dependencyOffset + visibleDependencies.length ? _jsx(StaticRow, { title: `↓ 后面还有 ${dependencies.length - dependencyOffset - visibleDependencies.length} 个依赖插件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: entry.pluginId, compact: true }, entry.pluginId)) }) : null] });
4297
4267
  }
4298
4268
  else if (route.name === 'portableAgentMode' && plugin && snapshot.portableDraft) {
4299
4269
  breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
@@ -4357,13 +4327,27 @@ export function ConsoleAppV4(props) {
4357
4327
  setScrollOffset(0);
4358
4328
  } }] : []),
4359
4329
  { label: '重新检查', run: () => beginPortable(plugin.pluginId) },
4330
+ ...(route.operationRecordId ? [{
4331
+ label: '查看诊断与报告',
4332
+ description: `安全记录 ${route.operationRecordId}`,
4333
+ run: () => go({ name: 'operationDiagnostic', recordId: route.operationRecordId }),
4334
+ }] : []),
4360
4335
  { label: snapshot.installPlacementDraft?.pluginId === plugin.pluginId ? '返回目标平台' : '返回插件详情', run: () => props.controller.resetPortableFlowToPreview(plugin.pluginId) },
4361
4336
  ...(cliUnavailable
4362
4337
  ? []
4363
4338
  : [{ label: showTechnical ? '返回问题摘要' : '技术详情', run: () => toggleTechnical() }]),
4364
4339
  ];
4340
+ const technicalPageRows = responsiveBudget(2, 4);
4341
+ if (showTechnical && !showPortableRequirements) {
4342
+ readingText = diagnosticDetailText(diagnostic);
4343
+ referenceDetailPagination = {
4344
+ total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
4345
+ pageRows: technicalPageRows,
4346
+ };
4347
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4348
+ }
4365
4349
  const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4366
- content = _jsxs(Box, { flexDirection: "column", children: [showPortableRequirements ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5728 PATH \u4E2D\u63D0\u4F9B\u517C\u5BB9\u7684 skills \u53EF\u6267\u884C\u6587\u4EF6\u3002", compact: true }), _jsx(StaticRow, { title: `当前 AW adapter 支持:${SKILLS_CLI_SUPPORTED_VERSION_LABEL}`, compact: true }), _jsx(StaticRow, { title: "\u51C6\u5907\u5B8C\u6210\u540E\u8FD4\u56DE\u95EE\u9898\u6458\u8981\u5E76\u91CD\u65B0\u68C0\u67E5\u3002", compact: true }), _jsx(StaticRow, { title: "\u4E0D\u4F1A\u81EA\u52A8\u8FD0\u884C npx latest\u3001sudo \u6216\u5728\u7EBF\u5B89\u88C5\u811A\u672C\u3002", status: "warning", compact: true }), _jsx(StaticRow, { title: "\u9009\u5B9A\u63D2\u4EF6\u3001\u5B89\u88C5\u76EE\u6807\u548C Portable \u8349\u7A3F\u4FDD\u6301\u4E0D\u53D8\u3002", status: "success", compact: true }), _jsx(StaticRow, { title: "AW \u4E0D\u4F1A\u5728\u6B64\u9875\u5B89\u88C5\u5916\u90E8\u4F9D\u8D56\u3002", status: "warning", compact: true })] }) : _jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
4350
+ content = _jsxs(Box, { flexDirection: "column", children: [showPortableRequirements ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5728 PATH \u4E2D\u63D0\u4F9B\u517C\u5BB9\u7684 skills \u53EF\u6267\u884C\u6587\u4EF6\u3002", compact: true }), _jsx(StaticRow, { title: `当前 AW adapter 支持:${SKILLS_CLI_SUPPORTED_VERSION_LABEL}`, compact: true }), _jsx(StaticRow, { title: "\u51C6\u5907\u5B8C\u6210\u540E\u8FD4\u56DE\u95EE\u9898\u6458\u8981\u5E76\u91CD\u65B0\u68C0\u67E5\u3002", compact: true }), _jsx(StaticRow, { title: "\u4E0D\u4F1A\u81EA\u52A8\u8FD0\u884C npx latest\u3001sudo \u6216\u5728\u7EBF\u5B89\u88C5\u811A\u672C\u3002", status: "warning", compact: true }), _jsx(StaticRow, { title: "\u9009\u5B9A\u63D2\u4EF6\u3001\u5B89\u88C5\u76EE\u6807\u548C Portable \u8349\u7A3F\u4FDD\u6301\u4E0D\u53D8\u3002", status: "success", compact: true }), _jsx(StaticRow, { title: "AW \u4E0D\u4F1A\u5728\u6B64\u9875\u5B89\u88C5\u5916\u90E8\u4F9D\u8D56\u3002", status: "warning", compact: true })] }) : _jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, columns: size.columns, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? technicalPageRows : undefined, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
4367
4351
  }
4368
4352
  else if (route.name === 'pluginDependencies' && plugin) {
4369
4353
  const overview = dependencyOverview(plugin);
@@ -4382,6 +4366,8 @@ export function ConsoleAppV4(props) {
4382
4366
  }), entries.length > safeOffset + visible.length ? _jsx(StaticRow, { title: `↓ 后面还有 ${entries.length - safeOffset - visible.length} 项`, status: "muted", compact: true }) : null] });
4383
4367
  };
4384
4368
  if (route.view === 'removeBlocked') {
4369
+ if (!showTechnical)
4370
+ readingText = visibleDependents.map((entry) => `${entry.displayName} · ${dependencyRelationDescription(entry)} · ${dependentTargetLabel(entry)}`).join('\n');
4385
4371
  breadcrumb = `${plugin.displayName} / 移除`;
4386
4372
  context = '已阻止';
4387
4373
  title = `无法移除 ${plugin.displayName}`;
@@ -4392,24 +4378,16 @@ export function ConsoleAppV4(props) {
4392
4378
  setFocus(0);
4393
4379
  setScrollOffset(0);
4394
4380
  } },
4395
- { label: '返回插件详情', run: back },
4381
+ { role: 'navigation', label: '返回插件详情', run: back },
4396
4382
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4397
4383
  ];
4398
- const blockedRemovalPagination = !showTechnical && visibleDependents.length > dependentLimit
4399
- ? paginationActions({
4400
- offset: batchTargetOffset,
4401
- total: visibleDependents.length,
4402
- pageRows: dependentLimit,
4403
- move: (direction) => moveBatchTargetPage(visibleDependents.length, dependentLimit, direction),
4404
- })
4405
- : [];
4406
- actions = [...blockedRemovalPagination, ...blockedRemovalActions];
4407
- if (blockedRemovalPagination.length > 0) {
4384
+ actions = blockedRemovalActions;
4385
+ if (!showTechnical && visibleDependents.length > dependentLimit) {
4408
4386
  referenceDetailPagination = { total: visibleDependents.length, pageRows: dependentLimit };
4409
4387
  }
4410
4388
  const blockedRemovalViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
4411
4389
  if (!showTechnical && visibleDependents.length > dependentLimit) {
4412
- help = 'p/n 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4390
+ help = '←→ 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4413
4391
  }
4414
4392
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `${dependentCount} 个已安装插件仍在使用`, status: "error", compact: true }), _jsx(StaticRow, { title: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002", status: "muted", compact: true }), showTechnical ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", compact: true }), dependencyRows(visibleDependents, dependentLimit, true, batchTargetOffset)] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockedRemovalViewport.offset, limit: blockedRemovalViewport.limit }), showTechnical ? _jsx(IssueTechnicalDetails, { details: [{
4415
4393
  code: 'dependencyStillRequired',
@@ -4556,38 +4534,7 @@ export function ConsoleAppV4(props) {
4556
4534
  meta: '依赖当前可用',
4557
4535
  };
4558
4536
  });
4559
- if (route.view === 'details') {
4560
- const detailSections = [
4561
- ['用途', node?.description ?? '当前目录没有用途说明'],
4562
- ['为何需要', description],
4563
- ['影响', state?.value ?? (node ? statusText(node) : '尚未检查当前状态')],
4564
- ];
4565
- const detailLines = detailSections.flatMap(([label, value]) => [
4566
- label,
4567
- ...wrapConsoleText(value, Math.max(1, size.columns - 6)).map((line) => ` ${line}`),
4568
- ]);
4569
- const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
4570
- const detailOffset = Math.min(batchTargetOffset, Math.max(0, detailLines.length - 1));
4571
- const visibleDetailLines = detailLines.slice(detailOffset, detailOffset + pageRows);
4572
- referenceDetailPagination = { total: detailLines.length, pageRows };
4573
- breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId} / 完整说明`;
4574
- context = `${Math.min(detailOffset + 1, detailLines.length)}–${Math.min(detailOffset + visibleDetailLines.length, detailLines.length)} / ${detailLines.length}`;
4575
- title = `${node?.displayName ?? route.nodePluginId} 的完整说明`;
4576
- description = '用途、关系原因和当前影响。';
4577
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回节点详情';
4578
- actions = [
4579
- ...paginationActions({
4580
- offset: detailOffset,
4581
- total: detailLines.length,
4582
- pageRows,
4583
- move: (direction) => moveBatchTargetPage(detailLines.length, pageRows, direction),
4584
- }),
4585
- { label: '返回节点详情', run: back },
4586
- ];
4587
- content = _jsxs(Box, { flexDirection: "column", children: [detailOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${detailOffset} 行`, status: "muted", compact: true }) : null, visibleDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${detailOffset + index}:${line}`)), detailOffset + visibleDetailLines.length < detailLines.length
4588
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${detailLines.length - detailOffset - visibleDetailLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
4589
- }
4590
- else if (route.view === 'references') {
4537
+ if (route.view === 'references') {
4591
4538
  breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId} / 使用位置`;
4592
4539
  context = showTechnical ? '技术详情' : '使用位置';
4593
4540
  title = `当前可用依赖位置 ${references.length} 个`;
@@ -4611,6 +4558,15 @@ export function ConsoleAppV4(props) {
4611
4558
  `dependency · platform=${platformLabel(selectedReference.dependency.platform)} · scope=${selectedReference.dependency.scope}`,
4612
4559
  `dependency · project=${selectedReference.dependency.project ?? 'none'}`,
4613
4560
  ] : [];
4561
+ if (showTechnical && selectedReference)
4562
+ readingText = [
4563
+ `consumer · ${selectedReference.consumer.pluginId}`,
4564
+ `consumer · platform=${platformLabel(selectedReference.consumer.platform)} · scope=${selectedReference.consumer.scope}`,
4565
+ `consumer · project=${selectedReference.consumer.project ?? 'none'}`,
4566
+ `dependency · ${selectedReference.dependency.pluginId}`,
4567
+ `dependency · platform=${platformLabel(selectedReference.dependency.platform)} · scope=${selectedReference.dependency.scope}`,
4568
+ `dependency · project=${selectedReference.dependency.project ?? 'none'}`,
4569
+ ].join('\n');
4614
4570
  const referenceDetailPageRows = referenceDetailLines.length <= 6
4615
4571
  ? 6
4616
4572
  : responsiveBudget(4, 8);
@@ -4620,24 +4576,14 @@ export function ConsoleAppV4(props) {
4620
4576
  total: referenceDetailLines.length,
4621
4577
  pageRows: referenceDetailPageRows,
4622
4578
  } : undefined;
4623
- const referencePaginationActions = showTechnical
4624
- && referenceDetailLines.length > referenceDetailPageRows
4625
- ? paginationActions({
4626
- offset: referenceDetailOffset,
4627
- total: referenceDetailLines.length,
4628
- pageRows: referenceDetailPageRows,
4629
- move: (direction) => moveBatchTargetPage(referenceDetailLines.length, referenceDetailPageRows, direction),
4630
- })
4631
- : [];
4632
4579
  actions = [
4633
4580
  ...(showTechnical && referenceActions[selectedReferenceIndex]
4634
4581
  ? [referenceActions[selectedReferenceIndex]] : referenceActions),
4635
- ...referencePaginationActions,
4636
- { label: '返回节点详情', run: back },
4582
+ { role: 'navigation', label: '返回节点详情', run: back },
4637
4583
  ];
4638
4584
  const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4639
4585
  help = showTechnical
4640
- ? 'p/n 查看当前位置详情 · ↑↓ 移动 · Enter 选择 · d 隐藏技术详情 · Esc 返回节点详情'
4586
+ ? '←→ 查看当前位置详情 · ↑↓ 移动 · Enter 选择 · d 隐藏技术详情 · Esc 返回节点详情'
4641
4587
  : '↑↓ 查看全部位置 · d 技术详情 · Enter 返回节点详情 · Esc 返回节点详情';
4642
4588
  content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? null
4643
4589
  : _jsx(StaticRow, { title: "\u672C\u6B21\u68C0\u67E5", value: "\u53EA\u68C0\u67E5\u5F53\u524D\u9879\u76EE\u548C\u4E2A\u4EBA\u4F4D\u7F6E\uFF1B\u5176\u4ED6\u9879\u76EE\u672A\u68C0\u67E5", status: "muted", compact: true }), showTechnical && referenceDetailOffset > 0
@@ -4660,20 +4606,33 @@ export function ConsoleAppV4(props) {
4660
4606
  direction: route.direction,
4661
4607
  }),
4662
4608
  }] : []),
4663
- {
4664
- label: '阅读完整说明',
4665
- description: '分页查看用途、为何需要和影响',
4666
- run: () => {
4667
- setBatchTargetOffset(0);
4668
- go({ ...route, view: 'details' });
4669
- },
4670
- },
4671
- { label: '返回关系图', run: back },
4609
+ { role: 'navigation', label: '返回关系图', run: back },
4610
+ ];
4611
+ const reason = description;
4612
+ description = undefined;
4613
+ const summary = [
4614
+ `用途:${node?.description ?? '当前目录没有用途说明'}`,
4615
+ `为何需要:${reason}`,
4616
+ `影响:${state?.value ?? (node ? statusText(node) : '尚未检查当前状态')}`,
4617
+ ...(showTechnical ? [`pluginId · ${route.nodePluginId}`, `relationship · ${relationship ?? 'unknown'}`] : []),
4672
4618
  ];
4673
- const nodeActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4674
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u8282\u70B9\u72B6\u6001", children: [_jsx(StaticRow, { title: "\u7528\u9014", value: node?.description ?? '当前目录没有用途说明', compact: true }), _jsx(StaticRow, { title: "\u4E3A\u4F55\u9700\u8981", value: description, compact: true }), _jsx(StaticRow, { title: "\u5F71\u54CD", value: state?.value ?? (node ? statusText(node) : '尚未检查当前状态'), status: state?.status ?? 'muted', compact: true }), references.length > 0
4675
- ? _jsx(StaticRow, { title: `当前可用依赖位置 ${references.length} 个`, description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u4F4D\u7F6E\u201D\u53EF\u9010\u9879\u67E5\u770B", compact: true })
4676
- : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: nodeActionViewport.offset, limit: nodeActionViewport.limit }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "pluginId", meta: route.nodePluginId, compact: true }), _jsx(StaticRow, { title: "relationship", meta: relationship ?? 'unknown', compact: true })] }) : null] });
4619
+ readingText = [title, ...summary].join('\n');
4620
+ const summaryLines = summary.flatMap((line) => wrapConsoleText(line, size.columns - 2));
4621
+ const actionCount = actions.filter((action) => action.role !== 'navigation').length;
4622
+ const availableSummaryRows = pageActionBudget(Math.min(2, actionCount));
4623
+ const pageRows = Math.max(1, availableSummaryRows - Number(summaryLines.length > availableSummaryRows));
4624
+ const offset = Math.min(batchTargetOffset, Math.max(0, summaryLines.length - pageRows));
4625
+ const visible = summaryLines.slice(offset, offset + pageRows);
4626
+ const paginated = summaryLines.length > pageRows;
4627
+ if (paginated) {
4628
+ referenceDetailPagination = { total: summaryLines.length, pageRows };
4629
+ context = `${offset + 1}–${offset + visible.length} / ${summaryLines.length}`;
4630
+ help = '←→ 翻页 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4631
+ }
4632
+ currentActionViewportLineBudget = pageActionBudget(visible.length + Number(paginated));
4633
+ const nodeActionViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
4634
+ content = _jsxs(Box, { flexDirection: "column", children: [visible.map((line, index) => _jsx(Text, { wrap: "truncate-end", children: line }, `${offset + index}`)), paginated ? _jsx(Text, { color: tuiTheme.colors.muted, children: offset + visible.length < summaryLines.length
4635
+ ? `↓ 后面还有 ${summaryLines.length - offset - visible.length} 行` : '已到正文末尾' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: nodeActionViewport.offset, limit: nodeActionViewport.limit })] });
4677
4636
  }
4678
4637
  }
4679
4638
  else if (route.name === 'installTargets' && plugin) {
@@ -4705,7 +4664,7 @@ export function ConsoleAppV4(props) {
4705
4664
  description = '根插件或必需依赖不支持当前目标平台组合,因此不会进入确认页。';
4706
4665
  actions = [
4707
4666
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
4708
- { label: '返回插件详情', run: back },
4667
+ { role: 'navigation', label: '返回插件详情', run: back },
4709
4668
  ];
4710
4669
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u6240\u9009\u76EE\u6807\u5E73\u53F0\u4E0D\u53EF\u7528", children: choices.map((choice) => _jsx(StaticRow, { title: `${platformLabel(choice.platform)} · ${choice.disabled ? `不可选 · ${choice.disabled}` : '不可选'}`, status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4711
4670
  }
@@ -4717,6 +4676,7 @@ export function ConsoleAppV4(props) {
4717
4676
  }
4718
4677
  setDirty(false);
4719
4678
  props.controller.setInstallPlatforms(validSelectedTargets);
4679
+ setInstallPlatformIndex(0);
4720
4680
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
4721
4681
  };
4722
4682
  const nativeActions = choices.map((choice) => {
@@ -4781,26 +4741,16 @@ export function ConsoleAppV4(props) {
4781
4741
  };
4782
4742
  const confirmAction = {
4783
4743
  actionId: 'confirm-install-platforms',
4784
- label: '确定所选平台',
4744
+ label: '确定所选平台 [c]',
4785
4745
  disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
4786
4746
  run: confirmNativePlatforms,
4787
4747
  };
4788
- const backAction = {
4789
- actionId: 'install-platforms-back', label: '返回插件详情', run: back,
4790
- };
4791
- actions = [...nativeActions, portableAction];
4792
- pinnedActions = [confirmAction, backAction];
4793
- const cardWidth = Math.max(20, size.columns - 2);
4794
- const installTargetStaticRows = (validSelectedTargets.length === 0 ? 2 : 1)
4795
- + (policy.mode === 'compact' ? 4 : 0);
4796
- const platformViewport = objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', focus, scrollOffset, Math.max(1, pageActionBudget(installTargetStaticRows)));
4797
- help = '↑↓ 移动 · Enter/Space 勾选平台 · Enter 打开通用 Skills · Tab 操作栏 · c 继续 · Esc 返回';
4798
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4799
- id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4800
- })), active: focusZone === 'actions', focus: pinnedActionFocus }), validSelectedTargets.length === 0
4801
- ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u76EE\u6807\u5E73\u53F0" }) : null, _jsx(ContentViewport, { children: actions.slice(platformViewport.offset, platformViewport.offset + platformViewport.limit)
4802
- .map((action, visibleIndex) => _jsx(ObjectCardRow, { width: cardWidth, title: action.actionId?.startsWith('install-platform-')
4803
- ? `${action.selected ? '[x]' : '[ ]'} ${action.label}` : action.label, description: action.disabled ?? action.description ?? '', tags: action.tags, focused: focusZone === 'content' && platformViewport.offset + visibleIndex === focus, anchored: focusZone !== 'content' && platformViewport.offset + visibleIndex === focus, compact: policy.mode === 'compact' }, action.actionId)) })] });
4748
+ actions = [...nativeActions, portableAction, confirmAction];
4749
+ bottomActions = [confirmAction];
4750
+ help = '↑↓ 移动 · Enter/Space 选择 · Esc 返回';
4751
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(0, nativeActions.length).map((action) => ({
4752
+ ...action, label: `${action.selected ? '[x]' : '[ ]'} ${action.label}`,
4753
+ })), focus: focus, kind: "action", compact: false }), _jsx(SemanticRows, { actions: actions, focus: focus, offset: nativeActions.length, kind: "action", compact: false })] });
4804
4754
  }
4805
4755
  }
4806
4756
  else if (route.name === 'installPlacement' && plugin) {
@@ -4838,35 +4788,53 @@ export function ConsoleAppV4(props) {
4838
4788
  }
4839
4789
  else {
4840
4790
  description = '所选平台没有共同范围。请分别确定每个平台的合法位置。';
4841
- const pairActions = selectedPlatforms.flatMap((platform) => legalScopes(platform).map((candidateScope) => ({
4842
- label: `${platformLabel(platform)} · ${scopeLabel(candidateScope)}`,
4791
+ const platformIndex = Math.min(installPlatformIndex, Math.max(0, selectedPlatforms.length - 1));
4792
+ const platform = selectedPlatforms[platformIndex];
4793
+ title = platform ? `${platformLabel(platform)} 的使用范围` : '请返回选择目标平台';
4794
+ context = `平台 ${platformIndex + 1} / ${selectedPlatforms.length}`;
4795
+ if (platformIndex > 0)
4796
+ returnToPreviousInstallPlatform = () => {
4797
+ setInstallPlatformIndex(platformIndex - 1);
4798
+ setFocus(0);
4799
+ setScrollOffset(0);
4800
+ };
4801
+ actions = platform ? legalScopes(platform).map((candidateScope) => ({
4802
+ label: scopeLabel(candidateScope),
4843
4803
  selected: installPlatformScopes[platform] === candidateScope,
4844
- run: () => setInstallPlatformScopes((current) => ({ ...current, [platform]: candidateScope })),
4845
- })));
4846
- const exactTargets = selectedPlatforms.flatMap((platform) => installPlatformScopes[platform]
4847
- ? [{ pluginId: plugin.pluginId, platform, scope: installPlatformScopes[platform] }] : []);
4848
- actions = [
4849
- ...pairActions,
4850
- {
4851
- label: '继续到操作汇总',
4852
- description: '只安装上方明确选择的实际目标',
4853
- disabled: exactTargets.length !== selectedPlatforms.length ? '请为每个平台选择一个位置' : undefined,
4854
- run: () => beginReview({
4855
- kind: 'pluginWriteBatch',
4856
- action: 'install',
4857
- selection: { kind: 'targets', targets: exactTargets },
4858
- platform: new Set(exactTargets.map((target) => target.platform)).size > 1
4859
- ? 'both' : exactTargets[0].platform,
4860
- scope: exactTargets[0].scope,
4861
- }),
4804
+ description: platformIndex + 1 < selectedPlatforms.length
4805
+ ? `下一步:${platformLabel(selectedPlatforms[platformIndex + 1])} 的使用范围`
4806
+ : '下一步:查看这些精确目标的操作汇总',
4807
+ run: async () => {
4808
+ const nextScopes = { ...installPlatformScopes, [platform]: candidateScope };
4809
+ setInstallPlatformScopes(nextScopes);
4810
+ if (platformIndex + 1 < selectedPlatforms.length) {
4811
+ setInstallPlatformIndex(platformIndex + 1);
4812
+ setFocus(0);
4813
+ setScrollOffset(0);
4814
+ return;
4815
+ }
4816
+ const exactTargets = selectedPlatforms.flatMap((selectedPlatform) => {
4817
+ const selectedScope = nextScopes[selectedPlatform];
4818
+ return selectedScope && legalScopes(selectedPlatform).includes(selectedScope)
4819
+ ? [{ pluginId: plugin.pluginId, platform: selectedPlatform, scope: selectedScope }] : [];
4820
+ });
4821
+ if (exactTargets.length !== selectedPlatforms.length) {
4822
+ setInstallPlatformIndex(0);
4823
+ setFocus(0);
4824
+ setScrollOffset(0);
4825
+ setActionError('合法位置已变化。请重新确定每个平台的范围。');
4826
+ return;
4827
+ }
4828
+ props.controller.setInstallRootTargets(exactTargets);
4829
+ if (hasDependencies && await props.controller.prepareInstallDependencyPlacement()) {
4830
+ go({ name: 'dependencyPlacement', pluginId: plugin.pluginId });
4831
+ return;
4832
+ }
4833
+ beginReview(props.controller.installPlacementIntent());
4862
4834
  },
4863
- { label: '返回目标平台', run: back },
4864
- ];
4835
+ })) : [];
4865
4836
  }
4866
- const scopeChoiceCount = commonScopes.length > 0 ? actions.length : Math.max(0, actions.length - 2);
4867
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: commonScopes.length > 0 ? 'choice' : 'toggle', compact: policy.mode === 'compact', limit: scopeChoiceCount }), commonScopes.length === 0
4868
- ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: scopeChoiceCount })
4869
- : null, hasDependencies ? _jsx(Callout, { title: "\u4F9D\u8D56\u8BA1\u5212", description: "\u6240\u9700\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u6392\uFF1B\u786E\u8BA4\u524D\u53EF\u67E5\u770B\u5F71\u54CD\u3002" }) : null] });
4837
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsx(Callout, { title: "\u4F9D\u8D56\u8BA1\u5212", description: "\u6240\u9700\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u6392\uFF1B\u786E\u8BA4\u524D\u53EF\u67E5\u770B\u5F71\u54CD\u3002" }) : null] });
4870
4838
  help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
4871
4839
  }
4872
4840
  else if (route.name === 'uninstallTargets' && plugin) {
@@ -4920,7 +4888,7 @@ export function ConsoleAppV4(props) {
4920
4888
  });
4921
4889
  },
4922
4890
  },
4923
- { label: '返回插件详情', run: back },
4891
+ { role: 'navigation', label: '返回插件详情', run: back },
4924
4892
  ];
4925
4893
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length + 1 }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length + 1 })] });
4926
4894
  help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
@@ -4928,6 +4896,9 @@ export function ConsoleAppV4(props) {
4928
4896
  else if (route.name === 'dependencyPlacement' && plugin) {
4929
4897
  const draft = snapshot.installPlacementDraft?.pluginId === plugin.pluginId
4930
4898
  ? snapshot.installPlacementDraft : undefined;
4899
+ const rootScopeSummary = draft?.exactRootTargets ? '按平台设置'
4900
+ : draft ? scopeLabel(draft.rootScope) : '未就绪';
4901
+ const exactRootRows = draft?.exactRootTargets?.map((target) => _jsx(StaticRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, compact: true }, target.platform));
4931
4902
  breadcrumb = `${plugin.displayName} / 添加 / 依赖位置`;
4932
4903
  context = '依赖位置';
4933
4904
  title = '选择每个平台的依赖位置';
@@ -4958,7 +4929,7 @@ export function ConsoleAppV4(props) {
4958
4929
  description: '恢复依赖自动安排的建议位置',
4959
4930
  run: () => props.controller.resetDependencyScopes(),
4960
4931
  },
4961
- { label: '返回目标平台', run: back },
4932
+ { role: 'navigation', label: '返回目标平台', run: back },
4962
4933
  ];
4963
4934
  const placementViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(dependencyPlacementStaticRows));
4964
4935
  const userCount = draft?.targets.filter((target) => target.scope === 'user').length ?? 0;
@@ -4971,8 +4942,8 @@ export function ConsoleAppV4(props) {
4971
4942
  ...(promotedCount > 0 ? [`因依赖自动调整 ${promotedCount} 项`] : []),
4972
4943
  ].join(';');
4973
4944
  content = _jsxs(Box, { flexDirection: "column", children: [policy.mode === 'compact'
4974
- ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: draft ? scopeLabel(draft.rootScope) : '未就绪', compact: true }), _jsx(StaticRow, { title: `位置原因 · ${reasonSummary || '由平台或上游约束固定'}`, status: userCount > 0 ? 'warning' : 'info', compact: true }), _jsx(StaticRow, { title: "\u8303\u56F4\u63D0\u793A \u00B7 \u6240\u6709\u9879\u76EE\u4F4D\u7F6E\u4F1A\u5F71\u54CD\u5176\u4ED6\u9879\u76EE\u548C\u7528\u6237\u73AF\u5883", status: userCount > 0 ? 'warning' : 'muted', compact: true })] })
4975
- : _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u6839\u63D2\u4EF6\u4F4D\u7F6E", children: _jsx(StaticRow, { title: plugin.displayName, meta: draft ? scopeLabel(draft.rootScope) : '未就绪', compact: true }) }), _jsx(Callout, { status: userCount > 0 ? 'warning' : 'info', title: userCount > 0 ? `${userCount} 个依赖平台目标使用所有项目位置` : '位置计算原因', description: `${reasonSummary || '依赖位置由平台或上游约束固定'}${userCount > 0 ? ';所有项目位置对其他项目也可见,并会改变你的用户环境' : ''}。` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: placementViewport.offset, limit: placementViewport.limit })] });
4945
+ ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: rootScopeSummary, compact: true }), exactRootRows, _jsx(StaticRow, { title: `位置原因 · ${reasonSummary || '由平台或上游约束固定'}`, status: userCount > 0 ? 'warning' : 'info', compact: true }), _jsx(StaticRow, { title: "\u8303\u56F4\u63D0\u793A \u00B7 \u6240\u6709\u9879\u76EE\u4F4D\u7F6E\u4F1A\u5F71\u54CD\u5176\u4ED6\u9879\u76EE\u548C\u7528\u6237\u73AF\u5883", status: userCount > 0 ? 'warning' : 'muted', compact: true })] })
4946
+ : _jsxs(_Fragment, { children: [_jsxs(SectionCard, { title: "\u6839\u63D2\u4EF6\u4F4D\u7F6E", children: [_jsx(StaticRow, { title: plugin.displayName, meta: rootScopeSummary, compact: true }), exactRootRows] }), _jsx(Callout, { status: userCount > 0 ? 'warning' : 'info', title: userCount > 0 ? `${userCount} 个依赖平台目标使用所有项目位置` : '位置计算原因', description: `${reasonSummary || '依赖位置由平台或上游约束固定'}${userCount > 0 ? ';所有项目位置对其他项目也可见,并会改变你的用户环境' : ''}。` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: placementViewport.offset, limit: placementViewport.limit })] });
4976
4947
  help = '↑↓ 移动 · Enter 打开/继续 · Esc 返回';
4977
4948
  }
4978
4949
  else if (route.name === 'dependencyPlacementEditor' && plugin) {
@@ -5207,9 +5178,21 @@ export function ConsoleAppV4(props) {
5207
5178
  setScrollOffset(0);
5208
5179
  },
5209
5180
  }] : []),
5210
- { label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
5181
+ { role: 'navigation', label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
5211
5182
  ];
5212
5183
  const taskTargetLimit = policy.mode === 'compact' ? 3 : 6;
5184
+ readingText = candidates.map((candidate) => {
5185
+ const target = candidate.target;
5186
+ const version = target.expectedUpdate
5187
+ ? `${target.expectedUpdate.installedVersion} → ${target.expectedUpdate.activeVersion}`
5188
+ : '保持当前版本';
5189
+ return `${candidate.displayName} · ${candidate.sourceId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)} · ${candidate.eligible
5190
+ ? pluginWriteTaskMethodLabel(candidate.recommendedMethod)
5191
+ : pluginWriteTaskBlockLabel(candidate.blockReason)} · ${version}`;
5192
+ }).join('\n');
5193
+ if (candidates.length > taskTargetLimit) {
5194
+ referenceDetailPagination = { total: candidates.length, pageRows: taskTargetLimit };
5195
+ }
5213
5196
  const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
5214
5197
  const visibleTaskCandidates = candidates.slice(taskTargetOffset, taskTargetOffset + taskTargetLimit);
5215
5198
  const targetRows = visibleTaskCandidates.map((candidate) => {
@@ -5243,7 +5226,7 @@ export function ConsoleAppV4(props) {
5243
5226
  ? plugin.version && plugin.version !== installedVersions[0]
5244
5227
  ? `${installedVersions[0]} → ${plugin.version}`
5245
5228
  : `已安装 ${installedVersions[0]}`
5246
- : plugin.version ? `可安装版本 ${plugin.version}` : '版本待确认';
5229
+ : plugin.version ?? '待确认';
5247
5230
  context = `${plugin.sourceLabel} · ${versionSummary}`;
5248
5231
  const updateTaskCandidates = plugin.configOnly ? []
5249
5232
  : props.controller.listPluginWriteTaskCandidates('update')
@@ -5357,18 +5340,20 @@ export function ConsoleAppV4(props) {
5357
5340
  actionId: 'content',
5358
5341
  label: '内容详情',
5359
5342
  description: contentOverview
5360
- ? `${contentOverview.items.length} 项目录内容 · 版本 ${contentOverview.pluginVersion}`
5343
+ ? `${contentOverview.items.length} 项当前目录内容`
5361
5344
  : '打开时读取当前目录版本的内容',
5362
5345
  run: () => go({ name: 'pluginContent', pluginId: plugin.pluginId }),
5363
5346
  } : undefined;
5364
5347
  const dependencyOverviewForDetail = dependencyOverview(plugin);
5365
- const dependencyAction = dependencyOverviewForDetail
5366
- && (dependencyOverviewForDetail.dependencies.length > 0
5367
- || dependencyOverviewForDetail.dependents.length > 0
5368
- || dependencyOverviewForDetail.blockers.length > 0) ? {
5348
+ const dependencyAction = !plugin.configOnly ? {
5369
5349
  actionId: 'dependencies',
5370
5350
  label: '依赖关系',
5371
- description: '在同一关系图中查看使用者和所需插件',
5351
+ description: dependencyOverviewForDetail
5352
+ && (dependencyOverviewForDetail.dependencies.length > 0
5353
+ || dependencyOverviewForDetail.dependents.length > 0
5354
+ || dependencyOverviewForDetail.blockers.length > 0)
5355
+ ? '在同一关系图中查看使用者和所需插件'
5356
+ : '当前没有已声明的所需插件或使用者',
5372
5357
  run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5373
5358
  } : undefined;
5374
5359
  const uninstallAction = installed ? {
@@ -5409,126 +5394,70 @@ export function ConsoleAppV4(props) {
5409
5394
  && dependencyOverviewForDetail.summary.totalCount > 0
5410
5395
  ? `需要 ${dependencyOverviewForDetail.summary.totalCount} 个插件;当前可用关系按当前范围检查,未显示的关系尚未检查通过。`
5411
5396
  : undefined;
5412
- const detailPositions = plugin.installedPlacements
5413
- .filter((placement) => placement.state !== 'notInstalled')
5414
- .map((placement) => `${platformLabel(placement.platform)}:${pluginPlacementConclusions(placement).map((entry) => entry.text).join(' · ')}`);
5415
- const detailSummary = [`${plugin.description}`, `版本 ${plugin.version ?? '未知'} · ${plugin.sourceLabel}`,
5416
- ...detailPositions].join('\n');
5417
- const detailSummaryLines = objectCardRowLayout({
5418
- description: detailSummary, width: size.columns - 2, hasTags: true,
5419
- }).descriptionLines;
5420
- const detailExtraRows = (updateDescription ? 1 : 0) + (dependencyDetailText ? 1 : 0)
5421
- + (snapshot.completionNotice?.pluginId === plugin.pluginId ? 4 : 0);
5422
- const detailPageRows = Math.max(1, pageActionBudget(detailExtraRows + 4 + 3));
5423
- const detailOffset = Math.min(batchTargetOffset, Math.max(0, detailSummaryLines.length - 1));
5424
- const visibleDetailLines = detailSummaryLines.slice(detailOffset, detailOffset + detailPageRows);
5425
- if (detailSummaryLines.length > detailPageRows) {
5426
- context += ` · 摘要 ${detailOffset + 1}–${detailOffset + visibleDetailLines.length}/${detailSummaryLines.length}`;
5427
- actions.push(...paginationActions({
5428
- offset: detailOffset, total: detailSummaryLines.length, pageRows: detailPageRows,
5429
- move: (direction) => moveBatchTargetPage(detailSummaryLines.length, detailPageRows, direction),
5430
- }));
5431
- referenceDetailPagination = { total: detailSummaryLines.length, pageRows: detailPageRows };
5432
- }
5433
- currentActionViewportLineBudget = pageActionBudget(detailExtraRows + 4 + visibleDetailLines.length);
5397
+ const detailPlatforms = [...new Set([
5398
+ ...plugin.installedPlacements.map((placement) => placement.platform), ...plugin.supportedPlatforms,
5399
+ ])];
5400
+ const detailPositions = detailPlatforms.flatMap((platform) => {
5401
+ const placements = plugin.installedPlacements.filter((placement) => placement.platform === platform && placement.state !== 'notInstalled');
5402
+ return placements.length > 0
5403
+ ? placements.map((placement) => `${platformLabel(platform)}:${pluginPlacementConclusions(placement).map((entry) => entry.text).join(' · ')}`)
5404
+ : [`${platformLabel(platform)} · ${pluginPlatformUnknown(plugin, platform) ? '未知 · 尚未确认' : '尚未安装'}`];
5405
+ });
5406
+ const platformSummary = detailPositions.length > 0 ? detailPositions.join(';') : '当前没有可用平台';
5407
+ context = '插件详情';
5408
+ const summary = [
5409
+ plugin.description,
5410
+ `版本 · ${versionSummary}`,
5411
+ `来源 · ${plugin.sourceLabel}`,
5412
+ ...(detailPositions.length > 0 ? detailPositions : [platformSummary]),
5413
+ ...(plugin.portableSkills ? ['[Skills 可选]'] : []),
5414
+ ...(plugin.configOnly ? ['[已保存设置]'] : []),
5415
+ ...(updateDescription ? [`有可用更新 · ${updateDescription}`] : []),
5416
+ ...(dependencyDetailText ? [dependencyDetailText] : []),
5417
+ ];
5418
+ readingText = [plugin.displayName, ...summary].join('\n');
5419
+ const summaryLines = summary.flatMap((line) => wrapConsoleText(line, size.columns - 2));
5420
+ const completion = snapshot.completionNotice?.pluginId === plugin.pluginId
5421
+ ? snapshot.completionNotice : undefined;
5422
+ const completionLines = completion
5423
+ ? wrapConsoleText(`${completion.title} · ${completion.description}`, size.columns - 2) : [];
5424
+ const availableSummaryRows = pageActionBudget(Math.min(3, actions.length) + completionLines.length);
5425
+ const summaryPageRows = Math.max(1, availableSummaryRows - Number(summaryLines.length > availableSummaryRows));
5426
+ const summaryOffset = Math.min(batchTargetOffset, Math.max(0, summaryLines.length - summaryPageRows));
5427
+ const visibleSummary = summaryLines.slice(summaryOffset, summaryOffset + summaryPageRows);
5428
+ const summaryPaginated = summaryLines.length > summaryPageRows;
5429
+ if (summaryPaginated) {
5430
+ referenceDetailPagination = { total: summaryLines.length, pageRows: summaryPageRows };
5431
+ context = `${summaryOffset + 1}–${summaryOffset + visibleSummary.length} / ${summaryLines.length}`;
5432
+ help = '←→ 翻页 · ↑↓ 移动 · Enter 选择 · Esc 返回';
5433
+ }
5434
+ currentActionViewportLineBudget = pageActionBudget(visibleSummary.length
5435
+ + Number(summaryPaginated) + completionLines.length);
5434
5436
  const detailViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
5435
- content = _jsxs(Box, { flexDirection: "column", children: [snapshot.completionNotice?.pluginId === plugin.pluginId
5436
- ? _jsx(Callout, { status: "success", title: snapshot.completionNotice.title, description: snapshot.completionNotice.description })
5437
- : null, _jsx(ObjectCardRow, { width: size.columns - 2, title: plugin.displayName, description: visibleDetailLines.join('\n'), tags: pluginPlatformBadges(plugin) }), updateDescription
5438
- ? _jsx(StaticRow, { title: "\u6709\u53EF\u7528\u66F4\u65B0", meta: updateDescription, status: "warning", compact: true })
5439
- : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit })] });
5437
+ content = _jsxs(Box, { flexDirection: "column", children: [completionLines.map((line, index) => _jsx(Text, { color: tuiTheme.statusColors.success, children: line }, `completion:${index}`)), visibleSummary.map((line, index) => _jsx(Text, { wrap: "truncate-end", children: line }, `${summaryOffset + index}`)), summaryPaginated ? _jsx(Text, { color: tuiTheme.colors.muted, children: summaryOffset + visibleSummary.length < summaryLines.length
5438
+ ? `↓ 后面还有 ${summaryLines.length - summaryOffset - visibleSummary.length} 行`
5439
+ : '已到正文末尾' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit })] });
5440
5440
  }
5441
5441
  else if (route.name === 'pluginMore' && plugin) {
5442
5442
  breadcrumb = `插件 / ${plugin.displayName} / 更多操作`;
5443
5443
  context = plugin.sourceLabel;
5444
5444
  title = '更多操作';
5445
5445
  description = '选择平台可使用同步、重新安装、移除和受限清理。';
5446
- const dependencySummary = plugin.dependencySummary;
5447
- const detailMenu = buildPluginDetailActions(plugin);
5448
- const updateTaskCandidates = props.controller.listPluginWriteTaskCandidates('update')
5449
- .filter((candidate) => candidate.target.pluginId === plugin.pluginId);
5450
- const restoreTaskCandidates = props.controller.listPluginWriteTaskCandidates('restore')
5451
- .filter((candidate) => candidate.target.pluginId === plugin.pluginId);
5452
5446
  actions = [
5453
- ...(updateTaskCandidates.length > 0 ? [{
5454
- label: '更新插件',
5455
- description: '使用精确版本和现有安装位置',
5456
- status: 'warning',
5457
- run: () => go({
5458
- name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
5459
- targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
5460
- versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
5461
- }),
5462
- }] : []),
5463
- ...(restoreTaskCandidates.length > 0 ? [{
5464
- label: '恢复正常',
5465
- description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
5466
- status: 'warning',
5467
- run: () => go({
5468
- name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
5469
- targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
5470
- }),
5471
- }] : []),
5472
- ...detailMenu.filter((action) => action.id !== 'refreshAndSync' && action.id !== 'about')
5473
- .map((action) => {
5474
- if (action.id === 'platform' || action.id === 'scopeConflict') {
5475
- const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
5476
- const projected = placementStatus(placement);
5477
- return {
5478
- label: `${platformLabel(action.platform)} 平台与维护`,
5479
- description: `${projected.description};同步、重装、移除和清理入口`,
5480
- meta: projected.value,
5481
- status: projected.status,
5482
- run: () => action.id === 'scopeConflict'
5483
- ? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
5484
- : go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
5485
- };
5486
- }
5487
- if (action.id === 'resolveScopeConflict')
5488
- return {
5489
- label: `处理 ${platformLabel(action.platform)} 位置冲突`,
5490
- description: action.description,
5491
- status: 'error',
5492
- run: () => beginScopeConflictRecovery(action),
5493
- };
5494
- if (action.id === 'dependencies')
5495
- return {
5496
- label: '依赖与当前可用关系',
5497
- description: dependencySummary?.issueCount ? `${dependencySummary.issueCount} 项需要处理` : '查看完整关系',
5498
- meta: dependencySummary ? dependencySummaryText(dependencySummary) : undefined,
5499
- run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5500
- };
5501
- if (action.id === 'settings')
5502
- return {
5503
- label: plugin.configOnly ? '查看已保存设置' : '插件设置',
5504
- run: () => plugin.configOnly
5505
- ? props.controller.beginSavedConfigRecovery(plugin.pluginId)
5506
- : beginConfigFlow(plugin.pluginId),
5507
- };
5508
- if (action.id === 'sourceProblem')
5509
- return {
5510
- label: '恢复插件来源', status: 'warning',
5511
- run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
5512
- };
5513
- if (action.id === 'upgradeAwCli' || action.id === 'capabilityTechnical')
5514
- return {
5515
- label: action.id === 'upgradeAwCli' ? '升级 AW CLI' : '查看功能支持详情',
5516
- status: action.id === 'upgradeAwCli' ? 'warning' : undefined,
5517
- run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
5518
- };
5447
+ ...plugin.installedPlacements
5448
+ .filter((placement) => placement.state !== 'notInstalled')
5449
+ .map((placement) => {
5450
+ const projected = placementStatus(placement);
5519
5451
  return {
5520
- label: '查看插件详情',
5521
- run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
5452
+ label: `${platformLabel(placement.platform)} 平台维护`,
5453
+ description: '同步、重新安装和受限清理',
5454
+ meta: projected.value,
5455
+ status: projected.status,
5456
+ run: () => go({ name: 'platform', pluginId: plugin.pluginId, platform: placement.platform }),
5522
5457
  };
5523
5458
  }),
5524
- ...(!detailMenu.some((action) => action.id === 'dependencies') ? [{
5525
- label: '依赖与当前可用关系',
5526
- description: '查看必需依赖、声明者和当前可用关系;当前没有声明的必需依赖',
5527
- run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5528
- }] : []),
5529
- { label: '插件定义信息', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
5530
5459
  { label: '查看插件来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) },
5531
- { label: '诊断与受管内容', description: '查看组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
5460
+ { label: '诊断与技术详情', description: '查看插件身份、组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
5532
5461
  ];
5533
5462
  const moreViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
5534
5463
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: moreViewport.offset, limit: moreViewport.limit });
@@ -5587,7 +5516,7 @@ export function ConsoleAppV4(props) {
5587
5516
  run: () => beginSecondScope(plugin, action.platform, action.scope),
5588
5517
  };
5589
5518
  if (action.id === 'about')
5590
- return { label: '查看支持信息', description: '插件定义与平台支持', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
5519
+ return { label: '查看支持信息', description: '插件定义与平台支持', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
5591
5520
  if (action.id === 'source')
5592
5521
  return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
5593
5522
  if (action.id === 'outputCollision')
@@ -5645,7 +5574,7 @@ export function ConsoleAppV4(props) {
5645
5574
  return typedIssue
5646
5575
  ? { label: '查看对应问题', description: typedIssue.explanation, run: () => go({ name: 'issueDetail', issueId: typedIssue.issueId }) }
5647
5576
  : { label: placement?.state === 'installed' && placement.health === 'error' ? '查看安全技术详情' : placement?.state === 'installed' && placement.health === 'incomplete' ? '查看影响' : '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
5648
- return { label: '返回插件', run: back };
5577
+ return { role: 'navigation', label: '返回插件', run: back };
5649
5578
  };
5650
5579
  const offlineCleanupAction = typedIssue?.actions.find((action) => action.id === 'cleanup');
5651
5580
  const offlineCleanup = typedIssue !== undefined
@@ -5790,9 +5719,7 @@ export function ConsoleAppV4(props) {
5790
5719
  const candidateSummary = selectedField && selectedMutation
5791
5720
  ? selectedMutation.kind === 'unset' ? '恢复继承'
5792
5721
  : selectedField.sensitive ? '已更新'
5793
- : typeof selectedMutation.value === 'object'
5794
- ? objectSummary(selectedField, selectedMutation.value)
5795
- : scalarSummary(selectedField, selectedMutation.value)
5722
+ : fieldValueText(selectedField, selectedMutation.value)
5796
5723
  : selectedPreset && configCandidate
5797
5724
  ? `方案“${selectedPreset.title}” · ${Object.keys(configCandidate.mutations).length} 项修改`
5798
5725
  : undefined;
@@ -5842,10 +5769,10 @@ export function ConsoleAppV4(props) {
5842
5769
  const readonly = false;
5843
5770
  breadcrumb = `${plugin.displayName} / 设置`;
5844
5771
  title = '插件设置';
5845
- description = '先编辑值,再选择保存位置。Tab 可选择完整分组。';
5772
+ description = '先编辑值,再选择保存位置。←→ 切换分组。';
5846
5773
  if (configActionError)
5847
5774
  description = configActionError;
5848
- help = '↑↓ 移动 · Enter 修改 · Tab 固定操作 · ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5775
+ help = '↑↓ 移动 · Enter 修改· ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5849
5776
  if (!currentConfigPage) {
5850
5777
  content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u8BBE\u7F6E", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6\u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
5851
5778
  }
@@ -5871,36 +5798,24 @@ export function ConsoleAppV4(props) {
5871
5798
  }))
5872
5799
  : (activeGroup?.fields ?? []).filter((field) => readonly || configFieldCanSet(field) || configFieldCanUnset(field)).map((field) => ({
5873
5800
  label: field.title,
5874
- description: field.invalidOverrideIssue ?? [
5875
- `当前生效:${effectiveFieldDisplay(field)}(${field.effectiveSourceLabel})`,
5876
- ...pendingFieldTargets(field).map(({ scope: draftScope, value }) => `待保存到${draftScope === 'project' ? '当前项目' : '个人默认'}:${value}`),
5877
- field.description,
5878
- ].filter((value) => Boolean(value)).join(' · '),
5801
+ description: field.invalidOverrideIssue ?? (focus === (activeGroup?.fields ?? []).indexOf(field)
5802
+ ? [field.description, ...pendingFieldTargets(field).filter(() => pendingFieldTargets(field).length > 1
5803
+ || field.effectiveSourceLabel === '当前项目' && pendingFieldDisplay(field, 'project') === undefined).map(({ scope, value }) => `待保存到${configLayerLabel({ scope })}:${value}`),
5804
+ pendingFieldDisplay(field, 'user') !== undefined
5805
+ && field.effectiveSourceLabel === '当前项目'
5806
+ && pendingFieldDisplay(field, 'project') === undefined
5807
+ ? '个人默认待修改,当前项目不受此值影响' : undefined,
5808
+ ].filter(Boolean).join(' · ') : undefined),
5879
5809
  meta: (() => {
5880
- const targets = pendingFieldTargets(field);
5881
- if (targets.length === 0) {
5882
- return `${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`;
5883
- }
5884
- if (targets.length === 1) {
5885
- const target = targets[0];
5886
- return `${target.value} · 拟保存:${target.scope === 'project' ? '当前项目' : '个人默认'}`;
5887
- }
5888
- return targets.map((target) => `${target.scope === 'project' ? '当前项目' : '个人默认'}:${target.value}`).join(' · ');
5810
+ const project = pendingFieldDisplay(field, 'project');
5811
+ const user = pendingFieldDisplay(field, 'user');
5812
+ const pending = project ?? (field.effectiveSourceLabel === '当前项目' ? undefined : user);
5813
+ return `${pending ?? effectiveFieldDisplay(field)} · ${pending !== undefined ? `拟保存:${project !== undefined ? '当前项目' : '个人默认'} · 待保存` : field.effectiveSourceLabel}${pendingFieldTargets(field).length > 0 && pending === undefined ? ' · 有草稿' : ''}`;
5889
5814
  })(),
5890
5815
  configKey: field.key,
5891
- run: () => go(['project', 'user'].some((draftScope) => configLayerPages[draftScope]?.groups.flatMap((group) => group.fields)
5892
- .some((candidate) => candidate.key === field.key && candidate.configured)) ? {
5893
- name: 'configFieldAction',
5894
- pluginId: plugin.pluginId,
5895
- layer: mutableLayer,
5896
- fieldKey: field.key,
5897
- } : {
5898
- name: 'configFieldEditor',
5899
- pluginId: plugin.pluginId,
5900
- layer: mutableLayer,
5901
- fieldKey: field.key,
5902
- editor: fieldEditor(field),
5903
- ...fieldEditorMode(field),
5816
+ run: () => go({
5817
+ name: 'configFieldEditor', pluginId: plugin.pluginId, layer: mutableLayer,
5818
+ fieldKey: field.key, editor: fieldEditor(field), ...fieldEditorMode(field),
5904
5819
  }),
5905
5820
  }));
5906
5821
  const operationActions = [
@@ -5935,7 +5850,7 @@ export function ConsoleAppV4(props) {
5935
5850
  ];
5936
5851
  const configSaveAction = {
5937
5852
  actionId: 'config-save-summary',
5938
- label: configDraftCount > 0 ? `保存设置(${configDraftCount})` : '保存设置',
5853
+ label: configDraftCount > 0 ? `保存设置(${configDraftCount}) [s]` : '保存设置 [s]',
5939
5854
  description: '确认全部设置层的变化,再选择只保存或保存并应用',
5940
5855
  status: configDraftCount > 0 ? 'success' : 'muted',
5941
5856
  disabled: configDraftCount === 0 ? '没有待保存设置'
@@ -5946,20 +5861,18 @@ export function ConsoleAppV4(props) {
5946
5861
  go({ name: 'configSave', pluginId: route.pluginId, layer: { scope: 'effective' } });
5947
5862
  },
5948
5863
  };
5949
- const configGroupAction = {
5950
- actionId: 'config-select-group',
5951
- label: '选择分组',
5952
- description: '查看全部设置分组和快速方案',
5953
- disabled: visibleConfigTabs.length === 0 ? '没有可用设置分组' : undefined,
5954
- run: () => {
5955
- const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeTab?.id));
5956
- setConfigGroupChoiceFocus(current);
5957
- setConfigGroupChooserOpen(true);
5958
- setFocusZone('content');
5959
- },
5960
- };
5961
- pinnedActions = [configSaveAction, configGroupAction];
5962
- actions = [...tabActions, ...operationActions];
5864
+ bottomActions = [configSaveAction, {
5865
+ label: '设置选项', run: () => go({ ...route, view: 'options' }),
5866
+ }];
5867
+ actions = [...tabActions, ...bottomActions];
5868
+ if (route.view === 'options') {
5869
+ title = '设置选项';
5870
+ description = '查看各层存储值,或清空一个保存位置。';
5871
+ actions = [{ label: '查看当前值的来源', run: () => {
5872
+ setBatchTargetOffset(0);
5873
+ go({ ...route, view: 'values' });
5874
+ } }, ...operationActions];
5875
+ }
5963
5876
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
5964
5877
  const focusedConfigField = effectiveConfigFocus < (activeGroup?.fields.length ?? 0)
5965
5878
  ? activeGroup?.fields[effectiveConfigFocus]
@@ -5969,43 +5882,30 @@ export function ConsoleAppV4(props) {
5969
5882
  ? ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
5970
5883
  .some((field) => field.key === focusedConfigField.key && configFieldCanUnset(field)))
5971
5884
  : false;
5972
- help = focusZone === 'actions'
5973
- ? '↑↓ 移动 · Enter 选择固定操作 · Tab 返回设置字段 · s 查看保存汇总 · Esc 返回'
5974
- : canUnsetFocused
5975
- ? '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · u 恢复继承 · Esc 返回'
5976
- : '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · Esc 返回';
5885
+ help = canUnsetFocused
5886
+ ? '↑↓ 移动 · Enter 修改 · ←→ 分组· s 查看汇总 · u 恢复继承 · Esc 返回'
5887
+ : '↑↓ 移动 · Enter 修改 · ←→ 分组· s 查看汇总 · Esc 返回';
5977
5888
  }
5978
- const fieldFocus = tabActions.length > 0
5979
- ? effectiveConfigFocus < tabActions.length ? effectiveConfigFocus : Math.min(scrollOffset, tabActions.length - 1)
5980
- : 0;
5981
- const operationRows = operationActions.reduce((rows, action) => rows + (policy.mode === 'compact' || !action.description ? 1 : 2), 0);
5982
- const viewport = actionViewport(tabActions, false, fieldFocus, scrollOffset, pageActionBudget(2 + operationRows + 3 + (configDraftIssues.length > 0 ? 3 : 0)));
5983
5889
  const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
5984
5890
  const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
5985
5891
  context = configDraftCount > 0 ? `${configDraftCount} 项待保存` : countLabel;
5986
- const groupChoiceActions = visibleConfigTabs.map((tab) => ({
5987
- label: tab.label,
5988
- description: tab.kind === 'presets' ? `${tab.count} 个快速方案` : `${tab.count} 个设置字段`,
5989
- }));
5990
- const groupChoiceViewport = actionViewport(groupChoiceActions, false, configGroupChoiceFocus, 0, Math.max(2, pageActionBudget(5)));
5991
- if (configGroupChooserOpen) {
5992
- title = '选择设置分组';
5993
- description = '选择后回到同一设置总览;未保存草稿保持不变。';
5994
- help = '↑↓ 移动 · Enter 选择 · Esc 取消';
5995
- context = `${visibleConfigTabs.length} 个入口`;
5996
- }
5997
- content = configGroupChooserOpen
5998
- ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["\u5B8C\u6574\u540D\u79F0\uFF1A", visibleConfigTabs[configGroupChoiceFocus]?.label] }), _jsx(SectionCard, { title: "\u5168\u90E8\u5206\u7EC4\u548C\u5FEB\u901F\u65B9\u6848", meta: groupChoiceViewport.total > groupChoiceViewport.limit
5999
- ? `${groupChoiceViewport.position}/${groupChoiceViewport.total}` : undefined, children: _jsx(SemanticRows, { actions: groupChoiceActions, focus: configGroupChoiceFocus, kind: "section", compact: false, offset: groupChoiceViewport.offset, limit: groupChoiceViewport.limit }) })] })
6000
- : _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
6001
- id: action.actionId,
6002
- label: action.label,
6003
- disabled: Boolean(action.disabled),
6004
- })), active: focusZone === 'actions', focus: pinnedActionFocus }), !readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
6005
- ? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "section", compact: false, offset: viewport.offset, limit: viewport.limit }) })
6006
- : null, operationActions.length > 0
6007
- ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
6008
- : null] });
5892
+ content = route.view === 'options' ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })
5893
+ : route.view === 'values' ? (() => {
5894
+ title = '完整设置值与来源';
5895
+ description = '显示各保存位置的完整安全值;敏感值不回显。';
5896
+ const lines = ['effective', 'project', 'user'].flatMap((scope) => ((scope === 'effective' ? currentConfigPage : configLayerPages[scope])?.groups.flatMap((group) => group.fields) ?? []).flatMap((field) => wrapConsoleText(`${field.title} · ${configLayerLabel({ scope })}:${scope === 'effective'
5897
+ ? field.invalidOverrideIssue ? '需修正' : fieldValueText(field, field.draftValue ?? field.effectiveValue, true, field.effectiveSourceLabel !== '未设置')
5898
+ : fieldValueText(field, field.draftValue ?? field.value, true, field.configured)}`, size.columns - 4)));
5899
+ readingText = ['effective', 'project', 'user'].flatMap((scope) => ((scope === 'effective' ? currentConfigPage : configLayerPages[scope])?.groups.flatMap((group) => group.fields) ?? []).map((field) => `${field.title} · ${configLayerLabel({ scope })}:${scope === 'effective'
5900
+ ? field.invalidOverrideIssue ? '需修正' : fieldValueText(field, field.draftValue ?? field.effectiveValue, true, field.effectiveSourceLabel !== '未设置')
5901
+ : fieldValueText(field, field.draftValue ?? field.value, true, field.configured)}`)).join('\n');
5902
+ const pageRows = Math.max(1, pageActionBudget(4));
5903
+ const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
5904
+ referenceDetailPagination = { total: lines.length, pageRows };
5905
+ actions = [{ label: '返回设置', run: back }];
5906
+ help = '←→ 或 PgUp/PgDn 阅读 · Esc 返回';
5907
+ return _jsxs(Box, { flexDirection: "column", children: [lines.slice(offset, offset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
5908
+ })() : _jsxs(Box, { flexDirection: "column", children: [!readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, _jsx(SequenceRows, { actions: actions, itemCount: tabActions.length, wrapValues: true, focus: effectiveConfigFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2 + (configDraftIssues.length > 0 ? 3 : 0)), compact: policy.mode === 'compact' })] });
6009
5909
  }
6010
5910
  }
6011
5911
  else if (route.name === 'configSave' && plugin) {
@@ -6020,6 +5920,7 @@ export function ConsoleAppV4(props) {
6020
5920
  })));
6021
5921
  if (route.view === 'changes') {
6022
5922
  const lines = changeDetailLines(changes, size.columns - 4);
5923
+ readingText = changeDetailText(changes);
6023
5924
  const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
6024
5925
  const detailOffset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
6025
5926
  const visibleLines = lines.slice(detailOffset, detailOffset + pageRows);
@@ -6028,21 +5929,15 @@ export function ConsoleAppV4(props) {
6028
5929
  context = `${Math.min(detailOffset + 1, lines.length)}–${Math.min(detailOffset + visibleLines.length, lines.length)} / ${lines.length}`;
6029
5930
  title = '完整设置变化';
6030
5931
  description = '逐项显示修改前和修改后的完整值。';
6031
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回保存汇总';
5932
+ help = '←→ 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回保存汇总';
6032
5933
  actions = [
6033
- ...paginationActions({
6034
- offset: detailOffset,
6035
- total: lines.length,
6036
- pageRows,
6037
- move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
6038
- }),
6039
5934
  { label: '返回保存汇总', run: back },
6040
5935
  ];
6041
5936
  content = _jsxs(Box, { flexDirection: "column", children: [detailOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${detailOffset} 行`, status: "muted", compact: true }) : null, visibleLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${detailOffset + index}:${line}`)), detailOffset + visibleLines.length < lines.length
6042
5937
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${lines.length - detailOffset - visibleLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6043
5938
  }
6044
5939
  else {
6045
- const visibleChanges = changes.slice(0, policy.mode === 'compact' ? 0 : 1);
5940
+ const visibleChanges = changes.slice(0, 2);
6046
5941
  breadcrumb = `${plugin.displayName} / 设置 / 保存汇总`;
6047
5942
  context = `${changes.length} 项未保存`;
6048
5943
  title = `保存 ${changes.length} 项设置修改?`;
@@ -6069,55 +5964,27 @@ export function ConsoleAppV4(props) {
6069
5964
  go({ ...route, view: 'changes' });
6070
5965
  },
6071
5966
  });
6072
- const configSaveStaticRows = 6 + visibleChanges.length * 2 + draftGroups.length;
6073
- const configSaveActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(configSaveStaticRows));
6074
- const changeSectionTitle = visibleChanges.length < changes.length
6075
- ? `设置变更 · 显示 ${visibleChanges.length}/${changes.length}(另 ${changes.length - visibleChanges.length} 项)`
6076
- : '设置变更';
6077
- content = _jsxs(Box, { flexDirection: "column", children: [visibleChanges.length > 0 ? _jsx(SectionCard, { title: changeSectionTitle, compact: policy.mode === 'compact', children: visibleChanges.map((change) => _jsx(ReviewChangeRow, { title: `~ ${change.title}`, value: `${change.before} → ${change.after}`, compact: true }, `${change.title}:${change.key}`)) }) : null, _jsxs(SectionCard, { title: "\u4FDD\u5B58\u76EE\u6807", compact: policy.mode === 'compact', children: [draftGroups.map((group) => _jsx(StaticRow, { title: scopeLabel(group.scope), meta: `${group.changes.length} 项修改`, compact: true }, group.scope)), _jsx(StaticRow, { title: "\u6BCF\u5C42\u72EC\u7ACB\u63D0\u4EA4\uFF1B\u5931\u8D25\u5C42\u4FDD\u7559\u8349\u7A3F", status: "warning", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: configSaveActionViewport.offset, limit: configSaveActionViewport.limit })] });
5967
+ const summaryLines = visibleChanges.flatMap((change) => {
5968
+ const lines = wrapConsoleText(`${change.title} · ${change.before ?? '未设置'} → ${change.after}`, Math.max(1, size.columns - 4));
5969
+ return lines.length > 2 ? [lines[0], `${lines[1]}…`] : lines;
5970
+ });
5971
+ const configSaveActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(summaryLines.length + 2));
5972
+ content = _jsxs(Box, { flexDirection: "column", children: [summaryLines.map((line, index) => _jsx(Text, { children: line }, index)), changes.length > visibleChanges.length ? _jsxs(Text, { children: ["\u53E6\u6709 ", changes.length - visibleChanges.length, " \u9879\uFF1B\u5B8C\u6574\u53D8\u5316\u4E2D\u53EF\u8BFB"] }) : null, _jsx(Text, { children: "\u6BCF\u5C42\u72EC\u7ACB\u63D0\u4EA4\uFF1B\u5931\u8D25\u5C42\u4FDD\u7559\u8349\u7A3F" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: configSaveActionViewport.offset, limit: configSaveActionViewport.limit })] });
6078
5973
  }
6079
5974
  }
6080
- else if (route.name === 'configFieldAction' && plugin && activeField) {
6081
- const layer = configLayerLabel(route.layer);
6082
- breadcrumb = `${plugin.displayName} / 设置 / ${layer} / ${activeField.title}`;
6083
- title = activeField.title;
6084
- description = `这个字段在${layer}中已经有值。`;
6085
- help = '↑↓ 移动 · Enter 选择 · Esc 返回';
6086
- const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
6087
- .filter((field) => field.key === activeField.key).map((field) => ({ scope, field })) ?? []);
6088
- const canSet = targetFields.some(({ field }) => configFieldCanSet(field));
6089
- const canUnset = targetFields.some(({ field }) => configFieldCanUnset(field));
6090
- actions = [
6091
- ...(canSet ? [{
6092
- label: '修改值',
6093
- run: () => go({
6094
- name: 'configFieldEditor',
6095
- pluginId: plugin.pluginId,
6096
- layer: route.layer,
6097
- fieldKey: activeField.key,
6098
- editor: fieldEditor(activeField),
6099
- ...fieldEditorMode(activeField),
6100
- }),
6101
- }] : []),
6102
- ...(canUnset ? [{
6103
- label: '恢复继承',
6104
- description: '选择要删除覆盖值的位置',
6105
- status: 'error',
6106
- run: () => removeDraft(activeField.key),
6107
- }] : []),
6108
- ];
6109
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [activeField.invalidOverrideIssue
6110
- ? _jsx(StaticRow, { title: "\u5F53\u524D\u8986\u76D6\u65E0\u6548", meta: activeField.invalidOverrideIssue, status: "warning" })
6111
- : null, !canSet && canUnset
6112
- ? _jsx(StaticRow, { title: "\u672C\u9879\u76EE\u65E7\u503C\u4E0D\u518D\u4F7F\u7528", meta: "\u5F53\u524D\u7EE7\u627F\u4E2A\u4EBA\u6216\u63D2\u4EF6\u9ED8\u8BA4\uFF1B\u5220\u9664\u65E7\u503C\u540E\u4FDD\u6301\u7EE7\u627F", status: "warning" })
6113
- : null, _jsx(StaticRow, { title: `${layer}中的值`, meta: currentFieldDisplay(activeField) }), _jsx(StaticRow, { title: "\u5F53\u524D\u5B9E\u9645\u751F\u6548", meta: `${effectiveFieldDisplay(activeField)} · 来自${activeField.effectiveSourceLabel}` }), !activeField.invalidOverrideIssue || activeField.invalidOverrideOwnedByLayer
6114
- ? _jsx(StaticRow, { title: "\u5220\u9664\u540E\u5C06\u6062\u590D", meta: `${activeField.fallbackDisplayValue ?? '未设置'} · 来自${activeField.fallbackSourceLabel ?? '未设置'}` })
6115
- : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6116
- }
6117
5975
  else if (route.name === 'configFieldEditor' && plugin && activeField) {
6118
5976
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title}`;
6119
5977
  const showChoices = (activeField.choices?.length ?? 0) > 0 && route.mode !== 'custom';
6120
- if (showChoices) {
5978
+ const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields).filter((field) => field.key === activeField.key) ?? []);
5979
+ const canSet = targetFields.some(configFieldCanSet);
5980
+ const canUnset = targetFields.some(configFieldCanUnset);
5981
+ if (!canSet) {
5982
+ title = activeField.title;
5983
+ description = activeField.invalidOverrideIssue ?? '此旧值不允许修改;可以删除覆盖并恢复继承。';
5984
+ actions = [];
5985
+ content = _jsx(StatusCard, { status: "warning", title: "\u4EC5\u53EF\u5220\u9664\u65E7\u503C", description: description });
5986
+ }
5987
+ else if (showChoices) {
6121
5988
  title = `选择${activeField.title}`;
6122
5989
  description = activeField.type !== 'boolean' && activeField.allowCustom
6123
5990
  ? '先选择插件提供的方案,也可以继续自定义。'
@@ -6180,18 +6047,12 @@ export function ConsoleAppV4(props) {
6180
6047
  .filter((property) => !fixedKeys.has(property))
6181
6048
  .sort((left, right) => left.localeCompare(right, 'en'));
6182
6049
  const propertySummary = (property, definition) => {
6183
- if (!Object.hasOwn(activeObjectDraft, property)) {
6184
- return 'required' in definition && definition.required ? '必填 · 未设置' : '未设置';
6185
- }
6186
- if (activeField.sensitive || definition.sensitive)
6187
- return '已设置';
6188
- const value = activeObjectDraft[property];
6189
- const preset = definition.choices?.presets.find((choice) => choice.value === value);
6190
- if (preset)
6191
- return preset.title;
6192
- if (typeof value === 'boolean')
6193
- return value ? '开启' : '关闭';
6194
- return String(value);
6050
+ const presentation = presentConfigValue({ definition,
6051
+ value: activeObjectDraft[property], privateValue: activeField.sensitive,
6052
+ configured: Object.hasOwn(activeObjectDraft, property),
6053
+ });
6054
+ return presentation.state === 'unset' && 'required' in definition && definition.required
6055
+ ? '必填 · 未设置' : presentation.text;
6195
6056
  };
6196
6057
  const propertyActions = [
6197
6058
  ...fixedEntries.map(([property, definition]) => ({
@@ -6242,6 +6103,16 @@ export function ConsoleAppV4(props) {
6242
6103
  : activeField.sensitive ? '当前值不会回显;留空不会自动删除旧值。' : activeField.description ?? '';
6243
6104
  content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: inputValue, sensitive: activeField.sensitive, placeholder: activeField.placeholder, columns: Math.max(8, size.columns - 8) }), description: inputError ?? (activeField.sensitive ? '输入已隐藏。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
6244
6105
  }
6106
+ if (canUnset) {
6107
+ const unset = { label: '恢复继承', description: '选择要删除覆盖值的位置', run: () => removeDraft(activeField.key) };
6108
+ const inputEditor = canSet && !showChoices && (route.editor === 'number' || route.editor === 'string');
6109
+ help += ' · Ctrl-R 恢复继承';
6110
+ if (!inputEditor) {
6111
+ const offset = actions.length;
6112
+ actions.push(unset);
6113
+ content = _jsxs(Box, { flexDirection: "column", children: [content, _jsx(SemanticRows, { actions: actions, offset: offset, focus: focus, kind: "action", compact: true })] });
6114
+ }
6115
+ }
6245
6116
  }
6246
6117
  else if (route.name === 'configObjectPropertyEditor' && plugin
6247
6118
  && activeField?.definition.type === 'object' && activeObjectDraft) {
@@ -6348,6 +6219,8 @@ export function ConsoleAppV4(props) {
6348
6219
  breadcrumb = `${unavailablePluginName} / 设置`;
6349
6220
  context = '暂时不可编辑';
6350
6221
  const diagnostic = configUnavailableDiagnostic(route);
6222
+ if (showTechnical)
6223
+ readingText = diagnosticDetailText(diagnostic);
6351
6224
  const invalidLayer = (route.invalidStores?.length ?? 0) > 0;
6352
6225
  const canRecoverSource = configUnavailableCanRecoverSource(route);
6353
6226
  title = diagnostic.title;
@@ -6380,16 +6253,25 @@ export function ConsoleAppV4(props) {
6380
6253
  },
6381
6254
  ] : []),
6382
6255
  { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => toggleTechnical() },
6383
- { label: '返回', run: back },
6256
+ { role: 'navigation', label: '返回', run: back },
6384
6257
  ];
6385
6258
  const unsupportedScopes = resetScopes.filter((scope) => configResetAvailability[scope]?.resettable === false);
6386
6259
  const unsupportedRows = unsupportedScopes.map((scope) => _jsx(StaticRow, { title: `${scope === 'user' ? '个人默认' : '当前项目'}设置层不能自动重置:特殊节点,请手动移动`, status: "warning", compact: true }, `unsupported-reset:${scope}`));
6260
+ const technicalPageRows = responsiveBudget(2, 4);
6261
+ if (showTechnical && !invalidLayer) {
6262
+ referenceDetailPagination = {
6263
+ total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
6264
+ pageRows: technicalPageRows,
6265
+ };
6266
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6267
+ }
6268
+ const unavailableActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(technicalPageRows + 6 + unsupportedRows.length));
6387
6269
  content = invalidLayer
6388
6270
  ? _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u4E0D\u53EF\u8BFB\u53D6\u7684\u8BBE\u7F6E\u5C42", children: (route.invalidStores ?? []).map((store) => _jsx(StaticRow, { title: store.scope === 'user' ? '个人默认设置' : '当前项目设置', meta: "\u9700\u8981\u91CD\u7F6E\u6216\u624B\u52A8\u79FB\u5F00", status: "warning", compact: true }, store.scope)) }), showTechnical ? _jsxs(TechnicalDetails, { children: [(route.invalidStores ?? []).flatMap((store) => [
6389
6271
  _jsx(StaticRow, { title: scopeLabel(store.scope), meta: store.code, compact: true }, store.scope),
6390
6272
  _jsx(StaticRow, { title: "path summary", meta: store.pathSummary, compact: true }, `${store.scope}:path`),
6391
6273
  ]), _jsx(StaticRow, { title: "stage", meta: safeDiagnosticCode(route.diagnosticStage, 'read-config'), compact: true })] }) : null, unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] })
6392
- : _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6274
+ : _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, columns: size.columns, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? technicalPageRows : undefined, compact: policy.mode === 'compact' }), unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: showTechnical || policy.mode === 'compact', offset: showTechnical ? unavailableActionViewport.offset : undefined, limit: showTechnical ? unavailableActionViewport.limit : undefined })] });
6393
6275
  }
6394
6276
  else if (route.name === 'sourceList') {
6395
6277
  breadcrumb = '插件来源';
@@ -6415,6 +6297,10 @@ export function ConsoleAppV4(props) {
6415
6297
  : configured?.type === 'git'
6416
6298
  && (configured.url.startsWith('git@') || configured.url.startsWith('ssh://'))
6417
6299
  ? 'SSH' : 'HTTPS';
6300
+ const policy = overview.detail?.type === 'path' ? 'Path'
6301
+ : overview.detail?.refPolicy === 'explicit'
6302
+ ? `固定 ${sourceRefSummary(overview.detail.explicitRef ?? overview.detail.requestedRef)}`
6303
+ : `通道 ${sourceRefSummary(overview.detail?.requestedRef)}`;
6418
6304
  const version = `${generation?.active?.catalogVersion ?? '未启用'}${freshness?.candidate?.catalogVersion
6419
6305
  ? `→${freshness.candidate.catalogVersion}` : ''}`;
6420
6306
  const available = generation?.state === 'ready' || health === 'ready';
@@ -6422,7 +6308,8 @@ export function ConsoleAppV4(props) {
6422
6308
  return {
6423
6309
  label: source.id,
6424
6310
  description: sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary),
6425
- meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`}`,
6311
+ sourceLabel: policy,
6312
+ meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`} · ${relativeCheckTime(freshness?.checkedAt)}`,
6426
6313
  tags: _jsxs(Box, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", source.primary ? '主来源' : transport, "] "] }), _jsx(StatusBadge, { items: [{ key: 'health', label: available ? '可用' : unavailable ? '不可用' : '未确认',
6427
6314
  symbol: available ? '✓' : unavailable ? '!' : '?', status: available ? 'success' : unavailable ? 'warning' : 'muted' }] }), freshness && freshness.state !== 'current' && freshness.state !== 'fixed'
6428
6315
  ? _jsxs(Text, { color: tuiTheme.colors.accent, children: [" [", freshness.state === 'updateAvailable' ? '↑ ' : '', presentation.label, "]"] })
@@ -6431,29 +6318,19 @@ export function ConsoleAppV4(props) {
6431
6318
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
6432
6319
  };
6433
6320
  });
6434
- pinnedActions = [
6321
+ bottomActions = [
6435
6322
  { label: '+ 添加来源', description: 'Git 仓库或本地目录', run: beginAddSource },
6436
6323
  {
6437
- label: '检查全部来源',
6324
+ actionId: 'source-check-all', label: '检查全部来源 [r]',
6438
6325
  description: '并行检查各来源;必要时完整验证;结果不会自动启用待检查版本',
6439
6326
  run: () => props.controller.recheckAllCatalogSources(),
6440
6327
  },
6441
6328
  { label: 'Catalog 本地存储', description: '检查占用和预览安全清理', run: () => go({ name: 'catalogStoreOverview' }) },
6442
6329
  ];
6443
- help = focusZone === 'actions'
6444
- ? '←→ 移动固定操作 · Enter 选择 · Tab 返回来源卡片 · Esc 返回'
6445
- : '↑↓ 移动 · Enter 打开 · Tab 固定操作 · r/c 检查全部 · Esc 返回';
6446
- const viewport = rowViewport({
6447
- rowHeights: actions.map((action) => objectCardRowLayout({
6448
- description: action.description ?? '', meta: action.meta, width: size.columns - 2, hasTags: true,
6449
- }).height),
6450
- focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(1),
6451
- });
6452
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
6453
- id: action.actionId ?? `${action.label}:${index}`,
6454
- label: action.label,
6455
- disabled: Boolean(action.disabled),
6456
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
6330
+ help = '↑↓ 移动 · Enter 打开 · Esc 返回';
6331
+ const sourceCount = actions.length;
6332
+ actions = [...actions, ...bottomActions];
6333
+ content = _jsx(SequenceRows, { actions: actions, itemCount: sourceCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(), compact: policy.mode === 'compact', cards: true });
6457
6334
  }
6458
6335
  else if (route.name === 'catalogStoreOverview') {
6459
6336
  breadcrumb = '插件来源 / 本地存储';
@@ -6475,7 +6352,7 @@ export function ConsoleAppV4(props) {
6475
6352
  },
6476
6353
  { actionId: 'catalog-store-rules', label: '查看保留规则', description: '了解哪些目录会保留,以及旧版本何时可以清理', run: () => go({ name: 'catalogStoreRules' }) },
6477
6354
  { actionId: 'catalog-store-refresh', label: '重新检查', description: '重新读取当前本地存储', run: () => setCatalogStoreRefresh((value) => value + 1) },
6478
- { actionId: 'catalog-store-back', label: '返回来源列表', run: back },
6355
+ { role: 'navigation', actionId: 'catalog-store-back', label: '返回来源列表', run: back },
6479
6356
  ];
6480
6357
  content = _jsxs(Box, { flexDirection: "column", children: [catalogStoreError
6481
6358
  ? _jsx(Callout, { status: "warning", title: "\u672C\u5730\u5B58\u50A8\u68C0\u67E5\u5931\u8D25", description: "\u672A\u6267\u884C\u5220\u9664\u3002\u8BF7\u68C0\u67E5 Catalog \u914D\u7F6E\u548C\u672C\u5730\u5B58\u50A8\u540E\u91CD\u8BD5\u3002" })
@@ -6490,7 +6367,7 @@ export function ConsoleAppV4(props) {
6490
6367
  ? '' : '清理前会再次检查每个项目,只删除能够确认安全的旧内容。';
6491
6368
  actions = [
6492
6369
  { actionId: 'catalog-store-rules-technical', label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
6493
- { actionId: 'catalog-store-rules-back', label: '返回存储概览', run: back },
6370
+ { role: 'navigation', actionId: 'catalog-store-rules-back', label: '返回存储概览', run: back },
6494
6371
  ];
6495
6372
  content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical ? _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u59CB\u7EC8\u4FDD\u7559 \u00B7 \u5F53\u524D\u4F7F\u7528\u7684\u76EE\u5F55\u4E0D\u4F1A\u5220\u9664", children: _jsx(StaticRow, { title: "\u4E0D\u5C5E\u4E8E AW\u3001\u65E0\u6CD5\u786E\u8BA4\u5B89\u5168\u6216\u4ECD\u5728\u4F7F\u7528\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559", status: "muted", compact: true }) }), _jsxs(SectionCard, { title: "\u6EE1\u8DB3\u65F6\u95F4\u548C\u5B8C\u6574\u9A8C\u8BC1\u540E\u53EF\u56DE\u6536", children: [_jsx(StaticRow, { title: "\u65E7\u7248\u672C\u6EE1\u8DB3\u4FDD\u7559\u671F\u9650\u540E\u624D\u6E05\u7406", compact: true }), _jsx(StaticRow, { title: "\u672A\u5B8C\u6210\u7684\u4E0B\u8F7D\u6216\u66FF\u6362\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u9694\u79BB\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u5BF9\u5E94\u6B63\u5F0F\u7248\u672C\u5B8C\u6574\u53EF\u7528\u540E\u624D\u6E05\u7406", compact: true })] })] }) : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u6E05\u7406\u5224\u5B9A\u8BC1\u636E", children: [_jsx(StaticRow, { title: "\u9501", meta: "AW_HOME \u5199\u9501\u5185\u91CD\u65B0\u68C0\u67E5", compact: true }), _jsx(StaticRow, { title: "\u6B63\u5F0F\u76EE\u5F55\u7248\u672C", meta: "\u975E Active \u9879\u4FDD\u7559 30 \u5929", compact: true }), _jsx(StaticRow, { title: "staging\u3001replacement\u3001delete claim", meta: "\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "quarantine", meta: "\u4FDD\u7559 24 \u5C0F\u65F6\uFF1B\u5BF9\u5E94\u6B63\u5F0F\u76EE\u5F55\u7248\u672C\u5FC5\u987B\u5B8C\u6574\u5065\u5EB7", compact: true }), _jsx(StaticRow, { title: "\u4FDD\u7559\u8282\u70B9", meta: "foreign\u3001\u7B26\u53F7\u94FE\u63A5\u3001unsafe\u3001\u635F\u574F\u6216\u672A\u77E5\u9879\u76EE", compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6496
6373
  }
@@ -6506,17 +6383,26 @@ export function ConsoleAppV4(props) {
6506
6383
  && generation?.configMismatch === true;
6507
6384
  const logicalIdentityChanged = generation?.errorCode === 'catalogLogicalIdentityChanged';
6508
6385
  const activationBlocked = props.controller.catalogSourceRecoveryBlockReason();
6386
+ const generationMissingCount = generation?.compatibility?.state === 'unsupported'
6387
+ ? generation.compatibility.missingCapabilities.length : undefined;
6388
+ const freshnessMissingCount = freshness?.missingCapabilities?.length;
6509
6389
  breadcrumb = `插件来源 / ${route.sourceId}`;
6510
6390
  context = detail?.type === 'path' ? '本地目录' : 'Git';
6511
6391
  title = route.sourceId;
6512
6392
  description = generationState === 'incompatible'
6513
- ? `当前目录版本与 AW 不兼容;缺少 ${generation?.compatibility?.state === 'unsupported' ? generation.compatibility.missingCapabilities.length : 0} 项功能支持。`
6393
+ ? generationMissingCount && generationMissingCount > 0
6394
+ ? `当前目录版本与 AW 不兼容;缺少 ${generationMissingCount} 项功能支持。`
6395
+ : '当前目录版本与 AW 不兼容;所需功能无法确认。'
6514
6396
  : freshness?.state === 'incompatible'
6515
6397
  ? generationState === 'repairRequired'
6516
6398
  ? `待检查版本需要升级 AW 或修改来源后才能使用;损坏的当前启用版本仍可按旧版本精确修复。`
6517
6399
  : activeVersion && generationState === 'ready'
6518
- ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
6519
- : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;这个来源没有可用目录版本。`
6400
+ ? freshnessMissingCount && freshnessMissingCount > 0
6401
+ ? `当前 AW 缺少 ${freshnessMissingCount} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
6402
+ : `待检查版本所需功能无法确认;已验证的当前目录版本 ${activeVersion} 仍可浏览和使用。`
6403
+ : freshnessMissingCount && freshnessMissingCount > 0
6404
+ ? `当前 AW 缺少 ${freshnessMissingCount} 项待检查版本所需功能;这个来源没有已验证可用的目录版本。`
6405
+ : '待检查版本所需功能无法确认;这个来源没有已验证可用的目录版本。'
6520
6406
  : freshness?.state === 'checkFailed' && activeVersion && generationState === 'ready'
6521
6407
  ? `更新检查失败,但当前目录版本 ${activeVersion} 仍可浏览和使用。`
6522
6408
  : freshness?.state === 'updateAvailable' && generationState === 'ready'
@@ -6529,7 +6415,7 @@ export function ConsoleAppV4(props) {
6529
6415
  ? '当前目录版本损坏,且来源配置已变化。验证待检查版本后可精确修复同一版本,或明确切换到新版本。'
6530
6416
  : '当前目录版本缺失或损坏。可在这里精确修复。'
6531
6417
  : generationState === 'configMismatch' ? '来源配置与当前启用版本不一致。可验证待检查版本并切换。'
6532
- : logicalIdentityChanged ? '来源发布身份已变化。请编辑来源,或移除后重新添加。'
6418
+ : logicalIdentityChanged ? '来源发布身份已变化;旧 Active 是否仍可用需要单独验证。请编辑来源,或移除后重新添加。'
6533
6419
  : '来源没有可用的启用版本。可在这里取得并启用。';
6534
6420
  help = '↑↓ 移动 · Enter 选择 · Esc 返回';
6535
6421
  if (policy.mode === 'compact')
@@ -6599,7 +6485,7 @@ export function ConsoleAppV4(props) {
6599
6485
  }]
6600
6486
  : [{
6601
6487
  actionId: 'source-check',
6602
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6488
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6603
6489
  description: '检查这个来源;检测到变化或异常时完整验证',
6604
6490
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6605
6491
  run: () => props.controller.recheckCatalogSource(route.sourceId),
@@ -6626,6 +6512,12 @@ export function ConsoleAppV4(props) {
6626
6512
  }]),
6627
6513
  { actionId: 'source-remove', label: '移除来源', description: '移除来源配置', status: 'error', disabled: props.controller.listSources().length <= 1 ? '至少保留一个来源' : detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6628
6514
  { actionId: 'source-technical', label: '技术详情', description: '查看脱敏错误代码和当前版本证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6515
+ ...(freshness?.operationRecordId ? [{
6516
+ actionId: 'source-check-diagnostic',
6517
+ label: '查看检查诊断与报告',
6518
+ description: `安全记录 ${freshness.operationRecordId}`,
6519
+ run: () => go({ name: 'operationDiagnostic', recordId: freshness.operationRecordId }),
6520
+ }] : []),
6629
6521
  ...(generationState !== 'ready'
6630
6522
  || freshness?.state === 'updateAvailable'
6631
6523
  || freshness?.state === 'localChangesAvailable'
@@ -6633,18 +6525,11 @@ export function ConsoleAppV4(props) {
6633
6525
  || freshness?.state === 'rollbackDetected'
6634
6526
  || freshness?.state === 'incompatible' ? [{
6635
6527
  actionId: 'source-check',
6636
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6528
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6637
6529
  description: '检查这个来源;检测到变化或异常时完整验证',
6638
6530
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6639
6531
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6640
6532
  }] : []),
6641
- {
6642
- actionId: 'source-full-validate',
6643
- label: '完整验证',
6644
- description: '强制重新取得并完整验证这个来源',
6645
- disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6646
- run: () => props.controller.fullValidateCatalogSource(route.sourceId),
6647
- },
6648
6533
  ];
6649
6534
  const sourcePrimaryAction = generationAction.length === 0 ? [] : [{
6650
6535
  ...generationAction[0],
@@ -6664,27 +6549,31 @@ export function ConsoleAppV4(props) {
6664
6549
  : configuredSource?.type === 'git'
6665
6550
  && (configuredSource.url.startsWith('git@') || configuredSource.url.startsWith('ssh://'))
6666
6551
  ? 'SSH' : 'HTTPS';
6667
- const versionPolicy = detail?.type === 'path' ? '当前本地内容'
6668
- : detail?.refPolicy === 'cliChannel' ? '跟随 AW 的稳定版或预览版目录'
6669
- : '固定版本或 Git 引用';
6552
+ const versionPolicy = detail?.type === 'path' ? 'Path · 当前本地内容'
6553
+ : detail?.refPolicy === 'cliChannel' ? `跟随 AW 通道 · ${sourceRefSummary(detail.requestedRef)}`
6554
+ : `固定 ref · ${sourceRefSummary(detail?.explicitRef ?? detail?.requestedRef)}`;
6670
6555
  const sourceCount = snapshot.viewModel?.sourceTabs.find((source) => source.id === route.sourceId)?.count;
6671
6556
  const checkAction = {
6672
6557
  actionId: 'source-check',
6673
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6558
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6674
6559
  description: '检查这个来源;检测到变化或异常时完整验证;不会自动启用更新',
6675
6560
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6676
6561
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6677
6562
  };
6563
+ const viewPluginsAction = {
6564
+ actionId: 'source-view-plugins',
6565
+ label: '查看此来源插件',
6566
+ description: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`,
6567
+ run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
6568
+ };
6569
+ const healthyWithoutUpdate = generationState === 'ready'
6570
+ && (freshness?.state === 'current' || freshness?.state === 'fixed');
6678
6571
  actions = [
6572
+ ...(healthyWithoutUpdate ? [viewPluginsAction] : []),
6679
6573
  ...sourcePrimaryAction,
6680
6574
  ...(sourcePrimaryAction.some((action) => action.actionId === 'source-check')
6681
6575
  ? [] : [checkAction]),
6682
- {
6683
- actionId: 'source-view-plugins',
6684
- label: '查看此来源插件',
6685
- description: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`,
6686
- run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
6687
- },
6576
+ ...(healthyWithoutUpdate ? [] : [viewPluginsAction]),
6688
6577
  managementActions[0],
6689
6578
  managementActions.find((action) => action.actionId === 'source-remove'),
6690
6579
  {
@@ -6706,7 +6595,9 @@ export function ConsoleAppV4(props) {
6706
6595
  `${transport} · ${sourceLocationSummary(detail?.type ?? 'git', detail?.locationSummary)}`,
6707
6596
  `当前 ${activeVersion ?? '未启用'}${candidateVersion ? ` → ${candidateVersion}` : ''} · ${sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`}`,
6708
6597
  `版本策略:${versionPolicy}`,
6709
- `检查:${freshnessLabel} · ${freshness?.checkedAt ?? '尚未检查'}`,
6598
+ `检查:${freshnessLabel} · ${freshness?.state === 'checking'
6599
+ ? '正在检查这个来源;必要时验证内容'
6600
+ : relativeCheckTime(freshness?.checkedAt)}`,
6710
6601
  ].join('\n');
6711
6602
  const sourceCardHeight = objectCardRowLayout({
6712
6603
  description: sourceSummary, width: size.columns - 2, hasTags: true,
@@ -6791,29 +6682,32 @@ export function ConsoleAppV4(props) {
6791
6682
  : detail?.primary ? '主来源不能直接移除' : undefined,
6792
6683
  run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId,
6793
6684
  changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6794
- { label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6685
+ { label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6795
6686
  description: '检查这个来源;检测到变化或异常时完整验证;不会自动启用更新',
6796
6687
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6797
6688
  run: () => props.controller.recheckCatalogSource(route.sourceId) },
6798
6689
  { label: '技术详情', description: '查看脱敏错误代码和版本证据',
6799
6690
  run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6691
+ ...(freshness?.operationRecordId ? [{
6692
+ label: '查看检查诊断与报告',
6693
+ description: `安全记录 ${freshness.operationRecordId}`,
6694
+ run: () => go({ name: 'operationDiagnostic', recordId: freshness.operationRecordId }),
6695
+ }] : []),
6800
6696
  ];
6801
6697
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
6802
6698
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
6803
6699
  }
6804
6700
  else if (route.name === 'sourceTechnical') {
6805
6701
  const diagnostic = resolveSourceTechnicalDiagnostic(route.sourceId);
6702
+ readingText = diagnosticDetailText(diagnostic);
6806
6703
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
6807
6704
  const lines = diagnosticDetailLines(diagnostic, size.columns - 6);
6808
6705
  const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - sourceTechnicalPageRows));
6809
6706
  context = `${offset + 1}–${Math.min(lines.length, offset + sourceTechnicalPageRows)} / ${lines.length} 行`;
6810
6707
  title = '来源技术诊断';
6811
6708
  description = '';
6812
- help = '↑↓ 选择翻页 · Enter 执行 · p/n 翻页 · Esc 返回';
6813
- const sourceTechnicalLastOffset = Math.max(0, lines.length - sourceTechnicalPageRows);
6709
+ help = '←→ 或 PgUp/PgDn 翻页 · Esc 返回';
6814
6710
  actions = [
6815
- { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalPageRows)) },
6816
- { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalPageRows)) },
6817
6711
  { label: '返回来源详情', run: back },
6818
6712
  ];
6819
6713
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5B8C\u6574\u5B89\u5168\u5B57\u6BB5", compact: true, children: lines.slice(offset, offset + sourceTechnicalPageRows).map((line, index) => _jsx(Text, { children: line }, offset + index)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -6954,6 +6848,7 @@ export function ConsoleAppV4(props) {
6954
6848
  const changes = sourceDraftChanges(sourceDraft);
6955
6849
  if (route.view === 'changes') {
6956
6850
  const lines = changeDetailLines(changes, size.columns - 4);
6851
+ readingText = changeDetailText(changes);
6957
6852
  const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
6958
6853
  const detailOffset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
6959
6854
  const visibleLines = lines.slice(detailOffset, detailOffset + pageRows);
@@ -6962,14 +6857,8 @@ export function ConsoleAppV4(props) {
6962
6857
  context = `${Math.min(detailOffset + 1, lines.length)}–${Math.min(detailOffset + visibleLines.length, lines.length)} / ${lines.length}`;
6963
6858
  title = '完整来源变化';
6964
6859
  description = '逐项显示修改前和修改后的完整值。';
6965
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回修改汇总';
6860
+ help = '←→ 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回修改汇总';
6966
6861
  actions = [
6967
- ...paginationActions({
6968
- offset: detailOffset,
6969
- total: lines.length,
6970
- pageRows,
6971
- move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
6972
- }),
6973
6862
  { label: '返回修改汇总', run: back },
6974
6863
  ];
6975
6864
  content = _jsxs(Box, { flexDirection: "column", children: [detailOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${detailOffset} 行`, status: "muted", compact: true }) : null, visibleLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${detailOffset + index}:${line}`)), detailOffset + visibleLines.length < lines.length
@@ -7022,22 +6911,18 @@ export function ConsoleAppV4(props) {
7022
6911
  actions = [{ label: sourceDependenciesExpanded ? '收起依赖内容' : '查看依赖内容', description: '只显示插件、来源、平台、位置和类别聚合', run: () => {
7023
6912
  setSourceDependenciesExpanded((value) => !value);
7024
6913
  setBatchTargetOffset(0);
7025
- } }, { label: '返回来源详情', run: back }];
6914
+ } }, { role: 'navigation', label: '返回来源详情', run: back }];
7026
6915
  const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
7027
6916
  const referenceLimit = Math.max(1, pageActionBudget(12));
7028
6917
  const references = sourceReferenceSummary?.dependencies ?? [];
6918
+ if (sourceDependenciesExpanded)
6919
+ readingText = references.map((dependency) => `${dependency.category} · ${dependency.pluginId ?? dependency.sourceId ?? '身份未知'} · ${dependency.platform ? platformLabel(dependency.platform) : ''}${dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''}`).join('\n');
7029
6920
  const referenceOffset = Math.min(batchTargetOffset, Math.max(0, references.length - 1));
7030
6921
  const visibleReferences = references.slice(referenceOffset, referenceOffset + referenceLimit);
7031
6922
  const hiddenReferences = Math.max(0, references.length - referenceOffset - visibleReferences.length);
7032
6923
  if (sourceDependenciesExpanded && references.length > referenceLimit) {
7033
- actions.unshift(...paginationActions({
7034
- offset: referenceOffset,
7035
- total: references.length,
7036
- pageRows: referenceLimit,
7037
- move: (direction) => moveBatchTargetPage(references.length, referenceLimit, direction),
7038
- }));
7039
6924
  referenceDetailPagination = { total: references.length, pageRows: referenceLimit };
7040
- help = 'p/n 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6925
+ help = '←→ 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7041
6926
  }
7042
6927
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: sourceDependenciesExpanded
7043
6928
  ? referenceOffset > 0 ? `引用内容 · ↑ 前面还有 ${referenceOffset} 项` : '引用内容'
@@ -7053,7 +6938,7 @@ export function ConsoleAppV4(props) {
7053
6938
  : '当前状态检查没有发现需要处理的安装、来源或设置问题。';
7054
6939
  if (issues.length === 0) {
7055
6940
  actions = [
7056
- { label: '返回首页', run: () => requestSessionNavigation('home') },
6941
+ { role: 'navigation', label: '返回首页', run: () => requestSessionNavigation('home') },
7057
6942
  { label: '浏览插件', run: () => go({ name: 'pluginList', mode: 'browse' }) },
7058
6943
  ];
7059
6944
  }
@@ -7073,8 +6958,18 @@ export function ConsoleAppV4(props) {
7073
6958
  : go({ name: 'issueDetail', issueId: entry.issueId }),
7074
6959
  }));
7075
6960
  }
6961
+ const reminder = snapshot.projectIntentReminder;
6962
+ const pendingProjectTargets = (reminder?.actionableTargets.length ?? 0)
6963
+ + (reminder?.blockedTargets.length ?? 0) + (reminder?.unconfirmedTargets.length ?? 0);
6964
+ if (snapshot.projectIntentIssue || pendingProjectTargets > 0) {
6965
+ title = '需要处理';
6966
+ description = '查看项目插件、安装、来源或设置问题。';
6967
+ actions.unshift({ actionId: 'project-status', label: '项目插件',
6968
+ description: snapshot.projectIntentIssue ? '项目声明需要检查' : `${pendingProjectTargets} 个位置待处理或未确认`,
6969
+ status: 'warning', run: () => go({ name: 'projectStatus' }) });
6970
+ }
7076
6971
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
7077
- if (issues.length === 0) {
6972
+ if (issues.length === 0 || snapshot.projectIntentIssue || pendingProjectTargets > 0) {
7078
6973
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
7079
6974
  }
7080
6975
  else {
@@ -7144,35 +7039,26 @@ export function ConsoleAppV4(props) {
7144
7039
  || entry.id === 'keepUserScope' || entry.id === 'keepProjectScope' ? 'error' : undefined,
7145
7040
  run: () => runIssueAction(entry),
7146
7041
  }));
7147
- const issueDetailRows = diagnosticDetailRowCount(issue.diagnostic);
7042
+ const issueDetailRows = diagnosticDetailLines(issue.diagnostic, size.columns - 6).length;
7043
+ if (showTechnical)
7044
+ readingText = diagnosticDetailText(issue.diagnostic);
7148
7045
  const issueDetailPageRows = policy.mode === 'compact'
7149
7046
  ? 1
7150
- : Math.max(1, responsiveBudget(5, 10) - 2);
7151
- const issuePaginationActions = showTechnical && issueDetailRows > issueDetailPageRows
7152
- ? paginationActions({
7153
- offset: batchTargetOffset,
7154
- total: issueDetailRows,
7155
- pageRows: issueDetailPageRows,
7156
- move: (direction) => moveBatchTargetPage(issueDetailRows, issueDetailPageRows, direction),
7157
- })
7158
- : [];
7159
- actions.push(...issuePaginationActions);
7160
- if (issuePaginationActions.length > 0) {
7047
+ : Math.max(1, responsiveBudget(1, 4));
7048
+ if (showTechnical && issueDetailRows > issueDetailPageRows) {
7161
7049
  referenceDetailPagination = { total: issueDetailRows, pageRows: issueDetailPageRows };
7162
- help = 'p/n 查看全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7050
+ help = '←→ 或 PgUp/PgDn 阅读全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7163
7051
  }
7164
7052
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
7165
7053
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
7166
- const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? issueDetailPageRows : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
7054
+ const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, columns: size.columns, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? issueDetailPageRows : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
7167
7055
  || issue.category === 'scopeConflict' });
7168
7056
  const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
7169
- const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
7170
- ? {
7171
- offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)),
7172
- limit: 2,
7173
- }
7174
- : {};
7175
- if (issue.category === 'dependencyStillRequired' && issuePlugin) {
7057
+ if (showTechnical) {
7058
+ const technicalActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(issueDetailPageRows + 6));
7059
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: technicalActionViewport.offset, limit: technicalActionViewport.limit })] });
7060
+ }
7061
+ else if (issue.category === 'dependencyStillRequired' && issuePlugin) {
7176
7062
  const blockingDependents = issue.platform && issue.scope
7177
7063
  ? props.controller.getBlockingDependents(issuePlugin.pluginId, issue.platform, issue.scope)
7178
7064
  : issueOverview?.dependents ?? [];
@@ -7182,7 +7068,6 @@ export function ConsoleAppV4(props) {
7182
7068
  { label: '查看完整依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
7183
7069
  { label: '返回插件详情', run: () => go({ name: 'pluginDetail', pluginId: issuePlugin.pluginId }) },
7184
7070
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
7185
- ...issuePaginationActions,
7186
7071
  ];
7187
7072
  const dependencyBlockedViewport = policy.mode === 'compact' && showTechnical
7188
7073
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -7197,7 +7082,7 @@ export function ConsoleAppV4(props) {
7197
7082
  issue.dependencyPlatform ? platformLabel(issue.dependencyPlatform) : undefined,
7198
7083
  issue.dependencyScope ? scopeLabel(issue.dependencyScope) : undefined,
7199
7084
  ].filter((entry) => Boolean(entry)).join(' · ');
7200
- content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', ...compactTechnicalActionViewport })] });
7085
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7201
7086
  }
7202
7087
  else if (issue.category === 'dependencyTargetUnsupported' && issuePlugin) {
7203
7088
  const issueScope = issue.scope ?? scope;
@@ -7223,7 +7108,6 @@ export function ConsoleAppV4(props) {
7223
7108
  } },
7224
7109
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
7225
7110
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
7226
- ...issuePaginationActions,
7227
7111
  ];
7228
7112
  const unsupportedTargetViewport = policy.mode === 'compact' && showTechnical
7229
7113
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -7269,7 +7153,7 @@ export function ConsoleAppV4(props) {
7269
7153
  status: operationHistoryState(record).status,
7270
7154
  run: () => go({ name: 'operationDiagnostic', recordId: record.recordId }),
7271
7155
  })),
7272
- { label: '返回上一页', run: back },
7156
+ { role: 'navigation', label: '返回上一页', run: back },
7273
7157
  ];
7274
7158
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
7275
7159
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: records.some((record) => record.state !== 'succeeded') ? 'warning' : 'success', title: records.length === 0 ? '没有可回看的安全结果' : '操作结果与问题', description: "\u9009\u62E9\u4E00\u9879\u53EF\u67E5\u770B\u5B8C\u6574\u5B89\u5168\u8BE6\u60C5\u6216\u62A5\u544A\u9884\u89C8\u3002", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
@@ -7277,30 +7161,31 @@ export function ConsoleAppV4(props) {
7277
7161
  else if (route.name === 'operationDiagnostic') {
7278
7162
  const detail = props.controller.getOperationDiagnostic(route.recordId);
7279
7163
  breadcrumb = '操作记录 / 技术详情';
7280
- context = 'p 上一页 · n 下一页';
7164
+ context = '安全详情';
7281
7165
  title = detail ? '安全技术详情' : '操作记录已不可用';
7282
7166
  description = detail
7283
7167
  ? '可滚动查看所有目标、阶段、代码和已保留的安全原因。'
7284
7168
  : '这项会话记录已经不再可用。';
7285
7169
  const preview = detail ? props.controller.previewOperationReport(route.recordId) : undefined;
7286
- const detailLineLimit = 2;
7287
- const detailLines = detail ? operationDiagnosticLineCount(detail) : 0;
7288
- const lastDetailOffset = Math.max(0, detailLines - detailLineLimit);
7170
+ const detailLineLimit = operationDetailPageRows;
7171
+ if (detail) {
7172
+ readingText = operationDiagnosticText(detail);
7173
+ const total = operationDiagnosticLineCount(detail, size.columns);
7174
+ context = `${Math.min(total, scrollOffset + 1)}–${Math.min(total, scrollOffset + detailLineLimit)} / ${total} 行`;
7175
+ }
7289
7176
  actions = detail ? [
7290
7177
  preview
7291
7178
  ? { label: '预览安全报告', description: '确认内容后选择本地保存位置', run: () => go({ name: 'operationReportPreview', recordId: route.recordId }) }
7292
7179
  : { label: '报告当前不可用', disabled: '无法生成这项记录的报告' },
7293
- { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - detailLineLimit)) },
7294
- { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastDetailOffset, value + detailLineLimit)) },
7295
- { label: '返回操作记录', run: back },
7296
- ] : [{ label: '返回操作记录', run: back }];
7180
+ { role: 'navigation', label: '返回操作记录', run: back },
7181
+ ] : [{ role: 'navigation', label: '返回操作记录', run: back }];
7297
7182
  const diagnosticActionViewport = actionViewport(actions, policy.mode === 'compact', focus, 0, policy.mode === 'compact' ? 2 : 3);
7298
- content = detail ? _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationDiagnosticScreen, { detail: detail, compact: policy.mode === 'compact', offset: scrollOffset, limit: detailLineLimit }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: diagnosticActionViewport.offset, limit: diagnosticActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u8BB0\u5F55\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u64CD\u4F5C\u8BB0\u5F55\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u7ED3\u679C\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7183
+ content = detail ? _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationDiagnosticScreen, { detail: detail, compact: policy.mode === 'compact', columns: size.columns, offset: scrollOffset, limit: detailLineLimit }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: diagnosticActionViewport.offset, limit: diagnosticActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u8BB0\u5F55\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u64CD\u4F5C\u8BB0\u5F55\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u7ED3\u679C\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7299
7184
  }
7300
7185
  else if (route.name === 'operationReportPreview') {
7301
7186
  const preview = props.controller.previewOperationReport(route.recordId);
7302
7187
  breadcrumb = '操作记录 / 报告预览';
7303
- context = 'p 上一页 · n 下一页';
7188
+ context = '安全报告';
7304
7189
  title = reportSaveMode ? '选择报告保存位置' : preview ? '安全报告预览' : '报告已不可用';
7305
7190
  description = reportSaveMode
7306
7191
  ? '输入一个新的本地文件路径,然后按 Enter 保存。'
@@ -7312,9 +7197,10 @@ export function ConsoleAppV4(props) {
7312
7197
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { title: "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84", focused: true, compact: policy.mode === 'compact', children: _jsx(InputDisplay, { value: reportSavePath, placeholder: "/tmp/aw-operation-report.md", columns: Math.max(12, size.columns - 10) }) }), reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(StaticRow, { title: "\u4EC5\u4FDD\u5B58\u5F53\u524D\u9884\u89C8\u4E2D\u7684\u5B89\u5168\u5185\u5BB9\u3002", status: "muted", compact: true })] });
7313
7198
  }
7314
7199
  else if (preview) {
7315
- const reportLineLimit = 2;
7316
- const reportLines = operationReportLineCount(preview);
7317
- const lastReportOffset = Math.max(0, reportLines - reportLineLimit);
7200
+ readingText = preview.content;
7201
+ const reportLineLimit = operationReportPageRows;
7202
+ const total = operationReportLineCount(preview, size.columns);
7203
+ context = `${Math.min(total, scrollOffset + 1)}–${Math.min(total, scrollOffset + reportLineLimit)} / ${total} 行`;
7318
7204
  actions = [
7319
7205
  { label: '选择本地保存位置', description: '保存前输入明确的文件路径', run: () => {
7320
7206
  setReportSaveMode(true);
@@ -7329,18 +7215,16 @@ export function ConsoleAppV4(props) {
7329
7215
  setReportSaveError('报告未保存。请检查保存位置后重试。');
7330
7216
  });
7331
7217
  } },
7332
- { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - reportLineLimit)) },
7333
- { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastReportOffset, value + reportLineLimit)) },
7334
- { label: '返回技术详情', run: back },
7218
+ { role: 'navigation', label: '返回技术详情', run: back },
7335
7219
  ];
7336
7220
  const reportFeedbackVisible = Boolean(reportSaveResult || reportSaveError);
7337
7221
  const compactReportActions = policy.mode === 'compact' || reportFeedbackVisible;
7338
7222
  const reportActionLimit = policy.mode === 'compact' || reportFeedbackVisible ? 2 : 3;
7339
7223
  const reportActionOffset = Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - reportActionLimit));
7340
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: compactReportActions, offset: reportActionOffset, limit: reportActionLimit })] });
7224
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', columns: size.columns, offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: compactReportActions, offset: reportActionOffset, limit: reportActionLimit })] });
7341
7225
  }
7342
7226
  else {
7343
- actions = [{ label: '返回技术详情', run: back }];
7227
+ actions = [{ role: 'navigation', label: '返回技术详情', run: back }];
7344
7228
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u62A5\u544A\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u6280\u672F\u8BE6\u60C5\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u8BB0\u5F55\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7345
7229
  }
7346
7230
  }
@@ -7395,6 +7279,17 @@ export function ConsoleAppV4(props) {
7395
7279
  }
7396
7280
  else if (route.name === 'review' && snapshot.activeReview) {
7397
7281
  const review = snapshot.activeReview;
7282
+ readingText = [
7283
+ `${review.title} · ${review.subject.name}`,
7284
+ review.description,
7285
+ ...(review.equivalentCommand && !review.requiresConfirmation
7286
+ ? [`等价命令:${review.equivalentCommand}`] : []),
7287
+ ...(review.rootSummary ?? []).map((root) => `${root.pluginId}${root.version ? ` · ${root.version}` : ''} · ${root.targets.map((target) => `${target.platform}/${target.scope}`).join('、')}`),
7288
+ ...review.changes.map((change) => `${change.sourceId ? `${change.sourceId} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`),
7289
+ ...(review.technicalDetails ?? []),
7290
+ ...(review.confirmationDetails ?? []),
7291
+ ...(review.sharedImpact.warning ? [review.sharedImpact.warning] : []),
7292
+ ].join('\n');
7398
7293
  const technicalDetails = (review.technicalDetails ?? []).flatMap((detail) => review.operationKind === 'catalogGeneration' && detail.startsWith('引用影响:')
7399
7294
  ? wrapConsoleText(detail, Math.max(20, size.columns - 10))
7400
7295
  : [detail]);
@@ -7604,12 +7499,6 @@ export function ConsoleAppV4(props) {
7604
7499
  : userReviewChanges.length;
7605
7500
  const reviewPageRows = Math.max(1, responsiveBudget(6, 10) - actions.length - 2);
7606
7501
  if (reviewPaginationTotal > reviewPageRows) {
7607
- actions.push(...paginationActions({
7608
- offset: batchTargetOffset,
7609
- total: reviewPaginationTotal,
7610
- pageRows: 1,
7611
- move: (direction) => moveBatchTargetPage(reviewPaginationTotal, 1, direction),
7612
- }));
7613
7502
  referenceDetailPagination = { total: reviewPaginationTotal, pageRows: 1 };
7614
7503
  }
7615
7504
  const reviewActionViewport = actionViewport(actions, true, focus, scrollOffset, responsiveBudget(2, 6));
@@ -7698,6 +7587,18 @@ export function ConsoleAppV4(props) {
7698
7587
  ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: false }, `${change.title}:${index}`);
7699
7588
  });
7700
7589
  const showEquivalentCommand = Boolean(review.equivalentCommand && showTechnical && !review.requiresConfirmation);
7590
+ const reviewRootPluginIds = taskReview || batchReview
7591
+ ? review.rootSummary?.map((root) => root.pluginId) ?? [] : [];
7592
+ const reviewRootSummary = reviewRootPluginIds.length === 1
7593
+ ? props.controller.getPluginOverview(reviewRootPluginIds[0])?.displayName
7594
+ ?? reviewRootPluginIds[0].split('/').at(-1)
7595
+ : reviewRootPluginIds.length > 1 ? `${reviewRootPluginIds.length} 个` : undefined;
7596
+ const reviewVersionSummary = review.rootSummary && review.rootSummary.length > 0
7597
+ ? [...new Set(review.rootSummary.map((root) => root.version ?? '未知'))].join('、')
7598
+ : undefined;
7599
+ const reviewRangeSummary = review.rootSummary && review.rootSummary.length > 0
7600
+ ? [...new Set(review.rootSummary.flatMap((root) => root.targets.map((target) => `${target.platform === 'both' ? 'Codex/Claude Code' : platformLabel(target.platform)}/${scopeLabel(target.scope)}`)))].join('、')
7601
+ : undefined;
7701
7602
  const installDependencyTargets = installReviewPluginId
7702
7603
  ? snapshot.installPlacementDraft?.targets ?? [] : [];
7703
7604
  const installDependencyPluginIds = new Set(installDependencyTargets.map((target) => target.pluginId));
@@ -7713,7 +7614,7 @@ export function ConsoleAppV4(props) {
7713
7614
  && (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
7714
7615
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7715
7616
  }
7716
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7617
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: reviewRootSummary ? `本次插件 · ${reviewRootSummary}` : review.subject.name, meta: reviewRootSummary ? `版本 ${reviewVersionSummary} · 范围 ${reviewRangeSummary || '未知'}` : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7717
7618
  }
7718
7619
  else if (route.name === 'progress') {
7719
7620
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -7796,6 +7697,11 @@ export function ConsoleAppV4(props) {
7796
7697
  : entry.state === 'skipped' ? 3 : 4;
7797
7698
  return priority(left) - priority(right);
7798
7699
  });
7700
+ readingText = [
7701
+ title,
7702
+ ...prioritizedProgressTargets.map((entry) => `${entry.pluginId} · ${platformLabel(entry.platform)} · ${scopeLabel(entry.scope)} · ${progressLabel(entry).label}`),
7703
+ ...snapshot.progress.messages.map(productResultMessage),
7704
+ ].join('\n');
7799
7705
  const progressStepRows = policy.mode === 'compact' ? 4 : 5;
7800
7706
  const progressTargetAndMessageBudget = pageActionBudget()
7801
7707
  - 2
@@ -7809,7 +7715,10 @@ export function ConsoleAppV4(props) {
7809
7715
  const progressTargetBudget = progressTargetAndMessageBudget - dependencyMessageLimit;
7810
7716
  const progressTargetLimit = prefixRowLimit(prioritizedProgressTargets.map(() => policy.mode === 'compact' ? 1 : 2), progressTargetBudget);
7811
7717
  help = prioritizedProgressTargets.length > progressTargetLimit
7812
- ? 'PgUp/PgDn 查看全部目标 · Ctrl-C 完成后退出' : 'Ctrl-C 完成后退出';
7718
+ ? '←→ 翻页 · [y] 复制全文 · Ctrl-C 完成后退出'
7719
+ : '[y] 复制全文 · Ctrl-C 完成后退出';
7720
+ if (copyFeedback?.routeKey === routeKey)
7721
+ description = copyFeedback.message;
7813
7722
  const progressTargetOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedProgressTargets.length - 1));
7814
7723
  const visibleProgressTargets = prioritizedProgressTargets.slice(progressTargetOffset, progressTargetOffset + progressTargetLimit);
7815
7724
  const hiddenProgressTargets = Math.max(0, snapshot.progressTargets.length - progressTargetOffset - visibleProgressTargets.length);
@@ -8117,11 +8026,20 @@ export function ConsoleAppV4(props) {
8117
8026
  : [];
8118
8027
  const safeCleanupKeys = new Set(props.controller.listPluginWriteBatchTargets('cleanup', [...new Set(failedUninstallTargets.map((target) => target.pluginId))]).map(batchTargetKey));
8119
8028
  const failedCleanupTargets = failedUninstallTargets.filter((target) => safeCleanupKeys.has(batchTargetKey(target)));
8029
+ const sourceRecoveryRoute = 'returnRoute' in result && result.returnRoute?.name === 'sourceDetail'
8030
+ ? result.returnRoute : undefined;
8031
+ const resumeConfigSourceDraft = operationKind === 'catalogGeneration'
8032
+ && sourceRecoveryRoute !== undefined
8033
+ && dirty
8034
+ && draftOwner?.startsWith(`${sourceRecoveryRoute.sourceId}/`)
8035
+ && props.controller.externalDrafts.some((draft) => draft.kind === 'configDraft' && draft.owner === draftOwner);
8120
8036
  actions = storeOperation
8121
8037
  ? [
8122
- { label: '返回存储概览', run: () => leaveResult({ name: 'catalogStoreOverview' }) },
8123
- { label: '查看本次记录', run: () => result.operationRecordId
8124
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8038
+ { role: 'navigation', label: '返回存储概览', run: () => leaveResult({ name: 'catalogStoreOverview' }) },
8039
+ { label: '查看本次记录', run: () => {
8040
+ if (result.operationRecordId)
8041
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8042
+ } },
8125
8043
  ]
8126
8044
  : taskOperation
8127
8045
  ? [
@@ -8146,7 +8064,8 @@ export function ConsoleAppV4(props) {
8146
8064
  status: 'warning',
8147
8065
  run: () => leaveResult(taskReturnRoute),
8148
8066
  }] : []),
8149
- ...(!nestedTaskReturn ? [{ label: '完成,返回我的插件', run: () => {
8067
+ ...(!nestedTaskReturn ? [{ role: result.state === 'succeeded' ? 'navigation' : undefined,
8068
+ label: '完成,返回我的插件', run: () => {
8150
8069
  if (taskBatchOrigin?.owner === 'batch-write'
8151
8070
  && taskBatchOrigin.flowId === batchDraft?.flowId) {
8152
8071
  setBatchDraft(undefined);
@@ -8154,8 +8073,10 @@ export function ConsoleAppV4(props) {
8154
8073
  }
8155
8074
  leaveResult({ name: 'pluginList', mode: 'installed' });
8156
8075
  } }] : []),
8157
- { label: '查看本次记录', run: () => result.operationRecordId
8158
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8076
+ { label: '查看本次记录', run: () => {
8077
+ if (result.operationRecordId)
8078
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8079
+ } },
8159
8080
  ]
8160
8081
  : batchOperation
8161
8082
  ? [
@@ -8168,9 +8089,12 @@ export function ConsoleAppV4(props) {
8168
8089
  setScrollOffset(0);
8169
8090
  },
8170
8091
  }] : []),
8171
- { label: '返回我的插件', run: () => leaveResult() },
8172
- { label: '查看本次记录', run: () => result.operationRecordId
8173
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8092
+ { role: result.state === 'succeeded' ? 'navigation' : undefined,
8093
+ label: '返回我的插件', run: () => leaveResult() },
8094
+ { label: '查看本次记录', run: () => {
8095
+ if (result.operationRecordId)
8096
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8097
+ } },
8174
8098
  ]
8175
8099
  : portableOperation
8176
8100
  ? blocked || partial ? [
@@ -8220,7 +8144,7 @@ export function ConsoleAppV4(props) {
8220
8144
  { label: portableReturnLabel, run: () => { if (pluginId)
8221
8145
  props.controller.resetPortableFlowToPreview(pluginId); } },
8222
8146
  ] : [
8223
- { label: portableReturnLabel, run: () => {
8147
+ { role: 'navigation', label: portableReturnLabel, run: () => {
8224
8148
  props.controller.discardPortableDraft();
8225
8149
  leaveResult();
8226
8150
  } },
@@ -8244,15 +8168,20 @@ export function ConsoleAppV4(props) {
8244
8168
  { label: '重新检查问题', run: () => leaveResult({ name: 'issueList' }) },
8245
8169
  ]
8246
8170
  : [
8247
- { label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
8248
- { label: '返回首页', run: () => requestSessionNavigation('home') },
8171
+ { role: 'navigation', label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
8172
+ { role: 'navigation', label: '返回首页', run: () => requestSessionNavigation('home') },
8249
8173
  ]
8250
8174
  : sourceOperation
8251
8175
  ? [{
8252
8176
  label: !blocked && 'returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask'
8253
- ? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试' : '查看来源',
8177
+ ? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试'
8178
+ : resumeConfigSourceDraft ? '继续编辑未保存设置' : '查看来源',
8179
+ role: !blocked && !partial && !resumeConfigSourceDraft
8180
+ && !('returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask')
8181
+ ? 'navigation' : undefined,
8254
8182
  run: () => blocked ? props.controller.resumeResultDraft() : leaveResult(),
8255
- }, { label: '返回首页', run: () => requestSessionNavigation('home') }]
8183
+ }, { role: !blocked && !partial ? 'navigation' : undefined,
8184
+ label: '返回首页', run: () => requestSessionNavigation('home') }]
8256
8185
  : configOperation && partial
8257
8186
  ? [
8258
8187
  ...configRetryScopes.map((scope) => ({
@@ -8277,7 +8206,8 @@ export function ConsoleAppV4(props) {
8277
8206
  { label: '返回设置', run: () => props.controller.resumeResultDraft() },
8278
8207
  { label: '返回首页', run: () => requestSessionNavigation('home') },
8279
8208
  ]
8280
- : [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
8209
+ : [{ role: !partial && !blocked && !blockedDependencyFailure ? 'navigation' : undefined,
8210
+ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
8281
8211
  ? configOrigin.name === 'pluginPreview' ? '返回插件预览'
8282
8212
  : configOrigin.name === 'pluginWriteTask' ? '返回恢复任务' : '返回插件详情'
8283
8213
  : configOperation ? '查看插件'
@@ -8317,7 +8247,18 @@ export function ConsoleAppV4(props) {
8317
8247
  scope: failedTarget.scope,
8318
8248
  });
8319
8249
  },
8320
- }] : []), { label: '返回首页', run: () => requestSessionNavigation('home') }];
8250
+ }] : []), { role: !partial && !blocked ? 'navigation' : undefined,
8251
+ label: '返回首页', run: () => requestSessionNavigation('home') }];
8252
+ if (snapshot.canResumeInstallDraft) {
8253
+ actions.unshift({ label: '返回修改', run: async () => {
8254
+ props.controller.resumeResultDraft();
8255
+ if (props.controller.snapshot.router.current.route.name === 'dependencyPlacement') {
8256
+ await props.controller.prepareInstallDependencyPlacement();
8257
+ }
8258
+ setFocus(0);
8259
+ setScrollOffset(0);
8260
+ } });
8261
+ }
8321
8262
  if (failedCleanupTargets.length > 0) {
8322
8263
  actions.unshift({
8323
8264
  label: '查看清理建议',
@@ -8428,23 +8369,36 @@ export function ConsoleAppV4(props) {
8428
8369
  ? wrapPortableField('materialization digest', portableReceipt.materializationDigest) : []),
8429
8370
  ...(resultSummary?.elapsedMs !== undefined ? [`elapsed · ${resultSummary.elapsedMs} ms`] : []),
8430
8371
  ] : ['当前结果已变化。请返回结果摘要。'];
8372
+ readingText = portableResultDetailOpen ? selectedPortablePair ? [
8373
+ `Skill · ${selectedPortablePair.skill}`,
8374
+ `Agent · ${selectedPortablePair.agent ?? '无法确认'}`,
8375
+ `状态 · ${portablePairStateLabel(selectedPortablePair)}`,
8376
+ ...(portableResultScope ? [`范围 · ${scopeLabel(portableResultScope)}`] : []),
8377
+ ...(portableReceipt?.recovery ? [`安全恢复 · ${portableReceipt.recovery}`] : []),
8378
+ ...(portableReceipt?.failureCode ? [`failure code · ${portableReceipt.failureCode}`] : []),
8379
+ ...(portableReceipt ? [`verification missing · ${portableReceipt.missing.length}`,
8380
+ `Skills CLI version · ${portableReceipt.skillsCliVersion}`,
8381
+ `materialization digest · ${portableReceipt.materializationDigest}`] : []),
8382
+ ...(resultSummary?.elapsedMs !== undefined ? [`elapsed · ${resultSummary.elapsedMs} ms`] : []),
8383
+ ].join('\n') : '当前结果已变化。请返回结果摘要。' : [
8384
+ resultSummary?.message ?? (blocked ? result.message : '操作结果'),
8385
+ ...(resultSummary?.messages ?? []).map(productResultMessage),
8386
+ ...(resultSummary?.changes ?? []),
8387
+ ...(resultSummary?.warnings ?? []),
8388
+ ...(resultSummary?.recoveryActions ?? []),
8389
+ ...prioritizedTargets.map((target) => `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)} · ${operationTargetStateLabel(target.state).label}`),
8390
+ ...prioritizedPortablePairs.map((pair) => `${pair.skill} · ${pair.agent ?? 'Agent 未确认'} · ${portablePairStateLabel(pair)}`),
8391
+ ...(taskSummary?.rootResults ?? []).map((root) => `${root.pluginId} · ${root.state}`),
8392
+ ...(resultSummary?.batch?.rootResults ?? []).map((root) => `${root.pluginId} · ${root.state}`),
8393
+ ].join('\n');
8431
8394
  const portableResultDetailPageRows = responsiveBudget(5, 10);
8432
8395
  const safePortableResultDetailOffset = Math.min(portableResultDetailOffset, Math.max(0, portableResultDetailLines.length - 1));
8433
8396
  const visiblePortableResultDetailLines = portableResultDetailLines.slice(safePortableResultDetailOffset, safePortableResultDetailOffset + portableResultDetailPageRows);
8434
8397
  if (portableOperation && portableResultDetailOpen) {
8435
8398
  title = '当前结果完整字段';
8436
8399
  description = '按行显示当前 Skill、Agent、状态和技术证据。';
8437
- help = 'p/n 阅读全部字段 · ↑↓ 移动 · Enter 选择 · Esc 返回结果摘要';
8438
- const detailPaginationActions = portableResultDetailLines.length > portableResultDetailPageRows
8439
- ? paginationActions({
8440
- offset: safePortableResultDetailOffset,
8441
- total: portableResultDetailLines.length,
8442
- pageRows: portableResultDetailPageRows,
8443
- move: (direction) => setPortableResultDetailOffset((value) => Math.min(Math.max(0, portableResultDetailLines.length - 1), Math.max(0, value + direction * portableResultDetailPageRows))),
8444
- })
8445
- : [];
8400
+ help = '←→ 阅读全部字段 · ↑↓ 移动 · Enter 选择 · Esc 返回结果摘要';
8446
8401
  actions = [
8447
- ...detailPaginationActions,
8448
8402
  { label: '返回结果摘要', run: () => {
8449
8403
  setPortableResultDetailOpen(false);
8450
8404
  setPortableResultDetailOffset(0);
@@ -8470,29 +8424,16 @@ export function ConsoleAppV4(props) {
8470
8424
  setScrollOffset(0);
8471
8425
  },
8472
8426
  } : undefined;
8473
- const pagination = resultPaginationTotal > 1 ? paginationActions({
8474
- offset: batchTargetOffset,
8475
- total: resultPaginationTotal,
8476
- pageRows: resultPageRows,
8477
- move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8478
- }) : [];
8479
8427
  actions = [
8480
8428
  ...actions.slice(0, 1),
8481
- ...pagination,
8482
8429
  ...(detailAction ? [detailAction] : []),
8483
8430
  ...actions.slice(1),
8484
8431
  ];
8485
- if (pagination.length > 0) {
8432
+ if (resultPaginationTotal > resultPageRows) {
8486
8433
  referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8487
8434
  }
8488
8435
  }
8489
8436
  else if (resultPaginationTotal > 1) {
8490
- actions.push(...paginationActions({
8491
- offset: batchTargetOffset,
8492
- total: resultPaginationTotal,
8493
- pageRows: resultPageRows,
8494
- move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8495
- }));
8496
8437
  referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8497
8438
  }
8498
8439
  const portableTechnicalFieldCount = portableReceipt
@@ -8586,47 +8527,70 @@ export function ConsoleAppV4(props) {
8586
8527
  const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
8587
8528
  return _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`);
8588
8529
  });
8589
- const taskLocationRows = taskRootResults.flatMap((root) => {
8590
- const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
8591
- return requestedTargets.map((target) => {
8592
- const key = [target.pluginId, target.platform, target.scope].join('\0');
8530
+ const taskLocationItems = taskRootResults.flatMap((root) => {
8531
+ return root.targets.map((target) => {
8532
+ const targetPluginId = target.pluginId ?? root.pluginId;
8533
+ const key = [targetPluginId, target.platform, target.scope].join('\0');
8593
8534
  const blockedTarget = blockedTaskTargets.get(key);
8594
- const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
8595
- && entry.scope === target.scope);
8596
8535
  const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
8597
- : targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
8598
- : targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
8599
- : targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
8536
+ : target.state === 'completed' ? { label: '完成', status: 'success' }
8537
+ : target.state === 'kept' ? { label: '无需更改', status: 'success' }
8538
+ : target.state === 'failed' ? { label: '失败', status: 'error' }
8600
8539
  : { label: '未开始', status: 'muted' };
8601
- return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
8540
+ const relation = target.relation === 'direct' ? '直接依赖'
8541
+ : target.relation === 'transitive' ? '间接依赖' : '所选插件位置';
8542
+ return {
8543
+ key: `target:${root.pluginId}:${key}`,
8544
+ title: `${root.pluginId}${targetPluginId === root.pluginId ? '' : ` → ${targetPluginId}`} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
8545
+ description: blockedTarget
8602
8546
  ? pluginWriteTaskBlockLabel(blockedTarget.reason)
8603
- : targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
8547
+ : `${relation}${target.errorCode ? ` · ${target.errorCode}` : ''}`,
8548
+ meta: `${targetState.label}${target.errorCode ? ` · ${target.errorCode}` : ''}`,
8549
+ status: targetState.status,
8550
+ compact: !blockedTarget,
8551
+ };
8604
8552
  });
8605
8553
  });
8606
- const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
8554
+ const taskLocationRows = taskLocationItems.map(({ key, ...item }) => _jsx(ResultRow, { ...item }, key));
8555
+ if (taskOperation)
8556
+ readingText = [
8557
+ resultSummary?.message ?? (blocked ? result.message : '操作结果'),
8558
+ ...(resultSummary?.messages ?? []).map(productResultMessage),
8559
+ ...(resultSummary?.changes ?? []),
8560
+ ...(resultSummary?.warnings ?? []),
8561
+ ...(resultSummary?.recoveryActions ?? []),
8562
+ ...taskRootResults.map((root) => `${root.pluginId} · ${taskRootState(root.state).label}`),
8563
+ ...taskLocationItems.flatMap((item) => [`${item.title} · ${item.meta}`, item.description]),
8564
+ ].join('\n');
8565
+ const batchPluginResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
8607
8566
  const rootState = taskRootState(root.state);
8608
8567
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
8609
8568
  ?? root.pluginId.split('/').at(-1);
8610
8569
  return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
8611
- }).concat(prioritizedTargets.map((target) => {
8570
+ });
8571
+ const batchLocationResultRows = prioritizedTargets.map((target) => {
8612
8572
  const state = operationTargetStateLabel(target.state);
8613
8573
  const relation = target.relation === 'requested' ? '你选择的插件'
8614
8574
  : target.relation === 'direct' ? '直接依赖'
8615
8575
  : target.relation === 'transitive' ? '间接依赖' : undefined;
8616
8576
  return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
8617
- }));
8577
+ });
8618
8578
  const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
8619
- const rootResultRows = taskOperation
8620
- ? [...taskSelectedPluginRows, ...taskLocationRows]
8621
- : batchResultRows;
8622
- const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
8623
- const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
8579
+ const pluginResultRows = taskOperation ? taskSelectedPluginRows : batchPluginResultRows;
8580
+ const locationResultRows = taskOperation ? taskLocationRows : batchLocationResultRows;
8581
+ const combinedResultRows = [...pluginResultRows, ...locationResultRows];
8582
+ const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, combinedResultRows.length - 1));
8583
+ const visiblePluginResultRows = taskResultOffset < pluginResultRows.length
8584
+ ? pluginResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit) : [];
8585
+ const visibleLocationStart = Math.max(0, taskResultOffset - pluginResultRows.length);
8586
+ const remainingResultRows = taskResultLimit - visiblePluginResultRows.length;
8587
+ const visibleLocationResultRows = locationResultRows.slice(visibleLocationStart, visibleLocationStart + remainingResultRows);
8624
8588
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
8625
8589
  || prioritizedPortablePairs.length > portableSkillLimit) {
8626
- help = 'p/n 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8590
+ help = '←→ 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8627
8591
  }
8628
- content = taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: "\u6240\u9009\u63D2\u4EF6\u7ED3\u679C", children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
8629
- ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableRecheckBusy
8592
+ content = taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visiblePluginResultRows.length > 0 ? _jsx(ResultCard, { status: resultStatus, title: "\u63D2\u4EF6\u7ED3\u679C", meta: `${pluginResultRows.length} 个插件`, children: visiblePluginResultRows }) : null, visibleLocationResultRows.length > 0 ? _jsx(ResultCard, { status: resultStatus, title: "\u4F7F\u7528\u4F4D\u7F6E\u4E0E\u6240\u9700\u63D2\u4EF6", meta: `${locationResultRows.length} 项`, children: visibleLocationResultRows }) : null, combinedResultRows.length > taskResultOffset + visiblePluginResultRows.length + visibleLocationResultRows.length
8593
+ ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${combinedResultRows.length - taskResultOffset - visiblePluginResultRows.length - visibleLocationResultRows.length} 项`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableRecheckBusy
8630
8594
  ? _jsx(ProgressRow, { title: "\u6B63\u5728\u91CD\u65B0\u68C0\u67E5\u7ED3\u679C", description: "\u53EA\u8BFB\u53D6 Skills CLI \u5F53\u524D\u72B6\u6001\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5", state: "active", compact: policy.mode === 'compact' })
8631
8595
  : null, portableResultDetailOpen ? _jsxs(_Fragment, { children: [safePortableResultDetailOffset > 0
8632
8596
  ? _jsx(StaticRow, { title: `↑ 前面还有 ${safePortableResultDetailOffset} 行`, status: "muted", compact: true }) : null, visiblePortableResultDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${safePortableResultDetailOffset + index}:${line}`)), safePortableResultDetailOffset + visiblePortableResultDetailLines.length
@@ -8653,7 +8617,6 @@ export function ConsoleAppV4(props) {
8653
8617
  ? '这里显示已验证目录包声明的组成,不读取或展示完整操作指令。'
8654
8618
  : '当前没有可安全读取的目录内容。';
8655
8619
  const contentNavigationActions = [
8656
- { label: '返回插件详情', run: back },
8657
8620
  { label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
8658
8621
  ];
8659
8622
  const contentTypeLabels = {
@@ -8665,41 +8628,27 @@ export function ConsoleAppV4(props) {
8665
8628
  const versionDiffers = contentAvailable
8666
8629
  && installedVersions.some((version) => version !== overview.pluginVersion);
8667
8630
  const items = overview?.items ?? [];
8668
- const pageRows = policy.mode === 'compact' ? 1 : responsiveBudget(1, 2);
8669
- const contentOffset = Math.min(batchTargetOffset, Math.max(0, items.length - 1));
8670
- const visibleItems = items.slice(contentOffset, contentOffset + pageRows);
8671
- if (items.length > pageRows) {
8672
- referenceDetailPagination = { total: items.length, pageRows };
8673
- actions = [
8674
- ...paginationActions({
8675
- offset: contentOffset,
8676
- total: items.length,
8677
- pageRows,
8678
- move: (direction) => moveBatchTargetPage(items.length, pageRows, direction),
8679
- }),
8680
- ...contentNavigationActions,
8681
- ];
8682
- context = `${Math.min(contentOffset + 1, items.length)}–${Math.min(contentOffset + visibleItems.length, items.length)} / ${items.length}`;
8683
- help = '↑↓ 操作 · Enter 选择 · p/n 翻页 · Esc 返回';
8684
- }
8685
- else {
8686
- actions = contentNavigationActions;
8687
- }
8688
- const contentViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(Math.min(items.length, pageRows) * 5 + 2));
8689
- content = _jsxs(Box, { flexDirection: "column", children: [contentAvailable ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u8BC1\u636E", meta: `插件 ${overview.pluginVersion} · 目录 ${overview.catalogVersion}`, compact: true }), _jsx(StaticRow, { title: installedVersions.length > 0
8690
- ? `已安装 ${installedVersions.join('、')}`
8691
- : '当前没有已确认的安装版本', description: versionDiffers
8692
- ? '以下是目录版本内容,不代表旧安装版本的内容。'
8693
- : '内容来自当前已验证的目录包。', status: versionDiffers ? 'warning' : 'info', compact: true })] }) : contentState === 'loading'
8631
+ const itemActions = items.map((item) => ({
8632
+ actionId: `content:${item.id}`,
8633
+ label: item.name,
8634
+ description: `${contentTypeLabels[item.type]} · ${item.tools.join('、') || '适用工具待确认'} · ${item.description ?? '目录没有提供简短描述'}`,
8635
+ status: item.description ? undefined : 'warning',
8636
+ }));
8637
+ actions = [...itemActions, ...contentNavigationActions];
8638
+ const fixedContentRows = contentAvailable ? 4 : 3;
8639
+ currentActionViewportLineBudget = pageActionBudget(fixedContentRows + (overview?.notices.length ?? 0));
8640
+ const contentViewport = actionViewport(actions, false, focus, scrollOffset, currentActionViewportLineBudget);
8641
+ if (items.length > contentViewport.limit) {
8642
+ context = `${items.length} 项内容 · 上下键滚动`;
8643
+ help = '↑↓ 阅读全部内容 · Enter 选择 · Esc 返回';
8644
+ }
8645
+ content = _jsxs(Box, { flexDirection: "column", children: [contentAvailable ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u76EE\u5F55\u7248\u672C", meta: `${overview.pluginVersion} · Catalog ${overview.catalogVersion}`, compact: true }), _jsx(StaticRow, { title: installedVersions.length > 0
8646
+ ? `安装版本 ${installedVersions.join('、')}`
8647
+ : '安装版本尚未确认', description: versionDiffers
8648
+ ? '以下清单属于目录版本,不代表旧安装版本的内容。'
8649
+ : '以下清单来自当前已验证的目录包。', status: versionDiffers ? 'warning' : 'info', compact: true })] }) : contentState === 'loading'
8694
8650
  ? _jsx(Callout, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u5185\u5BB9", description: "\u5B8C\u6210\u540E\u5C06\u5728\u5F53\u524D\u9875\u9762\u663E\u793A\u3002" })
8695
- : _jsx(Callout, { status: "warning", title: "\u5185\u5BB9\u6682\u4E0D\u53EF\u7528", description: "\u53EF\u4EE5\u67E5\u770B\u6280\u672F\u8BE6\u60C5\u6216\u6062\u590D\u63D2\u4EF6\u6765\u6E90\u540E\u91CD\u8BD5\u3002" }), visibleItems.map((item) => _jsxs(SectionCard, { title: item.name, compact: true, children: [_jsx(StaticRow, { title: `${contentTypeLabels[item.type]} · ${item.tools.join('、') || '适用工具待确认'}`, status: "muted", compact: true }), _jsx(StaticRow, { title: item.description ?? '目录没有提供简短描述', status: item.description ? 'muted' : 'warning', compact: true })] }, item.id)), overview?.notices.map((notice, index) => _jsx(StaticRow, { title: notice, status: "warning", compact: true }, `${index}:${notice}`)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: contentViewport.offset, limit: contentViewport.limit })] });
8696
- }
8697
- else if (route.name === 'pluginAbout' && plugin) {
8698
- breadcrumb = `${plugin.displayName} / 关于`;
8699
- title = plugin.displayName;
8700
- description = plugin.description;
8701
- actions = [{ label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) }, { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) }];
8702
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: plugin.version ?? '未知' }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceLabel }), _jsx(StaticRow, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", meta: plugin.supportedPlatforms.map(platformLabel).join('、') }), _jsx(StaticRow, { title: "\u914D\u7F6E", meta: `${plugin.configFieldCount} 个字段` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
8651
+ : _jsx(Callout, { status: "warning", title: "\u5185\u5BB9\u6682\u4E0D\u53EF\u7528", description: "\u53EF\u4EE5\u67E5\u770B\u6280\u672F\u8BE6\u60C5\u6216\u6062\u590D\u63D2\u4EF6\u6765\u6E90\u540E\u91CD\u8BD5\u3002" }), overview?.notices.map((notice, index) => _jsx(StaticRow, { title: notice, status: "warning", compact: true }, `${index}:${notice}`)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: false, offset: contentViewport.offset, limit: contentViewport.limit })] });
8703
8652
  }
8704
8653
  else if (route.name === 'pluginTechnical' && plugin) {
8705
8654
  breadcrumb = `${plugin.displayName} / 技术详情`;
@@ -8708,32 +8657,35 @@ export function ConsoleAppV4(props) {
8708
8657
  description = plugin.capabilityUnsupported
8709
8658
  ? '当前 AW CLI 不支持此插件所需 capability。'
8710
8659
  : '默认页面不会展示这些内部信息。';
8711
- actions = [{ label: '返回插件', run: back }];
8660
+ actions = [{ role: 'navigation', label: '返回插件', run: back }];
8712
8661
  const prioritizedComponents = [...plugin.technicalComponents].sort((left, right) => {
8713
8662
  const priority = (state) => state === 'error' ? 0
8714
8663
  : state === 'partial' ? 1 : state === 'absent' ? 2 : 3;
8715
8664
  return priority(left.state) - priority(right.state);
8716
8665
  });
8717
- const detailRowBudget = responsiveBudget(9, 15);
8718
- const capabilityRowCount = plugin.capabilityUnsupported ? 4 : 0;
8719
- const componentRowBudget = Math.max(1, detailRowBudget - 4 - plugin.installedPlacements.length - capabilityRowCount);
8720
- const componentLimit = prioritizedComponents.length > componentRowBudget
8721
- ? 1
8722
- : componentRowBudget;
8723
- const componentOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedComponents.length - 1));
8724
- const visibleComponents = prioritizedComponents.slice(componentOffset, componentOffset + componentLimit);
8725
- const hiddenComponents = Math.max(0, prioritizedComponents.length - componentOffset - visibleComponents.length);
8726
- if (prioritizedComponents.length > componentLimit) {
8727
- actions.push(...paginationActions({
8728
- offset: componentOffset,
8729
- total: prioritizedComponents.length,
8730
- pageRows: componentLimit,
8731
- move: (direction) => moveBatchTargetPage(prioritizedComponents.length, componentLimit, direction),
8732
- }));
8733
- referenceDetailPagination = { total: prioritizedComponents.length, pageRows: componentLimit };
8734
- help = 'p/n 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8666
+ const technicalFields = [
8667
+ `canonical pluginId · ${plugin.pluginId}`,
8668
+ `来源 · ${plugin.sourceId}`,
8669
+ ...prioritizedComponents.map((component) => `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${component.shared ? '平台共享' : scopeLabel(component.scope)}`),
8670
+ ...(plugin.capabilityUnsupported ? [
8671
+ 'failure code · awCapabilityUnsupported',
8672
+ 'capability 状态 · 当前 AW CLI 不支持',
8673
+ `缺少 capability · ${plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability'}`,
8674
+ `恢复方向 · ${plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。'}`,
8675
+ ] : []),
8676
+ ...plugin.installedPlacements.map((placement) => `${platformLabel(placement.platform)} target · ${placementStatus(placement).value}`),
8677
+ ];
8678
+ readingText = technicalFields.join('\n');
8679
+ const technicalLines = technicalFields.flatMap((field) => wrapDisplayText(field, size.columns - 6));
8680
+ const technicalPageRows = Math.max(1, pageActionBudget(7));
8681
+ const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
8682
+ const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
8683
+ if (technicalLines.length > technicalPageRows) {
8684
+ referenceDetailPagination = { total: technicalLines.length, pageRows: technicalPageRows };
8685
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8735
8686
  }
8736
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.capabilityUnsupported ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "failure code", meta: "awCapabilityUnsupported" }), _jsx(StaticRow, { title: "capability \u72B6\u6001", meta: "\u5F53\u524D AW CLI \u4E0D\u652F\u6301", status: "error" }), _jsx(StaticRow, { title: "\u7F3A\u5C11 capability", meta: plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability' }), _jsx(StaticRow, { title: "\u6062\u590D\u65B9\u5411", meta: plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。' })] }) : null, plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), componentOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${componentOffset} 个组件`, status: "muted", compact: true }) : null, visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${component.shared ? '平台共享' : scopeLabel(component.scope)}` }, `${component.platform}:${component.shared ? 'shared' : component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
8687
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [technicalOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${technicalOffset} 行`, status: "muted", compact: true }) : null, visibleTechnicalLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${technicalOffset + index}:${line}`)), technicalOffset + visibleTechnicalLines.length < technicalLines.length
8688
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
8737
8689
  }
8738
8690
  else {
8739
8691
  const missingPlugin = definition.entity === 'plugin' && plugin === undefined;
@@ -8759,13 +8711,12 @@ export function ConsoleAppV4(props) {
8759
8711
  case 'pluginContent':
8760
8712
  case 'pluginMore':
8761
8713
  case 'platform':
8762
- case 'pluginAbout':
8763
8714
  case 'pluginTechnical':
8764
8715
  case 'configLayer':
8765
8716
  unavailable = missingPlugin ? 'plugin' : 'flow';
8766
8717
  break;
8718
+ case 'portablePreparing':
8767
8719
  case 'portableSkillSelect':
8768
- case 'portableSkillSummary':
8769
8720
  case 'portableDependencies':
8770
8721
  case 'portableAgentMode':
8771
8722
  case 'portableAgentInput':
@@ -8833,6 +8784,51 @@ export function ConsoleAppV4(props) {
8833
8784
  : 'h/H 完成后导航'} · Esc 撤销等待 · ${help}`
8834
8785
  : `h 首页 · H 重载 · ${help}`;
8835
8786
  }
8787
+ const helpParts = help.split(' · ');
8788
+ const isRoutineKey = (part) => /^(↑|Enter|Space|Esc|Ctrl-C|←→)/u.test(part);
8789
+ help = [...helpParts.filter(isRoutineKey), ...helpParts.filter((part) => !isRoutineKey(part))].join(' · ');
8790
+ if (searchFocus)
8791
+ help = '←→ 光标 · ↑↓ 移动结果 · Enter/Esc 完成输入';
8792
+ else if (bottomActions.length > 0 && actions.length > 8)
8793
+ help += ' · a 下方操作';
8794
+ useEffect(() => {
8795
+ if ((bottomActions.length > 0 || route.name === 'issueList' || route.name === 'operationHistory')
8796
+ && focus >= actions.length && actions.length > 0)
8797
+ setFocus(actions.length - 1);
8798
+ else if (actions[focus]?.role === 'navigation')
8799
+ setFocus(moveFocus(actions, focus, 1));
8800
+ }, [routeKey, focus, actions.length, actions.map((action) => action.role ?? '').join(',')]);
8801
+ const listCandidates = route.name === 'pluginList'
8802
+ || route.name === 'batchSelection' && batchDraft?.stage === 'select'
8803
+ ? actions.filter((action) => action.pluginId !== undefined) : [];
8804
+ const listCandidateSignature = listCandidates.map((action) => action.pluginId).join('\0');
8805
+ useEffect(() => {
8806
+ const pluginId = listCandidates[focus]?.pluginId;
8807
+ if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
8808
+ && !batchDraft.showSelectionTools && pluginId) {
8809
+ batchSelectionAnchor.current = {
8810
+ frameId: snapshot.router.currentFrameId, pluginId, index: focus, scrollOffset,
8811
+ };
8812
+ }
8813
+ }, [snapshot.router.currentFrameId, route.name, batchDraft?.stage,
8814
+ batchDraft?.showSelectionTools, listCandidateSignature, focus, scrollOffset]);
8815
+ useEffect(() => {
8816
+ if (route.name !== 'pluginList' && !(route.name === 'batchSelection' && batchDraft?.stage === 'select')) {
8817
+ listFocusAnchor.current = undefined;
8818
+ return;
8819
+ }
8820
+ const previous = listFocusAnchor.current;
8821
+ let nextFocus = focus;
8822
+ if (previous?.frameId === snapshot.router.currentFrameId && previous.ids !== listCandidateSignature
8823
+ && previous.entityId) {
8824
+ const retained = listCandidates.findIndex((action) => action.pluginId === previous.entityId);
8825
+ nextFocus = retained >= 0 ? retained : Math.min(focus, Math.max(0, listCandidates.length - 1));
8826
+ if (nextFocus !== focus)
8827
+ setFocus(nextFocus);
8828
+ }
8829
+ listFocusAnchor.current = { frameId: snapshot.router.currentFrameId,
8830
+ ids: listCandidateSignature, entityId: listCandidates[nextFocus]?.pluginId };
8831
+ }, [snapshot.router.currentFrameId, route.name, batchDraft?.stage, listCandidateSignature, focus]);
8836
8832
  const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
8837
8833
  ? actions.map((action) => String(Boolean(action.disabled))).join(',')
8838
8834
  : '';
@@ -8895,7 +8891,7 @@ export function ConsoleAppV4(props) {
8895
8891
  };
8896
8892
  }, [dynamicActionSignature, routeKey]);
8897
8893
  const runAction = (action) => {
8898
- if (!action || action.disabled)
8894
+ if (!action || action.disabled || action.role === 'navigation')
8899
8895
  return;
8900
8896
  try {
8901
8897
  const pending = action.run?.();
@@ -9009,10 +9005,19 @@ export function ConsoleAppV4(props) {
9009
9005
  }
9010
9006
  if (busy) {
9011
9007
  const sessionNavigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: false });
9012
- if ((key.pageUp || key.pageDown) && route.name === 'progress') {
9008
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9009
+ && route.name === 'progress' && !overlay) {
9013
9010
  const total = snapshot.progressTargets.length;
9014
9011
  const pageRows = 1;
9015
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
9012
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp || key.leftArrow ? -pageRows : pageRows))));
9013
+ }
9014
+ else if (!overlay && input.toLowerCase() === 'y' && readingText !== undefined) {
9015
+ const currentRouteKey = routeKey;
9016
+ void copyToClipboard(readingText).then(() => {
9017
+ setCopyFeedback({ routeKey: currentRouteKey, message: '全文已复制' });
9018
+ }).catch(() => {
9019
+ setCopyFeedback({ routeKey: currentRouteKey, message: '复制失败,请重试' });
9020
+ });
9016
9021
  }
9017
9022
  else if (overlay === 'help') {
9018
9023
  if (key.escape || key.return || input.toLowerCase() === 'b' || input === '?') {
@@ -9118,31 +9123,31 @@ export function ConsoleAppV4(props) {
9118
9123
  closeConfirmation();
9119
9124
  requestSessionNavigation(navigation);
9120
9125
  }
9121
- else if ((key.pageUp || key.pageDown
9122
- || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9126
+ else if (input.toLowerCase() === 'y' && confirmationText !== undefined) {
9127
+ const currentRouteKey = `${routeKey}:confirmation`;
9128
+ void copyToClipboard(confirmationText).then(() => {
9129
+ setCopyFeedback({ routeKey: currentRouteKey, message: '全文已复制' });
9130
+ }).catch(() => {
9131
+ setCopyFeedback({ routeKey: currentRouteKey, message: '复制失败,请重试' });
9132
+ });
9133
+ }
9134
+ else if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9123
9135
  && confirmationPagination) {
9124
9136
  const warningCount = confirmationPagination.total;
9125
9137
  const warningLimit = confirmationPagination.pageRows;
9126
9138
  setOperationConfirmation((current) => current ? {
9127
9139
  ...current,
9128
9140
  warningOffset: Math.min(Math.max(0, warningCount - 1), Math.max(0, current.warningOffset
9129
- + (key.pageUp || input.toLowerCase() === 'p' ? -warningLimit : warningLimit))),
9141
+ + (key.pageUp || key.leftArrow ? -warningLimit : warningLimit))),
9130
9142
  } : current);
9131
9143
  }
9132
9144
  else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9133
- setOverlayFocus((value) => moveIndex(confirmationPagination ? 4 : 2, value, key.upArrow || key.leftArrow ? -1 : 1));
9145
+ setOverlayFocus((value) => moveIndex(2, value, key.upArrow || key.leftArrow ? -1 : 1));
9134
9146
  }
9135
9147
  else if (key.return) {
9136
9148
  if (overlayFocus === 0) {
9137
9149
  closeConfirmation();
9138
9150
  }
9139
- else if (confirmationPagination && (overlayFocus === 2 || overlayFocus === 3)) {
9140
- const direction = overlayFocus === 2 ? -1 : 1;
9141
- setOperationConfirmation((current) => current ? {
9142
- ...current,
9143
- warningOffset: Math.min(Math.max(0, confirmationPagination.total - 1), Math.max(0, current.warningOffset + direction * confirmationPagination.pageRows)),
9144
- } : current);
9145
- }
9146
9151
  else if (activeReview?.reviewToken !== operationConfirmation.reviewToken) {
9147
9152
  closeConfirmation();
9148
9153
  setActionError('确认内容已变化。请重新检查操作汇总。');
@@ -9194,35 +9199,6 @@ export function ConsoleAppV4(props) {
9194
9199
  }
9195
9200
  return;
9196
9201
  }
9197
- if (pluginListChooser) {
9198
- const sourceIds = ['all', ...props.controller.listSources().map((source) => source.id)];
9199
- const itemCount = pluginListChooser.kind === 'source' ? sourceIds.length : PLUGIN_FILTERS.length;
9200
- if (key.escape) {
9201
- setPluginListChooser(undefined);
9202
- }
9203
- else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9204
- const direction = key.upArrow || key.leftArrow ? -1 : 1;
9205
- setPluginListChooser((current) => current ? {
9206
- ...current,
9207
- focus: moveIndex(itemCount, current.focus, direction),
9208
- } : current);
9209
- }
9210
- else if (key.return) {
9211
- const selectedIndex = Math.min(pluginListChooser.focus, Math.max(0, itemCount - 1));
9212
- if (pluginListChooser.kind === 'source') {
9213
- const selectedSourceId = sourceIds[selectedIndex];
9214
- if (selectedSourceId)
9215
- choosePluginListSource?.(selectedSourceId);
9216
- }
9217
- else {
9218
- const selectedFilter = PLUGIN_FILTERS[selectedIndex];
9219
- if (selectedFilter)
9220
- choosePluginListFilter?.(selectedFilter);
9221
- }
9222
- setPluginListChooser(undefined);
9223
- }
9224
- return;
9225
- }
9226
9202
  if (overlay) {
9227
9203
  if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
9228
9204
  if (overlay === 'sessionNavigation')
@@ -9341,57 +9317,63 @@ export function ConsoleAppV4(props) {
9341
9317
  setReportSaveError(undefined);
9342
9318
  return;
9343
9319
  }
9344
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9320
+ if (input.toLowerCase() === 'y' && readingText !== undefined) {
9321
+ const currentRouteKey = routeKey;
9322
+ void copyToClipboard(readingText).then(() => {
9323
+ setCopyFeedback({ routeKey: currentRouteKey, message: '全文已复制' });
9324
+ }).catch(() => {
9325
+ setCopyFeedback({ routeKey: currentRouteKey, message: '复制失败,请重试' });
9326
+ });
9327
+ return;
9328
+ }
9329
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9345
9330
  && route.name === 'result' && portableResultDetailOpen
9346
9331
  && portableResultDetailPagination) {
9347
9332
  const { total, pageRows } = portableResultDetailPagination;
9348
- const direction = key.pageUp || input.toLowerCase() === 'p' ? -1 : 1;
9333
+ const direction = key.pageUp || key.leftArrow ? -1 : 1;
9349
9334
  setPortableResultDetailOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + direction * pageRows)));
9350
9335
  return;
9351
9336
  }
9352
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9337
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9338
+ && route.name === 'portableDependencies' && plugin) {
9339
+ const total = dependencyOverview(plugin)?.dependencies.length ?? 0;
9340
+ const pageRows = showTechnical ? 1 : responsiveBudget(2, 3);
9341
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || key.leftArrow ? -pageRows : pageRows))));
9342
+ return;
9343
+ }
9344
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9353
9345
  && (route.name === 'pluginContent'
9354
9346
  || route.name === 'pluginTechnical'
9355
9347
  || route.name === 'projectStatus' && showTechnical
9356
9348
  || route.name === 'issueDetail' && showTechnical
9349
+ || route.name === 'portableBlocker' && showTechnical
9350
+ || route.name === 'configUnavailable' && showTechnical
9357
9351
  || route.name === 'sourceRemoveBlocked' && sourceDependenciesExpanded
9358
9352
  || route.name === 'pluginDependencies' && route.view === 'removeBlocked' && !showTechnical
9359
9353
  || route.name === 'pluginDependencyNode'
9360
- && (route.view === 'references' || route.view === 'details')
9361
9354
  || route.name === 'pluginDetail'
9362
9355
  || route.name === 'configSave' && route.view === 'changes'
9356
+ || route.name === 'configFields' && route.view === 'values'
9363
9357
  || route.name === 'sourceSave' && route.view === 'changes'
9364
9358
  || route.name === 'review'
9365
9359
  || route.name === 'result')
9366
9360
  && referenceDetailPagination) {
9367
9361
  const { total, pageRows } = referenceDetailPagination;
9368
- moveBatchTargetPage(total, pageRows, key.pageUp || input.toLowerCase() === 'p' ? -1 : 1);
9362
+ moveBatchTargetPage(total, pageRows, key.pageUp || key.leftArrow ? -1 : 1);
9369
9363
  return;
9370
9364
  }
9371
- if ((key.pageUp || key.pageDown)
9365
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9372
9366
  && route.name === 'pluginWriteTask') {
9373
9367
  const total = props.controller.listPluginWriteTaskCandidates(route.goal)
9374
9368
  .filter((candidate) => candidate.target.pluginId === route.pluginId
9375
9369
  && (!route.targetKeys || route.targetKeys.includes(pluginWriteTaskTargetKey(candidate)))).length;
9376
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -1 : 1))));
9370
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp || key.leftArrow ? -1 : 1))));
9377
9371
  return;
9378
9372
  }
9379
- if ((key.pageUp || key.pageDown) && route.name === 'pluginTechnical' && plugin) {
9380
- const total = plugin.technicalComponents.length;
9381
- const pageRows = 1;
9382
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
9383
- return;
9384
- }
9385
- if ((key.pageUp || key.pageDown) && route.name === 'issueDetail' && issue && showTechnical) {
9386
- const total = diagnosticDetailRowCount(issue.diagnostic);
9387
- const pageRows = responsiveBudget(5, 10);
9388
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
9389
- return;
9390
- }
9391
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9373
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9392
9374
  && route.name === 'sourceTechnical') {
9393
9375
  const total = diagnosticDetailLines(resolveSourceTechnicalDiagnostic(route.sourceId), size.columns - 6).length;
9394
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - sourceTechnicalPageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p'
9376
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - sourceTechnicalPageRows), Math.max(0, value + (key.pageUp || key.leftArrow
9395
9377
  ? -sourceTechnicalPageRows : sourceTechnicalPageRows))));
9396
9378
  return;
9397
9379
  }
@@ -9415,18 +9397,16 @@ export function ConsoleAppV4(props) {
9415
9397
  return;
9416
9398
  }
9417
9399
  if ((route.name === 'operationDiagnostic' || route.name === 'operationReportPreview')
9418
- && (key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')) {
9400
+ && (key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)) {
9419
9401
  const detail = route.name === 'operationDiagnostic'
9420
9402
  ? props.controller.getOperationDiagnostic(route.recordId) : undefined;
9421
9403
  const preview = route.name === 'operationReportPreview'
9422
9404
  ? props.controller.previewOperationReport(route.recordId) : undefined;
9423
- const total = detail ? operationDiagnosticLineCount(detail)
9424
- : preview ? operationReportLineCount(preview) : 0;
9425
- const visibleRows = 2;
9426
- const pageRows = key.pageUp || key.pageDown
9427
- ? policy.mode === 'compact' ? 2 : Math.max(1, pageActionBudget(5))
9428
- : visibleRows;
9429
- setScrollOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p' ? -pageRows : pageRows))));
9405
+ const total = detail ? operationDiagnosticLineCount(detail, size.columns)
9406
+ : preview ? operationReportLineCount(preview, size.columns) : 0;
9407
+ const pageRows = route.name === 'operationReportPreview'
9408
+ ? operationReportPageRows : operationDetailPageRows;
9409
+ setScrollOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || key.leftArrow ? -pageRows : pageRows))));
9430
9410
  return;
9431
9411
  }
9432
9412
  if (route.name === 'portableAgentInput') {
@@ -9575,9 +9555,18 @@ export function ConsoleAppV4(props) {
9575
9555
  setSourceInputError(undefined);
9576
9556
  return;
9577
9557
  }
9558
+ if (route.name === 'configFieldEditor' && activeField && key.ctrl && input.toLowerCase() === 'r') {
9559
+ const canUnset = ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
9560
+ .some((field) => field.key === activeField.key && configFieldCanUnset(field)));
9561
+ if (canUnset)
9562
+ removeDraft(activeField.key);
9563
+ return;
9564
+ }
9578
9565
  if (route.name === 'configFieldEditor'
9579
9566
  && (route.editor === 'string' || route.editor === 'number')
9580
- && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')) {
9567
+ && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')
9568
+ && ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
9569
+ .some((field) => field.key === activeField?.key && configFieldCanSet(field)))) {
9581
9570
  if (key.escape) {
9582
9571
  setConfigCandidate(undefined);
9583
9572
  back();
@@ -9595,7 +9584,11 @@ export function ConsoleAppV4(props) {
9595
9584
  }
9596
9585
  let candidate = inputValue;
9597
9586
  if (route.editor === 'number') {
9598
- candidate = Number(inputValue);
9587
+ if (parseConfigNumberInput(inputValue) === undefined) {
9588
+ setInputError('请输入数字;空输入不会保存为 0。');
9589
+ return;
9590
+ }
9591
+ candidate = parseConfigNumberInput(inputValue);
9599
9592
  }
9600
9593
  const issues = validatePluginConfigValue(activeField.definition, candidate, activeField.key);
9601
9594
  if (issues.length > 0) {
@@ -9668,9 +9661,12 @@ export function ConsoleAppV4(props) {
9668
9661
  back();
9669
9662
  return;
9670
9663
  }
9664
+ if (activePropertyDefinition.type === 'number' && parseConfigNumberInput(inputValue) === undefined) {
9665
+ setInputError('请输入数字;空输入不会保存为 0。');
9666
+ return;
9667
+ }
9671
9668
  const candidate = activePropertyDefinition.type === 'number'
9672
- ? Number(inputValue)
9673
- : inputValue;
9669
+ ? parseConfigNumberInput(inputValue) : inputValue;
9674
9670
  if (updateObjectProperty(route.propertyKey, candidate))
9675
9671
  back();
9676
9672
  return;
@@ -9683,197 +9679,82 @@ export function ConsoleAppV4(props) {
9683
9679
  return;
9684
9680
  }
9685
9681
  if (searchFocus && (route.name === 'pluginList' || route.name === 'batchSelection')) {
9686
- if (key.escape) {
9682
+ const currentQuery = route.name === 'pluginList' ? query : batchDraft?.query ?? '';
9683
+ const parts = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(currentQuery)]
9684
+ .map((part) => part.segment);
9685
+ const cursor = Math.min(searchCursor, parts.length);
9686
+ if (key.escape || key.return) {
9687
9687
  setSearchFocus(false);
9688
9688
  return;
9689
9689
  }
9690
- if (key.return) {
9691
- setSearchFocus(false);
9692
- if (route.name === 'pluginList') {
9693
- const row = pluginRows[focus];
9694
- if (row?.menuAction)
9695
- row.menuAction();
9696
- else if (row)
9697
- go({ name: 'pluginDetail', pluginId: row.pluginId });
9698
- }
9699
- return;
9700
- }
9701
9690
  if (key.upArrow || key.downArrow) {
9702
9691
  setSearchFocus(false);
9703
- setFocus((value) => moveIndex(pluginRows.length, value, key.upArrow ? -1 : 1));
9692
+ const candidateCount = route.name === 'batchSelection' && batchDraft
9693
+ ? batchCandidates(batchDraft).length : pluginRows.length;
9694
+ setFocus((value) => moveIndex(candidateCount, Math.min(value, Math.max(0, candidateCount - 1)), key.upArrow ? -1 : 1));
9704
9695
  return;
9705
9696
  }
9706
9697
  if (key.leftArrow || key.rightArrow) {
9707
- setSearchFocus(false);
9708
- const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
9709
- const current = route.name === 'pluginList' ? route.sourceId ?? 'all' : batchDraft?.sourceId ?? 'all';
9710
- const direction = key.leftArrow ? -1 : 1;
9711
- const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
9712
- if (route.name === 'pluginList') {
9713
- props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
9714
- }
9715
- else {
9716
- setBatchDraft((draft) => draft ? {
9717
- ...draft,
9718
- ...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
9719
- showSelected: false,
9720
- } : draft);
9721
- }
9722
- setFocus(0);
9723
- setScrollOffset(0);
9698
+ setSearchCursor(Math.min(parts.length, Math.max(0, cursor + (key.leftArrow ? -1 : 1))));
9724
9699
  return;
9725
9700
  }
9701
+ let nextCursor = cursor;
9702
+ if (input === '\u0015' || input.toLowerCase() === 'u' && key.ctrl) {
9703
+ parts.splice(0);
9704
+ nextCursor = 0;
9705
+ }
9706
+ else if (key.backspace || key.delete) {
9707
+ if (cursor > 0)
9708
+ parts.splice(--nextCursor, 1);
9709
+ }
9710
+ else if (input && !key.ctrl && !key.meta && input >= ' ') {
9711
+ const inserted = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(input)]
9712
+ .map((part) => part.segment);
9713
+ parts.splice(cursor, 0, ...inserted);
9714
+ nextCursor += inserted.length;
9715
+ }
9716
+ const nextQuery = parts.join('');
9717
+ setSearchCursor(nextCursor);
9726
9718
  if (route.name === 'pluginList') {
9727
- const nextQuery = input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
9728
- ? ''
9729
- : key.backspace || key.delete
9730
- ? removeLastGrapheme(query)
9731
- : input && !key.ctrl && !key.meta && input >= ' '
9732
- ? query + input : query;
9733
9719
  setQuery(nextQuery);
9734
- const matchingItems = props.controller.listPlugins({
9735
- mode: route.mode,
9736
- ...(route.sourceId ? { sourceId: route.sourceId } : {}),
9737
- ...(nextQuery ? { query: nextQuery } : {}),
9738
- installFilter,
9739
- });
9740
- const matchingRows = pluginListRowIds(route.mode, matchingItems, nextQuery);
9741
- setFocus(matchingItems[0] ? matchingRows.indexOf(matchingItems[0].pluginId) : 0);
9720
+ props.controller.updatePageContext({ query: nextQuery });
9742
9721
  }
9743
9722
  else {
9744
- setBatchDraft((draft) => draft ? {
9745
- ...draft,
9746
- query: input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
9747
- ? ''
9748
- : key.backspace || key.delete
9749
- ? removeLastGrapheme(draft.query)
9750
- : input && !key.ctrl && !key.meta && input >= ' '
9751
- ? draft.query + input : draft.query,
9752
- showSelected: false,
9753
- } : draft);
9723
+ setBatchDraft((draft) => draft ? { ...draft, query: nextQuery, showSelected: false } : draft);
9754
9724
  }
9755
- if (route.name === 'batchSelection')
9756
- setFocus(0);
9725
+ setFocus(0);
9757
9726
  setScrollOffset(0);
9758
9727
  return;
9759
9728
  }
9760
- if (route.name === 'configFields' && configGroupChooserOpen) {
9761
- if (key.escape || input.toLowerCase() === 'b') {
9762
- setConfigGroupChooserOpen(false);
9763
- setFocusZone('actions');
9764
- setPinnedActionFocus(1);
9765
- }
9766
- else if (key.upArrow || key.downArrow) {
9767
- setConfigGroupChoiceFocus((value) => moveIndex(visibleConfigTabs.length, value, key.upArrow ? -1 : 1));
9768
- }
9769
- else if (key.return) {
9770
- const selected = visibleConfigTabs[configGroupChoiceFocus];
9771
- if (selected) {
9772
- props.controller.updatePageContext({
9773
- configGroupId: selected.id,
9774
- focusIndex: 0,
9775
- scrollOffset: 0,
9776
- });
9777
- setConfigGroupChooserOpen(false);
9778
- setFocusZone('content');
9779
- setFocus(0);
9780
- setScrollOffset(0);
9781
- }
9782
- }
9783
- return;
9784
- }
9785
- if ((route.name === 'pluginList' || route.name === 'sourceList' || route.name === 'projectStatus'
9786
- || route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9787
- || route.name === 'batchPlacement' && pinnedActions.length > 0
9788
- || route.name === 'portableSkillSelect'
9789
- || route.name === 'batchSelection' && batchDraft?.stage === 'select'
9790
- || route.name === 'installTargets' && route.view !== 'blocked') && key.tab) {
9791
- setFocusZone((current) => current === 'content' ? 'actions' : 'content');
9729
+ // Paste and composed text are data, never multiple background commands.
9730
+ if (input.length > 1 && !key.ctrl && !key.meta)
9792
9731
  return;
9793
- }
9794
- if (route.name === 'configFields' && key.tab) {
9795
- setFocusZone((current) => current === 'content' ? 'actions' : 'content');
9732
+ if (key.tab)
9796
9733
  return;
9797
- }
9798
- if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
9799
- openBatchSelection();
9800
- return;
9801
- }
9802
9734
  if (route.name === 'pluginList' && input === '/') {
9803
- setSearchFocus(true);
9804
- return;
9805
- }
9806
- if (route.name === 'pluginList' && focusZone === 'actions'
9807
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9808
- const direction = key.upArrow || key.leftArrow ? -1 : 1;
9809
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, direction));
9810
- return;
9811
- }
9812
- if (route.name === 'pluginList' && focusZone === 'actions' && key.return) {
9813
- const actionIndex = Math.min(pinnedActionFocus, Math.max(0, pinnedActions.length - 1));
9814
- runAction(pinnedActions[actionIndex]);
9815
- return;
9816
- }
9817
- if (route.name === 'sourceList' && focusZone === 'actions'
9818
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9819
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9820
- return;
9821
- }
9822
- if (route.name === 'sourceList' && focusZone === 'actions' && key.return) {
9823
- runAction(pinnedActions[pinnedActionFocus]);
9824
- return;
9825
- }
9826
- if (route.name === 'projectStatus' && focusZone === 'actions'
9827
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9828
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9829
- return;
9830
- }
9831
- if (route.name === 'projectStatus' && focusZone === 'actions' && key.return) {
9832
- runAction(pinnedActions[pinnedActionFocus]);
9735
+ runAction(listQueryActions.find((action) => action.actionId === 'plugin-list-search'));
9833
9736
  return;
9834
9737
  }
9835
- if (route.name === 'portableSkillSelect' && focusZone === 'actions'
9836
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9837
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9838
- return;
9839
- }
9840
- if (route.name === 'portableSkillSelect' && focusZone === 'actions' && key.return) {
9841
- runAction(pinnedActions[pinnedActionFocus]);
9842
- return;
9843
- }
9844
- if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9845
- || route.name === 'installTargets' && route.view !== 'blocked')
9846
- && focusZone === 'actions'
9847
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9848
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9849
- return;
9850
- }
9851
- if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9852
- || route.name === 'installTargets' && route.view !== 'blocked')
9853
- && focusZone === 'actions' && key.return) {
9854
- runAction(pinnedActions[pinnedActionFocus]);
9855
- return;
9856
- }
9857
- if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9858
- || route.name === 'batchPlacement' && pinnedActions.length > 0)
9859
- && focusZone === 'actions'
9860
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9861
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9862
- return;
9863
- }
9864
- if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9865
- || route.name === 'batchPlacement' && pinnedActions.length > 0)
9866
- && focusZone === 'actions' && key.return) {
9867
- runAction(pinnedActions[pinnedActionFocus]);
9868
- return;
9869
- }
9870
- if (route.name === 'configFields' && focusZone === 'actions'
9871
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9872
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9873
- return;
9874
- }
9875
- if (route.name === 'configFields' && focusZone === 'actions' && key.return) {
9876
- runAction(pinnedActions[pinnedActionFocus]);
9738
+ if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
9739
+ runAction(bottomActions.find((action) => action.actionId === 'plugin-list-multi'));
9740
+ return;
9741
+ }
9742
+ if (input.toLowerCase() === 'a' && bottomActions.length > 0) {
9743
+ const firstAction = actions.findIndex((action) => bottomActions.includes(action));
9744
+ if (firstAction >= 0) {
9745
+ const anchor = actionJumpAnchor.current;
9746
+ if (anchor?.routeKey === routeKey && focus >= firstAction) {
9747
+ const entityIndex = anchor.entityId
9748
+ ? actions.findIndex((action) => action.pluginId === anchor.entityId) : -1;
9749
+ setFocus(entityIndex >= 0 ? entityIndex : Math.min(anchor.index, Math.max(0, firstAction - 1)));
9750
+ actionJumpAnchor.current = undefined;
9751
+ }
9752
+ else {
9753
+ actionJumpAnchor.current = { routeKey, index: focus, entityId: actions[focus]?.pluginId };
9754
+ setFocus(firstAction);
9755
+ }
9756
+ setScrollOffset(0);
9757
+ }
9877
9758
  return;
9878
9759
  }
9879
9760
  if (key.tab && (route.name === 'configLayer'
@@ -9950,13 +9831,14 @@ export function ConsoleAppV4(props) {
9950
9831
  }
9951
9832
  else if (globalAction === 'back' && route.name === 'batchSelection'
9952
9833
  && batchDraft?.stage === 'select' && batchDraft.showSelectionTools) {
9953
- setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
9954
- setFocus(0);
9955
- setScrollOffset(0);
9834
+ closeBatchSelectionTools();
9835
+ }
9836
+ else if (globalAction === 'back' && route.name === 'installPlacement'
9837
+ && returnToPreviousInstallPlatform) {
9838
+ returnToPreviousInstallPlatform();
9956
9839
  }
9957
9840
  else if (globalAction === 'back'
9958
- && (route.name === 'configSave' || route.name === 'sourceSave')
9959
- && route.view === 'changes') {
9841
+ && (route.name === 'configSave' || route.name === 'sourceSave' && route.view === 'changes')) {
9960
9842
  back();
9961
9843
  }
9962
9844
  else if (globalAction === 'back' && route.name === 'configLayer') {
@@ -10016,7 +9898,7 @@ export function ConsoleAppV4(props) {
10016
9898
  back();
10017
9899
  }
10018
9900
  else if (route.name === 'pluginList' && input.toLowerCase() === 'f') {
10019
- togglePluginListFilter?.();
9901
+ runAction(listQueryActions.find((action) => action.actionId === 'plugin-list-filter'));
10020
9902
  }
10021
9903
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
10022
9904
  && input.toLowerCase() === 'f') {
@@ -10030,26 +9912,12 @@ export function ConsoleAppV4(props) {
10030
9912
  setFocus(0);
10031
9913
  setScrollOffset(0);
10032
9914
  }
10033
- else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
10034
- prepareCurrentProjectPlugins();
10035
- }
10036
9915
  else if (route.name === 'projectStatus' && input.toLowerCase() === 'c') {
10037
- runAction(pinnedActions.find((action) => action.actionId === 'project-sync-actionable'));
10038
- }
10039
- else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
10040
- const candidates = batchCandidates(batchDraft)
10041
- .filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined);
10042
- setBatchDraft((current) => current ? {
10043
- ...current,
10044
- selectedPluginIds: [...new Set([
10045
- ...current.selectedPluginIds,
10046
- ...candidates.map((candidate) => candidate.pluginId),
10047
- ])],
10048
- } : current);
9916
+ runAction(bottomActions.find((action) => action.actionId === 'project-sync-actionable'));
10049
9917
  }
10050
9918
  else if (route.name === 'installTargets' && route.view !== 'blocked'
10051
9919
  && input.toLowerCase() === 'c') {
10052
- runAction(pinnedActions.find((action) => action.actionId === 'confirm-install-platforms'));
9920
+ runAction(bottomActions.find((action) => action.actionId === 'confirm-install-platforms'));
10053
9921
  }
10054
9922
  else if (route.name === 'batchInstallPlatforms' && input.toLowerCase() === 'c') {
10055
9923
  runAction(actions.find((action) => action.actionId === 'batch-install-platforms-confirm'));
@@ -10057,15 +9925,15 @@ export function ConsoleAppV4(props) {
10057
9925
  else if (route.name === 'batchInstallScope' && input.toLowerCase() === 'c') {
10058
9926
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
10059
9927
  }
10060
- else if (route.name === 'batchPlacement' && pinnedActions.length > 0
9928
+ else if (route.name === 'batchPlacement' && bottomActions.length > 0
10061
9929
  && input.toLowerCase() === 'c') {
10062
9930
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
10063
9931
  }
10064
9932
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
10065
- runAction(pinnedActions.find((action) => action.actionId === 'batch-selection-continue'));
9933
+ runAction(bottomActions.find((action) => action.actionId === 'batch-selection-continue'));
10066
9934
  }
10067
9935
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
10068
- && focusZone === 'content' && input === ' ') {
9936
+ && input === ' ') {
10069
9937
  const action = actions[normalizedEffectiveFocus];
10070
9938
  if (action?.pluginId
10071
9939
  && (batchDraft.selectedPluginIds.includes(action.pluginId) || action.canSelectNew)) {
@@ -10073,12 +9941,20 @@ export function ConsoleAppV4(props) {
10073
9941
  }
10074
9942
  }
10075
9943
  else if (route.name === 'installTargets' && route.view !== 'blocked'
10076
- && focusZone === 'content' && input === ' ') {
9944
+ && input === ' ') {
10077
9945
  const action = actions[focus];
10078
9946
  if (action?.actionId?.startsWith('install-platform-'))
10079
9947
  runAction(action);
10080
9948
  }
9949
+ else if (route.name === 'batchInstallPlatforms'
9950
+ && input === ' ') {
9951
+ const action = actions[focus];
9952
+ if (action?.actionId === 'batch-install-platform-codex'
9953
+ || action?.actionId === 'batch-install-platform-claude')
9954
+ runAction(action);
9955
+ }
10081
9956
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
9957
+ setSearchCursor([...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(batchDraft.query)].length);
10082
9958
  setSearchFocus(true);
10083
9959
  }
10084
9960
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
@@ -10096,7 +9972,7 @@ export function ConsoleAppV4(props) {
10096
9972
  setScrollOffset(0);
10097
9973
  }
10098
9974
  else if (route.name === 'configFields' && input.toLowerCase() === 's') {
10099
- runAction(pinnedActions[0]);
9975
+ runAction(bottomActions[0]);
10100
9976
  }
10101
9977
  else if (route.name === 'configFields' && input.toLowerCase() === 'u') {
10102
9978
  const key = actions[focus]?.configKey;
@@ -10123,20 +9999,17 @@ export function ConsoleAppV4(props) {
10123
9999
  }
10124
10000
  else if ((input.toLowerCase() === 'r' || input.toLowerCase() === 'c')
10125
10001
  && route.name === 'sourceList') {
10126
- void props.controller.recheckAllCatalogSources();
10002
+ runAction(bottomActions.find((action) => action.actionId === 'source-check-all'));
10127
10003
  }
10128
10004
  else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
10129
10005
  const direction = key.leftArrow || input === '[' ? -1 : 1;
10130
10006
  movePluginListSource?.(direction);
10131
10007
  }
10132
10008
  else if (input.toLowerCase() === 'c' && route.name === 'portableSkillSelect') {
10133
- runAction(pinnedActions.find((action) => action.actionId === 'portable-selection-continue'));
10134
- }
10135
- else if (input.toLowerCase() === 'a' && route.name === 'portableSkillSelect') {
10136
- runAction(selectAllPortableCandidates);
10009
+ runAction(bottomActions.find((action) => action.actionId === 'portable-selection-continue'));
10137
10010
  }
10138
10011
  else if (input === ' ' && route.name === 'portableSkillSelect'
10139
- && focusZone === 'content' && !portableToolsOpen) {
10012
+ && !portableToolsOpen) {
10140
10013
  const candidate = portableVisibleCandidates[focus];
10141
10014
  if (candidate)
10142
10015
  props.controller.togglePortableSkill(candidate.name);
@@ -10148,9 +10021,8 @@ export function ConsoleAppV4(props) {
10148
10021
  setFocus((value) => {
10149
10022
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10150
10023
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
10151
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1)
10152
- : route.name === 'sourceDetail' ? moveIndex(actions.length, current, -1)
10153
- : moveFocus(actions, current, -1);
10024
+ const next = route.name === 'sourceDetail' ? moveIndex(actions.length, current, -1)
10025
+ : moveFocus(actions, current, -1);
10154
10026
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
10155
10027
  dynamicMenuFocus.current = {
10156
10028
  routeKey,
@@ -10158,17 +10030,7 @@ export function ConsoleAppV4(props) {
10158
10030
  focusedActionId: dynamicActionIds[next],
10159
10031
  };
10160
10032
  }
10161
- const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10162
- ? batchDraft.showSelectionTools
10163
- ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10164
- : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10165
- : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
10166
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10167
- + (portableVariantCandidates.length > 0 ? 3 : 0)))
10168
- : route.name === 'installTargets' && route.view !== 'blocked'
10169
- ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10170
- + (policy.mode === 'compact' ? 4 : 0))))
10171
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10033
+ const viewport = actionViewport(actions, policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10172
10034
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
10173
10035
  const projection = buildUnifiedDependencyGraphProjection({
10174
10036
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -10180,7 +10042,8 @@ export function ConsoleAppV4(props) {
10180
10042
  ...projection.dependents.defaultExpandedPluginIds,
10181
10043
  ]), next, viewport.offset);
10182
10044
  }
10183
- setScrollOffset(viewport.offset);
10045
+ if (bottomActions.length === 0)
10046
+ setScrollOffset(viewport.offset);
10184
10047
  return next;
10185
10048
  });
10186
10049
  }
@@ -10191,9 +10054,8 @@ export function ConsoleAppV4(props) {
10191
10054
  setFocus((value) => {
10192
10055
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10193
10056
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
10194
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1)
10195
- : route.name === 'sourceDetail' ? moveIndex(actions.length, current, 1)
10196
- : moveFocus(actions, current, 1);
10057
+ const next = route.name === 'sourceDetail' ? moveIndex(actions.length, current, 1)
10058
+ : moveFocus(actions, current, 1);
10197
10059
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
10198
10060
  dynamicMenuFocus.current = {
10199
10061
  routeKey,
@@ -10201,17 +10063,7 @@ export function ConsoleAppV4(props) {
10201
10063
  focusedActionId: dynamicActionIds[next],
10202
10064
  };
10203
10065
  }
10204
- const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10205
- ? batchDraft.showSelectionTools
10206
- ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10207
- : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10208
- : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
10209
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10210
- + (portableVariantCandidates.length > 0 ? 3 : 0)))
10211
- : route.name === 'installTargets' && route.view !== 'blocked'
10212
- ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10213
- + (policy.mode === 'compact' ? 4 : 0))))
10214
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10066
+ const viewport = actionViewport(actions, policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10215
10067
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
10216
10068
  const projection = buildUnifiedDependencyGraphProjection({
10217
10069
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -10223,17 +10075,11 @@ export function ConsoleAppV4(props) {
10223
10075
  ...projection.dependents.defaultExpandedPluginIds,
10224
10076
  ]), next, viewport.offset);
10225
10077
  }
10226
- setScrollOffset(viewport.offset);
10078
+ if (bottomActions.length === 0)
10079
+ setScrollOffset(viewport.offset);
10227
10080
  return next;
10228
10081
  });
10229
10082
  }
10230
- else if (key.return && route.name === 'pluginList' && focusZone === 'content' && pluginRows[focus]) {
10231
- const row = pluginRows[focus];
10232
- if (row.menuAction)
10233
- row.menuAction();
10234
- else
10235
- go({ name: 'pluginDetail', pluginId: row.pluginId });
10236
- }
10237
10083
  else if (key.return) {
10238
10084
  runAction(actions[normalizedEffectiveFocus]);
10239
10085
  }
@@ -10241,6 +10087,19 @@ export function ConsoleAppV4(props) {
10241
10087
  if (tooSmall && !overlay) {
10242
10088
  return (_jsx(ScreenFrame, { title: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", breadcrumb: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", context: `${size.columns} × ${size.rows}`, help: "q \u9000\u51FA", size: size, children: _jsx(Callout, { status: "warning", title: "\u2194 \u8BF7\u5C06\u7EC8\u7AEF\u8C03\u6574\u5230\u81F3\u5C11 60 \u00D7 18", description: "\u8C03\u6574\u540E\u4F1A\u8FD4\u56DE\u539F\u9875\u9762\uFF0C\u4E0D\u4F1A\u4E22\u5931\u5F53\u524D\u9009\u62E9\u3002" }) }));
10243
10089
  }
10090
+ if (readingText !== undefined && route.name !== 'progress') {
10091
+ const paginated = referenceDetailPagination !== undefined
10092
+ || portableResultDetailPagination !== undefined
10093
+ || route.name === 'sourceTechnical'
10094
+ || route.name === 'operationDiagnostic'
10095
+ || route.name === 'operationReportPreview'
10096
+ || route.name === 'portableDependencies';
10097
+ help = `${paginated ? '←→ 翻页 · ' : ''}[y] 复制全文 · Esc 返回${supportsTechnicalDetails
10098
+ ? ' · d 技术详情' : ''} · ${size.columns < 80
10099
+ ? '↑↓/Enter 菜单' : '↑↓ 移动 · Enter 选择'}`;
10100
+ if (copyFeedback?.routeKey === routeKey)
10101
+ description = copyFeedback.message;
10102
+ }
10244
10103
  if (actionError)
10245
10104
  description = actionError;
10246
10105
  const base = (_jsx(ScreenFrame, { title: projectReminderOpen ? '' : title, description: projectReminderOpen ? undefined : description, breadcrumb: breadcrumb, context: context, help: portableEmptySelectionOpen ? 'Enter 返回选择 · Esc 返回' : help, size: size, children: projectReminderOpen || sourceTransportOverlay ? null : content }));
@@ -10296,12 +10155,13 @@ export function ConsoleAppV4(props) {
10296
10155
  const visibleWarnings = shortConfirmation
10297
10156
  ? warningLines : warningLines.slice(warningOffset, warningOffset + warningLimit);
10298
10157
  const warningPaginated = !shortConfirmation && warningLines.length > warningLimit;
10299
- const moveWarningPage = (direction) => {
10300
- setOperationConfirmation((current) => current ? {
10301
- ...current,
10302
- warningOffset: Math.min(Math.max(0, warningLines.length - 1), Math.max(0, current.warningOffset + direction * warningLimit)),
10303
- } : current);
10304
- };
10158
+ confirmationText = portableUserConfirmation
10159
+ ? '! 本次在所有项目范围安装选定 Skills。\n具体对象和范围以刚才的汇总为准。'
10160
+ : [
10161
+ `操作:${review.title} · ${review.subject.name}`,
10162
+ ...(review.confirmationDetails ?? []),
10163
+ ...operationRiskWarnings(review).map((warning) => `! ${warning}`),
10164
+ ].join('\n');
10305
10165
  if (warningPaginated) {
10306
10166
  confirmationPagination = { total: warningLines.length, pageRows: warningLimit };
10307
10167
  }
@@ -10311,14 +10171,9 @@ export function ConsoleAppV4(props) {
10311
10171
  label: portableUserConfirmation ? '确认并执行' : `我已了解影响,${review.primaryLabel}`,
10312
10172
  status: 'error',
10313
10173
  },
10314
- ...(warningPaginated ? paginationActions({
10315
- offset: warningOffset,
10316
- total: warningLines.length,
10317
- pageRows: warningLimit,
10318
- move: moveWarningPage,
10319
- }) : []),
10320
10174
  ];
10321
- const confirmationCard = (_jsxs(ReviewCard, { title: portableUserConfirmation ? '确认本次影响' : '请确认本次风险', description: "\u786E\u8BA4\u53EA\u5BF9\u5F53\u524D\u64CD\u4F5C\u6C47\u603B\u6709\u6548\uFF1B\u5185\u5BB9\u53D8\u5316\u540E\u5FC5\u987B\u91CD\u65B0\u68C0\u67E5\u3002", children: [!shortConfirmation && warningOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${warningOffset} 行`, status: "muted", compact: true }) : null, visibleWarnings.length > 0
10175
+ const confirmationCard = (_jsxs(ReviewCard, { title: portableUserConfirmation ? '确认本次影响' : '请确认本次风险', description: `${copyFeedback?.routeKey === `${routeKey}:confirmation`
10176
+ ? `${copyFeedback.message} · ` : ''}确认只对当前操作汇总有效;[y] 复制全文。`, children: [!shortConfirmation && warningOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${warningOffset} 行`, status: "muted", compact: true }) : null, visibleWarnings.length > 0
10322
10177
  ? visibleWarnings.map((warning, index) => _jsx(StaticRow, { title: warning, status: warning.startsWith('! ') ? 'warning' : 'info', compact: true }, `${warningOffset + index}:${warning}`))
10323
10178
  : _jsx(StaticRow, { title: "! \u6B64\u64CD\u4F5C\u9700\u8981\u660E\u786E\u6388\u6743", status: "warning", compact: true }), !shortConfirmation && warningLines.length - warningOffset - visibleWarnings.length > 0
10324
10179
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${warningLines.length - warningOffset - visibleWarnings.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: confirmationActions, focus: overlayFocus, kind: "action", compact: true })] }));
@@ -10327,7 +10182,7 @@ export function ConsoleAppV4(props) {
10327
10182
  }
10328
10183
  return _jsx(ScreenFrame, { title: review.operationKind === 'catalogStorePrune'
10329
10184
  ? `确认${review.title}?`
10330
- : `确认${review.title} ${review.subject.name}?`, breadcrumb: `${review.subject.name} / 风险确认`, context: "\u72EC\u7ACB\u6388\u6743", help: "PgUp/PgDn \u67E5\u770B\u5168\u90E8\u8B66\u544A \u00B7 \u2191\u2193 \u79FB\u52A8 \u00B7 Enter \u9009\u62E9 \u00B7 Esc \u8FD4\u56DE", size: size, children: confirmationCard });
10185
+ : `确认${review.title} ${review.subject.name}?`, breadcrumb: `${review.subject.name} / 风险确认`, context: "\u72EC\u7ACB\u6388\u6743", help: "\u2190\u2192/PgUp/PgDn \u7FFB\u9875 \u00B7 [y] \u590D\u5236\u5168\u6587 \u00B7 Esc \u8FD4\u56DE", size: size, children: confirmationCard });
10331
10186
  }
10332
10187
  if (sourceTransportOverlay) {
10333
10188
  const configuredSource = route.name === 'sourceEdit'
@@ -10345,33 +10200,6 @@ export function ConsoleAppV4(props) {
10345
10200
  ];
10346
10201
  return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 4, marginLeft: 2, width: size.columns - 4, flexDirection: "column", children: _jsxs(ReviewCard, { title: "\u9009\u62E9\u4E0B\u8F7D\u65B9\u5F0F", description: `当前草稿:${currentTransport ?? '未识别'};仅修改草稿,保存前可查看汇总。`, children: [_jsx(SemanticRows, { actions: transportActions, focus: overlayFocus, kind: "choice", compact: false }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" })] }) })] });
10347
10202
  }
10348
- if (pluginListChooser && route.name === 'pluginList') {
10349
- const sourceId = route.sourceId ?? 'all';
10350
- const chooserActions = pluginListChooser.kind === 'source'
10351
- ? [
10352
- { label: '全部来源', selected: sourceId === 'all' },
10353
- ...props.controller.listSources().map((source) => ({
10354
- label: source.label,
10355
- meta: source.id === source.label ? undefined : source.id,
10356
- selected: source.id === sourceId,
10357
- })),
10358
- ]
10359
- : PLUGIN_FILTERS.map((filter) => ({
10360
- label: pluginFilterLabel(filter),
10361
- selected: filter === installFilter,
10362
- }));
10363
- const chooserFocus = Math.min(pluginListChooser.focus, Math.max(0, chooserActions.length - 1));
10364
- const chooserViewport = rowViewport({
10365
- rowHeights: chooserActions.map(() => 1),
10366
- focus: chooserFocus,
10367
- requestedOffset: 0,
10368
- rowBudget: responsiveBudget(6, 9),
10369
- });
10370
- const chooserWidth = Math.max(40, Math.floor(size.columns * 0.8));
10371
- return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: policy.mode === 'compact' ? 2 : 5, marginLeft: Math.max(2, Math.floor((size.columns - chooserWidth) / 2)), width: chooserWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: pluginListChooser.kind === 'source' ? '选择插件来源' : '筛选插件', description: pluginListChooser.kind === 'status' ? '只改变列表,不改变插件详情。' : undefined, children: [_jsx(SemanticRows, { actions: chooserActions, focus: chooserFocus, kind: "choice", compact: true, offset: chooserViewport.offset, limit: chooserViewport.limit }), chooserActions.length > chooserViewport.limit
10372
- ? _jsx(StaticRow, { title: `${chooserViewport.position} / ${chooserViewport.total}`, status: "muted", compact: true })
10373
- : null, _jsx(StaticRow, { title: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88", status: "muted", compact: true })] }) })] });
10374
- }
10375
10203
  if (!overlay)
10376
10204
  return base;
10377
10205
  const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy, props.onSessionNavigation !== undefined);