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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  2. package/dist/authoring/scripts/validate-plugin.mjs +5 -2
  3. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  4. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  5. package/dist/src/commands/aw-capabilities.js +2 -0
  6. package/dist/src/commands/aw-capabilities.js.map +1 -1
  7. package/dist/src/tui/catalog-sources/session.d.ts +5 -1
  8. package/dist/src/tui/catalog-sources/session.js +6 -1
  9. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  10. package/dist/src/tui/catalog-sources/types.d.ts +1 -0
  11. package/dist/src/tui/components.d.ts +1 -11
  12. package/dist/src/tui/components.js +9 -41
  13. package/dist/src/tui/components.js.map +1 -1
  14. package/dist/src/tui/config-value-presentation.d.ts +19 -0
  15. package/dist/src/tui/config-value-presentation.js +77 -0
  16. package/dist/src/tui/config-value-presentation.js.map +1 -0
  17. package/dist/src/tui/console-app-v4.d.ts +1 -1
  18. package/dist/src/tui/console-app-v4.js +885 -1121
  19. package/dist/src/tui/console-app-v4.js.map +1 -1
  20. package/dist/src/tui/controller.d.ts +8 -0
  21. package/dist/src/tui/controller.js +85 -18
  22. package/dist/src/tui/controller.js.map +1 -1
  23. package/dist/src/tui/diagnostics.d.ts +2 -1
  24. package/dist/src/tui/diagnostics.js +8 -1
  25. package/dist/src/tui/diagnostics.js.map +1 -1
  26. package/dist/src/tui/input-display.d.ts +1 -0
  27. package/dist/src/tui/input-display.js +13 -0
  28. package/dist/src/tui/input-display.js.map +1 -1
  29. package/dist/src/tui/model.d.ts +3 -2
  30. package/dist/src/tui/model.js +17 -33
  31. package/dist/src/tui/model.js.map +1 -1
  32. package/dist/src/tui/operation-diagnostic-screens.d.ts +5 -3
  33. package/dist/src/tui/operation-diagnostic-screens.js +26 -9
  34. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -1
  35. package/dist/src/tui/operation-history.js +5 -4
  36. package/dist/src/tui/operation-history.js.map +1 -1
  37. package/dist/src/tui/router.d.ts +4 -6
  38. package/dist/src/tui/router.js.map +1 -1
  39. package/dist/src/tui/screen-registry.d.ts +10 -19
  40. package/dist/src/tui/screen-registry.js +3 -4
  41. package/dist/src/tui/screen-registry.js.map +1 -1
  42. package/dist/src/tui/viewport.d.ts +1 -0
  43. package/dist/src/tui/viewport.js +23 -0
  44. package/dist/src/tui/viewport.js.map +1 -1
  45. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { presentConfigValue, safeConfigText } from './config-value-presentation.js';
2
3
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
4
  import { useEffect, useRef, useState } from 'react';
4
5
  import path from 'node:path';
@@ -11,7 +12,7 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
11
12
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
12
13
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
13
14
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
14
- import { ActionRow, ObjectCardRow, objectCardRowLayout, StatusBadge, diagnosticDetailLines, Callout, ChoiceRow, ContentViewport, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, PinnedActionBar, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
15
+ import { ActionRow, ObjectCardRow, objectCardRowLayout, 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
16
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
16
17
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
17
18
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
@@ -22,10 +23,10 @@ import { buildDependencyGraphProjection, buildUnifiedDependencyGraphProjection,
22
23
  import { platformLabel, scopeLabel } from './glossary.js';
23
24
  import { InputDisplay, removeLastGrapheme } from './input-display.js';
24
25
  import { catalogRefPolicyForPreset } from './source-ref-presets.js';
25
- import { buildPlatformActions, buildPluginDetailActions, configObjectHasSensitiveChild, hasInstalledPlacement, configValuesEqual, } from './model.js';
26
+ import { buildPlatformActions, configObjectHasSensitiveChild, hasInstalledPlacement, configValuesEqual, } from './model.js';
26
27
  import { resolveTuiGlobalKeyAction, resolveTuiSessionNavigationKey } from './keys.js';
27
28
  import { tuiTheme } from './theme.js';
28
- import { contentRowBudget, rowViewport } from './viewport.js';
29
+ import { contentRowBudget, rowViewport, wrapDisplayText } from './viewport.js';
29
30
  import { resolveRouteScreenId, routeDefinition, TUI_SCREEN_IDS, unreachableTuiRoute, } from './screen-registry.js';
30
31
  export const CONSOLE_V4_SCREEN_IDS = TUI_SCREEN_IDS;
31
32
  const PLUGIN_FILTERS = [
@@ -175,17 +176,17 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
175
176
  if (route.name === 'pluginList') {
176
177
  rows.push(['←/→ · [ / ]', '切换来源']);
177
178
  rows.push(['/', '进入搜索']);
178
- if (route.name === 'pluginList')
179
- rows.push(['f', '切换插件状态']);
179
+ rows.push(['f', '切换插件状态']);
180
+ rows.push(['m', '原位选择多个插件']);
180
181
  }
181
182
  if (route.name === 'portableSkillSelect') {
182
183
  rows.push(['Space', '切换当前 Skill']);
183
- rows.push(['a', '全选 Skills']);
184
+ rows.push(['a', '定位下方操作 / 回到原项']);
184
185
  }
185
186
  if (route.name === 'batchSelection') {
186
187
  rows.push(['Space', '切换当前插件']);
187
- rows.push(['a', '全选当前筛选结果']);
188
- rows.push(['c', '继续选择位置和平台']);
188
+ rows.push(['a', '定位下方操作 / 回到原项']);
189
+ rows.push(['c', '继续选择任务或查看汇总']);
189
190
  }
190
191
  if (route.name === 'configFields' && route.layer.scope !== 'effective') {
191
192
  rows.push(['s', '查看保存汇总']);
@@ -390,7 +391,7 @@ function moveFocus(actions, current, direction) {
390
391
  return 0;
391
392
  for (let offset = 1; offset <= actions.length; offset += 1) {
392
393
  const next = (current + direction * offset + actions.length) % actions.length;
393
- if (!actions[next]?.disabled)
394
+ if (!actions[next]?.disabled && actions[next]?.role !== 'navigation')
394
395
  return next;
395
396
  }
396
397
  return current;
@@ -431,6 +432,31 @@ function wrapConsoleText(value, columns) {
431
432
  lines.push(line);
432
433
  return lines;
433
434
  }
435
+ function sourceRefSummary(value) {
436
+ if (!value)
437
+ return '未知';
438
+ if (/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/iu.test(value))
439
+ return value.slice(0, 8);
440
+ return value.replace(/^refs\/(?:heads|tags)\//u, '');
441
+ }
442
+ function relativeCheckTime(value, now = Date.now()) {
443
+ if (!value)
444
+ return '尚未检查';
445
+ const checkedAt = Date.parse(value);
446
+ if (!Number.isFinite(checkedAt))
447
+ return '时间未知';
448
+ const elapsed = Math.max(0, now - checkedAt);
449
+ if (elapsed < 60_000)
450
+ return '刚刚';
451
+ if (elapsed < 3_600_000)
452
+ return `${Math.floor(elapsed / 60_000)} 分钟前`;
453
+ if (elapsed < 86_400_000)
454
+ return `${Math.floor(elapsed / 3_600_000)} 小时前`;
455
+ return `${Math.floor(elapsed / 86_400_000)} 天前`;
456
+ }
457
+ function parseConfigNumberInput(input) {
458
+ return input.trim().length === 0 ? undefined : Number(input);
459
+ }
434
460
  function changeDetailLines(changes, columns) {
435
461
  const valueLines = (label, value) => [
436
462
  label,
@@ -440,6 +466,7 @@ function changeDetailLines(changes, columns) {
440
466
  `修改项 · ${change.title}`,
441
467
  ...valueLines('修改前', change.before),
442
468
  ...valueLines('修改后', change.after),
469
+ ...(change.children ?? []).flatMap((child) => [child.property, ...valueLines('修改前', child.before), ...valueLines('修改后', child.after)]),
443
470
  ]);
444
471
  }
445
472
  function operationRiskWarnings(review) {
@@ -464,29 +491,12 @@ function operationRiskLines(review, columns) {
464
491
  }
465
492
  function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
466
493
  return rowViewport({
467
- rowHeights: actions.map((action) => compact || (!action.description && !action.disabled) ? 1 : 2),
468
- focus,
469
- requestedOffset,
470
- rowBudget: lineBudget,
471
- });
472
- }
473
- function objectCardActionViewport(actions, width, compact, focus, requestedOffset, lineBudget) {
474
- return rowViewport({
475
- rowHeights: actions.map((action) => objectCardRowLayout({
476
- width,
477
- description: action.disabled ?? action.description ?? '',
478
- meta: action.meta,
479
- hasTags: action.tags !== undefined,
480
- compact,
481
- }).height),
494
+ rowHeights: actions.map((action) => action.role === 'navigation' ? 0 : compact || (!action.description && !action.disabled) ? 1 : 2),
482
495
  focus,
483
496
  requestedOffset,
484
497
  rowBudget: lineBudget,
485
498
  });
486
499
  }
487
- function portableSkillCandidateViewport(actions, width, compact, focus, requestedOffset, rowBudget) {
488
- return objectCardActionViewport(actions, width, compact, focus, requestedOffset, rowBudget);
489
- }
490
500
  function prefixRowLimit(rowHeights, rowBudget) {
491
501
  let used = 0;
492
502
  let visible = 0;
@@ -662,17 +672,40 @@ function configUnavailableDiagnostic(route) {
662
672
  }
663
673
  function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
664
674
  const unavailable = state === 'unavailable' || state === 'error';
675
+ if (generation?.errorCode === 'catalogLogicalIdentityChanged') {
676
+ return {
677
+ severity: 'error',
678
+ title: '来源发布身份已变化',
679
+ userMessage: '待检查内容与当前来源不是同一发布身份。当前状态不能据此判定为健康。',
680
+ impact: [
681
+ { state: 'blocked', message: '不能把待检查内容作为当前来源的更新。' },
682
+ { state: 'degraded', message: '旧 Active 是否仍可用需要单独验证。' },
683
+ ],
684
+ nextStep: '检查来源地址和发布身份,然后编辑来源,或移除后重新添加。',
685
+ technical: {
686
+ code: 'catalogLogicalIdentityChanged',
687
+ stage: 'compare-source-identity',
688
+ sourceId,
689
+ checks: ['核对来源地址、ref 和 logical identity。', '确认后编辑来源,或移除后重新添加。'],
690
+ },
691
+ };
692
+ }
665
693
  if (generation?.compatibility?.state === 'unsupported') {
666
694
  const { requiredCapabilities, missingCapabilities } = generation.compatibility;
695
+ const missingKnown = missingCapabilities.length > 0;
667
696
  return {
668
697
  severity: 'error',
669
698
  title: '当前目录版本不兼容',
670
- userMessage: `当前 AW 缺少 ${missingCapabilities.length} 项所需功能支持。`,
699
+ userMessage: missingKnown
700
+ ? `当前 AW 缺少 ${missingCapabilities.length} 项所需功能支持。`
701
+ : '当前目录版本所需功能无法确认。',
671
702
  impact: [
672
703
  { state: 'blocked', message: '这个目录版本不能用于浏览、安装或同步。' },
673
704
  { state: 'preserved', message: '当前启用版本和已下载内容保持不变。' },
674
705
  ],
675
- nextStep: '升级 AW 到支持所需能力的版本,或修改来源后重新启用。',
706
+ nextStep: missingKnown
707
+ ? '升级 AW 到支持所需能力的版本,或修改来源后重新启用。'
708
+ : '检查来源内容和 capability 诊断,再决定修复来源或更新 AW。',
676
709
  technical: {
677
710
  code: 'awCapabilityUnsupported',
678
711
  stage: 'resolve-source',
@@ -681,7 +714,9 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
681
714
  key: 'compatibility',
682
715
  value: `required: ${requiredCapabilities.join('、')}; missing: ${missingCapabilities.join('、')}`,
683
716
  }],
684
- checks: ['检查所需和缺少的 Catalog capability。', '升级 AW 后重新加载来源。'],
717
+ checks: missingKnown
718
+ ? ['检查所需和缺少的 Catalog capability。', '升级 AW 后重新加载来源。']
719
+ : ['检查来源的 capability 声明和验证错误。', '确认原因后重新加载来源。'],
685
720
  },
686
721
  };
687
722
  }
@@ -704,18 +739,24 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
704
739
  };
705
740
  }
706
741
  if (freshness?.state === 'incompatible') {
707
- const hasActive = generation?.active !== undefined;
742
+ const activeHealthy = generation?.state === 'ready' && generation.active !== undefined;
743
+ const missingCount = freshness.missingCapabilities?.length ?? 0;
744
+ const missingSummary = missingCount && missingCount > 0
745
+ ? `缺少 ${missingCount} 项待检查版本所需功能`
746
+ : '待检查版本所需功能无法确认';
708
747
  return {
709
748
  severity: 'error',
710
749
  title: '待检查版本不兼容',
711
- userMessage: hasActive
712
- ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能。普通更新已阻止,当前目录版本保持不变。`
713
- : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能。这个来源没有可用目录版本。`,
750
+ userMessage: activeHealthy
751
+ ? `当前 AW ${missingSummary}。普通更新已阻止;已验证的当前目录版本保持不变。`
752
+ : `当前 AW ${missingSummary}。这个来源没有已验证可用的目录版本。`,
714
753
  impact: [
715
754
  { state: 'blocked', message: '不能直接切换到这个待检查版本。' },
716
- ...(hasActive ? [{ state: 'preserved', message: '当前目录版本和插件浏览仍可用。' }] : []),
755
+ ...(activeHealthy ? [{ state: 'preserved', message: '已验证的当前目录版本和插件浏览仍可用。' }] : []),
717
756
  ],
718
- nextStep: '升级 AW 到支持待检查版本所需功能的版本,或编辑来源后重新验证。',
757
+ nextStep: missingCount > 0
758
+ ? '升级 AW 到支持待检查版本所需功能的版本,或编辑来源后重新验证。'
759
+ : '检查来源的 capability 声明和安全诊断,再决定修正来源或升级 AW。',
719
760
  technical: {
720
761
  code: safeDiagnosticCode(freshness.errorCode, 'catalogincompatible'),
721
762
  stage: 'compare-candidate',
@@ -723,7 +764,9 @@ function sourceDiagnostic(sourceId, state, detail, generation, freshness) {
723
764
  ...(freshness.missingCapabilities?.length ? {
724
765
  context: [{ key: 'compatibility', value: freshness.missingCapabilities.join('、') }],
725
766
  } : {}),
726
- checks: ['检查缺少的 Catalog capability。', '升级 AW 后执行完整验证。'],
767
+ checks: missingCount > 0
768
+ ? ['检查缺少的 Catalog capability。', '升级 AW 后执行完整验证。']
769
+ : ['检查来源的 capability 声明。', '查看安全错误代码后重新验证。'],
727
770
  },
728
771
  };
729
772
  }
@@ -813,6 +856,8 @@ function SemanticRows(props) {
813
856
  ? props.actions.slice(offset)
814
857
  : props.actions.slice(offset, offset + props.limit);
815
858
  return (_jsx(Box, { flexDirection: "column", children: actions.map((action, visibleIndex) => {
859
+ if (action.role === 'navigation')
860
+ return null;
816
861
  const index = visibleIndex + offset;
817
862
  const rowProps = {
818
863
  title: action.label,
@@ -838,6 +883,32 @@ function SemanticRows(props) {
838
883
  return _jsx(TaskRow, { ...rowProps }, `${action.label}:${index}`);
839
884
  }) }));
840
885
  }
886
+ function SequenceRows(props) {
887
+ const configLines = (action) => [
888
+ ...wrapConsoleText(`${action.label}${action.meta ? ` · ${action.meta}` : ''}`, Math.max(1, props.columns - 2)),
889
+ ...(action.description ? wrapConsoleText(action.description, Math.max(1, props.columns - 2)) : []),
890
+ ];
891
+ const viewport = rowViewport({
892
+ rowHeights: props.actions.map((action, index) => action.role === 'navigation' ? 0 : index < props.itemCount
893
+ ? props.wrapValues ? configLines(action).length : props.cards ? objectCardRowLayout({
894
+ width: props.columns, description: action.disabled ?? action.description ?? '',
895
+ meta: action.meta, hasTags: action.tags !== undefined, compact: props.compact,
896
+ }).height : action.description ? 2 : 1
897
+ : 1),
898
+ focus: props.focus, requestedOffset: props.scrollOffset, rowBudget: props.rowBudget,
899
+ });
900
+ return _jsx(ContentViewport, { children: props.actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => {
901
+ if (action.role === 'navigation')
902
+ return null;
903
+ const index = viewport.offset + visibleIndex;
904
+ const focused = !props.inactive && index === props.focus;
905
+ if (index < props.itemCount && props.wrapValues)
906
+ return _jsx(Box, { flexDirection: "column", children: configLines(action).map((line, lineIndex) => _jsxs(Text, { bold: focused && lineIndex === 0, color: action.status === 'error' ? 'red' : undefined, children: [lineIndex === 0 ? focused ? '› ' : ' ' : ' ', line] }, lineIndex)) }, action.configKey ?? action.label);
907
+ return index < props.itemCount && props.cards
908
+ ? _jsx(ObjectCardRow, { width: props.columns, title: `${action.selected === undefined ? '' : action.selected ? '[x] ' : '[ ] '}${action.label}`, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: focused, compact: props.compact }, action.actionId ?? action.pluginId ?? action.label)
909
+ : _jsx(SemanticRows, { actions: [action], focus: focused ? 0 : -1, kind: index < props.itemCount ? 'section' : 'action', compact: index >= props.itemCount }, action.actionId ?? action.label);
910
+ }) });
911
+ }
841
912
  function PluginRows(props) {
842
913
  return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
843
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))) }));
@@ -870,7 +941,8 @@ function SourceTabs(props) {
870
941
  }).join('')}`;
871
942
  if (props.compact || terminalTextWidth(fullText) > props.columns) {
872
943
  const tab = tabs[index];
873
- return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
944
+ const neighbour = tabs[(index + 1) % tabs.length];
945
+ return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " \u2190\u2192 \u5207\u6362 ", tabs.length > 1 ? neighbour?.label : ''] });
874
946
  }
875
947
  return (_jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", tabs.map((tab) => {
876
948
  const active = tab.id === props.current;
@@ -987,9 +1059,6 @@ function dependencyReferenceUsageLabel(reference, consumerName) {
987
1059
  : reference.consumer.project === 'current' ? '当前项目' : '其他项目';
988
1060
  return `${consumerName} · ${location} · ${platformLabel(reference.consumer.platform)}`;
989
1061
  }
990
- function dependencySummaryText(summary) {
991
- return `${summary.directCount} 个直接 · ${summary.totalCount} 个总计`;
992
- }
993
1062
  function dependencyGraphRowStatus(row) {
994
1063
  if (row.nodeState === 'ready')
995
1064
  return { value: '正常', status: 'success' };
@@ -1064,14 +1133,13 @@ export function ConsoleAppV4(props) {
1064
1133
  const [overlayFocus, setOverlayFocus] = useState(0);
1065
1134
  const [operationConfirmation, setOperationConfirmation] = useState();
1066
1135
  const [sourceTransportOverlay, setSourceTransportOverlay] = useState();
1067
- const [pluginListChooser, setPluginListChooser] = useState();
1068
1136
  const [projectReminderOpen, setProjectReminderOpen] = useState(false);
1069
1137
  const [discardForExit, setDiscardForExit] = useState(false);
1070
1138
  const [discardForSessionNavigation, setDiscardForSessionNavigation] = useState();
1071
1139
  const [discardDraftConfirmation, setDiscardDraftConfirmation] = useState();
1072
1140
  const [focus, setFocus] = useState(0);
1073
- const [focusZone, setFocusZone] = useState('content');
1074
- const [pinnedActionFocus, setPinnedActionFocus] = useState(0);
1141
+ const actionJumpAnchor = useRef(undefined);
1142
+ const [searchCursor, setSearchCursor] = useState(0);
1075
1143
  const [scrollOffset, setScrollOffset] = useState(0);
1076
1144
  const [searchFocus, setSearchFocus] = useState(false);
1077
1145
  const [query, setQuery] = useState(snapshot.router.current.context.query ?? '');
@@ -1098,8 +1166,6 @@ export function ConsoleAppV4(props) {
1098
1166
  const [inputValue, setInputValue] = useState('');
1099
1167
  const [inputError, setInputError] = useState();
1100
1168
  const [configActionError, setConfigActionError] = useState();
1101
- const [configGroupChooserOpen, setConfigGroupChooserOpen] = useState(false);
1102
- const [configGroupChoiceFocus, setConfigGroupChoiceFocus] = useState(0);
1103
1169
  const [actionError, setActionError] = useState();
1104
1170
  const [configResetAvailability, setConfigResetAvailability] = useState({});
1105
1171
  const [sourceDraft, setSourceDraft] = useState();
@@ -1134,6 +1200,7 @@ export function ConsoleAppV4(props) {
1134
1200
  const [draftOwner, setDraftOwner] = useState();
1135
1201
  const dynamicMenuFocus = useRef(undefined);
1136
1202
  const pluginListFocusId = useRef(undefined);
1203
+ const listFocusAnchor = useRef(undefined);
1137
1204
  const dependencyGraphContexts = useRef(new Map());
1138
1205
  const selectedDependencyReferenceIndex = useRef(0);
1139
1206
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
@@ -1159,10 +1226,6 @@ export function ConsoleAppV4(props) {
1159
1226
  ...(configDrafts.user?.updates ?? {}),
1160
1227
  ...(configDrafts.project?.updates ?? {}),
1161
1228
  };
1162
- const draftRemoves = [...new Set([
1163
- ...(configDrafts.user?.removes ?? []),
1164
- ...(configDrafts.project?.removes ?? []),
1165
- ])];
1166
1229
  const definition = routeDefinition(route);
1167
1230
  const rendererFamily = definition.rendererFamily;
1168
1231
  const routeKey = JSON.stringify(route);
@@ -1378,10 +1441,6 @@ export function ConsoleAppV4(props) {
1378
1441
  setInputValue('');
1379
1442
  }
1380
1443
  }, [route.name]);
1381
- useEffect(() => {
1382
- if (route.name !== 'configFields')
1383
- setConfigGroupChooserOpen(false);
1384
- }, [route.name]);
1385
1444
  const configRoute = route.name === 'configFields'
1386
1445
  || route.name === 'configFieldEditor'
1387
1446
  || route.name === 'configObjectPropertyEditor'
@@ -1607,42 +1666,6 @@ export function ConsoleAppV4(props) {
1607
1666
  return;
1608
1667
  props.controller.updatePageContext({ configGroupId: tabs[0].id });
1609
1668
  }, [props.controller, route.name, currentConfigPage, snapshot.router.current.context.configGroupId]);
1610
- useEffect(() => {
1611
- let count;
1612
- if (route.name === 'sourceList')
1613
- count = props.controller.listSources().length;
1614
- else if (route.name === 'issueList') {
1615
- const issueCount = props.controller.listUserIssues().length;
1616
- count = issueCount || 2;
1617
- }
1618
- else if (route.name === 'operationHistory') {
1619
- count = snapshot.operationHistorySummaries.length + 1;
1620
- }
1621
- else if (route.name === 'pluginList') {
1622
- const items = props.controller.listPlugins({
1623
- mode: route.mode,
1624
- ...(route.sourceId ? { sourceId: route.sourceId } : {}),
1625
- ...(query ? { query } : {}),
1626
- installFilter,
1627
- });
1628
- const sourceUnavailable = route.sourceId !== undefined
1629
- && props.controller.getSourceOverview(route.sourceId).health?.state === 'unavailable';
1630
- count = pluginListRowIds(route.mode, items, query, sourceUnavailable).length || 1;
1631
- }
1632
- if (count === undefined)
1633
- return;
1634
- setFocus((value) => Math.min(value, Math.max(0, count - 1)));
1635
- setScrollOffset((value) => Math.min(value, Math.max(0, count - 1)));
1636
- }, [
1637
- policy.mode,
1638
- props.controller,
1639
- query,
1640
- installFilter,
1641
- route.name,
1642
- route.name === 'pluginList' ? route.mode : '',
1643
- route.name === 'pluginList' ? route.sourceId : '',
1644
- snapshot.viewModel,
1645
- ]);
1646
1669
  useEffect(() => {
1647
1670
  if (route.name !== 'pluginWriteTask' || route.goal !== 'update')
1648
1671
  return;
@@ -1800,6 +1823,11 @@ export function ConsoleAppV4(props) {
1800
1823
  layer: configRoute.layer,
1801
1824
  }, snapshot.router.current.context);
1802
1825
  }, [props.controller, configRoute, currentConfigPage, activeField, snapshot.router.current.context]);
1826
+ useEffect(() => {
1827
+ if (route.name === 'configFieldAction' && activeField) {
1828
+ props.controller.replace({ ...route, name: 'configFieldEditor', editor: activeField.type });
1829
+ }
1830
+ }, [props.controller, route, activeField]);
1803
1831
  const activeObjectKey = activeField?.type === 'object' && configRoute
1804
1832
  ? `${configOwnerKey(configRoute)}\0${activeField.key}`
1805
1833
  : undefined;
@@ -1944,7 +1972,8 @@ export function ConsoleAppV4(props) {
1944
1972
  let referenceDetailPagination;
1945
1973
  let confirmationPagination;
1946
1974
  let pluginRows = [];
1947
- let pinnedActions = [];
1975
+ let bottomActions = [];
1976
+ let listQueryActions = [];
1948
1977
  let selectAllPortableCandidates;
1949
1978
  let togglePluginListFilter;
1950
1979
  let movePluginListSource;
@@ -1971,9 +2000,11 @@ export function ConsoleAppV4(props) {
1971
2000
  : focus;
1972
2001
  const pageActionBudget = (staticRows = 0) => contentRowBudget({
1973
2002
  terminalRows: size.rows,
1974
- description: Boolean(description),
2003
+ description: Boolean(actionError ?? description),
1975
2004
  compact: policy.mode === 'compact',
1976
- staticRows,
2005
+ staticRows: staticRows
2006
+ + Math.max(0, wrapConsoleText(actionError ?? description ?? '', size.columns - 2).length - 1)
2007
+ + Math.max(0, wrapConsoleText(title, size.columns - 2).length - 1),
1977
2008
  });
1978
2009
  const dependencyPlacementStaticRows = policy.mode === 'compact' ? 3 : 10;
1979
2010
  const actionViewportStaticRows = route.name === 'dependencyPlacement'
@@ -1988,12 +2019,41 @@ export function ConsoleAppV4(props) {
1988
2019
  const base = policy.mode === 'compact' ? compactRows : standardRows;
1989
2020
  return base + Math.max(0, size.rows - baseline);
1990
2021
  };
2022
+ const operationDetailPageRows = 2;
1991
2023
  const sourceTechnicalPageRows = Math.max(1, contentRowBudget({
1992
2024
  terminalRows: size.rows, description: false, compact: policy.mode === 'compact', staticRows: 6,
1993
2025
  }));
1994
2026
  const resolveSourceTechnicalDiagnostic = (sourceId) => {
1995
2027
  const overview = props.controller.getSourceOverview(sourceId);
1996
- return sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), props.controller.getCatalogFreshness(sourceId)) ?? {
2028
+ const freshness = props.controller.getCatalogFreshness(sourceId);
2029
+ const diagnostic = sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), freshness);
2030
+ const evidenceContext = [
2031
+ { key: 'sourcePolicy', value: overview.detail?.type === 'path'
2032
+ ? 'path' : overview.detail?.refPolicy === 'explicit' ? 'explicit' : 'cliChannel' },
2033
+ ...(freshness?.checkedAt
2034
+ ? [{ key: 'freshness', value: freshness.checkedAt }]
2035
+ : []),
2036
+ ...(freshness?.candidate
2037
+ ? [{ key: 'candidate', value: JSON.stringify({
2038
+ catalogVersion: freshness.candidate.catalogVersion,
2039
+ requestedRef: freshness.candidate.requestedRef,
2040
+ resolvedCommit: freshness.candidate.resolvedCommit,
2041
+ }) }]
2042
+ : []),
2043
+ ];
2044
+ if (diagnostic)
2045
+ return {
2046
+ ...diagnostic,
2047
+ ...(diagnostic.technical ? { technical: {
2048
+ ...diagnostic.technical,
2049
+ ...(diagnostic.technical.requestedRef || !overview.detail?.requestedRef
2050
+ ? {} : { requestedRef: overview.detail.requestedRef }),
2051
+ ...(diagnostic.technical.resolvedCommit || !overview.detail?.resolvedCommit
2052
+ ? {} : { resolvedCommit: overview.detail.resolvedCommit }),
2053
+ context: [...(diagnostic.technical.context ?? []), ...evidenceContext],
2054
+ } } : {}),
2055
+ };
2056
+ return {
1997
2057
  severity: 'warning',
1998
2058
  title: '来源解析详情',
1999
2059
  userMessage: '来源当前可用。',
@@ -2005,6 +2065,7 @@ export function ConsoleAppV4(props) {
2005
2065
  sourceId,
2006
2066
  ...(overview.detail?.requestedRef ? { requestedRef: overview.detail.requestedRef } : {}),
2007
2067
  ...(overview.detail?.resolvedCommit ? { resolvedCommit: overview.detail.resolvedCommit } : {}),
2068
+ context: evidenceContext,
2008
2069
  },
2009
2070
  };
2010
2071
  };
@@ -2026,12 +2087,11 @@ export function ConsoleAppV4(props) {
2026
2087
  sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
2027
2088
  focusedEntityId: route.name === 'pluginList'
2028
2089
  ? pluginRows[focus]?.pluginId
2029
- : route.name === 'sourceList' ? props.controller.listSources()[focus]?.id : undefined,
2090
+ : route.name === 'batchSelection' ? actions[focus]?.pluginId
2091
+ : route.name === 'sourceList' ? props.controller.listSources()[focus]?.id : undefined,
2030
2092
  });
2031
2093
  props.controller.push(next);
2032
2094
  setFocus(0);
2033
- setFocusZone('content');
2034
- setPinnedActionFocus(0);
2035
2095
  setScrollOffset(0);
2036
2096
  setSearchFocus(false);
2037
2097
  if (next.name === 'pluginList') {
@@ -2072,6 +2132,12 @@ export function ConsoleAppV4(props) {
2072
2132
  ? restoredEntityIndex
2073
2133
  : Math.min(restoredFocus, Math.max(0, rowIds.length - 1));
2074
2134
  }
2135
+ else if (restoredFrame.route.name === 'batchSelection' && batchDraft) {
2136
+ const candidates = batchCandidates(batchDraft);
2137
+ const entityIndex = candidates.findIndex((candidate) => candidate.pluginId === restored.focusedEntityId);
2138
+ itemCount = candidates.length + 3;
2139
+ restoredFocus = entityIndex >= 0 ? entityIndex : Math.min(restoredFocus, Math.max(0, itemCount - 1));
2140
+ }
2075
2141
  else if (restoredFrame.route.name === 'sourceList') {
2076
2142
  const sources = props.controller.listSources();
2077
2143
  itemCount = sources.length + 3;
@@ -2139,8 +2205,6 @@ export function ConsoleAppV4(props) {
2139
2205
  else
2140
2206
  props.controller.back();
2141
2207
  restorePageContext();
2142
- setFocusZone('content');
2143
- setPinnedActionFocus(0);
2144
2208
  setActionError(undefined);
2145
2209
  };
2146
2210
  const acceptSourceFieldAndBack = () => {
@@ -2237,8 +2301,7 @@ export function ConsoleAppV4(props) {
2237
2301
  showSelected: false,
2238
2302
  });
2239
2303
  setDirty(true);
2240
- setFocus(0);
2241
- setScrollOffset(0);
2304
+ setFocus(Math.min(focus, Math.max(0, pluginRows.length - 1)));
2242
2305
  setSearchFocus(false);
2243
2306
  };
2244
2307
  const batchCandidates = (draft) => {
@@ -2433,7 +2496,13 @@ export function ConsoleAppV4(props) {
2433
2496
  };
2434
2497
  const beginPortable = async (pluginId) => {
2435
2498
  try {
2436
- await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
2499
+ if (route.name === 'portableBlocker' && snapshot.portableDraft?.pluginId === pluginId) {
2500
+ await props.controller.refreshPortableInstallDraft(pluginId);
2501
+ props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
2502
+ }
2503
+ else {
2504
+ await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
2505
+ }
2437
2506
  setFocus(0);
2438
2507
  setPortableSelectionView('all');
2439
2508
  setScrollOffset(0);
@@ -2443,8 +2512,13 @@ export function ConsoleAppV4(props) {
2443
2512
  if (error instanceof Error && error.name === 'AbortError')
2444
2513
  return;
2445
2514
  const blocker = extractPortableError(error);
2446
- const target = { name: 'portableBlocker', pluginId, ...blocker };
2447
- if (props.controller.snapshot.router.current.route.name === 'portableBlocker') {
2515
+ const operationRecordId = props.controller.recordUiFailure(error, {
2516
+ code: blocker.diagnosticCode,
2517
+ stage: 'prepare-portable',
2518
+ });
2519
+ const target = { name: 'portableBlocker', pluginId, ...blocker, operationRecordId };
2520
+ if (props.controller.snapshot.router.current.route.name === 'portableBlocker'
2521
+ || props.controller.snapshot.router.current.route.name === 'portablePreparing') {
2448
2522
  props.controller.replace(target);
2449
2523
  }
2450
2524
  else {
@@ -2476,7 +2550,11 @@ export function ConsoleAppV4(props) {
2476
2550
  if (error instanceof Error && error.name === 'AbortError')
2477
2551
  return false;
2478
2552
  const blocker = extractPortableError(error);
2479
- const target = { name: 'portableBlocker', pluginId, ...blocker };
2553
+ const operationRecordId = props.controller.recordUiFailure(error, {
2554
+ code: blocker.diagnosticCode,
2555
+ stage: 'prepare-portable',
2556
+ });
2557
+ const target = { name: 'portableBlocker', pluginId, ...blocker, operationRecordId };
2480
2558
  if (route.name === 'result')
2481
2559
  leaveResult(target);
2482
2560
  else
@@ -2735,43 +2813,14 @@ export function ConsoleAppV4(props) {
2735
2813
  const configLayerLabel = (layer) => layer.scope === 'effective' ? '有效设置'
2736
2814
  : layer.scope === 'user' ? '个人默认'
2737
2815
  : '当前项目';
2738
- const objectSummary = (field, value) => {
2739
- if (field.sensitive)
2740
- return '已设置';
2741
- if (configObjectHasSensitiveChild(field.definition)) {
2742
- const count = Object.keys(value).length;
2743
- return `已设置 · ${count === 0 ? '空对象' : `${count} 项`}`;
2744
- }
2745
- const preset = field.choices?.find((choice) => configValuesEqual(choice.value, value));
2746
- const keys = Object.keys(value).sort((left, right) => left.localeCompare(right, 'en'));
2747
- const count = keys.length === 0
2748
- ? '空对象'
2749
- : `${keys.length} 项(${keys.slice(0, 2).join('、')}${keys.length > 2 ? '…' : ''})`;
2750
- return preset ? `方案“${preset.title}” · ${count}` : `自定义 · ${count}`;
2751
- };
2752
- const scalarSummary = (field, value) => {
2753
- if (field.sensitive)
2754
- return '将更新';
2755
- const preset = field.choices?.find((choice) => configValuesEqual(choice.value, value));
2756
- if (preset)
2757
- return preset.title;
2758
- if (field.type === 'boolean')
2759
- return value ? '开启' : '关闭';
2760
- return `${String(value)}${field.unit ? ` ${field.unit}` : ''}`;
2761
- };
2762
- const currentFieldDisplay = (field) => {
2763
- if (draftRemoves.includes(field.key))
2764
- return '恢复继承';
2765
- if (Object.hasOwn(draftUpdates, field.key)) {
2766
- const value = draftUpdates[field.key];
2767
- return typeof value === 'object'
2768
- ? objectSummary(field, value)
2769
- : scalarSummary(field, value);
2770
- }
2771
- if (field.type === 'boolean' && field.value !== undefined)
2772
- return field.value ? '开启' : '关闭';
2773
- return `${field.displayValue}${field.unit && field.displayValue !== '未设置' ? ` ${field.unit}` : ''}`;
2774
- };
2816
+ const fieldValueText = (field, value, full = false, configured = value !== undefined) => presentConfigValue({
2817
+ definition: field.definition,
2818
+ value,
2819
+ configured,
2820
+ privateValue: field.sensitive,
2821
+ summary: field.summary,
2822
+ full,
2823
+ }).text;
2775
2824
  const effectiveFieldDisplay = (field) => {
2776
2825
  if (field.type === 'boolean' && field.effectiveValue !== undefined) {
2777
2826
  return field.effectiveValue ? '开启' : '关闭';
@@ -2789,7 +2838,7 @@ export function ConsoleAppV4(props) {
2789
2838
  const value = draft.updates[field.key];
2790
2839
  if (field.sensitive)
2791
2840
  return '将更新';
2792
- return typeof value === 'object' ? objectSummary(field, value) : scalarSummary(field, value);
2841
+ return fieldValueText(field, value);
2793
2842
  };
2794
2843
  const pendingFieldTargets = (field) => ['project', 'user'].flatMap((scope) => {
2795
2844
  const value = pendingFieldDisplay(field, scope);
@@ -3020,7 +3069,7 @@ export function ConsoleAppV4(props) {
3020
3069
  return '已设置';
3021
3070
  if (typeof value === 'boolean')
3022
3071
  return value ? '开启' : '关闭';
3023
- return String(value);
3072
+ return value === '' ? '(空字符串)' : safeConfigText(String(value));
3024
3073
  };
3025
3074
  const updatedChildren = beforeObject && afterObject
3026
3075
  ? [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])]
@@ -3059,13 +3108,11 @@ export function ConsoleAppV4(props) {
3059
3108
  : updatedChildren;
3060
3109
  const after = draft.removes.includes(key)
3061
3110
  ? `恢复继承(${field?.fallbackDisplayValue ?? '未设置'} · ${field?.fallbackSourceLabel ?? '未设置'})`
3062
- : updatedValue && typeof updatedValue === 'object'
3063
- ? objectSummary(field, updatedValue)
3064
- : scalarSummary(field, updatedValue);
3111
+ : fieldValueText(field, updatedValue, true);
3065
3112
  return {
3066
3113
  key,
3067
3114
  title: field?.title ?? key,
3068
- before: field?.displayValue ?? '未设置',
3115
+ before: field ? fieldValueText(field, field.draftValue ?? field.value, true, field.configured) : '未设置',
3069
3116
  after,
3070
3117
  ...(children && children.length > 0 ? { children } : {}),
3071
3118
  };
@@ -3184,34 +3231,8 @@ export function ConsoleAppV4(props) {
3184
3231
  const projectIntentIssue = snapshot.projectIntentIssue;
3185
3232
  const projectPendingCount = (projectReminder?.actionableTargets.length ?? 0)
3186
3233
  + (projectReminder?.blockedTargets.length ?? 0);
3187
- const projectTargetCount = projectPendingCount + (projectReminder?.unconfirmedTargets.length ?? 0);
3188
3234
  context = '当前项目';
3189
3235
  actions = [
3190
- ...(projectIntentIssue ? [{
3191
- actionId: 'project-intent-issue',
3192
- label: projectIntentIssue.state === 'invalid'
3193
- ? '本项目插件声明无效'
3194
- : '无法读取本项目插件声明',
3195
- description: projectIntentIssue.state === 'invalid'
3196
- ? '本项目插件声明无效;修复后重新加载'
3197
- : '无法读取本项目插件声明;检查项目位置或权限后重新加载',
3198
- status: 'warning',
3199
- run: () => go({ name: 'projectStatus' }),
3200
- }] : []),
3201
- ...(projectTargetCount > 0 ? [{
3202
- label: '项目插件',
3203
- description: projectReminder.actionableTargets.length > 0
3204
- ? `${projectReminder.actionableTargets.length} 个位置可同步;`
3205
- + `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3206
- + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3207
- : projectReminder.blockedTargets.length > 0
3208
- ? `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3209
- + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3210
- : `${projectReminder.unconfirmedTargets.length} 个位置未确认`,
3211
- meta: String(projectTargetCount),
3212
- status: 'warning',
3213
- run: () => go({ name: 'projectStatus' }),
3214
- }] : []),
3215
3236
  { label: '插件', description: '浏览、安装和管理所有插件', meta: `${home.installedPlugins} 已安装`, status: home.pluginUpdateCount > 0 ? 'warning' : undefined, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3216
3237
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
3217
3238
  {
@@ -3224,18 +3245,12 @@ export function ConsoleAppV4(props) {
3224
3245
  run: () => go({ name: 'sourceList' }),
3225
3246
  },
3226
3247
  ];
3227
- const homeViewport = rowViewport({
3228
- rowHeights: actions.map((action) => showTechnical ? 1 : objectCardRowLayout({
3229
- description: action.description ?? '', meta: action.meta, width: size.columns - 2,
3230
- compact: policy.mode === 'compact',
3231
- }).height), focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(showTechnical ? 7 : 2),
3232
- });
3233
3248
  content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[\u5DF2\u5B89\u88C5 ", home.installedPlugins, "] [\u53EF\u66F4\u65B0 ", home.pluginUpdateCount, "]", home.unavailableSourceCount > 0 ? ` [不可用来源 ${home.unavailableSourceCount}]` : ''] }), _jsx(Text, { color: tuiTheme.colors.muted, children: projectIntentIssue ? '! 本项目插件声明需要检查'
3234
3249
  : projectReminder && projectReminder.unconfirmedTargets.length > 0
3235
3250
  ? '? 部分项目插件状态无法确认' : projectPendingCount > 0
3236
3251
  ? `! ${projectPendingCount} 个位置待处理`
3237
3252
  : snapshot.projectDeclarationAbsent
3238
- ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", cards: !showTechnical, columns: size.columns - 2, compact: policy.mode === 'compact', offset: homeViewport.offset, limit: homeViewport.limit })] });
3253
+ ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", compact: false })] });
3239
3254
  }
3240
3255
  else if (route.name === 'projectStatus') {
3241
3256
  const reminder = snapshot.projectIntentReminder;
@@ -3321,7 +3336,7 @@ export function ConsoleAppV4(props) {
3321
3336
  : () => go({ name: 'pluginDetail', pluginId: target.pluginId }),
3322
3337
  };
3323
3338
  });
3324
- pinnedActions = [
3339
+ bottomActions = [
3325
3340
  ...(actionable.length > 0 ? [{
3326
3341
  actionId: 'project-sync-actionable',
3327
3342
  label: `同步明确的 ${actionable.length} 个位置`,
@@ -3348,28 +3363,12 @@ export function ConsoleAppV4(props) {
3348
3363
  description: '查看现有安全错误代码和诊断,不执行写操作',
3349
3364
  run: () => setShowTechnical((value) => !value),
3350
3365
  }] : []),
3351
- {
3352
- actionId: 'project-back-home',
3353
- label: '返回首页',
3354
- run: back,
3355
- },
3356
3366
  ];
3357
3367
  help = showTechnical
3358
- ? 'p/n 或 PgUp/PgDn 阅读 · d 返回项目位置 · Tab 固定操作 · Esc 返回'
3359
- : focusZone === 'actions'
3360
- ? '←→ 移动固定操作 · Enter 选择 · Tab 返回项目位置 · Esc 返回'
3361
- : '↑↓ 移动 · Enter 查看 · Tab 固定操作 · Esc 返回';
3362
- const viewport = rowViewport({
3363
- rowHeights: actions.map((action) => objectCardRowLayout({
3364
- description: action.description ?? '', meta: action.meta,
3365
- width: size.columns - 2, hasTags: true, compact: policy.mode === 'compact',
3366
- }).height),
3367
- focus,
3368
- requestedOffset: scrollOffset,
3369
- rowBudget: pageActionBudget(showTechnical ? 5
3370
- : 1 + (projectIntentIssue || unconfirmed.length > 0 ? 1 : 0)
3371
- + (actionable.length > 0 && blocked.length + unconfirmed.length > 0 ? 1 : 0)),
3372
- });
3368
+ ? 'p/n 或 PgUp/PgDn 阅读 · d 返回项目位置 · Esc 返回'
3369
+ : '↑↓ 移动 · Enter 查看 · Esc 返回';
3370
+ const projectItemCount = showTechnical ? 0 : actions.length;
3371
+ actions = [...(showTechnical ? [] : actions), ...bottomActions];
3373
3372
  const technicalLines = [
3374
3373
  ...(projectIntentIssue ? [
3375
3374
  `项目声明 · ${safeDiagnosticCode(projectIntentIssue.code, 'projectIntentUnavailable')}`,
@@ -3383,21 +3382,19 @@ export function ConsoleAppV4(props) {
3383
3382
  : state === 'blocked' ? '原因 · blocked' : '原因 · unconfirmed']),
3384
3383
  ]),
3385
3384
  ];
3385
+ if (showTechnical)
3386
+ description = undefined;
3386
3387
  const technicalPageRows = responsiveBudget(4, 8);
3387
3388
  const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
3388
3389
  const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
3389
3390
  referenceDetailPagination = showTechnical && technicalLines.length > 0
3390
3391
  ? { total: technicalLines.length, pageRows: technicalPageRows }
3391
3392
  : undefined;
3392
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
3393
- id: action.actionId ?? `${action.label}:${index}`,
3394
- label: action.label,
3395
- disabled: Boolean(action.disabled),
3396
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), projectIntentIssue || unconfirmed.length > 0 ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, actionable.length > 0 && blocked.length + unconfirmed.length > 0
3393
+ content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical && (projectIntentIssue || unconfirmed.length > 0) ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, !showTechnical && actionable.length > 0 && blocked.length + unconfirmed.length > 0
3397
3394
  ? _jsx(StaticRow, { status: "info", title: `只同步明确项;保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置。`, compact: true })
3398
3395
  : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u72B6\u6001\u5B89\u5168\u8BE6\u60C5", compact: true, children: [technicalOffset > 0
3399
3396
  ? _jsx(StaticRow, { title: `↑ 前面还有 ${technicalOffset} 行`, status: "muted", compact: true }) : null, visibleTechnicalLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${technicalOffset + index}:${line}`)), technicalOffset + visibleTechnicalLines.length < technicalLines.length
