Merge branch 'master' into fix/copy-pasted-on-canva
Project CI / Repository checks (pull_request) Successful in 59s
Project CI / Frontend tests (pull_request) Successful in 3m29s
Project CI / Backend tests (pull_request) Successful in 3m56s
Project CI / Native shell tests (pull_request) Successful in 12m46s

This commit is contained in:
2026-08-03 19:11:03 +08:00
54 changed files with 4030 additions and 453 deletions
@@ -3638,6 +3638,8 @@
- 决策:`PlatformSegmentedTabs` 继续承接首页 / 结果页剩余的横向 rail 与二选一切换;`RpgEntryHomeView.tsx` 的 discover channel bar、移动端 / 桌面端分类 chip rail,`CustomWorldEntityCatalog.tsx` 的 `RESULT_TABS` sticky rail,以及 `PlatformProfileRechargeModal.tsx` 的“泥点充值 / 会员卡”切换条已迁移。像 `CustomWorldEntityCatalog` 这种“标题 + count”内容直接走 `ReactNode label`;首页 / 创作入口 / 作品架 / 个人中心里稳定复用的频道下划线、创作 pill rail、二列 option segment 皮肤走 `PlatformSegmentedTabPresets`。同类切换在测试里应优先按 `role="tablist" / "tab"` 查询,而不是把它们继续当普通 button。
- 决策:简单泥点确认流的开关状态机统一收口到 `src/components/common/useMudPointConfirmController.ts`,只暴露 `open / requestOpen / close / confirm`,不持有点数、标题、描述或禁用态等业务字段;`PuzzleCreationWorkspace.tsx`、`Match3DCreationWorkspace.tsx` 与 `Match3DResultView.tsx` 的两个批量素材面板已接入。`PuzzleResultView.tsx` 和 `RpgCreationRoleAssetStudioModalImpl.tsx` 这类节奏不同或携带 pending payload 的场景继续保留本地状态机,避免把简单 hook 扩成泛型动作路由器。
- 决策:标准平台 modal header 的关闭入口继续统一到 `PlatformModalCloseButton variant="platformIcon"`;结果页 / 工具页重复的白底 portal 弹窗壳层收口到 `src/components/common/PlatformToolModalShell.tsx`,由它统一承接平台主题 overlay、白底 remap panel、标准 header/body/footer spacing、关闭按钮和遮罩 / Escape 关闭策略。`PuzzleResultView.tsx` 的关卡详情 / 发布弹窗、`Match3DResultView.tsx` 的封面 / 发布工具弹窗,以及 `PuzzleHistoryAssetPickerDialog.tsx` 的历史素材弹窗已迁移;`UnifiedModal` 新增 `ariaLabel` 支持可见标题动态、可访问名称固定的场景。像素风 runtime、drawer collapse、玩法规则面板和运行态 overlay 不跟这条线混收,继续保留局部 close 语义。
- 决策:平台 portal 主题恢复下沉到 `UnifiedModal`;`portal=true` 默认从 `AuthUiContext` 注入当前 light / dark 主题,已显式给出主题的调用保留原选择,无 Provider 回退 light。`portalTheme="none"` 只用于全黑图片预览等完全自绘弹层,`portal=false` 仍使用原 DOM 主题作用域。图片信息、修改图片与画布快捷键弹窗在完整支持暗色样式前显式使用 `portalTheme="light"`,不将固定白底面板与暗色文本变量混用。共享业务壳不再重复读取 AuthUi 只为 portal 补 class,画布私有变量则继续通过 `ImageCanvasEditorPortal` 桥接。已退役玩法不因该底层修复恢复入口或维护范围。
- 决策:`PlatformUtilityInfoModal` 未显式传主题时必须沿用 `UnifiedModal` 的 auto 主题,不在共享壳里默认锁定 light。`PublishShareModal` 跟随当前 light / dark 主题;`PlatformReportDialog` 因包含二维码 / 扫码展示区,显式固定 light 以保证白底对比度和识别率。
- 决策:平台入口的创作前置泥点阻断提示只在 `platform-entry` 局部抽成 `src/components/platform-entry/PlatformDraftGenerationPointNoticeDialog.tsx`,并使用 `DraftGenerationPointNotice` union(`insufficient-points` / `balance-load-failed`)承接业务真相;不要在 `common/` 再抽一个泛化 `BlockingNoticeDialog`,否则会把 `PlatformAcknowledgeStatusDialog` 的样式透传再包装一层而不缩小调用面。
- 决策:`PlatformAsyncStatePanel` 从 profile modal 扩展到作品架类白底 panel;`CustomWorldCreationHub.tsx` 的作品架主体现在也统一走 `loadingState / emptyState / children` 三段 slot,但 error + 重试继续留在业务层外侧,不把共享组件扩成“banner + retry + content”全能状态机。后续白底作品架或列表 panel 若只是互斥的 `loading / empty / content`,优先直接复用这套骨架。
- 决策:`CopyFeedbackButton.tsx` 的 `actionSurface` 分支继续收口到 `PlatformActionButton`,`pill` 分支继续保留 `PlatformPillBadge` 风格;复制反馈按钮不再直接调用 `getPlatformActionButtonClassName` 手拼平台按钮基础 chrome。后续同类“复制状态机 + 平台动作按钮”组合优先直接复用 `CopyFeedbackButton`,不要在业务页重新混写图标、文案、aria 和动作按钮 class。
@@ -5926,7 +5928,9 @@
## 2026-07-31 External v1 生成统一异步并提供托管 MCP 与完整 Skill 包
- 异步契约:External v1 的图片生成、图片编辑、图标图集、UI 素材提取、角色动画、视频、音效和背景音乐八类 POST 固定持久化入 `external_generation_job` 并返回 HTTP `202 + operationId/statusUrl/pollAfterMs`;不受站内 `GENARRATIVE_EXTERNAL_GENERATION_MODE=inline` 影响。每次逻辑生成必须携带稳定 `Idempotency-Key`,网络结果未知或调用方轮询超时时复用原键和原 operationId,不得换键重提。
- 发布窗口兼容:AI 游戏创作桌面客户端严格按 HTTP 状态分流生成首响应;旧服务 `200` 只作为已经完成且含可下载媒体的同步结果消费,旧图集允许从顶层 `spritesheetImageSrc` 换签下载且无效值不得遮蔽可用 `objectKey`;新服务 `202` 必须取得 `operationId` 后轮询,轮询间隔按 OpenAPI 限制在 `250..=5000ms`,其他 2xx 失败关闭。Runtime 在 POST 前原子持久化精确请求体、请求 SHA-256 与稳定幂等键,`202` 后先原子追加 `operationId` 并回读一致再查询;重启时 `accepted` 账本只恢复 GET,`prepared` 表示提交结果未知并禁止自动 POST。生成 POST 使用独立三十五分钟等待预算且不自动重提;game-chat 仍受父 run 五分钟总截止约束,但截止时若 `canvas.asset_generate` 已进入 executing,客户端与预览照常退出,Runtime 保留 pending action、provider batch、生成账本与 `needs-reconciliation`。响应丢失、旧 `200` 结果损坏、`202` 缺 operationId、轮询超时、状态损坏、透明派生失败或外部完成后的本地提交失败统一投影为不可自动重生的对账边界。非阻断 general warning 继续消费结果并与 `sliceWarning` 分别展示。权威 External v1 OpenAPI 仍只声明新异步 `202`,不把部署过渡兼容公开成正式双协议。
- 查询与结果:新增 owner-safe `GET /api/external/v1/generations/{operationId}`。`queued/running` 返回 phase/progress,`completed` 返回 compact 稳定 artifact 引用,`failed` 返回脱敏错误,跨 owner 按不存在处理。compact result 允许 objectKey、resource/asset ID、assetObjectId、尺寸、媒体类型、taskId 和告警;禁止完整 project/canvas、Data URL、Blob URL、临时 signed URL、内部 provider 原文和 lease/fencing 控制字段。
- 客户端 durable 查询约束:私有生成账本同时绑定 base URL/API Key 配置指纹,指纹不一致不查询旧 operation。旧 `200` 兼容结果只持久恢复允许字段和安全媒体引用。operation 明确 failed 的账本保留到 pending observation 和 Provider batch 终态落盘后再清理。生成提交只有契约明确的 `400 / 401 / 403` 可判定为入队前拒绝并清理 prepared 账本;其它非成功状态一律保留账本进入对账。账本路径解析、扫描和删除逐级拒绝符号链接,非法控制路径失败关闭。
- MCP:新增托管 `/api/external/v1/mcp`,使用现有 External API Key Bearer 鉴权和无协议 session 的 Streamable HTTP JSON direct 模式。MCP tools 从同一 OpenAPI operation 形成并复用 External REST router;生成 tool 显式要求 `idempotencyKey`,另有统一任务查询 tool。MCP resources 提供使用说明、OpenAPI、Skill 入口 `SKILL.md` 和 `references/capability-routing.md`、`references/api-operations.md`、`references/authentication-and-safety.md`、`references/requests-and-outputs.md` 四篇稳定 reference;日后新增 reference 时必须同步新增独立 resource。MCP Agent 直接调用托管 tools,不安装 CLI,也不将脚本、测试或 workflow 暴露为 MCP resources。禁止开放内部 SpacetimeDB MCP、worker procedure、controller 或队列控制面。
- Agent 发现:新增公开 `agent-integration.json`、`skill/SKILL.md` 和 `skill.zip`。manifest 同时声明 MCP、OpenAPI、完整 Skill archive、SHA-256 和包内清单;archive 必须包含 `SKILL.md`、上述四篇 references、stdlib Python helper 和 `agents/openai.yaml` 七个声明文件,不能只提供 OpenAPI JSON,也不能包含 API Key、本机路径或个人配置。完整 `skill.zip` 只供不支持 MCP 或需要本地文件上传编排的 Agent 使用,不作为 MCP resource。
- 兼容边界:这是基于「截至 2026-07-31 尚无外部第三方存量调用方」接受的 v1 原地 breaking change;一旦出现外部活跃 Key、公开契约或联调方,后续破坏性变更必须保留兼容、经过弃用期或升级 `/api/external/v2`。
@@ -3587,8 +3587,8 @@
- 现象:项目库点击“重命名”后,标题、输入框和按钮仍显示,但弹窗面板及遮罩背景变透明,看起来像“改名界面的背景没了”。
- 原因:`UnifiedModal` 默认 portal 到 `document.body`;若业务入口只在页面内层继承 `platform-theme`,portal 根节点不会继承该容器的 CSS 变量。此时 `.platform-modal-shell` 的 `background: var(--platform-modal-fill)` 和 `.platform-overlay` 的背景声明都会失效。
- 处理:平台白底工具弹窗优先复用 `PlatformToolModalShell`,由共享壳读取当前 `AuthUiContext.platformTheme`,并把 `platform-theme platform-theme--<light|dark>` 挂到 portal overlay;不要用硬编码白底掩盖主题变量缺失。必须直接使用 `UnifiedModal` 的特殊场景,也要在 `overlayClassName` 显式传递当前平台主题。
- 验证:在 light / dark 主题下打开 portal 弹窗,断言 dialog 的 overlay 携带对应主题类,并在真实浏览器核对 panel 与遮罩的 computed background 均非透明。
- 处理:`UnifiedModal` 在 `portal=true` 时默认把 `AuthUiContext.platformTheme` 注入 overlay,共享白底弹窗和直接调用都不应再手工拼接主题 class。完全自绘的黑底预览显式使用 `portalTheme="none"`;已明确固定主题的弹窗使用 `light` / `dark`;局部 CSS 仍固定白底且未完成暗色样式的弹窗,必须暂时显式固定 `light`,否则会出现白底白字或深浅样式混杂;`portal=false` 继续依赖原 DOM 主题作用域。裸 `createPortal` 若使用平台或画布 CSS 变量,必须改用相应的主题 portal 壳,不要用硬编码白底掩盖主题变量缺失。
- 验证:在真实 `AuthUiContext.platformTheme="dark"` Provider 下打开 portal 弹窗,断言 auto 弹窗的 overlay 携带暗色主题类,固定浅色弹窗只携带浅色主题类,panel 与遮罩的 computed background 均非透明;同时断言 `portalTheme="none"` 的黑底预览不被平台 remap。
- 关联:`src/components/project/ProjectGalleryView.tsx`、`src/components/common/PlatformToolModalShell.tsx`、`src/components/common/UnifiedModal.tsx`。
## 自主试玩失败后的修复责任不能同时落给总控和专业 Agent
@@ -4035,7 +4035,9 @@
- 现象:生成提交发生客户端超时、连接中断或响应丢失后,调用方创建新的 `Idempotency-Key` 再提交一次;原任务其实已经入队,最终造成重复生成、重复扣费和重复画布 / 素材库写入。
- 原因:把“客户端没有收到结果”误判为“服务端没有受理”,又没有持久保留逻辑请求的幂等键和服务端返回的 `operationId`。托管 MCP 若绕过 External REST router 直接调用 worker 或 SpacetimeDB,也会形成第二套去重与状态语义。
- 处理:一次逻辑生成只分配一个稳定幂等键;传输重试必须使用完全相同的请求体和原键。收到 `operationId` 后只查询 `/api/external/v1/generations/{operationId}`,调用方轮询超时不改变服务端任务状态。结果未知且尚未拿到 operationId 时也只用原键重试提交。MCP 生成工具必须把 `idempotencyKey` 映射到同一 REST header,并复用同一 External router、owner 和任务账本。
- 处理:一次逻辑生成只分配一个稳定幂等键。桌面 Runtime 在 POST 前先把精确请求体、SHA-256 和幂等键原子写入私有生成账本并回读一致;收到 `202 + operationId` 后先把账本升级为 `accepted` 再轮询。重启时 `accepted` 只恢复 GET,`prepared`、响应丢失、`202` 缺 operationId、轮询超时和状态损坏都进入 `needs-reconciliation`,绝不自动 POST。game-chat 五分钟硬截止可以结束本轮、关闭预览和客户端,但 executing 的 `canvas.asset_generate` 必须保留 pending action、provider batch 与生成账本;旧 `200` 图集的 `spritesheetResource` 允许为空,此时只在顶层 `spritesheetImageSrc` 是有效下载引用时优先使用,否则回退可用 `objectKey`。`postprocess-failed-source-preserved` 进入不可自动重生的对账边界;其它 non-blocking warning 继续消费成功结果并单独展示。旧 `200` 兼容不改变权威 External v1 的异步契约。MCP 生成工具必须把 `idempotencyKey` 映射到同一 REST header,并复用同一 External router、owner 和任务账本。
- 补充:不能把“accepted 分支里没有生成 POST”误当成 GET-only 恢复。若读取账本前仍重做项目/素材目录准备、输出路径预检或请求正文构造,恢复仍可能创建远端资源或在查询 operation 前失败。恢复必须直接使用 durable snapshot;清理必须最后删除 pending 身份锚点,活动 orphan 不得自动删除。完整恢复 future 还要在默认 Tokio worker 栈下验证,不能靠测试环境调大 `RUST_MIN_STACK` 掩盖栈溢出。
- 加固:durable snapshot 必须绑定不含明文凭据的 base URL/API Key 配置指纹,配置漂移时连 GET 也必须阻断。accepted operation 明确 failed 也不能在 observation 持久化前删账本。旧 `200` durable result 只保留允许字段与安全 objectKey/相对路径,签名 URL、query/fragment 和未知字段不落盘。提交只有契约明确的 `400 / 401 / 403` 可证明未入队并清理 prepared 账本;超时、冲突、限流、网关错误及其它意外状态均保留账本进入对账。账本根目录、扫描和删除必须通过受控路径解析逐级拒绝符号链接,不能让项目内链接把清理目标指向项目外。
- 验证:覆盖“服务端已入队但提交响应丢失”后原键重试仍返回同一 operation、换 owner 不可见、查询最终只出现一份 completed result 和一次计费 / 写回;MCP 与 REST 对同一 owner、同一请求和同一键必须命中同一 operation。
- 关联:`server-rs/crates/api-server/src/external_generation.rs`、`server-rs/crates/api-server/src/external_mcp.rs`、`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
@@ -283,6 +283,7 @@
19.3.53. 认证入口白底弹窗壳层收口到 `src/components/auth/PlatformAuthModalShell.tsx`;该 Module 只承接平台主题 overlay、`platform-auth-card`、标准标题栏、关闭按钮、点击遮罩关闭和禁用 Escape 的认证弹窗策略,不持有短信 / 密码登录、重置密码、邀请码规范化、法律协议或错误状态。`LoginScreen.tsx` 与 `RegistrationInviteModal.tsx` 已接入,业务组件只保留表单状态与提交流程。后续认证域新增同形态白底弹窗时优先复用该壳层;账号安全详情和绑定手机号这类布局差异较大的卡片先独立评估,不把 auth shell 扩成万能认证容器。验证命令:`npx vitest run src/components/auth/PlatformAuthModalShell.test.tsx src/components/auth/AuthGate.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.54. 账号 / 运行态 / onboarding 这轮继续分场景收口:`AccountModal.tsx` 的设置入口外层 overlay 与 auth card 壳层复用 `PlatformAuthModalShell`,并通过 `overlaySpacing`、`overlayStyle`、`showHeader` 和尺寸透传保留账号弹窗的 safe-area 与 direct account 唯一 dialog 语义;拼图运行态新增 `src/components/puzzle-runtime/PuzzleRuntimeModalShell.tsx`,只在 `puzzle-runtime` 内承接道具确认、设置、退出改造提示、失败弹窗和通关结算的 overlay / dialog / footer / button 骨架,原图查看、拖拽 ghost、飞行动画和全屏 runtime 容器不纳入 modal 收口;抓大鹅与跳一跳结算弹窗分别在 `Match3DRuntimeShell.tsx` 和 `JumpHopRuntimeShell.tsx` 内提取本地结算壳层 / summary / actions,保留玩法视觉身份;拼图 onboarding 首屏继续保留沉浸式全屏体验,只把登录保存覆盖层迁入 `UnifiedModal`,保持无关闭按钮、禁用遮罩关闭和禁用 Escape。后续 runtime 专属弹窗优先先抽玩法目录内薄壳;只有出现跨玩法稳定同构接口时再上升到 `common/`,不要把 `PlatformToolModalShell` 强行套到像素 / 游戏运行态 overlay。验证命令:`npm run test -- src/components/auth/AccountModal.test.tsx src/components/auth/PlatformAuthModalShell.test.tsx src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.55. 拼图 / 拼消消运行态的剩余阻断层继续按玩法目录局部收口:`src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleRuntimeBlockingOverlay.tsx` 只承接平台入口里拼图“正在准备下一关”的短暂阻断层,继续复用 `UnifiedModal` 的遮罩、dialog 语义和关闭禁用策略,但不把这类运行态等待面板直接提升到 `common/`;`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx` 则在玩法目录内新增 `PuzzleClearRuntimeOverlayShell`、`PuzzleClearRuntimePendingOverlay` 与 `PuzzleClearRuntimeSettlementDialog`,把 `!activeRun` 的等待层和 `level_cleared / finished / level_failed` 的结算层统一成一条本地结构线,同时保留拼消消自己的视觉和动作分流。拖拽 ghost、swap flight、补牌 / 消除动画、全屏 runtime 容器和其它强玩法视觉层不算旧 modal 债务,不跟这条线混收。验证命令:`npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleRuntimeBlockingOverlay.test.tsx src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.56. `UnifiedModal` 默认在 `portal=true` 时读取 `AuthUiContext.platformTheme`,并把 `platform-theme platform-theme--<light|dark>` 注入 portal overlay,从底层保证 `--platform-modal-fill`、`--platform-overlay-fill` 等 CSS 变量不因挂到 `document.body` 而丢失。新增 `portalTheme="auto|light|dark|none"`:默认 `auto` 优先保留调用方已给出的明确主题,否则使用当前主题并在无 Provider 时回退 light;纯黑底图片 / 素材预览使用 `none` 保持完全自绘;`portal=false` 不改变原 DOM 继承。`PlatformToolModalShell`、`PlatformDangerConfirmDialog`、认证与工具信息壳不再重复维护 portal 主题桥,但继续保留各自的 panel、间距、按钮和层级语义。编辑器裸 portal 菜单应复用 `ImageCanvasEditorPortal` 同时获得平台与画布变量;图片信息、修改图片和画布快捷键三个面板仍使用固定浅色 CSS,在完整支持暗色样式前显式传入 `portalTheme="light"`;已退役玩法目录不因本次收口重新进入维护范围。验证命令:`npm run test -- src/components/image-editor/UnifiedModalPortalTheme.test.tsx src/components/image-editor/ImageCanvasShortcutDialogView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasMetadataModalView.test.tsx src/components/image-editor/EditorAgentConversation/MessageBubble.test.tsx src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3. creative-agent 首页的侧边栏菜单、账号入口、开启新对话、我的创作、首页激励 CTA 和 prompt suggestion 按钮迁移到 `PlatformIconButton` / `PlatformActionButton`;首页继续保留 `creative-agent-home__*` 本地 class 承接透明顶栏、抽屉和品牌化胶囊视觉,不把视觉回收和语义收口绑成一次大改。`Beta` 徽标和历史记录纯文本行暂保留本地实现,等出现更多同构轻量列表行后再评估是否抽新的共享 row primitive。
19.4. 大鱼吃小鱼结果页 hero 的返回入口迁移到 `PlatformIconButton variant="darkMini"`,测试 / 发布动作迁移到 `PlatformActionButton surface="editorDark"`;结果页只保留测试运行、发布提交和文案状态语义,不再手写 hero 顶栏按钮壳。
19.4.1. 大鱼吃小鱼结果页的发布失败弹层迁移到 `src/components/common/PlatformStatusDialog.tsx`;`PlatformStatusDialog` 补充自定义图标、可访问标签和动作按钮样式透传后,`BigFishResultView` 不再保留 `BigFishResultErrorModal` 内联的 `UnifiedConfirmDialog + PlatformIconBadge` 组合。结果页只保留失败文案和关闭回调,发布失败的状态图标、遮罩、白底面板和“知道了”主动作统一由共享状态弹层承接。验证命令:`npm run test -- src/components/common/PlatformStatusDialog.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`、`npm run typecheck`。
File diff suppressed because one or more lines are too long
@@ -65,6 +65,7 @@
- Enter 发送必须同时排除 `isComposing` 和旧 Safari / WebKit 候选词确认事件的 `keyCode === 229`,避免输入法选词时误发送。
- 用户消息必须包含去除首尾空白后的非空文本;附件只能随文本消息发送,前端发送门禁与后端 `module-editor-agent` 领域校验必须同时拒绝纯附件消息。
- 会话管理入口在对话框头部:当前会话标题 + 历史会话下拉(按更新时间倒序)+ 新建对话按钮,全部包在对话框内。
- 右上角删除当前对话的危险确认框继续使用 `PlatformDangerConfirmDialog`;其 portal 主题由 `UnifiedModal` 统一恢复,panel 使用 `platform-remap-surface`,且层级与附件选择弹窗一致,避免背景透明、错色或被画布控件遮挡。
- 当前会话没有任何已发送消息时,新建对话按钮置灰且不可点击;输入框草稿和未发送附件不算会话内容。当前会话已有消息时可新建,新建成功后只切换到返回的空白会话,输入文字、附件及附件选择状态与切换历史会话时一样原样保留,旧会话继续保留在历史会话下拉中;创建失败同样不修改草稿。
- 新会话创建请求 pending 时禁用历史会话下拉和发送动作,但输入框与附件仍可编辑;会话列表或历史消息加载期间同样禁用发送。表单提交处理器必须复用相同门禁,不能先清空草稿再由 hook 静默跳过发送。
- 快速切换会话或会话轮询刷新产生并发详情请求时,每个请求必须获得唯一且单调递增的请求序号;前端只允许最后发起且有权生效的请求更新当前会话、消息、错误和加载态。被正在进行的会话切换压制的旧会话 refresh 不得提前结束新切换的加载态,旧响应也不得覆盖用户最新选择。
@@ -78,7 +79,7 @@
- 「素材库」页签:账号级素材库(复用 `ImageCanvasAssetLibrary` 数据源);
- 多选 + 底部「取消 / 应用」。
- 网格末尾上传格为后续补齐项;在上传格未落地前,对话附件只从已有画布资源和账号素材库选择。后续若从对话入口上传图片,必须复用素材库 / 画布资源登记链路,不新增对话私有图片类型。
- 附件选择弹窗使用 `PlatformToolModalShell` 承接 portal 主题变量和不透明 panel 背景;不能直接把未注入 `platform-theme` 的 `UnifiedModal` portal 到 `document.body`,否则 `--platform-modal-fill` 失效后面板会变透明。
- 附件选择弹窗使用 `PlatformToolModalShell` 承接白底 panel 和标准间距;底层 `UnifiedModal` 会把当前 `platform-theme` 自动注入 portal overlay,保证 `--platform-modal-fill` 在 `document.body` 下仍有效。
- 应用后附件以胶囊 chip 挂在输入框上方;发出的消息内附件渲染为纯文本胶囊 chip(名称 + 小图标),**默认无缩略图,鼠标悬浮才浮出缩略图预览**。
- 附件领域形状:统一为画布资源 / 素材库对象引用(`resourceId` / `assetId` + 可选 `objectKey`),不存在只属于对话的第三种图;单条消息上限 9 张(前后端共同校验)。前端可携带展示用 `imageSrc` / `thumbnailSrc`,后端必须按当前工程和当前账号重新归一、校验归属与 `objectKey`。
- 附件 `label` 是人类可读的展示元数据,统一限制为最多 24 个 Unicode 码点。归一化时先去掉首尾空白,删除控制字符以及除 `-`、`_`、`.` 之外的 ASCII 标点,把连续空白折叠为一个半角空格,再按 24 码点截断;只含被过滤字符的 label 视为缺失。中文等非 ASCII 标点不属于本轮过滤范围。