@lesliechan721/aw 0.1.0-beta.32 → 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.
@@ -12,12 +12,13 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
12
12
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
13
13
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
14
14
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
15
- import { ActionRow, ObjectCardRow, objectCardRowLayout, StatusBadge, diagnosticDetailLines, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, 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';
16
16
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
17
17
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
18
18
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
19
19
  import { catalogSourceFreshnessLabel, catalogSourceListPresentation, } from './catalog-sources/screens.js';
20
- 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';
21
22
  import { resolveTuiSizePolicy } from './layout.js';
22
23
  import { buildDependencyGraphProjection, buildUnifiedDependencyGraphProjection, } from './dependency-graph.js';
23
24
  import { platformLabel, scopeLabel } from './glossary.js';
@@ -41,22 +42,6 @@ function pluginFilterLabel(filter) {
41
42
  attention: '需处理',
42
43
  }[filter];
43
44
  }
44
- function paginationActions(options) {
45
- return [
46
- {
47
- actionId: 'pagination-previous',
48
- label: 'p 上一页',
49
- disabled: options.offset <= 0 ? '已在第一页' : undefined,
50
- run: () => options.move(-1),
51
- },
52
- {
53
- actionId: 'pagination-next',
54
- label: 'n 下一页',
55
- disabled: options.offset + options.pageRows >= options.total ? '已在最后一页' : undefined,
56
- run: () => options.move(1),
57
- },
58
- ];
59
- }
60
45
  function sourceOperationBlockLabel(reason) {
61
46
  switch (reason) {
62
47
  case 'configDraft': return '未保存的插件设置';
@@ -306,12 +291,16 @@ function pluginUpdateDescription(plugin) {
306
291
  }
307
292
  return '当前启用目录已验证;可直接进入更新确认。';
308
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
+ }
309
299
  function pluginPlatformBadges(plugin) {
310
300
  const items = plugin.supportedPlatforms.map((platform) => {
311
301
  const placements = plugin.installedPlacements.filter((entry) => entry.platform === platform);
312
302
  const installed = placements.some((entry) => entry.state === 'installed' || entry.state === 'scopeConflict');
313
- const unknown = plugin.sourceUnavailable || placements.length === 0
314
- || placements.some((entry) => entry.state === 'installed' && entry.sharedNative?.state === 'unobserved');
303
+ const unknown = pluginPlatformUnknown(plugin, platform);
315
304
  const attention = placements.some((entry) => entry.state === 'blocked' || entry.state === 'scopeConflict'
316
305
  || entry.state === 'installed' && (entry.health !== 'ready' || entry.blockingReason !== undefined));
317
306
  const update = plugin.update.targets.some((target) => target.platform === platform
@@ -469,6 +458,16 @@ function changeDetailLines(changes, columns) {
469
458
  ...(change.children ?? []).flatMap((child) => [child.property, ...valueLines('修改前', child.before), ...valueLines('修改后', child.after)]),
470
459
  ]);
471
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
+ }
472
471
  function operationRiskWarnings(review) {
473
472
  return [...new Set([
474
473
  ...(review.sharedImpact.warning ? [review.sharedImpact.warning] : []),
@@ -869,7 +868,7 @@ function SemanticRows(props) {
869
868
  compact: props.compact,
870
869
  };
871
870
  if (props.cards)
872
- 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}`);
873
872
  if (props.kind === 'section')
874
873
  return _jsx(SectionRow, { ...rowProps }, `${action.label}:${index}`);
875
874
  if (props.kind === 'choice') {
@@ -892,7 +891,8 @@ function SequenceRows(props) {
892
891
  rowHeights: props.actions.map((action, index) => action.role === 'navigation' ? 0 : index < props.itemCount
893
892
  ? props.wrapValues ? configLines(action).length : props.cards ? objectCardRowLayout({
894
893
  width: props.columns, description: action.disabled ?? action.description ?? '',
895
- meta: action.meta, hasTags: action.tags !== undefined, compact: props.compact,
894
+ meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows,
895
+ hasTags: action.tags !== undefined, compact: props.compact,
896
896
  }).height : action.description ? 2 : 1
897
897
  : 1),
898
898
  focus: props.focus, requestedOffset: props.scrollOffset, rowBudget: props.rowBudget,
@@ -905,13 +905,13 @@ function SequenceRows(props) {
905
905
  if (index < props.itemCount && props.wrapValues)
906
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
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, tags: action.tags, focused: focused, compact: props.compact }, action.actionId ?? action.pluginId ?? action.label)
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
909
  : _jsx(SemanticRows, { actions: [action], focus: focused ? 0 : -1, kind: index < props.itemCount ? 'section' : 'action', compact: index >= props.itemCount }, action.actionId ?? action.label);
910
910
  }) });
911
911
  }
912
912
  function PluginRows(props) {
913
913
  return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
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, { 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))) }));
915
915
  }
916
916
  function IssueTechnicalDetails(props) {
917
917
  const visible = props.details.slice(0, props.compact ? 1 : 2);
@@ -939,7 +939,7 @@ function SourceTabs(props) {
939
939
  const state = 'state' in tab ? tab.state : 'ready';
940
940
  return `${tab.id === props.current ? '[' : ''}${tab.label}${badge(state, tab.count)}${tab.id === props.current ? ']' : ''} `;
941
941
  }).join('')}`;
942
- if (props.compact || terminalTextWidth(fullText) > props.columns) {
942
+ if (props.compact || terminalTextWidth(`${fullText}←→ 切换`) > props.columns) {
943
943
  const tab = tabs[index];
944
944
  const neighbour = tabs[(index + 1) % tabs.length];
945
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 : ''] });
@@ -951,7 +951,7 @@ function SourceTabs(props) {
951
951
  : state === 'error' ? tuiTheme.statusColors.error
952
952
  : state === 'update' || state === 'recovery' ? tuiTheme.statusColors.warning
953
953
  : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, badge(state, tab.count), active ? ']' : '', ' '] }, tab.id));
954
- })] }));
954
+ }), "\u2190\u2192 \u5207\u6362"] }));
955
955
  }
