修复:粘贴解析出的引用若解析不出来,退化成 token 文本而不是整条丢掉

$insertContentAtSelection 的引用分支补 else:referenceFromPart 返回 null 时插入该 part 的 token 文本
此前上游 buildContentFromPastedText 已把 token 摘走,插入端再丢一次就会让这段内容静默消失
新增用例:provider 认得出 token 但插入时已解析不出引用,断言正文保持 `看 @hero 一眼`、不产生芯片
变异验证:去掉该兜底后同一用例变红(正文变成 `看  一眼`),恢复即绿
This commit is contained in:
2026-09-22 19:54:36 +08:00
parent 9e50947d02
commit 14f11e77a2
2 changed files with 38 additions and 0 deletions
@@ -322,6 +322,9 @@ function $selectionOrRootEnd() {
* 与 `applyContentToRoot`(整根替换,供初始草稿与润色回写使用)的区别只在替换范围:
* 文本 part 的 `\n` 落成真正的段落分隔(与编辑器默认的纯文本粘贴同一形状),引用 part 落成
* chip;不加任何补白,token 原位替换、token 之外的每个字符照原样保留。
*
* 认不出的引用 partprovider 的 `toReference` 解析不出来)退回它的 token 文本,
* 绝不静默丢掉——粘贴进来的内容一个字符都不会凭空消失。
*/
function $insertContentAtSelection(
content: readonly DirectCodexUserContentPart[],
@@ -342,7 +345,11 @@ function $insertContentAtSelection(
$selectionOrRootEnd()?.insertNodes([
$createResourceReferenceNode(reference),
]);
return;
}
// 解析不出的 part 已经在上游被摘掉了 token,这里必须把文本补回去,否则这段内容会静默消失。
const token = mentionTokenFromPart(providers, part);
if (token) $selectionOrRootEnd()?.insertText(token);
});
}
@@ -33,6 +33,7 @@ import { attachmentReferenceProvider } from '../src/features/project-workspace/r
import { createResourceReferenceProvider } from '../src/features/project-workspace/reference-source/resourceReferenceProvider';
import { runtimeRegionReferenceProvider } from '../src/features/project-workspace/reference-source/runtimeRegionReferenceProvider';
import { useSkillReferenceProvider } from '../src/features/project-workspace/reference-source/skillReferenceProvider';
import type { ReferenceProvider } from '../src/features/project-workspace/reference-source/types';
import {
ResourceReferenceInput,
type ResourceReferenceInputHandle,
@@ -458,6 +459,36 @@ describe('ResourceReferenceInput', () => {
expect(editorTextSize()).toBe('看 '.length + 1 + ' 这一版'.length);
});
test('粘贴解析出的引用若已无法解析,退化成 token 文本而不是整条丢掉', async () => {
const ref = createRef<ResourceReferenceInputHandle>();
// provider 认得出这个 tokenlookup 有候选),但插入那一刻已经解析不出引用(例如资产刚被删)。
const ghostProvider: ReferenceProvider = {
trigger: '@',
fuzzy_lookup: () => [],
lookup: () => [resourceReferenceFromAsset(assets[0]!, 'asset-picker')],
toReference: () => null,
refresh: (reference) => reference,
mentionToken: (part) =>
part.type === 'agc_resource_reference' ? '@hero' : null,
};
render(
<ResourceReferenceInput
ref={ref}
providers={[ghostProvider]}
projectPath="C:/project"
ariaLabel="聊天"
onChange={vi.fn()}
/>,
);
pasteComposerText('看 @hero 一眼');
await waitFor(() => {
expect(draftText(ref.current?.getDraft())).toBe('看 @hero 一眼');
});
expect(draftResourceIds(ref.current?.getDraft())).toEqual([]);
});
test('粘贴未命中的 token:整段按字面落进正文,不接管、不提示', async () => {
const ref = createRef<ResourceReferenceInputHandle>();
render(