@menghuan1918/dsh-apollo 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,13 +90,13 @@ flowchart TB
90
90
 
91
91
  ```sh
92
92
  # npm 安装(发布后)
93
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.4
93
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.5
94
94
 
95
95
  # 或从 GitHub main 源码安装(备选)
96
96
  dsh plugin --profile <profile> add github:Menghuan1918/dsh-apollo
97
97
  ```
98
98
 
99
- > **发布状态**:Apollo `0.6.4` 与 `dsh-better-ask-user@0.1.6` 已发布;`dsh-better-ptc@0.1.0` 已发布。提交的 manifest 使用精确 npm 版本,**没有** `file:` / `link:` / Git 说明符。发布顺序见[发布顺序](#发布顺序)。
99
+ > **发布状态**:Apollo `0.6.5` 与 `dsh-better-ask-user@0.1.7` 的本地修复与隔离安装验证已完成,npm 发布待确认;`dsh-better-ptc@0.1.0` 已发布。提交的 manifest 使用精确 npm 版本,**没有** `file:` / `link:` / Git 说明符。发布顺序见[发布顺序](#发布顺序)。
100
100
 
101
101
  安装、升级或换包都会改变运行的代码,必须重启对应 profile 的 DSH 进程;只刷新网页不够。目标行、子代理行、压缩行、共享 PTC 行与 ask-user 行由 Apollo 的 bundle patch 插入,不需要再单独 `add` 五个子包。
102
102
 
@@ -106,7 +106,7 @@ dsh plugin --profile <profile> add github:Menghuan1918/dsh-apollo
106
106
 
107
107
  ```sh
108
108
  dsh plugin --profile <profile> remove dsh-apollo
109
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.4
109
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.5
110
110
  ```
111
111
 
112
112
  > `dsh-apollo` 只是旧入口名;unscoped 的 `dsh-apollo` 已被其他项目占用,不要从 npm 安装它。
@@ -180,7 +180,7 @@ dsh plugin --profile <profile> add @menghuan1918/dsh-better-subagent@0.1.0
180
180
  dsh plugin --profile <profile> add dsh-better-compact@0.1.1
181
181
 
182
182
  # 只装提问增强(同时需要共享 PTC 行)
183
- dash plugin --profile <profile> add dsh-better-ask-user@0.1.6 dsh-better-ptc@0.1.0
183
+ dash plugin --profile <profile> add dsh-better-ask-user@0.1.7 dsh-better-ptc@0.1.0
184
184
  ```
185
185
 
186
186
  独立安装时的行、页面和提示词与 Apollo 集成完全相同;行 id/name 也是同一组稳定值。区别只在**由谁插入这些行**:Apollo 的 bundle patch 自己携带同样的行,因为 DSH 不会自动应用传递依赖的 bundle patch。
@@ -235,7 +235,7 @@ dsh plugin --profile <profile> remove dsh-better-compact
235
235
  - `package.json` 的 `files` 只列入 `docs/DEVELOPMENT.md`,并额外用 [.npmignore](.npmignore) 排除 `docs/specs/`;
236
236
  - `tests/packaging.test.mjs` 直接运行 `npm pack --dry-run --json`,断言发布清单没有 Specs、测试、脚本、快照或 tarball。
237
237
 
238
- 发布顺序:**先发布并验证五个独立包(`dsh-better-ask-user@0.1.6`、`dsh-better-ptc@0.1.0`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`),再发布 Apollo 0.6.4**。Apollo 的精确依赖指向尚不存在的版本时,`npm install` 会失败;不要用开发 `file:`/`link:` 依赖发布。各子仓各自有 trusted publishing workflow;Apollo 的 [release workflow](.github/workflows/release.yml) 只发布本包。
238
+ 发布顺序:**先发布并验证五个独立包(`dsh-better-ask-user@0.1.7`、`dsh-better-ptc@0.1.0`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`),再发布 Apollo 0.6.5**。Apollo 的精确依赖指向尚不存在的版本时,`npm install` 会失败;不要用开发 `file:`/`link:` 依赖发布。各子仓各自有 trusted publishing workflow;Apollo 的 [release workflow](.github/workflows/release.yml) 只发布本包。
239
239
 
240
240
  ## 验证状态(诚实记录)
241
241
 
@@ -248,7 +248,7 @@ dsh plugin --profile <profile> remove dsh-better-compact
248
248
 
249
249
  **未验证 / 明确限制**:
250
250
 
251
- - 五个独立包与 Apollo 的 0.6.4 配套版本均已发布;临时 profile 验证通过。
251
+ - 0.6.5 与 ask-user 0.1.7 的本地修复及隔离安装验证通过;实际 npm 发布待确认。
252
252
  - 未修改或重启用户当前运行的 profile,未验证当前 GUI(`http://127.0.0.1:3384`)的设置页/卡片;S3 视觉设置页的 GUI 验收归 S3/最终宿主验收。
253
253
  - 未运行真实 Codex/Claude CLI 的真实模型回合(那属于两个子包自己的验证;Apollo 侧本轮只用脚本化适配器)。
254
254
  - 真实模型摘要保真、缓存命中率与跨版本预设漂移仍需按实际环境复验。
@@ -2,7 +2,7 @@
2
2
 
3
3
  面向维护者、集成者和需要排查装配问题的用户。用户安装与配置请看 [README](../README.md);本文保留 DSH 插件的实现细节、旧配置迁移、组合安装边界与验证方法。
4
4
 
5
- 当前实现版本:`0.6.4`;npm 包名是 `@menghuan1918/dsh-apollo`。纯 ESM,无构建步骤;宿主浏览器半边是直接加载的闭包工厂。
5
+ 当前实现版本:`0.6.5`;npm 包名是 `@menghuan1918/dsh-apollo`。纯 ESM,无构建步骤;宿主浏览器半边是直接加载的闭包工厂。
6
6
 
7
7
  ## 目录
8
8
 
@@ -36,7 +36,7 @@
36
36
  | `dsh-better-goal` | `0.2.1` | 目标域(`questionTimeoutSeconds` 已弃用、仅保留读取) |
37
37
  | `@menghuan1918/dsh-better-subagent` | `0.2.0` | 子代理 CLI/等待/child guard/分档/设置页;注册 `wait_subagent` PTC 豁免 |
38
38
  | `dsh-better-compact` | `0.1.2` | 压缩 companion + engine + 设置页/诊断 |
39
- | `dsh-better-ask-user` | `0.1.6` | 官方 `ask_user_question` 入参/返回 + 插件超时等待 + 历史预览行;注册 PTC 豁免;旧 goal 等待迁移 |
39
+ | `dsh-better-ask-user` | `0.1.7` | 官方 `ask_user_question` 入参/返回 + 插件超时等待 + 历史预览行;注册 PTC 豁免;旧 goal 等待迁移 |
40
40
  | `dsh-better-ptc` | `0.1.0` | 单例 PTC deadline 豁免服务 |
41
41
  | `@deepseek-ai/schemastery` | `~3.18.4` | Apollo 行 Config |
42
42
  | `react` | peer | 宿主提供 |
@@ -318,10 +318,10 @@ DSH_PACKAGE_ROOT="/…/node_modules/@deepseek-ai/dsh" npm run prompts
318
318
  ### npm 安装
319
319
 
320
320
  ```sh
321
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.4
321
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.5
322
322
  ```
323
323
 
324
- 包发布后即可用此命令安装,并带入五个精确依赖、应用三份 bundle patch。安装后需要重启对应 profile 的 DSH 进程。0.6.4 与 ask-user 0.1.6 已发布,临时 profile 验证通过。
324
+ 包发布后即可用此命令安装,并带入五个精确依赖、应用三份 bundle patch。安装后需要重启对应 profile 的 DSH 进程。0.6.5 与 ask-user 0.1.7 的本地修复及隔离安装验证通过;实际 npm 发布待确认。
325
325
 
326
326
  ### GitHub 源码安装
327
327
 
@@ -376,9 +376,9 @@ dsh plugin --profile <profile> remove @menghuan1918/dsh-apollo
376
376
 
377
377
  两个子仓和 Apollo 都用 GitHub Release 触发 `.github/workflows/release.yml`,通过 npm trusted publishing(OIDC)发布,仓库里不存 npm token。tag 必须是 `v<package.json 的 version>`,CI 会先校验再跑测试和 `npm publish --provenance`。
378
378
 
379
- 1. 先发布 `dsh-better-ptc@0.1.0`、`dsh-better-ask-user@0.1.6`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`(各自仓库的 Release);
379
+ 1. 先发布 `dsh-better-ptc@0.1.0`、`dsh-better-ask-user@0.1.7`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`(各自仓库的 Release);
380
380
  2. 在官方 registry(`https://registry.npmjs.org/`)确认五个版本可取得、可安装;
381
- 3. 再把 Apollo 的精确依赖指向已发布版本,推 main 并发布 Apollo Release(本轮为 `v0.6.4`);
381
+ 3. 再把 Apollo 的精确依赖指向已发布版本,推 main 并发布 Apollo Release(本轮为 `v0.6.5`);
382
382
  4. 子包版本可取得前,不要把指向它们的精确依赖推到 Apollo main,否则 `npm install` / GitHub 源码安装会 404。
383
383
 
384
384
  ## 测试与冒烟
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menghuan1918/dsh-apollo",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "DSH plugin bundle: workflow skills, engineering presets, independent better-goal / better-subagent / better-compact / better-ask-user integration, and a minimal job exit guard. Pure ESM, no build step.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@deepseek-ai/schemastery": "~3.18.4",
57
57
  "@menghuan1918/dsh-better-subagent": "0.2.0",
58
- "dsh-better-ask-user": "0.1.6",
58
+ "dsh-better-ask-user": "0.1.7",
59
59
  "dsh-better-compact": "0.1.2",
60
60
  "dsh-better-goal": "0.2.1",
61
61
  "dsh-better-ptc": "0.1.0"
package/src/client.js CHANGED
@@ -1,17 +1,23 @@
1
1
  /**
2
- * dsh-apollo (npm: @menghuan1918/dsh-apollo) — 浏览器半边:官方 Plugins 页的最小行配置页。
2
+ * dsh-apollo (npm: @menghuan1918/dsh-apollo) — 浏览器半边。
3
3
  *
4
- * 0.5.0 起 Apollo 只保留两件事需要网页入口:
5
- * - `exitGuard`:运行中**后台 job** 的退出守卫开关(volatile,保存即生效);
6
- * - 迁移/弃用说明:CLI 子代理、模型分档、运行中子代理守卫与压缩设置分别由
7
- * `@menghuan1918/dsh-better-subagent` 与 `dsh-better-compact` 的行拥有。
8
- * Apollo 的旧字段仍能装载(供一次性迁移读取),但不再作为可编辑的子代理设置。
4
+ * 两块 UI:
9
5
  *
10
- * 本文件注册 `plugins.row.config`(键 `<npm 包名>#apollo-skills`,与
11
- * src/config.js 的 ROW_CONFIG_KEY/LEGACY_ROW_CONFIG_KEY 同步),自画紧凑表单并只在
12
- * 保存时一次提交全部改动。零构建:closure factory,只 require('react'),不 import
13
- * 任何 DSH client 包;样式随组件树内一个 <style> 渲染(不写 DOM)。注册 id 必须等于
14
- * npm 包名(client closure id)。不注册新插件的页面键——那些由各自的包拥有。
6
+ * 1. **行配置页**(`plugins.row.config`,键 `<包名>#apollo-skills`):后台任务
7
+ * `exitGuard` 开关 + 迁移/弃用说明。保存即生效(volatile),写入当前
8
+ * profile 的行配置(cordis.patch.yml)。
9
+ *
10
+ * 2. **集成设置弹窗**(`plugins.detail.actions` 的 "Apollo 集成设置" 按钮,
11
+ * 只在 subject 为 Apollo bundle 详情页时渲染):弹窗左 nav + 右内容,聚合
12
+ * 渲染各子插件自己注册到 `plugins.row.config` 的设置组件——entry 组件经
13
+ * `ctx.slots.entriesOfSlot` 按 `#<rowId>` 键后缀找到,`form` 用
14
+ * `ctx.configForms.get(rowId)` 复刻官方 `formFor`(`{state,mutate}`),
15
+ * `t` 用 `ctx.locale.bind(entry.locale)` 补齐。子插件页面永远是单一事实
16
+ * 源,本文件不复制任何字段定义。
17
+ *
18
+ * 主题:#3388BB(`--cfg-accent`),其余全部走 --dsw-alias-* 中性令牌。
19
+ * 零构建:closure factory,只 require('react') + 可选 ui-primitives(Modal
20
+ * 兜底为自绘 overlay);不 fetch、不直接写 DOM。
15
21
  */
16
22
 
17
23
  window.__ModuleLoader__.load({
@@ -19,19 +25,43 @@ window.__ModuleLoader__.load({
19
25
  factory: (require) => {
20
26
  var module = { exports: {} };
21
27
  var exports = module.exports;
22
- const { createElement: h, useState } = require('react');
28
+ const React = require('react');
29
+ const { createElement: h, useEffect, useMemo, useState, useSyncExternalStore } = React;
30
+
31
+ /** ui-primitives 是可选依赖:拿不到时弹窗退化为自绘 overlay。 */
32
+ let Primitives = null;
33
+ try {
34
+ Primitives = require('@deepseek-ai/dsh-client-ui-primitives');
35
+ } catch {
36
+ Primitives = null;
37
+ }
38
+ const Modal = Primitives?.Modal ?? null;
23
39
 
24
40
  const NS = 'apollo';
41
+ const PACKAGE_NAME = '@menghuan1918/dsh-apollo';
42
+ const LEGACY_PACKAGE_NAME = 'dsh-apollo';
25
43
  // 新键是 Plugins 页按“bundle 依赖名 + 行 id”算出的(= npm 包名 + #apollo-skills);
26
44
  // 旧键并排注册,只为仍把依赖槽记成旧 unscoped 名的 profile 保留配置入口。
27
45
  const ROW_KEYS = ['@menghuan1918/dsh-apollo#apollo-skills', 'dsh-apollo#apollo-skills'];
46
+ const DETAIL_ACTIONS_SLOT = 'plugins.detail.actions';
47
+ const ROW_CONFIG_SLOT = 'plugins.row.config';
48
+ const HUB_ENTRY_ID = 'apollo-settings-hub';
49
+
50
+ /** 聚合弹窗展示的设置命名空间(= 行 id),按 nav 顺序排列。 */
51
+ const HUB_ROWS = [
52
+ { rowId: 'apollo-skills', titleKey: 'pluginApollo' },
53
+ { rowId: 'dsh-better-subagent', titleKey: 'pluginSubagent' },
54
+ { rowId: 'dsh-better-compact', titleKey: 'pluginCompact' },
55
+ { rowId: 'dsh-better-goal', titleKey: 'pluginGoal' },
56
+ { rowId: 'dsh-better-ask-user', titleKey: 'pluginAskUser' },
57
+ ];
28
58
 
29
59
  const zh = {
30
60
  summary: 'Apollo 集成设置:后台任务退出守卫与迁移说明',
31
61
  title: 'Apollo 集成设置',
32
62
  intro: 'Apollo 负责技能、工程预设和独立插件的装配。这里只保留后台任务退出守卫;保存后即时生效,配置写入当前 profile 的行配置(cordis.patch.yml)。',
33
63
  migratedTitle: '已迁移的能力',
34
- migratedBody: 'CLI 子代理、wait_subagent、运行中子代理守卫、模型分档与子代理设置页由 @menghuan1918/dsh-better-subagent 拥有;压缩模式与预算由 dsh-better-compact 拥有。请到 Plugins 中对应的行配置。',
64
+ migratedBody: 'CLI 子代理、wait_subagent、运行中子代理守卫、模型分档与子代理设置页由 @menghuan1918/dsh-better-subagent 拥有;压缩模式与预算由 dsh-better-compact 拥有。请到 Plugins 中对应的行配置,或使用包详情页的集成设置弹窗。',
35
65
  deprecatedBody: '旧 Apollo 行里的 tiers / subagents / trustedHosts 只为迁移旧 profile 保留,运行时不再生效;迁移在启动后自动补齐新插件的缺失值,目标行已有显式设置时不会被覆盖。',
36
66
  reloadBody: '从 0.4.x 升级、或同时显式选中了独立 bundle 时,行可能显示为只读/不可寻址;请只保留一个显式入口并重载 profile。压缩设置的保存需要重载 profile 才会被引擎采用。',
37
67
  loading: '正在读取配置…',
@@ -49,6 +79,18 @@ window.__ModuleLoader__.load({
49
79
  saveFailed: '保存失败:',
50
80
  readonlyNote: '只读:当前无法写入宿主配置。',
51
81
  unavailableNote: '不可写:当前连接下该配置不可用。',
82
+ hubButton: '集成设置',
83
+ hubTitle: 'Apollo 集成设置',
84
+ hubIntro: '统一查看和修改 Apollo 及各子插件的设置;各页面与各插件行配置共用同一份表单。',
85
+ hubEmpty: '暂无可用的子插件设置:子插件客户端尚未注册设置页,或本部署未组合设置服务。',
86
+ done: '完成',
87
+ close: '关闭',
88
+ entryError: '此设置区加载失败:',
89
+ pluginApollo: 'Apollo 核心',
90
+ pluginSubagent: 'Better Subagent',
91
+ pluginCompact: 'Better Compact',
92
+ pluginGoal: 'Better Goal',
93
+ pluginAskUser: 'Better Ask User',
52
94
  };
53
95
 
54
96
  const en = {
@@ -56,7 +98,7 @@ window.__ModuleLoader__.load({
56
98
  title: 'Apollo integration settings',
57
99
  intro: 'Apollo owns the skills, the engineering presets and the assembly of the independent plugins. Only the background-job exit guard remains here; saving applies immediately and persists in the active profile row config (cordis.patch.yml).',
58
100
  migratedTitle: 'Migrated capabilities',
59
- migratedBody: 'CLI subagents, wait_subagent, the running-child guard, model tiers and the subagent settings page are owned by @menghuan1918/dsh-better-subagent; compaction modes and budgets are owned by dsh-better-compact. Use their rows under Plugins.',
101
+ migratedBody: 'CLI subagents, wait_subagent, the running-child guard, model tiers and the subagent settings page are owned by @menghuan1918/dsh-better-subagent; compaction modes and budgets are owned by dsh-better-compact. Configure their rows under Plugins, or use the integrated settings dialog on the package page.',
60
102
  deprecatedBody: 'The old Apollo tiers / subagents / trustedHosts fields are kept only so 0.4.x profiles load; they are inert at runtime. After boot the migration fills the new plugins’ missing values and never overwrites an explicit target setting.',
61
103
  reloadBody: 'Upgrading from 0.4.x, or selecting both the standalone and Apollo bundles explicitly, can make the row read-only/unaddressable: keep a single explicit entry and reload the profile. Saved compaction settings take effect only after the profile reloads.',
62
104
  loading: 'Loading configuration…',
@@ -74,6 +116,18 @@ window.__ModuleLoader__.load({
74
116
  saveFailed: 'Save failed: ',
75
117
  readonlyNote: 'Read-only: host configuration cannot be written.',
76
118
  unavailableNote: 'Not writable: this configuration is unavailable on the current connection.',
119
+ hubButton: 'Integrated settings',
120
+ hubTitle: 'Apollo integrated settings',
121
+ hubIntro: 'Review and edit Apollo and every sub-plugin’s settings in one place; each pane shares the same form as that plugin’s row configuration page.',
122
+ hubEmpty: 'No sub-plugin settings are available yet: the sub-plugin clients have not registered their pages, or this deployment does not compose the settings service.',
123
+ done: 'Done',
124
+ close: 'Close',
125
+ entryError: 'This settings pane failed to load: ',
126
+ pluginApollo: 'Apollo core',
127
+ pluginSubagent: 'Better Subagent',
128
+ pluginCompact: 'Better Compact',
129
+ pluginGoal: 'Better Goal',
130
+ pluginAskUser: 'Better Ask User',
77
131
  };
78
132
 
79
133
  function interpolate(text, params) {
@@ -85,37 +139,83 @@ window.__ModuleLoader__.load({
85
139
 
86
140
  const STYLE_ID = '@menghuan1918/dsh-apollo/settings.css';
87
141
  const CSS = [
88
- '.apollo-cfg{display:flex;flex-direction:column;gap:12px;max-width:760px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}',
89
- '.apollo-note{margin:0;padding:8px 10px;border-radius:var(--dsw-radius-md);font-size:12.5px;line-height:18px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary)}',
90
- '.apollo-note[data-tone=error]{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary)}',
142
+ /* 主题:#3388BB accent;其余全部中性 --dsw-alias-* 令牌。 */
143
+ '.apollo-cfg,.apollo-root{--cfg-accent:#3388BB;--cfg-accent-hover:#2e7aa8;--cfg-accent-fg:#fff}',
144
+ '.apollo-cfg{display:flex;flex-direction:column;gap:16px;max-width:760px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}',
145
+ '.apollo-intro{margin:0;padding:0 2px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}',
146
+ '.apollo-note{margin:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;font-size:12px;line-height:18px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary)}',
147
+ '.apollo-note[data-tone=error]{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent);color:var(--dsw-alias-state-error-primary)}',
91
148
  '.apollo-note[data-tone=warn]{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-label-secondary)}',
92
149
  '.apollo-pulse{animation:apollo-pulse 2s cubic-bezier(.36,0,.64,1) infinite}',
93
150
  '@keyframes apollo-pulse{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}',
94
151
  '@media (prefers-reduced-motion:reduce){.apollo-pulse{animation:none}}',
95
- '.apollo-title{margin:0;font-size:14px;font-weight:500;line-height:22px}',
96
- '.apollo-hint{margin:0;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}',
97
- '.apollo-intro{margin:0;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary)}',
98
- '.apollo-section{display:flex;flex-direction:column;gap:2px;padding-top:12px;border-top:.5px solid var(--dsw-alias-border-l2)}',
99
- '.apollo-sectionHead{display:flex;flex-direction:column;gap:2px;padding-bottom:4px}',
100
- '.apollo-switchRow{display:flex;align-items:flex-start;gap:12px;padding:6px 0}',
101
- '.apollo-switchMain{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}',
102
- '.apollo-switchLabel{font-size:13px;font-weight:500}',
103
- '.apollo-switch{box-sizing:border-box;position:relative;flex:none;width:36px;height:20px;margin:2px 0 0;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer;appearance:none}',
104
- '.apollo-switch[aria-checked=true]{background:var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary))}',
152
+ /* 分组容器卡:1px hairline + 16px 圆角 + layer-3 底。 */
153
+ '.apollo-group{display:flex;flex-direction:column;gap:6px;padding:16px 18px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-3)}',
154
+ '.apollo-groupHead{display:flex;align-items:baseline;gap:7px;padding:0 2px 8px;font-size:13px;line-height:20px;font-weight:600;color:var(--dsw-alias-label-primary)}',
155
+ '.apollo-count{padding:1px 8px;border-radius:999px;background:color-mix(in srgb, var(--cfg-accent) 10%, transparent);font-size:11px;line-height:16px;font-weight:500;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}',
156
+ '.apollo-title{margin:0;font-size:14px;font-weight:600;line-height:22px;color:var(--dsw-alias-label-primary)}',
157
+ '.apollo-hint{margin:0;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}',
158
+ /* 设置行:标题/描述居左,控件居右,行间 hairline。 */
159
+ '.apollo-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:11px 2px;border-bottom:1px solid var(--dsw-alias-border-l2)}',
160
+ '.apollo-row:last-child{border-bottom:none}',
161
+ '.apollo-rowText{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}',
162
+ '.apollo-rowTitle{font-size:13.5px;line-height:20px;font-weight:500;color:var(--dsw-alias-label-primary)}',
163
+ '.apollo-rowDesc{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}',
164
+ /* Switch:36x20 track,accent 实心 + 白点滑动。 */
165
+ '.apollo-switch{box-sizing:border-box;position:relative;flex:none;width:36px;height:20px;margin:2px 0 0;padding:2px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;appearance:none;transition:background 150ms ease,border-color 150ms ease}',
166
+ '.apollo-switch:hover{border-color:var(--dsw-alias-label-dimmed)}',
167
+ '.apollo-switch[aria-checked=true]{border-color:var(--cfg-accent);background:var(--cfg-accent)}',
105
168
  '.apollo-switch:disabled{opacity:.4;cursor:not-allowed}',
106
- '.apollo-switchThumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}',
107
- '.apollo-switch[aria-checked=true] .apollo-switchThumb{transform:translateX(16px)}',
108
- '@media (prefers-reduced-motion:reduce){.apollo-switchThumb{transition:none}}',
109
- '.apollo-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 10px;border:0;border-radius:var(--dsw-radius-sm);font:inherit;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);background:transparent;cursor:pointer}',
169
+ '.apollo-switchThumb{display:block;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-tertiary);transition:transform 150ms ease,background 150ms ease}',
170
+ '.apollo-switch[aria-checked=true] .apollo-switchThumb{transform:translateX(16px);background:var(--cfg-accent-fg)}',
171
+ '.apollo-switch:focus-visible{outline:2px solid var(--cfg-accent);outline-offset:2px}',
172
+ '@media (prefers-reduced-motion:reduce){.apollo-switch,.apollo-switchThumb{transition:none}}',
173
+ /* 按钮:primary=accent 实心白字;outline=1px 边;hover 走中性填充。 */
174
+ '.apollo-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:6px;height:30px;padding:0 14px;border:1px solid transparent;border-radius:8px;font:inherit;font-size:12.5px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-primary);background:transparent;cursor:pointer;transition:background 120ms ease,border-color 120ms ease,color 120ms ease}',
110
175
  '.apollo-button:disabled{opacity:.4;cursor:not-allowed}',
111
- '.apollo-button[data-variant=primary]{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-label-primary));color:var(--dsw-alias-label-primary-foreground,var(--dsw-alias-bg-layer-3))}',
112
- '.apollo-button[data-variant=outline]{border:.5px solid var(--dsw-alias-border-l3)}',
113
- '.apollo-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
114
- '.apollo-footer{display:flex;align-items:center;gap:8px;padding-top:12px;border-top:.5px solid var(--dsw-alias-border-l2)}',
176
+ '.apollo-button[data-variant=primary]{background:var(--cfg-accent);color:var(--cfg-accent-fg)}',
177
+ '.apollo-button[data-variant=primary]:hover:not(:disabled){background:var(--cfg-accent-hover)}',
178
+ '.apollo-button[data-variant=outline]{border-color:var(--dsw-alias-border-l3)}',
179
+ '.apollo-button[data-variant=outline]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
180
+ '.apollo-button[data-variant=ghost]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
181
+ '.apollo-button:focus-visible{outline:2px solid var(--cfg-accent);outline-offset:2px}',
182
+ '.apollo-input,.apollo-select{box-sizing:border-box;min-width:0;height:30px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px}',
183
+ '.apollo-input:hover,.apollo-select:hover{border-color:var(--dsw-alias-label-dimmed)}',
184
+ '.apollo-input:focus,.apollo-select:focus{outline:none;border-color:var(--cfg-accent);box-shadow:0 0 0 2px color-mix(in srgb, var(--cfg-accent) 25%, transparent)}',
185
+ '.apollo-input:disabled,.apollo-select:disabled{opacity:.4;cursor:not-allowed}',
186
+ '.apollo-input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary)}',
187
+ '.apollo-select{appearance:none;padding-right:26px;background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'%3E%3Cpath d=\'M1 1l4 4 4-4\' stroke=\'%23999\' stroke-width=\'1.5\' fill=\'none\' stroke-linecap=\'round\'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px center}',
188
+ '.apollo-footer{display:flex;align-items:center;gap:8px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-l2)}',
115
189
  '.apollo-status{flex:1;min-width:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}',
116
190
  '.apollo-status[data-tone=ok]{color:var(--dsw-alias-state-success-primary)}',
117
191
  '.apollo-status[data-tone=error]{color:var(--dsw-alias-state-error-primary)}',
118
192
  '.apollo-status[data-tone=warn]{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary))}',
193
+ /* ── 集成设置弹窗 ────────────────────────────────────────────── */
194
+ '.apollo-hubGlyph{flex:none;color:var(--cfg-accent)}',
195
+ /* ui-primitives Modal 加宽(双类提升优先级,同 better-sidebar 做法)。 */
196
+ '.apollo-modal.apollo-modal{width:min(880px, calc(100% - 32px))}',
197
+ '.apollo-hub{display:flex;gap:0;min-height:320px;max-height:60vh;--cfg-accent:#3388BB;--cfg-accent-hover:#2e7aa8;--cfg-accent-fg:#fff}',
198
+ '.apollo-hubNav{flex:none;width:184px;display:flex;flex-direction:column;gap:2px;padding-right:12px;border-right:1px solid var(--dsw-alias-border-l2);overflow-y:auto;scrollbar-width:thin}',
199
+ '.apollo-hubNavItem{display:flex;flex-direction:column;gap:1px;padding:8px 10px;border:0;border-radius:8px;background:transparent;font:inherit;text-align:left;cursor:pointer;color:var(--dsw-alias-label-secondary);transition:background 120ms ease,color 120ms ease}',
200
+ '.apollo-hubNavItem:hover{background:var(--dsw-alias-interactive-bg-hover)}',
201
+ '.apollo-hubNavItem[data-active]{background:color-mix(in srgb, var(--cfg-accent) 10%, transparent);color:var(--dsw-alias-label-primary)}',
202
+ '.apollo-hubNavItem:focus-visible{outline:2px solid var(--cfg-accent);outline-offset:1px}',
203
+ '.apollo-hubNavName{font-size:13px;line-height:18px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
204
+ '.apollo-hubNavId{font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
205
+ '.apollo-hubContent{flex:1;min-width:0;overflow-y:auto;padding-left:18px;scrollbar-width:thin}',
206
+ '.apollo-hubPane[hidden]{display:none}',
207
+ '@media (max-width:640px){.apollo-hub{flex-direction:column}.apollo-hubNav{width:auto;flex-direction:row;gap:4px;padding:0 0 10px;border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2);overflow-x:auto}.apollo-hubNavId{display:none}.apollo-hubContent{padding-left:0;padding-top:12px}}',
208
+ /* 自绘 overlay(ui-primitives 缺失时的兜底弹窗)。 */
209
+ '.apollo-mask{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;background:rgba(0,0,0,.4)}',
210
+ '.apollo-modalCard{display:flex;flex-direction:column;width:min(880px,100%);max-height:calc(100vh - 64px);box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 12px 48px rgba(0,0,0,.22)}',
211
+ '.apollo-modalHead{display:flex;flex-direction:column;gap:4px;padding:18px 20px 12px;border-bottom:1px solid var(--dsw-alias-border-l2)}',
212
+ '.apollo-modalTitleRow{display:flex;align-items:center;justify-content:space-between;gap:12px}',
213
+ '.apollo-modalTitle{margin:0;font-size:15px;font-weight:600;line-height:22px}',
214
+ '.apollo-modalDesc{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}',
215
+ '.apollo-modalBody{flex:1;min-height:0;overflow:hidden;display:flex;padding:14px 20px}',
216
+ '.apollo-modalFoot{display:flex;justify-content:flex-end;gap:8px;padding:12px 20px;border-top:1px solid var(--dsw-alias-border-l2)}',
217
+ '.apollo-iconBtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:16px;line-height:1;cursor:pointer}',
218
+ '.apollo-iconBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
119
219
  ].join('\n');
120
220
 
121
221
  /** 宿主快照只投影这一个可编辑字段。 */
@@ -125,10 +225,10 @@ window.__ModuleLoader__.load({
125
225
  }
126
226
 
127
227
  function SwitchRow({ id, label, hint, checked, disabled, onChange }) {
128
- return h('div', { className: 'apollo-switchRow' },
129
- h('div', { className: 'apollo-switchMain' },
130
- h('label', { className: 'apollo-switchLabel', htmlFor: id }, label),
131
- hint === undefined ? null : h('p', { className: 'apollo-hint' }, hint)),
228
+ return h('div', { className: 'apollo-row' },
229
+ h('div', { className: 'apollo-rowText' },
230
+ h('label', { className: 'apollo-rowTitle', htmlFor: id }, label),
231
+ hint === undefined ? null : h('span', { className: 'apollo-rowDesc' }, hint)),
132
232
  h('button', {
133
233
  type: 'button',
134
234
  id,
@@ -192,13 +292,10 @@ window.__ModuleLoader__.load({
192
292
  : h('span', { className: 'apollo-status', role: 'status' }, writable ? '' : translate('readonlyNote'));
193
293
 
194
294
  return h('div', { className: 'apollo-cfg' },
195
- h('div', { className: 'apollo-sectionHead' },
196
- h('h3', { className: 'apollo-title' }, translate('title')),
197
- h('p', { className: 'apollo-intro' }, translate('intro'))),
295
+ h('p', { className: 'apollo-intro' }, translate('intro')),
198
296
  writable ? null : h('p', { className: 'apollo-note', 'data-tone': 'warn' }, translate('readonly')),
199
- h('div', { className: 'apollo-section' },
200
- h('div', { className: 'apollo-sectionHead' },
201
- h('h3', { className: 'apollo-title' }, translate('exitGuardLabel'))),
297
+ h('div', { className: 'apollo-group' },
298
+ h('div', { className: 'apollo-groupHead' }, translate('title')),
202
299
  h(SwitchRow, {
203
300
  id: 'apollo-exit-guard',
204
301
  label: translate('exitGuardLabel'),
@@ -207,9 +304,8 @@ window.__ModuleLoader__.load({
207
304
  disabled: !writable || busy,
208
305
  onChange: (next) => { setDraft({ revision: state.revision, value: { exitGuard: next } }); setNotice(null); },
209
306
  })),
210
- h('div', { className: 'apollo-section' },
211
- h('div', { className: 'apollo-sectionHead' },
212
- h('h3', { className: 'apollo-title' }, translate('migratedTitle'))),
307
+ h('div', { className: 'apollo-group' },
308
+ h('div', { className: 'apollo-groupHead' }, translate('migratedTitle')),
213
309
  h('p', { className: 'apollo-note' }, translate('migratedBody')),
214
310
  h('p', { className: 'apollo-note' }, translate('deprecatedBody')),
215
311
  h('p', { className: 'apollo-note', 'data-tone': 'warn' }, translate('reloadBody'))),
@@ -236,12 +332,217 @@ window.__ModuleLoader__.load({
236
332
  * 样式随组件树内的 <style> 挂载一次(不写 DOM,重复渲染不重复注入)。
237
333
  */
238
334
  function ApolloPanel(props) {
239
- if (props.view === 'summary') return h('span', null, t('summary'));
335
+ if (props.view === 'summary') return h('span', null, (props.t ?? t)('summary'));
240
336
  return h('div', { className: 'apollo-root' },
241
337
  h('style', { 'data-plugin-css': STYLE_ID }, CSS),
242
338
  h(ApolloConfigPage, { form: props.form, t: props.t ?? t }));
243
339
  }
244
340
 
341
+ // ── 集成设置弹窗 ─────────────────────────────────────────────────
342
+
343
+ function gearGlyph() {
344
+ return h('svg', { width: 13, height: 13, viewBox: '0 0 16 16', fill: 'none', 'aria-hidden': 'true', className: 'apollo-hubGlyph' },
345
+ h('path', {
346
+ d: 'M8 10.4a2.4 2.4 0 1 0 0-4.8 2.4 2.4 0 0 0 0 4.8Z',
347
+ stroke: 'currentColor', strokeWidth: 1.4,
348
+ }),
349
+ h('path', {
350
+ d: 'M13.3 8c0-.4-.1-.8-.2-1.2l1.1-.9-1.2-2-1.3.5c-.6-.5-1.2-.8-1.9-1L9.3 1.7H6.7l-.5 1.7c-.7.2-1.3.5-1.9 1l-1.3-.5-1.2 2 1.1.9a4.9 4.9 0 0 0 0 2.4l-1.1.9 1.2 2 1.3-.5c.6.5 1.2.8 1.9 1l.5 1.7h2.6l.5-1.7c.7-.2 1.3-.5 1.9-1l1.3.5 1.2-2-1.1-.9c.1-.4.2-.8.2-1.2Z',
351
+ stroke: 'currentColor', strokeWidth: 1.2, strokeLinejoin: 'round',
352
+ }));
353
+ }
354
+
355
+ /**
356
+ * 按 HUB_ROWS 顺序收集已注册的子插件设置组件:entry.options.key 以
357
+ * `#<rowId>` 结尾即归该行;同 rowId 多个键(独立/Apollo 两种装配)内容
358
+ * 相同,优先取 Apollo bundle 键。
359
+ */
360
+ function collectPluginEntries(ctx) {
361
+ const read = typeof ctx.slots.entriesOfSlot === 'function'
362
+ ? () => ctx.slots.entriesOfSlot(ROW_CONFIG_SLOT)
363
+ : () => (typeof ctx.slots.entries === 'function' ? ctx.slots.entries(ROW_CONFIG_SLOT) : []);
364
+ let entries = [];
365
+ try {
366
+ entries = read() ?? [];
367
+ } catch {
368
+ entries = [];
369
+ }
370
+ const found = new Map();
371
+ for (const row of HUB_ROWS) {
372
+ const suffix = '#' + row.rowId;
373
+ for (const entry of entries) {
374
+ const key = entry?.options?.key;
375
+ if (typeof key !== 'string' || !key.endsWith(suffix) || entry.component === undefined || entry.component === null) continue;
376
+ const score = key.startsWith(PACKAGE_NAME + '#') ? 0 : 1;
377
+ const prev = found.get(row.rowId);
378
+ if (prev === undefined || score < prev.score) found.set(row.rowId, { entry });
379
+ }
380
+ }
381
+ const items = [];
382
+ for (const row of HUB_ROWS) {
383
+ const hit = found.get(row.rowId);
384
+ if (hit !== undefined) items.push({ rowId: row.rowId, titleKey: row.titleKey, entry: hit.entry });
385
+ }
386
+ return items;
387
+ }
388
+
389
+ /** 复刻官方插件页的 formFor:describe 镜像决定可见性,get() 拿共享表单。 */
390
+ function formFor(ctx, mirror, rowId) {
391
+ const namespaces = mirror?.view?.namespaces;
392
+ if (!Array.isArray(namespaces) || !namespaces.some((view) => view?.ns === rowId)) return undefined;
393
+ try {
394
+ const form = ctx.configForms.get(rowId);
395
+ return { state: form.getSnapshot(), mutate: (ops, revision) => form.mutate(ops, revision) };
396
+ } catch {
397
+ return undefined;
398
+ }
399
+ }
400
+
401
+ /** entry 声明的 locale 命名空间 → t;没有则给 undefined(组件自带兜底)。 */
402
+ function boundT(ctx, entry) {
403
+ if (typeof entry?.locale !== 'string' || entry.locale === '') return undefined;
404
+ try {
405
+ return typeof ctx.locale?.bind === 'function' ? ctx.locale.bind(entry.locale) : undefined;
406
+ } catch {
407
+ return undefined;
408
+ }
409
+ }
410
+
411
+ function subscribeSlot(ctx, listener) {
412
+ try {
413
+ return typeof ctx.slots.subscribe === 'function' ? ctx.slots.subscribe(ROW_CONFIG_SLOT, listener) : () => {};
414
+ } catch {
415
+ return () => {};
416
+ }
417
+ }
418
+
419
+ function slotVersion(ctx) {
420
+ try {
421
+ return typeof ctx.slots.getVersion === 'function' ? ctx.slots.getVersion(ROW_CONFIG_SLOT) : 0;
422
+ } catch {
423
+ return 0;
424
+ }
425
+ }
426
+
427
+ /** 单个设置面板崩溃不炸掉整个弹窗。 */
428
+ class EntryBoundary extends React.Component {
429
+ constructor(props) {
430
+ super(props);
431
+ this.state = { error: null };
432
+ }
433
+ static getDerivedStateFromError(error) {
434
+ return { error };
435
+ }
436
+ render() {
437
+ if (this.state.error !== null) {
438
+ return h('p', { className: 'apollo-note', 'data-tone': 'error', role: 'alert' },
439
+ t('entryError') + String(this.state.error?.message ?? this.state.error));
440
+ }
441
+ return this.props.children;
442
+ }
443
+ }
444
+
445
+ /** 弹窗主体:左 nav + 右面板(全部挂载,display:none 保住未存草稿)。 */
446
+ function HubBody({ ctx }) {
447
+ const version = useSyncExternalStore((listener) => subscribeSlot(ctx, listener), () => slotVersion(ctx));
448
+ const items = useMemo(() => collectPluginEntries(ctx), [ctx, version]);
449
+ const describe = ctx.configForms.describe();
450
+ const mirror = useSyncExternalStore(
451
+ (listener) => (typeof describe?.subscribe === 'function' ? describe.subscribe(listener) : () => {}),
452
+ () => describe?.getSnapshot?.());
453
+ useEffect(() => { void describe?.ensure?.(); }, [describe]);
454
+ const [active, setActive] = useState(null);
455
+ const current = items.find((item) => item.rowId === active) ?? items[0];
456
+
457
+ if (items.length === 0) {
458
+ return h('p', { className: 'apollo-note', role: 'status' }, t('hubEmpty'));
459
+ }
460
+ return h('div', { className: 'apollo-hub' },
461
+ h('div', { className: 'apollo-hubNav', role: 'tablist', 'aria-label': t('hubTitle') },
462
+ items.map((item) => h('button', {
463
+ key: item.rowId,
464
+ type: 'button',
465
+ role: 'tab',
466
+ className: 'apollo-hubNavItem',
467
+ 'data-active': item === current ? true : undefined,
468
+ 'aria-selected': item === current,
469
+ onClick: () => setActive(item.rowId),
470
+ },
471
+ h('span', { className: 'apollo-hubNavName' }, t(item.titleKey)),
472
+ h('span', { className: 'apollo-hubNavId' }, item.rowId)))),
473
+ h('div', { className: 'apollo-hubContent', role: 'tabpanel' },
474
+ items.map((item) => h('div', {
475
+ key: item.rowId,
476
+ className: 'apollo-hubPane',
477
+ hidden: item !== current,
478
+ },
479
+ h(EntryBoundary, null,
480
+ h(item.entry.component, {
481
+ view: 'page',
482
+ form: formFor(ctx, mirror, item.rowId),
483
+ t: boundT(ctx, item.entry),
484
+ }))))));
485
+ }
486
+
487
+ /** 自绘弹窗兜底:fixed mask + card,Esc/遮罩点击关闭(不依赖 ui-primitives)。 */
488
+ function FallbackModal({ onClose, children }) {
489
+ useEffect(() => {
490
+ const doc = globalThis['document'];
491
+ if (doc === undefined || doc === null) return undefined;
492
+ const onKey = (event) => { if (event.key === 'Escape') onClose(); };
493
+ doc.addEventListener('keydown', onKey);
494
+ return () => doc.removeEventListener('keydown', onKey);
495
+ }, [onClose]);
496
+ return h('div', {
497
+ className: 'apollo-mask',
498
+ role: 'presentation',
499
+ onClick: (event) => { if (event.target === event.currentTarget) onClose(); },
500
+ },
501
+ h('div', { className: 'apollo-modalCard', role: 'dialog', 'aria-label': t('hubTitle') },
502
+ h('div', { className: 'apollo-modalHead' },
503
+ h('div', { className: 'apollo-modalTitleRow' },
504
+ h('h2', { className: 'apollo-modalTitle' }, t('hubTitle')),
505
+ h('button', { type: 'button', className: 'apollo-iconBtn', 'aria-label': t('close'), onClick: onClose }, '×')),
506
+ h('p', { className: 'apollo-modalDesc' }, t('hubIntro'))),
507
+ h('div', { className: 'apollo-modalBody' }, children),
508
+ h('div', { className: 'apollo-modalFoot' },
509
+ h('button', { type: 'button', className: 'apollo-button', 'data-variant': 'primary', onClick: onClose }, t('done')))));
510
+ }
511
+
512
+ function SettingsModal({ ctx, onClose }) {
513
+ const body = h(HubBody, { ctx });
514
+ if (Modal === null) return h(FallbackModal, { onClose }, body);
515
+ return h(Modal, {
516
+ open: true,
517
+ onClose,
518
+ title: t('hubTitle'),
519
+ description: t('hubIntro'),
520
+ closeLabel: t('close'),
521
+ className: 'apollo-modal',
522
+ contentClassName: 'apollo-hubModalContent',
523
+ footer: h('button', { type: 'button', className: 'apollo-button', 'data-variant': 'primary', onClick: onClose }, t('done')),
524
+ }, body);
525
+ }
526
+
527
+ /** `plugins.detail.actions` 入口:仅 Apollo bundle 详情页渲染触发按钮。 */
528
+ function HubEntry(props) {
529
+ const subject = props.subject;
530
+ const isApolloBundle = subject?.kind === 'bundle'
531
+ && (subject.pkg?.name === PACKAGE_NAME || subject.pkg?.name === LEGACY_PACKAGE_NAME);
532
+ const [open, setOpen] = useState(false);
533
+ if (!isApolloBundle) return null;
534
+ return h(React.Fragment, null,
535
+ h('style', { 'data-plugin-css': STYLE_ID + ':hub' }, CSS),
536
+ h('button', {
537
+ type: 'button',
538
+ className: 'apollo-button',
539
+ 'data-variant': 'outline',
540
+ 'data-apollo-hub': 'trigger',
541
+ onClick: () => setOpen(true),
542
+ }, gearGlyph(), (props.t ?? t)('hubButton')),
543
+ open ? h(SettingsModal, { ctx: props.ctx, onClose: () => setOpen(false) }) : null);
544
+ }
545
+
245
546
  const inject = ['slots', 'locale'];
246
547
 
247
548
  function apply(ctx) {
@@ -252,12 +553,12 @@ window.__ModuleLoader__.load({
252
553
  } catch {
253
554
  // locale 服务缺失时保持中文默认。
254
555
  }
255
- ctx.effect(() => ctx.slots.inject('plugins.row.config', () => {
556
+ ctx.effect(() => ctx.slots.inject(ROW_CONFIG_SLOT, () => {
256
557
  const disposers = [];
257
558
  for (const key of ROW_KEYS) {
258
559
  try {
259
560
  const dispose = ctx.slots.register({
260
- name: 'plugins.row.config',
561
+ name: ROW_CONFIG_SLOT,
261
562
  key,
262
563
  locale: NS,
263
564
  }, ApolloPanel);
@@ -270,6 +571,27 @@ window.__ModuleLoader__.load({
270
571
  for (const dispose of disposers) dispose();
271
572
  };
272
573
  }), '@menghuan1918/dsh-apollo: row config page');
574
+
575
+ // 集成设置弹窗依赖 configForms(读取各子插件命名空间的共享表单);
576
+ // 服务缺失时仅弹窗缺席,行配置页照常工作。
577
+ try {
578
+ ctx.inject(['configForms'], (sctx) => {
579
+ const register = () => sctx.slots.inject(DETAIL_ACTIONS_SLOT, () =>
580
+ sctx.slots.register({
581
+ name: DETAIL_ACTIONS_SLOT,
582
+ id: HUB_ENTRY_ID,
583
+ order: 0,
584
+ locale: NS,
585
+ }, (props) => h(HubEntry, { ctx: sctx, subject: props.subject, t: props.t ?? t })));
586
+ if (typeof sctx.effect === 'function') {
587
+ sctx.effect(register, '@menghuan1918/dsh-apollo: settings hub');
588
+ return undefined;
589
+ }
590
+ return register();
591
+ });
592
+ } catch {
593
+ // 无 configForms 的组合:集成弹窗不注册。
594
+ }
273
595
  }
274
596
 
275
597
  exports.inject = inject;