956
956
  function configTabs(page) {
957
957
  return [
@@ -1149,6 +1149,7 @@ export function ConsoleAppV4(props) {
1149
1149
  const [scope, setScope] = useState('user');
1150
1150
  const [selectedTargets, setSelectedTargets] = useState([]);
1151
1151
  const [installPlatformScopes, setInstallPlatformScopes] = useState({});
1152
+ const [installPlatformIndex, setInstallPlatformIndex] = useState(0);
1152
1153
  const [uninstallTargetKeys, setUninstallTargetKeys] = useState([]);
1153
1154
  const [technicalRouteKey, setTechnicalRouteKey] = useState();
1154
1155
  const [exitAfterBusy, setExitAfterBusy] = useState(false);
@@ -1167,9 +1168,11 @@ export function ConsoleAppV4(props) {
1167
1168
  const [inputError, setInputError] = useState();
1168
1169
  const [configActionError, setConfigActionError] = useState();
1169
1170
  const [actionError, setActionError] = useState();
1171
+ const [copyFeedback, setCopyFeedback] = useState();
1170
1172
  const [configResetAvailability, setConfigResetAvailability] = useState({});
1171
1173
  const [sourceDraft, setSourceDraft] = useState();
1172
1174
  const [batchDraft, setBatchDraft] = useState();
1175
+ const batchSelectionAnchor = useRef(undefined);
1173
1176
  const [batchTargetOffset, setBatchTargetOffset] = useState(0);
1174
1177
  const [portableResultDetailOpen, setPortableResultDetailOpen] = useState(false);
1175
1178
  const [portableResultDetailOffset, setPortableResultDetailOffset] = useState(0);
@@ -1966,9 +1969,13 @@ export function ConsoleAppV4(props) {
1966
1969
  let description = '为 Codex 和 Claude Code 添加、设置或移除开发能力。';
1967
1970
  let breadcrumb = '首页';
1968
1971
  let context;
1969
- let help = '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出';
1972
+ let help = route.name === 'home'
1973
+ ? '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出'
1974
+ : '↑↓ 移动 · Enter 选择 · Esc 返回 · ? 帮助';
1970
1975
  let content;
1971
1976
  let actions = [];
1977
+ let readingText;
1978
+ let confirmationText;
1972
1979
  let referenceDetailPagination;
1973
1980
  let confirmationPagination;
1974
1981
  let pluginRows = [];
@@ -1979,6 +1986,7 @@ export function ConsoleAppV4(props) {
1979
1986
  let movePluginListSource;
1980
1987
  let choosePluginListFilter;
1981
1988
  let choosePluginListSource;
1989
+ let returnToPreviousInstallPlatform;
1982
1990
  let visibleConfigTabs = [];
1983
1991
  let activeConfigTabId;
1984
1992
  const dynamicMenu = plugin && route.name === 'platform'
@@ -2006,7 +2014,8 @@ export function ConsoleAppV4(props) {
2006
2014
  + Math.max(0, wrapConsoleText(actionError ?? description ?? '', size.columns - 2).length - 1)
2007
2015
  + Math.max(0, wrapConsoleText(title, size.columns - 2).length - 1),
2008
2016
  });
2009
- const dependencyPlacementStaticRows = policy.mode === 'compact' ? 3 : 10;
2017
+ const dependencyPlacementStaticRows = (policy.mode === 'compact' ? 3 : 10)
2018
+ + (snapshot.installPlacementDraft?.exactRootTargets?.length ?? 0);
2010
2019
  const actionViewportStaticRows = route.name === 'dependencyPlacement'
2011
2020
  ? dependencyPlacementStaticRows
2012
2021
  : route.name === 'pluginDetail' ? policy.mode === 'compact' ? 6 : 8
@@ -2019,7 +2028,8 @@ export function ConsoleAppV4(props) {
2019
2028
  const base = policy.mode === 'compact' ? compactRows : standardRows;
2020
2029
  return base + Math.max(0, size.rows - baseline);
2021
2030
  };
2022
- const operationDetailPageRows = 2;
2031
+ const operationDetailPageRows = responsiveBudget(3, 6);
2032
+ const operationReportPageRows = responsiveBudget(3, 4);
2023
2033
  const sourceTechnicalPageRows = Math.max(1, contentRowBudget({
2024
2034
  terminalRows: size.rows, description: false, compact: policy.mode === 'compact', staticRows: 6,
2025
2035
  }));
@@ -2100,6 +2110,19 @@ export function ConsoleAppV4(props) {
2100
2110
  setShowTechnical(false);
2101
2111
  setActionError(undefined);
2102
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
+ };
2103
2126
  const toggleTechnical = () => {
2104
2127
  if (portableResultDetailOpen)
2105
2128
  setFocus(portableResultDetailReturnFocus.current);
@@ -3365,23 +3388,26 @@ export function ConsoleAppV4(props) {
3365
3388
  }] : []),
3366
3389
  ];
3367
3390
  help = showTechnical
3368
- ? 'p/n 或 PgUp/PgDn 阅读 · d 返回项目位置 · Esc 返回'
3391
+ ? '←→ 或 PgUp/PgDn 阅读 · d 返回项目位置 · Esc 返回'
3369
3392
  : '↑↓ 移动 · Enter 查看 · Esc 返回';
3370
3393
  const projectItemCount = showTechnical ? 0 : actions.length;
3371
3394
  actions = [...(showTechnical ? [] : actions), ...bottomActions];
3372
- const technicalLines = [
3395
+ const technicalFields = [
3373
3396
  ...(projectIntentIssue ? [
3374
3397
  `项目声明 · ${safeDiagnosticCode(projectIntentIssue.code, 'projectIntentUnavailable')}`,
3375
- ...wrapConsoleText(`说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`, Math.max(20, size.columns - 8)),
3398
+ `说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`,
3376
3399
  ] : []),
3377
3400
  ...allTargets.flatMap(({ target, state }) => [
3378
- ...wrapConsoleText(`${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, Math.max(20, size.columns - 8)),
3401
+ `${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3379
3402
  ...(target.reasonCodes.length > 0
3380
- ? target.reasonCodes.flatMap((code) => wrapConsoleText(`原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`, Math.max(20, size.columns - 8)))
3403
+ ? target.reasonCodes.map((code) => `原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`)
3381
3404
  : [state === 'actionable' ? '原因 · confirmedDifference'
3382
3405
  : state === 'blocked' ? '原因 · blocked' : '原因 · unconfirmed']),
3383
3406
  ]),
3384
3407
  ];
3408
+ const technicalLines = technicalFields.flatMap((field) => wrapConsoleText(field, Math.max(20, size.columns - 8)));
3409
+ if (showTechnical)
3410
+ readingText = technicalFields.join('\n');
3385
3411
  if (showTechnical)
3386
3412
  description = undefined;
3387
3413
  const technicalPageRows = responsiveBudget(4, 8);
@@ -3598,7 +3624,10 @@ export function ConsoleAppV4(props) {
3598
3624
  })), ...bottomActions];
3599
3625
  const viewport = rowViewport({
3600
3626
  rowHeights: actions.map((_, index) => index < pluginRows.length
3601
- ? pluginRows[index].attention ? 6 : 5 : 1),
3627
+ ? pluginListRowLayout({
3628
+ ...pluginRows[index], width: size.columns - 2,
3629
+ hasTags: Boolean(pluginRows[index].tags),
3630
+ }).height : 1),
3602
3631
  focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(3),
3603
3632
  });
3604
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
@@ -3609,7 +3638,7 @@ export function ConsoleAppV4(props) {
3609
3638
  ? installFilter === 'updateAvailable'
3610
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" })
3611
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" })
3612
- : null, _jsxs(ContentViewport, { children: [_jsx(PluginRows, { 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)) })] })] }));
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)) })] })] }));
3613
3642
  }
3614
3643
  else if (route.name === 'batchSelection') {
3615
3644
  breadcrumb = '插件 / 选择多个';
@@ -3636,8 +3665,10 @@ export function ConsoleAppV4(props) {
3636
3665
  const plugin = candidate.plugin;
3637
3666
  return {
3638
3667
  label: plugin
3639
- ? `${plugin.displayName} · ${plugin.sourceLabel}`
3668
+ ? plugin.displayName
3640
3669
  : `${candidate.pluginId}(已不可用,可取消选择)`,
3670
+ sourceLabel: plugin?.sourceLabel,
3671
+ maxDescriptionRows: 2,
3641
3672
  description: plugin
3642
3673
  ? `${plugin.description} · ${blockReason
3643
3674
  ? `${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
@@ -3673,9 +3704,11 @@ export function ConsoleAppV4(props) {
3673
3704
  }
3674
3705
  moveBatchStage('task');
3675
3706
  };
3707
+ const continueLabel = batchDraft.recommendedGoal === 'update' ? '查看升级汇总'
3708
+ : batchDraft.recommendedGoal ? '查看恢复汇总' : '继续选择任务';
3676
3709
  const continueAction = {
3677
3710
  actionId: 'batch-selection-continue',
3678
- label: `继续(${batchDraft.selectedPluginIds.length}) [c]`,
3711
+ label: `${continueLabel}(${batchDraft.selectedPluginIds.length}) [c]`,
3679
3712
  description: batchDraft.selectedPluginIds.length > 0
3680
3713
  ? batchDraft.recommendedGoal ? '重新读取更新信息并显示操作汇总' : '继续选择任务和确认范围'
3681
3714
  : '先选择至少一个插件',
@@ -3699,11 +3732,7 @@ export function ConsoleAppV4(props) {
3699
3732
  {
3700
3733
  role: 'navigation', label: '返回插件选择',
3701
3734
  description: '保留当前已选插件',
3702
- run: () => {
3703
- setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
3704
- setFocus(0);
3705
- setScrollOffset(0);
3706
- },
3735
+ run: closeBatchSelectionTools,
3707
3736
  },
3708
3737
  {
3709
3738
  label: '全选当前结果',
@@ -3731,10 +3760,11 @@ export function ConsoleAppV4(props) {
3731
3760
  label: batchDraft.showSelectionTools ? '返回插件选择' : '选择工具',
3732
3761
  description: '全选、取消当前结果或清空全部选择',
3733
3762
  run: () => {
3734
- setBatchDraft((current) => current ? {
3735
- ...current,
3736
- showSelectionTools: !current.showSelectionTools,
3737
- } : current);
3763
+ if (batchDraft.showSelectionTools) {
3764
+ closeBatchSelectionTools();
3765
+ return;
3766
+ }
3767
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: true } : current);
3738
3768
  setFocus(0);
3739
3769
  setScrollOffset(0);
3740
3770
  },
@@ -3748,7 +3778,7 @@ export function ConsoleAppV4(props) {
3748
3778
  : '↑↓ 移动 · Space 选择 · Enter 查看 · Esc 返回';
3749
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
3750
3780
  ? _jsx(InputDisplay, { value: batchDraft.query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3751
- : _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] \u7EE7\u7EED"] }), _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 })] });
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 })] });
3752
3782
  }
3753
3783
  else {
3754
3784
  const selectedIds = batchDraft.selectedPluginIds;
@@ -4224,13 +4254,14 @@ export function ConsoleAppV4(props) {
4224
4254
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4225
4255
  ];
4226
4256
  const dependencies = overview?.dependencies ?? [];
4257
+ readingText = [plugin.displayName, ...dependencies.map((entry) => `${entry.displayName} · ${entry.pluginId} · ${entry.relation === 'direct' ? '直接依赖' : '间接依赖'} · ${entry.requiredBy.map((parent) => parent.displayName).join('、')} 需要它`)].join('\n');
4227
4258
  const dependencyLimit = showTechnical ? 1 : responsiveBudget(2, 3);
4228
4259
  const dependencyOffset = Math.min(batchTargetOffset, Math.max(0, dependencies.length - dependencyLimit));
4229
4260
  const visibleDependencies = dependencies.slice(dependencyOffset, dependencyOffset + dependencyLimit);
4230
4261
  const technicalDependencies = dependencies.slice(dependencyOffset, dependencyOffset + 1);
4231
4262
  const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
4232
4263
  help = dependencies.length > dependencyLimit
4233
- ? '↑↓ 移动 · PageUp/PageDown 或 p/n 阅读依赖 · Enter 选择 · Esc 返回'
4264
+ ? '↑↓ 移动 · PageUp/PageDown 或 ←→ 阅读依赖 · Enter 选择 · Esc 返回'
4234
4265
  : '↑↓ 移动 · Enter 选择 · Esc 返回';
4235
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] });
4236
4267
  }
@@ -4308,11 +4339,12 @@ export function ConsoleAppV4(props) {
4308
4339
  ];
4309
4340
  const technicalPageRows = responsiveBudget(2, 4);
