---
url: /wave-agent/specs/desktop/desktop-artifacts.md
description: CodeWave IDE 桌面端创作集：我发布的/共享给我的列表、自定义分类（本机存储）与批量归类、文件名搜索、分享范围与查看者版本、撤销发布、模板新建
---

# 功能规格说明：创作集（Artifacts 画廊）

**创建日期**：2026-10-20

**需求来源**：原型与需求文档 v1.19（`https://codechat.codewave.163.com/code/artifact/4ybm36o8s4`）

> **本期范围**：需求文档第一、二、三、六节（视图与分类 / 模板新建 / 列表搜索 / 用户场景识别）。
> **非目标（本期明确不做）**：第四节「评论」的全部交互（列表操作列的评论按钮、评论列表、逐级回复、AI 是否解决标记、一键拉取评论命令），第五节「订阅」（在对话里描述订阅、由 AI 完成订阅）。
> **页面归属**：创作集是**账号级**整页（内容与当前会话、工作目录、分屏数量无关），形态复用桌面端「插件市场」：保留左侧栏、替换 pane 行、自带返回；不做成右侧面板的 tab，桌面端独有（VS Code / JetBrains 不提供）。
> **数据面**：全部走 codechat 的 artifact 接口（`GET /api/frame/frames`、`POST /api/frame/{slug}/share`、`GET /api/frame/{slug}/versions`、`DELETE /api/frame/{slug}`）。服务端需要补齐的字段与接口集中列在「外部依赖与服务端补齐」一节，不在各用户故事里重复。

## 用户场景与测试

### 用户故事：创作集入口与「我发布的 / 共享给我的」列表（优先级：P1）

作为桌面端用户，我希望从左侧栏直接进入创作集、看到我发布的和别人分享给我的创作（文件名称、发布人、最近发布时间、分享给），以便不用记得链接就能找回并分享自己做过的页面。

**为什么是这个优先级**：创作集是 Artifact 能力的「收纳面」——发布出去的页面现在只有拿到链接才能回到，列表是所有其它操作（分享、撤销、归类、打开对话）的唯一入口。

**独立测试**：已登录账号下发布两个 artifact（其中一个分享给两位成员、一个仅自己）并由另一位成员分享一个过来，打开创作集，验证默认停在「默认视图 · 我发布的」，列齐全、按最近发布时间倒序、自有行的「分享给」显示真实范围（含具体账号）、共享行的「分享给」为 `—`；断开网络重进页面验证错误态与重试；未登录进入验证提示先登录。

**验收场景**：

1. **假设**应用以 desktop 宿主运行且左侧栏展开，**当**用户点击侧栏的「创作集」入口，**则**主区整页替换为创作集（侧栏保留、pane 行与当前对话不销毁），入口在页面打开期间显示选中态，点击页面左上角返回箭头回到进入前的会话视图。
2. **假设**创作集首次打开，**当**数据返回，**则**默认停在**默认视图**、分组为「我发布的」，列表列固定为：文件名称、发布人、最近发布时间、分享给、操作；按最近发布时间倒序（同时间按文件名稳定排序），不出现勾选列。
3. **假设**当前账号发布过 3 个 artifact 且被 2 位成员分享过，**当**切换到「共享给我的」分组，**则**只列出 `rel = shared` 的行；这类行的「发布人」显示分享者的账号、「分享给」显示 `—`（范围由对方管理），操作列只有「复制链接」「在浏览器中打开」（自己没有编辑权）。
4. **假设**「我发布的」里三条行的分享范围分别是仅自己、共享给整个企业（服务端 `shareMode = org`）、共享给三位指定成员，**当**列表渲染，**则**「分享给」列分别显示固定文案「仅自己」/「企业成员」/「团队成员」加最多两位账号（余下省略，完整名单见 `title` 与分享弹层，按服务端顺序排列）；企业成员档**不列任何人名、不带人数**（组织内成员会变，列名单无意义），仅自己与企业成员两档的 `title` 与文案一致；任何行都不显示空白单元格，共享给我的行同列显示 `—`。
5. **假设**列表为空（当前分组下没有任何创作），**当**页面渲染完成，**则**显示明确的空态文案（如「还没有创作」/「还没有人分享给你」），不显示空表格骨架、不报错。
6. **假设**会话未登录或登录态已失效（列表接口 401），**当**打开创作集，**则**页面显示「请先登录」的提示与登录入口，不显示空列表（把鉴权失败误呈现为「没有数据」是禁止的）；服务端回 403（已登录但无权访问）时**不得**冒充登录失效，按通用错误提示与「重试」呈现。
7. **假设**列表接口网络失败或返回 5xx，**当**请求失败，**则**页面显示错误提示与「重试」按钮，点击重试重新拉取；已渲染的数据不被清空。
8. **假设**创作集打开期间用户在本机发布了新 artifact 或撤销了发布，**当**回到创作集（或点击刷新），**则**列表反映最新状态；页面不做后台轮询，也不在打开期间自动变化。
9. **假设**创作集打开期间用户切换会话或新增分屏，**当**视图变化，**则**创作集保持打开、内容不重新拉取（切会话不重置页面状态）。