3400
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
3397
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(SequenceRows, { actions: actions, itemCount: projectItemCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2), compact: policy.mode === 'compact', cards: true }), showTechnical ? _jsx(SemanticRows, { actions: actions, offset: projectItemCount, focus: focus, kind: "action", compact: true }) : null] });
3401
3398
  }
3402
3399
  else if (route.name === 'pluginList') {
3403
3400
  const sourceId = route.sourceId ?? 'all';
@@ -3416,12 +3413,10 @@ export function ConsoleAppV4(props) {
3416
3413
  }).length,
3417
3414
  ]));
3418
3415
  breadcrumb = '插件';
3419
- title = '插件';
3420
- description = '浏览、安装和管理所有插件。选择会跨来源、搜索和筛选保留。';
3416
+ title = `插件 · ${items.length} 个 [m] 多选`;
3417
+ description = undefined;
3421
3418
  context = `${items.length} 个插件`;
3422
- help = focusZone === 'actions'
3423
- ? '←→/↑↓ 操作 · Enter 执行 · Tab 插件列表 · Esc 返回'
3424
- : '↑↓ 插件 · Enter 查看 · Tab 操作栏 · m 多选 · / 搜索 · f 状态 · Esc 返回';
3419
+ help = '↑↓ 移动 · Enter 查看 · Esc 返回';
3425
3420
  const sourceError = sourceId !== 'all'
3426
3421
  && props.controller.getSourceOverview(sourceId).health?.state === 'unavailable';
3427
3422
  actions = sourceError
@@ -3439,7 +3434,7 @@ export function ConsoleAppV4(props) {
3439
3434
  const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
3440
3435
  setInstallFilter(nextFilter);
3441
3436
  props.controller.updatePageContext({ installFilter: nextFilter });
3442
- setFocus(retained >= 0 ? retained : 0);
3437
+ setFocus(retained >= 0 ? retained : Math.min(focus, Math.max(0, nextRowIds.length - 1)));
3443
3438
  setScrollOffset(0);
3444
3439
  };
3445
3440
  togglePluginListFilter = () => {
@@ -3462,7 +3457,7 @@ export function ConsoleAppV4(props) {
3462
3457
  props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
3463
3458
  const retainedFocus = retained >= 0
3464
3459
  ? retained
3465
- : nextItems[0] ? nextRowIds.indexOf(nextItems[0].pluginId) : 0;
3460
+ : Math.min(focus, Math.max(0, nextRowIds.length - 1));
3466
3461
  setFocus(retainedFocus);
3467
3462
  setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
3468
3463
  };
@@ -3504,32 +3499,24 @@ export function ConsoleAppV4(props) {
3504
3499
  }] : []),
3505
3500
  {
3506
3501
  actionId: 'plugin-list-multi',
3507
- label: query ? 'm 当前结果多选' : 'm 多选',
3502
+ label: '选择多个插件 [m]',
3508
3503
  description: '先选择插件,再选择安装、更新、恢复、卸载或维护任务',
3509
3504
  status: 'info',
3510
3505
  run: () => openBatchSelection(),
3511
3506
  },
3512
3507
  ];
3513
- const toolActions = [
3514
- {
3515
- actionId: 'plugin-list-filter',
3516
- label: `筛选:${pluginFilterLabel(installFilter)}`,
3517
- run: () => setPluginListChooser({
3518
- kind: 'status',
3519
- focus: Math.max(0, PLUGIN_FILTERS.indexOf(installFilter)),
3520
- }),
3521
- },
3522
- { actionId: 'plugin-list-search', label: '/ 搜索', run: () => setSearchFocus(true) },
3523
- {
3524
- actionId: 'plugin-list-source',
3525
- label: `来源:${sourceId === 'all' ? '全部' : sourceId}`,
3526
- run: () => setPluginListChooser({
3527
- kind: 'source',
3528
- focus: Math.max(0, ['all', ...props.controller.listSources().map((source) => source.id)]
3529
- .indexOf(sourceId)),
3530
- }),
3508
+ const searchAction = {
3509
+ actionId: 'plugin-list-search', label: '搜索 [/]',
3510
+ run: () => {
3511
+ setSearchCursor([...new Intl.Segmenter(undefined, { granularity: 'grapheme' })
3512
+ .segment(query)].length);
3513
+ setSearchFocus(true);
3531
3514
  },
3532
- ];
3515
+ };
3516
+ const filterAction = {
3517
+ actionId: 'plugin-list-filter', label: '切换筛选 [f]', run: togglePluginListFilter,
3518
+ };
3519
+ listQueryActions = [searchAction, filterAction];
3533
3520
  const pluginRowsById = new Map([
3534
3521
  ...items.map((item) => [item.pluginId, {
3535
3522
  pluginId: item.pluginId,
@@ -3600,38 +3587,29 @@ export function ConsoleAppV4(props) {
3600
3587
  } } : { disabled: '没有可打开的插件' }),
3601
3588
  }];
3602
3589
  }
3603
- pinnedActions = dedupeActions([
3590
+ bottomActions = dedupeActions([
3604
3591
  ...(sourceError ? actions : []),
3605
- ...(pluginRows.length > 0 ? menuActions : actions),
3606
- ...toolActions,
3592
+ ...actions,
3593
+ ...menuActions,
3607
3594
  ]);
3608
- const pluginListStaticRows = 4;
3609
- const viewport = pluginRows.length === 0
3610
- ? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
3611
- : rowViewport({
3612
- rowHeights: pluginRows.map((row) => row.attention ? 6 : 5),
3613
- focus,
3614
- requestedOffset: scrollOffset,
3615
- rowBudget: pageActionBudget(pluginListStaticRows),
3616
- });
3617
- if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
3618
- context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
3619
- }
3620
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
3621
- id: action.actionId ?? `${action.label}:${index}`,
3622
- label: action.label,
3623
- disabled: Boolean(action.disabled),
3624
- })), active: !pluginListChooser && focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3625
- ? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3626
- : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " \u5168\u90E8 ", filterCounts.all, " \u00B7 \u5DF2\u88C5 ", filterCounts.installed, " \u00B7 \u672A\u88C5 ", filterCounts.uninstalled, " \u00B7 \u66F4\u65B0 ", filterCounts.updateAvailable, " \u00B7 \u5904\u7406 ", filterCounts.attention, " f \u5207\u6362"] }), sourceError
3595
+ actions = [...pluginRows.map((row) => ({
3596
+ actionId: `plugin:${row.pluginId}`, pluginId: row.pluginId, label: row.name,
3597
+ run: () => go({ name: 'pluginDetail', pluginId: row.pluginId }),
3598
+ })), ...bottomActions];
3599
+ const viewport = rowViewport({
3600
+ rowHeights: actions.map((_, index) => index < pluginRows.length
3601
+ ? pluginRows[index].attention ? 6 : 5 : 1),
3602
+ focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(3),
3603
+ });
3604
+ 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
3605
+ ? _jsx(InputDisplay, { value: query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3606
+ : _jsxs(_Fragment, { children: [query || '输入名称或用途', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " [f] \u5207\u6362"] }), sourceError
3627
3607
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
3628
3608
  : route.mode === 'installed' && items.length === 0 && !query && sourceId === 'all'
3629
3609
  ? installFilter === 'updateAvailable'
3630
3610
  ? _jsx(Callout, { title: "\u6CA1\u6709\u53EF\u66F4\u65B0\u63D2\u4EF6", description: "\u5F53\u524D\u7B5B\u9009\u8303\u56F4\u5185\u6CA1\u6709\u7ECF\u8FC7\u7248\u672C\u8BC1\u636E\u786E\u8BA4\u7684\u66F4\u65B0\u3002" })
3631
3611
  : _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
3632
- : null, _jsx(ContentViewport, { children: pluginRows.length > 0
3633
- ? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport, active: !pluginListChooser && focusZone === 'content' })
3634
- : _jsx(SemanticRows, { actions: actions, focus: !pluginListChooser && focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] }));
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)) })] })] }));
3635
3613
  }
