diff --git a/.project-docs/10-decisions/proposals/20260903-plugin-navigation-design-7c4e2a91__unified-plugin-workspace-spec.md b/.project-docs/10-decisions/proposals/20260903-plugin-navigation-design-7c4e2a91__unified-plugin-workspace-spec.md new file mode 100644 index 0000000..cb58752 --- /dev/null +++ b/.project-docs/10-decisions/proposals/20260903-plugin-navigation-design-7c4e2a91__unified-plugin-workspace-spec.md @@ -0,0 +1,576 @@ +# 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=` 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>; + library: MarketplaceLibrarySnapshot | null; + marketplaceInstallations: Readonly>; + devicePackages: DevicePackageIndexV1 | null; + project: CodingPluginProject | null; + activeProject: { id: string; name: string } | null; + agentNames: Readonly>; + filters: PluginWorkspaceFilters; +} + +interface PluginWorkspaceProjection { + items: readonly PluginWorkspaceItem[]; + selected: PluginWorkspaceItem | null; + counts: Readonly>; + 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 自动加载”的合同,不能作为本次导航合并的顺手功能。 diff --git a/.project-docs/30-worklog/tasks/20260903-plugin-navigation-design-7c4e2a91.md b/.project-docs/30-worklog/tasks/20260903-plugin-navigation-design-7c4e2a91.md new file mode 100644 index 0000000..5841aed --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260903-plugin-navigation-design-7c4e2a91.md @@ -0,0 +1,88 @@ +# Task: Design unified MakeLore plugin navigation + +## Identity + +- Task ID: 20260903-plugin-navigation-design-7c4e2a91 +- Mode: Feature +- Branch: codex/20260903-plugin-navigation-design-7c4e2a91-plugin-navigation-design +- Worktree: D:\Datas\OthersProjects\makelore-plugin-navigation-design-7c4e2a91 +- Base commit: e9875145b41a2cb1827de10d27a4fc6a352704ea +- Owner: codex-root-plugin-nav +- Status: Ready for Integration + +## Scope + +- Product and architecture assessment of the current Plugin Marketplace, My Plugins, + Project Plugins, local Device Package, and sidebar/router surfaces. +- Compare the current navigation model with OpenAI's documented Codex Plugins + directory and propose one unified MakeLore Plugin surface. +- Write the task-owned implementation proposal + `10-decisions/proposals/20260903-plugin-navigation-design-7c4e2a91__unified-plugin-workspace-spec.md`. +- No product, route, state, server contract, package, or canonical shared-memory + changes. + +## Intent And Constraints + +- Preserve the independent authorities for catalog, Account Library, device bytes, + project enablement, Agent assignment, runtime admission, and billing even though + their user interface is consolidated. +- Keep local npm/Git/Plugin/loose-Skill installation conversation-only; the unified + page may manage installed Device Packages but must not add an install picker. +- Treat selected-model Web Search as a model tool, not a Marketplace Plugin. +- Avoid semantic overlap with concurrent task + `20260903-design-message-send-client-8d3f2a71`, whose owner confirmed it does not + touch Plugin navigation, pages, sidebar, or router. + +## Outcome + +- Recommend one sidebar entry and canonical `/plugins` page. +- The page should use one searchable list with source/status/scope filters rather + than three destination tabs: official catalog and locally installed packages are + source projections; acquired/installed/enabled are status filters; the active + project is a scope filter. +- One Plugin detail panel should compose description, capabilities, Token Point + summary, account/device state, current-project enablement, Agent assignments, and + plugin-specific settings while dispatching each mutation to its existing owner. +- Legacy `/plugin-marketplace`, `/my-plugins`, and `/project-plugins` routes should + redirect to deterministic `/plugins` query states during the navigation cutover. +- Official Data Service and Game Resource remain Plugin entries. Model Web Search + belongs in selected-model capabilities and should not appear as a Plugin card. +- A unified projection must tolerate partial backend failure: cached official data + and local Device Packages remain visible independently. +- The accepted direction is now frozen as Spec `ML-PLUGIN-NAV-001`, including the + pure Renderer projection Interface, source-qualified item identity, action matrix, + exact legacy redirects, partial-failure behavior, accessibility contract, file + ownership, tests, four implementation stages, and Definition of Done. + +## Verification + +- Concurrent Task Gate PASS after the peer owner confirmed disjoint scope. +- Planning Gate PASS after reading the required project memory, architecture, + domain rules, and current integrated state. +- Inspected current sidebar/router and all three Renderer Plugin pages on exact base + `e9875145b41a2cb1827de10d27a4fc6a352704ea`. +- Reviewed official OpenAI Plugins documentation for the single directory, + marketplace-source grouping, Installed projection, enable/disable behavior, and + new-session activation semantics. +- Re-ran the Concurrent and Planning Gates before writing the implementation Spec; + the AI Design peer still has disjoint file and semantic ownership. +- Re-inspected current Renderer stores, DTO projections, page actions, route/module + guards, sidebar entries, unit tests, E2E seams, and canonical Device Package/model + tool rules on the recorded base. +- No executable tests were run because this task makes no product changes; tests + would not alter the design conclusion. + +## Follow-ups + +- Implement `PN-01` through `PN-04` serially in an isolated client task after explicit + implementation authorization. +- Run fresh Standards and Spec review over the implementation range before promotion + to `main`. + +## Promotion Candidates + +- If accepted, promote the single-Plugin-surface navigation and state-as-filter + distinction into the architecture/module map during the later Integration Gate. +- Promote the explicit distinction between native selected-model tools and Plugin + entries into the user-facing glossary if future UI work makes that distinction + visible outside Code.