***

### 用户故事：自定义视图与分类管理（优先级：P1）

作为用户，我希望在「自定义视图」里自己建分类、把创作归类或移出分类，以便把一堆页面按项目/用途整理起来。

**为什么是这个优先级**：默认视图只有「我发布的 / 共享给我的」两档，创作一多就无法按主题找回；分类是需求文档里唯一的内容组织手段。

**独立测试**：新建两个分类，把两个文件批量归类到第一个分类，验证列表只剩该分类的行、「全部」仍能看到全部；重命名分类验证列表 Tab 与行归属同步；删除一个有文件的分类，验证必须先选迁移目标才能确认；用重名与超 20 字名称验证校验；重启应用验证分类与归类关系仍在，换一个账号登录验证看不到上一个账号的分类（分类本地存储、按账号隔离）。

**验收场景**：

1. **假设**用户在分类行右侧的分段控件点击「自定义视图」，**当**视图切换完成，**则**分类行变为「全部」+ 自定义分类 + 「新建分类」，列表列出全部创作（含共享给我的行）并新增勾选列；再点「默认视图」回到「我发布的 / 共享给我的」，已建分类与其归类关系保留（不因切视图丢失）。
2. **假设**用户在「新建分类」输入一个 ≤20 字、与现有分类不重名的名称并确认，**当**创建成功，**则**新分类出现在分类行末尾并自动选中，列表切到该分类（此时为空态）；名称为空、超过 20 字、与现有分类重名、或为保留名（「我发布的」「共享给我的」「全部」）时，确认按钮不可用或就地给出错误提示，不创建分类。
3. **假设**用户悬停某个自定义分类 Tab，**当**操作图标出现，**则**可「重命名」或「删除」；重命名沿用同一套名称校验，改名后 Tab 文案与其下文件的归属同步更新，文件名不变。
4. **假设**该分类下有 N 个文件，**当**用户确认删除分类，**则**必须先选择这些文件的迁移目标（另一分类，或「移出分类（未分类）」）；未选目标时确认被拒并给出提示。分类内无文件时只需一次确认即可删除。
5. **假设**自定义视图下用户勾选了 2 行，**当**勾选生效，**则**列表上方出现批量条「已选 2 项 / 归类到… / 取消选择」；点「归类到…」弹出单选目标（含「移出分类（未分类）」），确认后这 2 行的分类被更新、勾选清空、当前分类列表就地刷新。
6. **假设**用户点击表头的全选框，**当**当前可见行未全选，**则**选中当前可见的全部行；已全选时再点则清空。
7. **假设**用户切换分组 Tab、切换视图或修改搜索词，**当**可见行集合发生变化，**则**已勾选项一律清空（不允许保留看不见的勾选项）。
8. **假设**用户把某文件归入 A 分类，**当**查看「全部」列表，**则**该文件仍出现在「全部」中（「全部」不因归类而丢失行）；一个文件同一时刻只属于一个自定义分类。
9. **假设**用户建好分类并归类后关闭应用、再次打开创作集，**当**页面加载，**则**分类与其下的文件归属原样恢复（分类是本机持久化数据，不随应用重启丢失）；在另一台机器登录同一账号时**看不到**这些分类（本期不做跨机器同步）。
10. **假设**本机切换了登录账号（登出后换另一个账号登录），**当**打开创作集的自定义视图，**则**只显示当前账号自己的分类与归属，上一个账号的分类不出现、也不被覆盖或删除。
11. **假设**分类的本地存储读取失败（文件损坏或不可写），**当**打开自定义视图，**则**页面提示「分类数据暂不可用」并允许继续用默认视图浏览；本次会话内的新建/归类照常生效（内存态），不因存储故障阻断操作。