3636
3614
  else if (route.name === 'batchSelection') {
3637
3615
  breadcrumb = '插件 / 选择多个';
@@ -3639,12 +3617,12 @@ export function ConsoleAppV4(props) {
3639
3617
  : batchDraft?.stage === 'maintenance' ? '其他维护方式'
3640
3618
  : '选择插件';
3641
3619
  description = batchDraft?.stage === 'select'
3642
- ? 'Space 切换选择,Enter 查看插件。选择会跨来源、搜索和筛选保留。'
3620
+ ? undefined
3643
3621
  : batchDraft?.stage === 'task'
3644
3622
  ? '更新和恢复沿用已经添加的位置;移除会逐项显示已有位置。'
3645
3623
  : '同步、重新安装和普通清理使用所选插件的已有位置。';
3646
3624
  if (!batchDraft) {
3647
- actions = [{ label: '返回插件列表', run: back }];
3625
+ actions = [{ role: 'navigation', label: '返回插件列表', run: back }];
3648
3626
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
3649
3627
  }
3650
3628
  else if (batchDraft.stage === 'select') {
@@ -3697,7 +3675,7 @@ export function ConsoleAppV4(props) {
3697
3675
  };
3698
3676
  const continueAction = {
3699
3677
  actionId: 'batch-selection-continue',
3700
- label: `查看确认(${batchDraft.selectedPluginIds.length})`,
3678
+ label: `继续(${batchDraft.selectedPluginIds.length}) [c]`,
3701
3679
  description: batchDraft.selectedPluginIds.length > 0
3702
3680
  ? batchDraft.recommendedGoal ? '重新读取更新信息并显示操作汇总' : '继续选择任务和确认范围'
3703
3681
  : '先选择至少一个插件',
@@ -3715,18 +3693,16 @@ export function ConsoleAppV4(props) {
3715
3693
  } : current);
3716
3694
  setFocus(0);
3717
3695
  setScrollOffset(0);
3718
- setFocusZone('content');
3719
3696
  },
3720
3697
  };
3721
3698
  const selectionToolActions = [
3722
3699
  {
3723
- label: '返回插件选择',
3700
+ role: 'navigation', label: '返回插件选择',
3724
3701
  description: '保留当前已选插件',
3725
3702
  run: () => {
3726
3703
  setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
3727
3704
  setFocus(0);
3728
3705
  setScrollOffset(0);
3729
- setFocusZone('content');
3730
3706
  },
3731
3707
  },
3732
3708
  {
@@ -3761,29 +3737,18 @@ export function ConsoleAppV4(props) {
3761
3737
  } : current);
3762
3738
  setFocus(0);
3763
3739
  setScrollOffset(0);
3764
- setFocusZone('content');
3765
3740
  },
3766
3741
  };
3767
- actions = batchDraft.showSelectionTools ? selectionToolActions : candidateActions;
3768
- pinnedActions = [continueAction, selectedSetAction, selectionToolsAction];
3742
+ bottomActions = [continueAction, selectedSetAction, selectionToolsAction];
3743
+ actions = [...(batchDraft.showSelectionTools ? selectionToolActions : candidateActions), ...bottomActions];
3769
3744
  const batchFocus = Math.min(focus, Math.max(0, actions.length - 1));
3770
3745
  context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
3771
3746
  help = batchDraft.showSelectionTools
3772
- ? '↑↓ 移动 · Enter 执行 · Tab 操作栏 · Esc 返回选择'
3773
- : '↑↓ 移动 · Space 选择 · Enter 查看 · Tab 操作栏 · c 查看确认 · ←→ 来源 · / 搜索 · f 状态 · Esc 返回';
3774
- const cardWidth = Math.max(20, size.columns - 2);
3775
- const viewport = batchDraft.showSelectionTools
3776
- ? actionViewport(actions, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)))
3777
- : objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)));
3778
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
3779
- id: action.actionId ?? action.label,
3780
- label: action.label,
3781
- disabled: Boolean(action.disabled),
3782
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3783
- ? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3784
- : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(batchDraft.installFilter), "]"] }), " f \u5207\u6362"] }), _jsx(ContentViewport, { children: batchDraft.showSelectionTools
3785
- ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? batchFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })
3786
- : _jsx(Box, { flexDirection: "column", children: actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => (_jsx(ObjectCardRow, { width: cardWidth, title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: focusZone === 'content' && viewport.offset + visibleIndex === batchFocus, anchored: focusZone !== 'content' && viewport.offset + visibleIndex === batchFocus, compact: policy.mode === 'compact' }, action.pluginId ?? action.label))) }) })] });
3747
+ ? '↑↓ 移动 · Enter 执行 · Esc 返回选择'
3748
+ : '↑↓ 移动 · Space 选择 · Enter 查看 · Esc 返回';
3749
+ 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
+ ? _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 })] });
3787
3752
  }
3788
3753
  else {
3789
3754
  const selectedIds = batchDraft.selectedPluginIds;
@@ -3824,7 +3789,7 @@ export function ConsoleAppV4(props) {
3824
3789
  { label: '重新安装', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
3825
3790
  { label: '清理遗留内容', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个已核验位置;保留不属于插件或被共用的内容`, run: () => startExistingBatch('cleanup') },
3826
3791
  {
3827
- label: '返回任务选择',
3792
+ role: 'navigation', label: '返回任务选择',
3828
3793
  run: () => moveBatchStage('task'),
3829
3794
  },
3830
3795
  ];
@@ -3866,7 +3831,7 @@ export function ConsoleAppV4(props) {
3866
3831
  description: '同步、重新安装和普通清理',
3867
3832
  run: () => moveBatchStage('maintenance'),
3868
3833
  },
3869
- { label: '返回调整选择', run: () => moveBatchStage('select') },
3834
+ { role: 'navigation', label: '返回调整选择', run: () => moveBatchStage('select') },
3870
3835
  ];
3871
3836
  }
3872
3837
  context = `${selectedIds.length} 个插件 · ${syncTargets.length} 个已保存或现场目标`;
@@ -3905,7 +3870,7 @@ export function ConsoleAppV4(props) {
3905
3870
  };
3906
3871
  const confirmPlatforms = {
3907
3872
  actionId: 'batch-install-platforms-confirm',
3908
- label: '确定所选平台',
3873
+ label: '确定所选平台 [c]',
3909
3874
  description: `${[...selectedPlatforms].map(platformLabel).join('、')} · 继续选择目标`,
3910
3875
  run: () => {
3911
3876
  props.controller.push({ name: 'batchInstallScope' });
@@ -3930,9 +3895,9 @@ export function ConsoleAppV4(props) {
3930
3895
  },
3931
3896
  confirmPlatforms,
3932
3897
  ];
3933
- pinnedActions = [confirmPlatforms];
3934
- help = '↑↓ 移动 · Enter 选择/确定 · c 确定所选平台 · Esc 返回任务选择';
3935
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: [{ id: confirmPlatforms.actionId, label: confirmPlatforms.label }], active: focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focusZone === 'content' ? focus : -1, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: true, offset: 2 })] })] });
3898
+ bottomActions = [confirmPlatforms];
3899
+ help = '↑↓ 移动 · Space/Enter 切换 · c 确定所选平台 · Esc 返回任务选择';
3900
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: 2 })] }) });
3936
3901
  }
3937
3902
  else if (route.name === 'batchInstallScope' || route.name === 'batchPlacement') {
3938
3903
  breadcrumb = route.name === 'batchInstallScope'
@@ -3960,7 +3925,7 @@ export function ConsoleAppV4(props) {
3960
3925
  let installPlacementActions = [];
3961
3926
  let maintenanceTargetCount = 0;
3962
3927
  if (!batchDraft?.action && !batchDraft?.maintenanceGoal) {
3963
- actions = [{ label: '返回批量选择', run: back }];
3928
+ actions = [{ role: 'navigation', label: '返回批量选择', run: back }];
3964
3929
  }
3965
3930
  else if (maintenanceMode) {
3966
3931
  const currentTargets = batchDraft.maintenanceGoal
@@ -4014,9 +3979,9 @@ export function ConsoleAppV4(props) {
4014
3979
  actions = [
4015
3980
  ...targetActions,
4016
3981
  confirmMaintenance,
4017
- { label: '返回任务选择', run: back },
3982
+ { role: 'navigation', label: '返回任务选择', run: back },
4018
3983
  ];
4019
- pinnedActions = [confirmMaintenance];
3984
+ bottomActions = [confirmMaintenance];
4020
3985
  }
4021
3986
  else {
4022
3987
  const projectIntent = batchDraft.selection === 'currentProjectIntentRoots';
@@ -4106,7 +4071,7 @@ export function ConsoleAppV4(props) {
4106
4071
  if (route.name === 'batchInstallScope') {
4107
4072
  const confirmPlacement = installPlacementActions.find((action) => action.actionId === 'batch-placement-confirm');
4108
4073
  if (confirmPlacement)
4109
- pinnedActions = [confirmPlacement];
4074
+ bottomActions = [confirmPlacement];
4110
4075
  }
4111
4076
  if (batchDraft.showPlacementTargets) {
4112
4077
  actions = [
@@ -4118,7 +4083,7 @@ export function ConsoleAppV4(props) {
4118
4083
  : target.state === 'blocked' ? 'warning' : 'muted',
4119
4084
  })),
4120
4085
  {
4121
- label: '返回位置选择',
4086
+ role: 'navigation', label: '返回位置选择',
4122
4087
  run: () => {
4123
4088
  setBatchDraft((current) => current ? { ...current, showPlacementTargets: false } : current);
4124
4089
  restoreActionFocus('batch-placement-details');
@@ -4137,35 +4102,26 @@ export function ConsoleAppV4(props) {
4137
4102
  help = batchDraft?.showPlacementTargets
4138
4103
  ? '↑↓ 查看全部位置 · Esc 返回位置选择'
4139
4104
  : maintenanceMode
4140
- ? '↑↓ 移动 · Enter 选择 · Tab 固定操作 · c 查看确认 · Esc 返回修改'
4105
+ ? '↑↓ 移动 · Enter 选择· c 查看确认 · Esc 返回修改'
4141
4106
  : '↑↓ 移动 · Enter 选择 · Esc 返回修改';
4142
4107
  const placementDetail = Boolean(batchDraft?.showPlacementTargets);
4143
- const viewport = actionViewport(actions, placementDetail ? false : policy.mode === 'compact', focus, scrollOffset, pageActionBudget(maintenanceMode ? 2 : 1));
4108
+ const viewport = actionViewport(actions, placementDetail ? false : policy.mode === 'compact', focus, scrollOffset, pageActionBudget(maintenanceMode ? 4 : 1));
4144
4109
  if (maintenanceMode) {
4145
4110
  const targetOffset = Math.min(viewport.offset, maintenanceTargetCount);
4146
4111
  const targetLimit = Math.max(0, Math.min(maintenanceTargetCount, viewport.offset + viewport.limit) - targetOffset);
4147
4112
  const actionOffset = Math.max(0, viewport.offset - maintenanceTargetCount);
4148
4113
  const actionLimit = Math.max(0, Math.min(actions.length - maintenanceTargetCount, viewport.offset + viewport.limit - maintenanceTargetCount) - actionOffset);
4149
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: [{
4150
- id: pinnedActions[0].actionId,
4151
- label: pinnedActions[0].label,
4152
- disabled: Boolean(pinnedActions[0].disabled),
4153
- }], active: focusZone === 'actions', focus: pinnedActionFocus }), _jsx(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${maintenanceSelection.size} 个位置`, compact: true, children: targetLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(0, maintenanceTargetCount), focus: focus, kind: "toggle", compact: true, offset: targetOffset, limit: targetLimit }) : null }), actionLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(maintenanceTargetCount), focus: focus - maintenanceTargetCount, kind: "action", compact: policy.mode === 'compact', offset: actionOffset, limit: actionLimit }) : null] });
4114
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${maintenanceSelection.size} 个位置`, compact: true, children: targetLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(0, maintenanceTargetCount), focus: focus, kind: "toggle", compact: true, offset: targetOffset, limit: targetLimit }) : null }), actionLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(maintenanceTargetCount), focus: focus - maintenanceTargetCount, kind: "action", compact: policy.mode === 'compact', offset: actionOffset, limit: actionLimit }) : null] });
4154
4115
  }
4155
4116
  else if (!placementDetail && batchDraft?.action === 'install') {
4156
- content = _jsxs(Box, { flexDirection: "column", children: [route.name === 'batchInstallScope' && pinnedActions[0]
4157
- ? _jsx(PinnedActionBar, { actions: [{
4158
- id: pinnedActions[0].actionId, label: pinnedActions[0].label,
4159
- disabled: Boolean(pinnedActions[0].disabled),
4160
- }], active: focusZone === 'actions', focus: pinnedActionFocus })
4161
- : null, _jsx(ContentViewport, { children: installPlacementGroups.map((group) => {
4162
- const groupOffset = installPlacementActions.findIndex((action) => action.actionId === group.actions[0]?.actionId);
4163
- const localOffset = Math.max(0, viewport.offset - groupOffset);
4164
- const localLimit = Math.max(0, Math.min(group.actions.length - localOffset, viewport.offset + viewport.limit - groupOffset - localOffset));
4165
- if (localLimit === 0)
4166
- return null;
4167
- return _jsxs(Box, { flexDirection: "column", children: [group.title ? _jsx(Text, { color: tuiTheme.colors.muted, children: group.title }) : null, _jsx(SemanticRows, { actions: group.actions, focus: focusZone === 'content' ? focus - groupOffset : -1, kind: group.kind, compact: group.kind === 'choice' || policy.mode === 'compact', offset: localOffset, limit: localLimit })] }, group.title ?? 'actions');
4168
- }) })] });
4117
+ content = _jsx(Box, { flexDirection: "column", children: _jsx(ContentViewport, { children: installPlacementGroups.map((group) => {
4118
+ const groupOffset = installPlacementActions.findIndex((action) => action.actionId === group.actions[0]?.actionId);
4119
+ const localOffset = Math.max(0, viewport.offset - groupOffset);
4120
+ const localLimit = Math.max(0, Math.min(group.actions.length - localOffset, viewport.offset + viewport.limit - groupOffset - localOffset));
4121
+ if (localLimit === 0)
4122
+ return null;
4123
+ return _jsxs(Box, { flexDirection: "column", children: [group.title ? _jsx(Text, { color: tuiTheme.colors.muted, children: group.title }) : null, _jsx(SemanticRows, { actions: group.actions, focus: focus - groupOffset, kind: group.kind, compact: group.kind === 'choice' || policy.mode === 'compact', offset: localOffset, limit: localLimit })] }, group.title ?? 'actions');
4124
+ }) }) });
4169
4125
  }
4170
4126
  else
4171
4127
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: placementDetail ? 'action' : 'choice', compact: placementDetail ? false : policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
@@ -4193,11 +4149,7 @@ export function ConsoleAppV4(props) {
4193
4149
  meta: `${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件'
4194
4150
  : candidate.relation === 'direct' ? '直接依赖插件' : '间接依赖插件'}`,
4195
4151
  tags: _jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", candidate.availableScopes.map(scopeLabel).join('、') || '范围待确认', "]", candidate.dynamic ? ' [按设置生成]' : ''] }),
4196
- run: () => go({
4197
- name: 'portableSkillSummary',
4198
- pluginId: plugin.pluginId,
4199
- skillName: candidate.name,
4200
- }),
4152
+ run: () => props.controller.togglePortableSkill(candidate.name),
4201
4153
  }));
4202
4154
  selectAllPortableCandidates = { actionId: 'portable-selection-select-all', label: '全选当前结果', run: () => {
4203
4155
  for (const candidate of visibleCandidates) {
@@ -4221,63 +4173,45 @@ export function ConsoleAppV4(props) {
4221
4173
  setScrollOffset(0);
4222
4174
  } },
4223
4175
  ...(plugin.dependencySummary?.totalCount ? [{ actionId: 'portable-selection-dependencies', label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
4224
- { actionId: 'portable-selection-tools-back', label: '返回 Skills 列表', run: () => {
4176
+ { role: 'navigation', actionId: 'portable-selection-tools-back', label: '返回 Skills 列表', run: () => {
4225
4177
  setPortableToolsOpen(false);
4226
4178
  setFocus(0);
4227
4179
  setScrollOffset(0);
4228
4180
  } },
4229
4181
  ];
4230
- const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount})`, run: continuePortableSelection };
4182
+ const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount}) [c]`, run: continuePortableSelection };
4231
4183
  const openToolsAction = { actionId: 'portable-selection-tools', label: '选择工具', run: () => {
4232
4184
  setPortableToolsOpen(true);
4233
- setFocusZone('content');
4234
4185
  setFocus(0);
4235
4186
  setScrollOffset(0);
4236
4187
  } };
4237
- pinnedActions = [continueAction, openToolsAction];
4238
- actions = portableToolsOpen ? selectionToolActions : candidateActions;
4239
- const candidateViewport = portableSkillCandidateViewport(candidateActions, Math.max(20, size.columns - 2), policy.mode === 'compact', focus, scrollOffset, pageActionBudget(2 + (portableVariantCandidates.length > 0 ? 3 : 0)));
4240
- const viewport = showTechnical
4241
- ? { offset: focus < visibleCandidates.length ? focus : candidateViewport.offset, limit: 1 }
4242
- : candidateViewport;
4243
- const visibleCandidateEntries = visibleCandidates
4244
- .map((candidate, index) => ({ candidate, action: candidateActions[index], index }))
4245
- .slice(viewport.offset, viewport.offset + viewport.limit);
4188
+ bottomActions = [continueAction, openToolsAction];
4189
+ actions = portableToolsOpen ? selectionToolActions : [...candidateActions, ...bottomActions];
4190
+ const visibleCandidateEntries = visibleCandidates.map((candidate, index) => ({ candidate, index }));
4246
4191
  const variantCandidates = portableVariantCandidates;
4247
4192
  const focusedVariant = variantCandidates.find((candidate) => candidate.name === visibleCandidates[focus]?.name);
4248
4193
  const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
4249
4194
  const technicalVariant = focusedVariant ?? visibleVariant ?? variantCandidates[0];
4250
- help = portableToolsOpen
4251
- ? '↑↓ 移动 · Enter 选择 · Tab 返回固定栏 · Esc 返回'
4252
- : focusZone === 'actions'
4253
- ? '←→/↑↓ 移动操作 · Enter 选择 · Tab 返回列表 · c 继续 · Esc 返回'
4254
- : '↑↓ 移动 · Space 切换 · Enter 查看摘要 · Tab 固定栏 · c 继续 · Esc 返回';
4255
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4256
- id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4257
- })), active: !portableEmptySelectionOpen && focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [portableToolsOpen
4258
- ? _jsx(SemanticRows, { actions: selectionToolActions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact' })
4259
- : visibleCandidateEntries.map(({ action, index }) => _jsx(ObjectCardRow, { width: Math.max(20, size.columns - 2), title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.description ?? '', meta: action.meta, tags: action.tags, focused: !portableEmptySelectionOpen && focusZone === 'content' && focus === index, anchored: !portableEmptySelectionOpen && focusZone !== 'content' && focus === index, muted: portableEmptySelectionOpen, compact: policy.mode === 'compact' }, String(action.label))), !portableToolsOpen && variantCandidates.length > 0
4260
- ? showTechnical
4261
- ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4262
- : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
4263
- : null, !portableToolsOpen && visibleCandidates.length === 0
4264
- ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
4265
- : null, !portableToolsOpen && !draft.canContinue
4266
- ? _jsx(StaticRow, { title: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A Skill", status: "warning", compact: true })
4267
- : null, !portableToolsOpen && showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] })] });
4195
+ help = portableToolsOpen ? '↑↓ 移动 · Enter 选择 · Esc 返回'
4196
+ : '↑↓ 移动 · Space/Enter 切换 · Esc 返回';
4197
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SequenceRows, { actions: actions, itemCount: portableToolsOpen ? 0 : candidateActions.length, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget((variantCandidates.length > 0 ? 3 : 0) + (!draft.canContinue ? 1 : 0)
4198
+ + (showTechnical && technicalVariant ? 5 : 0)), compact: policy.mode === 'compact', cards: true, inactive: portableEmptySelectionOpen }), !portableToolsOpen && variantCandidates.length > 0
4199
+ ? showTechnical
4200
+ ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4201
+ : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
4202
+ : null, !portableToolsOpen && visibleCandidates.length === 0
4203
+ ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
4204
+ : null, !portableToolsOpen && !draft.canContinue
4205
+ ? _jsx(StaticRow, { title: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A Skill", status: "warning", compact: true })
4206
+ : null, !portableToolsOpen && showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] }) });
4268
4207
  }
4269
- else if (route.name === 'portableSkillSummary' && plugin && snapshot.portableDraft) {
4270
- const candidate = snapshot.portableDraft.candidates.find((entry) => entry.name === route.skillName);
4271
- breadcrumb = `${plugin.displayName} / 通用 Skills / 内容摘要`;
4272
- context = candidate?.providerDisplayName ?? plugin.displayName;
4273
- title = candidate?.name ?? 'Skill 不再可用';
4274
- description = candidate?.description ?? '候选已经变化。请返回重新选择。';
4275
- actions = [
4276
- { label: '返回 Skill 选择', run: back },
4277
- { label: '继续选择 Agent', disabled: snapshot.portableDraft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
4278
- ];
4279
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5185\u5BB9\u6458\u8981", children: [_jsx(StaticRow, { title: "\u6240\u5C5E\u63D2\u4EF6", meta: candidate?.providerDisplayName ?? '未知', compact: true }), _jsx(StaticRow, { title: "\u751F\u6210\u65B9\u5F0F", meta: candidate?.dynamic ? '按当前插件设置生成' : '目录中的静态 Skill', compact: true }), _jsx(StaticRow, { title: "\u53EF\u7528\u8303\u56F4", meta: candidate?.availableScopes.map(scopeLabel).join('、') ?? '未确认', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4280
- help = '↑↓ 移动 · Enter 选择 · Esc 返回';
4208
+ else if (route.name === 'portablePreparing' && plugin) {
4209
+ breadcrumb = `${plugin.displayName} / 通用 Skills`;
4210
+ context = '只读检查';
4211
+ title = '正在准备可安装的 Skills';
4212
+ description = '正在检查当前项目和所有项目范围的完整依赖。此步骤不会写入文件或调用 Skills CLI。';
4213
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(StatusCard, { status: "info", title: "\u51C6\u5907\u5019\u9009", children: [_jsx(StaticRow, { title: "\u25CF \u68C0\u67E5\u5F53\u524D\u9879\u76EE\u8303\u56F4", compact: true }), _jsx(StaticRow, { title: "\u25CF \u68C0\u67E5\u6240\u6709\u9879\u76EE\u8303\u56F4", compact: true }), _jsx(StaticRow, { title: "\u25CB \u5408\u5E76\u53EF\u9009 Skill \u4E0E Agent \u4FE1\u606F", status: "muted", compact: true })] }) });
4214
+ help = 'Esc 取消检查并返回目标平台';
4281
4215
  }
4282
4216
  else if (route.name === 'portableDependencies' && plugin && snapshot.portableDraft) {
4283
4217
  const overview = dependencyOverview(plugin);
@@ -4286,14 +4220,19 @@ export function ConsoleAppV4(props) {
4286
4220
  title = '生成这些候选 Skills 需要的插件';
4287
4221
  description = '插件依赖用于生成,不能在这里取消。';
4288
4222
  actions = [
4289
- { label: '返回 Skill 选择', run: back },
4223
+ { role: 'navigation', label: '返回 Skill 选择', run: back },
4290
4224
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4291
4225
  ];
4292
4226
  const dependencies = overview?.dependencies ?? [];
4293
- const visibleDependencies = dependencies.slice(0, showTechnical ? 1 : responsiveBudget(2, 3));
4294
- const technicalDependencies = dependencies.slice(0, 1);
4227
+ const dependencyLimit = showTechnical ? 1 : responsiveBudget(2, 3);
4228
+ const dependencyOffset = Math.min(batchTargetOffset, Math.max(0, dependencies.length - dependencyLimit));
4229
+ const visibleDependencies = dependencies.slice(dependencyOffset, dependencyOffset + dependencyLimit);
4230
+ const technicalDependencies = dependencies.slice(dependencyOffset, dependencyOffset + 1);
4295
4231
  const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
4296
- content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56\u63D2\u4EF6", meta: `${dependencies.length} 个`, children: [visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '当前插件直接需要' : '由依赖插件继续需要'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > visibleDependencies.length ? _jsx(StaticRow, { title: `另有 ${dependencies.length - visibleDependencies.length} 个依赖插件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: `${entry.pluginId}${compactTechnicalSummary && dependencies.length > technicalDependencies.length ? ` · 另有 ${dependencies.length - technicalDependencies.length} 个依赖` : ''}`, compact: true }, entry.pluginId)) }) : null] });
4232
+ help = dependencies.length > dependencyLimit
4233
+ ? '↑↓ 移动 · PageUp/PageDown 或 p/n 阅读依赖 · Enter 选择 · Esc 返回'
4234
+ : '↑↓ 移动 · Enter 选择 · Esc 返回';
4235
+ content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56\u63D2\u4EF6", meta: `${dependencies.length} 个`, children: [dependencyOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${dependencyOffset} 个依赖插件`, status: "muted", compact: true }) : null, visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '当前插件直接需要' : '由依赖插件继续需要'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > dependencyOffset + visibleDependencies.length ? _jsx(StaticRow, { title: `↓ 后面还有 ${dependencies.length - dependencyOffset - visibleDependencies.length} 个依赖插件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: entry.pluginId, compact: true }, entry.pluginId)) }) : null] });
4297
4236
  }
4298
4237
  else if (route.name === 'portableAgentMode' && plugin && snapshot.portableDraft) {
4299
4238
  breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
@@ -4357,13 +4296,26 @@ export function ConsoleAppV4(props) {
4357
4296
  setScrollOffset(0);
4358
4297
  } }] : []),
4359
4298
  { label: '重新检查', run: () => beginPortable(plugin.pluginId) },
4299
+ ...(route.operationRecordId ? [{
4300
+ label: '查看诊断与报告',
4301
+ description: `安全记录 ${route.operationRecordId}`,
4302
+ run: () => go({ name: 'operationDiagnostic', recordId: route.operationRecordId }),
4303
+ }] : []),
4360
4304
  { label: snapshot.installPlacementDraft?.pluginId === plugin.pluginId ? '返回目标平台' : '返回插件详情', run: () => props.controller.resetPortableFlowToPreview(plugin.pluginId) },
4361
4305
  ...(cliUnavailable
4362
4306
  ? []
4363
4307
  : [{ label: showTechnical ? '返回问题摘要' : '技术详情', run: () => toggleTechnical() }]),
4364
4308
  ];
