diff --git a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.i18n.yaml b/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.i18n.yaml deleted file mode 100644 index e5e46fe351..0000000000 --- a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.i18n.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.md -/: - en: a99282dc38a7dfeb - zh: 6f6ee2170907eb26 -/composer-prefill-method-on-input-actions: - en: 976e77fa72fdfdd2 - zh: dbaa50bd205dc1f3 -/composer-prefill-method-on-input-actions/change: - en: 5186bc4ec9254340 - zh: ff0e406a96d5c50b -/composer-prefill-method-on-input-actions/migration: - en: 21625abfcb53c507 - zh: 4a6f0aaa04eb3a2c diff --git a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.md b/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.md deleted file mode 100644 index b275cb5754..0000000000 --- a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -kind: upgrade-guide -description: "Client code that builds InputActions or implements SessionInput must provide the required prefill method." ---- - -# Composer prefill method on input actions - -English | [中文](guide.zh.md) - -## Change - -`InputActions` and `SessionInput` in `@deepseek-ai/dsh-client-ui-conversation/client` now require `prefill(text, options?)`, and the package exports `ComposerPrefillMode` and `ComposerPrefillOptions`. Slot components and Session-scope plugins read prefill from the harness, so their call sites compile unchanged. Code that constructs an `InputActions` value, or implements `SessionInput`, including test doubles passed to client components, no longer satisfies the interfaces until it adds the member. The method returns whether the draft accepted the write; returning `false` is valid for doubles that do not support prefill. - -## Migration - -1. Add `prefill` to every hand-written `InputActions` or `SessionInput` object. Forward to the harness-provided action, or return `false` when the double does not support writing. -2. Import `ComposerPrefillOptions` or `ComposerPrefillMode` from `@deepseek-ai/dsh-client-ui-conversation/client` when a signature needs them. -3. Rebuild custom Client plugins and their test suites, then run the type check. A passing check confirms the interface is satisfied; call `inputActions.prefill('text')` from a slot component to verify the harness path when the object forwards the operation. diff --git a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.zh.md b/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.zh.md deleted file mode 100644 index baad8a7de4..0000000000 --- a/docs/upgrade-guide/v0.2.2-alpha.2/composer-prefill-method/guide.zh.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -kind: upgrade-guide -description: "手写 InputActions 或实现 SessionInput 的 Client 代码必须提供必选方法 prefill。" ---- - -# 输入操作上的 Composer 预填方法 - -[English](guide.md) | 中文 - -## 变更 - -`@deepseek-ai/dsh-client-ui-conversation/client` 中的 `InputActions` 和 `SessionInput` 现在要求提供 `prefill(text, options?)`,该包同时导出 `ComposerPrefillMode` 和 `ComposerPrefillOptions`。Slot 组件与 Session 作用域插件从 Harness 读取预填操作,调用方无需改动即可编译。构造 `InputActions` 值或实现 `SessionInput` 的代码(包括传给 Client 组件的测试替身)必须新增该成员才能满足接口。该方法返回草稿是否接受写入;不支持预填的替身可以返回 `false`。 - -## 迁移 - -1. 在所有手写的 `InputActions` 或 `SessionInput` 对象中加入 `prefill`。转发到 Harness 提供的操作,或在替身不支持写入时返回 `false`。 -2. 签名需要时,从 `@deepseek-ai/dsh-client-ui-conversation/client` 导入 `ComposerPrefillOptions` 或 `ComposerPrefillMode`。 -3. 重新构建自定义 Client 插件及其测试,然后运行类型检查。检查通过即表示接口已满足;当对象转发该操作时,可从 Slot 组件调用 `inputActions.prefill('text')` 验证 Harness 路径。 diff --git a/packages/client/ui-chat/tests/chat-view.client.spec.tsx b/packages/client/ui-chat/tests/chat-view.client.spec.tsx index aab9a1d2da..2bc8286e93 100644 --- a/packages/client/ui-chat/tests/chat-view.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-view.client.spec.tsx @@ -525,7 +525,6 @@ function makeHarness( captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: () => {}, - prefill: () => false, persistDraft: () => {}, addAttachments: () => true, removeAttachment: () => {}, diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index eeae778f15..f17358e4b5 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -12,8 +12,8 @@ en: 8811c28013fdc6dc zh: 0a86edc422b38ba8 /deepseek-ai-dsh-client-ui-conversation/table-of-contents: - en: c1ca3d169bea2ccc - zh: 1892939a2ec76e1e + en: eef4f18c3972ccc4 + zh: 6be65c6ac84ce497 /deepseek-ai-dsh-client-ui-conversation/conversation-assembly: en: ef43c4f149195939 zh: 49fc6c83fd3a05ce @@ -24,11 +24,8 @@ en: 99ec447f0a6bda48 zh: 922cf64666676530 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: - en: 9acb3b1d7f279d97 - zh: 8544b7a140437553 -/deepseek-ai-dsh-client-ui-conversation/plugin-prefill-api: - en: 0d16e9363a821bc6 - zh: eca2ba2076a3ca9d + en: 59cd63057dfb6eeb + zh: b2e2c86e810b7bb8 /deepseek-ai-dsh-client-ui-conversation/model-experience: en: e0a05b73ba3e4318 zh: dc85ab8433bb4456 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 7982114736..14f54dc287 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -19,7 +19,6 @@ Desktop product events use the optional [product analytics service](../product-a - [Sent user text](#sent-user-text) - [Shell and standard props](#shell-and-standard-props) - [Temporary composer entries](#temporary-composer-entries) -- [Plugin prefill API](#plugin-prefill-api) - [Model Experience](#model-experience) - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) - [Dev Note](#dev-note) @@ -187,15 +186,6 @@ The selector must be a pure function of the owner currency. Its non-null return Session title context gestures use the shared [context menu](../ui-context-menu/README.md) with the clicked breadcrumb’s Session. The reusable image gallery receives `getTriggerProps` from its Factory’s injected `useContextMenu` hook. - -## Plugin prefill API - -A plugin can write reviewable text into the current composer through the stable `prefill` action. Slot components receive it as `inputActions.prefill(text, options)`. A session-scope plugin resolves the same action with `ctx.conversation.input.for(actx).prefill(text, options)`. - -The exported `ComposerPrefillOptions` type and `InputActions.prefill` signature are defined in the [input contracts](src/client/contract/input.ts). - -An empty string is ignored and returns `false`. An empty or whitespace-only draft, or `mode: 'replace'`, replaces the whole draft only while the composer is in the plain phase and the session is live; that replacement is its own undo step. Otherwise the text replaces the current selection in one undo step, preceded by `separator` when the editor text before the selection ends in non-whitespace; the separator defaults to a newline and an empty separator inserts verbatim. A rejected write — a non-plain replacement, a changed draft revision, a locked submission phase, or a disposed session — returns `false` without changing the draft. `prefill` never sends the draft. - ## Model Experience diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index fe33ee7860..473e15825e 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -19,7 +19,6 @@ kind: "package-reference" - [已发送用户文本](#sent-user-text) - [Shell 与标准 props](#shell-and-standard-props) - [临时 composer entry](#temporary-composer-entries) -- [插件预填 API](#plugin-prefill-api) - [模型体验](#model-experience) - [已知限制与暂缓事项](#known-limitations-and-deferred-work) - [开发备注](#dev-note) @@ -187,15 +186,6 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match 会话标题右键使用共享[右键菜单](../ui-context-menu/README.zh.md),目标是被点击面包屑所属会话。可复用图片库从其 Factory 注入的 `useContextMenu` hook 获取 `getTriggerProps`。 - -## 插件预填 API - -插件可以通过稳定的 `prefill` 操作把可检查的文本写入当前 Composer。Slot 组件收到的是 `inputActions.prefill(text, options)`;Session 作用域的插件通过 `ctx.conversation.input.for(actx).prefill(text, options)` 解析同一个操作。 - -导出的 `ComposerPrefillOptions` 类型和 `InputActions.prefill` 签名定义在[输入接口](src/client/contract/input.ts)中。 - -空字符串会被忽略并返回 `false`。草稿为空或只有空白,或者使用 `mode: 'replace'` 时,只有在 Composer 处于 plain 阶段且 Session 仍存活时才替换整个草稿;这次替换本身就是独立的撤销步骤。否则文本会替换当前选区,作为一次可撤销编辑;若选区前的编辑器文本末尾不是空白,则先插入 `separator`。`separator` 默认是换行,传空字符串时原样插入。非 plain 阶段的替换、草稿版本已变、提交阶段锁定或 Session 已释放时,写入会被拒绝并返回 `false`,草稿保持不变。`prefill` 不会发送草稿。 - ## 模型体验 diff --git a/packages/client/ui-conversation/src/client/contract/input.ts b/packages/client/ui-conversation/src/client/contract/input.ts index e0c93121e1..82f030c775 100644 --- a/packages/client/ui-conversation/src/client/contract/input.ts +++ b/packages/client/ui-conversation/src/client/contract/input.ts @@ -187,15 +187,6 @@ export interface SessionInput extends InputTarget { /** Replace the whole draft with plain text, removing inline reference chips. */ setDraft(text: string): void - /** - * Write plugin-owned text into the draft in one undo step. This is the - * session-scope entry to the same prefill operation exposed as - * {@link InputActions.prefill} to slot components. - * @param text - text to write; an empty string is ignored. - * @param options - replacement mode and insertion separator. - * @returns whether the draft accepted the write. - */ - prefill(text: string, options?: ComposerPrefillOptions): boolean /** Append ordered browser-owned attachment ids; busy admission phases refuse. */ addAttachments(ids: readonly DraftAttachmentId[]): boolean /** Remove one browser-owned attachment id; busy admission phases refuse. @returns whether the id was removed. */ @@ -258,20 +249,6 @@ export interface DraftInitializationOptions { /** An initialization request never waits for a React mount or a remote catalog. */ export type DraftInitializationResult = 'applied' | 'preserved' | 'blocked' -/** How a composer prefill treats existing draft text. */ -export type ComposerPrefillMode = 'insert' | 'replace' - -/** Options for a plugin-owned composer prefill. */ -export interface ComposerPrefillOptions { - /** Replace the whole draft instead of inserting at the caret; defaults to `insert`. */ - readonly mode?: ComposerPrefillMode - /** - * Text placed before an inserted prefill when the caret follows non-whitespace. - * Defaults to a newline; an empty string inserts verbatim. Ignored when replacing. - */ - readonly separator?: string -} - /** * The public input action face provided to every session-scope slot * component: stable-identity callbacks, mirroring the @@ -292,19 +269,6 @@ export interface InputActions { insertText(text: string, span: TokenSpan): boolean /** Replace the whole draft with plain text, removing inline reference chips. */ setDraft(text: string): void - /** - * Write plugin-owned text into the draft in one undo step. An empty or - * whitespace-only draft, or `mode: 'replace'`, replaces the draft only while - * the session is live and the composer is in the plain phase; otherwise the - * current selection is replaced by an insertion at the selection, preceded - * by `separator` when the editor text before it ends in non-whitespace. - * Replacement edits are independent undo steps. - * @param text - text to write; an empty string is ignored. - * @param options - replacement mode and insertion separator. - * @returns whether the draft accepted the write; false for an empty string, - * a non-plain replacement, a changed draft revision, or a disposed session. - */ - prefill(text: string, options?: ComposerPrefillOptions): boolean /** Write the current semantic document through the mounted persistence callback. */ persistDraft(): void /** Append ordered browser-owned attachment ids; busy admission phases refuse. */ diff --git a/packages/client/ui-conversation/src/client/index.ts b/packages/client/ui-conversation/src/client/index.ts index a2eb26cd6f..7a39200c30 100644 --- a/packages/client/ui-conversation/src/client/index.ts +++ b/packages/client/ui-conversation/src/client/index.ts @@ -98,8 +98,7 @@ export type { UseConversationViews, } from './contract/slots.ts' export type { - BeginCommandRequest, CommandClaim, ComposerPrefillMode, ComposerPrefillOptions, - ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, + BeginCommandRequest, CommandClaim, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, DraftInitializationResult, InputActions, InputState, InsertReferenceRequest, InsertTextRequest, PickOutcome, SessionInput, SessionInputResolver, SubmitAttachment, SubmitOutcome, diff --git a/packages/client/ui-conversation/src/client/input/editor/runtime.ts b/packages/client/ui-conversation/src/client/input/editor/runtime.ts index 1a66543018..f162943b6f 100644 --- a/packages/client/ui-conversation/src/client/input/editor/runtime.ts +++ b/packages/client/ui-conversation/src/client/input/editor/runtime.ts @@ -205,26 +205,6 @@ export class DraftEditorRuntime { }, { discrete: true, tag: HISTORY_MERGE_TAG }) } - /** - * Replace the whole draft as one independent undo step at the top level. - * Unlike the seed {@link setDraft}, this edit carries a history boundary so - * the user can undo the plugin prefill without undoing an earlier seed or - * edit. Nested calls join their enclosing editor transaction. - * @param text - the full next draft. - */ - replaceDraft(text: string): void { - const clean = text.replace(REFERENCE_PLACEHOLDER_RE, '') - if (clean === this.projection.clipboardText && this.projection.occurrences.length === 0 && this.projection.quotes.length === 0) return - // An empty editor has no earlier entry to undo to; seed one before the first prefill. - if (this.editor.getEditorState().isEmpty()) { - this.applyEdit(() => { $getRoot().append($createParagraphNode()) }, HISTORY_MERGE_TAG) - } - this.applyEdit(() => { - $getRoot().clear() - $appendParagraphs(clean) - }, PASTE_TAG) - } - /** * Add plain text after the current draft as new paragraphs, keeping its text * and references; an empty draft takes the text as its whole content. @@ -273,15 +253,9 @@ export class DraftEditorRuntime { * @returns the ordered [start, end) span in detect coordinates. */ caretSpan(): { start: number; end: number } { - let span = { start: 0, end: 0 } - // A snapshot read misses uncommitted native selection. A discrete update - // synchronizes it; nested calls read the enclosing update's pending state. - this.applyEdit(() => { - const projection = $projectComposer(key => this.occurrenceIdOf(key)) - const at = projection.detectText.length - span = projection.selection ?? { start: at, end: at } - }) - return span + if (this.projection.selection !== null) return this.projection.selection + const at = this.projection.detectText.length + return { start: at, end: at } } /** diff --git a/packages/client/ui-conversation/src/client/input/facade.ts b/packages/client/ui-conversation/src/client/input/facade.ts index 2012a02eeb..6306216b1c 100644 --- a/packages/client/ui-conversation/src/client/input/facade.ts +++ b/packages/client/ui-conversation/src/client/input/facade.ts @@ -15,7 +15,7 @@ import { } from '@deepseek-ai/dsh-client-store' import type { LexicalEditor } from 'lexical' import type { - CommandClaim, ComposerPrefillOptions, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, DraftInitializationResult, + CommandClaim, ConsumeTokenRequest, DraftAttachmentId, DraftInitializationOptions, DraftInitializationResult, InputActions, InputEffect, InputNotice, InputState, InputTriggerController, PickOutcome, SessionInput, SubmitAttempt, SubmitAttachment, SubmitOutcome, } from '../contract/input.ts' @@ -148,7 +148,6 @@ export class SessionInputShell implements SessionInput { return this.draftEditor.insertAsyncText(span, text) }, setDraft: (text) => { this.setDraft(text) }, - prefill: (text, options) => this.prefill(text, options), persistDraft: () => { this.persistCurrentDraft() }, addAttachments: ids => this.addAttachments(ids), removeAttachment: (id) => { this.removeAttachment(id) }, @@ -239,27 +238,6 @@ export class SessionInputShell implements SessionInput { return inserted } - /** - * Write plugin-owned text into the draft in one undo step. - * @param text - text to write; an empty string is ignored. - * @param options - replacement mode and insertion separator. - * @returns whether the draft accepted the write. - */ - prefill(text: string, options: ComposerPrefillOptions = {}): boolean { - if (text.length === 0) return false - if ((options.mode ?? 'insert') === 'replace' || this.snapshot.draft.trim() === '') { - if (this.snapshot.phase !== 'plain' || this.disposed) return false - this.cancelCommandRestore() - this.draftEditor.replaceDraft(text) - return true - } - const span = this.actions.captureInsertion() - const separator = options.separator ?? '\n' - const preceding = this.projection.detectText.slice(0, span.start) - const prefix = separator.length > 0 && span.start > 0 && !/\s$/.test(preceding) ? separator : '' - return this.actions.insertText(`${prefix}${text}`, span) - } - /** * Reconnect optional catalog notifications and resume a pending command * chip restore without waiting for another edit. diff --git a/packages/client/ui-conversation/tests/draft-document.client.spec.ts b/packages/client/ui-conversation/tests/draft-document.client.spec.ts index 44b67bf92b..8e50e1ad3a 100644 --- a/packages/client/ui-conversation/tests/draft-document.client.spec.ts +++ b/packages/client/ui-conversation/tests/draft-document.client.spec.ts @@ -818,44 +818,6 @@ describe('draft documents in the input shell', () => { ) }) -describe('plugin prefill history', () => { - it.each(['insert', 'replace'] as const)('keeps the first %s prefill undoable before the editor mounts', async (mode) => { - const shell = makeShell() - expect(shell.editor.getRootElement()).toBeNull() - expect(shell.prefill('First plugin draft', { mode })).toBe(true) - shell.editor.dispatchCommand(UNDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: '', references: [] }) }) - shell.editor.dispatchCommand(REDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First plugin draft', references: [] }) }) - }) - - it('keeps consecutive unmounted prefills as separate undo steps', async () => { - const shell = makeShell() - expect(shell.prefill('First')).toBe(true) - expect(shell.prefill('Second', { mode: 'replace' })).toBe(true) - shell.editor.dispatchCommand(UNDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First', references: [] }) }) - shell.editor.dispatchCommand(UNDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: '', references: [] }) }) - shell.editor.dispatchCommand(REDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'First', references: [] }) }) - shell.editor.dispatchCommand(REDO_COMMAND, undefined) - await vi.waitFor(() => { expect(shell.draftSnapshot).toEqual({ text: 'Second', references: [] }) }) - }) - - it('leaves an ordinary empty editor untouched until a prefill arrives', () => { - const shell = makeShell() - const root = document.createElement('div') - root.contentEditable = 'true' - document.body.append(root) - onTestFinished(() => { shell.editor.setRootElement(null); root.remove() }) - shell.editor.setRootElement(root) - expect(shell.prefill('')).toBe(false) - expect(root.childElementCount).toBe(0) - expect(shell.draftSnapshot).toEqual({ text: '', references: [] }) - }) -}) - describe('plain-text reference catalogs', () => { it('leaves named tokens plain until their own catalog includes them', () => { const unavailable = new Map<'/' | '@', readonly string[]>() diff --git a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx index 8f7a886720..2129c605d2 100644 --- a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx @@ -639,120 +639,3 @@ describe('scenario I: unknown /xyz + enter', () => { expect(b.sink).not.toHaveBeenCalled() }) }) - -describe('plugin composer prefill API', () => { - it('fills empty drafts, inserts at the caret, and honors replace and separator options', async () => { - const b = await bench() - expect(b.shell.prefill('')).toBe(false) - act(() => { expect(b.shell.prefill('Explain')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Explain') - act(() => { expect(b.shell.actions.prefill('tests')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Explain\ntests') - act(() => { expect(b.shell.prefill('next', { separator: ' ' })).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Explain\ntests next') - act(() => { b.shell.setDraft('Keep ') }) - act(() => { expect(b.shell.prefill('Next')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Keep Next') - act(() => { expect(b.shell.prefill('Only', { mode: 'replace' })).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Only') - act(() => { expect(b.shell.prefill('Next', { separator: '' })).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('OnlyNext') - act(() => { - b.shell.editor.update(() => { $getRoot().selectStart() }, { discrete: true }) - }) - act(() => { expect(b.shell.prefill('Head')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('HeadOnlyNext') - }) - - it('reports a rejected insertion without changing the draft', async () => { - const b = await bench() - act(() => { b.shell.setDraft('Busy') }) - const insertText = vi.spyOn(b.shell.actions, 'insertText').mockReturnValue(false) - act(() => { expect(b.shell.prefill('Next')).toBe(false) }) - expect(b.shell.snapshot.draft).toBe('Busy') - insertText.mockRestore() - }) -}) - -describe('plugin composer prefill API guards and history', () => { - it('makes a replacement its own undo step', async () => { - const b = await bench() - act(() => { b.shell.setDraft('Old') }) - act(() => { expect(b.shell.prefill('New', { mode: 'replace' })).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('New') - act(() => { b.shell.editor.dispatchCommand(UNDO_COMMAND, undefined) }) - await act(async () => {}) - expect(b.shell.snapshot.draft).toBe('Old') - }) - - it('refuses replacement while sending and after disposal', async () => { - const sending = await bench() - act(() => { sending.shell.setDraft('Sent') }) - act(() => { sending.shell.submit() }) - expect(sending.shell.snapshot.phase).toBe('sending') - act(() => { expect(sending.shell.prefill('New', { mode: 'replace' })).toBe(false) }) - - const disposed = await bench() - act(() => { disposed.shell.setDraft('Keep') }) - disposed.shell.dispose() - act(() => { expect(disposed.shell.prefill('New', { mode: 'replace' })).toBe(false) }) - act(() => { expect(disposed.shell.prefill('New')).toBe(false) }) - }) - - it('checks the separator against detect coordinates around a reference chip', async () => { - const b = await bench() - act(() => { b.shell.setDraft('@res') }) - const accepted = b.shell.insertReference({ - source: 'reference', - ref: '@[Research](dsh-session:InNvdXJjZSI)', - label: 'Research', - clipboardText: '@[Research](dsh-session:InNvdXJjZSI)', - }, { - start: 0, - end: 4, - draftRev: b.shell.snapshot.draftRev, - }) - expect(accepted).toBe(true) - const afterChip = b.shell.snapshot.draft - expect(afterChip.endsWith(' ')).toBe(true) - act(() => { expect(b.shell.prefill('Next')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe(`${afterChip}Next`) - }) - - it('replaces a whitespace-only draft', async () => { - const b = await bench() - act(() => { b.shell.setDraft(' \n ') }) - act(() => { expect(b.shell.prefill('Text')).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('Text') - }) - - it.each(['captureInsertion', 'prefill'] as const)('reads the native caret through %s before its selection is projected', async (entry) => { - const b = await bench() - act(() => { b.shell.setDraft('Keep text') }) - b.textarea.focus() - const text = b.textarea.firstChild?.firstChild - const selection = document.getSelection() - if (text === undefined || text === null || selection === null) throw new Error('missing native draft selection') - selection.setBaseAndExtent(text, 0, text, 0) - const before = b.shell.draftSnapshot - if (entry === 'captureInsertion') { - const revision = b.shell.snapshot.draftRev - const track = vi.spyOn(b.controller, 'track') - const expected = { start: 0, end: 0, draftRev: revision } - expect(b.shell.actions.captureInsertion()).toEqual(expected) - expect(track).toHaveBeenCalledOnce() - expect(b.shell.actions.captureInsertion()).toEqual(expected) - expect(track).toHaveBeenCalledOnce() - expect(b.shell.draftSnapshot).toBe(before) - expect(b.shell.snapshot.draftRev).toBe(revision) - expect(b.sink).not.toHaveBeenCalled() - return - } - act(() => { expect(b.shell.prefill('Head', { separator: '' })).toBe(true) }) - expect(b.shell.snapshot.draft).toBe('HeadKeep text') - act(() => { b.shell.editor.dispatchCommand(UNDO_COMMAND, undefined) }) - await act(async () => {}) - expect(b.shell.draftSnapshot).toEqual(before) - expect(b.sink).not.toHaveBeenCalled() - }) -}) diff --git a/packages/client/ui-sidebar-files/tests/mount.client.tsx b/packages/client/ui-sidebar-files/tests/mount.client.tsx index f4939367a9..35a660f264 100644 --- a/packages/client/ui-sidebar-files/tests/mount.client.tsx +++ b/packages/client/ui-sidebar-files/tests/mount.client.tsx @@ -242,7 +242,6 @@ const unusedFactoryProps = { captureInsertion: unusedFrameworkProp, insertText: unusedFrameworkProp, setDraft: unusedFrameworkProp, - prefill: unusedFrameworkProp, persistDraft: unusedFrameworkProp, addAttachments: unusedFrameworkProp, removeAttachment: unusedFrameworkProp, diff --git a/packages/client/ui-sidebar-right/tests/expand-button.client.spec.tsx b/packages/client/ui-sidebar-right/tests/expand-button.client.spec.tsx index fee4cd7840..205ebcf35f 100644 --- a/packages/client/ui-sidebar-right/tests/expand-button.client.spec.tsx +++ b/packages/client/ui-sidebar-right/tests/expand-button.client.spec.tsx @@ -29,8 +29,7 @@ const standard: GlobalStandardProps & SessionStandardProps = { useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused, ...allowAllAccess, useContextMenu: contextMenuHook(), - inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, - setDraft: unused, prefill: unused, persistDraft: unused, + inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused, addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused }, } diff --git a/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx b/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx index c9ad11215f..b8ed43cf9c 100644 --- a/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx +++ b/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx @@ -36,8 +36,7 @@ const standard: GlobalStandardProps & SessionStandardProps = { useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused, ...allowAllAccess, useContextMenu: contextMenuHook(), - inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, - setDraft: unused, prefill: unused, persistDraft: unused, + inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused, addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused }, } diff --git a/packages/client/ui-sidebar-terminal/tests/terminal-guide.client.spec.tsx b/packages/client/ui-sidebar-terminal/tests/terminal-guide.client.spec.tsx index ba63e2dd3f..19a6c0210c 100644 --- a/packages/client/ui-sidebar-terminal/tests/terminal-guide.client.spec.tsx +++ b/packages/client/ui-sidebar-terminal/tests/terminal-guide.client.spec.tsx @@ -25,8 +25,7 @@ const standard: GlobalStandardProps & SessionStandardProps = { useSessionRetainInfo: unused, useResource: unused, useWorkspaces: unused, ...allowAllAccess, useContextMenu: contextMenuHook(), - inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, - setDraft: unused, prefill: unused, persistDraft: unused, + inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused, addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused }, } const choices: TerminalLaunchShells = { diff --git a/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx b/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx index bff439df98..3ac3a7b325 100644 --- a/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx +++ b/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx @@ -107,7 +107,6 @@ function props( captureInsertion: unused, insertText: unused, setDraft: unused, - prefill: unused, persistDraft: unused, addAttachments: unused, removeAttachment: unused, diff --git a/packages/client/ui-trajectory/tests/views.client.spec.tsx b/packages/client/ui-trajectory/tests/views.client.spec.tsx index 1da25650d1..9b61aaef03 100644 --- a/packages/client/ui-trajectory/tests/views.client.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.client.spec.tsx @@ -220,7 +220,6 @@ function standaloneProps( captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: () => {}, - prefill: () => false, persistDraft: () => {}, addAttachments: () => false, removeAttachment: () => {}, @@ -359,7 +358,6 @@ function mount(fixture: Awaited>) { captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: vi.fn(), - prefill: vi.fn(() => false), persistDraft: vi.fn(), addAttachments: vi.fn(() => false), removeAttachment: vi.fn(), diff --git a/packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx b/packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx index d767bd695e..41e0e6902f 100644 --- a/packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx +++ b/packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx @@ -137,7 +137,6 @@ const kit: Omit = { captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: () => { throw new Error('unused') }, - prefill: () => false, persistDraft: () => { throw new Error('unused') }, addAttachments: () => { throw new Error('unused') }, removeAttachment: () => { throw new Error('unused') }, diff --git a/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx b/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx index b5a1167643..b3fdffda6b 100644 --- a/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx +++ b/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx @@ -143,7 +143,6 @@ const kitBase: Omit ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: () => { throw new Error('unused') }, - prefill: () => false, persistDraft: () => { throw new Error('unused') }, addAttachments: () => { throw new Error('unused') }, removeAttachment: () => { throw new Error('unused') }, diff --git a/packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx b/packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx index c088085b61..9ba88045d8 100644 --- a/packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx +++ b/packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx @@ -355,7 +355,6 @@ function panelProps(data: WorkflowRunChatData, sessions = listState(), openSessi captureInsertion: () => ({ start: 0, end: 0, draftRev: 0 }), insertText: () => false, setDraft: () => {}, - prefill: () => false, persistDraft: () => {}, addAttachments: () => false, removeAttachment: () => {}, diff --git a/packages/experimental/client-ui-voice-input/tests/voice-input.client.spec.tsx b/packages/experimental/client-ui-voice-input/tests/voice-input.client.spec.tsx index bef03240e9..b0e23ff464 100644 --- a/packages/experimental/client-ui-voice-input/tests/voice-input.client.spec.tsx +++ b/packages/experimental/client-ui-voice-input/tests/voice-input.client.spec.tsx @@ -24,7 +24,6 @@ function fixture(recording?: Recording, navigable = true) { const inputActions = { notify: vi.fn(), insertQuote: () => false, captureInsertion: vi.fn(() => ({ start: 3, end: 3, draftRev: 1 })), insertText: vi.fn(() => true), setDraft: vi.fn(), persistDraft: vi.fn(), addAttachments: vi.fn(() => true), removeAttachment: vi.fn(), - prefill: vi.fn(() => false), pruneAttachments: vi.fn(), submit: vi.fn() } const readiness = createSnapshotStore({ connected: true, error: null, catalog: { providers: [{ id, name: 'SenseVoiceSmall', location: 'host-local', languages: ['auto', 'zh', 'en', 'ja'], preparation: { phase: 'ready' } }], diff --git a/packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts b/packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts index 0675b63893..03688e1ae4 100644 --- a/packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts +++ b/packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts @@ -182,12 +182,11 @@ describe('Node program process', () => { }) it.each([{}, { timeoutMs: null }] as const)('cancels a live program and closes its managed process with %j', async (timing) => { - const { run, cwd } = await setup() + const { run } = await setup() + const entered = Promise.withResolvers() const controller = new AbortController() - // Synchronous file readiness does not depend on the busy child's control pipe draining. - const active = run({ ...timing, program: 'const fs = await import("node:fs"); fs.writeFileSync("entered", "ready"); for (;;) {}', signal: controller.signal, bindings: [] }) - onTestFinished(async () => { controller.abort('test finished'); await active }) - await expect.poll(() => readFile(join(cwd, 'entered'), 'utf8')).toBe('ready') + const active = run({ ...timing, program: 'void tools.enter({}); for (;;) {}', signal: controller.signal, bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) }) + await entered.promise controller.abort('stop') expect((await active).error).toEqual({ kind: 'abort', message: 'stop' }) }) diff --git a/packages/session-query/session-log-export/tests/header-action.client.spec.tsx b/packages/session-query/session-log-export/tests/header-action.client.spec.tsx index 95162fffdf..4a64ffb70d 100644 --- a/packages/session-query/session-log-export/tests/header-action.client.spec.tsx +++ b/packages/session-query/session-log-export/tests/header-action.client.spec.tsx @@ -27,8 +27,7 @@ const standard: GlobalStandardProps & SessionStandardProps = { useSessionAccess: () => true, useRemoteAccess: () => true, useWorkspaceAccess: () => true, useConversation: unused, useInput: unused, useChat: unused, useTrajectory: unused, usePanelInfo: unused, useContextMenu: contextMenuHook(), useResource: unused, useWorkspaces: unused, - inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, - setDraft: unused, prefill: unused, persistDraft: unused, + inputActions: { insertQuote: () => false, captureInsertion: unused, insertText: unused, setDraft: unused, persistDraft: unused, addAttachments: unused, removeAttachment: unused, pruneAttachments: unused, submit: unused }, }