***

### 用户故事：按文件名搜索（优先级：P2）

作为用户，我希望在创作集列表里按文件名模糊搜索，范围就是当前所在的分类，以便在几十个创作里快速定位。

**为什么是这个优先级**：分类解决「按主题分组」，搜索解决「记得名字找不到」；两者互补但不影响基本的浏览能力。

**独立测试**：在「全部」与某个自定义分类下分别输入同一关键词，验证结果只落在当前分类范围内；输入大小写混合的关键词验证大小写不敏感；清空搜索验证恢复完整列表且勾选已清空。

**验收场景**：

1. **假设**列表已加载，**当**用户在标题行右侧的搜索框输入关键词，**则**列表即时按**文件名**做大小写不敏感的子串匹配过滤，匹配范围**仅限当前定位的分类/Tab**（切换 Tab 后同一关键词在新范围内重新过滤）。
2. **假设**搜索框有内容，**当**用户点击清空按钮，**则**输入框清空、列表恢复当前分类的全部行、焦点回到搜索框。
3. **假设**搜索词无匹配，**当**过滤结果为空，**则**显示「没有匹配的文件」类空态（与「该分类为空」区分文案），不显示错误。
4. **假设**搜索框已有内容，**当**用户切换分组 Tab 或视图，**则**搜索词保留、在新范围内重新过滤（不因切 Tab 静默清空关键词）。
5. **假设**搜索触发了可见行变化，**当**过滤生效，**则**批量勾选被清空（同分类场景 7），不会出现「勾选了但看不见」的行。

***

### 用户故事：行内复制链接与在浏览器中打开（优先级：P1）

作为用户，我希望每一行都能直接被复制分享链接或用系统浏览器打开，以便把创作发给同事或在大窗口里查看。

**为什么是这个优先级**：这是列表最基本的两个出口，缺了它们列表只能看不能用。

**独立测试**：点「复制链接」验证剪贴板得到 `{host}/code/artifact/{slug}` 形态的链接并有轻提示；点「在浏览器中打开」验证用系统默认浏览器打开同一链接（不在应用内新开面板）。

**验收场景**：

1. **假设**用户在任一行点击「复制链接」，**当**复制成功，**则**剪贴板内容为该创作的 artifact URL（`{server}/code/artifact/{slug}`），并出现「链接已复制」轻提示；剪贴板 API 不可用时回退到隐藏 textarea 复制路径，仍要有成功提示。
2. **假设**用户点击「在浏览器中打开」，**则**用系统默认浏览器打开该 URL（沿用桌面端 `openExternal` 语义），不在应用内新增 preview tab，也不替换当前 pane。
3. **假设**分享范围是「仅自己」，**当**用户复制链接发给同事，**则**对方打开会得到无权限页——页面不需要为此额外提示，链接语义保持与服务端一致（列表不臆断链接可用性）。
4. **假设**该行来自「共享给我的」，**当**用户复制链接或浏览器打开，**则**行为与自有行完全一致（共享行同样拿得到 URL）。

***

### 用户故事：分享设置与查看者版本（优先级：P1）

作为发布者，我希望在列表里直接改分享范围（仅自己 / 指定用户 / 企业成员）并选择查看者看到的版本（默认最新），以便不用打开网页外壳就能控制谁能看、看到哪一版。

**为什么是这个优先级**：分享是 artifact 的核心价值，页面外壳的 Share 菜单是唯一现状入口；列表里既然要能分享，就必须有这个编辑入口。

**独立测试**：对自有行打开分享弹层，切到「指定用户」搜索两位成员并勾选保存，验证服务端收到 `mode=users` 与这两个人的 `userIds`、列表该行「分享给」就地变成这两位、关闭后重开弹层回显这两位（已勾选）；再切回「仅自己」保存后验证该行「分享给」变为「仅自己」且重开弹层回显「仅自己」；选择固定版本保存后重新打开弹层验证版本回显。