4309
+ const technicalPageRows = responsiveBudget(2, 4);
4310
+ if (showTechnical && !showPortableRequirements) {
4311
+ referenceDetailPagination = {
4312
+ total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
4313
+ pageRows: technicalPageRows,
4314
+ };
4315
+ help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4316
+ }
4365
4317
  const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4366
- content = _jsxs(Box, { flexDirection: "column", children: [showPortableRequirements ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5728 PATH \u4E2D\u63D0\u4F9B\u517C\u5BB9\u7684 skills \u53EF\u6267\u884C\u6587\u4EF6\u3002", compact: true }), _jsx(StaticRow, { title: `当前 AW adapter 支持:${SKILLS_CLI_SUPPORTED_VERSION_LABEL}`, compact: true }), _jsx(StaticRow, { title: "\u51C6\u5907\u5B8C\u6210\u540E\u8FD4\u56DE\u95EE\u9898\u6458\u8981\u5E76\u91CD\u65B0\u68C0\u67E5\u3002", compact: true }), _jsx(StaticRow, { title: "\u4E0D\u4F1A\u81EA\u52A8\u8FD0\u884C npx latest\u3001sudo \u6216\u5728\u7EBF\u5B89\u88C5\u811A\u672C\u3002", status: "warning", compact: true }), _jsx(StaticRow, { title: "\u9009\u5B9A\u63D2\u4EF6\u3001\u5B89\u88C5\u76EE\u6807\u548C Portable \u8349\u7A3F\u4FDD\u6301\u4E0D\u53D8\u3002", status: "success", compact: true }), _jsx(StaticRow, { title: "AW \u4E0D\u4F1A\u5728\u6B64\u9875\u5B89\u88C5\u5916\u90E8\u4F9D\u8D56\u3002", status: "warning", compact: true })] }) : _jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
4318
+ content = _jsxs(Box, { flexDirection: "column", children: [showPortableRequirements ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5728 PATH \u4E2D\u63D0\u4F9B\u517C\u5BB9\u7684 skills \u53EF\u6267\u884C\u6587\u4EF6\u3002", compact: true }), _jsx(StaticRow, { title: `当前 AW adapter 支持:${SKILLS_CLI_SUPPORTED_VERSION_LABEL}`, compact: true }), _jsx(StaticRow, { title: "\u51C6\u5907\u5B8C\u6210\u540E\u8FD4\u56DE\u95EE\u9898\u6458\u8981\u5E76\u91CD\u65B0\u68C0\u67E5\u3002", compact: true }), _jsx(StaticRow, { title: "\u4E0D\u4F1A\u81EA\u52A8\u8FD0\u884C npx latest\u3001sudo \u6216\u5728\u7EBF\u5B89\u88C5\u811A\u672C\u3002", status: "warning", compact: true }), _jsx(StaticRow, { title: "\u9009\u5B9A\u63D2\u4EF6\u3001\u5B89\u88C5\u76EE\u6807\u548C Portable \u8349\u7A3F\u4FDD\u6301\u4E0D\u53D8\u3002", status: "success", compact: true }), _jsx(StaticRow, { title: "AW \u4E0D\u4F1A\u5728\u6B64\u9875\u5B89\u88C5\u5916\u90E8\u4F9D\u8D56\u3002", status: "warning", compact: true })] }) : _jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, columns: size.columns, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? technicalPageRows : undefined, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
4367
4319
  }
4368
4320
  else if (route.name === 'pluginDependencies' && plugin) {
4369
4321
  const overview = dependencyOverview(plugin);
@@ -4392,7 +4344,7 @@ export function ConsoleAppV4(props) {
4392
4344
  setFocus(0);
4393
4345
  setScrollOffset(0);
4394
4346
  } },
4395
- { label: '返回插件详情', run: back },
4347
+ { role: 'navigation', label: '返回插件详情', run: back },
4396
4348
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4397
4349
  ];
4398
4350
  const blockedRemovalPagination = !showTechnical && visibleDependents.length > dependentLimit
@@ -4582,7 +4534,7 @@ export function ConsoleAppV4(props) {
4582
4534
  pageRows,
4583
4535
  move: (direction) => moveBatchTargetPage(detailLines.length, pageRows, direction),
4584
4536
  }),
4585
- { label: '返回节点详情', run: back },
4537
+ { role: 'navigation', label: '返回节点详情', run: back },
4586
4538
  ];
4587
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
4588
4540
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${detailLines.length - detailOffset - visibleDetailLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -4633,7 +4585,7 @@ export function ConsoleAppV4(props) {
4633
4585
  ...(showTechnical && referenceActions[selectedReferenceIndex]
4634
4586
  ? [referenceActions[selectedReferenceIndex]] : referenceActions),
4635
4587
  ...referencePaginationActions,
4636
- { label: '返回节点详情', run: back },
4588
+ { role: 'navigation', label: '返回节点详情', run: back },
4637
4589
  ];
4638
4590
  const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4639
4591
  help = showTechnical
@@ -4668,7 +4620,7 @@ export function ConsoleAppV4(props) {
4668
4620
  go({ ...route, view: 'details' });
4669
4621
  },
4670
4622
  },
4671
- { label: '返回关系图', run: back },
4623
+ { role: 'navigation', label: '返回关系图', run: back },
4672
4624
  ];
4673
4625
  const nodeActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4674
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
@@ -4705,7 +4657,7 @@ export function ConsoleAppV4(props) {
4705
4657
  description = '根插件或必需依赖不支持当前目标平台组合,因此不会进入确认页。';
4706
4658
  actions = [
4707
4659
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
4708
- { label: '返回插件详情', run: back },
4660
+ { role: 'navigation', label: '返回插件详情', run: back },
4709
4661
  ];
4710
4662
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u6240\u9009\u76EE\u6807\u5E73\u53F0\u4E0D\u53EF\u7528", children: choices.map((choice) => _jsx(StaticRow, { title: `${platformLabel(choice.platform)} · ${choice.disabled ? `不可选 · ${choice.disabled}` : '不可选'}`, status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4711
4663
  }
@@ -4781,26 +4733,16 @@ export function ConsoleAppV4(props) {
4781
4733
  };
4782
4734
  const confirmAction = {
4783
4735
  actionId: 'confirm-install-platforms',
4784
- label: '确定所选平台',
4736
+ label: '确定所选平台 [c]',
4785
4737
  disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
4786
4738
  run: confirmNativePlatforms,
4787
4739
  };
4788
- const backAction = {
4789
- actionId: 'install-platforms-back', label: '返回插件详情', run: back,
4790
- };
4791
- actions = [...nativeActions, portableAction];
4792
- pinnedActions = [confirmAction, backAction];
4793
- const cardWidth = Math.max(20, size.columns - 2);
4794
- const installTargetStaticRows = (validSelectedTargets.length === 0 ? 2 : 1)
4795
- + (policy.mode === 'compact' ? 4 : 0);
4796
- const platformViewport = objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', focus, scrollOffset, Math.max(1, pageActionBudget(installTargetStaticRows)));
4797
- help = '↑↓ 移动 · Enter/Space 勾选平台 · Enter 打开通用 Skills · Tab 操作栏 · c 继续 · Esc 返回';
4798
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4799
- id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4800
- })), active: focusZone === 'actions', focus: pinnedActionFocus }), validSelectedTargets.length === 0
4801
- ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u76EE\u6807\u5E73\u53F0" }) : null, _jsx(ContentViewport, { children: actions.slice(platformViewport.offset, platformViewport.offset + platformViewport.limit)
4802
- .map((action, visibleIndex) => _jsx(ObjectCardRow, { width: cardWidth, title: action.actionId?.startsWith('install-platform-')
4803
- ? `${action.selected ? '[x]' : '[ ]'} ${action.label}` : action.label, description: action.disabled ?? action.description ?? '', tags: action.tags, focused: focusZone === 'content' && platformViewport.offset + visibleIndex === focus, anchored: focusZone !== 'content' && platformViewport.offset + visibleIndex === focus, compact: policy.mode === 'compact' }, action.actionId)) })] });
4740
+ actions = [...nativeActions, portableAction, confirmAction];
4741
+ bottomActions = [confirmAction];
4742
+ help = '↑↓ 移动 · Enter/Space 选择 · Esc 返回';
4743
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(0, nativeActions.length).map((action) => ({
4744
+ ...action, label: `${action.selected ? '[x]' : '[ ]'} ${action.label}`,
4745
+ })), focus: focus, kind: "action", compact: false }), _jsx(SemanticRows, { actions: actions, focus: focus, offset: nativeActions.length, kind: "action", compact: false })] });
4804
4746
  }
4805
4747
  }
4806
4748
  else if (route.name === 'installPlacement' && plugin) {
@@ -4860,7 +4802,7 @@ export function ConsoleAppV4(props) {
4860
4802
  scope: exactTargets[0].scope,
4861
4803
  }),
4862
4804
  },
4863
- { label: '返回目标平台', run: back },
4805
+ { role: 'navigation', label: '返回目标平台', run: back },
4864
4806
  ];
4865
4807
  }
4866
4808
  const scopeChoiceCount = commonScopes.length > 0 ? actions.length : Math.max(0, actions.length - 2);
@@ -4920,7 +4862,7 @@ export function ConsoleAppV4(props) {
4920
4862
  });
4921
4863
  },
4922
4864
  },
4923
- { label: '返回插件详情', run: back },
4865
+ { role: 'navigation', label: '返回插件详情', run: back },
4924
4866
  ];
4925
4867
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length + 1 }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length + 1 })] });
4926
4868
  help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
@@ -4958,7 +4900,7 @@ export function ConsoleAppV4(props) {
4958
4900
  description: '恢复依赖自动安排的建议位置',
4959
4901
  run: () => props.controller.resetDependencyScopes(),
4960
4902
  },
4961
- { label: '返回目标平台', run: back },
4903
+ { role: 'navigation', label: '返回目标平台', run: back },
4962
4904
  ];
4963
4905
  const placementViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(dependencyPlacementStaticRows));
4964
4906
  const userCount = draft?.targets.filter((target) => target.scope === 'user').length ?? 0;
@@ -5207,7 +5149,7 @@ export function ConsoleAppV4(props) {
5207
5149
  setScrollOffset(0);
5208
5150
  },
5209
5151
  }] : []),
5210
- { label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
5152
+ { role: 'navigation', label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
5211
5153
  ];
5212
5154
  const taskTargetLimit = policy.mode === 'compact' ? 3 : 6;
5213
5155
  const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
@@ -5243,7 +5185,7 @@ export function ConsoleAppV4(props) {
5243
5185
  ? plugin.version && plugin.version !== installedVersions[0]
5244
5186
  ? `${installedVersions[0]} → ${plugin.version}`
5245
5187
  : `已安装 ${installedVersions[0]}`
5246
- : plugin.version ? `可安装版本 ${plugin.version}` : '版本待确认';
5188
+ : plugin.version ?? '待确认';
5247
5189
  context = `${plugin.sourceLabel} · ${versionSummary}`;
5248
5190
  const updateTaskCandidates = plugin.configOnly ? []
5249
5191
  : props.controller.listPluginWriteTaskCandidates('update')
@@ -5357,18 +5299,20 @@ export function ConsoleAppV4(props) {
5357
5299
  actionId: 'content',
5358
5300
  label: '内容详情',
5359
5301
  description: contentOverview
5360
- ? `${contentOverview.items.length} 项目录内容 · 版本 ${contentOverview.pluginVersion}`
5302
+ ? `${contentOverview.items.length} 项当前目录内容`
5361
5303
  : '打开时读取当前目录版本的内容',
5362
5304
  run: () => go({ name: 'pluginContent', pluginId: plugin.pluginId }),
5363
5305
  } : undefined;
5364
5306
  const dependencyOverviewForDetail = dependencyOverview(plugin);
5365
- const dependencyAction = dependencyOverviewForDetail
5366
- && (dependencyOverviewForDetail.dependencies.length > 0
5367
- || dependencyOverviewForDetail.dependents.length > 0
5368
- || dependencyOverviewForDetail.blockers.length > 0) ? {
5307
+ const dependencyAction = !plugin.configOnly ? {
5369
5308
  actionId: 'dependencies',
5370
5309
  label: '依赖关系',
5371
- description: '在同一关系图中查看使用者和所需插件',
5310
+ description: dependencyOverviewForDetail
5311
+ && (dependencyOverviewForDetail.dependencies.length > 0
5312
+ || dependencyOverviewForDetail.dependents.length > 0
5313
+ || dependencyOverviewForDetail.blockers.length > 0)
5314
+ ? '在同一关系图中查看使用者和所需插件'
5315
+ : '当前没有已声明的所需插件或使用者',
5372
5316
  run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5373
5317
  } : undefined;
5374
5318
  const uninstallAction = installed ? {
@@ -5396,6 +5340,17 @@ export function ConsoleAppV4(props) {
5396
5340
  ...(installAction ? [installAction] : []),
5397
5341
  ...(settingsAction ? [{ ...settingsAction, actionId: 'settings' }] : []),
5398
5342
  ...(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
+ },
5399
5354
  ...(dependencyAction ? [dependencyAction] : []),
5400
5355
  ...(uninstallAction ? [uninstallAction] : []),
5401
5356
  {
@@ -5412,123 +5367,81 @@ export function ConsoleAppV4(props) {
5412
5367
  const detailPositions = plugin.installedPlacements
5413
5368
  .filter((placement) => placement.state !== 'notInstalled')
5414
5369
  .map((placement) => `${platformLabel(placement.platform)}:${pluginPlacementConclusions(placement).map((entry) => entry.text).join(' · ')}`);
5415
- const detailSummary = [`${plugin.description}`, `版本 ${plugin.version ?? '未知'} · ${plugin.sourceLabel}`,
5416
- ...detailPositions].join('\n');
5417
- const detailSummaryLines = objectCardRowLayout({
5418
- description: detailSummary, width: size.columns - 2, hasTags: true,
5419
- }).descriptionLines;
5420
- const detailExtraRows = (updateDescription ? 1 : 0) + (dependencyDetailText ? 1 : 0)
5421
- + (snapshot.completionNotice?.pluginId === plugin.pluginId ? 4 : 0);
5422
- const detailPageRows = Math.max(1, pageActionBudget(detailExtraRows + 4 + 3));
5423
- const detailOffset = Math.min(batchTargetOffset, Math.max(0, detailSummaryLines.length - 1));
5424
- const visibleDetailLines = detailSummaryLines.slice(detailOffset, detailOffset + detailPageRows);
5425
- if (detailSummaryLines.length > detailPageRows) {
5426
- context += ` · 摘要 ${detailOffset + 1}–${detailOffset + visibleDetailLines.length}/${detailSummaryLines.length}`;
5427
- actions.push(...paginationActions({
5428
- offset: detailOffset, total: detailSummaryLines.length, pageRows: detailPageRows,
5429
- move: (direction) => moveBatchTargetPage(detailSummaryLines.length, detailPageRows, direction),
5430
- }));
5431
- referenceDetailPagination = { total: detailSummaryLines.length, pageRows: detailPageRows };
5432
- }
5433
- currentActionViewportLineBudget = pageActionBudget(detailExtraRows + 4 + visibleDetailLines.length);
5434
- const detailViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
5435
- content = _jsxs(Box, { flexDirection: "column", children: [snapshot.completionNotice?.pluginId === plugin.pluginId
5436
- ? _jsx(Callout, { status: "success", title: snapshot.completionNotice.title, description: snapshot.completionNotice.description })
5437
- : null, _jsx(ObjectCardRow, { width: size.columns - 2, title: plugin.displayName, description: visibleDetailLines.join('\n'), tags: pluginPlatformBadges(plugin) }), updateDescription
5438
- ? _jsx(StaticRow, { title: "\u6709\u53EF\u7528\u66F4\u65B0", meta: updateDescription, status: "warning", compact: true })
5439
- : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit })] });
5370
+ const platformSummary = detailPositions.length > 0
5371
+ ? detailPositions.join(';')
5372
+ : plugin.supportedPlatforms.length > 0
5373
+ ? `${plugin.supportedPlatforms.map(platformLabel).join('、')} · 尚未安装`
5374
+ : '当前没有可用平台';
5375
+ 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}`),
5385
+ ];
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
+ }
5440
5424
  }
5441
5425
  else if (route.name === 'pluginMore' && plugin) {
5442
5426
  breadcrumb = `插件 / ${plugin.displayName} / 更多操作`;
5443
5427
  context = plugin.sourceLabel;
5444
5428
  title = '更多操作';
5445
5429
  description = '选择平台可使用同步、重新安装、移除和受限清理。';
5446
- const dependencySummary = plugin.dependencySummary;
5447
- const detailMenu = buildPluginDetailActions(plugin);
5448
- const updateTaskCandidates = props.controller.listPluginWriteTaskCandidates('update')
5449
- .filter((candidate) => candidate.target.pluginId === plugin.pluginId);
5450
- const restoreTaskCandidates = props.controller.listPluginWriteTaskCandidates('restore')
5451
- .filter((candidate) => candidate.target.pluginId === plugin.pluginId);
5452
5430
  actions = [
5453
- ...(updateTaskCandidates.length > 0 ? [{
5454
- label: '更新插件',
5455
- description: '使用精确版本和现有安装位置',
5456
- status: 'warning',
5457
- run: () => go({
5458
- name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
5459
- targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
5460
- versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
5461
- }),
5462
- }] : []),
5463
- ...(restoreTaskCandidates.length > 0 ? [{
5464
- label: '恢复正常',
5465
- description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
5466
- status: 'warning',
5467
- run: () => go({
5468
- name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
5469
- targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
5470
- }),
5471
- }] : []),
5472
- ...detailMenu.filter((action) => action.id !== 'refreshAndSync' && action.id !== 'about')
5473
- .map((action) => {
5474
- if (action.id === 'platform' || action.id === 'scopeConflict') {
5475
- const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
5476
- const projected = placementStatus(placement);
5477
- return {
5478
- label: `${platformLabel(action.platform)} 平台与维护`,
5479
- description: `${projected.description};同步、重装、移除和清理入口`,
5480
- meta: projected.value,
5481
- status: projected.status,
5482
- run: () => action.id === 'scopeConflict'
5483
- ? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
5484
- : go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
5485
- };
5486
- }
5487
- if (action.id === 'resolveScopeConflict')
5488
- return {
5489
- label: `处理 ${platformLabel(action.platform)} 位置冲突`,
5490
- description: action.description,
5491
- status: 'error',
5492
- run: () => beginScopeConflictRecovery(action),
5493
- };
5494
- if (action.id === 'dependencies')
5495
- return {
5496
- label: '依赖与当前可用关系',
5497
- description: dependencySummary?.issueCount ? `${dependencySummary.issueCount} 项需要处理` : '查看完整关系',
5498
- meta: dependencySummary ? dependencySummaryText(dependencySummary) : undefined,
5499
- run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5500
- };
5501
- if (action.id === 'settings')
5502
- return {
5503
- label: plugin.configOnly ? '查看已保存设置' : '插件设置',
5504
- run: () => plugin.configOnly
5505
- ? props.controller.beginSavedConfigRecovery(plugin.pluginId)
5506
- : beginConfigFlow(plugin.pluginId),
5507
- };
5508
- if (action.id === 'sourceProblem')
5509
- return {
5510
- label: '恢复插件来源', status: 'warning',
5511
- run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
5512
- };
5513
- if (action.id === 'upgradeAwCli' || action.id === 'capabilityTechnical')
5514
- return {
5515
- label: action.id === 'upgradeAwCli' ? '升级 AW CLI' : '查看功能支持详情',
5516
- status: action.id === 'upgradeAwCli' ? 'warning' : undefined,
5517
- run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
5518
- };
5431
+ ...plugin.installedPlacements
5432
+ .filter((placement) => placement.state !== 'notInstalled')
5433
+ .map((placement) => {
5434
+ const projected = placementStatus(placement);
5519
5435
  return {
5520
- label: '查看插件详情',
5521
- run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
5436
+ label: `${platformLabel(placement.platform)} 平台维护`,
5437
+ description: '同步、重新安装和受限清理',
5438
+ meta: projected.value,
5439
+ status: projected.status,
5440
+ run: () => go({ name: 'platform', pluginId: plugin.pluginId, platform: placement.platform }),
5522
5441
  };
5523
5442
  }),
5524
- ...(!detailMenu.some((action) => action.id === 'dependencies') ? [{
5525
- label: '依赖与当前可用关系',
5526
- description: '查看必需依赖、声明者和当前可用关系;当前没有声明的必需依赖',
5527
- run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
5528
- }] : []),
5529
- { label: '插件定义信息', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
5530
5443
  { label: '查看插件来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) },
5531
- { label: '诊断与受管内容', description: '查看组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
5444
+ { label: '诊断与技术详情', description: '查看插件身份、组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
5532
5445
  ];
5533
5446
  const moreViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
5534
5447
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: moreViewport.offset, limit: moreViewport.limit });
@@ -5587,7 +5500,7 @@ export function ConsoleAppV4(props) {
5587
5500
  run: () => beginSecondScope(plugin, action.platform, action.scope),
5588
5501
  };
5589
5502
  if (action.id === 'about')
5590
- return { label: '查看支持信息', description: '插件定义与平台支持', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
5503
+ return { label: '查看支持信息', description: '插件定义与平台支持', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
5591
5504
  if (action.id === 'source')
5592
5505
  return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
5593
5506
  if (action.id === 'outputCollision')
@@ -5645,7 +5558,7 @@ export function ConsoleAppV4(props) {
5645
5558
  return typedIssue
5646
5559
  ? { label: '查看对应问题', description: typedIssue.explanation, run: () => go({ name: 'issueDetail', issueId: typedIssue.issueId }) }
5647
5560
  : { label: placement?.state === 'installed' && placement.health === 'error' ? '查看安全技术详情' : placement?.state === 'installed' && placement.health === 'incomplete' ? '查看影响' : '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
5648
- return { label: '返回插件', run: back };
5561
+ return { role: 'navigation', label: '返回插件', run: back };
5649
5562
  };
5650
5563
  const offlineCleanupAction = typedIssue?.actions.find((action) => action.id === 'cleanup');
5651
5564
  const offlineCleanup = typedIssue !== undefined
@@ -5790,9 +5703,7 @@ export function ConsoleAppV4(props) {
5790
5703
  const candidateSummary = selectedField && selectedMutation
5791
5704
  ? selectedMutation.kind === 'unset' ? '恢复继承'
5792
5705
  : selectedField.sensitive ? '已更新'
5793
- : typeof selectedMutation.value === 'object'
5794
- ? objectSummary(selectedField, selectedMutation.value)
5795
- : scalarSummary(selectedField, selectedMutation.value)
5706
+ : fieldValueText(selectedField, selectedMutation.value)
5796
5707
  : selectedPreset && configCandidate
5797
5708
  ? `方案“${selectedPreset.title}” · ${Object.keys(configCandidate.mutations).length} 项修改`
5798
5709
  : undefined;
@@ -5842,10 +5753,10 @@ export function ConsoleAppV4(props) {
5842
5753
  const readonly = false;
5843
5754
  breadcrumb = `${plugin.displayName} / 设置`;
5844
5755
  title = '插件设置';
5845
- description = '先编辑值,再选择保存位置。Tab 可选择完整分组。';
5756
+ description = '先编辑值,再选择保存位置。←→ 切换分组。';
5846
5757
  if (configActionError)
5847
5758
  description = configActionError;
5848
- help = '↑↓ 移动 · Enter 修改 · Tab 固定操作 · ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5759
+ help = '↑↓ 移动 · Enter 修改· ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5849
5760
  if (!currentConfigPage) {
5850
5761
  content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u8BBE\u7F6E", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6\u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
5851
5762
  }
@@ -5871,36 +5782,24 @@ export function ConsoleAppV4(props) {
5871
5782
  }))
5872
5783
  : (activeGroup?.fields ?? []).filter((field) => readonly || configFieldCanSet(field) || configFieldCanUnset(field)).map((field) => ({
5873
5784
  label: field.title,
5874
- description: field.invalidOverrideIssue ?? [
5875
- `当前生效:${effectiveFieldDisplay(field)}(${field.effectiveSourceLabel})`,
5876
- ...pendingFieldTargets(field).map(({ scope: draftScope, value }) => `待保存到${draftScope === 'project' ? '当前项目' : '个人默认'}:${value}`),
5877
- field.description,
5878
- ].filter((value) => Boolean(value)).join(' · '),
5785
+ description: field.invalidOverrideIssue ?? (focus === (activeGroup?.fields ?? []).indexOf(field)
5786
+ ? [field.description, ...pendingFieldTargets(field).filter(() => pendingFieldTargets(field).length > 1
5787
+ || field.effectiveSourceLabel === '当前项目' && pendingFieldDisplay(field, 'project') === undefined).map(({ scope, value }) => `待保存到${configLayerLabel({ scope })}:${value}`),
5788
+ pendingFieldDisplay(field, 'user') !== undefined
5789
+ && field.effectiveSourceLabel === '当前项目'
5790
+ && pendingFieldDisplay(field, 'project') === undefined
5791
+ ? '个人默认待修改,当前项目不受此值影响' : undefined,
5792
+ ].filter(Boolean).join(' · ') : undefined),
5879
5793
  meta: (() => {
5880
- const targets = pendingFieldTargets(field);
5881
- if (targets.length === 0) {
5882
- return `${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`;
5883
- }
5884
- if (targets.length === 1) {
5885
- const target = targets[0];
5886
- return `${target.value} · 拟保存:${target.scope === 'project' ? '当前项目' : '个人默认'}`;
5887
- }
5888
- return targets.map((target) => `${target.scope === 'project' ? '当前项目' : '个人默认'}:${target.value}`).join(' · ');
5794
+ const project = pendingFieldDisplay(field, 'project');
5795
+ const user = pendingFieldDisplay(field, 'user');
5796
+ const pending = project ?? (field.effectiveSourceLabel === '当前项目' ? undefined : user);
5797
+ return `${pending ?? effectiveFieldDisplay(field)} · ${pending !== undefined ? `拟保存:${project !== undefined ? '当前项目' : '个人默认'} · 待保存` : field.effectiveSourceLabel}${pendingFieldTargets(field).length > 0 && pending === undefined ? ' · 有草稿' : ''}`;
5889
5798
  })(),
5890
5799
  configKey: field.key,
5891
- run: () => go(['project', 'user'].some((draftScope) => configLayerPages[draftScope]?.groups.flatMap((group) => group.fields)
5892
- .some((candidate) => candidate.key === field.key && candidate.configured)) ? {
5893
- name: 'configFieldAction',
5894
- pluginId: plugin.pluginId,
5895
- layer: mutableLayer,
5896
- fieldKey: field.key,
5897
- } : {
5898
- name: 'configFieldEditor',
5899
- pluginId: plugin.pluginId,
5900
- layer: mutableLayer,
5901
- fieldKey: field.key,
5902
- editor: fieldEditor(field),
5903
- ...fieldEditorMode(field),
5800
+ run: () => go({
5801
+ name: 'configFieldEditor', pluginId: plugin.pluginId, layer: mutableLayer,
5802
+ fieldKey: field.key, editor: fieldEditor(field), ...fieldEditorMode(field),
5904
5803
  }),
5905
5804
  }));
5906
5805
  const operationActions = [
@@ -5935,7 +5834,7 @@ export function ConsoleAppV4(props) {
5935
5834
  ];
5936
5835
  const configSaveAction = {
5937
5836
  actionId: 'config-save-summary',
5938
- label: configDraftCount > 0 ? `保存设置(${configDraftCount})` : '保存设置',
5837
+ label: configDraftCount > 0 ? `保存设置(${configDraftCount}) [s]` : '保存设置 [s]',
5939
5838
  description: '确认全部设置层的变化,再选择只保存或保存并应用',
5940
5839
  status: configDraftCount > 0 ? 'success' : 'muted',
5941
5840
  disabled: configDraftCount === 0 ? '没有待保存设置'
@@ -5946,20 +5845,18 @@ export function ConsoleAppV4(props) {
5946
5845
  go({ name: 'configSave', pluginId: route.pluginId, layer: { scope: 'effective' } });
5947
5846
  },
5948
5847
  };
5949
- const configGroupAction = {
5950
- actionId: 'config-select-group',
5951
- label: '选择分组',
5952
- description: '查看全部设置分组和快速方案',
5953
- disabled: visibleConfigTabs.length === 0 ? '没有可用设置分组' : undefined,
5954
- run: () => {
5955
- const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeTab?.id));
5956
- setConfigGroupChoiceFocus(current);
5957
- setConfigGroupChooserOpen(true);
5958
- setFocusZone('content');
5959
- },
5960
- };
5961
- pinnedActions = [configSaveAction, configGroupAction];
5962
- actions = [...tabActions, ...operationActions];
5848
+ bottomActions = [configSaveAction, {
5849
+ label: '设置选项', run: () => go({ ...route, view: 'options' }),
5850
+ }];
5851
+ actions = [...tabActions, ...bottomActions];
5852
+ if (route.view === 'options') {
5853
+ title = '设置选项';
5854
+ description = '查看各层存储值,或清空一个保存位置。';
5855
+ actions = [{ label: '查看当前值的来源', run: () => {
5856
+ setBatchTargetOffset(0);
5857
+ go({ ...route, view: 'values' });
5858
+ } }, ...operationActions];
5859
+ }
5963
5860
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
5964
5861
  const focusedConfigField = effectiveConfigFocus < (activeGroup?.fields.length ?? 0)
5965
5862
  ? activeGroup?.fields[effectiveConfigFocus]
@@ -5969,43 +5866,28 @@ export function ConsoleAppV4(props) {
5969
5866
  ? ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
5970
5867
  .some((field) => field.key === focusedConfigField.key && configFieldCanUnset(field)))
5971
5868
  : false;
5972
- help = focusZone === 'actions'
5973
- ? '↑↓ 移动 · Enter 选择固定操作 · Tab 返回设置字段 · s 查看保存汇总 · Esc 返回'
5974
- : canUnsetFocused
5975
- ? '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · u 恢复继承 · Esc 返回'
5976
- : '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · Esc 返回';
5869
+ help = canUnsetFocused
5870
+ ? '↑↓ 移动 · Enter 修改 · ←→ 分组· s 查看汇总 · u 恢复继承 · Esc 返回'
5871
+ : '↑↓ 移动 · Enter 修改 · ←→ 分组· s 查看汇总 · Esc 返回';
5977
5872
  }
5978
- const fieldFocus = tabActions.length > 0
5979
- ? effectiveConfigFocus < tabActions.length ? effectiveConfigFocus : Math.min(scrollOffset, tabActions.length - 1)
5980
- : 0;
5981
- const operationRows = operationActions.reduce((rows, action) => rows + (policy.mode === 'compact' || !action.description ? 1 : 2), 0);
5982
- const viewport = actionViewport(tabActions, false, fieldFocus, scrollOffset, pageActionBudget(2 + operationRows + 3 + (configDraftIssues.length > 0 ? 3 : 0)));
5983
5873
  const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
5984
5874
  const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
5985
5875
  context = configDraftCount > 0 ? `${configDraftCount} 项待保存` : countLabel;
5986
- const groupChoiceActions = visibleConfigTabs.map((tab) => ({
5987
- label: tab.label,
5988
- description: tab.kind === 'presets' ? `${tab.count} 个快速方案` : `${tab.count} 个设置字段`,
5989
- }));
5990
- const groupChoiceViewport = actionViewport(groupChoiceActions, false, configGroupChoiceFocus, 0, Math.max(2, pageActionBudget(5)));
5991
- if (configGroupChooserOpen) {
5992
- title = '选择设置分组';
5993
- description = '选择后回到同一设置总览;未保存草稿保持不变。';
5994
- help = '↑↓ 移动 · Enter 选择 · Esc 取消';
5995
- context = `${visibleConfigTabs.length} 个入口`;
5996
- }
5997
- content = configGroupChooserOpen
5998
- ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["\u5B8C\u6574\u540D\u79F0\uFF1A", visibleConfigTabs[configGroupChoiceFocus]?.label] }), _jsx(SectionCard, { title: "\u5168\u90E8\u5206\u7EC4\u548C\u5FEB\u901F\u65B9\u6848", meta: groupChoiceViewport.total > groupChoiceViewport.limit
5999
- ? `${groupChoiceViewport.position}/${groupChoiceViewport.total}` : undefined, children: _jsx(SemanticRows, { actions: groupChoiceActions, focus: configGroupChoiceFocus, kind: "section", compact: false, offset: groupChoiceViewport.offset, limit: groupChoiceViewport.limit }) })] })
6000
- : _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
6001
- id: action.actionId,
6002
- label: action.label,
6003
- disabled: Boolean(action.disabled),
6004
- })), active: focusZone === 'actions', focus: pinnedActionFocus }), !readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
6005
- ? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "section", compact: false, offset: viewport.offset, limit: viewport.limit }) })
6006
- : null, operationActions.length > 0
6007
- ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
6008
- : null] });
5876
+ content = route.view === 'options' ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })
5877
+ : route.view === 'values' ? (() => {
5878
+ title = '完整设置值与来源';
5879
+ description = '显示各保存位置的完整安全值;敏感值不回显。';
5880
+ 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
+ ? field.invalidOverrideIssue ? '需修正' : fieldValueText(field, field.draftValue ?? field.effectiveValue, true, field.effectiveSourceLabel !== '未设置')
5882
+ : fieldValueText(field, field.draftValue ?? field.value, true, field.configured)}`, size.columns - 4)));
5883
+ const pageRows = Math.max(1, pageActionBudget(4));
5884
+ const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
5885
+ 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 返回';
5889
+ 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
+ })() : _jsxs(Box, { flexDirection: "column", children: [!readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, _jsx(SequenceRows, { actions: actions, itemCount: tabActions.length, wrapValues: true, focus: effectiveConfigFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2 + (configDraftIssues.length > 0 ? 3 : 0)), compact: policy.mode === 'compact' })] });
6009
5891
  }