4310
4341
  if (showTechnical && !showPortableRequirements) {
4342
+ readingText = diagnosticDetailText(diagnostic);
4311
4343
  referenceDetailPagination = {
4312
4344
  total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
4313
4345
  pageRows: technicalPageRows,
4314
4346
  };
4315
- help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4347
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4316
4348
  }
4317
4349
  const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4318
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 })] });
@@ -4334,6 +4366,8 @@ export function ConsoleAppV4(props) {
4334
4366
  }), entries.length > safeOffset + visible.length ? _jsx(StaticRow, { title: `↓ 后面还有 ${entries.length - safeOffset - visible.length} 项`, status: "muted", compact: true }) : null] });
4335
4367
  };
4336
4368
  if (route.view === 'removeBlocked') {
4369
+ if (!showTechnical)
4370
+ readingText = visibleDependents.map((entry) => `${entry.displayName} · ${dependencyRelationDescription(entry)} · ${dependentTargetLabel(entry)}`).join('\n');
4337
4371
  breadcrumb = `${plugin.displayName} / 移除`;
4338
4372
  context = '已阻止';
4339
4373
  title = `无法移除 ${plugin.displayName}`;
@@ -4347,21 +4381,13 @@ export function ConsoleAppV4(props) {
4347
4381
  { role: 'navigation', label: '返回插件详情', run: back },
4348
4382
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4349
4383
  ];
4350
- const blockedRemovalPagination = !showTechnical && visibleDependents.length > dependentLimit
4351
- ? paginationActions({
4352
- offset: batchTargetOffset,
4353
- total: visibleDependents.length,
4354
- pageRows: dependentLimit,
4355
- move: (direction) => moveBatchTargetPage(visibleDependents.length, dependentLimit, direction),
4356
- })
4357
- : [];
4358
- actions = [...blockedRemovalPagination, ...blockedRemovalActions];
4359
- if (blockedRemovalPagination.length > 0) {
4384
+ actions = blockedRemovalActions;
4385
+ if (!showTechnical && visibleDependents.length > dependentLimit) {
4360
4386
  referenceDetailPagination = { total: visibleDependents.length, pageRows: dependentLimit };
4361
4387
  }
4362
4388
  const blockedRemovalViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
4363
4389
  if (!showTechnical && visibleDependents.length > dependentLimit) {
4364
- help = 'p/n 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4390
+ help = '←→ 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4365
4391
  }
4366
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: [{
4367
4393
  code: 'dependencyStillRequired',
@@ -4508,38 +4534,7 @@ export function ConsoleAppV4(props) {
4508
4534
  meta: '依赖当前可用',
4509
4535
  };
4510
4536
  });
4511
- if (route.view === 'details') {
4512
- const detailSections = [
4513
- ['用途', node?.description ?? '当前目录没有用途说明'],
4514
- ['为何需要', description],
4515
- ['影响', state?.value ?? (node ? statusText(node) : '尚未检查当前状态')],
4516
- ];
4517
- const detailLines = detailSections.flatMap(([label, value]) => [
4518
- label,
4519
- ...wrapConsoleText(value, Math.max(1, size.columns - 6)).map((line) => ` ${line}`),
4520
- ]);
4521
- const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
4522
- const detailOffset = Math.min(batchTargetOffset, Math.max(0, detailLines.length - 1));
4523
- const visibleDetailLines = detailLines.slice(detailOffset, detailOffset + pageRows);
4524
- referenceDetailPagination = { total: detailLines.length, pageRows };
4525
- breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId} / 完整说明`;
4526
- context = `${Math.min(detailOffset + 1, detailLines.length)}–${Math.min(detailOffset + visibleDetailLines.length, detailLines.length)} / ${detailLines.length}`;
4527
- title = `${node?.displayName ?? route.nodePluginId} 的完整说明`;
4528
- description = '用途、关系原因和当前影响。';
4529
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回节点详情';
4530
- actions = [
4531
- ...paginationActions({
4532
- offset: detailOffset,
4533
- total: detailLines.length,
4534
- pageRows,
4535
- move: (direction) => moveBatchTargetPage(detailLines.length, pageRows, direction),
4536
- }),
4537
- { role: 'navigation', label: '返回节点详情', run: back },
4538
- ];
4539
- 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
4540
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${detailLines.length - detailOffset - visibleDetailLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
4541
- }
4542
- else if (route.view === 'references') {
4537
+ if (route.view === 'references') {
4543
4538
  breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId} / 使用位置`;
4544
4539
  context = showTechnical ? '技术详情' : '使用位置';
4545
4540
  title = `当前可用依赖位置 ${references.length} 个`;
@@ -4563,6 +4558,15 @@ export function ConsoleAppV4(props) {
4563
4558
  `dependency · platform=${platformLabel(selectedReference.dependency.platform)} · scope=${selectedReference.dependency.scope}`,
4564
4559
  `dependency · project=${selectedReference.dependency.project ?? 'none'}`,
4565
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');
4566
4570
  const referenceDetailPageRows = referenceDetailLines.length <= 6
4567
4571
  ? 6
4568
4572
  : responsiveBudget(4, 8);
@@ -4572,24 +4576,14 @@ export function ConsoleAppV4(props) {
4572
4576
  total: referenceDetailLines.length,
4573
4577
  pageRows: referenceDetailPageRows,
4574
4578
  } : undefined;
4575
- const referencePaginationActions = showTechnical
4576
- && referenceDetailLines.length > referenceDetailPageRows
4577
- ? paginationActions({
4578
- offset: referenceDetailOffset,
4579
- total: referenceDetailLines.length,
4580
- pageRows: referenceDetailPageRows,
4581
- move: (direction) => moveBatchTargetPage(referenceDetailLines.length, referenceDetailPageRows, direction),
4582
- })
4583
- : [];
4584
4579
  actions = [
4585
4580
  ...(showTechnical && referenceActions[selectedReferenceIndex]
4586
4581
  ? [referenceActions[selectedReferenceIndex]] : referenceActions),
4587
- ...referencePaginationActions,
4588
4582
  { role: 'navigation', label: '返回节点详情', run: back },
4589
4583
  ];
4590
4584
  const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4591
4585
  help = showTechnical
4592
- ? 'p/n 查看当前位置详情 · ↑↓ 移动 · Enter 选择 · d 隐藏技术详情 · Esc 返回节点详情'
4586
+ ? '←→ 查看当前位置详情 · ↑↓ 移动 · Enter 选择 · d 隐藏技术详情 · Esc 返回节点详情'
4593
4587
  : '↑↓ 查看全部位置 · d 技术详情 · Enter 返回节点详情 · Esc 返回节点详情';
4594
4588
  content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? null
4595
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
@@ -4612,20 +4606,33 @@ export function ConsoleAppV4(props) {
4612
4606
  direction: route.direction,
4613
4607
  }),
4614
4608
  }] : []),
4615
- {
4616
- label: '阅读完整说明',
4617
- description: '分页查看用途、为何需要和影响',
4618
- run: () => {
4619
- setBatchTargetOffset(0);
4620
- go({ ...route, view: 'details' });
4621
- },
4622
- },
4623
4609
  { role: 'navigation', label: '返回关系图', run: back },
4624
4610
  ];
4625
- const nodeActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4626
- 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
4627
- ? _jsx(StaticRow, { title: `当前可用依赖位置 ${references.length} 个`, description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u4F4D\u7F6E\u201D\u53EF\u9010\u9879\u67E5\u770B", compact: true })
4628
- : 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] });
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'}`] : []),
4618
+ ];
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 })] });
4629
4636
  }
4630
4637
  }
4631
4638
  else if (route.name === 'installTargets' && plugin) {
@@ -4669,6 +4676,7 @@ export function ConsoleAppV4(props) {
4669
4676
  }
4670
4677
  setDirty(false);
4671
4678
  props.controller.setInstallPlatforms(validSelectedTargets);
4679
+ setInstallPlatformIndex(0);
4672
4680
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
4673
4681
  };
4674
4682
  const nativeActions = choices.map((choice) => {
@@ -4780,35 +4788,53 @@ export function ConsoleAppV4(props) {
4780
4788
  }
4781
4789
  else {
4782
4790
  description = '所选平台没有共同范围。请分别确定每个平台的合法位置。';
4783
- const pairActions = selectedPlatforms.flatMap((platform) => legalScopes(platform).map((candidateScope) => ({
4784
- 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),
4785
4803
  selected: installPlatformScopes[platform] === candidateScope,
4786
- run: () => setInstallPlatformScopes((current) => ({ ...current, [platform]: candidateScope })),
4787
- })));
4788
- const exactTargets = selectedPlatforms.flatMap((platform) => installPlatformScopes[platform]
4789
- ? [{ pluginId: plugin.pluginId, platform, scope: installPlatformScopes[platform] }] : []);
4790
- actions = [
4791
- ...pairActions,
4792
- {
4793
- label: '继续到操作汇总',
4794
- description: '只安装上方明确选择的实际目标',
4795
- disabled: exactTargets.length !== selectedPlatforms.length ? '请为每个平台选择一个位置' : undefined,
4796
- run: () => beginReview({
4797
- kind: 'pluginWriteBatch',
4798
- action: 'install',
4799
- selection: { kind: 'targets', targets: exactTargets },
4800
- platform: new Set(exactTargets.map((target) => target.platform)).size > 1
4801
- ? 'both' : exactTargets[0].platform,
4802
- scope: exactTargets[0].scope,
4803
- }),
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());
4804
4834
  },
4805
- { role: 'navigation', label: '返回目标平台', run: back },
4806
- ];
4835
+ })) : [];
4807
4836
  }