**验收场景**：

1. **假设**用户点击自有行的「分享」，**当**弹层打开，**则**显示文件名、三档分享范围（仅自己 / 指定用户 / 企业成员，单选）、查看者版本下拉（默认「最新版本」），并**从服务端**回显当前状态（分享范围、已勾选的指定用户、已固定的版本）；列表中该行「分享给」列的展示与弹层回显始终一致。
2. **假设**用户选择「指定用户」，**当**范围生效，**则**出现成员搜索框与候选列表（按名字/邮箱模糊检索本企业 active 成员，输入即过滤）；勾选/取消勾选即时反映在已选集合里，未勾选任何成员时点击「保存」被拒并就地提示「请至少选择 1 位用户」，不发请求。
3. **假设**成员检索接口失败（网络错误或 5xx），**当**用户输入关键词，**则**候选区显示错误与重试，已勾选内容保留；「仅自己」「企业成员」两档不受影响、仍可保存。
4. **假设**保存成功（任一档范围），**当**弹层关闭，**则**轻提示「分享设置已保存」，且该行「分享给」列就地更新为新的范围（仅自己 / 企业成员 / 团队成员+账号），不需要重新拉整个列表；请求失败时弹层保持打开并显示错误，列表数据不变。
5. **假设**用户把查看者版本从「最新版本」改成某个固定版本，**当**保存成功，**则**该创作对读者固定展示该版本（服务端 `pinnedVersion`），发布者自己仍看到最新版本；重新打开弹层时版本回显为已固定的那一版。
6. **假设**已分享的某成员已离职/被禁用，**当**重新打开弹层，**则**该账号仍按服务端返回的邮箱回显在已选列表里（标为不可选、仅展示），保存时以服务端校验结果为准（400 就地提示），不静默把人从列表里丢掉。
7. **假设**该行来自「共享给我的」，**当**查看操作列，**则**没有「分享」入口（分享范围只有拥有者能改），也不通过其它路径暴露编辑能力。
8. **假设**分享保存成功，**当**返回列表，**则**列表的排序、搜索词、勾选状态保持不变（分享是单行数据的写动作，不重排、不清空勾选）。

***

### 用户故事：撤销发布（优先级：P1）

作为发布者，我希望撤销某个创作的发布，使其立刻从所有创作集列表里消失、分享链接失效，但本地原文件不受影响，以便发错了或不想再对外展示时收回。

**为什么是这个优先级**：撤销是分享的相反操作，没有它发出去就收不回；需求文档明确「不删除原文件」，与「删除文件」是两件事。

**独立测试**：对有文件的分类…（不适用）——对自有行执行撤销发布，验证二次确认文案含文件名与「本地原文件不会被删除」，确认后该行从列表消失、原链接 404、本地文件仍在；共享行验证没有该入口。

**验收场景**：

1. **假设**用户在自有行的「分享」弹层里点击「撤销发布」（弹层页脚最左、默认次级色），**当**确认弹层出现，**则**文案说明「撤销这条发布记录与分享链接后，该文件不再在创作集列表中展示；本地原文件不会被删除。此操作不可撤销」，主按钮为危险色「撤销发布」，取消则回到分享弹层。
2. **假设**用户确认撤销，**当**请求成功（服务端删除该 slug 的发布记录），**则**该行从当前列表消失、轻提示「已撤销「{文件名}」的发布」，此后该 artifact URL 打开即无权限/不存在。
3. **假设**该创作正被应用内某个会话引用（如某个 pane 打开过它的预览），**当**撤销成功，**则**不主动关闭既有的预览/文件面板，只是后续访问该 URL 失败（按各面板既有失败语义展示，不弹出与新状态矛盾的成功提示）。
4. **假设**撤销请求失败（网络错误或服务端 5xx），**当**失败返回，**则**弹层保持打开并显示错误，列表不提前删除该行。
5. **假设**撤销的是共享给我的行，**当**查看可用操作，**则**没有撤销入口（只有拥有者能撤销自己的发布）。

***

