Files
makelore/.project-docs/10-decisions/proposals/20260903-plugin-navigation-design-7c4e2a91__unified-plugin-workspace-spec.md
T

577 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MakeLore 统一插件工作台实施规范
## 0. 文档信息
| 字段 | 值 |
| --- | --- |
| Spec ID | `ML-PLUGIN-NAV-001` |
| 状态 | 产品方向已获用户确认;待实施 |
| 日期 | 2026-09-03 |
| Task | `20260903-plugin-navigation-design-7c4e2a91` |
| 客户端基线 | `e9875145b41a2cb1827de10d27a4fc6a352704ea` |
| 影响仓库 | MakeLore 客户端 |
| 服务端影响 | 无新接口、无 DTO 变化、无迁移 |
| 来源设计 | 将“插件中心 / 我的插件 / 项目插件”合并为一个 Codex 风格插件入口 |
本文把已确认的产品方向收敛为可实施、可验证的客户端合同。关键词含义:
- **MUST / 必须**:不满足即本次改造未完成。
- **MUST NOT / 禁止**:实现不得出现。
- **SHOULD / 应当**:默认实现方式;偏离时须在任务记录中说明理由。
- **MAY / 可以**:不影响合同的实现选择。
## 1. 已确认事实与独立判断
### 1.1 当前事实
客户端目前存在三个独立页面和三个侧栏入口:
- `/plugin-marketplace`:运营发布的官方目录、搜索、详情和免费获取。
- `/my-plugins`:账号插件库、官方设备包状态和通过对话安装的本机 Device Package。
- `/project-plugins`:当前项目启用状态、伙伴分配投影、计费能力与插件设置。
这些页面实际投影的是同一插件在不同阶段的状态,但底层权威并不相同:
1. 官方目录与详情;
2. Account Library 免费获取状态;
3. 官方设备包的 Package Store 状态;
4. 本机 Device Package 状态;
5. 当前项目启用状态;
6. Agent Skill 分配状态;
7. runtime admission、后端与计费状态。
当前原生 Web Search 已是所选模型的核心工具,不再是 Marketplace Plugin。通过对话安装的
第三方 Web Search、Skill 或 Pi extension 仍属于本机 Device Package。
### 1.2 结论
三个导航入口必须合并,但只能合并用户界面和只读投影,不能合并上述权威状态机。
若只是把三个旧页面放进三个 Tab,用户仍需理解“去哪里发现、去哪里下载、去哪里启用”的
旧流程,且新插件类型会继续制造第四个页面。因此本规范采用:
- 一个侧栏入口;
- 一个 canonical route;
- 一份统一列表;
- 范围、来源和状态筛选;
- 一个详情面板;
- 各 mutation 仍发送给原有 owner。
## 2. 目标结果
完成后必须实现以下产品结果:
1. Code 侧栏只出现一个“插件”入口。
2. `/plugins` 是唯一 canonical 页面。
3. 官方目录、账号已获取插件、本机包和当前项目状态可以在同一列表中理解和操作。
4. 用户无需先进入“我的插件”再跳转“项目插件”。
5. 免费获取、设备交付、项目启用和 Agent 分配仍是显式且互不自动推进的步骤。
6. 本机 Skill/extension 仍只能通过对话安装;页面只管理已安装内容。
7. 任一远端投影失败时,其他来源仍可独立显示和操作。
8. 旧书签和旧页面链接通过确定性 redirect 进入新的等价筛选状态。
9. 原生模型 Web Search 不作为插件卡片出现。
## 3. 范围
### 3.1 包含
- Code 侧栏插件导航合并。
- `/plugins` route 与旧 route redirects。
- Renderer 统一插件只读投影 Module。
- 统一搜索、范围、来源、状态筛选。
- 官方、本机、项目保留项的统一卡片与详情面板。
- 现有获取、移除、官方设备包、项目启用、本机启停/移除和 Data Service 设置入口。
- 部分失败、缓存、账号切换和项目切换行为。
- 单元测试、页面测试、路由测试和 Electron E2E 更新。
- README 与 task-scoped 项目文档更新。
### 3.2 不包含
- 服务端 Marketplace、Library、Admission、Policy、Token Point 或 Provider 接口变化。
- 新的 Renderer 持久化状态或第四份插件数据库。
- 可见的 npm、Git、文件夹、ZIP 或 `SKILL.md` 安装入口。
- 将本机 Device Package 改为项目级启用。
- 将原生模型 Web Search 重新注册为 Marketplace Plugin。
- 自动获取、自动下载、自动启用项目或自动分配 Agent。
- 插件发布、运营端管理或 Release 生命周期改造。
- 通用第三方插件审核或权限沙箱。
## 4. 不可变产品合同
### 4.1 导航
- `NAV-001`:侧栏 MUST 只有一个 `插件` 按钮,测试标识为
`sidebar-nav-plugins`。
- `NAV-002`:按钮在存在当前项目时 MUST 打开
`/plugins?scope=project`;没有当前项目时 MUST 打开
`/plugins?scope=all`。
- `NAV-003`:`/plugins` MUST 是 Code 模块路由且是项目初始化安全路由。
- `NAV-004`:`/plugins` MUST NOT 成为全页 Provider gate。目录和本机包不能因当前项目
Provider 未配置而不可见。
- `NAV-005`:旧路由 MUST 使用 replace redirect,不得继续挂载旧页面。
旧路由映射固定为:
| 旧路由 | 新路由 |
| --- | --- |
| `/plugin-marketplace` | `/plugins?scope=all&source=official` |
| `/my-plugins` | `/plugins?scope=all&state=mine` |
| `/project-plugins` | `/plugins?scope=project` |
### 4.2 状态推进
- `STATE-001`:免费获取只改变 Account Library。
- `STATE-002`:免费获取 MUST NOT 自动下载、启用项目或分配 Agent。
- `STATE-003`:项目启用 MUST 使用当前项目的显式 ID;项目切换后旧确认框不得作用于
新项目。
- `STATE-004`:Agent 分配仍由项目配置拥有;统一页面只展示投影并跳转到现有分配入口。
- `STATE-005`:本机包启停是设备全局行为,文案必须写明“所有项目”或“本机全局”,
不得显示为“当前项目已启用”。
- `STATE-006`:本机包安装仍是 prepare → 用户确认 → commit 的对话流程;页面不得提供
install picker。
- `STATE-007`:system-included 和 code-owned bundled 插件不得显示不适用的下载、更新、
Beta、签名或设备删除动作。
- `STATE-008`:项目中保留但当前无法解析的 plugin ID 必须继续显示,并只提供明确的
“从项目禁用”动作。
### 4.3 Web Search
- `WEB-001`:所选模型的原生 `makelore_web_search` MUST NOT 进入统一插件投影。
- `WEB-002`:原生 Web Search 的可用性属于模型/Agent capability UI。
- `WEB-003`:若用户通过对话安装第三方 Web Search package,它只以“本机”来源显示,
并遵守 Device Package 的全局启停与新会话加载合同。
## 5. 页面信息架构
### 5.1 页面头部
页面标题固定为“插件”,说明文案必须同时表达:
- 官方插件由 MakeLore 运营发布;
- 本机 Skill/extension 通过对话安装;
- 获取、项目启用和 Agent 分配是独立步骤。
页面提供一个刷新按钮。刷新 MUST 独立触发可用的数据源,不得因为一个 Promise reject
而跳过其他数据源。
### 5.2 筛选
筛选参数是 URL query 的 canonical 状态:
```ts
type PluginWorkspaceScope = 'all' | 'project';
type PluginWorkspaceSource = 'all' | 'official' | 'local';
type PluginWorkspaceState =
| 'all'
| 'available'
| 'mine'
| 'enabled'
| 'update'
| 'unavailable';
```
裸 `/plugins` 的默认 `scope` 与侧栏一致:存在当前项目时为 `project`,否则为 `all`。
实现等待 workspace hydration 后只规范化一次 URL,禁止因后续异步投影反复改写用户选择。
详情可以使用可选的 `plugin=<source-qualified-key>` query;无效或已消失的 key 只关闭详情,
不影响其余筛选。
用户可见控件:
- 范围:`全部插件` / `当前项目`;
- 来源:`全部来源` / `官方` / `本机`;
- 状态:`全部状态` / `可获取` / `我的` / `已启用` / `可更新` / `不可用`;
- 搜索:名称、简介、标签、package ID。
要求:
- `FILTER-001`:未知 query 值 MUST 归一为默认值,不得导致白屏。
- `FILTER-002`:`scope=project` 且没有当前项目时 MUST 回到 `scope=all`,并显示一次
非阻断说明。
- `FILTER-003`:`state=mine` 表示官方 system-included/当前已获取项与全部本机安装项;
不等同于“已下载到设备”。
- `FILTER-004`:官方搜索词 MUST 发送给现有目录搜索;同一搜索词 MUST 同时在本地过滤
已知 Library、项目投影和 Device Package。
- `FILTER-005`:远端搜索失败不得清空符合条件的本机结果。
- `FILTER-006`:筛选只影响展示,不得触发 acquisition、install 或 enable mutation。
范围语义固定为:
- `scope=all`:官方 catalog/Library/project 已知条目的并集、全部本机包,以及必要的
project retained 条目;
- `scope=project`:当前 project projection 的官方条目、全部本机包和 retained IDs。
本机包必须放在“本机全局生效”分组,不能伪装成项目 assignment;
- `state=available`:当前可免费获取且尚未获取的官方条目;
- `state=mine`:system-included、当前已获取的官方条目及全部本机包;
- `state=enabled`:当前项目已启用的官方条目及本机全局已启用条目;
- `state=update`:现有官方设备包明确存在同频道更新的条目;
- `state=unavailable`:暂停、退役后不可重新获取、client incompatible、投影 unavailable
或 retained 的条目。
默认排序不得依赖异步完成顺序:官方 catalog 项保持服务端目录顺序,随后是 Library-only、
project-only、按显示名排序的本机包,最后是 retained IDs;`scope=project` 时先保持 project
projection 顺序,再列本机包和 retained IDs。
### 5.3 列表与卡片
每张卡片必须在一个位置展示适用的状态维度:
- 来源:`官方` / `本机` / `配置保留`;
- 交付:`随 MakeLore 提供` / `账号已获取` / `本机已安装` / `尚未获取`;
- 当前项目:`已启用` / `未启用` / `暂不可用`;
- 本机包:`本机全局已启用` / `本机全局已停用`;
- Agent:已分配伙伴数量或“尚未分配”;
- 计费:`包含` / `按 Token Point` / `混合` / `无平台插件计费投影`;
- 更新、暂停、退役、缓存或不可用提示。
卡片不得用一个“已安装”标签混淆 Account Library、官方设备包和本机包。
### 5.4 详情面板
选择卡片后打开一个统一详情抽屉或同页详情面板,内容顺序固定为:
1. 名称、来源、发布者和版本;
2. 介绍;
3. 当前账号/设备/项目状态;
4. 主动作;
5. 能力与权限;
6. Token Point 说明;
7. Agent 分配;
8. 插件专属设置。
官方详情按需调用现有 `loadDetail(pluginId)`。本机详情只能使用 Device Package 的安全
Renderer 投影,不得读取包目录、任意 manifest 文件或绝对安装路径。
Data Service 设置 Surface 必须迁移到新详情面板,行为和 destructive confirmation 保持
不变。
## 6. 统一投影 Module
### 6.1 Seam
在 Renderer 增加一个纯计算的 `PluginWorkspaceProjection` Module。它是页面唯一学习的
聚合 Interface;它不发网络请求、不写 store、不持久化数据。
建议位置:
```text
src/pages/Plugins/
index.tsx
plugin-workspace-model.ts
PluginDetails.tsx
```
外部 Interface:
```ts
interface PluginWorkspaceInputs {
catalog: MarketplaceCatalogPage | null;
details: Readonly<Record<string, MarketplacePluginDetail>>;
library: MarketplaceLibrarySnapshot | null;
marketplaceInstallations: Readonly<Record<string, MarketplaceInstallation>>;
devicePackages: DevicePackageIndexV1 | null;
project: CodingPluginProject | null;
activeProject: { id: string; name: string } | null;
agentNames: Readonly<Record<string, string>>;
filters: PluginWorkspaceFilters;
}
interface PluginWorkspaceProjection {
items: readonly PluginWorkspaceItem[];
selected: PluginWorkspaceItem | null;
counts: Readonly<Record<PluginWorkspaceState, number>>;
notices: readonly PluginWorkspaceNotice[];
}
function buildPluginWorkspaceProjection(
input: PluginWorkspaceInputs,
): PluginWorkspaceProjection;
```
页面可以保留短暂的 selected key 与 filter form state;不得新增第二个持久 Zustand authority。
### 6.2 稳定身份
统一条目 key 必须带来源,禁止按裸 ID 把本机包和官方插件合并:
```ts
type PluginWorkspaceKey =
| `official:${string}`
| `local:${string}`
| `retained:${string}`;
```
投影算法:
1. 按 `pluginId` 合并 catalog、detail、Library、官方设备包和 project item;
2. catalog 不含但 Library 或 project 仍存在的官方条目不得丢失;
3. 每个 Device Package 始终生成独立的 `local:` 条目;
4. `unknownPluginIds` 中仍未被官方条目解释的 ID 生成 `retained:` 条目;
5. 不得根据相同 Skill ID、显示名或 package ID 推断二者是同一插件。
### 6.3 Action union
投影条目只暴露当前合法动作:
```ts
type PluginWorkspaceCommand =
| { kind: 'acquire'; pluginId: string }
| { kind: 'reacquire'; pluginId: string }
| { kind: 'remove_from_library'; pluginId: string }
| { kind: 'install_stable'; pluginId: string }
| { kind: 'install_beta'; pluginId: string }
| { kind: 'update_official'; pluginId: string }
| { kind: 'remove_official_device_package'; pluginId: string }
| { kind: 'enable_project'; projectId: string; pluginId: string }
| { kind: 'disable_project'; projectId: string; pluginId: string }
| { kind: 'enable_local'; packageId: string }
| { kind: 'disable_local'; packageId: string }
| { kind: 'remove_local'; packageId: string }
| { kind: 'open_agent_assignment'; projectId: string; pluginId: string }
| { kind: 'open_settings'; projectId: string; pluginId: string };
```
页面使用单一局部 dispatcher 将 command 交给现有三个 store。dispatcher 不是状态 authority,
不得复制 generation、pending 或 retry 逻辑。
### 6.4 Action matrix
| 条目类型 | 账号动作 | 设备动作 | 项目动作 | Agent 动作 |
| --- | --- | --- | --- | --- |
| system-included 官方插件 | 无 | 无 | 启用/禁用 | 分配 |
| code-owned bundled、已获取 | 移除 | 无 | 启用/禁用 | 分配 |
| code-owned bundled、未获取 | 免费获取 | 无 | 无 | 无 |
| downloadable 官方插件、已获取 | 移除 | 下载/更新/Beta/删除设备包 | 交付就绪后启用/禁用 | 分配 |
| downloadable 官方插件、未获取 | 免费获取 | 无 | 无 | 无 |
| 本机 Device Package | 无 | 本机全局启用/停用/移除 | 无 | 无;自动进入符合条件的新/空闲 parent |
| retained project ID | 无 | 无 | 仅从项目禁用 | 无 |
暂停、退役、removed、client-incompatible 或 unavailable 状态继续遵守现有 store/DTO 的
fail-closed 语义;统一页面不得为了显示按钮而自行放宽。
## 7. 数据加载与错误隔离
### 7.1 首次加载
页面挂载时独立启动:
1. `pluginMarketplaceStore.loadCatalog({ limit: 24 })`;
2. 登录时 `activateAccount(accountKey)` + `loadLibrary()`;
3. `devicePackageStore.load()`;
4. 存在当前项目时 `codingPluginsStore.load(projectId)`。
实现 MUST 使用独立 catch 或 `Promise.allSettled` 等价行为。禁止一个请求失败阻止其他请求。
### 7.2 部分失败
| 失败来源 | 必须保留的内容 |
| --- | --- |
| catalog | 可信缓存、Library 项、本机包、项目投影 |
| Library | 官方目录、本机包、项目保留配置;账号状态显示未知 |
| official Package Store | Library 和项目配置;保留旧设备版本与错误原因 |
| Device Packages | 官方目录、Library 和项目插件 |
| project projection | 官方目录、Library、本机包;当前项目区域显示独立错误 |
- `ERR-001`:错误必须靠近对应来源显示,禁止整页 error boundary。
- `ERR-002`:已有可信缓存时继续展示并标记 stale。
- `ERR-003`:没有权威值时显示未知,不得从另一来源推断 acquisition、pricing 或 enablement。
- `ERR-004`:账号或项目切换必须使旧 generation 结果失去提交资格;复用现有 store 保护,
统一层不得另建竞态较弱的缓存。
- `ERR-005`:选择项在新投影中消失时关闭详情;不得把 A 项详情显示到 B 项。
## 8. 计费与文案
- `BILL-001`:“免费获取”只描述加入账号插件库,不能写成“免费使用”。
- `BILL-002`:列表只显示 `included`、`token_point`、`mixed` 的简洁摘要。
- `BILL-003`:详情中的 operation 级价格只来自 Marketplace detail 或当前 project policy
projection;不得由客户端计算、缓存拼接或从 manifest 推断。
- `BILL-004`:stale/unavailable pricing 必须显示对应状态,不作为新的 runtime admission 依据。
- `BILL-005`:本机包显示“无 MakeLore 平台插件计费投影”,不得承诺其外部服务或模型调用
一定免费。
## 9. 交互与可访问性
- `A11Y-001`:范围、来源、状态必须有可读 label,并可用键盘完成选择。
- `A11Y-002`:当前筛选状态必须同时体现在 URL 与控件状态中。
- `A11Y-003`:状态不得只靠颜色表达,必须有文本。
- `A11Y-004`:详情抽屉使用 dialog 语义、初始焦点、Esc 关闭,并在关闭后把焦点还给原卡片。
- `A11Y-005`:loading 用 `role=status`,阻断错误用 `role=alert`。
- `A11Y-006`:destructive action 必须继续使用确认对话框;确认文案包含具体插件名和作用域。
- `A11Y-007`:按钮文字必须区分“从账号移除”“删除设备包”“从项目禁用”“移除本机包”。
- `A11Y-008`:窄窗口中筛选可以换行,卡片主动作仍保持至少 40 px 点击高度。
## 10. 文件级改造建议
### 10.1 新增
| 文件 | 责任 |
| --- | --- |
| `src/pages/Plugins/index.tsx` | route orchestration、独立加载、query state、command dispatch |
| `src/pages/Plugins/plugin-workspace-model.ts` | 纯统一投影、identity、筛选、排序、actions、notices |
| `src/pages/Plugins/PluginDetails.tsx` | 详情、能力、计费、Agent 与 Data Service settings surface |
| `tests/unit/plugin-workspace-model.test.ts` | 深 Module Interface 测试 |
| `tests/unit/plugins-page.test.tsx` | 页面、部分失败、动作与可访问性测试 |
文件名可以按仓库命名习惯微调,但 Module 责任不得重新散回三个页面。
### 10.2 修改
| 文件 | 改造 |
| --- | --- |
| `src/App.tsx` | 注册 `/plugins`;旧 route replace redirect;移除三个 lazy page |
| `src/components/layout/Sidebar.tsx` | 三入口收敛为 `sidebar-nav-plugins` |
| `src/components/layout/MainLayout.tsx` | `/plugins` 为 initialization-safe route |
| `src/lib/ai-modules.ts` | 新 route 加入 Code module;不加入全页 Provider gate |
| `tests/unit/plugin-marketplace-pages.test.tsx` | 迁移到统一页面行为或删除被替代测试 |
| `tests/unit/project-plugins-page.test.tsx` | 迁移项目投影/settings/unknown ID 行为 |
| `tests/unit/main-layout-module-gate.test.tsx` | 新 route 与 redirects |
| `tests/e2e/plugin-marketplace.spec.ts` | 统一导航、发现、我的、本机和部分失败 |
| `tests/e2e/project-plugins.spec.ts` | 当前项目筛选、启停、Agent 跳转与 settings |
| artifact proof tests | 更新 canonical route/marker,保持 Main authority 检查 |
### 10.3 删除
完成行为迁移后删除:
```text
src/pages/PluginMarketplace/
src/pages/MyPlugins/
src/pages/ProjectPlugins/
```
禁止保留旧页面作为 hidden fallback 或让旧、新页面同时拥有产品行为。
### 10.4 保持不变
以下 Module 继续拥有现有 Interface:
```text
src/stores/plugin-marketplace.ts
src/stores/device-packages.ts
src/stores/coding-plugins.ts
src/lib/plugin-marketplace.ts
src/lib/device-packages.ts
src/lib/coding-plugins.ts
electron/api/routes/plugin-marketplace.ts
electron/api/routes/device-packages.ts
electron/coding-plugins/**
electron/coding-packages/**
```
若实施证据显示必须改变这些 Interface,应先停止并修订本 Spec,不能静默扩大范围。
## 11. 验证合同
### 11.1 统一投影单元测试
至少覆盖:
1. 同一官方 ID 从 catalog/Library/device/project 正确合并为一个条目;
2. 同名或同 ID 的本机 package 与官方插件保持两个来源 key;
3. Library-only retired/removed 条目不会因当前 catalog query 缺失而消失;
4. project-only unknown ID 生成 disable-only retained 条目;
5. system-included 无获取、下载、更新或移除动作;
6. code-owned bundled 无设备包动作;
7. generic downloadable 插件保留现有 stable/Beta/update/remove 行为;
8. 本机包只有全局启停和移除动作;
9. `mine`、`project`、source、state 与 search 组合筛选;
10. 原生模型 Web Search 不进入投影;
11. Token Point 摘要不把免费获取误写为免费使用;
12. 任一输入来源为 null/error 时其他条目仍存在。
### 11.2 Renderer 页面测试
至少覆盖:
1. 仅一个页面标题和一组筛选控件;
2. catalog 失败时本机包仍显示;
3. Device Package 失败时官方条目仍显示;
4. 未登录时目录和本机包可见,账号动作要求登录;
5. 项目切换后旧 enable/disable confirmation 不得操作新项目;
6. 详情选择 A→B 时 A 的迟到 detail 不覆盖 B;
7. Data Service settings surface 行为迁移无回归;
8. local package 页面没有安装按钮、文件选择器或 source 输入框;
9. destructive action 的 label 与 confirmation 精确区分作用域;
10. keyboard、focus return、status/alert 语义。
### 11.3 路由与侧栏测试
- 侧栏只存在 `sidebar-nav-plugins`。
- active project 与 no-project 点击目标正确。
- 三个旧地址按第 4.1 节映射并使用 replace。
- `/plugins` 受 Code module access 管理,但不被项目初始化或 Provider 全页 gate 阻断。
- 登录回跳使用 `/plugins` canonical query,不再写旧 route。
### 11.4 Electron E2E
至少覆盖四条真实 Renderer → Main Host API 流程:
1. 官方目录 → 免费获取 → `state=mine` 可见,但项目仍未启用;
2. 当前项目启用/禁用插件,Agent 分配跳转仍正确;
3. catalog 503 时已安装本机 Skill 仍可见并可停用/启用;
4. system-included、code-owned bundled、本机包三类动作不会互相串线。
### 11.5 必跑命令
实施任务至少运行:
```text
pnpm run typecheck
pnpm run lint:check
pnpm test
pnpm run build:vite
pnpm run test:e2e -- tests/e2e/plugin-marketplace.spec.ts tests/e2e/project-plugins.spec.ts
```
若仓库脚本不接受上述 E2E 过滤语法,可使用项目已有的等价精确命令并在任务记录中写明。
任何失败必须区分本次回归、既有基线和环境阻塞,不得只报告总数。
## 12. 实施顺序
### `PN-01`:统一投影 Module
所有权:`src/pages/Plugins/plugin-workspace-model.ts` 与其测试。
交付:identity、merge、filter、action matrix、partial-input 行为。此票据不改 route 或旧页面。
### `PN-02`:统一页面与详情
所有权:`src/pages/Plugins/**`、必要页面测试。
交付:加载协调、列表、详情、commands、Data Service settings、可访问性。调用现有 stores,
不改 Main contracts。
### `PN-03`:导航硬切换
所有权:`src/App.tsx`、Sidebar、MainLayout、`ai-modules.ts`、route/module tests。
交付:唯一侧栏入口、canonical route、legacy redirects、旧页面删除。依赖 `PN-02`。
### `PN-04`:回归与交付验证
所有权:插件 E2E、artifact markers、README 和 task-scoped evidence。
交付:全量静态/单元/build/Electron 验证,确认没有旧入口、hidden fallback 或安装 picker。
建议单仓串行实施 `PN-01 → PN-02 → PN-03 → PN-04`,避免多个任务同时改页面测试和路由。
## 13. Definition of Done
只有同时满足以下条件才可宣称完成:
1. 侧栏与 route 只剩一个 canonical 插件入口。
2. 三个旧页面实现已删除,旧地址只做 redirect。
3. 统一投影有独立 Interface 测试,页面不直接散落跨 store 合并判断。
4. Catalog、Library、官方设备包、本机包、项目和 Agent 状态仍各自归原 owner。
5. 官方、本机和 retained 项的动作矩阵全部通过测试。
6. catalog 或 project 任一失败不会遮蔽本机包;Device Package 失败不会遮蔽官方目录。
7. 没有可见本机安装入口。
8. 原生模型 Web Search 不出现在插件列表;第三方本机包仍可出现。
9. Data Service 设置、Game Resource 获取/项目启用和本机 Skill 启停均无回归。
10. typecheck、lint、unit、Vite build 和目标 Electron E2E 通过。
11. 实施任务完成项目文档门禁;canonical 架构更新只在 Integration Gate 进行。
## 14. 后续但不属于本次
若未来要让本机 Skill 按项目或 Agent 单独启用,必须另立领域设计:它会改变 Device Package
当前“本机全局、所有符合条件 parent 自动加载”的合同,不能作为本次导航合并的顺手功能。