From 3bda17aa3c1d034ddb3feaa08e516398e91cba0a Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 10:09:05 +0800 Subject: [PATCH 1/5] fix(design): remove redundant assistant progress banner --- ...0260904-remove-design-progress-4e8a1c73.md | 86 +++++++++++++++++++ .../ImageCanvas/DesignConversationPane.tsx | 36 +------- tests/unit/image-canvas-page.test.tsx | 8 +- 3 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260904-remove-design-progress-4e8a1c73.md diff --git a/.project-docs/30-worklog/tasks/20260904-remove-design-progress-4e8a1c73.md b/.project-docs/30-worklog/tasks/20260904-remove-design-progress-4e8a1c73.md new file mode 100644 index 0000000..3610445 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-remove-design-progress-4e8a1c73.md @@ -0,0 +1,86 @@ +# Task: Remove redundant AI Design progress banner + +## Identity + +- Task ID: 20260904-remove-design-progress-4e8a1c73 +- Mode: Feature +- Branch: codex/20260904-remove-design-progress-4e8a1c73-remove-design-progress +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-remove-design-progress-4e8a1c73 +- Base commit: 403236115bf81e7617856cd8219b1be23f6abbb8 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Remove the visible AI Design assistant-progress banner from the main conversation + pane. The right-side “AI 听懂的想法” projection remains the sole surface for the + currently organized design understanding. +- Preserve immediate provisional user messages, their `发送中` / `正在确认` feedback, + canonical assistant replies, and the existing internal stream reconciliation. +- Update focused Renderer regression coverage only. Do not change the Works Square + server, Electron Main/API contracts, Current Specification authority, generation, + navigation, packaging, publication, or unrelated project records. + +## Intent And Constraints + +- The user explicitly identified the progress banner as duplicated information and + requested that it be removed. It must not be replaced by another central loading + surface. +- Raw assistant deltas remain non-canonical and must stay out of the conversation. + Removing their presentation must not remove their Store state or change operation + identity and failure recovery. +- Keep the pending user bubble as the honest acknowledgement that a send was accepted + locally; only the redundant assistant-side banner is removed. +- Concurrent Task Gate and Planning Gate passed in the managed isolated worktree from + exact client `main` base `403236115bf81e7617856cd8219b1be23f6abbb8`. +- The root integration task overlaps the same AI Design area but owns only root/canonical + reconciliation; this feature task owns the component and focused test in isolation. +- ADR-007 remains unchanged: the server Current Specification is semantic authority, + while the right-side youth summary is its secondary client projection. + +## Outcome + +- `DesignConversationPane` no longer subscribes to `assistantStreams` for visible + presentation and no longer renders the “AI 正在整理你的想法” / result-confirmation + banner in the conversation timeline. +- Raw assistant deltas remain Store-owned transport state and still never appear as + committed chat content. The final canonical assistant turn remains unchanged. +- Provisional user messages and their `发送中` / `正在确认` labels remain visible, so + removing the duplicated assistant banner does not remove send acknowledgement. +- The right-side “AI 听懂的想法” card and all Current Specification behavior are + unchanged. + +## Verification + +- Focused Renderer test: `pnpm exec vitest run tests/unit/image-canvas-page.test.tsx` + passed `1 file / 10 tests`. +- TypeScript: `pnpm exec tsc --noEmit` passed. +- Scoped ESLint for the changed component and test passed. +- The isolated worktree dependencies were restored from the existing lockfile with + offline, frozen resolution and lifecycle scripts disabled; no dependency version or + lockfile changed. + +## Follow-ups + +- Integrate this source commit after the existing root AI Design integration task has + safely completed or released its ownership. Do not overwrite that task's dirty + canonical-document reconciliation or its three preserved foreign records. + +## Promotion Candidates + +- Target: `.project-docs/30-worklog/current-state.md`, AI Design presentation wording + in `.project-docs/20-architecture/system-overview.md`, and the corresponding rule in + `.project-docs/40-domain/business-rules.md`. +- Proposal: record that raw assistant deltas stay internal and invisible in the main + conversation; the right-side Current Specification projection is the single surface + for the AI's organized understanding. The main timeline shows provisional/canonical + user messages and canonical assistant turns, without a separate assistant-progress + banner. +- Evidence: the user explicitly identified the banner as duplicated information and + requested its removal; focused Renderer coverage confirms the banner is absent while + send acknowledgement remains. +- Future impact: later conversation changes should not reintroduce a second summary or + progress card into the timeline merely because assistant delta transport state exists. +- Semantic conflict: this narrows the prior task's optional visible progress projection; + it does not change ADR-007 or server authority. Human confirmation is already present + in this task request. diff --git a/src/pages/ImageCanvas/DesignConversationPane.tsx b/src/pages/ImageCanvas/DesignConversationPane.tsx index 1b6488a..edd8b23 100644 --- a/src/pages/ImageCanvas/DesignConversationPane.tsx +++ b/src/pages/ImageCanvas/DesignConversationPane.tsx @@ -44,7 +44,6 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa const chatDraft = useImageWorkspaceStore((state) => state.chatDraft); const setChatDraft = useImageWorkspaceStore((state) => state.setChatDraft); const sendChat = useImageWorkspaceStore((state) => state.sendChat); - const assistantStreams = useImageWorkspaceStore((state) => state.assistantStreams); const pendingOperations = useImageWorkspaceStore((state) => state.pendingOperations); const scrollAnchorRef = useRef(null); const pendingChatMessages = Object.values(pendingOperations).flatMap((operation) => { @@ -60,13 +59,6 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa status: operation.status, }]; }); - const streamingOperationIds = Object.entries(assistantStreams) - .filter(([, text]) => text.trim()) - .map(([operationId]) => operationId); - const hasAssistantProgress = streamingOperationIds.length > 0; - const hasUnknownAssistantProgress = streamingOperationIds.some( - (operationId) => pendingOperations[operationId]?.status === 'unknown', - ); const submittingDesignInput = Object.values(pendingOperations).some( (operation) => operation.status === 'submitting' && operation.command.kind === 'apply_input', ); @@ -81,7 +73,7 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa if (typeof scrollAnchor?.scrollIntoView === 'function') { scrollAnchor.scrollIntoView({ block: 'end' }); } - }, [hasAssistantProgress, pendingChatKey, workspace.turns.length]); + }, [pendingChatKey, workspace.turns.length]); const submit = () => { if (!composerDraft.trim() || submittingDesignInput) return; @@ -107,8 +99,7 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa
{workspace.turns.length === 0 - && pendingChatMessages.length === 0 - && !hasAssistantProgress && ( + && pendingChatMessages.length === 0 && (
@@ -169,29 +160,6 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa
))} - {hasAssistantProgress && ( -
- - -
-

- {hasUnknownAssistantProgress ? '正在确认刚才的整理结果' : 'AI 正在整理你的想法'} -

-

- {hasUnknownAssistantProgress - ? '结果回来后会显示在对话里,不需要重复发送。' - : '这不是新的回复,完成后会显示下一步问题或建议。'} -

-
-
- )} -
diff --git a/tests/unit/image-canvas-page.test.tsx b/tests/unit/image-canvas-page.test.tsx index 78d3a80..9049664 100644 --- a/tests/unit/image-canvas-page.test.tsx +++ b/tests/unit/image-canvas-page.test.tsx @@ -93,7 +93,7 @@ describe('youth AI Design Canvas page', () => { expect(screen.queryByText(/规格版本|编译器|生成策略|字段决策/)).not.toBeInTheDocument(); }); - it('presents unfinished AI output as progress instead of a chat reply', () => { + it('keeps unfinished AI output out of the conversation while send status stays visible', () => { prepareWorkspace(); const unfinishedDraft = '收到,我们要收到,我们要制作一张社团活动海报。'; useImageWorkspaceStore.setState({ @@ -121,7 +121,11 @@ describe('youth AI Design Canvas page', () => { render(); const conversation = screen.getByTestId('image-workspace-conversation'); - expect(within(conversation).getByRole('status')).toHaveTextContent('AI 正在整理你的想法'); + const pendingMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-1'); + expect(within(pendingMessage).getByText('做一张社团活动海报')).toBeInTheDocument(); + expect(within(pendingMessage).getByText('发送中')).toBeInTheDocument(); + expect(within(conversation).queryByTestId('design-assistant-progress')).not.toBeInTheDocument(); + expect(within(conversation).queryByText('AI 正在整理你的想法')).not.toBeInTheDocument(); expect(within(conversation).getByText('我已经整理了用途、受众和初步概念,请确认右侧建议。')).toBeInTheDocument(); expect(within(conversation).queryByText(unfinishedDraft)).not.toBeInTheDocument(); }); From aa3f52a8f8414c9ad939d396bd2828c3e07c70e7 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 10:20:44 +0800 Subject: [PATCH 2/5] docs: finalize AI Design feedback integration --- .../20-architecture/system-overview.md | 4 +- .project-docs/30-worklog/current-state.md | 21 +++++ ...60904-finalize-design-feedback-7c4e2a91.md | 89 +++++++++++++++++++ .project-docs/40-domain/business-rules.md | 2 + 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 .project-docs/30-worklog/tasks/20260904-finalize-design-feedback-7c4e2a91.md diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index 63e5e82..f0f5500 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -26,7 +26,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 | Official Hosted Plugins | Acquired code-owned bundled Game Resource package → effective parent snapshot → code-owned Main adapter → fixed Works Square hosted route | 无设备下载、更新、Beta 或签名步骤;Renderer/Package/Pi 不持有 Provider key、model 或 URL。每次计费操作要求显式确认,child 不继承 hosted tool。历史 Hosted Web Search 不再进入当前客户端。 | | Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`;没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 拥有桌面用户权限且生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension;新/idle parent 自动刷新,active parent 在 turn settled 后刷新,child 始终为空。 | | AI Design Workspace & Living Form | 一个 Workspace 的当前 Direction、Current Specification、持久 Agent Session、conversation timeline、Tasks 与 Assets | 自然对话是主创作面;Living Form 仅以“AI 已理解”的紧凑辅助摘要与可选手动调整投影服务端 Current Specification,Renderer 只持有草稿和已接受投影 | -| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态 | +| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,raw assistant delta 保持内部且不生成对话区进度栏 | | AI Design Gateway Routing | Main-owned Works Square V2 adapter 与 Direction event stream | Main 持有 Works Token、stream ticket、WebSocket、重试分类和错误脱敏;事件顺序与 Task progress 不构成 Specification 真值 | | AI Design Quote & Task Controls | 精确 Specification revision 编译出的不可变 Quote 与 Workspace Tasks | 客户端只展示 public output summary、warnings、expiry 与 Token Points,并以 Quote ID 确认;Provider Prompt/model/route/storage/billing atoms 不下发 | | AI Design Assets | Workspace 已完成作品或本地上传的真实 Asset | Asset 通过 typed binding 写入 Specification;生成结果经 Main-owned asset download 保存 | @@ -88,6 +88,8 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - Main 通过 `design.input.apply`、`design.quote.request` 和 `design.generation.confirm` 访问 V2。unknown transport result 必须复用原 command/operation identity;结构化业务错误不得重放,未知上游文本不得穿透安全投影。 - Main-owned Canvas Workspace JSON 请求和 shared Works token refresh 的完整生命周期最多 30 秒,超时 abort transport 并以固定 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT` 结束 Renderer 等待。Electron `net.fetch` 失败后的 Node fetch 透明回退只允许 `GET`、`HEAD`、`OPTIONS`;PATCH/POST 等 mutation 只允许由持有显式幂等身份的上层协议决定重试,不能由底层 transport 隐式重放。该边界不改变上面的 WebSocket→REST 幂等 fallback。 - Renderer 的异步与流式结果必须核对 Workspace、Direction、revision 和 operation identity;Direction snapshot 是 Specification 真值,Task/Asset 事件只更新 Workspace resource projection。 +- 待提交 chat 可以从现有 pending operation 立即投影为明确标注“发送中/正在确认”的用户气泡,但只有服务端 canonical turn 能进入对话历史;确定失败必须恢复原草稿,不能另建一套消息状态或新业务意图。 +- `design.assistant.delta` 是未完成的内部传输状态,不是助手回复。Renderer 不得显示原始片段,也不得在对话区为它创建独立整理进度栏;右侧 Current Specification 投影继续呈现 AI 当前理解。重叠连接与重放 chunk 必须按连接 generation 和 `chunkIndex` 收敛。 - 确认生成只提交不可变 Quote ID。Task 事务已提交但事件迟到时可刷新 Workspace projection;Task progress 不得改写 Living Form 或授权新的生成操作。 - Updater feed 选择、原始错误日志、下载和安装生命周期只属于 Electron Main。Renderer 不得把缺失稳定 manifest 投影为“已是最新版”,也不得显示原始堆栈、URL、路径或错误码;并发检查共享同一错误事件时只发送一次错误状态,后续独立重试仍可重新报告。 - 图片与视频复用同一个单图来源选择器。图片 Brief 可从当前 Workspace 的已完成作品或本地上传中选择一张参考图继续生成;视频 Brief 使用同一入口绑定首帧。两条路径都必须通过现有 Workspace Asset 上传/选择契约提交一个真实 `attachmentAssetIds`,不得用本地路径或自然语言描述代替资产身份。 diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index e1a1872..9aaec3a 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,6 +4,25 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through +- AI Design conversation-feedback sources + `403236115bf81e7617856cd8219b1be23f6abbb8` from task + `20260904-design-summary-separation-9c4e7a21` and + `3bda17aa3c1d034ddb3feaa08e516398e91cba0a` from task + `20260904-remove-design-progress-4e8a1c73` are integrated onto local `main` and + canonically reconciled by task `20260904-finalize-design-feedback-7c4e2a91`. A + submitted chat appears immediately as a provisional user bubble from its existing + pending operation, shows `发送中` or `正在确认`, restores the retained draft after + definitive failure, and is replaced by the canonical Workspace turn without a + second message store. Raw `design.assistant.delta` text remains internal transport + state: it is neither a finished reply nor a visible progress banner. The right-side + “AI 听懂的想法” remains the single organized-understanding projection, while + connection generation fencing and `chunkIndex` deduplication still converge repeated + streams. Current Specification, immutable Quote confirmation, server/Main contracts, + plugins, packaging, deployment, publication, and paid Provider behavior are + unchanged. Source verification passed focused/adjacent tests, typecheck, scoped + lint, production build, and both existing AI Design Electron checks individually; + the banner-removal follow-up passed its focused 10-test file, typecheck, and scoped + lint. - Conversation-first AI Design client source `fe50e4ba198649a07c5c9443f15edf7dfa2a47e6` from task `20260903-design-guided-conversation-client-8b4e1c72` is integrated onto local @@ -551,6 +570,8 @@ This file is the integrated default-branch snapshot. Feature tasks record progre AI Design Canvas 现在默认以对话和一张持续可见的“我的创作”卡服务 8-16 岁创作者;专业字段矩阵收进按需打开的“精细调整”,移动端保持对话优先并只挂载一个卡片/底部面板。Creation Card、精细调整、Quote、Task 与结果提示都只投影权威状态,已知问题按 code 转成通俗中文,未知服务端或 Provider 文本不会直接显示。一个 Workspace 仍只公开一个 current Direction、一个 persistent Agent Session 和一个 Current Specification;conversation timeline 只记录交互历史。Chat、direct edits、decision responses、proposal acceptance、locks、Asset binding 与 restore 都通过 `design.input.apply` 进入同一服务端 reducer,Renderer drafts 在 accepted 前保持本地。Main 持有 Works Token、stream ticket、WebSocket、request deadline、stable command/operation IDs 与错误脱敏;unknown result 只能复用原 identity,结构化业务错误不得重放。Generation 由服务端对 exact Specification revision 编译 immutable Quote,客户端只展示 public output plan、warnings、expiry 与 Token Points,并以 Quote ID 调用 `design.generation.confirm`;Provider Prompt、model、route、storage 和 billing atoms 不进入 Renderer。Task/Asset events 独立收敛 Workspace resources,不改写 Living Form。Development 与 packaged builds 均使用 Works Square V2,V1 DTO、local semantic adapter、mutable Quote PATCH 与 editable provider Prompt 已移除。 +新提交的 Design chat 会立即从现有 pending operation 投影为带“发送中”或“正在确认”的临时用户气泡,服务端确认的 canonical turn 到达后再替换它;确定失败时原草稿重新出现在输入框。`design.assistant.delta` 只作为内部传输与收敛状态,不显示原始片段,也不在对话区生成独立整理进度栏;右侧 Current Specification 摘要继续承担“AI 当前听懂的内容”。重复连接和事件按 connection generation 与 `chunkIndex` 收敛。 + Canvas 侧栏提供“获取灵感”进入 Prompt Museum。列表、筛选、分页、详情、作者/来源/许可证和图片地址全部由服务端经 Main-owned Host API 提供,客户端不打包静态数据集;服务端相对媒体只允许固定 `/api/image-prompt-museum/{entry}/media/{thumbnail|number}` 形状,并由 Main 注入 Works Bearer、执行一次 401 刷新、可信 raster MIME 与 10 MiB 上限后转为 Renderer data URL;credential-free HTTPS CDN 图片保持直连。图片失败只显示卡片内占位,不阻断卡片或详情;缺少来源 URL 时显示纯文本。“使用此 Prompt”只把原文带回当前 Canvas 输入框,不自动发送。该模块不是投稿、点赞、评论或排行榜社区。客户端契约已就绪,但不据此宣称 Works Square 内容后台和生产数据已经部署。`pnpm run dev` 现在默认使用云端 Canvas 适配器,本地适配器只能通过显式开发命令启用;产品 UI 只保留中文。 密码登录提供可选“记住密码”。该记录与七天登录会话分离,只在正式安装包且系统安全存储可用时由 Electron Main 加密落盘;Renderer 仅在登录页内存中接收回填,不写 Zustand/localStorage,Works Square 也不持久化桌面密码。退出登录和短信登录不删除记录,成功的未勾选密码登录会清除旧记录。未打包开发版禁用该选项,避免未签名 Electron 调试进程触发 macOS 钥匙串。 diff --git a/.project-docs/30-worklog/tasks/20260904-finalize-design-feedback-7c4e2a91.md b/.project-docs/30-worklog/tasks/20260904-finalize-design-feedback-7c4e2a91.md new file mode 100644 index 0000000..60890ce --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-finalize-design-feedback-7c4e2a91.md @@ -0,0 +1,89 @@ +# Task: Finalize AI Design conversation feedback integration + +## Identity + +- Task ID: 20260904-finalize-design-feedback-7c4e2a91 +- Mode: Integration +- Branch: main +- Worktree: D:\Datas\OthersProjects\makelore +- Base commit: 3bda17aa3c1d034ddb3feaa08e516398e91cba0a +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Reconcile the already integrated AI Design conversation-feedback sources + `403236115bf81e7617856cd8219b1be23f6abbb8` and + `3bda17aa3c1d034ddb3feaa08e516398e91cba0a` into canonical current state, + architecture, and business rules. +- Record the final behavior: submitted user messages appear immediately with honest + pending status, raw assistant deltas remain internal, and the main conversation has + no assistant-progress banner because the right-side Current Specification projection + owns the organized understanding. +- Preserve the three adopted foreign untracked task records byte-for-byte. Do not add, + edit, move, stash, reset, clean, or commit them. +- Do not change product code, Works Square server, Electron Main/API contracts, + generation, billing, plugins, navigation, packaging, publication, remote history, or + paid Provider behavior. + +## Intent And Constraints + +- The user explicitly authorized recovery of the prior integration lock and made the + final presentation decision that the central progress banner is redundant and must + be removed rather than restyled. +- Client `main` already contains both source records and product changes at exact base + `3bda17aa3c1d034ddb3feaa08e516398e91cba0a`; this task performs canonical integration + only, so the document drift boundary does not need to cross another task's record. +- ADR-007 remains accepted. Current Specification is still server authority, its + right-side projection remains auxiliary UI, provisional user messages remain pending + projections, and only canonical turns enter conversation history. +- The newer source narrows the older optional progress presentation: raw assistant + deltas stay internal and invisible. The user's explicit instruction resolves that + semantic conflict. +- Concurrent Task Gate and Integration Planning Gate passed. Relevant source tasks are + Ready for Integration; other active work remains isolated and does not alter this + exact final behavior. + +## Outcome + +- Canonically recorded both integrated conversation-feedback sources on client `main` + at product frontier `3bda17aa3c1d034ddb3feaa08e516398e91cba0a`. +- The main conversation no longer renders raw assistant delta text or an independent + “AI 正在整理你的想法” progress banner. The right-side Current Specification summary + is the single organized-understanding surface. +- Immediate provisional user bubbles, `发送中` / `正在确认`, canonical turn replacement, + draft recovery, connection-generation fencing, and `chunkIndex` deduplication remain + intact. +- Updated canonical current state, system architecture, and business rules without + changing ADR-007, product code, server/Main contracts, immutable Quotes, navigation, + plugins, packaging, billing, or remote history. +- The earlier root lock and one transient wrong-base integration lock were recovered + under the user's explicit authorization. Each uncommitted document edit was restored + before release; the three adopted foreign files remained unchanged throughout. + +## Verification + +- Product source commit `3bda17a` has exact parent + `403236115bf81e7617856cd8219b1be23f6abbb8`; its changed paths are limited to the + source task record, `DesignConversationPane`, and its focused test. +- Root focused Renderer test passed: `pnpm exec vitest run + tests/unit/image-canvas-page.test.tsx` → `1 file / 10 tests`. +- Root TypeScript `pnpm exec tsc --noEmit` passed. +- Root scoped ESLint for the changed component and test passed. +- Root product paths matched source `3bda17a` exactly and `git diff --check` passed + before canonical documentation. +- The earlier conversation-feedback source retains its adjacent 51-test, + production-build, and individual Electron evidence; this final integration did not + repeat those broader checks for a presentation-only removal. + +## Follow-ups + +- Build and publish a new Makelore client version before expecting installed users to + receive this UI change. Packaging, deployment, publication, and remote push are not + part of this integration. + +## Promotion Candidates + +- Applied the two source promotion candidates to `current-state.md`, + `system-overview.md`, and `business-rules.md` using the user's final presentation + decision. No unresolved promotion candidate remains. diff --git a/.project-docs/40-domain/business-rules.md b/.project-docs/40-domain/business-rules.md index c6f3541..485e381 100644 --- a/.project-docs/40-domain/business-rules.md +++ b/.project-docs/40-domain/business-rules.md @@ -99,6 +99,8 @@ - AI Design 必须以自然对话帮助用户描述清楚需求:先提取和复述已明确内容,每次最多推进一个真正影响作品的问题。客户端 Current Specification 投影是辅助摘要和可选手动调整,不得把内部字段、缺项或普通 Decision Prompt 变成用户必须逐项填写的表单。 - 每个 mutation 使用稳定 command 与 semantic operation identity。transport-unknown 只能重放原命令;business rejection、timeout 或用户再次点击不能自动生成新的业务意图。 - Direction projection 是 Specification 真值。event cursor、assistant delta、Task progress 与 Asset event 只用于传输/资源收敛,不得推进或覆盖 canonical specification revision。 +- 用户提交 chat 后,客户端可以立即把同一个 pending operation 投影为临时用户气泡,并明确显示“发送中”或“正在确认”;只有服务端返回的 canonical turn 才进入 conversation timeline。确定失败时必须让原草稿重新可编辑,不得把临时投影持久化为第二条消息。 +- `design.assistant.delta` 只用于内部传输和结果收敛;原始片段不得作为完成的助手回复展示,对话区也不得为其增加独立的整理进度栏。AI 当前整理出的设计理解由右侧 Current Specification 投影承载;连接重叠或事件重放按 connection generation 与 `chunkIndex` 去重收敛。 - Main 只向 Renderer 投影已知错误码的固定中文提示,未知上游错误文本必须脱敏为通用提示;Works Token、stream ticket、provider internals 留在 Main/服务端。 - AI 绘画 Workspace JSON 请求与 shared Works token refresh 必须覆盖取凭据、发请求和读取响应 body 的完整 30 秒 deadline;即使底层 transport 忽略 abort,调用方也必须确定性结束为 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT` 并释放共同等待者。Electron-to-Node 透明 fallback 仅允许 `GET`/`HEAD`/`OPTIONS`;mutation 不得因 transport failure 被隐式重放,任何重试必须由上层显式幂等合同授权。 - `design.quote.request` 必须绑定 exact current Specification revision 并返回 immutable Quote;任何 production-meaning edit 都需要新 revision 与新 Quote。 From 229b1b1ce39b7f1541a93ea3c980ab82b6d6266c Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 11:10:51 +0800 Subject: [PATCH 3/5] docs: diagnose AI Design streaming and generation --- ...0260904-diagnose-design-stream-6a4e9c21.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 .project-docs/30-worklog/tasks/20260904-diagnose-design-stream-6a4e9c21.md diff --git a/.project-docs/30-worklog/tasks/20260904-diagnose-design-stream-6a4e9c21.md b/.project-docs/30-worklog/tasks/20260904-diagnose-design-stream-6a4e9c21.md new file mode 100644 index 0000000..e0e1f28 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-diagnose-design-stream-6a4e9c21.md @@ -0,0 +1,132 @@ +# Task: Diagnose AI Design streaming and generation trigger + +## Identity + +- Task ID: 20260904-diagnose-design-stream-6a4e9c21 +- Mode: Feature +- Branch: codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21 +- Base commit: aa3f52a8f8414c9ad939d396bd2828c3e07c70e7 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Diagnose, without changing product behavior, why AI Design assistant replies no + longer appear incrementally and why the screenshot path does not visibly create a + generation Task. +- Trace the current Renderer/store/Main contracts at the public test seams for + `design.assistant.delta`, Quote request/opening, immutable Quote confirmation, and + Workspace Task reconciliation. +- Build focused red-capable regressions that distinguish a presentation regression + from a transport failure and distinguish “no Quote requested” from “Quote confirmed + but no Task recovered”. Remove temporary probes before completion. +- Own only this task record and temporary diagnostic tests. Do not change product + code, the Works Square server, database, billing, packaging, publication, Plugin + navigation, or the three foreign untracked root task records. + +## Intent And Constraints + +- The user reports two observable regressions after the conversation-feedback UI + changes: completed assistant text arrives all at once, and the ready-state action + appears not to trigger generation. +- Preserve ADR-007. Streaming deltas are not canonical conversation turns, but the + diagnosis must determine whether they can be presented as an explicitly unfinished + reply without duplicating the right-side Current Specification summary. +- Generation remains a deliberate two-step boundary: request/check an immutable Quote, + then explicitly confirm its ID. A chat reply saying creation is possible is not + authorization to create a paid Task. +- Do not infer failure from the screenshot alone. Reproduce the actual button callback, + panel-mount, command dispatch, confirmation, and Task projection seams one at a time. +- Preserve stable operation identity and no-replay behavior while diagnosing. + +## Project Context Loaded + +- Concurrent Task Gate and Planning Gate passed in the managed isolated worktree at + exact client `main` base `aa3f52a8f8414c9ad939d396bd2828c3e07c70e7`. +- Loaded the startup memory set, ADR-007, architecture/data flow, business rules, + success criteria, evidence/reflection/commitment indexes, and the three integrated + AI Design conversation-feedback task records. +- Assessed 176 peer task records with no unreadable record. Relevant Canvas peers are + historical Ready-for-Integration sources already reflected in this base; none is a + live semantic conflict. One old undefined Design assessment remains unknown but is + on a stale base and this task is read-only diagnosis. +- Likely seams are `DesignConversationPane`, `YouthCreationCard`, the ImageCanvas + composition, `image-workspace` store, Quote/Task panels, and focused unit/Electron + fixtures. +- Gate result: Passed. + +## Outcome + +- Confirmed a Renderer presentation regression rather than a transport outage. The + server still persists and emits `design.assistant.delta`; Main maps it and the + Renderer store deduplicates and appends it to `assistantStreams`. The current + `DesignConversationPane` no longer subscribes to or renders that state, so only the + later canonical `workspace.turns` assistant message becomes visible. +- Identified the exact history. Commit `c6b0490` replaced the visible unfinished text + with a generic progress banner to keep the reasoner's summary-like draft out of the + conversation. Commit `3bda17a` then removed both the banner and the + `assistantStreams` subscription. The latter change explains why replies now appear + only after completion. +- Confirmed the server event is not provider-native time-to-first-token streaming. + `OpenAIJsonDesignReasoningRunner` waits for one complete JSON-object response; + `DesignAgentRuntimeV2` then takes the validated canonical `assistant_message`, splits + it into six-character chunks, and the Gateway spaces frames by 60 ms. Restoring the + old bubble would restore incremental painting only after reasoning has completed and + risks recreating the summary-as-chat confusion reported by the user. +- Built a temporary red-capable Renderer probe that seeded a pending operation plus + `assistantStreams` text. It failed because no streamed text was rendered, while the + provisional user message remained visible. The probe was removed after diagnosis. +- Found no break in the current source generation command chain. A ready Creation Card + calls `requestQuote`; an offered immutable Quote exposes an explicit Design Point + confirmation; confirmation calls `confirmGeneration(quoteId)`; the Electron flow + projects the resulting Task. Focused unit tests and the real Electron scenario pass. +- The supplied screenshot is factually before Quote confirmation: the card is still in + `ready` and offers “看看制作方案”. A chat sentence saying the design can be generated + is not authorization for a paid Task. Whether that particular installed client + emitted a Quote command after a click remains unverified because no matching Run or + client version was supplied. +- No product, server, database, billing, packaging, publication, or Plugin navigation + code was changed. + +## Verification + +- Temporary red probe: + `pnpm exec vitest run tests/unit/image-canvas-page.test.tsx -t "shows unfinished assistant reply text incrementally"` + -> expected failure: streamed text was absent from the conversation. The temporary + assertion was restored and is not part of this task. +- `pnpm exec vitest run tests/unit/youth-creation-card.test.tsx tests/unit/image-canvas-page.test.tsx -t "plain creation summary|production confirmation"` + -> 2 passed, 15 skipped. +- `pnpm run build:vite` -> passed for Renderer, Main, Preload, and utility processes; + only existing Browserslist, chunk-size, and dynamic-import warnings were reported. +- `node ./node_modules/@playwright/test/cli.js test tests/e2e/image-workspace-v2.spec.ts -g "keeps conversation and youth-friendly direct edits"` + -> 1 passed. The scenario exercised “看看制作方案” -> offered Quote -> explicit + Design Point confirmation -> queued Task projection. + +## Follow-ups + +- Product decision required before implementation: choose between (a) showing the + already-finalized assistant message with a typewriter effect after reasoning, or + (b) introducing a distinct conversational streaming contract that can emit safe + assistant prose before the structured Specification proposal is complete. Option + (b) is the only route to genuine lower time-to-first-visible-text; do not render raw + structured reasoner JSON or revive the former summary-like draft bubble. +- Make the two-step production boundary explicit in the conversation: when the assistant + says the idea is ready, point to or provide an inline “看看制作方案” action; after Quote + creation, keep the existing explicit Design Point confirmation. Do not auto-create a + paid Task from ordinary chat. +- If a user reports that clicking “看看制作方案” itself does nothing, capture that exact + operation's client version, `client_operation_id`, and `design.quote.request` Run. The + current source/Electron fixture does not reproduce such a failure. + +## Promotion Candidates + +- Target canonical documents: `.project-docs/20-architecture/system-overview.md` and + `.project-docs/40-domain/business-rules.md`. Proposal: distinguish provider-native + conversational streaming from post-validation chunk projection, and record that + readiness chat must lead users to Quote review but cannot itself authorize a paid + generation Task. Evidence: this task's red presentation probe, commit history, and + passing end-to-end Quote-confirm-Task flow. Future impact: prevents another UI change + from conflating an internal structured-reasoning draft, a canonical assistant turn, + and a progress indicator. No known semantic conflict; human confirmation is required + because the streaming architecture and conversation CTA behavior are product choices. From 23f96a523eb37d8397bb3766ce95a59d56e59225 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 11:37:03 +0800 Subject: [PATCH 4/5] fix(design): restore streamed replies and quote handoff --- .../20260904-fix-design-stream-4f7b91c2.md | 149 ++++++++++++++++++ .../ImageCanvas/DesignConversationPane.tsx | 85 +++++++++- .../ImageCanvas/DesignProductionPanel.tsx | 6 +- src/pages/ImageCanvas/YouthCreationCard.tsx | 13 +- src/pages/ImageCanvas/index.tsx | 19 ++- tests/e2e/image-workspace-v2.spec.ts | 3 +- tests/unit/image-canvas-page.test.tsx | 71 ++++++++- tests/unit/image-workspace-store.test.ts | 24 ++- tests/unit/youth-creation-card.test.tsx | 11 +- 9 files changed, 362 insertions(+), 19 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260904-fix-design-stream-4f7b91c2.md diff --git a/.project-docs/30-worklog/tasks/20260904-fix-design-stream-4f7b91c2.md b/.project-docs/30-worklog/tasks/20260904-fix-design-stream-4f7b91c2.md new file mode 100644 index 0000000..8af3246 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-fix-design-stream-4f7b91c2.md @@ -0,0 +1,149 @@ +# Task: Restore AI Design reply streaming and generation CTA + +## Identity + +- Task ID: 20260904-fix-design-stream-4f7b91c2 +- Mode: Feature +- Branch: codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21 +- Base commit: 229b1b1ce39b7f1541a93ea3c980ab82b6d6266c +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Restore a visible, explicitly unfinished assistant reply in the AI Design + conversation from the existing `design.assistant.delta` / `assistantStreams` + transport, and remove it when the canonical assistant turn settles. +- Add a youth-readable inline “看看制作方案” action at the end of a ready + conversation. Reuse the existing Quote request and reveal the offered Quote after + success; preserve the separate explicit Design Point confirmation before Task + creation. +- Own only `DesignConversationPane`, the ImageCanvas composition, the smallest + necessary Quote/Card/store settlement seams, focused Renderer/Electron tests, and + this task record. +- Do not modify the Works Square server, reasoner contract, database/API DTOs, + billing semantics, Plugin navigation, packaging, publication, or the three foreign + untracked task records in the client root. + +## Intent And Constraints + +- The user confirmed the preferred interaction: the conversation should paint the + assistant's reply incrementally again, but must not restore the removed generic + “AI 正在整理你的想法” progress banner or present the right-side Specification + summary as a second completed chat reply. +- A streamed fragment is provisional presentation only. `workspace.turns` remains + canonical history; canonical settlement replaces the provisional bubble and must + not leave duplicate or permanently pulsing text. +- The existing transport is post-validation chunk projection, not provider-native + time-to-first-token streaming. This task restores honest incremental painting from + that supported contract and does not broaden the server work. +- Readiness may invite the user to inspect a Quote, but ordinary chat never authorizes + a paid generation Task. Quote request, offered Quote, explicit Quote-ID confirmation, + and Task projection remain separate boundaries. +- Preserve provisional user-message immediacy, stable operation identity, unknown + outcome recovery, and youth-readable failure copy. + +## Project Context Loaded + +- Concurrent Task Gate passed for task `20260904-fix-design-stream-4f7b91c2`, mode + Feature, on branch + `codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream` in isolated + worktree + `D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21` + at exact base `229b1b1ce39b7f1541a93ea3c980ab82b6d6266c`; ownership is claimed by + `codex-root`. +- Loaded the startup memory set, current state, ADR-007, system overview, business + rules, success criteria, evidence/reflection/commitment indexes, and the completed + diagnosis task that reproduced the missing streamed presentation while proving the + Quote-confirm-Task source path still works. +- Relevant peer tasks are historical/stale or already integrated. This scope does not + overlap the Plugin navigation planner, the older Enter-submission test task, or the + three foreign untracked root records. +- The current canonical docs intentionally keep raw deltas out of completed history + and prohibit a separate progress banner. The user's explicit product decision adds + a narrower distinction: a delta may be shown as one visibly unfinished assistant + bubble that is replaced by the canonical turn. Record this as a promotion candidate + rather than editing canonical docs in Feature mode. +- Planning Gate result: Passed. + +## Plan + +1. Add failing Renderer regressions for provisional assistant visibility, + canonical replacement, and the ready-conversation Quote action. +2. Implement the minimal presentation and settlement changes, then make both the + conversation CTA and right-side CTA reveal the offered Quote. +3. Run focused unit/Electron checks plus typecheck, scoped lint, build, diff, and + project-document gates; document the exact outcome and remaining architectural + limitation. + +## Outcome + +- Restored the existing `assistantStreams` presentation as one assistant-shaped, + visibly unfinished reply beneath its matching pending chat operation. New chunks + update that same bubble and its pulse cursor; the removed generic “AI 正在整理你的 + 想法” status banner remains absent. +- Kept provisional output out of canonical history. Per-command success or definitive + failure clears that operation's stream, and the conversation only renders a stream + while its original pending chat identity still exists, so the later canonical + `workspace.turns` reply replaces rather than duplicates it. Direction events do not + globally clear unrelated streams because they expose no client operation identity; + unknown outcomes retain their original pending identity and partial reply. +- Added an inline ready-state handoff at the end of the conversation. It explains that + the next step only calculates Design Point cost and requests a Quote; it never calls + generation confirmation. +- After a Quote request succeeds from either the conversation action or the right-side + Creation Card, desktop scrolls the offered Quote into view and mobile opens the + Current Idea/Works sheet before locating it. The existing explicit + “花 N 设计点开始制作” Quote-ID confirmation remains the only Task-creation action. +- No Works Square server, reasoner, database/API DTO, billing, Plugin navigation, + packaging, or publication code changed. + +## Verification + +- Red phase: the focused Renderer suite failed exactly because no streamed assistant + bubble and no conversation Quote action existed (2 failed / 26 passed). +- Focused final suite: + `pnpm exec vitest run tests/unit/image-canvas-page.test.tsx tests/unit/image-workspace-store.test.ts tests/unit/youth-creation-card.test.tsx` + -> 3 files / 35 tests passed. +- The focused regressions include two simultaneous stream identities, an unknown Quote + that disables both Quote entry points and retries the original operation, and the + mobile inline action opening the Sheet with its offered Quote. +- Electron production flow: + `node ./node_modules/@playwright/test/cli.js test tests/e2e/image-workspace-v2.spec.ts -g "keeps conversation and youth-friendly direct edits"` + -> 1 passed; it used the new conversation action, verified the Quote was in the + viewport, explicitly confirmed 12 Design Points, and observed the queued Task. +- `pnpm exec tsc --noEmit`, scoped ESLint for every changed TypeScript/test file, + `pnpm run build:vite`, and `git diff --check` passed. Vite reported only existing + Browserslist, chunk-size, and dynamic/static-import warnings. +- Full ordinary suite reached 224 passing files / 1,873 passing tests / 2 declared + skips. Its only failure was an unrelated real Pi subprocess latency assertion + (2,310 ms versus a 2,000 ms threshold); the exact process test passed 1/1 when + immediately rerun alone. The separately gated pressure test passed 1/1. +- Independent read-only review found and then verified fixes for the two concrete P2 + gaps above (global stream cleanup and duplicate unknown Quote submission); final + verdict: PASS with no remaining P1/P2 blocker. + +## Follow-ups + +- The current server does not provide provider-native time-to-first-token streaming: + it validates the complete structured reasoner response, then projects the final + assistant message in short chunks. Genuine earlier first-visible-text would require + a separately designed server conversational-stream contract; this client task does + not pretend otherwise. + +## Promotion Candidates + +- Target canonical documents: `.project-docs/20-architecture/system-overview.md` and + `.project-docs/40-domain/business-rules.md`. Proposal: distinguish completed + canonical assistant turns, a single provisional assistant bubble backed by + `design.assistant.delta`, and the prohibited generic progress/summary banner; also + record that ready conversation UI may request/reveal a Quote but only explicit + Quote-ID confirmation can create a paid Task. Evidence: the red-to-green Renderer + regressions and passing Electron Quote-confirm-Task flow in this task. Future impact: + prevents another presentation cleanup from accidentally removing supported + incremental feedback or collapsing Quote review into generation authorization. + Semantic conflict: current wording says raw delta must not be displayed as a + completed reply and forbids a separate progress bar; the new behavior preserves + both constraints by displaying exactly one explicitly unfinished reply. Human + confirmation was required and was supplied by the user before implementation. diff --git a/src/pages/ImageCanvas/DesignConversationPane.tsx b/src/pages/ImageCanvas/DesignConversationPane.tsx index edd8b23..501286b 100644 --- a/src/pages/ImageCanvas/DesignConversationPane.tsx +++ b/src/pages/ImageCanvas/DesignConversationPane.tsx @@ -1,5 +1,12 @@ import { useEffect, useRef } from 'react'; -import { Clock3, Loader2, MessageSquareText, Send, Sparkles } from 'lucide-react'; +import { + Clock3, + Loader2, + MessageSquareText, + Send, + Sparkles, + WandSparkles, +} from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; @@ -40,10 +47,20 @@ function chatFailureMessage(error: unknown): string { return '暂时没能确认这条消息的处理结果,内容还在输入框里'; } -export function DesignConversationPane({ workspace }: { workspace: DesignWorkspace }) { +export function DesignConversationPane({ + workspace, + canRequestQuote, + onQuoteOffered, +}: { + workspace: DesignWorkspace; + canRequestQuote: boolean; + onQuoteOffered: () => void; +}) { const chatDraft = useImageWorkspaceStore((state) => state.chatDraft); const setChatDraft = useImageWorkspaceStore((state) => state.setChatDraft); const sendChat = useImageWorkspaceStore((state) => state.sendChat); + const requestQuote = useImageWorkspaceStore((state) => state.requestQuote); + const assistantStreams = useImageWorkspaceStore((state) => state.assistantStreams); const pendingOperations = useImageWorkspaceStore((state) => state.pendingOperations); const scrollAnchorRef = useRef(null); const pendingChatMessages = Object.values(pendingOperations).flatMap((operation) => { @@ -62,18 +79,29 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa const submittingDesignInput = Object.values(pendingOperations).some( (operation) => operation.status === 'submitting' && operation.command.kind === 'apply_input', ); + const quoteRequestPending = Object.values(pendingOperations).some( + (operation) => operation.command.kind === 'request_quote' + && operation.command.workspaceId === workspace.workspace.workspaceId, + ); + const streamingReplies = pendingChatMessages.flatMap((message) => { + const text = assistantStreams[message.operationId]?.trim(); + return text ? [{ ...message, text }] : []; + }); const projectedDraft = chatDraft.trim() !== '' && pendingChatMessages.some((message) => message.message === chatDraft.trim()); const composerDraft = projectedDraft ? '' : chatDraft; const pendingChatKey = pendingChatMessages .map((message) => `${message.operationId}:${message.status}`) .join('|'); + const streamingReplyKey = streamingReplies + .map((reply) => `${reply.operationId}:${reply.text}`) + .join('|'); useEffect(() => { const scrollAnchor = scrollAnchorRef.current; if (typeof scrollAnchor?.scrollIntoView === 'function') { scrollAnchor.scrollIntoView({ block: 'end' }); } - }, [pendingChatKey, workspace.turns.length]); + }, [canRequestQuote, pendingChatKey, streamingReplyKey, workspace.turns.length]); const submit = () => { if (!composerDraft.trim() || submittingDesignInput) return; @@ -82,6 +110,15 @@ export function DesignConversationPane({ workspace }: { workspace: DesignWorkspa }); }; + const prepareQuote = () => { + if (!canRequestQuote || quoteRequestPending || pendingChatMessages.length > 0) return; + void requestQuote() + .then(onQuoteOffered) + .catch(() => { + toast.error('制作方案还没准备好,请稍后再试'); + }); + }; + return (
))} + {streamingReplies.map((reply) => ( +
+ {reply.text} +
+ ))} + + {canRequestQuote && pendingChatMessages.length === 0 && ( +
+
+

想法已经清楚了

+

+ 先看看需要多少设计点,确认后才会开始制作。 +

+
+ +
+ )} +
diff --git a/src/pages/ImageCanvas/DesignProductionPanel.tsx b/src/pages/ImageCanvas/DesignProductionPanel.tsx index 3c19e96..abca885 100644 --- a/src/pages/ImageCanvas/DesignProductionPanel.tsx +++ b/src/pages/ImageCanvas/DesignProductionPanel.tsx @@ -177,7 +177,11 @@ export function DesignQuotePanel({ workspace }: { workspace: DesignWorkspace }) } return ( -
+
diff --git a/src/pages/ImageCanvas/YouthCreationCard.tsx b/src/pages/ImageCanvas/YouthCreationCard.tsx index b8c8e53..85b8672 100644 --- a/src/pages/ImageCanvas/YouthCreationCard.tsx +++ b/src/pages/ImageCanvas/YouthCreationCard.tsx @@ -33,6 +33,7 @@ export type YouthCreationCardProps = { generationAvailable: boolean; onOpenFineTune: () => void; onFocusComposer: () => void; + onQuoteOffered?: () => void; }; function formatDuration(seconds: number): string { @@ -335,19 +336,23 @@ export function YouthCreationCard({ generationAvailable, onOpenFineTune, onFocusComposer, + onQuoteOffered, }: YouthCreationCardProps) { const requestQuote = useImageWorkspaceStore((state) => state.requestQuote); const pendingOperations = useImageWorkspaceStore((state) => state.pendingOperations); const model = projectYouthCreationCard(workspace, { quoteBlockers }); const requesting = Object.values(pendingOperations).some( - (operation) => operation.status === 'submitting' && operation.command.kind === 'request_quote', + (operation) => operation.command.kind === 'request_quote' + && operation.command.workspaceId === workspace.workspace.workspaceId, ); const request = () => { if (!generationAvailable || requesting || !model.canRequestQuote) return; - void requestQuote().catch(() => { - toast.error('制作方案还没准备好,请稍后再试'); - }); + void requestQuote() + .then(() => onQuoteOffered?.()) + .catch(() => { + toast.error('制作方案还没准备好,请稍后再试'); + }); }; const hasMoreDetails = model.mustInclude.length > 0 || model.mustPreserve.length > 0 diff --git a/src/pages/ImageCanvas/index.tsx b/src/pages/ImageCanvas/index.tsx index f259a61..4731569 100644 --- a/src/pages/ImageCanvas/index.tsx +++ b/src/pages/ImageCanvas/index.tsx @@ -185,6 +185,15 @@ export function ImageCanvas() { setCreationCardOpen(false); setFineTuneOpen(true); }; + const revealOfferedQuote = () => { + if (!desktopLayout) setCreationCardOpen(true); + globalThis.setTimeout(() => { + const quote = document.getElementById('design-offered-quote'); + if (typeof quote?.scrollIntoView === 'function') { + quote.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, 0); + }; const productionContent = (
@@ -270,7 +279,13 @@ export function ImageCanvas() {
- +
{desktopLayout && ( @@ -286,6 +301,7 @@ export function ImageCanvas() { generationAvailable={generationAvailable} onOpenFineTune={openFineTune} onFocusComposer={focusComposer} + onQuoteOffered={revealOfferedQuote} /> {productionContent}
@@ -309,6 +325,7 @@ export function ImageCanvas() { generationAvailable={generationAvailable} onOpenFineTune={openFineTune} onFocusComposer={focusComposer} + onQuoteOffered={revealOfferedQuote} /> {productionContent}
diff --git a/tests/e2e/image-workspace-v2.spec.ts b/tests/e2e/image-workspace-v2.spec.ts index 4bd5277..3f61ae4 100644 --- a/tests/e2e/image-workspace-v2.spec.ts +++ b/tests/e2e/image-workspace-v2.spec.ts @@ -462,8 +462,9 @@ test.describe('AI Design V2 workspace', () => { await expect(page.getByTestId('image-workspace-conversation')) .toContainText('记住了:主体更简洁,也会为标题留出空间。'); - await page.getByRole('button', { name: '看看制作方案' }).click(); + await page.getByRole('button', { name: '下一步:看看制作方案', exact: true }).click(); await expect(page.getByRole('heading', { name: '制作方案准备好了' })).toBeVisible(); + await expect(page.getByTestId('design-offered-quote')).toBeInViewport(); await expect(page.getByText('开始后会按现在的想法制作;如果再改想法,需要重新准备方案。')).toBeVisible(); await expect(page.getByLabel(/Prompt|提示词/i)).toHaveCount(0); await expect(page.getByText(/规格版本|编译器|生成方式|不可变/)).toHaveCount(0); diff --git a/tests/unit/image-canvas-page.test.tsx b/tests/unit/image-canvas-page.test.tsx index 9049664..dc3bdae 100644 --- a/tests/unit/image-canvas-page.test.tsx +++ b/tests/unit/image-canvas-page.test.tsx @@ -93,11 +93,12 @@ describe('youth AI Design Canvas page', () => { expect(screen.queryByText(/规格版本|编译器|生成策略|字段决策/)).not.toBeInTheDocument(); }); - it('keeps unfinished AI output out of the conversation while send status stays visible', () => { - prepareWorkspace(); - const unfinishedDraft = '收到,我们要收到,我们要制作一张社团活动海报。'; + it('shows one unfinished assistant reply incrementally and replaces it with the canonical turn', async () => { + const { workspace } = prepareWorkspace(); + const firstChunk = '收到,我们要制作'; + const unfinishedDraft = `${firstChunk}一张社团活动海报。`; useImageWorkspaceStore.setState({ - assistantStreams: { 'operation-chat-1': unfinishedDraft }, + assistantStreams: { 'operation-chat-1': firstChunk }, pendingOperations: { 'operation-chat-1': { id: 'operation-chat-1', @@ -124,10 +125,66 @@ describe('youth AI Design Canvas page', () => { const pendingMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-1'); expect(within(pendingMessage).getByText('做一张社团活动海报')).toBeInTheDocument(); expect(within(pendingMessage).getByText('发送中')).toBeInTheDocument(); + expect(within(conversation).queryByRole('button', { name: '下一步:看看制作方案' })) + .not.toBeInTheDocument(); expect(within(conversation).queryByTestId('design-assistant-progress')).not.toBeInTheDocument(); expect(within(conversation).queryByText('AI 正在整理你的想法')).not.toBeInTheDocument(); expect(within(conversation).getByText('我已经整理了用途、受众和初步概念,请确认右侧建议。')).toBeInTheDocument(); - expect(within(conversation).queryByText(unfinishedDraft)).not.toBeInTheDocument(); + const streamingReply = within(conversation) + .getByTestId('streaming-design-assistant-operation-chat-1'); + expect(streamingReply).toHaveTextContent(firstChunk); + + act(() => { + useImageWorkspaceStore.setState({ + assistantStreams: { 'operation-chat-1': unfinishedDraft }, + }); + }); + expect(streamingReply).toHaveTextContent(unfinishedDraft); + + act(() => { + useImageWorkspaceStore.setState({ + workspace: { + ...workspace, + turns: [...workspace.turns, { + turnId: 'turn-2', + rawTurnSequence: 2, + userMessage: '做一张社团活动海报', + assistantMessage: unfinishedDraft, + }], + }, + pendingOperations: {}, + }); + }); + + await waitFor(() => { + expect(within(conversation).queryByTestId('streaming-design-assistant-operation-chat-1')) + .not.toBeInTheDocument(); + }); + expect(within(conversation).getAllByText(unfinishedDraft)).toHaveLength(1); + expect(within(conversation).getByRole('button', { name: '下一步:看看制作方案' })) + .toBeInTheDocument(); + }); + + it('opens the mobile quote confirmation from the ready conversation without starting a paid task', async () => { + const { actions } = prepareWorkspace(); + const quotedWorkspace = designWorkspaceFixture({ + form: designFormFixture({ activeQuotes: [designQuoteFixture()] }), + }); + actions.requestQuote.mockImplementationOnce(async () => { + useImageWorkspaceStore.setState({ workspace: quotedWorkspace }); + return quotedWorkspace; + }); + render(); + + const conversation = screen.getByTestId('image-workspace-conversation'); + fireEvent.click(within(conversation).getByRole('button', { name: '下一步:看看制作方案' })); + + expect(actions.requestQuote).toHaveBeenCalledOnce(); + expect(actions.confirmGeneration).not.toHaveBeenCalled(); + await waitFor(() => { + expect(screen.getByRole('heading', { name: '制作方案准备好了' })).toBeInTheDocument(); + }); + expect(screen.getByTestId('design-offered-quote')).toBeInTheDocument(); }); it('shows a submitted message immediately and replaces it with the canonical turn', async () => { @@ -338,9 +395,13 @@ describe('youth AI Design Canvas page', () => { }); render(); + expect(within(screen.getByTestId('image-workspace-conversation')) + .getByRole('button', { name: '正在准备方案' })).toBeDisabled(); openMobileCreationCard(); expect(screen.getByText('还在确认刚才的操作')).toBeInTheDocument(); expect(screen.getByText(/不会重复制作或重复扣费/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '看看制作方案', exact: true })).toBeDisabled(); + expect(actions.requestQuote).not.toHaveBeenCalled(); fireEvent.click(screen.getByRole('button', { name: '查看原来的结果' })); expect(actions.retryOperation).toHaveBeenCalledWith('operation-1'); }); diff --git a/tests/unit/image-workspace-store.test.ts b/tests/unit/image-workspace-store.test.ts index 2d29916..18aea60 100644 --- a/tests/unit/image-workspace-store.test.ts +++ b/tests/unit/image-workspace-store.test.ts @@ -523,6 +523,25 @@ describe('V2 Living Form store', () => { const source = await loadedStore(); useImageWorkspaceStore.getState().setFieldDraft('intent.purpose', '本地草稿'); useImageWorkspaceStore.setState({ + assistantStreams: { 'operation-chat-unknown': '这段回复还没确认' }, + pendingOperations: { + 'operation-chat-unknown': { + id: 'operation-chat-unknown', + label: '发送创作想法', + command: { + kind: 'apply_input', + workspaceId: 'workspace-1', + sessionId: 'session-1', + expectedDirectionRevision: 4, + clientOperationId: 'operation-chat-unknown', + input: { kind: 'chat', message: '保留这条未知结果' }, + }, + status: 'unknown', + error: '结果尚未确认', + clearDraftPaths: [], + clearChatDraft: true, + }, + }, quoteBlockers: [{ code: 'missing_choice', message: '还需要选择作品形状', @@ -573,9 +592,12 @@ describe('V2 Living Form store', () => { }), }); + expect(useImageWorkspaceStore.getState().assistantStreams).toEqual({ + 'operation-chat-unknown': '这段回复还没确认', + 'operation-chat-1': '正在整理', + }); expect(useImageWorkspaceStore.getState()).toMatchObject({ lastEventId: 'session-1:8', - assistantStreams: { 'operation-chat-1': '正在整理' }, fieldDrafts: { 'intent.purpose': '本地草稿' }, quoteBlockers: [], workspace: { form: { directionRevision: 5, specificationRevision: 4 } }, diff --git a/tests/unit/youth-creation-card.test.tsx b/tests/unit/youth-creation-card.test.tsx index 839f201..b603bfe 100644 --- a/tests/unit/youth-creation-card.test.tsx +++ b/tests/unit/youth-creation-card.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'; +import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { YouthCreationCard } from '@/pages/ImageCanvas/YouthCreationCard'; import { DesignTaskPanel } from '@/pages/ImageCanvas/DesignProductionPanel'; @@ -21,12 +21,14 @@ function renderCard( quoteBlockers?: DesignCompilationIssue[]; onOpenFineTune?: () => void; onFocusComposer?: () => void; + onQuoteOffered?: () => void; } = {}, ) { const requestQuote = vi.fn().mockResolvedValue(workspace); const resolveDecisionPrompt = vi.fn().mockResolvedValue(workspace); const onOpenFineTune = options.onOpenFineTune ?? vi.fn(); const onFocusComposer = options.onFocusComposer ?? vi.fn(); + const onQuoteOffered = options.onQuoteOffered ?? vi.fn(); useImageWorkspaceStore.setState({ workspace, quoteBlockers: [], @@ -41,6 +43,7 @@ function renderCard( generationAvailable={options.generationAvailable ?? true} onOpenFineTune={onOpenFineTune} onFocusComposer={onFocusComposer} + onQuoteOffered={onQuoteOffered} />, ); return { @@ -49,6 +52,7 @@ function renderCard( resolveDecisionPrompt, onOpenFineTune, onFocusComposer, + onQuoteOffered, }; } @@ -97,8 +101,8 @@ describe('YouthCreationCard', () => { vi.clearAllMocks(); }); - it('shows a plain creation summary and offers a youth-facing ready action', () => { - const { requestQuote } = renderCard(); + it('shows a plain creation summary and reveals the offered quote after requesting it', async () => { + const { onQuoteOffered, requestQuote } = renderCard(); expect(screen.getByRole('heading', { name: 'AI 听懂的想法' })).toBeInTheDocument(); expect(screen.getByText('会跟着聊天自动更新,不需要逐项填写。')).toBeInTheDocument(); @@ -112,6 +116,7 @@ describe('YouthCreationCard', () => { fireEvent.click(screen.getByRole('button', { name: '看看制作方案' })); expect(requestQuote).toHaveBeenCalledOnce(); + await waitFor(() => expect(onQuoteOffered).toHaveBeenCalledOnce()); }); it('does not surface legacy decision prompts as a form', () => { From 336e0bb0caf24537b7b0f350aba3e04a37f5544c Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 11:46:11 +0800 Subject: [PATCH 5/5] docs: integrate design stream and quote handoff --- .../20-architecture/system-overview.md | 4 +- .project-docs/30-worklog/current-state.md | 21 ++++ ...260904-integrate-design-stream-5e7c2a91.md | 114 ++++++++++++++++++ .project-docs/40-domain/business-rules.md | 6 +- 4 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260904-integrate-design-stream-5e7c2a91.md diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index f0f5500..588e5d1 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -26,7 +26,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 | Official Hosted Plugins | Acquired code-owned bundled Game Resource package → effective parent snapshot → code-owned Main adapter → fixed Works Square hosted route | 无设备下载、更新、Beta 或签名步骤;Renderer/Package/Pi 不持有 Provider key、model 或 URL。每次计费操作要求显式确认,child 不继承 hosted tool。历史 Hosted Web Search 不再进入当前客户端。 | | Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`;没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 拥有桌面用户权限且生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension;新/idle parent 自动刷新,active parent 在 turn settled 后刷新,child 始终为空。 | | AI Design Workspace & Living Form | 一个 Workspace 的当前 Direction、Current Specification、持久 Agent Session、conversation timeline、Tasks 与 Assets | 自然对话是主创作面;Living Form 仅以“AI 已理解”的紧凑辅助摘要与可选手动调整投影服务端 Current Specification,Renderer 只持有草稿和已接受投影 | -| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,raw assistant delta 保持内部且不生成对话区进度栏 | +| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,assistant delta 只能在匹配该 operation 的一个未完成助手气泡中临时绘制且不生成独立整理进度栏 | | AI Design Gateway Routing | Main-owned Works Square V2 adapter 与 Direction event stream | Main 持有 Works Token、stream ticket、WebSocket、重试分类和错误脱敏;事件顺序与 Task progress 不构成 Specification 真值 | | AI Design Quote & Task Controls | 精确 Specification revision 编译出的不可变 Quote 与 Workspace Tasks | 客户端只展示 public output summary、warnings、expiry 与 Token Points,并以 Quote ID 确认;Provider Prompt/model/route/storage/billing atoms 不下发 | | AI Design Assets | Workspace 已完成作品或本地上传的真实 Asset | Asset 通过 typed binding 写入 Specification;生成结果经 Main-owned asset download 保存 | @@ -89,7 +89,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - Main-owned Canvas Workspace JSON 请求和 shared Works token refresh 的完整生命周期最多 30 秒,超时 abort transport 并以固定 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT` 结束 Renderer 等待。Electron `net.fetch` 失败后的 Node fetch 透明回退只允许 `GET`、`HEAD`、`OPTIONS`;PATCH/POST 等 mutation 只允许由持有显式幂等身份的上层协议决定重试,不能由底层 transport 隐式重放。该边界不改变上面的 WebSocket→REST 幂等 fallback。 - Renderer 的异步与流式结果必须核对 Workspace、Direction、revision 和 operation identity;Direction snapshot 是 Specification 真值,Task/Asset 事件只更新 Workspace resource projection。 - 待提交 chat 可以从现有 pending operation 立即投影为明确标注“发送中/正在确认”的用户气泡,但只有服务端 canonical turn 能进入对话历史;确定失败必须恢复原草稿,不能另建一套消息状态或新业务意图。 -- `design.assistant.delta` 是未完成的内部传输状态,不是助手回复。Renderer 不得显示原始片段,也不得在对话区为它创建独立整理进度栏;右侧 Current Specification 投影继续呈现 AI 当前理解。重叠连接与重放 chunk 必须按连接 generation 和 `chunkIndex` 收敛。 +- `design.assistant.delta` 是未完成的传输状态,不是 canonical assistant turn。Renderer 只能把它临时绘制为与同一 pending chat identity 绑定的单个未完成助手气泡,并在该 operation 确定收敛后由 canonical turn 替换;不得持久化为第二条消息、伪装为已完成回复或创建独立整理进度栏。unknown outcome 保留原 identity 与已有片段,其他 operation 的更新不得全局清除它;右侧 Current Specification 继续呈现 AI 当前整理出的设计理解。重叠连接与重放 chunk 必须按连接 generation 和 `chunkIndex` 收敛。 - 确认生成只提交不可变 Quote ID。Task 事务已提交但事件迟到时可刷新 Workspace projection;Task progress 不得改写 Living Form 或授权新的生成操作。 - Updater feed 选择、原始错误日志、下载和安装生命周期只属于 Electron Main。Renderer 不得把缺失稳定 manifest 投影为“已是最新版”,也不得显示原始堆栈、URL、路径或错误码;并发检查共享同一错误事件时只发送一次错误状态,后续独立重试仍可重新报告。 - 图片与视频复用同一个单图来源选择器。图片 Brief 可从当前 Workspace 的已完成作品或本地上传中选择一张参考图继续生成;视频 Brief 使用同一入口绑定首帧。两条路径都必须通过现有 Workspace Asset 上传/选择契约提交一个真实 `attachmentAssetIds`,不得用本地路径或自然语言描述代替资产身份。 diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 9aaec3a..2404612 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,6 +4,27 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through +- AI Design streamed-reply diagnosis + `229b1b1ce39b7f1541a93ea3c980ab82b6d6266c` and client fix + `23f96a523eb37d8397bb3766ce95a59d56e59225` from tasks + `20260904-diagnose-design-stream-6a4e9c21` and + `20260904-fix-design-stream-4f7b91c2` are integrated onto local `main` and + canonically reconciled by task `20260904-integrate-design-stream-5e7c2a91`. + The existing post-validation `design.assistant.delta` transport is visible again as + exactly one provisional assistant-shaped bubble tied to its pending chat identity; + canonical `workspace.turns` still owns history and replaces the unfinished bubble, + while unknown outcomes keep the same identity and partial reply. The removed generic + “AI 正在整理你的想法” banner is not restored. A ready conversation now offers an + inline Quote step; successful Quote requests reveal the confirmation on desktop or + open it on mobile, but only explicit immutable Quote-ID confirmation creates a paid + Task. Unknown Quote requests disable both entry points and retry the original + operation. Focused tests passed 35/35, the Electron Quote-confirm-Task flow passed + 1/1, typecheck, scoped lint, Vite build, document gates, and independent review + passed. The ordinary full suite had one unrelated Pi real-process 2-second timing + miss among 1,873 passing tests and 2 skips; that exact test passed immediately in + isolation, and the pressure test passed 1/1. Provider-native first-token streaming + remains a separate server follow-up; no server, database, billing, Plugin, + packaging, deployment, publication, or remote push changed. - AI Design conversation-feedback sources `403236115bf81e7617856cd8219b1be23f6abbb8` from task `20260904-design-summary-separation-9c4e7a21` and diff --git a/.project-docs/30-worklog/tasks/20260904-integrate-design-stream-5e7c2a91.md b/.project-docs/30-worklog/tasks/20260904-integrate-design-stream-5e7c2a91.md new file mode 100644 index 0000000..37ed79e --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-integrate-design-stream-5e7c2a91.md @@ -0,0 +1,114 @@ +# Task: Integrate AI Design streamed replies and quote handoff + +## Identity + +- Task ID: 20260904-integrate-design-stream-5e7c2a91 +- Mode: Integration +- Branch: codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21 +- Base commit: 23f96a523eb37d8397bb3766ce95a59d56e59225 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Integrate diagnosis commit `229b1b1ce39b7f1541a93ea3c980ab82b6d6266c` + and implementation commit `23f96a523eb37d8397bb3766ce95a59d56e59225` + from tasks `20260904-diagnose-design-stream-6a4e9c21` and + `20260904-fix-design-stream-4f7b91c2` onto local client `main`. +- Reconcile the accepted promotion candidate into `current-state.md`, the AI Design + architecture boundary, and business rules: a delta may appear only as one visibly + unfinished assistant reply; Quote review remains separate from paid generation + confirmation. +- Preserve the three known foreign untracked task records in the root checkout exactly + as found. Do not modify the Works Square server, billing, Plugin navigation, + packaging, publication, remote refs, or any unrelated client feature. + +## Intent And Constraints + +- The user explicitly requested this completed client fix be merged to the main branch + and had already confirmed the product decision behind the canonical wording change. +- Root `main` was verified at exact head + `aa3f52a8f8414c9ad939d396bd2828c3e07c70e7`, with only the three previously known + untracked records. The source is a linear descendant of that frontier, so use a + fast-forward after canonical reconciliation; do not stash, reset, clean, add, or + adopt the foreign records. +- Keep `workspace.turns` as canonical conversation history. The provisional stream is + presentation tied to an existing pending chat identity, never a second completed + turn or generic progress banner. +- Keep production authorization two-step: Quote request/reveal may be prompted by the + conversation, while only explicit confirmation of the immutable Quote ID creates a + paid Task. + +## Project Context Loaded + +- Concurrent Task Gate passed for integration task + `20260904-integrate-design-stream-5e7c2a91`, owner `codex-root`, mode Integration, + branch `codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream`, + worktree + `D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21`, + base `23f96a523eb37d8397bb3766ce95a59d56e59225`; the repository Integration lock is held. +- Loaded the startup memory set, current integrated state, system overview, business + rules, ADR-007 context, and both completed source task records including outcome, + verification, follow-up, and promotion candidates. +- Relevant active peers are unrelated historical/stale task records. The exclusive + Integration lock prevents concurrent canonical reconciliation; no peer owns a + conflicting current AI Design product decision. +- The old canonical rule hid raw delta text entirely. The accepted source narrows that + rule without weakening authority: exactly one provisional bubble may paint the + already validated assistant text, while canonical turns, Current Specification, + stable operation identity, Quote identity, and explicit payment confirmation remain + unchanged. The user's confirmation resolves the semantic conflict. +- Planning Gate result: Passed. + +## Plan + +1. Reconcile the accepted AI Design stream/Quote behavior into canonical architecture, + business rules, and current-state integration evidence. +2. Re-run task-aware document and source verification at the final integration head. +3. Commit the integration record, fast-forward exact root `main`, verify the three + foreign untracked files are unchanged, then complete and release the Integration + lock. + +## Outcome + +- Accepted and reconciled both source tasks on top of exact local `main` frontier + `aa3f52a8f8414c9ad939d396bd2828c3e07c70e7`; their commit chain is linear and + contains no unrelated product changes. +- Updated the canonical architecture and business rules to distinguish one + operation-bound unfinished assistant bubble from canonical conversation history + and from the prohibited generic progress banner. Unknown operations retain their + identity/partial text and cannot be erased by unrelated updates. +- Recorded the ready-conversation Quote handoff: desktop reveals the offered Quote, + mobile opens it, and neither path creates a Task until the user explicitly confirms + the immutable Quote ID. Unknown Quote requests keep and retry the original operation + instead of creating another intent. +- Updated `current-state.md` with source commits, behavior, verification, the known + post-validation streaming limitation, and the explicit no-server/no-billing scope. +- Prepared the integrated branch for a fast-forward of root `main`; the root's three + pre-existing untracked task records remain outside this commit and are not adopted, + staged, modified, moved, or deleted. + +## Verification + +- Source task evidence: focused 35/35, Electron Quote-confirm-Task 1/1, typecheck, + scoped ESLint, Vite build, diff check, pressure 1/1, and independent final review + passed. The full suite's only miss was an unrelated Pi process 2-second timing check, + which passed immediately in isolation. +- Integration-head rerun: focused Renderer/store/Card suite 3 files / 35 tests passed; + `pnpm exec tsc --noEmit` passed. +- Integration-head Electron flow passed 1/1 and again exercised the conversation CTA, + Quote visibility, explicit 12-Design-Point confirmation, and queued Task projection. +- `git diff --check`, `check_project_docs.py`, and task-aware + `check_doc_drift.py --task-id 20260904-integrate-design-stream-5e7c2a91` passed. + +## Follow-ups + +- Provider-native first-token streaming still requires a separately scoped server + contract; this integration only exposes the existing validated-message chunk + projection honestly. + +## Promotion Candidates + +- None. The source candidates were accepted by the user and applied to canonical + architecture, domain rules, and current state in this integration task. diff --git a/.project-docs/40-domain/business-rules.md b/.project-docs/40-domain/business-rules.md index 485e381..df5de0e 100644 --- a/.project-docs/40-domain/business-rules.md +++ b/.project-docs/40-domain/business-rules.md @@ -100,11 +100,11 @@ - 每个 mutation 使用稳定 command 与 semantic operation identity。transport-unknown 只能重放原命令;business rejection、timeout 或用户再次点击不能自动生成新的业务意图。 - Direction projection 是 Specification 真值。event cursor、assistant delta、Task progress 与 Asset event 只用于传输/资源收敛,不得推进或覆盖 canonical specification revision。 - 用户提交 chat 后,客户端可以立即把同一个 pending operation 投影为临时用户气泡,并明确显示“发送中”或“正在确认”;只有服务端返回的 canonical turn 才进入 conversation timeline。确定失败时必须让原草稿重新可编辑,不得把临时投影持久化为第二条消息。 -- `design.assistant.delta` 只用于内部传输和结果收敛;原始片段不得作为完成的助手回复展示,对话区也不得为其增加独立的整理进度栏。AI 当前整理出的设计理解由右侧 Current Specification 投影承载;连接重叠或事件重放按 connection generation 与 `chunkIndex` 去重收敛。 +- `design.assistant.delta` 只用于未完成回复的传输和结果收敛,不得进入 canonical conversation timeline。客户端可以把它临时显示为与同一 pending chat identity 绑定的单个未完成助手气泡;确定收敛后必须由 canonical turn 替换,unknown outcome 则保留原 identity 与已有片段。不得把片段伪装为完成回复、持久化为第二条消息、因其他 operation 更新而全局清除,或另加独立的整理进度栏。AI 当前整理出的设计理解仍由右侧 Current Specification 投影承载;连接重叠或事件重放按 connection generation 与 `chunkIndex` 去重收敛。 - Main 只向 Renderer 投影已知错误码的固定中文提示,未知上游错误文本必须脱敏为通用提示;Works Token、stream ticket、provider internals 留在 Main/服务端。 - AI 绘画 Workspace JSON 请求与 shared Works token refresh 必须覆盖取凭据、发请求和读取响应 body 的完整 30 秒 deadline;即使底层 transport 忽略 abort,调用方也必须确定性结束为 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT` 并释放共同等待者。Electron-to-Node 透明 fallback 仅允许 `GET`/`HEAD`/`OPTIONS`;mutation 不得因 transport failure 被隐式重放,任何重试必须由上层显式幂等合同授权。 -- `design.quote.request` 必须绑定 exact current Specification revision 并返回 immutable Quote;任何 production-meaning edit 都需要新 revision 与新 Quote。 -- `design.generation.confirm` 只提交 Quote identity。客户端不编辑 provider Prompt/model/route/storage,不计算 Token Points,也不把 Task recovery 当作再次确认授权。 +- `design.quote.request` 必须绑定 exact current Specification revision 并返回 immutable Quote;任何 production-meaning edit 都需要新 revision 与新 Quote。对话或 Current Specification 就绪态可以引导用户请求并查看 Quote,桌面端可定位到报价区、移动端可打开报价面板,但该动作不得创建 Task。 +- `design.generation.confirm` 只提交用户明确确认的 Quote identity。客户端不编辑 provider Prompt/model/route/storage,不计算 Token Points,也不把聊天就绪文案、Quote 展示、重复点击或 Task recovery 当作生成授权。 - 图片/视频 source 必须是当前 Workspace 的 canonical Asset,并通过 typed binding 进入 Specification;medium/role 决定用途,不得从 quick-reply 文案、V1 Brief 或本地路径推断。 - Task/Asset reconciliation 只能更新 Workspace resources;已经落库但事件迟到的 Task 可通过 refresh 恢复,不能覆盖 Living Form、local drafts 或 pending Design input。 - 删除 Canvas 项目必须要求用户完整输入项目名并通过 Main-owned Workspace DELETE。成功后被删 Workspace 的会话、任务和资产不得继续留在 Renderer 可访问状态;服务端决定软删除、任务取消、预留积分释放和运行中任务结算。