### 用户故事：模板新建直达对话页（优先级：P1）

作为用户，我希望在创作集点「新建」后选一个模板（高保真原型 / 网页小游戏 / 效率工具 / 运营活动页 / 文档和模板 / 演示与教学 / 从零开始），直接回到对话页且输入框已预填该模板的提示词，以便不用自己想提示词就能开工；预填的提示词还要把「先本地出稿 → 每轮生成结束都让我在『继续调整』与『发布为创作（artifact）』之间选」这个节奏写进去，避免 AI 一生成完就直接发布，也避免它自己判断「已经定稿」。

**为什么是这个优先级**：创作集如果只有「看已有的」没有「做新的」，用户要回到空对话自己描述；模板把「做一个页面」的门槛降到一次点击。

**独立测试**：从创作集点「新建」，验证 7 张类型卡片与各自描述；点「高保真原型」验证回到当前会话、输入框已预填该模板提示词（正文 + 统一的发布节奏收尾）且光标在末尾（不新开会话、不切换会话）；发送后验证用户消息、开场句与 3 条追问、生成中的右侧预览骨架；返回箭头验证回到类型选择页、再返回到创作集列表。

**验收场景**：

1. **假设**用户在创作集点击右上角「新建」，**当**面板打开，**则**显示 7 张类型卡片（高保真原型、网页小游戏、效率工具、运营活动页、文档和模板、演示与教学、从零开始），每张含图标、名称与一句描述；顶部有返回箭头回到创作集列表。
2. **假设**用户在一个已打开会话的情况下点击某张类型卡片，**当**回到对话页，**则**提示词预填进**当前选中会话**的输入框（不新开会话、不切换会话），文本可编辑、光标落在末尾，不直接发送；预填在点击后立即完成，不等任何新会话初始化。
3. **假设**用户点击任意一张类型卡片，**当**提示词预填进输入框，**则**其末尾固定带同一句收尾口径：先在本地做好 HTML/Markdown，**每轮生成结束后**都以「继续调整」与「发布为创作（artifact）」两个选项询问用户（用产品中文名，不写裸英文 Artifact）——七张卡一致，不因模板类型不同而有例外；用户选「继续调整」则继续按意见修改、不发布，选「发布为创作（artifact）」才发布；AI 不得自己判断已定稿而跳过这次询问，也不得在同一轮里生成完就直接发布。
4. **假设**用户在预填的提示词里填写占位内容后发送，**当**消息发出，**则**进入常规对话流：用户消息、AI 开场句与 3 条追问、右侧预览面板随生成出现（生成中为空态骨架）；此时创作集页面已让位给该会话的对话页。
5. **假设**用户在类型选择页点击返回箭头，**当**返回生效，**则**回到创作集列表（保持进入时的视图/分组/搜索词）；从对话页返回则回到类型选择页（两级返回，不跳过中间层）。
6. **假设**当前对话是一条刚打开的「新对话」（未发过消息），**当**点类型卡片，**则**提示词预填进这条空对话的输入框，不会因此再补开一条会话。
7. **假设**用户当前未选择过任何工作目录（新装、无最近目录），**当**点类型卡片，**则**提示词同样预填进当前输入框；点发送时按既有「请先选择工作目录」导流去选目录，已预填的内容不丢。

***

### 用户故事：对话中的 artifact 卡片（优先级：P2）

作为用户，当 AI 把页面发布到创作集后，我希望对话里出现一张卡片、点「在浏览器中打开」就能看这个 artifact，以便不用从回复正文里翻链接。

**为什么是这个优先级**：发布命令的原始结果是一段带 URL 的文本，用户要自己找、自己复制；卡片把「产物」和「查看」接起来，省掉从消息正文里翻链接这一步。

**独立测试**：让 AI 发布一次成功，验证对话里出现卡片（标题、版本与一个「在浏览器中打开」按钮）且工具行仍在、不显示原始结果文本；点击验证打开该 artifact 的页面 URL；失败与非发布动作验证不出卡片。

**验收场景**：