4808
- const scopeChoiceCount = commonScopes.length > 0 ? actions.length : Math.max(0, actions.length - 2);
4809
- 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
4810
- ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: scopeChoiceCount })
4811
- : 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] });
4812
4838
  help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
4813
4839
  }
4814
4840
  else if (route.name === 'uninstallTargets' && plugin) {
@@ -4870,6 +4896,9 @@ export function ConsoleAppV4(props) {
4870
4896
  else if (route.name === 'dependencyPlacement' && plugin) {
4871
4897
  const draft = snapshot.installPlacementDraft?.pluginId === plugin.pluginId
4872
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));
4873
4902
  breadcrumb = `${plugin.displayName} / 添加 / 依赖位置`;
4874
4903
  context = '依赖位置';
4875
4904
  title = '选择每个平台的依赖位置';
@@ -4913,8 +4942,8 @@ export function ConsoleAppV4(props) {
4913
4942
  ...(promotedCount > 0 ? [`因依赖自动调整 ${promotedCount} 项`] : []),
4914
4943
  ].join(';');
4915
4944
  content = _jsxs(Box, { flexDirection: "column", children: [policy.mode === 'compact'
4916
- ? _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 })] })
4917
- : _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 })] });
4918
4947
  help = '↑↓ 移动 · Enter 打开/继续 · Esc 返回';
4919
4948
  }
4920
4949
  else if (route.name === 'dependencyPlacementEditor' && plugin) {
@@ -5152,6 +5181,18 @@ export function ConsoleAppV4(props) {
5152
5181
  { role: 'navigation', label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
5153
5182
  ];
5154
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
+ }
5155
5196
  const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
5156
5197
  const visibleTaskCandidates = candidates.slice(taskTargetOffset, taskTargetOffset + taskTargetLimit);
5157
5198
  const targetRows = visibleTaskCandidates.map((candidate) => {
@@ -5340,17 +5381,6 @@ export function ConsoleAppV4(props) {
5340
5381
  ...(installAction ? [installAction] : []),
5341
5382
  ...(settingsAction ? [{ ...settingsAction, actionId: 'settings' }] : []),
5342
5383
  ...(contentAction ? [contentAction] : []),
5343
- {
5344
- actionId: 'full-description',
5345
- label: '完整基本信息',
5346
- description: '阅读未截断的用途和平台状态',
5347
- run: () => {
5348
- setShowTechnical(true);
5349
- setBatchTargetOffset(0);
5350
- setFocus(0);
5351
- setScrollOffset(0);
5352
- },
5353
- },
5354
5384
  ...(dependencyAction ? [dependencyAction] : []),
5355
5385
  ...(uninstallAction ? [uninstallAction] : []),
5356
5386
  {
@@ -5364,63 +5394,49 @@ export function ConsoleAppV4(props) {
5364
5394
  && dependencyOverviewForDetail.summary.totalCount > 0
5365
5395
  ? `需要 ${dependencyOverviewForDetail.summary.totalCount} 个插件;当前可用关系按当前范围检查,未显示的关系尚未检查通过。`
5366
5396
  : undefined;
5367
- const detailPositions = plugin.installedPlacements
5368
- .filter((placement) => placement.state !== 'notInstalled')
5369
- .map((placement) => `${platformLabel(placement.platform)}:${pluginPlacementConclusions(placement).map((entry) => entry.text).join(' · ')}`);
5370
- const platformSummary = detailPositions.length > 0
5371
- ? detailPositions.join(';')
5372
- : plugin.supportedPlatforms.length > 0
5373
- ? `${plugin.supportedPlatforms.map(platformLabel).join('、')} · 尚未安装`
5374
- : '当前没有可用平台';
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(';') : '当前没有可用平台';
5375
5407
  context = '插件详情';
5376
- const completeDescriptionLines = [
5377
- '用途',
5378
- ...wrapConsoleText(plugin.description, Math.max(12, size.columns - 8)).map((line) => ` ${line}`),
5379
- ...wrapConsoleText(`版本 · ${versionSummary}`, Math.max(12, size.columns - 8)),
5380
- ...wrapConsoleText(`来源 · ${plugin.sourceLabel}`, Math.max(12, size.columns - 8)),
5381
- '平台状态',
5382
- ...(detailPositions.length > 0 ? detailPositions : [platformSummary])
5383
- .flatMap((line) => wrapConsoleText(line, Math.max(12, size.columns - 8)))
5384
- .map((line) => ` ${line}`),
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] : []),
5385
5417
  ];
5386
- if (showTechnical) {
5387
- const descriptionPageRows = responsiveBudget(5, 10);
5388
- const descriptionOffset = Math.min(batchTargetOffset, Math.max(0, completeDescriptionLines.length - 1));
5389
- const visibleDescriptionLines = completeDescriptionLines.slice(descriptionOffset, descriptionOffset + descriptionPageRows);
5390
- breadcrumb = `插件 / ${plugin.displayName} / 完整基本信息`;
5391
- context = `${descriptionOffset + 1}–${descriptionOffset + visibleDescriptionLines.length} / ${completeDescriptionLines.length}`;
5392
- title = '完整基本信息';
5393
- description = '以下是未截断的插件用途和已检查平台状态。';
5394
- actions = [
5395
- ...paginationActions({
5396
- offset: descriptionOffset,
5397
- total: completeDescriptionLines.length,
5398
- pageRows: descriptionPageRows,
5399
- move: (direction) => moveBatchTargetPage(completeDescriptionLines.length, descriptionPageRows, direction),
5400
- }),
5401
- { role: 'navigation', label: '返回插件详情', run: () => toggleTechnical() },
5402
- ];
5403
- referenceDetailPagination = {
5404
- total: completeDescriptionLines.length,
5405
- pageRows: descriptionPageRows,
5406
- };
5407
- const descriptionActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(visibleDescriptionLines.length + 2));
5408
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回';
5409
- content = _jsxs(Box, { flexDirection: "column", children: [descriptionOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${descriptionOffset} 行`, status: "muted", compact: true }) : null, visibleDescriptionLines.map((line, index) => _jsx(StaticRow, { title: line || ' ', compact: true }, `${descriptionOffset + index}:${line}`)), descriptionOffset + visibleDescriptionLines.length < completeDescriptionLines.length
5410
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${completeDescriptionLines.length - descriptionOffset - visibleDescriptionLines.length} 行`, status: "muted", compact: true })
5411
- : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: descriptionActionViewport.offset, limit: descriptionActionViewport.limit })] });
5412
- }
5413
- else {
5414
- const detailExtraRows = (updateDescription ? 1 : 0) + (dependencyDetailText ? 1 : 0)
5415
- + (snapshot.completionNotice?.pluginId === plugin.pluginId ? 4 : 0);
5416
- currentActionViewportLineBudget = pageActionBudget(detailExtraRows + 4 + Number(Boolean(plugin.portableSkills)) + Number(Boolean(plugin.configOnly)));
5417
- const detailViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
5418
- content = _jsxs(Box, { flexDirection: "column", children: [snapshot.completionNotice?.pluginId === plugin.pluginId
5419
- ? _jsx(Callout, { status: "success", title: snapshot.completionNotice.title, description: snapshot.completionNotice.description })
5420
- : null, _jsx(StaticRow, { title: plugin.description, compact: true }), _jsx(StaticRow, { title: `版本 · ${versionSummary}`, compact: true }), _jsx(StaticRow, { title: `来源 · ${plugin.sourceLabel}`, compact: true }), _jsx(StaticRow, { title: platformSummary, compact: true }), plugin.portableSkills ? _jsx(Text, { color: tuiTheme.colors.accent, children: "[Skills \u53EF\u9009]" }) : null, plugin.configOnly ? _jsx(Text, { color: tuiTheme.colors.muted, children: "[\u5DF2\u4FDD\u5B58\u8BBE\u7F6E]" }) : null, updateDescription
5421
- ? _jsx(StaticRow, { title: "\u6709\u53EF\u7528\u66F4\u65B0", meta: updateDescription, status: "warning", compact: true })
5422
- : 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 })] });
5423
- }
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);
5436
+ const detailViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
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 })] });
5424
5440
  }
5425
5441
  else if (route.name === 'pluginMore' && plugin) {
5426
5442
  breadcrumb = `插件 / ${plugin.displayName} / 更多操作`;
@@ -5880,12 +5896,14 @@ export function ConsoleAppV4(props) {
5880
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'
5881
5897
  ? field.invalidOverrideIssue ? '需修正' : fieldValueText(field, field.draftValue ?? field.effectiveValue, true, field.effectiveSourceLabel !== '未设置')
5882
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');
5883
5902
  const pageRows = Math.max(1, pageActionBudget(4));
5884
5903
  const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
5885
5904
  referenceDetailPagination = { total: lines.length, pageRows };
5886
- actions = paginationActions({ offset, total: lines.length, pageRows,
5887
- move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction) });
5888
- help = 'p/n 或 PgUp/PgDn 阅读 · Esc 返回';
5905
+ actions = [{ label: '返回设置', run: back }];
5906
+ help = '←→ 或 PgUp/PgDn 阅读 · Esc 返回';
5889
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 })] });
5890
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' })] });
5891
5909
  }
@@ -5902,6 +5920,7 @@ export function ConsoleAppV4(props) {
5902
5920
  })));