6010
5892
  }
6011
5893
  else if (route.name === 'configSave' && plugin) {
@@ -6036,13 +5918,13 @@ export function ConsoleAppV4(props) {
6036
5918
  pageRows,
6037
5919
  move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
6038
5920
  }),
6039
- { label: '返回保存汇总', run: back },
5921
+ { role: 'navigation', label: '返回保存汇总', run: back },
6040
5922
  ];
6041
5923
  content = _jsxs(Box, { flexDirection: "column", children: [detailOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${detailOffset} 行`, status: "muted", compact: true }) : null, visibleLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${detailOffset + index}:${line}`)), detailOffset + visibleLines.length < lines.length
6042
5924
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${lines.length - detailOffset - visibleLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6043
5925
  }
6044
5926
  else {
6045
- const visibleChanges = changes.slice(0, policy.mode === 'compact' ? 0 : 1);
5927
+ const visibleChanges = changes.slice(0, 2);
6046
5928
  breadcrumb = `${plugin.displayName} / 设置 / 保存汇总`;
6047
5929
  context = `${changes.length} 项未保存`;
6048
5930
  title = `保存 ${changes.length} 项设置修改?`;
@@ -6069,55 +5951,27 @@ export function ConsoleAppV4(props) {
6069
5951
  go({ ...route, view: 'changes' });
6070
5952
  },
6071
5953
  });
6072
- const configSaveStaticRows = 6 + visibleChanges.length * 2 + draftGroups.length;
6073
- const configSaveActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(configSaveStaticRows));
6074
- const changeSectionTitle = visibleChanges.length < changes.length
6075
- ? `设置变更 · 显示 ${visibleChanges.length}/${changes.length}(另 ${changes.length - visibleChanges.length} 项)`
6076
- : '设置变更';
6077
- content = _jsxs(Box, { flexDirection: "column", children: [visibleChanges.length > 0 ? _jsx(SectionCard, { title: changeSectionTitle, compact: policy.mode === 'compact', children: visibleChanges.map((change) => _jsx(ReviewChangeRow, { title: `~ ${change.title}`, value: `${change.before} → ${change.after}`, compact: true }, `${change.title}:${change.key}`)) }) : null, _jsxs(SectionCard, { title: "\u4FDD\u5B58\u76EE\u6807", compact: policy.mode === 'compact', children: [draftGroups.map((group) => _jsx(StaticRow, { title: scopeLabel(group.scope), meta: `${group.changes.length} 项修改`, compact: true }, group.scope)), _jsx(StaticRow, { title: "\u6BCF\u5C42\u72EC\u7ACB\u63D0\u4EA4\uFF1B\u5931\u8D25\u5C42\u4FDD\u7559\u8349\u7A3F", status: "warning", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: configSaveActionViewport.offset, limit: configSaveActionViewport.limit })] });
5954
+ const summaryLines = visibleChanges.flatMap((change) => {
5955
+ const lines = wrapConsoleText(`${change.title} · ${change.before ?? '未设置'} → ${change.after}`, Math.max(1, size.columns - 4));
5956
+ return lines.length > 2 ? [lines[0], `${lines[1]}…`] : lines;
5957
+ });
5958
+ const configSaveActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(summaryLines.length + 2));
5959
+ content = _jsxs(Box, { flexDirection: "column", children: [summaryLines.map((line, index) => _jsx(Text, { children: line }, index)), changes.length > visibleChanges.length ? _jsxs(Text, { children: ["\u53E6\u6709 ", changes.length - visibleChanges.length, " \u9879\uFF1B\u5B8C\u6574\u53D8\u5316\u4E2D\u53EF\u8BFB"] }) : null, _jsx(Text, { children: "\u6BCF\u5C42\u72EC\u7ACB\u63D0\u4EA4\uFF1B\u5931\u8D25\u5C42\u4FDD\u7559\u8349\u7A3F" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: configSaveActionViewport.offset, limit: configSaveActionViewport.limit })] });
6078
5960
  }
6079
5961
  }
6080
- else if (route.name === 'configFieldAction' && plugin && activeField) {
6081
- const layer = configLayerLabel(route.layer);
6082
- breadcrumb = `${plugin.displayName} / 设置 / ${layer} / ${activeField.title}`;
6083
- title = activeField.title;
6084
- description = `这个字段在${layer}中已经有值。`;
6085
- help = '↑↓ 移动 · Enter 选择 · Esc 返回';
6086
- const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
6087
- .filter((field) => field.key === activeField.key).map((field) => ({ scope, field })) ?? []);
6088
- const canSet = targetFields.some(({ field }) => configFieldCanSet(field));
6089
- const canUnset = targetFields.some(({ field }) => configFieldCanUnset(field));
6090
- actions = [
6091
- ...(canSet ? [{
6092
- label: '修改值',
6093
- run: () => go({
6094
- name: 'configFieldEditor',
6095
- pluginId: plugin.pluginId,
6096
- layer: route.layer,
6097
- fieldKey: activeField.key,
6098
- editor: fieldEditor(activeField),
6099
- ...fieldEditorMode(activeField),
6100
- }),
6101
- }] : []),
6102
- ...(canUnset ? [{
6103
- label: '恢复继承',
6104
- description: '选择要删除覆盖值的位置',
6105
- status: 'error',
6106
- run: () => removeDraft(activeField.key),
6107
- }] : []),
6108
- ];
6109
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [activeField.invalidOverrideIssue
6110
- ? _jsx(StaticRow, { title: "\u5F53\u524D\u8986\u76D6\u65E0\u6548", meta: activeField.invalidOverrideIssue, status: "warning" })
6111
- : null, !canSet && canUnset
6112
- ? _jsx(StaticRow, { title: "\u672C\u9879\u76EE\u65E7\u503C\u4E0D\u518D\u4F7F\u7528", meta: "\u5F53\u524D\u7EE7\u627F\u4E2A\u4EBA\u6216\u63D2\u4EF6\u9ED8\u8BA4\uFF1B\u5220\u9664\u65E7\u503C\u540E\u4FDD\u6301\u7EE7\u627F", status: "warning" })
6113
- : null, _jsx(StaticRow, { title: `${layer}中的值`, meta: currentFieldDisplay(activeField) }), _jsx(StaticRow, { title: "\u5F53\u524D\u5B9E\u9645\u751F\u6548", meta: `${effectiveFieldDisplay(activeField)} · 来自${activeField.effectiveSourceLabel}` }), !activeField.invalidOverrideIssue || activeField.invalidOverrideOwnedByLayer
6114
- ? _jsx(StaticRow, { title: "\u5220\u9664\u540E\u5C06\u6062\u590D", meta: `${activeField.fallbackDisplayValue ?? '未设置'} · 来自${activeField.fallbackSourceLabel ?? '未设置'}` })
6115
- : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6116
- }
6117
5962
  else if (route.name === 'configFieldEditor' && plugin && activeField) {
6118
5963
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title}`;
6119
5964
  const showChoices = (activeField.choices?.length ?? 0) > 0 && route.mode !== 'custom';
6120
- if (showChoices) {
5965
+ const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields).filter((field) => field.key === activeField.key) ?? []);
5966
+ const canSet = targetFields.some(configFieldCanSet);
5967
+ const canUnset = targetFields.some(configFieldCanUnset);
5968
+ if (!canSet) {
5969
+ title = activeField.title;
5970
+ description = activeField.invalidOverrideIssue ?? '此旧值不允许修改;可以删除覆盖并恢复继承。';
5971
+ actions = [];
5972
+ content = _jsx(StatusCard, { status: "warning", title: "\u4EC5\u53EF\u5220\u9664\u65E7\u503C", description: description });
5973
+ }
5974
+ else if (showChoices) {
6121
5975
  title = `选择${activeField.title}`;
6122
5976
  description = activeField.type !== 'boolean' && activeField.allowCustom
6123
5977
  ? '先选择插件提供的方案,也可以继续自定义。'
@@ -6180,18 +6034,12 @@ export function ConsoleAppV4(props) {
6180
6034
  .filter((property) => !fixedKeys.has(property))
6181
6035
  .sort((left, right) => left.localeCompare(right, 'en'));
6182
6036
  const propertySummary = (property, definition) => {
6183
- if (!Object.hasOwn(activeObjectDraft, property)) {
6184
- return 'required' in definition && definition.required ? '必填 · 未设置' : '未设置';
6185
- }
6186
- if (activeField.sensitive || definition.sensitive)
6187
- return '已设置';
6188
- const value = activeObjectDraft[property];
6189
- const preset = definition.choices?.presets.find((choice) => choice.value === value);
6190
- if (preset)
6191
- return preset.title;
6192
- if (typeof value === 'boolean')
6193
- return value ? '开启' : '关闭';
6194
- return String(value);
6037
+ const presentation = presentConfigValue({ definition,
6038
+ value: activeObjectDraft[property], privateValue: activeField.sensitive,
6039
+ configured: Object.hasOwn(activeObjectDraft, property),
6040
+ });
6041
+ return presentation.state === 'unset' && 'required' in definition && definition.required
6042
+ ? '必填 · 未设置' : presentation.text;
6195
6043
  };
6196
6044
  const propertyActions = [
6197
6045
  ...fixedEntries.map(([property, definition]) => ({
@@ -6242,6 +6090,16 @@ export function ConsoleAppV4(props) {
6242
6090
  : activeField.sensitive ? '当前值不会回显;留空不会自动删除旧值。' : activeField.description ?? '';
6243
6091
  content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: inputValue, sensitive: activeField.sensitive, placeholder: activeField.placeholder, columns: Math.max(8, size.columns - 8) }), description: inputError ?? (activeField.sensitive ? '输入已隐藏。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
6244
6092
  }
6093
+ if (canUnset) {
6094
+ const unset = { label: '恢复继承', description: '选择要删除覆盖值的位置', run: () => removeDraft(activeField.key) };
6095
+ const inputEditor = canSet && !showChoices && (route.editor === 'number' || route.editor === 'string');
6096
+ help += ' · Ctrl-R 恢复继承';
6097
+ if (!inputEditor) {
6098
+ const offset = actions.length;
6099
+ actions.push(unset);
6100
+ content = _jsxs(Box, { flexDirection: "column", children: [content, _jsx(SemanticRows, { actions: actions, offset: offset, focus: focus, kind: "action", compact: true })] });
6101
+ }
6102
+ }
6245
6103
  }
6246
6104
  else if (route.name === 'configObjectPropertyEditor' && plugin
6247
6105
  && activeField?.definition.type === 'object' && activeObjectDraft) {
@@ -6380,16 +6238,25 @@ export function ConsoleAppV4(props) {
6380
6238
  },
6381
6239
  ] : []),
6382
6240
  { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => toggleTechnical() },
6383
- { label: '返回', run: back },
6241
+ { role: 'navigation', label: '返回', run: back },
6384
6242
  ];
6385
6243
  const unsupportedScopes = resetScopes.filter((scope) => configResetAvailability[scope]?.resettable === false);
6386
6244
  const unsupportedRows = unsupportedScopes.map((scope) => _jsx(StaticRow, { title: `${scope === 'user' ? '个人默认' : '当前项目'}设置层不能自动重置:特殊节点,请手动移动`, status: "warning", compact: true }, `unsupported-reset:${scope}`));
6245
+ const technicalPageRows = responsiveBudget(2, 4);
6246
+ if (showTechnical && !invalidLayer) {
6247
+ referenceDetailPagination = {
6248
+ total: diagnosticDetailLines(diagnostic, size.columns - 6).length,
6249
+ pageRows: technicalPageRows,
6250
+ };
6251
+ help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6252
+ }
6253
+ const unavailableActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(technicalPageRows + 6 + unsupportedRows.length));
6387
6254
  content = invalidLayer
6388
6255
  ? _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u4E0D\u53EF\u8BFB\u53D6\u7684\u8BBE\u7F6E\u5C42", children: (route.invalidStores ?? []).map((store) => _jsx(StaticRow, { title: store.scope === 'user' ? '个人默认设置' : '当前项目设置', meta: "\u9700\u8981\u91CD\u7F6E\u6216\u624B\u52A8\u79FB\u5F00", status: "warning", compact: true }, store.scope)) }), showTechnical ? _jsxs(TechnicalDetails, { children: [(route.invalidStores ?? []).flatMap((store) => [
6389
6256
  _jsx(StaticRow, { title: scopeLabel(store.scope), meta: store.code, compact: true }, store.scope),
6390
6257
  _jsx(StaticRow, { title: "path summary", meta: store.pathSummary, compact: true }, `${store.scope}:path`),
6391
6258
  ]), _jsx(StaticRow, { title: "stage", meta: safeDiagnosticCode(route.diagnosticStage, 'read-config'), compact: true })] }) : null, unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] })
6392
- : _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6259
+ : _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, columns: size.columns, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? technicalPageRows : undefined, compact: policy.mode === 'compact' }), unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: showTechnical || policy.mode === 'compact', offset: showTechnical ? unavailableActionViewport.offset : undefined, limit: showTechnical ? unavailableActionViewport.limit : undefined })] });
6393
6260
  }
6394
6261
  else if (route.name === 'sourceList') {
6395
6262
  breadcrumb = '插件来源';
@@ -6415,14 +6282,18 @@ export function ConsoleAppV4(props) {
6415
6282
  : configured?.type === 'git'
6416
6283
  && (configured.url.startsWith('git@') || configured.url.startsWith('ssh://'))
6417
6284
  ? 'SSH' : 'HTTPS';
6285
+ const policy = overview.detail?.type === 'path' ? 'Path'
6286
+ : overview.detail?.refPolicy === 'explicit'
6287
+ ? `固定 ${sourceRefSummary(overview.detail.explicitRef ?? overview.detail.requestedRef)}`
6288
+ : `通道 ${sourceRefSummary(overview.detail?.requestedRef)}`;
6418
6289
  const version = `${generation?.active?.catalogVersion ?? '未启用'}${freshness?.candidate?.catalogVersion
6419
6290
  ? `→${freshness.candidate.catalogVersion}` : ''}`;
6420
6291
  const available = generation?.state === 'ready' || health === 'ready';
6421
6292
  const unavailable = health === 'unavailable' || health === 'error' || health === 'recovery';
6422
6293
  return {
6423
6294
  label: source.id,
6424
- description: sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary),
6425
- meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`}`,
6295
+ description: `${sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary)} · ${policy}`,
6296
+ meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`} · ${relativeCheckTime(freshness?.checkedAt)}`,
6426
6297
  tags: _jsxs(Box, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", source.primary ? '主来源' : transport, "] "] }), _jsx(StatusBadge, { items: [{ key: 'health', label: available ? '可用' : unavailable ? '不可用' : '未确认',
6427
6298
  symbol: available ? '✓' : unavailable ? '!' : '?', status: available ? 'success' : unavailable ? 'warning' : 'muted' }] }), freshness && freshness.state !== 'current' && freshness.state !== 'fixed'
6428
6299
  ? _jsxs(Text, { color: tuiTheme.colors.accent, children: [" [", freshness.state === 'updateAvailable' ? '↑ ' : '', presentation.label, "]"] })
@@ -6431,29 +6302,19 @@ export function ConsoleAppV4(props) {
6431
6302
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
6432
6303
  };
6433
6304
  });
6434
- pinnedActions = [
6305
+ bottomActions = [
6435
6306
  { label: '+ 添加来源', description: 'Git 仓库或本地目录', run: beginAddSource },
6436
6307
  {
6437
- label: '检查全部来源',
6308
+ actionId: 'source-check-all', label: '检查全部来源 [r]',
6438
6309
  description: '并行检查各来源;必要时完整验证;结果不会自动启用待检查版本',
6439
6310
  run: () => props.controller.recheckAllCatalogSources(),
6440
6311
  },
6441
6312
  { label: 'Catalog 本地存储', description: '检查占用和预览安全清理', run: () => go({ name: 'catalogStoreOverview' }) },
6442
6313
  ];
6443
- help = focusZone === 'actions'
6444
- ? '←→ 移动固定操作 · Enter 选择 · Tab 返回来源卡片 · Esc 返回'
6445
- : '↑↓ 移动 · Enter 打开 · Tab 固定操作 · r/c 检查全部 · Esc 返回';
6446
- const viewport = rowViewport({
6447
- rowHeights: actions.map((action) => objectCardRowLayout({
6448
- description: action.description ?? '', meta: action.meta, width: size.columns - 2, hasTags: true,
6449
- }).height),
6450
- focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(1),
6451
- });
6452
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
6453
- id: action.actionId ?? `${action.label}:${index}`,
6454
- label: action.label,
6455
- disabled: Boolean(action.disabled),
6456
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
6314
+ help = '↑↓ 移动 · Enter 打开 · Esc 返回';
6315
+ const sourceCount = actions.length;
6316
+ actions = [...actions, ...bottomActions];
6317
+ content = _jsx(SequenceRows, { actions: actions, itemCount: sourceCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(), compact: policy.mode === 'compact', cards: true });
6457
6318
  }
6458
6319
  else if (route.name === 'catalogStoreOverview') {
6459
6320
  breadcrumb = '插件来源 / 本地存储';
@@ -6475,7 +6336,7 @@ export function ConsoleAppV4(props) {
6475
6336
  },
6476
6337
  { actionId: 'catalog-store-rules', label: '查看保留规则', description: '了解哪些目录会保留,以及旧版本何时可以清理', run: () => go({ name: 'catalogStoreRules' }) },
6477
6338
  { actionId: 'catalog-store-refresh', label: '重新检查', description: '重新读取当前本地存储', run: () => setCatalogStoreRefresh((value) => value + 1) },
6478
- { actionId: 'catalog-store-back', label: '返回来源列表', run: back },
6339
+ { role: 'navigation', actionId: 'catalog-store-back', label: '返回来源列表', run: back },
6479
6340
  ];
6480
6341
  content = _jsxs(Box, { flexDirection: "column", children: [catalogStoreError
6481
6342
  ? _jsx(Callout, { status: "warning", title: "\u672C\u5730\u5B58\u50A8\u68C0\u67E5\u5931\u8D25", description: "\u672A\u6267\u884C\u5220\u9664\u3002\u8BF7\u68C0\u67E5 Catalog \u914D\u7F6E\u548C\u672C\u5730\u5B58\u50A8\u540E\u91CD\u8BD5\u3002" })
@@ -6490,7 +6351,7 @@ export function ConsoleAppV4(props) {
6490
6351
  ? '' : '清理前会再次检查每个项目,只删除能够确认安全的旧内容。';
6491
6352
  actions = [
6492
6353
  { actionId: 'catalog-store-rules-technical', label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
6493
- { actionId: 'catalog-store-rules-back', label: '返回存储概览', run: back },
6354
+ { role: 'navigation', actionId: 'catalog-store-rules-back', label: '返回存储概览', run: back },
6494
6355
  ];
6495
6356
  content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical ? _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u59CB\u7EC8\u4FDD\u7559 \u00B7 \u5F53\u524D\u4F7F\u7528\u7684\u76EE\u5F55\u4E0D\u4F1A\u5220\u9664", children: _jsx(StaticRow, { title: "\u4E0D\u5C5E\u4E8E AW\u3001\u65E0\u6CD5\u786E\u8BA4\u5B89\u5168\u6216\u4ECD\u5728\u4F7F\u7528\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559", status: "muted", compact: true }) }), _jsxs(SectionCard, { title: "\u6EE1\u8DB3\u65F6\u95F4\u548C\u5B8C\u6574\u9A8C\u8BC1\u540E\u53EF\u56DE\u6536", children: [_jsx(StaticRow, { title: "\u65E7\u7248\u672C\u6EE1\u8DB3\u4FDD\u7559\u671F\u9650\u540E\u624D\u6E05\u7406", compact: true }), _jsx(StaticRow, { title: "\u672A\u5B8C\u6210\u7684\u4E0B\u8F7D\u6216\u66FF\u6362\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u9694\u79BB\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u5BF9\u5E94\u6B63\u5F0F\u7248\u672C\u5B8C\u6574\u53EF\u7528\u540E\u624D\u6E05\u7406", compact: true })] })] }) : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u6E05\u7406\u5224\u5B9A\u8BC1\u636E", children: [_jsx(StaticRow, { title: "\u9501", meta: "AW_HOME \u5199\u9501\u5185\u91CD\u65B0\u68C0\u67E5", compact: true }), _jsx(StaticRow, { title: "\u6B63\u5F0F\u76EE\u5F55\u7248\u672C", meta: "\u975E Active \u9879\u4FDD\u7559 30 \u5929", compact: true }), _jsx(StaticRow, { title: "staging\u3001replacement\u3001delete claim", meta: "\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "quarantine", meta: "\u4FDD\u7559 24 \u5C0F\u65F6\uFF1B\u5BF9\u5E94\u6B63\u5F0F\u76EE\u5F55\u7248\u672C\u5FC5\u987B\u5B8C\u6574\u5065\u5EB7", compact: true }), _jsx(StaticRow, { title: "\u4FDD\u7559\u8282\u70B9", meta: "foreign\u3001\u7B26\u53F7\u94FE\u63A5\u3001unsafe\u3001\u635F\u574F\u6216\u672A\u77E5\u9879\u76EE", compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6496
6357
  }
@@ -6506,17 +6367,26 @@ export function ConsoleAppV4(props) {
6506
6367
  && generation?.configMismatch === true;
6507
6368
  const logicalIdentityChanged = generation?.errorCode === 'catalogLogicalIdentityChanged';
6508
6369
  const activationBlocked = props.controller.catalogSourceRecoveryBlockReason();
6370
+ const generationMissingCount = generation?.compatibility?.state === 'unsupported'
6371
+ ? generation.compatibility.missingCapabilities.length : undefined;
6372
+ const freshnessMissingCount = freshness?.missingCapabilities?.length;
6509
6373
  breadcrumb = `插件来源 / ${route.sourceId}`;
6510
6374
  context = detail?.type === 'path' ? '本地目录' : 'Git';
6511
6375
  title = route.sourceId;
6512
6376
  description = generationState === 'incompatible'
6513
- ? `当前目录版本与 AW 不兼容;缺少 ${generation?.compatibility?.state === 'unsupported' ? generation.compatibility.missingCapabilities.length : 0} 项功能支持。`
6377
+ ? generationMissingCount && generationMissingCount > 0
6378
+ ? `当前目录版本与 AW 不兼容;缺少 ${generationMissingCount} 项功能支持。`
6379
+ : '当前目录版本与 AW 不兼容;所需功能无法确认。'
6514
6380
  : freshness?.state === 'incompatible'
6515
6381
  ? generationState === 'repairRequired'
6516
6382
  ? `待检查版本需要升级 AW 或修改来源后才能使用;损坏的当前启用版本仍可按旧版本精确修复。`
6517
6383
  : activeVersion && generationState === 'ready'
6518
- ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
6519
- : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;这个来源没有可用目录版本。`
6384
+ ? freshnessMissingCount && freshnessMissingCount > 0
6385
+ ? `当前 AW 缺少 ${freshnessMissingCount} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
6386
+ : `待检查版本所需功能无法确认;已验证的当前目录版本 ${activeVersion} 仍可浏览和使用。`
6387
+ : freshnessMissingCount && freshnessMissingCount > 0
6388
+ ? `当前 AW 缺少 ${freshnessMissingCount} 项待检查版本所需功能;这个来源没有已验证可用的目录版本。`
6389
+ : '待检查版本所需功能无法确认;这个来源没有已验证可用的目录版本。'
6520
6390
  : freshness?.state === 'checkFailed' && activeVersion && generationState === 'ready'
6521
6391
  ? `更新检查失败,但当前目录版本 ${activeVersion} 仍可浏览和使用。`
6522
6392
  : freshness?.state === 'updateAvailable' && generationState === 'ready'
@@ -6529,7 +6399,7 @@ export function ConsoleAppV4(props) {
6529
6399
  ? '当前目录版本损坏,且来源配置已变化。验证待检查版本后可精确修复同一版本,或明确切换到新版本。'
6530
6400
  : '当前目录版本缺失或损坏。可在这里精确修复。'
6531
6401
  : generationState === 'configMismatch' ? '来源配置与当前启用版本不一致。可验证待检查版本并切换。'
6532
- : logicalIdentityChanged ? '来源发布身份已变化。请编辑来源,或移除后重新添加。'
6402
+ : logicalIdentityChanged ? '来源发布身份已变化;旧 Active 是否仍可用需要单独验证。请编辑来源,或移除后重新添加。'
6533
6403
  : '来源没有可用的启用版本。可在这里取得并启用。';
6534
6404
  help = '↑↓ 移动 · Enter 选择 · Esc 返回';
6535
6405
  if (policy.mode === 'compact')
@@ -6626,6 +6496,12 @@ export function ConsoleAppV4(props) {
6626
6496
  }]),
6627
6497
  { actionId: 'source-remove', label: '移除来源', description: '移除来源配置', status: 'error', disabled: props.controller.listSources().length <= 1 ? '至少保留一个来源' : detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6628
6498
  { actionId: 'source-technical', label: '技术详情', description: '查看脱敏错误代码和当前版本证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6499
+ ...(freshness?.operationRecordId ? [{
6500
+ actionId: 'source-check-diagnostic',
6501
+ label: '查看检查诊断与报告',
6502
+ description: `安全记录 ${freshness.operationRecordId}`,
6503
+ run: () => go({ name: 'operationDiagnostic', recordId: freshness.operationRecordId }),
6504
+ }] : []),
6629
6505
  ...(generationState !== 'ready'
6630
6506
  || freshness?.state === 'updateAvailable'
6631
6507
  || freshness?.state === 'localChangesAvailable'
@@ -6638,13 +6514,6 @@ export function ConsoleAppV4(props) {
6638
6514
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6639
6515
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6640
6516
  }] : []),
6641
- {
6642
- actionId: 'source-full-validate',
6643
- label: '完整验证',
6644
- description: '强制重新取得并完整验证这个来源',
6645
- disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6646
- run: () => props.controller.fullValidateCatalogSource(route.sourceId),
6647
- },
6648
6517
  ];
6649
6518
  const sourcePrimaryAction = generationAction.length === 0 ? [] : [{
6650
6519
  ...generationAction[0],
@@ -6664,9 +6533,9 @@ export function ConsoleAppV4(props) {
6664
6533
  : configuredSource?.type === 'git'
6665
6534
  && (configuredSource.url.startsWith('git@') || configuredSource.url.startsWith('ssh://'))
6666
6535
  ? 'SSH' : 'HTTPS';
6667
- const versionPolicy = detail?.type === 'path' ? '当前本地内容'
6668
- : detail?.refPolicy === 'cliChannel' ? '跟随 AW 的稳定版或预览版目录'
6669
- : '固定版本或 Git 引用';
6536
+ const versionPolicy = detail?.type === 'path' ? 'Path · 当前本地内容'
6537
+ : detail?.refPolicy === 'cliChannel' ? `跟随 AW 通道 · ${sourceRefSummary(detail.requestedRef)}`
6538
+ : `固定 ref · ${sourceRefSummary(detail?.explicitRef ?? detail?.requestedRef)}`;
6670
6539
  const sourceCount = snapshot.viewModel?.sourceTabs.find((source) => source.id === route.sourceId)?.count;
6671
6540
  const checkAction = {
6672
6541
  actionId: 'source-check',
@@ -6675,16 +6544,20 @@ export function ConsoleAppV4(props) {
6675
6544
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6676
6545
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6677
6546
  };
6547
+ const viewPluginsAction = {
6548
+ actionId: 'source-view-plugins',
6549
+ label: '查看此来源插件',
6550
+ description: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`,
6551
+ run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
6552
+ };
6553
+ const healthyWithoutUpdate = generationState === 'ready'
6554
+ && (freshness?.state === 'current' || freshness?.state === 'fixed');
6678
6555
  actions = [
6556
+ ...(healthyWithoutUpdate ? [viewPluginsAction] : []),
6679
6557
  ...sourcePrimaryAction,
6680
6558
  ...(sourcePrimaryAction.some((action) => action.actionId === 'source-check')
6681
6559
  ? [] : [checkAction]),
6682
- {
6683
- actionId: 'source-view-plugins',
6684
- label: '查看此来源插件',
6685
- description: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`,
6686
- run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
6687
- },
6560
+ ...(healthyWithoutUpdate ? [] : [viewPluginsAction]),
6688
6561
  managementActions[0],
6689
6562
  managementActions.find((action) => action.actionId === 'source-remove'),
6690
6563
  {
@@ -6706,7 +6579,9 @@ export function ConsoleAppV4(props) {
6706
6579
  `${transport} · ${sourceLocationSummary(detail?.type ?? 'git', detail?.locationSummary)}`,
6707
6580
  `当前 ${activeVersion ?? '未启用'}${candidateVersion ? ` → ${candidateVersion}` : ''} · ${sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`}`,
6708
6581
  `版本策略:${versionPolicy}`,
6709
- `检查:${freshnessLabel} · ${freshness?.checkedAt ?? '尚未检查'}`,
6582
+ `检查:${freshnessLabel} · ${freshness?.state === 'checking'
6583
+ ? '正在检查这个来源;必要时验证内容'
6584
+ : relativeCheckTime(freshness?.checkedAt)}`,
6710
6585
  ].join('\n');
6711
6586
  const sourceCardHeight = objectCardRowLayout({
6712
6587
  description: sourceSummary, width: size.columns - 2, hasTags: true,
@@ -6797,6 +6672,11 @@ export function ConsoleAppV4(props) {
6797
6672
  run: () => props.controller.recheckCatalogSource(route.sourceId) },
6798
6673
  { label: '技术详情', description: '查看脱敏错误代码和版本证据',
6799
6674
  run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6675
+ ...(freshness?.operationRecordId ? [{
6676
+ label: '查看检查诊断与报告',
6677
+ description: `安全记录 ${freshness.operationRecordId}`,
6678
+ run: () => go({ name: 'operationDiagnostic', recordId: freshness.operationRecordId }),
6679
+ }] : []),
6800
6680
  ];
6801
6681
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
6802
6682
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
@@ -6814,7 +6694,7 @@ export function ConsoleAppV4(props) {
6814
6694
  actions = [
6815
6695
  { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalPageRows)) },
6816
6696
  { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalPageRows)) },
6817
- { label: '返回来源详情', run: back },
6697
+ { role: 'navigation', label: '返回来源详情', run: back },
6818
6698
  ];
6819
6699
  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 })] });