1. **假设**`Artifact` 工具以 `publish` 动作发布成功，**当**该轮结束，**则**该工具行（`Artifact` + 参数摘要）照常显示，卡片挂在其下方（与 Bash 工具「头部一行 + 内容在下面」同形态），卡片上为标题、`已发布 · v{版本}` 副信息与一个「在浏览器中打开」按钮；只顶掉该工具块的原始结果文本。
2. **假设**用户点击卡片上的「在浏览器中打开」，**当**动作生效，**则**用系统默认浏览器打开该 artifact 的页面 URL。
3. **假设**工具调用失败，或动作不是发布（`list` / `read` / 资源 / 评论），**当**该轮结束，**则**保持既有文本结果渲染，不出卡片。
4. **假设**卡片缺标题或版本（宿主拿到的附加信息不完整），**当**渲染卡片，**则**标题回落为 URL、副信息只写「已发布」，不出现空白卡片。

***

### 用户故事：自然语言识别「新建/更新创作集」（优先级：P3）

作为用户，我希望在对话里说「更新创作集」「新建创作集」时 AI 能理解为发布/更新 artifact，而不必说「发布成网页」，以便用产品里的名字指代这个动作。

**为什么是这个优先级**：这是提示词层面的对齐，可用性增强；现有的「发布/分享/做成网页/给链接」语义已能覆盖大多数表达。

**独立测试**：断言 `Artifact` 工具的 description 覆盖「创作集」字样，并有针对「新建创作集 → 发布新 artifact」「更新创作集 → 带 url 重新发布」的用例断言工具被调用。

**验收场景**：

1. **假设**用户说「把这份原型**新建**到创作集」，**当**模型据此调用 `Artifact` 工具，**则**按发布新 artifact 处理（无 `url`），结果出现在创作集「我发布的」列表。
2. **假设**用户说「**更新**创作集里的 codewave-pricing 页面」，**当**模型据此定位到该 artifact 并调用 `Artifact` 工具（带 `url`），**则**按重新发布处理、产生新版本，创作集里仍是同一行、最近发布时间刷新。
3. **假设**用户只是提到「创作集」但语境是浏览（如「我的创作集里有什么」），**当**模型处理，**则**走 `list` 动作而不是发布（工具 description 需区分「看列表」与「发布/更新」两类意图）。
4. **假设**`enableArtifact` 关闭或未登录，**当**用户这样描述，**则**与现状一致：工具不可用，模型如实说明能力不可用，不假装已经发布。

***

## 非功能需求

* **页面归属与形态**：创作集是 desktop 独有整页，账号级（与 session/workdir/pane 无关）；打开/关闭不销毁任何 pane，不改变当前会话选中项；从会话视图返回时恢复进入前的视图状态。
* **数据来源单一**：列表、分享、版本、撤销全部走 codechat artifact 接口，webview 不直连网络——由桌面端主进程带登录 token 请求，再经 host→webview 消息下发（沿用既有宿主命令/响应通道与 `ReplyAttribution` 归属登记）。
* **写动作的口径**：改分享、撤销发布是账号数据写动作，由桌面端主进程带 token 执行，失败时给可操作错误，不静默吞掉；**分类与归类只写本机存储**（桌面端主进程的 userData，按账号隔离），不产生网络请求，写失败只影响本机数据且要提示。
* **刷新时机**：只在「打开页面 / 手动刷新 / 本页完成的写动作之后 / 登录态翻转（登录成功、换账号、登出）」拉取列表；不做轮询、不做后台定时刷新；切 Tab/视图/搜索都是本地过滤，不重复请求。列表与分类都是账号级数据，登录成功或换账号后必须重取，不能停在打开那一刻的结果上。
* **性能**：列表一次拉取（服务端固定页大小），首屏 200 行内无虚拟滚动需求；搜索过滤在客户端完成（文件名子串匹配），输入即时响应（不做 debounce 卡顿）；模板提示词点击即预填，不依赖（也不触发）新会话初始化。
* **状态一致性**：勾选、搜索词、视图/分组是**页面内**状态，关闭页面即丢弃（下次打开回到默认视图）；**分类与文件归属是本机持久化数据**（按账号隔离、跨重启保留），跨机器/跨设备不同步——同一账号在另一台机器上看到的是空分类。
* **鉴权**：所有请求带当前登录 token；**401 一律呈现为「需要登录」、403 呈现为「无权限」**，两者都不呈现为空列表（403 不等于登录失效，不弹登录入口）。
* **平台**：仅 macOS/Windows/Linux 桌面端；VS Code 与 JetBrains 插件不提供创作集页面（沿用「发布走 `/artifact` 或自然语言」现状）。
* **可测性**：列表、搜索、分类、分享、撤销、模板新建都要有对应单测/组件测试（锚定 testid 与语义，不锚定图标字体类名）；列表与分享的服务端调用在测试里 mock，不真实联网。