5903
5921
  if (route.view === 'changes') {
5904
5922
  const lines = changeDetailLines(changes, size.columns - 4);
5923
+ readingText = changeDetailText(changes);
5905
5924
  const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
5906
5925
  const detailOffset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
5907
5926
  const visibleLines = lines.slice(detailOffset, detailOffset + pageRows);
@@ -5910,15 +5929,9 @@ export function ConsoleAppV4(props) {
5910
5929
  context = `${Math.min(detailOffset + 1, lines.length)}–${Math.min(detailOffset + visibleLines.length, lines.length)} / ${lines.length}`;
5911
5930
  title = '完整设置变化';
5912
5931
  description = '逐项显示修改前和修改后的完整值。';
5913
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回保存汇总';
5932
+ help = '←→ 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回保存汇总';
5914
5933
  actions = [
5915
- ...paginationActions({
5916
- offset: detailOffset,
5917
- total: lines.length,
5918
- pageRows,
5919
- move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
5920
- }),
5921
- { role: 'navigation', label: '返回保存汇总', run: back },
5934
+ { label: '返回保存汇总', run: back },
5922
5935
  ];
5923
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
5924
5937
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${lines.length - detailOffset - visibleLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -6206,6 +6219,8 @@ export function ConsoleAppV4(props) {
6206
6219
  breadcrumb = `${unavailablePluginName} / 设置`;
6207
6220
  context = '暂时不可编辑';
6208
6221
  const diagnostic = configUnavailableDiagnostic(route);
6222
+ if (showTechnical)
6223
+ readingText = diagnosticDetailText(diagnostic);
6209
6224
  const invalidLayer = (route.invalidStores?.length ?? 0) > 0;
6210
6225
  const canRecoverSource = configUnavailableCanRecoverSource(route);
6211
6226
  title = diagnostic.title;
@@ -6248,7 +6263,7 @@ export function ConsoleAppV4(props) {
6248
6263
  total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
6249
6264
  pageRows: technicalPageRows,
6250
6265
  };
6251
- help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6266
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6252
6267
  }
6253
6268
  const unavailableActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(technicalPageRows + 6 + unsupportedRows.length));
6254
6269
  content = invalidLayer
@@ -6292,7 +6307,8 @@ export function ConsoleAppV4(props) {
6292
6307
  const unavailable = health === 'unavailable' || health === 'error' || health === 'recovery';
6293
6308
  return {
6294
6309
  label: source.id,
6295
- description: `${sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary)} · ${policy}`,
6310
+ description: sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary),
6311
+ sourceLabel: policy,
6296
6312
  meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`} · ${relativeCheckTime(freshness?.checkedAt)}`,
6297
6313
  tags: _jsxs(Box, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", source.primary ? '主来源' : transport, "] "] }), _jsx(StatusBadge, { items: [{ key: 'health', label: available ? '可用' : unavailable ? '不可用' : '未确认',
6298
6314
  symbol: available ? '✓' : unavailable ? '!' : '?', status: available ? 'success' : unavailable ? 'warning' : 'muted' }] }), freshness && freshness.state !== 'current' && freshness.state !== 'fixed'
@@ -6469,7 +6485,7 @@ export function ConsoleAppV4(props) {
6469
6485
  }]
6470
6486
  : [{
6471
6487
  actionId: 'source-check',
6472
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6488
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6473
6489
  description: '检查这个来源;检测到变化或异常时完整验证',
6474
6490
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6475
6491
  run: () => props.controller.recheckCatalogSource(route.sourceId),
@@ -6509,7 +6525,7 @@ export function ConsoleAppV4(props) {
6509
6525
  || freshness?.state === 'rollbackDetected'
6510
6526
  || freshness?.state === 'incompatible' ? [{
6511
6527
  actionId: 'source-check',
6512
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6528
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6513
6529
  description: '检查这个来源;检测到变化或异常时完整验证',
6514
6530
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6515
6531
  run: () => props.controller.recheckCatalogSource(route.sourceId),
@@ -6539,7 +6555,7 @@ export function ConsoleAppV4(props) {
6539
6555
  const sourceCount = snapshot.viewModel?.sourceTabs.find((source) => source.id === route.sourceId)?.count;
6540
6556
  const checkAction = {
6541
6557
  actionId: 'source-check',
6542
- label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6558
+ label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6543
6559
  description: '检查这个来源;检测到变化或异常时完整验证;不会自动启用更新',
6544
6560
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6545
6561
  run: () => props.controller.recheckCatalogSource(route.sourceId),
@@ -6666,7 +6682,7 @@ export function ConsoleAppV4(props) {
6666
6682
  : detail?.primary ? '主来源不能直接移除' : undefined,
6667
6683
  run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId,
6668
6684
  changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6669
- { label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6685
+ { label: freshness?.state === 'checking' ? '正在检查' : '检查来源 [r]',
6670
6686
  description: '检查这个来源;检测到变化或异常时完整验证;不会自动启用更新',
6671
6687
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6672
6688
  run: () => props.controller.recheckCatalogSource(route.sourceId) },
@@ -6683,18 +6699,16 @@ export function ConsoleAppV4(props) {
6683
6699
  }
6684
6700
  else if (route.name === 'sourceTechnical') {
6685
6701
  const diagnostic = resolveSourceTechnicalDiagnostic(route.sourceId);
6702
+ readingText = diagnosticDetailText(diagnostic);
6686
6703
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
6687
6704
  const lines = diagnosticDetailLines(diagnostic, size.columns - 6);
6688
6705
  const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - sourceTechnicalPageRows));
6689
6706
  context = `${offset + 1}–${Math.min(lines.length, offset + sourceTechnicalPageRows)} / ${lines.length} 行`;
6690
6707
  title = '来源技术诊断';
6691
6708
  description = '';
6692
- help = '↑↓ 选择翻页 · Enter 执行 · p/n 翻页 · Esc 返回';
6693
- const sourceTechnicalLastOffset = Math.max(0, lines.length - sourceTechnicalPageRows);
6709
+ help = '←→ 或 PgUp/PgDn 翻页 · Esc 返回';
6694
6710
  actions = [
6695
- { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalPageRows)) },
6696
- { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalPageRows)) },
6697
- { role: 'navigation', label: '返回来源详情', run: back },
6711
+ { label: '返回来源详情', run: back },
6698
6712
  ];
6699
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 })] });
6700
6714
  }