6820
6700
  }
@@ -6970,7 +6850,7 @@ export function ConsoleAppV4(props) {
6970
6850
  pageRows,
6971
6851
  move: (direction) => moveBatchTargetPage(lines.length, pageRows, direction),
6972
6852
  }),
6973
- { label: '返回修改汇总', run: back },
6853
+ { role: 'navigation', label: '返回修改汇总', run: back },
6974
6854
  ];
6975
6855
  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
6976
6856
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${lines.length - detailOffset - visibleLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
@@ -7022,7 +6902,7 @@ export function ConsoleAppV4(props) {
7022
6902
  actions = [{ label: sourceDependenciesExpanded ? '收起依赖内容' : '查看依赖内容', description: '只显示插件、来源、平台、位置和类别聚合', run: () => {
7023
6903
  setSourceDependenciesExpanded((value) => !value);
7024
6904
  setBatchTargetOffset(0);
7025
- } }, { label: '返回来源详情', run: back }];
6905
+ } }, { role: 'navigation', label: '返回来源详情', run: back }];
7026
6906
  const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
7027
6907
  const referenceLimit = Math.max(1, pageActionBudget(12));
7028
6908
  const references = sourceReferenceSummary?.dependencies ?? [];
@@ -7053,7 +6933,7 @@ export function ConsoleAppV4(props) {
7053
6933
  : '当前状态检查没有发现需要处理的安装、来源或设置问题。';
7054
6934
  if (issues.length === 0) {
7055
6935
  actions = [
7056
- { label: '返回首页', run: () => requestSessionNavigation('home') },
6936
+ { role: 'navigation', label: '返回首页', run: () => requestSessionNavigation('home') },
7057
6937
  { label: '浏览插件', run: () => go({ name: 'pluginList', mode: 'browse' }) },
7058
6938
  ];
7059
6939
  }
@@ -7073,8 +6953,18 @@ export function ConsoleAppV4(props) {
7073
6953
  : go({ name: 'issueDetail', issueId: entry.issueId }),
7074
6954
  }));
7075
6955
  }
6956
+ const reminder = snapshot.projectIntentReminder;
6957
+ const pendingProjectTargets = (reminder?.actionableTargets.length ?? 0)
6958
+ + (reminder?.blockedTargets.length ?? 0) + (reminder?.unconfirmedTargets.length ?? 0);
6959
+ if (snapshot.projectIntentIssue || pendingProjectTargets > 0) {
6960
+ title = '需要处理';
6961
+ description = '查看项目插件、安装、来源或设置问题。';
6962
+ actions.unshift({ actionId: 'project-status', label: '项目插件',
6963
+ description: snapshot.projectIntentIssue ? '项目声明需要检查' : `${pendingProjectTargets} 个位置待处理或未确认`,
6964
+ status: 'warning', run: () => go({ name: 'projectStatus' }) });
6965
+ }
7076
6966
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
7077
- if (issues.length === 0) {
6967
+ if (issues.length === 0 || snapshot.projectIntentIssue || pendingProjectTargets > 0) {
7078
6968
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
7079
6969
  }
7080
6970
  else {
@@ -7144,10 +7034,10 @@ export function ConsoleAppV4(props) {
7144
7034
  || entry.id === 'keepUserScope' || entry.id === 'keepProjectScope' ? 'error' : undefined,
7145
7035
  run: () => runIssueAction(entry),
7146
7036
  }));
7147
- const issueDetailRows = diagnosticDetailRowCount(issue.diagnostic);
7037
+ const issueDetailRows = diagnosticDetailLines(issue.diagnostic, size.columns - 6).length;
7148
7038
  const issueDetailPageRows = policy.mode === 'compact'
7149
7039
  ? 1
7150
- : Math.max(1, responsiveBudget(5, 10) - 2);
7040
+ : Math.max(1, responsiveBudget(1, 4));
7151
7041
  const issuePaginationActions = showTechnical && issueDetailRows > issueDetailPageRows
7152
7042
  ? paginationActions({
7153
7043
  offset: batchTargetOffset,
@@ -7159,20 +7049,18 @@ export function ConsoleAppV4(props) {
7159
7049
  actions.push(...issuePaginationActions);
7160
7050
  if (issuePaginationActions.length > 0) {
7161
7051
  referenceDetailPagination = { total: issueDetailRows, pageRows: issueDetailPageRows };
7162
- help = 'p/n 查看全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7052
+ help = 'p/n 或 PgUp/PgDn 阅读全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7163
7053
  }
7164
7054
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
7165
7055
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
7166
- const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? issueDetailPageRows : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
7056
+ const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, columns: size.columns, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? issueDetailPageRows : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
7167
7057
  || issue.category === 'scopeConflict' });
7168
7058
  const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
7169
- const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
7170
- ? {
7171
- offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)),
7172
- limit: 2,
7173
- }
7174
- : {};
7175
- if (issue.category === 'dependencyStillRequired' && issuePlugin) {
7059
+ if (showTechnical) {
7060
+ const technicalActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(issueDetailPageRows + 6));
7061
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: technicalActionViewport.offset, limit: technicalActionViewport.limit })] });
7062
+ }
7063
+ else if (issue.category === 'dependencyStillRequired' && issuePlugin) {
7176
7064
  const blockingDependents = issue.platform && issue.scope
7177
7065
  ? props.controller.getBlockingDependents(issuePlugin.pluginId, issue.platform, issue.scope)
7178
7066
  : issueOverview?.dependents ?? [];
@@ -7197,7 +7085,7 @@ export function ConsoleAppV4(props) {
7197
7085
  issue.dependencyPlatform ? platformLabel(issue.dependencyPlatform) : undefined,
7198
7086
  issue.dependencyScope ? scopeLabel(issue.dependencyScope) : undefined,
7199
7087
  ].filter((entry) => Boolean(entry)).join(' · ');
7200
- content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', ...compactTechnicalActionViewport })] });
7088
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7201
7089
  }
7202
7090
  else if (issue.category === 'dependencyTargetUnsupported' && issuePlugin) {
7203
7091
  const issueScope = issue.scope ?? scope;
@@ -7269,7 +7157,7 @@ export function ConsoleAppV4(props) {
7269
7157
  status: operationHistoryState(record).status,
7270
7158
  run: () => go({ name: 'operationDiagnostic', recordId: record.recordId }),
7271
7159
  })),
7272
- { label: '返回上一页', run: back },
7160
+ { role: 'navigation', label: '返回上一页', run: back },
7273
7161
  ];
7274
7162
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
7275
7163
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: records.some((record) => record.state !== 'succeeded') ? 'warning' : 'success', title: records.length === 0 ? '没有可回看的安全结果' : '操作结果与问题', description: "\u9009\u62E9\u4E00\u9879\u53EF\u67E5\u770B\u5B8C\u6574\u5B89\u5168\u8BE6\u60C5\u6216\u62A5\u544A\u9884\u89C8\u3002", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
@@ -7283,8 +7171,8 @@ export function ConsoleAppV4(props) {
7283
7171
  ? '可滚动查看所有目标、阶段、代码和已保留的安全原因。'
7284
7172
  : '这项会话记录已经不再可用。';
7285
7173
  const preview = detail ? props.controller.previewOperationReport(route.recordId) : undefined;
7286
- const detailLineLimit = 2;
7287
- const detailLines = detail ? operationDiagnosticLineCount(detail) : 0;
7174
+ const detailLineLimit = operationDetailPageRows;
7175
+ const detailLines = detail ? operationDiagnosticLineCount(detail, size.columns) : 0;
7288
7176
  const lastDetailOffset = Math.max(0, detailLines - detailLineLimit);
7289
7177
  actions = detail ? [
7290
7178
  preview
@@ -7292,10 +7180,10 @@ export function ConsoleAppV4(props) {
7292
7180
  : { label: '报告当前不可用', disabled: '无法生成这项记录的报告' },
7293
7181
  { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - detailLineLimit)) },
7294
7182
  { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastDetailOffset, value + detailLineLimit)) },
7295
- { label: '返回操作记录', run: back },
7296
- ] : [{ label: '返回操作记录', run: back }];
7183
+ { role: 'navigation', label: '返回操作记录', run: back },
7184
+ ] : [{ role: 'navigation', label: '返回操作记录', run: back }];
7297
7185
  const diagnosticActionViewport = actionViewport(actions, policy.mode === 'compact', focus, 0, policy.mode === 'compact' ? 2 : 3);
7298
- content = detail ? _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationDiagnosticScreen, { detail: detail, compact: policy.mode === 'compact', offset: scrollOffset, limit: detailLineLimit }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: diagnosticActionViewport.offset, limit: diagnosticActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u8BB0\u5F55\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u64CD\u4F5C\u8BB0\u5F55\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u7ED3\u679C\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7186
+ content = detail ? _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationDiagnosticScreen, { detail: detail, compact: policy.mode === 'compact', columns: size.columns, offset: scrollOffset, limit: detailLineLimit }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: diagnosticActionViewport.offset, limit: diagnosticActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u8BB0\u5F55\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u64CD\u4F5C\u8BB0\u5F55\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u7ED3\u679C\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7299
7187
  }
7300
7188
  else if (route.name === 'operationReportPreview') {
7301
7189
  const preview = props.controller.previewOperationReport(route.recordId);
@@ -7312,8 +7200,8 @@ export function ConsoleAppV4(props) {
7312
7200
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { title: "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84", focused: true, compact: policy.mode === 'compact', children: _jsx(InputDisplay, { value: reportSavePath, placeholder: "/tmp/aw-operation-report.md", columns: Math.max(12, size.columns - 10) }) }), reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(StaticRow, { title: "\u4EC5\u4FDD\u5B58\u5F53\u524D\u9884\u89C8\u4E2D\u7684\u5B89\u5168\u5185\u5BB9\u3002", status: "muted", compact: true })] });
7313
7201
  }
7314
7202
  else if (preview) {
7315
- const reportLineLimit = 2;
7316
- const reportLines = operationReportLineCount(preview);
7203
+ const reportLineLimit = operationDetailPageRows;
7204
+ const reportLines = operationReportLineCount(preview, size.columns);
7317
7205
  const lastReportOffset = Math.max(0, reportLines - reportLineLimit);
7318
7206
  actions = [
7319
7207
  { label: '选择本地保存位置', description: '保存前输入明确的文件路径', run: () => {
@@ -7331,16 +7219,16 @@ export function ConsoleAppV4(props) {
7331
7219
  } },
7332
7220
  { label: 'p 上一页', run: () => setScrollOffset((value) => Math.max(0, value - reportLineLimit)) },
7333
7221
  { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastReportOffset, value + reportLineLimit)) },
7334
- { label: '返回技术详情', run: back },
7222
+ { role: 'navigation', label: '返回技术详情', run: back },
7335
7223
  ];
7336
7224
  const reportFeedbackVisible = Boolean(reportSaveResult || reportSaveError);
7337
7225
  const compactReportActions = policy.mode === 'compact' || reportFeedbackVisible;
7338
7226
  const reportActionLimit = policy.mode === 'compact' || reportFeedbackVisible ? 2 : 3;
7339
7227
  const reportActionOffset = Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - reportActionLimit));
7340
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: compactReportActions, offset: reportActionOffset, limit: reportActionLimit })] });
7228
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', columns: size.columns, offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: compactReportActions, offset: reportActionOffset, limit: reportActionLimit })] });
7341
7229
  }
7342
7230
  else {
7343
- actions = [{ label: '返回技术详情', run: back }];
7231
+ actions = [{ role: 'navigation', label: '返回技术详情', run: back }];
7344
7232
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "warning", title: "\u62A5\u544A\u5DF2\u4E0D\u53EF\u7528", description: "\u8FD4\u56DE\u6280\u672F\u8BE6\u60C5\u540E\u53EF\u67E5\u770B\u4ECD\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u7684\u8BB0\u5F55\u3002" }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7345
7233
  }
7346
7234
  }
@@ -7698,6 +7586,18 @@ export function ConsoleAppV4(props) {
7698
7586
  ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: false }, `${change.title}:${index}`);
7699
7587
  });
7700
7588
  const showEquivalentCommand = Boolean(review.equivalentCommand && showTechnical && !review.requiresConfirmation);
7589
+ const reviewRootPluginIds = taskReview || batchReview
7590
+ ? review.rootSummary?.map((root) => root.pluginId) ?? [] : [];
7591
+ const reviewRootSummary = reviewRootPluginIds.length === 1
7592
+ ? props.controller.getPluginOverview(reviewRootPluginIds[0])?.displayName
7593
+ ?? reviewRootPluginIds[0].split('/').at(-1)
7594
+ : reviewRootPluginIds.length > 1 ? `${reviewRootPluginIds.length} 个` : undefined;
7595
+ const reviewVersionSummary = review.rootSummary && review.rootSummary.length > 0
7596
+ ? [...new Set(review.rootSummary.map((root) => root.version ?? '未知'))].join('、')
7597
+ : undefined;
7598
+ const reviewRangeSummary = review.rootSummary && review.rootSummary.length > 0
7599
+ ? [...new Set(review.rootSummary.flatMap((root) => root.targets.map((target) => `${target.platform === 'both' ? 'Codex/Claude Code' : platformLabel(target.platform)}/${scopeLabel(target.scope)}`)))].join('、')
7600
+ : undefined;
7701
7601
  const installDependencyTargets = installReviewPluginId
7702
7602
  ? snapshot.installPlacementDraft?.targets ?? [] : [];
7703
7603
  const installDependencyPluginIds = new Set(installDependencyTargets.map((target) => target.pluginId));
@@ -7713,7 +7613,7 @@ export function ConsoleAppV4(props) {
7713
7613
  && (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
7714
7614
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7715
7615
  }
7716
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7616
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: reviewRootSummary ? `本次插件 · ${reviewRootSummary}` : review.subject.name, meta: reviewRootSummary ? `版本 ${reviewVersionSummary} · 范围 ${reviewRangeSummary || '未知'}` : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7717
7617
  }