## 外部依赖与服务端补齐

> **本期要 codechat 服务端补齐两处**：①「普通成员可用的成员检索」接口（见第 2 条）；② 列表接口 `/api/frame/frames` 每行补发布人与分享范围字段（见第 1 条）。列表、分享写入、版本、撤销用的都是 codechat 现有接口，分类存本机（第 5 条）、不涉及服务端。
>
> 两处服务端补齐已随 codechat `MR !218` 落地（`GET /api/frame/frames` 三字段 + `GET /api/enterprise/members/search`），最终形状见第 1、2 条末尾的「落地形状」。

1. **列表字段（服务端需新增）**：`GET /api/frame/frames` 现在只返回 `{ slug, title, favicon, rel, updatedAt }`，没有发布人与分享范围/分享账号。列表要显示「发布人」「分享给」两列，服务端需按行补：发布者账号（`ownerEmail`，有显示名则一并给）、分享范围（`shareMode`：`owner` / `users` / `org`）、指定成员账号列表（`sharedEmails`，与 `/view` 的 `sharedUsers` 同源，一条 join 即可）；`rel = shared` 的行同样返回 `ownerEmail`，`shareMode`/`sharedEmails` 可为空（前端该行「分享给」显示 `—`）。字段缺失时前端按「未知」降级（不报错、不误显「仅自己」）。

   **落地形状（codechat `MR !218`）**：每行在原有五个字段之后追加这三项；`ownerEmail` 查不到时给空串 `""`，`sharedEmails` 在非 `users` 模式恒为 `[]`（前端把空串/空数组都当作「缺省」）；分页、排序与 `rel` 计算未变，取数固定两次查询。

2. **成员检索接口（服务端需新增）**：`GET /enterprise/members` 现在要企业管理员（`backend-enterprise/src/routes/enterprise.ts:33` 的 `requireEnterpriseAdmin`），普通成员无法用于选人；现有 artifact 网页外壳只能靠「用户邮箱（逗号分隔）」文本框兜底（`routes/artifacts.ts:1848`、`:1999`）。本期需 codechat 提供一个**普通成员可用**的成员检索：按 `search`（名字/邮箱模糊）与分页返回本企业 active 成员的 `{ userId, email }`（有显示名则一并返回），供弹层候选用。对齐 claude.ai 的做法：它用 `GET /api/organizations/{org}/members_v2?search=<kw>&types[]=member` 同时服务管理页与分享选人，并用 `emails[]` 批量反查已选邮箱对应的账号。
   **落地形状（codechat `MR !218`）**：`GET /api/enterprise/members/search?search=&limit=`，不挂 `requireEnterpriseAdmin`，返回 `{ members: [{ userId, email }] }`（`userId` 可直接回填分享接口）；`limit` 缺省 20、上限 50，非法值按缺省处理，超长 `search` 返回 400。

3. **分享写入**：`POST /api/frame/{slug}/share` 已支持 `{ mode, userIds | emails, pinnedVersion }`（`routes/artifacts.ts:718-790`），指定用户以检索结果里的 `userIds` 提交即可，无需改协议。

4. **分享弹层回显**：`GET /api/frame/{slug}/view`（拥有者专用）已返回 `perm.mode`、`pinnedVersion`、`sharedUsers`（`{userId, email}`）、`versions`，正是弹层需要的三项回显数据（范围 / 已选成员 / 版本下拉），无需服务端改动。

5. **分类存储（已定：本地）**：服务端不实现分类。分类与「文件 → 分类」归属由桌面端主进程写本机 userData（一个文件最多一个分类，按 slug 记），按当前登录账号隔离命名空间；服务端只需保证 `slug` 稳定（重发布不换 slug，现状即如此）。代价：换机器看不到分类，本期接受。