@@ -6834,6 +6848,7 @@ export function ConsoleAppV4(props) {
6834
6848
  const changes = sourceDraftChanges(sourceDraft);
6835
6849
  if (route.view === 'changes') {
6836
6850
  const lines = changeDetailLines(changes, size.columns - 4);
6851
+ readingText = changeDetailText(changes);
6837
6852
  const pageRows = Math.max(1, Math.min(responsiveBudget(5, 10), pageActionBudget(5)));
6838
6853
  const detailOffset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
6839
6854
  const visibleLines = lines.slice(detailOffset, detailOffset + pageRows);
@@ -6842,15 +6857,9 @@ export function ConsoleAppV4(props) {
6842
6857
  context = `${Math.min(detailOffset + 1, lines.length)}–${Math.min(detailOffset + visibleLines.length, lines.length)} / ${lines.length}`;
6843
6858
  title = '完整来源变化';
6844
6859
  description = '逐项显示修改前和修改后的完整值。';
6845
- help = 'p/n 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回修改汇总';
6860
+ help = '←→ 或 PgUp/PgDn 阅读 · ↑↓ 移动 · Enter 选择 · Esc 返回修改汇总';
6846
6861
  actions = [
6847
- ...paginationActions({
6848
- offset: detailOffset,
6849
- total: lines.length,
6850
- pageRows,
6851
- move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
6852
- }),
6853
- { role: 'navigation', label: '返回修改汇总', run: back },
6862
+ { label: '返回修改汇总', run: back },
6854
6863
  ];
6855
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
6856
6865
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${lines.length - detailOffset - visibleLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -6906,18 +6915,14 @@ export function ConsoleAppV4(props) {
6906
6915
  const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
6907
6916
  const referenceLimit = Math.max(1, pageActionBudget(12));
6908
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');
6909
6920
  const referenceOffset = Math.min(batchTargetOffset, Math.max(0, references.length - 1));
6910
6921
  const visibleReferences = references.slice(referenceOffset, referenceOffset + referenceLimit);
6911
6922
  const hiddenReferences = Math.max(0, references.length - referenceOffset - visibleReferences.length);
6912
6923
  if (sourceDependenciesExpanded && references.length > referenceLimit) {
6913
- actions.unshift(...paginationActions({
6914
- offset: referenceOffset,
6915
- total: references.length,
6916
- pageRows: referenceLimit,
6917
- move: (direction) => moveBatchTargetPage(references.length, referenceLimit, direction),
6918
- }));
6919
6924
  referenceDetailPagination = { total: references.length, pageRows: referenceLimit };
6920
- help = 'p/n 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6925
+ help = '←→ 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6921
6926
  }
6922
6927
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: sourceDependenciesExpanded
6923
6928
  ? referenceOffset > 0 ? `引用内容 · ↑ 前面还有 ${referenceOffset} 项` : '引用内容'
@@ -7035,21 +7040,14 @@ export function ConsoleAppV4(props) {
7035
7040
  run: () => runIssueAction(entry),
7036
7041
  }));
7037
7042
  const issueDetailRows = diagnosticDetailLines(issue.diagnostic, size.columns - 6).length;
7043
+ if (showTechnical)
7044
+ readingText = diagnosticDetailText(issue.diagnostic);
7038
7045
  const issueDetailPageRows = policy.mode === 'compact'
7039
7046
  ? 1
7040
7047
  : Math.max(1, responsiveBudget(1, 4));
7041
- const issuePaginationActions = showTechnical && issueDetailRows > issueDetailPageRows
7042
- ? paginationActions({
7043
- offset: batchTargetOffset,
7044
- total: issueDetailRows,
7045
- pageRows: issueDetailPageRows,
7046
- move: (direction) => moveBatchTargetPage(issueDetailRows, issueDetailPageRows, direction),
7047
- })
7048
- : [];
7049
- actions.push(...issuePaginationActions);
7050
- if (issuePaginationActions.length > 0) {
7048
+ if (showTechnical && issueDetailRows > issueDetailPageRows) {
7051
7049
  referenceDetailPagination = { total: issueDetailRows, pageRows: issueDetailPageRows };
7052
- help = 'p/n 或 PgUp/PgDn 阅读全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7050
+ help = '←→ 或 PgUp/PgDn 阅读全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7053
7051
  }
7054
7052
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
7055
7053
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
@@ -7070,7 +7068,6 @@ export function ConsoleAppV4(props) {
7070
7068
  { label: '查看完整依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
7071
7069
  { label: '返回插件详情', run: () => go({ name: 'pluginDetail', pluginId: issuePlugin.pluginId }) },
7072
7070
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
7073
- ...issuePaginationActions,
7074
7071
  ];
7075
7072
  const dependencyBlockedViewport = policy.mode === 'compact' && showTechnical
7076
7073
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -7111,7 +7108,6 @@ export function ConsoleAppV4(props) {
7111
7108
  } },
7112
7109
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
7113
7110
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
7114
- ...issuePaginationActions,
7115
7111
  ];
7116
7112
  const unsupportedTargetViewport = policy.mode === 'compact' && showTechnical
7117
7113
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -7165,21 +7161,22 @@ export function ConsoleAppV4(props) {
7165
7161
  else if (route.name === 'operationDiagnostic') {
7166
7162
  const detail = props.controller.getOperationDiagnostic(route.recordId);
7167
7163
  breadcrumb = '操作记录 / 技术详情';
7168
- context = 'p 上一页 · n 下一页';
7164
+ context = '安全详情';
7169
7165
  title = detail ? '安全技术详情' : '操作记录已不可用';
7170
7166
  description = detail
7171
7167
  ? '可滚动查看所有目标、阶段、代码和已保留的安全原因。'
7172
7168
  : '这项会话记录已经不再可用。';
7173
7169
  const preview = detail ? props.controller.previewOperationReport(route.recordId) : undefined;
7174
7170
  const detailLineLimit = operationDetailPageRows;
7175
- const detailLines = detail ? operationDiagnosticLineCount(detail, size.columns) : 0;
7176
- const lastDetailOffset = Math.max(0, detailLines - detailLineLimit);
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
+ }
7177
7176
  actions = detail ? [
7178
7177
  preview
7179
7178
  ? { label: '预览安全报告', description: '确认内容后选择本地保存位置', run: () => go({ name: 'operationReportPreview', recordId: route.recordId }) }
7180
7179
  : { label: '报告当前不可用', disabled: '无法生成这项记录的报告' },
7181
- { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - detailLineLimit)) },
7182
- { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastDetailOffset, value + detailLineLimit)) },
7183
7180
  { role: 'navigation', label: '返回操作记录', run: back },
7184
7181
  ] : [{ role: 'navigation', label: '返回操作记录', run: back }];
7185
7182
  const diagnosticActionViewport = actionViewport(actions, policy.mode === 'compact', focus, 0, policy.mode === 'compact' ? 2 : 3);
@@ -7188,7 +7185,7 @@ export function ConsoleAppV4(props) {
7188
7185
  else if (route.name === 'operationReportPreview') {
7189
7186
  const preview = props.controller.previewOperationReport(route.recordId);
7190
7187
  breadcrumb = '操作记录 / 报告预览';
7191
- context = 'p 上一页 · n 下一页';
7188
+ context = '安全报告';
7192
7189
  title = reportSaveMode ? '选择报告保存位置' : preview ? '安全报告预览' : '报告已不可用';
7193
7190
  description = reportSaveMode
7194
7191
  ? '输入一个新的本地文件路径,然后按 Enter 保存。'
@@ -7200,9 +7197,10 @@ export function ConsoleAppV4(props) {
7200
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 })] });
7201
7198
  }
7202
7199
  else if (preview) {
7203
- const reportLineLimit = operationDetailPageRows;
7204
- const reportLines = operationReportLineCount(preview, size.columns);
7205
- 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} 行`;
7206
7204
  actions = [
7207
7205
  { label: '选择本地保存位置', description: '保存前输入明确的文件路径', run: () => {
7208
7206
  setReportSaveMode(true);
@@ -7217,8 +7215,6 @@ export function ConsoleAppV4(props) {
7217
7215
  setReportSaveError('报告未保存。请检查保存位置后重试。');
7218
7216
  });
7219
7217
  } },
7220
- { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - reportLineLimit)) },
7221
- { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastReportOffset, value + reportLineLimit)) },
7222
7218
  { role: 'navigation', label: '返回技术详情', run: back },
7223
7219
  ];
7224
7220
  const reportFeedbackVisible = Boolean(reportSaveResult || reportSaveError);
@@ -7283,6 +7279,17 @@ export function ConsoleAppV4(props) {
7283
7279
  }
7284
7280
  else if (route.name === 'review' && snapshot.activeReview) {
7285
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');
7286
7293
  const technicalDetails = (review.technicalDetails ?? []).flatMap((detail) => review.operationKind === 'catalogGeneration' && detail.startsWith('引用影响:')
7287
7294
  ? wrapConsoleText(detail, Math.max(20, size.columns - 10))
7288
7295
  : [detail]);
@@ -7492,12 +7499,6 @@ export function ConsoleAppV4(props) {
7492
7499
  : userReviewChanges.length;
7493
7500
  const reviewPageRows = Math.max(1, responsiveBudget(6, 10) - actions.length - 2);
7494
7501
  if (reviewPaginationTotal > reviewPageRows) {
7495
- actions.push(...paginationActions({
7496
- offset: batchTargetOffset,
7497
- total: reviewPaginationTotal,
7498
- pageRows: 1,
7499
- move: (direction) => moveBatchTargetPage(reviewPaginationTotal, 1, direction),
7500
- }));
7501
7502
  referenceDetailPagination = { total: reviewPaginationTotal, pageRows: 1 };
7502
7503
  }
7503
7504
  const reviewActionViewport = actionViewport(actions, true, focus, scrollOffset, responsiveBudget(2, 6));
@@ -7696,6 +7697,11 @@ export function ConsoleAppV4(props) {
7696
7697
  : entry.state === 'skipped' ? 3 : 4;
7697
7698
  return priority(left) - priority(right);
7698
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');
7699
7705
  const progressStepRows = policy.mode === 'compact' ? 4 : 5;
7700
7706
  const progressTargetAndMessageBudget = pageActionBudget()
7701
7707
  - 2
@@ -7709,7 +7715,10 @@ export function ConsoleAppV4(props) {
7709
7715
  const progressTargetBudget = progressTargetAndMessageBudget - dependencyMessageLimit;
7710
7716
  const progressTargetLimit = prefixRowLimit(prioritizedProgressTargets.map(() => policy.mode === 'compact' ? 1 : 2), progressTargetBudget);
7711
7717
  help = prioritizedProgressTargets.length > progressTargetLimit
7712
- ? 'PgUp/PgDn 查看全部目标 · Ctrl-C 完成后退出' : 'Ctrl-C 完成后退出';
7718
+ ? '←→ 翻页 · [y] 复制全文 · Ctrl-C 完成后退出'
7719
+ : '[y] 复制全文 · Ctrl-C 完成后退出';
7720
+ if (copyFeedback?.routeKey === routeKey)
7721
+ description = copyFeedback.message;
7713
7722
  const progressTargetOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedProgressTargets.length - 1));
7714
7723
  const visibleProgressTargets = prioritizedProgressTargets.slice(progressTargetOffset, progressTargetOffset + progressTargetLimit);
7715
7724
  const hiddenProgressTargets = Math.max(0, snapshot.progressTargets.length - progressTargetOffset - visibleProgressTargets.length);
@@ -8240,6 +8249,16 @@ export function ConsoleAppV4(props) {
8240
8249
  },
8241
8250
  }] : []), { role: !partial && !blocked ? 'navigation' : undefined,
8242
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
+ }
8243
8262
  if (failedCleanupTargets.length > 0) {
8244
8263
  actions.unshift({
8245
8264
  label: '查看清理建议',
@@ -8350,23 +8369,36 @@ export function ConsoleAppV4(props) {
8350
8369
  ? wrapPortableField('materialization digest', portableReceipt.materializationDigest) : []),
8351
8370
  ...(resultSummary?.elapsedMs !== undefined ? [`elapsed · ${resultSummary.elapsedMs} ms`] : []),
8352
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');
8353
8394
  const portableResultDetailPageRows = responsiveBudget(5, 10);
8354
8395
  const safePortableResultDetailOffset = Math.min(portableResultDetailOffset, Math.max(0, portableResultDetailLines.length - 1));
8355
8396
  const visiblePortableResultDetailLines = portableResultDetailLines.slice(safePortableResultDetailOffset, safePortableResultDetailOffset + portableResultDetailPageRows);
8356
8397
  if (portableOperation && portableResultDetailOpen) {
8357
8398
  title = '当前结果完整字段';
8358
8399
  description = '按行显示当前 Skill、Agent、状态和技术证据。';
8359
- help = 'p/n 阅读全部字段 · ↑↓ 移动 · Enter 选择 · Esc 返回结果摘要';
8360
- const detailPaginationActions = portableResultDetailLines.length > portableResultDetailPageRows
8361
- ? paginationActions({
8362
- offset: safePortableResultDetailOffset,
8363
- total: portableResultDetailLines.length,
8364
- pageRows: portableResultDetailPageRows,
8365
- move: (direction) => setPortableResultDetailOffset((value) => Math.min(Math.max(0, portableResultDetailLines.length - 1), Math.max(0, value + direction * portableResultDetailPageRows))),
8366
- })
8367
- : [];
8400
+ help = '←→ 阅读全部字段 · ↑↓ 移动 · Enter 选择 · Esc 返回结果摘要';
8368
8401
  actions = [
8369
- ...detailPaginationActions,
8370
8402
  { label: '返回结果摘要', run: () => {
8371
8403
  setPortableResultDetailOpen(false);
8372
8404
  setPortableResultDetailOffset(0);
@@ -8392,29 +8424,16 @@ export function ConsoleAppV4(props) {
8392
8424
  setScrollOffset(0);
8393
8425
  },
8394
8426
  } : undefined;
8395
- const pagination = resultPaginationTotal > 1 ? paginationActions({
8396
- offset: batchTargetOffset,
8397
- total: resultPaginationTotal,
8398
- pageRows: resultPageRows,
8399
- move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8400
- }) : [];
8401
8427
  actions = [
8402
8428
  ...actions.slice(0, 1),
8403
- ...pagination,
8404
8429
  ...(detailAction ? [detailAction] : []),
8405
8430
  ...actions.slice(1),
8406
8431
  ];
8407
- if (pagination.length > 0) {
8432
+ if (resultPaginationTotal > resultPageRows) {
8408
8433
  referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8409
8434
  }
8410
8435
  }
8411
8436
  else if (resultPaginationTotal > 1) {
8412
- actions.push(...paginationActions({
8413
- offset: batchTargetOffset,
8414
- total: resultPaginationTotal,
8415
- pageRows: resultPageRows,
8416
- move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8417
- }));
8418
8437
  referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8419
8438
  }
8420
8439
  const portableTechnicalFieldCount = portableReceipt
@@ -8508,7 +8527,7 @@ export function ConsoleAppV4(props) {
8508
8527
  const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
8509
8528
  return _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`);