7718
7618
  else if (route.name === 'progress') {
7719
7619
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -8117,11 +8017,20 @@ export function ConsoleAppV4(props) {
8117
8017
  : [];
8118
8018
  const safeCleanupKeys = new Set(props.controller.listPluginWriteBatchTargets('cleanup', [...new Set(failedUninstallTargets.map((target) => target.pluginId))]).map(batchTargetKey));
8119
8019
  const failedCleanupTargets = failedUninstallTargets.filter((target) => safeCleanupKeys.has(batchTargetKey(target)));
8020
+ const sourceRecoveryRoute = 'returnRoute' in result && result.returnRoute?.name === 'sourceDetail'
8021
+ ? result.returnRoute : undefined;
8022
+ const resumeConfigSourceDraft = operationKind === 'catalogGeneration'
8023
+ && sourceRecoveryRoute !== undefined
8024
+ && dirty
8025
+ && draftOwner?.startsWith(`${sourceRecoveryRoute.sourceId}/`)
8026
+ && props.controller.externalDrafts.some((draft) => draft.kind === 'configDraft' && draft.owner === draftOwner);
8120
8027
  actions = storeOperation
8121
8028
  ? [
8122
- { label: '返回存储概览', run: () => leaveResult({ name: 'catalogStoreOverview' }) },
8123
- { label: '查看本次记录', run: () => result.operationRecordId
8124
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8029
+ { role: 'navigation', label: '返回存储概览', run: () => leaveResult({ name: 'catalogStoreOverview' }) },
8030
+ { label: '查看本次记录', run: () => {
8031
+ if (result.operationRecordId)
8032
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8033
+ } },
8125
8034
  ]
8126
8035
  : taskOperation
8127
8036
  ? [
@@ -8146,7 +8055,8 @@ export function ConsoleAppV4(props) {
8146
8055
  status: 'warning',
8147
8056
  run: () => leaveResult(taskReturnRoute),
8148
8057
  }] : []),
8149
- ...(!nestedTaskReturn ? [{ label: '完成,返回我的插件', run: () => {
8058
+ ...(!nestedTaskReturn ? [{ role: result.state === 'succeeded' ? 'navigation' : undefined,
8059
+ label: '完成,返回我的插件', run: () => {
8150
8060
  if (taskBatchOrigin?.owner === 'batch-write'
8151
8061
  && taskBatchOrigin.flowId === batchDraft?.flowId) {
8152
8062
  setBatchDraft(undefined);
@@ -8154,8 +8064,10 @@ export function ConsoleAppV4(props) {
8154
8064
  }
8155
8065
  leaveResult({ name: 'pluginList', mode: 'installed' });
8156
8066
  } }] : []),
8157
- { label: '查看本次记录', run: () => result.operationRecordId
8158
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8067
+ { label: '查看本次记录', run: () => {
8068
+ if (result.operationRecordId)
8069
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8070
+ } },
8159
8071
  ]
8160
8072
  : batchOperation
8161
8073
  ? [
@@ -8168,9 +8080,12 @@ export function ConsoleAppV4(props) {
8168
8080
  setScrollOffset(0);
8169
8081
  },
8170
8082
  }] : []),
8171
- { label: '返回我的插件', run: () => leaveResult() },
8172
- { label: '查看本次记录', run: () => result.operationRecordId
8173
- && go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
8083
+ { role: result.state === 'succeeded' ? 'navigation' : undefined,
8084
+ label: '返回我的插件', run: () => leaveResult() },
8085
+ { label: '查看本次记录', run: () => {
8086
+ if (result.operationRecordId)
8087
+ go({ name: 'operationDiagnostic', recordId: result.operationRecordId });
8088
+ } },
8174
8089
  ]
8175
8090
  : portableOperation
8176
8091
  ? blocked || partial ? [
@@ -8220,7 +8135,7 @@ export function ConsoleAppV4(props) {
8220
8135
  { label: portableReturnLabel, run: () => { if (pluginId)
8221
8136
  props.controller.resetPortableFlowToPreview(pluginId); } },
8222
8137
  ] : [
8223
- { label: portableReturnLabel, run: () => {
8138
+ { role: 'navigation', label: portableReturnLabel, run: () => {
8224
8139
  props.controller.discardPortableDraft();
8225
8140
  leaveResult();
8226
8141
  } },
@@ -8244,15 +8159,20 @@ export function ConsoleAppV4(props) {
8244
8159
  { label: '重新检查问题', run: () => leaveResult({ name: 'issueList' }) },
8245
8160
  ]
8246
8161
  : [
8247
- { label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
8248
- { label: '返回首页', run: () => requestSessionNavigation('home') },
8162
+ { role: 'navigation', label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
8163
+ { role: 'navigation', label: '返回首页', run: () => requestSessionNavigation('home') },
8249
8164
  ]
8250
8165
  : sourceOperation
8251
8166
  ? [{
8252
8167
  label: !blocked && 'returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask'
8253
- ? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试' : '查看来源',
8168
+ ? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试'
8169
+ : resumeConfigSourceDraft ? '继续编辑未保存设置' : '查看来源',
8170
+ role: !blocked && !partial && !resumeConfigSourceDraft
8171
+ && !('returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask')
8172
+ ? 'navigation' : undefined,
8254
8173
  run: () => blocked ? props.controller.resumeResultDraft() : leaveResult(),
8255
- }, { label: '返回首页', run: () => requestSessionNavigation('home') }]
8174
+ }, { role: !blocked && !partial ? 'navigation' : undefined,
8175
+ label: '返回首页', run: () => requestSessionNavigation('home') }]
8256
8176
  : configOperation && partial
8257
8177
  ? [
8258
8178
  ...configRetryScopes.map((scope) => ({
@@ -8277,7 +8197,8 @@ export function ConsoleAppV4(props) {
8277
8197
  { label: '返回设置', run: () => props.controller.resumeResultDraft() },
8278
8198
  { label: '返回首页', run: () => requestSessionNavigation('home') },
8279
8199
  ]
8280
- : [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
8200
+ : [{ role: !partial && !blocked && !blockedDependencyFailure ? 'navigation' : undefined,
8201
+ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
8281
8202
  ? configOrigin.name === 'pluginPreview' ? '返回插件预览'
8282
8203
  : configOrigin.name === 'pluginWriteTask' ? '返回恢复任务' : '返回插件详情'
8283
8204
  : configOperation ? '查看插件'
@@ -8317,7 +8238,8 @@ export function ConsoleAppV4(props) {
8317
8238
  scope: failedTarget.scope,
8318
8239
  });
8319
8240
  },
8320
- }] : []), { label: '返回首页', run: () => requestSessionNavigation('home') }];
8241
+ }] : []), { role: !partial && !blocked ? 'navigation' : undefined,
8242
+ label: '返回首页', run: () => requestSessionNavigation('home') }];
8321
8243
  if (failedCleanupTargets.length > 0) {
8322
8244
  actions.unshift({
8323
8245
  label: '查看清理建议',
@@ -8587,46 +8509,51 @@ export function ConsoleAppV4(props) {
8587
8509
  return _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`);
8588
8510
  });
8589
8511
  const taskLocationRows = taskRootResults.flatMap((root) => {
8590
- const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
8591
- return requestedTargets.map((target) => {
8592
- const key = [target.pluginId, target.platform, target.scope].join('\0');
8512
+ return root.targets.map((target) => {
8513
+ const targetPluginId = target.pluginId ?? root.pluginId;
8514
+ const key = [targetPluginId, target.platform, target.scope].join('\0');
8593
8515
  const blockedTarget = blockedTaskTargets.get(key);
8594
- const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
8595
- && entry.scope === target.scope);
8596
8516
  const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
8597
- : targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
8598
- : targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
8599
- : targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
8517
+ : target.state === 'completed' ? { label: '完成', status: 'success' }
8518
+ : target.state === 'kept' ? { label: '无需更改', status: 'success' }
8519
+ : target.state === 'failed' ? { label: '失败', status: 'error' }
8600
8520
  : { label: '未开始', status: 'muted' };
8601
- return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
8521
+ const relation = target.relation === 'direct' ? '直接依赖'
8522
+ : target.relation === 'transitive' ? '间接依赖' : '所选插件位置';
8523
+ return _jsx(ResultRow, { title: `${root.pluginId}${targetPluginId === root.pluginId ? '' : ` → ${targetPluginId}`} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
8602
8524
  ? pluginWriteTaskBlockLabel(blockedTarget.reason)
8603
- : targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
8525
+ : `${relation}${target.errorCode ? ` · ${target.errorCode}` : ''}`, meta: `${targetState.label}${target.errorCode ? ` · ${target.errorCode}` : ''}`, status: targetState.status, compact: !blockedTarget }, `target:${root.pluginId}:${key}`);
8604
8526
  });
8605
8527
  });
8606
- const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
8528
+ const batchPluginResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
8607
8529
  const rootState = taskRootState(root.state);
8608
8530
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
8609
8531
  ?? root.pluginId.split('/').at(-1);
8610
8532
  return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
8611
- }).concat(prioritizedTargets.map((target) => {
8533
+ });
8534
+ const batchLocationResultRows = prioritizedTargets.map((target) => {
8612
8535
  const state = operationTargetStateLabel(target.state);
8613
8536
  const relation = target.relation === 'requested' ? '你选择的插件'
8614
8537
  : target.relation === 'direct' ? '直接依赖'
8615
8538
  : target.relation === 'transitive' ? '间接依赖' : undefined;
8616
8539
  return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
8617
- }));
8540
+ });
8618
8541
  const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
8619
- const rootResultRows = taskOperation
8620
- ? [...taskSelectedPluginRows, ...taskLocationRows]
8621
- : batchResultRows;
8622
- const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
8623
- const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
8542
+ const pluginResultRows = taskOperation ? taskSelectedPluginRows : batchPluginResultRows;
8543
+ const locationResultRows = taskOperation ? taskLocationRows : batchLocationResultRows;
8544
+ const combinedResultRows = [...pluginResultRows, ...locationResultRows];
8545
+ const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, combinedResultRows.length - 1));
8546
+ const visiblePluginResultRows = taskResultOffset < pluginResultRows.length
8547
+ ? pluginResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit) : [];
8548
+ const visibleLocationStart = Math.max(0, taskResultOffset - pluginResultRows.length);
8549
+ const remainingResultRows = taskResultLimit - visiblePluginResultRows.length;
8550
+ const visibleLocationResultRows = locationResultRows.slice(visibleLocationStart, visibleLocationStart + remainingResultRows);
8624
8551
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
8625
8552
  || prioritizedPortablePairs.length > portableSkillLimit) {
8626
8553
  help = 'p/n 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8627
8554
  }
8628
- content = taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: "\u6240\u9009\u63D2\u4EF6\u7ED3\u679C", children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
8629
- ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableRecheckBusy
8555
+ 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
+ ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${combinedResultRows.length - taskResultOffset - visiblePluginResultRows.length - visibleLocationResultRows.length} 项`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableRecheckBusy
8630
8557
  ? _jsx(ProgressRow, { title: "\u6B63\u5728\u91CD\u65B0\u68C0\u67E5\u7ED3\u679C", description: "\u53EA\u8BFB\u53D6 Skills CLI \u5F53\u524D\u72B6\u6001\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5", state: "active", compact: policy.mode === 'compact' })
8631
8558
  : null, portableResultDetailOpen ? _jsxs(_Fragment, { children: [safePortableResultDetailOffset > 0
8632
8559
  ? _jsx(StaticRow, { title: `↑ 前面还有 ${safePortableResultDetailOffset} 行`, status: "muted", compact: true }) : null, visiblePortableResultDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${safePortableResultDetailOffset + index}:${line}`)), safePortableResultDetailOffset + visiblePortableResultDetailLines.length
@@ -8653,7 +8580,6 @@ export function ConsoleAppV4(props) {
8653
8580
  ? '这里显示已验证目录包声明的组成,不读取或展示完整操作指令。'
8654
8581
  : '当前没有可安全读取的目录内容。';
8655
8582
  const contentNavigationActions = [
8656
- { label: '返回插件详情', run: back },
8657
8583
  { label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
8658
8584
  ];
8659
8585
  const contentTypeLabels = {
@@ -8665,41 +8591,27 @@ export function ConsoleAppV4(props) {
8665
8591
  const versionDiffers = contentAvailable
8666
8592
  && installedVersions.some((version) => version !== overview.pluginVersion);
8667
8593
  const items = overview?.items ?? [];
8668
- const pageRows = policy.mode === 'compact' ? 1 : responsiveBudget(1, 2);
8669
- const contentOffset = Math.min(batchTargetOffset, Math.max(0, items.length - 1));
8670
- const visibleItems = items.slice(contentOffset, contentOffset + pageRows);
8671
- if (items.length > pageRows) {
8672
- referenceDetailPagination = { total: items.length, pageRows };
8673
- actions = [
8674
- ...paginationActions({
8675
- offset: contentOffset,
8676
- total: items.length,
8677
- pageRows,
8678
- move: (direction) => moveBatchTargetPage(items.length, pageRows, direction),
8679
- }),
8680
- ...contentNavigationActions,
8681
- ];
8682
- context = `${Math.min(contentOffset + 1, items.length)}–${Math.min(contentOffset + visibleItems.length, items.length)} / ${items.length}`;
8683
- help = '↑↓ 操作 · Enter 选择 · p/n 翻页 · Esc 返回';
8684
- }
8685
- else {
8686
- actions = contentNavigationActions;
8687
- }
8688
- const contentViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(Math.min(items.length, pageRows) * 5 + 2));
8689
- content = _jsxs(Box, { flexDirection: "column", children: [contentAvailable ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u8BC1\u636E", meta: `插件 ${overview.pluginVersion} · 目录 ${overview.catalogVersion}`, compact: true }), _jsx(StaticRow, { title: installedVersions.length > 0
8690
- ? `已安装 ${installedVersions.join('、')}`
8691
- : '当前没有已确认的安装版本', description: versionDiffers
8692
- ? '以下是目录版本内容,不代表旧安装版本的内容。'
8693
- : '内容来自当前已验证的目录包。', status: versionDiffers ? 'warning' : 'info', compact: true })] }) : contentState === 'loading'
8594
+ const itemActions = items.map((item) => ({
8595
+ actionId: `content:${item.id}`,
8596
+ label: item.name,
8597
+ description: `${contentTypeLabels[item.type]} · ${item.tools.join('、') || '适用工具待确认'} · ${item.description ?? '目录没有提供简短描述'}`,
8598
+ status: item.description ? undefined : 'warning',
8599
+ }));
8600
+ actions = [...itemActions, ...contentNavigationActions];
8601
+ const fixedContentRows = contentAvailable ? 4 : 3;
8602
+ currentActionViewportLineBudget = pageActionBudget(fixedContentRows + (overview?.notices.length ?? 0));
8603
+ const contentViewport = actionViewport(actions, false, focus, scrollOffset, currentActionViewportLineBudget);
8604
+ if (items.length > contentViewport.limit) {
8605
+ context = `${items.length} 项内容 · 上下键滚动`;
8606
+ help = '↑↓ 阅读全部内容 · Enter 选择 · Esc 返回';
8607
+ }
8608
+ content = _jsxs(Box, { flexDirection: "column", children: [contentAvailable ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u76EE\u5F55\u7248\u672C", meta: `${overview.pluginVersion} · Catalog ${overview.catalogVersion}`, compact: true }), _jsx(StaticRow, { title: installedVersions.length > 0
8609
+ ? `安装版本 ${installedVersions.join('、')}`
8610
+ : '安装版本尚未确认', description: versionDiffers
8611
+ ? '以下清单属于目录版本,不代表旧安装版本的内容。'
8612
+ : '以下清单来自当前已验证的目录包。', status: versionDiffers ? 'warning' : 'info', compact: true })] }) : contentState === 'loading'
8694
8613
  ? _jsx(Callout, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u5185\u5BB9", description: "\u5B8C\u6210\u540E\u5C06\u5728\u5F53\u524D\u9875\u9762\u663E\u793A\u3002" })
8695
- : _jsx(Callout, { status: "warning", title: "\u5185\u5BB9\u6682\u4E0D\u53EF\u7528", description: "\u53EF\u4EE5\u67E5\u770B\u6280\u672F\u8BE6\u60C5\u6216\u6062\u590D\u63D2\u4EF6\u6765\u6E90\u540E\u91CD\u8BD5\u3002" }), visibleItems.map((item) => _jsxs(SectionCard, { title: item.name, compact: true, children: [_jsx(StaticRow, { title: `${contentTypeLabels[item.type]} · ${item.tools.join('、') || '适用工具待确认'}`, status: "muted", compact: true }), _jsx(StaticRow, { title: item.description ?? '目录没有提供简短描述', status: item.description ? 'muted' : 'warning', compact: true })] }, item.id)), overview?.notices.map((notice, index) => _jsx(StaticRow, { title: notice, status: "warning", compact: true }, `${index}:${notice}`)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: contentViewport.offset, limit: contentViewport.limit })] });
8696
- }
8697
- else if (route.name === 'pluginAbout' && plugin) {
8698
- breadcrumb = `${plugin.displayName} / 关于`;
8699
- title = plugin.displayName;
8700
- description = plugin.description;
8701
- actions = [{ label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) }, { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) }];
8702
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: plugin.version ?? '未知' }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceLabel }), _jsx(StaticRow, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", meta: plugin.supportedPlatforms.map(platformLabel).join('、') }), _jsx(StaticRow, { title: "\u914D\u7F6E", meta: `${plugin.configFieldCount} 个字段` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
8614
+ : _jsx(Callout, { status: "warning", title: "\u5185\u5BB9\u6682\u4E0D\u53EF\u7528", description: "\u53EF\u4EE5\u67E5\u770B\u6280\u672F\u8BE6\u60C5\u6216\u6062\u590D\u63D2\u4EF6\u6765\u6E90\u540E\u91CD\u8BD5\u3002" }), overview?.notices.map((notice, index) => _jsx(StaticRow, { title: notice, status: "warning", compact: true }, `${index}:${notice}`)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: false, offset: contentViewport.offset, limit: contentViewport.limit })] });
8703
8615
  }
8704
8616
  else if (route.name === 'pluginTechnical' && plugin) {
8705
8617
  breadcrumb = `${plugin.displayName} / 技术详情`;
@@ -8708,32 +8620,40 @@ export function ConsoleAppV4(props) {
8708
8620
  description = plugin.capabilityUnsupported
8709
8621
  ? '当前 AW CLI 不支持此插件所需 capability。'
8710
8622
  : '默认页面不会展示这些内部信息。';
8711
- actions = [{ label: '返回插件', run: back }];
8623
+ actions = [{ role: 'navigation', label: '返回插件', run: back }];
8712
8624
  const prioritizedComponents = [...plugin.technicalComponents].sort((left, right) => {
8713
8625
  const priority = (state) => state === 'error' ? 0
8714
8626
  : state === 'partial' ? 1 : state === 'absent' ? 2 : 3;
8715
8627
  return priority(left.state) - priority(right.state);
8716
8628
  });
8717
- const detailRowBudget = responsiveBudget(9, 15);
8718
- const capabilityRowCount = plugin.capabilityUnsupported ? 4 : 0;
8719
- const componentRowBudget = Math.max(1, detailRowBudget - 4 - plugin.installedPlacements.length - capabilityRowCount);
8720
- const componentLimit = prioritizedComponents.length > componentRowBudget
8721
- ? 1
8722
- : componentRowBudget;
8723
- const componentOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedComponents.length - 1));
8724
- const visibleComponents = prioritizedComponents.slice(componentOffset, componentOffset + componentLimit);
8725
- const hiddenComponents = Math.max(0, prioritizedComponents.length - componentOffset - visibleComponents.length);
8726
- if (prioritizedComponents.length > componentLimit) {
8629
+ const technicalFields = [
8630
+ `canonical pluginId · ${plugin.pluginId}`,
8631
+ `来源 · ${plugin.sourceId}`,
8632
+ ...prioritizedComponents.map((component) => `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${component.shared ? '平台共享' : scopeLabel(component.scope)}`),
8633
+ ...(plugin.capabilityUnsupported ? [
8634
+ 'failure code · awCapabilityUnsupported',
8635
+ 'capability 状态 · 当前 AW CLI 不支持',
8636
+ `缺少 capability · ${plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability'}`,
8637
+ `恢复方向 · ${plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。'}`,
8638
+ ] : []),
8639
+ ...plugin.installedPlacements.map((placement) => `${platformLabel(placement.platform)} target · ${placementStatus(placement).value}`),
8640
+ ];
8641
+ const technicalLines = technicalFields.flatMap((field) => wrapDisplayText(field, size.columns - 6));
8642
+ const technicalPageRows = Math.max(1, pageActionBudget(7));
8643
+ const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
8644
+ const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
8645
+ if (technicalLines.length > technicalPageRows) {
8727
8646
  actions.push(...paginationActions({
8728
- offset: componentOffset,
8729
- total: prioritizedComponents.length,
8730
- pageRows: componentLimit,
8731
- move: (direction) => moveBatchTargetPage(prioritizedComponents.length, componentLimit, direction),
8647
+ offset: technicalOffset,
8648
+ total: technicalLines.length,
8649
+ pageRows: technicalPageRows,
8650
+ move: (direction) => moveBatchTargetPage(technicalLines.length, technicalPageRows, direction),
8732
8651
  }));
8733
- referenceDetailPagination = { total: prioritizedComponents.length, pageRows: componentLimit };
8734
- help = 'p/n 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8652
+ referenceDetailPagination = { total: technicalLines.length, pageRows: technicalPageRows };
8653
+ help = 'p/n 或 PgUp/PgDn 阅读技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8735
8654
  }
8736
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.capabilityUnsupported ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "failure code", meta: "awCapabilityUnsupported" }), _jsx(StaticRow, { title: "capability \u72B6\u6001", meta: "\u5F53\u524D AW CLI \u4E0D\u652F\u6301", status: "error" }), _jsx(StaticRow, { title: "\u7F3A\u5C11 capability", meta: plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability' }), _jsx(StaticRow, { title: "\u6062\u590D\u65B9\u5411", meta: plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。' })] }) : null, plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), componentOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${componentOffset} 个组件`, status: "muted", compact: true }) : null, visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${component.shared ? '平台共享' : scopeLabel(component.scope)}` }, `${component.platform}:${component.shared ? 'shared' : component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
8655
+ 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
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
8737
8657
  }
8738
8658
  else {
8739
8659
  const missingPlugin = definition.entity === 'plugin' && plugin === undefined;
@@ -8759,13 +8679,12 @@ export function ConsoleAppV4(props) {
8759
8679
  case 'pluginContent':
8760
8680
  case 'pluginMore':
8761
8681
  case 'platform':
8762
- case 'pluginAbout':
8763
8682
  case 'pluginTechnical':
8764
8683
  case 'configLayer':
8765
8684
  unavailable = missingPlugin ? 'plugin' : 'flow';
8766
8685
  break;
8686
+ case 'portablePreparing':
8767
8687
  case 'portableSkillSelect':
8768
- case 'portableSkillSummary':
8769
8688
  case 'portableDependencies':
8770
8689
  case 'portableAgentMode':
8771
8690
  case 'portableAgentInput':
@@ -8833,6 +8752,41 @@ export function ConsoleAppV4(props) {
8833
8752
  : 'h/H 完成后导航'} · Esc 撤销等待 · ${help}`
8834
8753
  : `h 首页 · H 重载 · ${help}`;
8835
8754
  }
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);
8757
+ help = [...helpParts.filter(isRoutineKey), ...helpParts.filter((part) => !isRoutineKey(part))].join(' · ');
8758
+ if (searchFocus)
8759
+ help = '←→ 光标 · ↑↓ 移动结果 · Enter/Esc 完成输入';
8760
+ else if (bottomActions.length > 0 && actions.length > 8)
8761
+ help += ' · a 下方操作';
8762
+ useEffect(() => {
8763
+ if ((bottomActions.length > 0 || route.name === 'issueList' || route.name === 'operationHistory')
8764
+ && focus >= actions.length && actions.length > 0)
8765
+ setFocus(actions.length - 1);
8766
+ else if (actions[focus]?.role === 'navigation')
8767
+ setFocus(moveFocus(actions, focus, 1));
8768
+ }, [routeKey, focus, actions.length, actions.map((action) => action.role ?? '').join(',')]);
8769
+ const listCandidates = route.name === 'pluginList'
8770
+ || route.name === 'batchSelection' && batchDraft?.stage === 'select'
8771
+ ? actions.filter((action) => action.pluginId !== undefined) : [];
8772
+ const listCandidateSignature = listCandidates.map((action) => action.pluginId).join('\0');
8773
+ useEffect(() => {
8774
+ if (route.name !== 'pluginList' && !(route.name === 'batchSelection' && batchDraft?.stage === 'select')) {
8775
+ listFocusAnchor.current = undefined;
8776
+ return;
8777
+ }
8778
+ const previous = listFocusAnchor.current;
8779
+ let nextFocus = focus;
8780
+ if (previous?.frameId === snapshot.router.currentFrameId && previous.ids !== listCandidateSignature
8781
+ && previous.entityId) {
8782
+ const retained = listCandidates.findIndex((action) => action.pluginId === previous.entityId);
8783
+ nextFocus = retained >= 0 ? retained : Math.min(focus, Math.max(0, listCandidates.length - 1));
8784
+ if (nextFocus !== focus)
8785
+ setFocus(nextFocus);
8786
+ }
8787
+ listFocusAnchor.current = { frameId: snapshot.router.currentFrameId,
8788
+ ids: listCandidateSignature, entityId: listCandidates[nextFocus]?.pluginId };
8789
+ }, [snapshot.router.currentFrameId, route.name, batchDraft?.stage, listCandidateSignature, focus]);
8836
8790
  const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
8837
8791
  ? actions.map((action) => String(Boolean(action.disabled))).join(',')
8838
8792
  : '';
@@ -8895,7 +8849,7 @@ export function ConsoleAppV4(props) {
8895
8849
  };
8896
8850
  }, [dynamicActionSignature, routeKey]);
8897
8851
  const runAction = (action) => {
8898
- if (!action || action.disabled)
8852
+ if (!action || action.disabled || action.role === 'navigation')
8899
8853
  return;
8900
8854
  try {
8901
8855
  const pending = action.run?.();
@@ -9194,35 +9148,6 @@ export function ConsoleAppV4(props) {
9194
9148
  }
9195
9149
  return;
9196
9150
  }
9197
- if (pluginListChooser) {
9198
- const sourceIds = ['all', ...props.controller.listSources().map((source) => source.id)];
9199
- const itemCount = pluginListChooser.kind === 'source' ? sourceIds.length : PLUGIN_FILTERS.length;
9200
- if (key.escape) {
9201
- setPluginListChooser(undefined);
9202
- }
9203
- else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9204
- const direction = key.upArrow || key.leftArrow ? -1 : 1;
9205
- setPluginListChooser((current) => current ? {
9206
- ...current,
9207
- focus: moveIndex(itemCount, current.focus, direction),
9208
- } : current);
9209
- }
9210
- else if (key.return) {
9211
- const selectedIndex = Math.min(pluginListChooser.focus, Math.max(0, itemCount - 1));
9212
- if (pluginListChooser.kind === 'source') {
9213
- const selectedSourceId = sourceIds[selectedIndex];
9214
- if (selectedSourceId)
9215
- choosePluginListSource?.(selectedSourceId);
9216
- }
9217
- else {
9218
- const selectedFilter = PLUGIN_FILTERS[selectedIndex];
9219
- if (selectedFilter)
9220
- choosePluginListFilter?.(selectedFilter);
9221
- }
9222
- setPluginListChooser(undefined);
9223
- }
9224
- return;
9225
- }
9226
9151
  if (overlay) {
9227
9152
  if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
9228
9153
  if (overlay === 'sessionNavigation')
@@ -9349,17 +9274,27 @@ export function ConsoleAppV4(props) {
9349
9274
  setPortableResultDetailOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + direction * pageRows)));
9350
9275
  return;
9351
9276
  }
9277
+ if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9278
+ && route.name === 'portableDependencies' && plugin) {
9279
+ const total = dependencyOverview(plugin)?.dependencies.length ?? 0;
9280
+ 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))));
9282
+ return;
9283
+ }
9352
9284
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9353
9285
  && (route.name === 'pluginContent'
9354
9286
  || route.name === 'pluginTechnical'
9355
9287
  || route.name === 'projectStatus' && showTechnical
9356
9288
  || route.name === 'issueDetail' && showTechnical
9289
+ || route.name === 'portableBlocker' && showTechnical
9290
+ || route.name === 'configUnavailable' && showTechnical
9357
9291
  || route.name === 'sourceRemoveBlocked' && sourceDependenciesExpanded
9358
9292
  || route.name === 'pluginDependencies' && route.view === 'removeBlocked' && !showTechnical
9359
9293
  || route.name === 'pluginDependencyNode'
9360
9294
  && (route.view === 'references' || route.view === 'details')
9361
9295
  || route.name === 'pluginDetail'
9362
9296
  || route.name === 'configSave' && route.view === 'changes'
9297
+ || route.name === 'configFields' && route.view === 'values'
9363
9298
  || route.name === 'sourceSave' && route.view === 'changes'
9364
9299
  || route.name === 'review'
9365
9300
  || route.name === 'result')
@@ -9376,18 +9311,6 @@ export function ConsoleAppV4(props) {
9376
9311
  setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -1 : 1))));
9377
9312
  return;
9378
9313
  }