6. **撤销发布**：`DELETE /api/frame/{slug}` 已存在且语义吻合（删除该 slug 的发布记录与分享，不影响本地文件）；前端只需二次确认与列表就地更新。

7. **本地 slug 与分类的清理**：某 slug 被撤销或不再可见时，本机那条归类记录**保留**（不主动删）——记录本身不影响列表渲染；分类里引用了已消失的 slug 时按「不可见即不显示」处理。

## 边界情况

* **登录态在页面打开期间失效**：下一次请求返回 401 时整页切为「请先登录」，已渲染数据保留但不可再操作；401 之外的失败（含 403、5xx）只换错误提示，不切整页。
* **多个窗口/多台机器同时改同一条分享**：以最后一次保存为准（服务端覆盖写）；本地不缓存分享状态，下次打开分享弹层按服务端当前状态回显。
* **分类被删除时正有会话在显示它的文件**：只影响创作集里的归属，不影响文件与预览。
* **分类是本机数据**：同一账号在本机开两个窗口时，写的是同一份存储、后写覆盖先写，另一窗口在下次打开页面时读到最新；不做跨窗口实时同步，也不与服务端对账。
* **未登录**：创作集本身打不开（列表需要登录），因此分类既不加载也不写入；登录后按新账号的命名空间重新加载。
* **同一个 artifact 在「我发布的」与某分类下**：默认视图与自定义视图是两个视图维度，行归属由 `rel` 与 `cat` 分别决定，不互相屏蔽。
* **文件名过长**：列表按列宽省略并以 `title` 提供全文；文件名列吸收剩余宽度，不截断到不可辨认。
* **列表接口在共享行也返回分享范围**：`sharedEmails` 目前对所有行都给（含 `rel = shared`），这是服务端待收窄的一处跨企业信息面；前端在共享行的「分享给」固定显示 `—`，页面不会把他人名单呈现出来，收窄由服务端决定（例如非拥有者只给人数）。
* **「分享给」列内容过长**：指定成员多于 2 位时只显示前两位加省略号，完整名单放 `title` 与分享弹层里；不因长文本撑宽列或挤压文件名列。
* **共享行被对方撤销发布**：下次拉取时该行消失；若用户正在操作该行（弹层已打开），保存请求收到 404 时提示「该创作已被撤销」并关闭弹层。
* **分类名称含空白与 Emoji**：按字面校验长度（≤20 字符）与重名（精确匹配），不做归一化之外的额外处理；纯空白名称视为空。
* **成员检索的边界**：关键词为空时按首批成员列出（或提示「输入关键字搜索」）；无匹配显示空态文案；同名成员以邮箱区分；已分享但不在当前搜索结果里的成员仍留在「已选」区，不因过滤消失。
* **侧栏处于收起状态进入创作集**：页面照常打开（创作集页面自带返回箭头，不依赖侧栏），展开侧栏后入口选中态随之出现。

## 非目标（明确排除）

* **评论**：列表操作列的「评论」按钮、评论列表与发表、逐级回复、`@` 引用成员、AI 是否解决标记、一键拉取评论命令——整块本期不做。
* **订阅**：对话中描述订阅、由 AI 完成订阅、订阅通知——本期不做。
* **预览面板的「发布为 Artifact」按钮**：发布只经 `Artifact` 工具（`/artifact` 命令或自然语言）发生，桌面端不在预览工具条上提供第二条发布入口（2026-10-22 与产品确认取消，原「预览窗口发布为 artifact」故事已删除）。
* **VS Code / JetBrains 的创作集页面**：仅在桌面端提供。
* **创作集的其它入口**：不做侧栏以外的入口（如命令面板、深链、设置页入口）；深链打开某个创作沿用既有 artifact URL 行为。
* **列表分页/无限滚动**：首版按服务端固定页大小一次拉取；超过一页再按需扩展。
* **版本管理 UI**：不在创作集里提供版本列表/回滚，只提供「查看者版本」这一项选择。
* **「发布人」列的排序与搜索**：只展示，不参与排序、不被文件名搜索命中（搜索口径仅文件名）。
* **批量分享、批量撤销**：批量只支持归类；分享与撤销逐条操作。