8510
8529
  });
8511
- const taskLocationRows = taskRootResults.flatMap((root) => {
8530
+ const taskLocationItems = taskRootResults.flatMap((root) => {
8512
8531
  return root.targets.map((target) => {
8513
8532
  const targetPluginId = target.pluginId ?? root.pluginId;
8514
8533
  const key = [targetPluginId, target.platform, target.scope].join('\0');
@@ -8520,11 +8539,29 @@ export function ConsoleAppV4(props) {
8520
8539
  : { label: '未开始', status: 'muted' };
8521
8540
  const relation = target.relation === 'direct' ? '直接依赖'
8522
8541
  : target.relation === 'transitive' ? '间接依赖' : '所选插件位置';
8523
- return _jsx(ResultRow, { title: `${root.pluginId}${targetPluginId === root.pluginId ? '' : ` → ${targetPluginId}`} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
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
8524
8546
  ? pluginWriteTaskBlockLabel(blockedTarget.reason)
8525
- : `${relation}${target.errorCode ? ` · ${target.errorCode}` : ''}`, meta: `${targetState.label}${target.errorCode ? ` · ${target.errorCode}` : ''}`, status: targetState.status, compact: !blockedTarget }, `target:${root.pluginId}:${key}`);
8547
+ : `${relation}${target.errorCode ? ` · ${target.errorCode}` : ''}`,
8548
+ meta: `${targetState.label}${target.errorCode ? ` · ${target.errorCode}` : ''}`,
8549
+ status: targetState.status,
8550
+ compact: !blockedTarget,
8551
+ };
8526
8552
  });
8527
8553
  });
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');
8528
8565
  const batchPluginResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
8529
8566
  const rootState = taskRootState(root.state);
8530
8567
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
@@ -8550,7 +8587,7 @@ export function ConsoleAppV4(props) {
8550
8587
  const visibleLocationResultRows = locationResultRows.slice(visibleLocationStart, visibleLocationStart + remainingResultRows);
8551
8588
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
8552
8589
  || prioritizedPortablePairs.length > portableSkillLimit) {
8553
- help = 'p/n 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8590
+ help = '←→ 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8554
8591
  }
8555
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
8556
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
@@ -8638,19 +8675,14 @@ export function ConsoleAppV4(props) {
8638
8675
  ] : []),
8639
8676
  ...plugin.installedPlacements.map((placement) => `${platformLabel(placement.platform)} target · ${placementStatus(placement).value}`),
8640
8677
  ];
8678
+ readingText = technicalFields.join('\n');
8641
8679
  const technicalLines = technicalFields.flatMap((field) => wrapDisplayText(field, size.columns - 6));
8642
8680
  const technicalPageRows = Math.max(1, pageActionBudget(7));
8643
8681
  const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
8644
8682
  const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
8645
8683
  if (technicalLines.length > technicalPageRows) {
8646
- actions.push(...paginationActions({
8647
- offset: technicalOffset,
8648
- total: technicalLines.length,
8649
- pageRows: technicalPageRows,
8650
- move: (direction) => moveBatchTargetPage(technicalLines.length, technicalPageRows, direction),
8651
- }));
8652
8684
  referenceDetailPagination = { total: technicalLines.length, pageRows: technicalPageRows };
8653
- help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8685
+ help = '←→ 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8654
8686
  }
8655
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
8656
8688
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -8752,8 +8784,8 @@ export function ConsoleAppV4(props) {
8752
8784
  : 'h/H 完成后导航'} · Esc 撤销等待 · ${help}`
8753
8785
  : `h 首页 · H 重载 · ${help}`;
8754
8786
  }
8755
- const helpParts = help.split(' · ').map((part) => part.startsWith('p/n ') ? 'p/n 阅读' : part);
8756
- const isRoutineKey = (part) => /^(↑|Enter|Space|Esc|Ctrl-C|p\/n)/u.test(part);
8787
+ const helpParts = help.split(' · ');
8788
+ const isRoutineKey = (part) => /^(↑|Enter|Space|Esc|Ctrl-C|←→)/u.test(part);
8757
8789
  help = [...helpParts.filter(isRoutineKey), ...helpParts.filter((part) => !isRoutineKey(part))].join(' · ');
8758
8790
  if (searchFocus)
8759
8791
  help = '←→ 光标 · ↑↓ 移动结果 · Enter/Esc 完成输入';
@@ -8770,6 +8802,16 @@ export function ConsoleAppV4(props) {
8770
8802
  || route.name === 'batchSelection' && batchDraft?.stage === 'select'
8771
8803
  ? actions.filter((action) => action.pluginId !== undefined) : [];
8772
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]);
8773
8815
  useEffect(() => {
8774
8816
  if (route.name !== 'pluginList' && !(route.name === 'batchSelection' && batchDraft?.stage === 'select')) {
8775
8817
  listFocusAnchor.current = undefined;
@@ -8963,10 +9005,19 @@ export function ConsoleAppV4(props) {
8963
9005
  }
8964
9006
  if (busy) {
8965
9007
  const sessionNavigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: false });
8966
- if ((key.pageUp || key.pageDown) && route.name === 'progress') {
9008
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9009
+ && route.name === 'progress' && !overlay) {
8967
9010
  const total = snapshot.progressTargets.length;
8968
9011
  const pageRows = 1;
8969
- 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
+ });
8970
9021
  }
8971
9022
  else if (overlay === 'help') {
8972
9023
  if (key.escape || key.return || input.toLowerCase() === 'b' || input === '?') {
@@ -9072,31 +9123,31 @@ export function ConsoleAppV4(props) {
9072
9123
  closeConfirmation();
9073
9124
  requestSessionNavigation(navigation);
9074
9125
  }
9075
- else if ((key.pageUp || key.pageDown
9076
- || 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)
9077
9135
  && confirmationPagination) {
9078
9136
  const warningCount = confirmationPagination.total;
9079
9137
  const warningLimit = confirmationPagination.pageRows;
9080
9138
  setOperationConfirmation((current) => current ? {
9081
9139
  ...current,
9082
9140
  warningOffset: Math.min(Math.max(0, warningCount - 1), Math.max(0, current.warningOffset
9083
- + (key.pageUp || input.toLowerCase() === 'p' ? -warningLimit : warningLimit))),
9141
+ + (key.pageUp || key.leftArrow ? -warningLimit : warningLimit))),
9084
9142
  } : current);
9085
9143
  }
9086
9144
  else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9087
- 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));
9088
9146
  }
9089
9147
  else if (key.return) {
9090
9148
  if (overlayFocus === 0) {
9091
9149
  closeConfirmation();
9092
9150
  }
9093
- else if (confirmationPagination && (overlayFocus === 2 || overlayFocus === 3)) {
9094
- const direction = overlayFocus === 2 ? -1 : 1;
9095
- setOperationConfirmation((current) => current ? {
9096
- ...current,
9097
- warningOffset: Math.min(Math.max(0, confirmationPagination.total - 1), Math.max(0, current.warningOffset + direction * confirmationPagination.pageRows)),
9098
- } : current);
9099
- }
9100
9151
  else if (activeReview?.reviewToken !== operationConfirmation.reviewToken) {
9101
9152
  closeConfirmation();
9102
9153
  setActionError('确认内容已变化。请重新检查操作汇总。');
@@ -9266,22 +9317,31 @@ export function ConsoleAppV4(props) {
9266
9317
  setReportSaveError(undefined);
9267
9318
  return;
9268
9319
  }
9269
- 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)
9270
9330
  && route.name === 'result' && portableResultDetailOpen
9271
9331
  && portableResultDetailPagination) {
9272
9332
  const { total, pageRows } = portableResultDetailPagination;
9273
- const direction = key.pageUp || input.toLowerCase() === 'p' ? -1 : 1;
9333
+ const direction = key.pageUp || key.leftArrow ? -1 : 1;
9274
9334
  setPortableResultDetailOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + direction * pageRows)));
9275
9335
  return;
9276
9336
  }
9277
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9337
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9278
9338
  && route.name === 'portableDependencies' && plugin) {
9279
9339
  const total = dependencyOverview(plugin)?.dependencies.length ?? 0;
9280
9340
  const pageRows = showTechnical ? 1 : responsiveBudget(2, 3);
9281
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p' ? -pageRows : pageRows))));
9341
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || key.leftArrow ? -pageRows : pageRows))));
9282
9342
  return;