9379
- if ((key.pageUp || key.pageDown) && route.name === 'pluginTechnical' && plugin) {
9380
- const total = plugin.technicalComponents.length;
9381
- const pageRows = 1;
9382
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
9383
- return;
9384
- }
9385
- if ((key.pageUp || key.pageDown) && route.name === 'issueDetail' && issue && showTechnical) {
9386
- const total = diagnosticDetailRowCount(issue.diagnostic);
9387
- const pageRows = responsiveBudget(5, 10);
9388
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
9389
- return;
9390
- }
9391
9314
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
9392
9315
  && route.name === 'sourceTechnical') {
9393
9316
  const total = diagnosticDetailLines(resolveSourceTechnicalDiagnostic(route.sourceId), size.columns - 6).length;
@@ -9420,12 +9343,9 @@ export function ConsoleAppV4(props) {
9420
9343
  ? props.controller.getOperationDiagnostic(route.recordId) : undefined;
9421
9344
  const preview = route.name === 'operationReportPreview'
9422
9345
  ? props.controller.previewOperationReport(route.recordId) : undefined;
9423
- const total = detail ? operationDiagnosticLineCount(detail)
9424
- : preview ? operationReportLineCount(preview) : 0;
9425
- const visibleRows = 2;
9426
- const pageRows = key.pageUp || key.pageDown
9427
- ? policy.mode === 'compact' ? 2 : Math.max(1, pageActionBudget(5))
9428
- : visibleRows;
9346
+ const total = detail ? operationDiagnosticLineCount(detail, size.columns)
9347
+ : preview ? operationReportLineCount(preview, size.columns) : 0;
9348
+ const pageRows = operationDetailPageRows;
9429
9349
  setScrollOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p' ? -pageRows : pageRows))));
9430
9350
  return;
9431
9351
  }
@@ -9575,9 +9495,18 @@ export function ConsoleAppV4(props) {
9575
9495
  setSourceInputError(undefined);
9576
9496
  return;
9577
9497
  }
9498
+ if (route.name === 'configFieldEditor' && activeField && key.ctrl && input.toLowerCase() === 'r') {
9499
+ const canUnset = ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
9500
+ .some((field) => field.key === activeField.key && configFieldCanUnset(field)));
9501
+ if (canUnset)
9502
+ removeDraft(activeField.key);
9503
+ return;
9504
+ }
9578
9505
  if (route.name === 'configFieldEditor'
9579
9506
  && (route.editor === 'string' || route.editor === 'number')
9580
- && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')) {
9507
+ && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')
9508
+ && ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
9509
+ .some((field) => field.key === activeField?.key && configFieldCanSet(field)))) {
9581
9510
  if (key.escape) {
9582
9511
  setConfigCandidate(undefined);
9583
9512
  back();
@@ -9595,7 +9524,11 @@ export function ConsoleAppV4(props) {
9595
9524
  }
9596
9525
  let candidate = inputValue;
9597
9526
  if (route.editor === 'number') {
9598
- candidate = Number(inputValue);
9527
+ if (parseConfigNumberInput(inputValue) === undefined) {
9528
+ setInputError('请输入数字;空输入不会保存为 0。');
9529
+ return;
9530
+ }
9531
+ candidate = parseConfigNumberInput(inputValue);
9599
9532
  }
9600
9533
  const issues = validatePluginConfigValue(activeField.definition, candidate, activeField.key);
9601
9534
  if (issues.length > 0) {
@@ -9668,9 +9601,12 @@ export function ConsoleAppV4(props) {
9668
9601
  back();
9669
9602
  return;
9670
9603
  }
9604
+ if (activePropertyDefinition.type === 'number' && parseConfigNumberInput(inputValue) === undefined) {
9605
+ setInputError('请输入数字;空输入不会保存为 0。');
9606
+ return;
9607
+ }
9671
9608
  const candidate = activePropertyDefinition.type === 'number'
9672
- ? Number(inputValue)
9673
- : inputValue;
9609
+ ? parseConfigNumberInput(inputValue) : inputValue;
9674
9610
  if (updateObjectProperty(route.propertyKey, candidate))
9675
9611
  back();
9676
9612
  return;
@@ -9683,197 +9619,82 @@ export function ConsoleAppV4(props) {
9683
9619
  return;
9684
9620
  }
9685
9621
  if (searchFocus && (route.name === 'pluginList' || route.name === 'batchSelection')) {
9686
- if (key.escape) {
9622
+ const currentQuery = route.name === 'pluginList' ? query : batchDraft?.query ?? '';
9623
+ const parts = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(currentQuery)]
9624
+ .map((part) => part.segment);
9625
+ const cursor = Math.min(searchCursor, parts.length);
9626
+ if (key.escape || key.return) {
9687
9627
  setSearchFocus(false);
9688
9628
  return;
9689
9629
  }
9690
- if (key.return) {
9691
- setSearchFocus(false);
9692
- if (route.name === 'pluginList') {
9693
- const row = pluginRows[focus];
9694
- if (row?.menuAction)
9695
- row.menuAction();
9696
- else if (row)
9697
- go({ name: 'pluginDetail', pluginId: row.pluginId });
9698
- }
9699
- return;
9700
- }
9701
9630
  if (key.upArrow || key.downArrow) {
9702
9631
  setSearchFocus(false);
9703
- setFocus((value) => moveIndex(pluginRows.length, value, key.upArrow ? -1 : 1));
9632
+ const candidateCount = route.name === 'batchSelection' && batchDraft
9633
+ ? batchCandidates(batchDraft).length : pluginRows.length;
9634
+ setFocus((value) => moveIndex(candidateCount, Math.min(value, Math.max(0, candidateCount - 1)), key.upArrow ? -1 : 1));
9704
9635
  return;
9705
9636
  }
9706
9637
  if (key.leftArrow || key.rightArrow) {
9707
- setSearchFocus(false);
9708
- const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
9709
- const current = route.name === 'pluginList' ? route.sourceId ?? 'all' : batchDraft?.sourceId ?? 'all';
9710
- const direction = key.leftArrow ? -1 : 1;
9711
- const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
9712
- if (route.name === 'pluginList') {
9713
- props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
9714
- }
9715
- else {
9716
- setBatchDraft((draft) => draft ? {
9717
- ...draft,
9718
- ...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
9719
- showSelected: false,
9720
- } : draft);
9721
- }
9722
- setFocus(0);
9723
- setScrollOffset(0);
9638
+ setSearchCursor(Math.min(parts.length, Math.max(0, cursor + (key.leftArrow ? -1 : 1))));
9724
9639
  return;
9725
9640
  }
9641
+ let nextCursor = cursor;
9642
+ if (input === '\u0015' || input.toLowerCase() === 'u' && key.ctrl) {
9643
+ parts.splice(0);
9644
+ nextCursor = 0;
9645
+ }
9646
+ else if (key.backspace || key.delete) {
9647
+ if (cursor > 0)
9648
+ parts.splice(--nextCursor, 1);
9649
+ }
9650
+ else if (input && !key.ctrl && !key.meta && input >= ' ') {
9651
+ const inserted = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(input)]
9652
+ .map((part) => part.segment);
9653
+ parts.splice(cursor, 0, ...inserted);
9654
+ nextCursor += inserted.length;
9655
+ }
9656
+ const nextQuery = parts.join('');
9657
+ setSearchCursor(nextCursor);
9726
9658
  if (route.name === 'pluginList') {
9727
- const nextQuery = input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
9728
- ? ''
9729
- : key.backspace || key.delete
9730
- ? removeLastGrapheme(query)
9731
- : input && !key.ctrl && !key.meta && input >= ' '
9732
- ? query + input : query;
9733
9659
  setQuery(nextQuery);
9734
- const matchingItems = props.controller.listPlugins({
9735
- mode: route.mode,
9736
- ...(route.sourceId ? { sourceId: route.sourceId } : {}),
9737
- ...(nextQuery ? { query: nextQuery } : {}),
9738
- installFilter,
9739
- });
9740
- const matchingRows = pluginListRowIds(route.mode, matchingItems, nextQuery);
9741
- setFocus(matchingItems[0] ? matchingRows.indexOf(matchingItems[0].pluginId) : 0);
9660
+ props.controller.updatePageContext({ query: nextQuery });
9742
9661
  }
9743
9662
  else {
9744
- setBatchDraft((draft) => draft ? {
9745
- ...draft,
9746
- query: input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
9747
- ? ''
9748
- : key.backspace || key.delete
9749
- ? removeLastGrapheme(draft.query)
9750
- : input && !key.ctrl && !key.meta && input >= ' '
9751
- ? draft.query + input : draft.query,
9752
- showSelected: false,
9753
- } : draft);
9663
+ setBatchDraft((draft) => draft ? { ...draft, query: nextQuery, showSelected: false } : draft);
9754
9664
  }
9755
- if (route.name === 'batchSelection')
9756
- setFocus(0);
9665
+ setFocus(0);
9757
9666
  setScrollOffset(0);
9758
9667
  return;
9759
9668
  }
9760
- if (route.name === 'configFields' && configGroupChooserOpen) {
9761
- if (key.escape || input.toLowerCase() === 'b') {
9762
- setConfigGroupChooserOpen(false);
9763
- setFocusZone('actions');
9764
- setPinnedActionFocus(1);
9765
- }
9766
- else if (key.upArrow || key.downArrow) {
9767
- setConfigGroupChoiceFocus((value) => moveIndex(visibleConfigTabs.length, value, key.upArrow ? -1 : 1));
9768
- }
9769
- else if (key.return) {
9770
- const selected = visibleConfigTabs[configGroupChoiceFocus];
9771
- if (selected) {
9772
- props.controller.updatePageContext({
9773
- configGroupId: selected.id,
9774
- focusIndex: 0,
9775
- scrollOffset: 0,
9776
- });
9777
- setConfigGroupChooserOpen(false);
9778
- setFocusZone('content');
9779
- setFocus(0);
9780
- setScrollOffset(0);
9781
- }
9782
- }
9783
- return;
9784
- }
9785
- if ((route.name === 'pluginList' || route.name === 'sourceList' || route.name === 'projectStatus'
9786
- || route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9787
- || route.name === 'batchPlacement' && pinnedActions.length > 0
9788
- || route.name === 'portableSkillSelect'
9789
- || route.name === 'batchSelection' && batchDraft?.stage === 'select'
9790
- || route.name === 'installTargets' && route.view !== 'blocked') && key.tab) {
9791
- setFocusZone((current) => current === 'content' ? 'actions' : 'content');
9792
- return;
9793
- }
9794
- if (route.name === 'configFields' && key.tab) {
9795
- setFocusZone((current) => current === 'content' ? 'actions' : 'content');
9669
+ // Paste and composed text are data, never multiple background commands.
9670
+ if (input.length > 1 && !key.ctrl && !key.meta)
9796
9671
  return;
9797
- }
9798
- if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
9799
- openBatchSelection();
9672
+ if (key.tab)
9800
9673
  return;
9801
- }
9802
9674
  if (route.name === 'pluginList' && input === '/') {
9803
- setSearchFocus(true);
9675
+ runAction(listQueryActions.find((action) => action.actionId === 'plugin-list-search'));
9804
9676
  return;
9805
9677
  }
9806
- if (route.name === 'pluginList' && focusZone === 'actions'
9807
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9808
- const direction = key.upArrow || key.leftArrow ? -1 : 1;
9809
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, direction));
9810
- return;
9811
- }
9812
- if (route.name === 'pluginList' && focusZone === 'actions' && key.return) {
9813
- const actionIndex = Math.min(pinnedActionFocus, Math.max(0, pinnedActions.length - 1));
9814
- runAction(pinnedActions[actionIndex]);
9815
- return;
9816
- }
9817
- if (route.name === 'sourceList' && focusZone === 'actions'
9818
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9819
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9820
- return;
9821
- }
9822
- if (route.name === 'sourceList' && focusZone === 'actions' && key.return) {
9823
- runAction(pinnedActions[pinnedActionFocus]);
9824
- return;
9825
- }
9826
- if (route.name === 'projectStatus' && focusZone === 'actions'
9827
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9828
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9829
- return;
9830
- }
9831
- if (route.name === 'projectStatus' && focusZone === 'actions' && key.return) {
9832
- runAction(pinnedActions[pinnedActionFocus]);
9833
- return;
9834
- }
9835
- if (route.name === 'portableSkillSelect' && focusZone === 'actions'
9836
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9837
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9838
- return;
9839
- }
9840
- if (route.name === 'portableSkillSelect' && focusZone === 'actions' && key.return) {
9841
- runAction(pinnedActions[pinnedActionFocus]);
9842
- return;
9843
- }
9844
- if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9845
- || route.name === 'installTargets' && route.view !== 'blocked')
9846
- && focusZone === 'actions'
9847
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9848
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9849
- return;
9850
- }
9851
- if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9852
- || route.name === 'installTargets' && route.view !== 'blocked')
9853
- && focusZone === 'actions' && key.return) {
9854
- runAction(pinnedActions[pinnedActionFocus]);
9855
- return;
9856
- }
9857
- if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9858
- || route.name === 'batchPlacement' && pinnedActions.length > 0)
9859
- && focusZone === 'actions'
9860
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9861
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9862
- return;
9863
- }
9864
- if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9865
- || route.name === 'batchPlacement' && pinnedActions.length > 0)
9866
- && focusZone === 'actions' && key.return) {
9867
- runAction(pinnedActions[pinnedActionFocus]);
9868
- return;
9869
- }
9870
- if (route.name === 'configFields' && focusZone === 'actions'
9871
- && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9872
- setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9873
- return;
9874
- }
9875
- if (route.name === 'configFields' && focusZone === 'actions' && key.return) {
9876
- runAction(pinnedActions[pinnedActionFocus]);
9678
+ if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
9679
+ runAction(bottomActions.find((action) => action.actionId === 'plugin-list-multi'));
9680
+ return;
9681
+ }
9682
+ if (input.toLowerCase() === 'a' && bottomActions.length > 0) {
9683
+ const firstAction = actions.findIndex((action) => bottomActions.includes(action));
9684
+ if (firstAction >= 0) {
9685
+ const anchor = actionJumpAnchor.current;
9686
+ if (anchor?.routeKey === routeKey && focus >= firstAction) {
9687
+ const entityIndex = anchor.entityId
9688
+ ? actions.findIndex((action) => action.pluginId === anchor.entityId) : -1;
9689
+ setFocus(entityIndex >= 0 ? entityIndex : Math.min(anchor.index, Math.max(0, firstAction - 1)));
9690
+ actionJumpAnchor.current = undefined;
9691
+ }
9692
+ else {
9693
+ actionJumpAnchor.current = { routeKey, index: focus, entityId: actions[focus]?.pluginId };
9694
+ setFocus(firstAction);
9695
+ }
9696
+ setScrollOffset(0);
9697
+ }
9877
9698
  return;
9878
9699
  }
9879
9700
  if (key.tab && (route.name === 'configLayer'
@@ -9943,6 +9764,12 @@ export function ConsoleAppV4(props) {
9943
9764
  setFocus(portableResultDetailReturnFocus.current);
9944
9765
  setScrollOffset(0);
9945
9766
  }
9767
+ else if (globalAction === 'back' && route.name === 'pluginDetail' && showTechnical) {
9768
+ setShowTechnical(false);
9769
+ setBatchTargetOffset(0);
9770
+ setFocus(0);
9771
+ setScrollOffset(0);
9772
+ }
9946
9773
  else if (globalAction === 'back' && route.name === 'portableSkillSelect' && portableToolsOpen) {
9947
9774
  setPortableToolsOpen(false);
9948
9775
  setFocus(0);
@@ -10016,7 +9843,7 @@ export function ConsoleAppV4(props) {
10016
9843
  back();
10017
9844
  }
10018
9845
  else if (route.name === 'pluginList' && input.toLowerCase() === 'f') {
10019
- togglePluginListFilter?.();
9846
+ runAction(listQueryActions.find((action) => action.actionId === 'plugin-list-filter'));
10020
9847
  }
10021
9848
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
10022
9849
  && input.toLowerCase() === 'f') {
@@ -10030,26 +9857,12 @@ export function ConsoleAppV4(props) {
10030
9857
  setFocus(0);
10031
9858
  setScrollOffset(0);
10032
9859
  }
10033
- else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
10034
- prepareCurrentProjectPlugins();
10035
- }
10036
9860
  else if (route.name === 'projectStatus' && input.toLowerCase() === 'c') {
10037
- runAction(pinnedActions.find((action) => action.actionId === 'project-sync-actionable'));
10038
- }
10039
- else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
10040
- const candidates = batchCandidates(batchDraft)
10041
- .filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined);
10042
- setBatchDraft((current) => current ? {
10043
- ...current,
10044
- selectedPluginIds: [...new Set([
10045
- ...current.selectedPluginIds,
10046
- ...candidates.map((candidate) => candidate.pluginId),
10047
- ])],
10048
- } : current);
9861
+ runAction(bottomActions.find((action) => action.actionId === 'project-sync-actionable'));
10049
9862
  }
10050
9863
  else if (route.name === 'installTargets' && route.view !== 'blocked'
10051
9864
  && input.toLowerCase() === 'c') {
10052
- runAction(pinnedActions.find((action) => action.actionId === 'confirm-install-platforms'));
9865
+ runAction(bottomActions.find((action) => action.actionId === 'confirm-install-platforms'));
10053
9866
  }
10054
9867
  else if (route.name === 'batchInstallPlatforms' && input.toLowerCase() === 'c') {
10055
9868
  runAction(actions.find((action) => action.actionId === 'batch-install-platforms-confirm'));
@@ -10057,15 +9870,15 @@ export function ConsoleAppV4(props) {
10057
9870
  else if (route.name === 'batchInstallScope' && input.toLowerCase() === 'c') {
10058
9871
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
10059
9872
  }
10060
- else if (route.name === 'batchPlacement' && pinnedActions.length > 0
9873
+ else if (route.name === 'batchPlacement' && bottomActions.length > 0
10061
9874
  && input.toLowerCase() === 'c') {
10062
9875
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
10063
9876
  }
10064
9877
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
10065
- runAction(pinnedActions.find((action) => action.actionId === 'batch-selection-continue'));
9878
+ runAction(bottomActions.find((action) => action.actionId === 'batch-selection-continue'));
10066
9879
  }
10067
9880
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
10068
- && focusZone === 'content' && input === ' ') {
9881
+ && input === ' ') {
10069
9882
  const action = actions[normalizedEffectiveFocus];
10070
9883
  if (action?.pluginId
10071
9884
  && (batchDraft.selectedPluginIds.includes(action.pluginId) || action.canSelectNew)) {
@@ -10073,12 +9886,20 @@ export function ConsoleAppV4(props) {
10073
9886
  }
10074
9887
  }
10075
9888
  else if (route.name === 'installTargets' && route.view !== 'blocked'
10076
- && focusZone === 'content' && input === ' ') {
9889
+ && input === ' ') {
10077
9890
  const action = actions[focus];
10078
9891
  if (action?.actionId?.startsWith('install-platform-'))
10079
9892
  runAction(action);
10080
9893
  }
9894
+ else if (route.name === 'batchInstallPlatforms'
9895
+ && input === ' ') {
9896
+ const action = actions[focus];
9897
+ if (action?.actionId === 'batch-install-platform-codex'
9898
+ || action?.actionId === 'batch-install-platform-claude')
9899
+ runAction(action);
9900
+ }
10081
9901
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
9902
+ setSearchCursor([...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(batchDraft.query)].length);
10082
9903
  setSearchFocus(true);
10083
9904
  }
10084
9905
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
@@ -10096,7 +9917,7 @@ export function ConsoleAppV4(props) {
10096
9917
  setScrollOffset(0);
10097
9918
  }
10098
9919
  else if (route.name === 'configFields' && input.toLowerCase() === 's') {
10099
- runAction(pinnedActions[0]);
9920
+ runAction(bottomActions[0]);
10100
9921
  }
10101
9922
  else if (route.name === 'configFields' && input.toLowerCase() === 'u') {
10102
9923
  const key = actions[focus]?.configKey;
@@ -10123,20 +9944,17 @@ export function ConsoleAppV4(props) {
10123
9944
  }
10124
9945
  else if ((input.toLowerCase() === 'r' || input.toLowerCase() === 'c')
10125
9946
  && route.name === 'sourceList') {
10126
- void props.controller.recheckAllCatalogSources();
9947
+ runAction(bottomActions.find((action) => action.actionId === 'source-check-all'));
10127
9948
  }
10128
9949
  else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
10129
9950
  const direction = key.leftArrow || input === '[' ? -1 : 1;
10130
9951
  movePluginListSource?.(direction);
10131
9952
  }
10132
9953
  else if (input.toLowerCase() === 'c' && route.name === 'portableSkillSelect') {
10133
- runAction(pinnedActions.find((action) => action.actionId === 'portable-selection-continue'));
10134
- }
10135
- else if (input.toLowerCase() === 'a' && route.name === 'portableSkillSelect') {
10136
- runAction(selectAllPortableCandidates);
9954
+ runAction(bottomActions.find((action) => action.actionId === 'portable-selection-continue'));
10137
9955
  }
10138
9956
  else if (input === ' ' && route.name === 'portableSkillSelect'
10139
- && focusZone === 'content' && !portableToolsOpen) {
9957
+ && !portableToolsOpen) {
10140
9958
  const candidate = portableVisibleCandidates[focus];
10141
9959
  if (candidate)
10142
9960
  props.controller.togglePortableSkill(candidate.name);
@@ -10148,9 +9966,8 @@ export function ConsoleAppV4(props) {
10148
9966
  setFocus((value) => {
10149
9967
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10150
9968
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
10151
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1)
10152
- : route.name === 'sourceDetail' ? moveIndex(actions.length, current, -1)
10153
- : moveFocus(actions, current, -1);
9969
+ const next = route.name === 'sourceDetail' ? moveIndex(actions.length, current, -1)
9970
+ : moveFocus(actions, current, -1);
10154
9971
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
10155
9972
  dynamicMenuFocus.current = {
10156
9973
  routeKey,
@@ -10158,17 +9975,7 @@ export function ConsoleAppV4(props) {
10158
9975
  focusedActionId: dynamicActionIds[next],
10159
9976
  };
10160
9977
  }
10161
- const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10162
- ? batchDraft.showSelectionTools
10163
- ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10164
- : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10165
- : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
10166
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10167
- + (portableVariantCandidates.length > 0 ? 3 : 0)))
10168
- : route.name === 'installTargets' && route.view !== 'blocked'
10169
- ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10170
- + (policy.mode === 'compact' ? 4 : 0))))
10171
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
9978
+ const viewport = actionViewport(actions, policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10172
9979
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
10173
9980
  const projection = buildUnifiedDependencyGraphProjection({
10174
9981
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -10180,7 +9987,8 @@ export function ConsoleAppV4(props) {
10180
9987
  ...projection.dependents.defaultExpandedPluginIds,
10181
9988
  ]), next, viewport.offset);
10182
9989
  }
10183
- setScrollOffset(viewport.offset);
9990
+ if (bottomActions.length === 0)
9991
+ setScrollOffset(viewport.offset);
10184
9992
  return next;
10185
9993
  });
10186
9994
  }
@@ -10191,9 +9999,8 @@ export function ConsoleAppV4(props) {
10191
9999
  setFocus((value) => {
10192
10000
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10193
10001
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
10194
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1)
10195
- : route.name === 'sourceDetail' ? moveIndex(actions.length, current, 1)
10196
- : moveFocus(actions, current, 1);
10002
+ const next = route.name === 'sourceDetail' ? moveIndex(actions.length, current, 1)
10003
+ : moveFocus(actions, current, 1);
10197
10004
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
10198
10005
  dynamicMenuFocus.current = {
10199
10006
  routeKey,
@@ -10201,17 +10008,7 @@ export function ConsoleAppV4(props) {
10201
10008
  focusedActionId: dynamicActionIds[next],
10202
10009
  };
10203
10010
  }
10204
- const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
10205
- ? batchDraft.showSelectionTools
10206
- ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10207
- : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10208
- : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
10209
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10210
- + (portableVariantCandidates.length > 0 ? 3 : 0)))
10211
- : route.name === 'installTargets' && route.view !== 'blocked'
10212
- ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10213
- + (policy.mode === 'compact' ? 4 : 0))))
10214
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10011
+ const viewport = actionViewport(actions, policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10215
10012
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
10216
10013
  const projection = buildUnifiedDependencyGraphProjection({
10217
10014
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -10223,17 +10020,11 @@ export function ConsoleAppV4(props) {
10223
10020
  ...projection.dependents.defaultExpandedPluginIds,
10224
10021
  ]), next, viewport.offset);
10225
10022
  }
10226
- setScrollOffset(viewport.offset);
10023
+ if (bottomActions.length === 0)
10024
+ setScrollOffset(viewport.offset);
10227
10025
  return next;
10228
10026
  });
10229
10027
  }
10230
- else if (key.return && route.name === 'pluginList' && focusZone === 'content' && pluginRows[focus]) {
10231
- const row = pluginRows[focus];
10232
- if (row.menuAction)
10233
- row.menuAction();
10234
- else
10235
- go({ name: 'pluginDetail', pluginId: row.pluginId });
10236
- }
10237
10028
  else if (key.return) {
10238
10029
  runAction(actions[normalizedEffectiveFocus]);
10239
10030
  }
@@ -10345,33 +10136,6 @@ export function ConsoleAppV4(props) {
10345
10136
  ];
10346
10137
  return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 4, marginLeft: 2, width: size.columns - 4, flexDirection: "column", children: _jsxs(ReviewCard, { title: "\u9009\u62E9\u4E0B\u8F7D\u65B9\u5F0F", description: `当前草稿:${currentTransport ?? '未识别'};仅修改草稿,保存前可查看汇总。`, children: [_jsx(SemanticRows, { actions: transportActions, focus: overlayFocus, kind: "choice", compact: false }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" })] }) })] });
10347
10138
  }
10348
- if (pluginListChooser && route.name === 'pluginList') {
10349
- const sourceId = route.sourceId ?? 'all';
10350
- const chooserActions = pluginListChooser.kind === 'source'
10351
- ? [
10352
- { label: '全部来源', selected: sourceId === 'all' },
10353
- ...props.controller.listSources().map((source) => ({
10354
- label: source.label,
10355
- meta: source.id === source.label ? undefined : source.id,
10356
- selected: source.id === sourceId,
10357
- })),
10358
- ]
10359
- : PLUGIN_FILTERS.map((filter) => ({
10360
- label: pluginFilterLabel(filter),
10361
- selected: filter === installFilter,
10362
- }));
10363
- const chooserFocus = Math.min(pluginListChooser.focus, Math.max(0, chooserActions.length - 1));
10364
- const chooserViewport = rowViewport({
10365
- rowHeights: chooserActions.map(() => 1),
10366
- focus: chooserFocus,
10367
- requestedOffset: 0,
10368
- rowBudget: responsiveBudget(6, 9),
10369
- });
10370
- const chooserWidth = Math.max(40, Math.floor(size.columns * 0.8));
10371
- return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: policy.mode === 'compact' ? 2 : 5, marginLeft: Math.max(2, Math.floor((size.columns - chooserWidth) / 2)), width: chooserWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: pluginListChooser.kind === 'source' ? '选择插件来源' : '筛选插件', description: pluginListChooser.kind === 'status' ? '只改变列表,不改变插件详情。' : undefined, children: [_jsx(SemanticRows, { actions: chooserActions, focus: chooserFocus, kind: "choice", compact: true, offset: chooserViewport.offset, limit: chooserViewport.limit }), chooserActions.length > chooserViewport.limit
10372
- ? _jsx(StaticRow, { title: `${chooserViewport.position} / ${chooserViewport.total}`, status: "muted", compact: true })
10373
- : null, _jsx(StaticRow, { title: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88", status: "muted", compact: true })] }) })] });
10374
- }
10375
10139
  if (!overlay)
10376
10140
  return base;
10377
10141
  const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy, props.onSessionNavigation !== undefined);