9283
9343
  }
9284
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9344
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9285
9345
  && (route.name === 'pluginContent'
9286
9346
  || route.name === 'pluginTechnical'
9287
9347
  || route.name === 'projectStatus' && showTechnical
@@ -9291,7 +9351,6 @@ export function ConsoleAppV4(props) {
9291
9351
  || route.name === 'sourceRemoveBlocked' && sourceDependenciesExpanded
9292
9352
  || route.name === 'pluginDependencies' && route.view === 'removeBlocked' && !showTechnical
9293
9353
  || route.name === 'pluginDependencyNode'
9294
- && (route.view === 'references' || route.view === 'details')
9295
9354
  || route.name === 'pluginDetail'
9296
9355
  || route.name === 'configSave' && route.view === 'changes'
9297
9356
  || route.name === 'configFields' && route.view === 'values'
@@ -9300,21 +9359,21 @@ export function ConsoleAppV4(props) {
9300
9359
  || route.name === 'result')
9301
9360
  && referenceDetailPagination) {
9302
9361
  const { total, pageRows } = referenceDetailPagination;
9303
- moveBatchTargetPage(total, pageRows, key.pageUp || input.toLowerCase() === 'p' ? -1 : 1);
9362
+ moveBatchTargetPage(total, pageRows, key.pageUp || key.leftArrow ? -1 : 1);
9304
9363
  return;
9305
9364
  }
9306
- if ((key.pageUp || key.pageDown)
9365
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9307
9366
  && route.name === 'pluginWriteTask') {
9308
9367
  const total = props.controller.listPluginWriteTaskCandidates(route.goal)
9309
9368
  .filter((candidate) => candidate.target.pluginId === route.pluginId
9310
9369
  && (!route.targetKeys || route.targetKeys.includes(pluginWriteTaskTargetKey(candidate)))).length;
9311
- 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))));
9312
9371
  return;
9313
9372
  }
9314
- if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9373
+ if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9315
9374
  && route.name === 'sourceTechnical') {
9316
9375
  const total = diagnosticDetailLines(resolveSourceTechnicalDiagnostic(route.sourceId), size.columns - 6).length;
9317
- 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
9318
9377
  ? -sourceTechnicalPageRows : sourceTechnicalPageRows))));
9319
9378
  return;
9320
9379
  }
@@ -9338,15 +9397,16 @@ export function ConsoleAppV4(props) {
9338
9397
  return;
9339
9398
  }
9340
9399
  if ((route.name === 'operationDiagnostic' || route.name === 'operationReportPreview')
9341
- && (key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')) {
9400
+ && (key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)) {
9342
9401
  const detail = route.name === 'operationDiagnostic'
9343
9402
  ? props.controller.getOperationDiagnostic(route.recordId) : undefined;
9344
9403
  const preview = route.name === 'operationReportPreview'
9345
9404
  ? props.controller.previewOperationReport(route.recordId) : undefined;
9346
9405
  const total = detail ? operationDiagnosticLineCount(detail, size.columns)
9347
9406
  : preview ? operationReportLineCount(preview, size.columns) : 0;
9348
- const pageRows = operationDetailPageRows;
9349
- setScrollOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p' ? -pageRows : pageRows))));
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))));
9350
9410
  return;
9351
9411
  }
9352
9412
  if (route.name === 'portableAgentInput') {
@@ -9764,12 +9824,6 @@ export function ConsoleAppV4(props) {
9764
9824
  setFocus(portableResultDetailReturnFocus.current);
9765
9825
  setScrollOffset(0);
9766
9826
  }
9767
- else if (globalAction === 'back' && route.name === 'pluginDetail' && showTechnical) {
9768
- setShowTechnical(false);
9769
- setBatchTargetOffset(0);
9770
- setFocus(0);
9771
- setScrollOffset(0);
9772
- }
9773
9827
  else if (globalAction === 'back' && route.name === 'portableSkillSelect' && portableToolsOpen) {
9774
9828
  setPortableToolsOpen(false);
9775
9829
  setFocus(0);
@@ -9777,13 +9831,14 @@ export function ConsoleAppV4(props) {
9777
9831
  }
9778
9832
  else if (globalAction === 'back' && route.name === 'batchSelection'
9779
9833
  && batchDraft?.stage === 'select' && batchDraft.showSelectionTools) {
9780
- setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
9781
- setFocus(0);
9782
- setScrollOffset(0);
9834
+ closeBatchSelectionTools();
9835
+ }
9836
+ else if (globalAction === 'back' && route.name === 'installPlacement'
9837
+ && returnToPreviousInstallPlatform) {
9838
+ returnToPreviousInstallPlatform();
9783
9839
  }
9784
9840
  else if (globalAction === 'back'
9785
- && (route.name === 'configSave' || route.name === 'sourceSave')
9786
- && route.view === 'changes') {
9841
+ && (route.name === 'configSave' || route.name === 'sourceSave' && route.view === 'changes')) {
9787
9842
  back();
9788
9843
  }
9789
9844
  else if (globalAction === 'back' && route.name === 'configLayer') {
@@ -10032,6 +10087,19 @@ export function ConsoleAppV4(props) {
10032
10087
  if (tooSmall && !overlay) {
10033
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" }) }));
10034
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
+ }
10035
10103
  if (actionError)
10036
10104
  description = actionError;
10037
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 }));
@@ -10087,12 +10155,13 @@ export function ConsoleAppV4(props) {
10087
10155
  const visibleWarnings = shortConfirmation
10088
10156
  ? warningLines : warningLines.slice(warningOffset, warningOffset + warningLimit);
10089
10157
  const warningPaginated = !shortConfirmation && warningLines.length > warningLimit;
10090
- const moveWarningPage = (direction) => {
10091
- setOperationConfirmation((current) => current ? {
10092
- ...current,
10093
- warningOffset: Math.min(Math.max(0, warningLines.length - 1), Math.max(0, current.warningOffset + direction * warningLimit)),
10094
- } : current);
10095
- };
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');
10096
10165
  if (warningPaginated) {
10097
10166
  confirmationPagination = { total: warningLines.length, pageRows: warningLimit };
10098
10167
  }
@@ -10102,14 +10171,9 @@ export function ConsoleAppV4(props) {
10102
10171
  label: portableUserConfirmation ? '确认并执行' : `我已了解影响,${review.primaryLabel}`,
10103
10172
  status: 'error',
10104
10173
  },
10105
- ...(warningPaginated ? paginationActions({
10106
- offset: warningOffset,
10107
- total: warningLines.length,
10108
- pageRows: warningLimit,
10109
- move: moveWarningPage,
10110
- }) : []),
10111
10174
  ];
10112
- 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
10113
10177
  ? visibleWarnings.map((warning, index) => _jsx(StaticRow, { title: warning, status: warning.startsWith('! ') ? 'warning' : 'info', compact: true }, `${warningOffset + index}:${warning}`))
10114
10178
  : _jsx(StaticRow, { title: "! \u6B64\u64CD\u4F5C\u9700\u8981\u660E\u786E\u6388\u6743", status: "warning", compact: true }), !shortConfirmation && warningLines.length - warningOffset - visibleWarnings.length > 0
10115
10179
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${warningLines.length - warningOffset - visibleWarnings.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: confirmationActions, focus: overlayFocus, kind: "action", compact: true })] }));
@@ -10118,7 +10182,7 @@ export function ConsoleAppV4(props) {
10118
10182
  }
10119
10183
  return _jsx(ScreenFrame, { title: review.operationKind === 'catalogStorePrune'
10120
10184
  ? `确认${review.title}?`
10121
- : `确认${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 });
10122
10186
  }
10123
10187
  if (sourceTransportOverlay) {
10124
10188
  const configuredSource = route.name === 'sourceEdit'