功能:引用输入区支持粘贴解析 @显示名 / $名称 #473

Merged
k88936 merged 17 commits from feat/smart-paste into master 2026-09-23 18:51:24 +08:00
Member
No description provided.
k88936 added 1 commit 2026-09-22 19:32:15 +08:00
功能:引用输入区支持粘贴解析 @显示名 / $名称
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m26s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m55s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
6950e08506
新增 buildContentFromPastedText:粘贴文本按显示口径反解析,同名多候选与未命中 token 一律按文本保留
ReferenceProvider 增加可选 candidates 与 onPasteText:候选枚举保持纯函数,Skill 目录冷启动时补读一次
资源与 Skill provider 落地新缝,静默 provider(附件 / 运行画面区域)不参与粘贴解析
输入区在 CRITICAL 优先级接管 PASTE_COMMAND,仅解析出引用时插入并打 PASTE_TAG,一次 Ctrl+Z 整体回退
补充粘贴解析规则矩阵、provider 候选枚举与补读、输入区粘贴集成用例
同步 ADR 修订、实施计划、术语表、功能说明与决策记录
k88936 added 1 commit 2026-09-22 19:50:52 +08:00
重构:引用 provider 的 match / candidates 改名为 fuzzyLookup / lookup,删除 onPasteText
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m16s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m7s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
ec1f207021
match(query) 改名 fuzzyLookup(query):名字写明它是包含匹配 + 截断的模糊菜单查询
candidates() 改名 lookup():名字写明它是精确查找用的就绪全量候选,不模糊不截断
删除 onPasteText 补读钩子:粘贴只用此刻就绪的候选,不等待、不补读,也不为解析提前读盘
输入区与资源 / Skill provider 同步改名,粘贴接管路径改读 lookup()
provider 用例与粘贴集成用例同步:Skill 冷启动改为「保持字面,敲过 `$` 后可解析」
补 jsdom 缺的 Range.getBoundingClientRect 与 ResizeObserver 桩(候选菜单测量用,真实浏览器都有)
同步 ADR 修订、实施计划、功能说明与决策记录
k88936 added 3 commits 2026-09-22 19:56:41 +08:00
抽出 $selectionOrRootEnd():选区缺失或指向已被重建掉的节点时统一回落到草稿末尾
insertReferences / insertText / $insertContentAtSelection 三处重复的兜底判断改为调用它
行为不变:插件插入与粘贴插入仍走同一套「插到草稿末尾」的策略,只是不再各写一份
$insertContentAtSelection 的引用分支补 else:referenceFromPart 返回 null 时插入该 part 的 token 文本
此前上游 buildContentFromPastedText 已把 token 摘走,插入端再丢一次就会让这段内容静默消失
新增用例:provider 认得出 token 但插入时已解析不出引用,断言正文保持 `看 @hero 一眼`、不产生芯片
变异验证:去掉该兜底后同一用例变红(正文变成 `看  一眼`),恢复即绿
修复:粘贴解析注释里的 provider 成员名与实际 API 对齐
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
072bf691d9
buildContentFromPastedText 的注释仍写着重命名前的 ReferenceProvider.candidates,改成 lookup
顺带写明候选来自调用方传入的引用清单,避免把「谁去查候选」误记进这个纯函数
k88936 added 1 commit 2026-09-23 10:41:20 +08:00
修复:引用名内部空白统一折成 -,粘贴解析不再多插一枚芯片
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m21s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m53s
Project CI / Frontend tests (pull_request) Successful in 2m4s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m19s
Project CI / Native shell tests (pull_request) Successful in 5m13s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m46s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m16s
5e36b61534
resourceReferences.ts 新增共享 normalizeMentionName:内部空白折 `-`、裁掉首尾,token 层幂等再折一次
素材显示名(resourceDisplayName)、Skill 名(目录读入与 toReference / mentionToken)、附件名(导入映射与 toReference / mentionToken)统一过这一份口径
resourceDisplayName 去掉 `|| asset.id` 兜底:引用名假定非空,不再回退 resourceId
前缀重叠 bug 由此收口:`@hero` 不再命中 `@hero-v2`(后一个字符不是空白),粘贴只剩正确的那一枚芯片
新增用例:素材 / Skill / 附件三类归一化口径、前缀重叠回归、Skill 目录名带空白、附件名带空白;把 normalizeMentionName 变异成恒等函数后新增用例全红
顺带修掉 referenceSourceProviders 用例里重命名遗留的 provider.match 断言与 skill provider 注释里的 match
同步 CONTEXT.md 术语、功能说明、实施计划与决策记录
Author
Member

- [x] 1. 前缀重叠的 token 会多插一个 chip(原 review 第 1 条 / bug / medium,已修:5e36b6153

你的裁决(2026-09-23):不动反解析,改为 enforce「引用名内部不允许空白」——素材 / Skill / 附件三类引用名统一走共享 normalizeMentionName(内部空白折 -),不做 resourceId 兜底、假定名字非空。落地见本节末尾。

当前实现
buildContentFromPastedTextresourceReferences.ts)先按「某个 token 在文本里出现几次」把候选展开成
ContentTokenCandidate[](同一 token 出现 N 次就放 N 条),再交给 buildContentFromTextTokens 定位。
buildContentFromTextTokens 的定位规则是:每行从 cursor 起找「仍未被消费、且 index 最小」的候选,
平局时按候选数组顺序取第一个;扫完后没有任何候选被消费掉的话,会作为 orphan 追加到正文末尾
(那是润色回包的语义:引用与附件绝不凭空消失)。

问题(已实测复现,不是推测)
当两个候选的 token 互为前缀且短 token 后面正好是空白时(例如素材显示名 herohero v2),
短 token 也会在 @hero v2 里命中一次边界(@hero␠ 的空白算合法边界)。粘贴 看 @hero v2 这一版

['看 '] [chip hero] [' v2 这一版'] [' '] [chip hero-v2]

即:短 token 先按 index 平局抢到位置 0,长 token 无处可放,被当成 orphan 追加到末尾——
多出一个不该有的 hero 芯片,且顺序与原文不符。这违反「宁可不成芯片也不能认错」的口径。

推荐修复(两条路线,必须先定口径)

  • 路线 A(推荐):最长优先 + 粘贴路径永不追加 orphan。做法是粘贴侧改成一次最长匹配扫描
    (同一起点命中多个候选时取最长者、并跳过被它覆盖的短候选),并且只投真正选中的那一条;
    顺带给 buildContentFromTextTokens 加一个「不追加 orphan」的入参(或给粘贴单独一个插入原语),
    让「未消费候选补到末尾」只保留给润色回包。代价:动到与润色共用的函数,需要回归润色用例。
  • 路线 B(更保守):遇到前缀重叠就整组按文本保留(两个 token 都不成芯片,逐字留在正文),
    实现只需在 buildContentFromPastedText 里丢掉「与前缀关系有关的组」,不碰共享函数。
    代价:素材名叫 hero 时,只要项目里同时存在 hero v2@hero 的粘贴解析也会一起失效。

我倾向 A(长名字是用户真实输入,短名字不该抢位;orphan 追加在粘贴场景本来就是错的),
但「谁赢」和「要不要动共享反解析」是你的口径,所以没动代码。

落地(5e36b6153
normalizeMentionName 加在 resourceReferences.ts,落点是名字产生处(resourceDisplayName、Skill 目录读入与 toReference / mentionToken、附件导入映射与 toReference / mentionToken)与两个 token 投影(chatReferenceMentionTokendirectCodexContentToPromptText),token 层幂等再折一次;resourceDisplayName 去掉 || asset.id

于是 @hero@hero-v2 里面后面跟的是 -、不再是空白 → 不命中,前缀重叠不可能发生;buildContentFromTextTokens 与润色回包零改动。代价hero v2hero-v2 归一化后同名时走既有的「同名多候选一律按文本保留」(不认错,但两者都成不了芯片);本次改动前生成的旧文本(历史回合 prompt、旧气泡)里的 @hero v2 不再解析,重试 / 润色回填时那条引用会退化成末尾孤儿(内容不丢、位置可能不对)。

验证:新增素材 / Skill / 附件三类归一化口径用例、前缀重叠回归用例(归一化后粘贴只剩正确的那一枚芯片)、Skill 目录名带空白与附件名带空白的 provider 用例;把 normalizeMentionName 变异成恒等函数后以上新增用例全红。


- [x] 2. 选区兜底在三处重复(原 review 第 2 条 / maintainability / low,已修:9e50947d0

当前实现
「选区缺失、或选区指向已被重建掉的节点时回落到草稿末尾」这段判断,原本在
insertReferencesinsertText$insertContentAtSelection 里各写了一份(三份逐字重复)。

问题
策略是三处复制的:任何一处改成「插到光标原文处」或加一条新的失效条件,另外两处会悄悄漂移,
表现是同一份草稿在不同入口(@ 面板 / 资源卡拖拽 / 粘贴 / 润色回填)落点不一致。

修复
抽出 $selectionOrRootEnd()ResourceReferenceInput.tsx:307):统一取「当前可用选区,失效则回落到
草稿末尾」,三处调用点改为调用它(净减 14 行)。行为逐字不变,仅去重;其余几处 selectEnd()
是「整根替换后把光标放到末尾」的另一种意图,没有并入。

验证:输入区 39 条 + 画布生成面板引用 19 条用例通过;tsc、eslint、prettier 通过。


- [x] 3. 解析不出的引用 part 被静默丢掉(原 review 第 3 条 / bug / low,已修:14f11e77a

当前实现
$insertContentAtSelection 原本只在 referenceFromPart(providers, part) 返回引用时插入 chip,
返回 null 时什么都不做。而 token 已经在上一层的 buildContentFromPastedText 里被摘成 part 了。

问题
这是一条潜在的数据丢失路径:一旦某个 provider 的 toReference 在插入那一刻解析不出来
(资产被删、将来出现异步 provider),这段内容会彻底消失——用户看到的是粘贴少了一段字,
既没提示也无法从正文找回,与函数自身「token 之外的每个字符照原样保留」的不变量自相矛盾。
今天的 4 个内置 provider 在同一 tick 内都能同步解析,所以尚未触发。

修复
else 分支:退回该 part 按 provider 口径的 token 文本(mentionTokenFromPart)插入,字符不丢。
新增用例:自定义 provider 的 lookup() 有候选、toReference() 返回 null,断言正文保持
看 @hero 一眼 且不产生芯片。变异验证:去掉该兜底后用例变红(正文变成 看 一眼),恢复即绿。

验证:输入区 40 条(新增 1 条)+ provider 用例通过;tsc、eslint、prettier 通过。


- [x] 4. 重命名后注释里的 provider 成员名过期(Agent 自查发现,非原评审条目,已修:072bf691d

当前实现
buildContentFromPastedText 的文档注释写着「候选由宿主注入的 provider 枚举(ReferenceProvider.candidates)」,
ec1f20702 已经把 candidates() 改名成 lookup(),注释没跟上;而且这句话把「谁去查候选」
记在了这个纯函数头上——实际是输入区在 PASTE_COMMAND 里调 provider.lookup(),再把清单当参数传进来。

问题
ReferenceProvider.candidates 已不存在,注释指着一个被删掉的成员。不影响运行,但属于「看着像对的」
的一类过期注释:下一个人按注释去找那个 API 会找不到,也会误判这个函数的职责边界。

修复
改成「候选来自调用方传入的引用清单(输入区给的是 ReferenceProvider.lookup() 此刻就绪的全量候选)」,
只有注释变化,无行为变化。验证:prettier、check:encodinggit diff --check 通过;输入区相关
3 个测试文件 69 条用例通过。

## - [x] 1. 前缀重叠的 token 会多插一个 chip(原 review 第 1 条 / bug / medium,已修:`5e36b6153`) **你的裁决(2026-09-23)**:不动反解析,改为 enforce「引用名内部不允许空白」——素材 / Skill / 附件三类引用名统一走共享 `normalizeMentionName`(内部空白折 `-`),不做 `resourceId` 兜底、假定名字非空。落地见本节末尾。 **当前实现** `buildContentFromPastedText`(`resourceReferences.ts`)先按「某个 token 在文本里出现几次」把候选展开成 `ContentTokenCandidate[]`(同一 token 出现 N 次就放 N 条),再交给 `buildContentFromTextTokens` 定位。 而 `buildContentFromTextTokens` 的定位规则是:每行从 cursor 起找「仍未被消费、且 index 最小」的候选, 平局时按候选数组顺序取第一个;扫完后**没有任何候选被消费掉的话,会作为 orphan 追加到正文末尾** (那是润色回包的语义:引用与附件绝不凭空消失)。 **问题(已实测复现,不是推测)** 当两个候选的 token 互为前缀且短 token 后面正好是空白时(例如素材显示名 `hero` 与 `hero v2`), 短 token 也会在 `@hero v2` 里命中一次边界(`@hero␠` 的空白算合法边界)。粘贴 `看 @hero v2 这一版`: ``` ['看 '] [chip hero] [' v2 这一版'] [' '] [chip hero-v2] ``` 即:短 token 先按 index 平局抢到位置 0,长 token 无处可放,被当成 orphan 追加到末尾—— **多出一个不该有的 `hero` 芯片**,且顺序与原文不符。这违反「宁可不成芯片也不能认错」的口径。 **推荐修复(两条路线,必须先定口径)** - 路线 A(推荐):**最长优先 + 粘贴路径永不追加 orphan**。做法是粘贴侧改成一次最长匹配扫描 (同一起点命中多个候选时取最长者、并跳过被它覆盖的短候选),并且只投真正选中的那一条; 顺带给 `buildContentFromTextTokens` 加一个「不追加 orphan」的入参(或给粘贴单独一个插入原语), 让「未消费候选补到末尾」只保留给润色回包。代价:动到与润色共用的函数,需要回归润色用例。 - 路线 B(更保守):**遇到前缀重叠就整组按文本保留**(两个 token 都不成芯片,逐字留在正文), 实现只需在 `buildContentFromPastedText` 里丢掉「与前缀关系有关的组」,不碰共享函数。 代价:素材名叫 `hero` 时,只要项目里同时存在 `hero v2`,`@hero` 的粘贴解析也会一起失效。 我倾向 A(长名字是用户真实输入,短名字不该抢位;orphan 追加在粘贴场景本来就是错的), 但「谁赢」和「要不要动共享反解析」是你的口径,所以没动代码。 **落地(`5e36b6153`)** `normalizeMentionName` 加在 `resourceReferences.ts`,落点是名字产生处(`resourceDisplayName`、Skill 目录读入与 `toReference` / `mentionToken`、附件导入映射与 `toReference` / `mentionToken`)与两个 token 投影(`chatReferenceMentionToken`、`directCodexContentToPromptText`),token 层幂等再折一次;`resourceDisplayName` 去掉 `|| asset.id`。 于是 `@hero` 在 `@hero-v2` 里面后面跟的是 `-`、不再是空白 → 不命中,前缀重叠不可能发生;`buildContentFromTextTokens` 与润色回包零改动。**代价**:`hero v2` 与 `hero-v2` 归一化后同名时走既有的「同名多候选一律按文本保留」(不认错,但两者都成不了芯片);本次改动前生成的旧文本(历史回合 prompt、旧气泡)里的 `@hero v2` 不再解析,重试 / 润色回填时那条引用会退化成末尾孤儿(内容不丢、位置可能不对)。 **验证**:新增素材 / Skill / 附件三类归一化口径用例、前缀重叠回归用例(归一化后粘贴只剩正确的那一枚芯片)、Skill 目录名带空白与附件名带空白的 provider 用例;把 `normalizeMentionName` 变异成恒等函数后以上新增用例全红。 --- ## - [x] 2. 选区兜底在三处重复(原 review 第 2 条 / maintainability / low,已修:`9e50947d0`) **当前实现** 「选区缺失、或选区指向已被重建掉的节点时回落到草稿末尾」这段判断,原本在 `insertReferences`、`insertText`、`$insertContentAtSelection` 里各写了一份(三份逐字重复)。 **问题** 策略是三处复制的:任何一处改成「插到光标原文处」或加一条新的失效条件,另外两处会悄悄漂移, 表现是同一份草稿在不同入口(`@` 面板 / 资源卡拖拽 / 粘贴 / 润色回填)落点不一致。 **修复** 抽出 `$selectionOrRootEnd()`(`ResourceReferenceInput.tsx:307`):统一取「当前可用选区,失效则回落到 草稿末尾」,三处调用点改为调用它(净减 14 行)。行为逐字不变,仅去重;其余几处 `selectEnd()` 是「整根替换后把光标放到末尾」的另一种意图,没有并入。 **验证**:输入区 39 条 + 画布生成面板引用 19 条用例通过;`tsc`、eslint、prettier 通过。 --- ## - [x] 3. 解析不出的引用 part 被静默丢掉(原 review 第 3 条 / bug / low,已修:`14f11e77a`) **当前实现** `$insertContentAtSelection` 原本只在 `referenceFromPart(providers, part)` 返回引用时插入 chip, 返回 `null` 时什么都不做。而 token 已经在上一层的 `buildContentFromPastedText` 里被摘成 part 了。 **问题** 这是一条潜在的数据丢失路径:一旦某个 provider 的 `toReference` 在插入那一刻解析不出来 (资产被删、将来出现异步 provider),这段内容会**彻底消失**——用户看到的是粘贴少了一段字, 既没提示也无法从正文找回,与函数自身「token 之外的每个字符照原样保留」的不变量自相矛盾。 今天的 4 个内置 provider 在同一 tick 内都能同步解析,所以尚未触发。 **修复** 补 `else` 分支:退回该 part 按 provider 口径的 token 文本(`mentionTokenFromPart`)插入,字符不丢。 新增用例:自定义 provider 的 `lookup()` 有候选、`toReference()` 返回 `null`,断言正文保持 `看 @hero 一眼` 且不产生芯片。**变异验证**:去掉该兜底后用例变红(正文变成 `看 一眼`),恢复即绿。 **验证**:输入区 40 条(新增 1 条)+ provider 用例通过;`tsc`、eslint、prettier 通过。 --- ## - [x] 4. 重命名后注释里的 provider 成员名过期(Agent 自查发现,非原评审条目,已修:`072bf691d`) **当前实现** `buildContentFromPastedText` 的文档注释写着「候选由宿主注入的 provider 枚举(`ReferenceProvider.candidates`)」, 但 `ec1f20702` 已经把 `candidates()` 改名成 `lookup()`,注释没跟上;而且这句话把「谁去查候选」 记在了这个纯函数头上——实际是输入区在 `PASTE_COMMAND` 里调 `provider.lookup()`,再把清单当参数传进来。 **问题** `ReferenceProvider.candidates` 已不存在,注释指着一个被删掉的成员。不影响运行,但属于「看着像对的」 的一类过期注释:下一个人按注释去找那个 API 会找不到,也会误判这个函数的职责边界。 **修复** 改成「候选来自调用方传入的引用清单(输入区给的是 `ReferenceProvider.lookup()` 此刻就绪的全量候选)」, 只有注释变化,无行为变化。**验证**:prettier、`check:encoding`、`git diff --check` 通过;输入区相关 3 个测试文件 69 条用例通过。
k88936 added 1 commit 2026-09-23 11:05:28 +08:00
Merge remote-tracking branch 'origin/master' into feat/smart-paste
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m30s
Project CI / Backend tests (pull_request) Successful in 4m43s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m16s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m10s
Project CI / Frontend tests (pull_request) Successful in 2m23s
Project CI / Native shell tests (pull_request) Successful in 7m2s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m10s
Project CI / Repository checks (pull_request) Successful in 2m49s
44148542f1
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
Author
Member

自动评审(引用粘贴解析,第二轮)处理结果 · 2026-09-23 · 分支 feat/smart-paste

已由我改掉并逐项提交(一个问题一个提交;工作区只剩你自己的 .env 改动):

  • 4e90465c4 素材显示名词干为空(.env / .gitignore)时回退 asset.id(原第 1 项,含决策记录校正与功能说明同步)
  • 3f9c698a6 粘贴解析跳过只有触发符的退化 token(原第 4 项)
  • 3391ecf7d 粘贴接管改为「插入真的发生之后」才 preventDefault,插入为空时放行默认粘贴(原第 3 项前半)
  • c9bb5a93f 归一化撞名的代码注释与功能说明(原第 2 项前半)
  • 00301b4a4 插入兜底改掉「什么都不插」:新增 contentPartText,解析不出引用也落一段正常文本(原第 3 项后半,你指出的那条)

原第 3 项后半现在的实现:$insertContentAtSelection 的兜底链是 provider 的 mentionToken → contentPartText(资源落 @resourceId、Skill 落 $名称、附件落 @附件名、运行区域落 @标签,与出站文本同一份渲染),只有「连通用文本形态都拿不到」(只剩 input_text 这一种,函数前面已经单独处理)才是空。单测覆盖四类 part 的文本形态,输入区集成用例覆盖「provider 的 toReference 与 mentionToken 都答不出来」时粘贴仍按文本落下;去掉兜底即变红。

评审里一处描述与现状不符:原第 3 项说的「取不到选区时粘贴文字静默丢失」不可达——选区缺失或节点已卸载时 $selectionOrRootEnd() 都回落到 $getRoot().selectEnd(),而 Lexical 的 selectEnd() 恒返回 RangeSelection(空根走 select())。顺序仍按建议加固了,因为它把「绝不静默丢字」从约定变成结构。

下面这项需要你拍板,我没有动代码:

  • 1. 引用名归一化后撞名时,候选菜单不显示冲突
    现状:名字统一过 normalizeMentionName(内部空白折 -),hero v2.pnghero-v2.png 这类名字会折成同一个 token @hero-v2。生成候选菜单(reference-source/resourceReferenceProvider.ts 的 fuzzyLookup / lookup)只按名字过滤,不做去重或冲突标记;ResourceReferenceInput 的候选项标签直接取 chatReferenceMentionToken。
    问题:两条候选在菜单里显示完全相同的 @hero-v2 标签,用户无从分辨要选哪一条;从气泡复制的 @hero-v2 也重建不成芯片——buildContentFromPastedText 见到「同一个 token 对应多条引用身份」就整段按文本保留(bucket.length !== 1),粘回来只是死文本。不认错这一点是对的,但菜单侧没有任何提示。
    建议(需要产品取舍,所以留给你):两种走法都是产品可见行为变化——(a) 菜单侧做冲突检测,撞名的候选只保留一条或给非命中那条加区分配角(会改变「哪些素材能被 @ 到」);(b) 名字派生时保底唯一化(例如撞名时把 asset.id 片段接在名字后面),代价是显示名会带出 id,且要处理改名前后的稳定性。只做提示不做处理(例如菜单里标一句「同名」)是最小的第三选项。选 (a)/(b) 时要补 provider 用例。
自动评审(引用粘贴解析,第二轮)处理结果 · 2026-09-23 · 分支 feat/smart-paste 已由我改掉并逐项提交(一个问题一个提交;工作区只剩你自己的 .env 改动): - 4e90465c4 素材显示名词干为空(`.env` / `.gitignore`)时回退 asset.id(原第 1 项,含决策记录校正与功能说明同步) - 3f9c698a6 粘贴解析跳过只有触发符的退化 token(原第 4 项) - 3391ecf7d 粘贴接管改为「插入真的发生之后」才 preventDefault,插入为空时放行默认粘贴(原第 3 项前半) - c9bb5a93f 归一化撞名的代码注释与功能说明(原第 2 项前半) - 00301b4a4 插入兜底改掉「什么都不插」:新增 contentPartText,解析不出引用也落一段正常文本(原第 3 项后半,你指出的那条) 原第 3 项后半现在的实现:$insertContentAtSelection 的兜底链是 provider 的 mentionToken → contentPartText(资源落 @resourceId、Skill 落 $名称、附件落 @附件名、运行区域落 @标签,与出站文本同一份渲染),只有「连通用文本形态都拿不到」(只剩 input_text 这一种,函数前面已经单独处理)才是空。单测覆盖四类 part 的文本形态,输入区集成用例覆盖「provider 的 toReference 与 mentionToken 都答不出来」时粘贴仍按文本落下;去掉兜底即变红。 评审里一处描述与现状不符:原第 3 项说的「取不到选区时粘贴文字静默丢失」不可达——选区缺失或节点已卸载时 $selectionOrRootEnd() 都回落到 $getRoot().selectEnd(),而 Lexical 的 selectEnd() 恒返回 RangeSelection(空根走 select())。顺序仍按建议加固了,因为它把「绝不静默丢字」从约定变成结构。 下面这项需要你拍板,我没有动代码: - [ ] 1. 引用名归一化后撞名时,候选菜单不显示冲突 现状:名字统一过 normalizeMentionName(内部空白折 `-`),`hero v2.png` 与 `hero-v2.png` 这类名字会折成同一个 token `@hero-v2`。生成候选菜单(reference-source/resourceReferenceProvider.ts 的 fuzzyLookup / lookup)只按名字过滤,不做去重或冲突标记;ResourceReferenceInput 的候选项标签直接取 chatReferenceMentionToken。 问题:两条候选在菜单里显示完全相同的 `@hero-v2` 标签,用户无从分辨要选哪一条;从气泡复制的 `@hero-v2` 也重建不成芯片——buildContentFromPastedText 见到「同一个 token 对应多条引用身份」就整段按文本保留(`bucket.length !== 1`),粘回来只是死文本。不认错这一点是对的,但菜单侧没有任何提示。 建议(需要产品取舍,所以留给你):两种走法都是产品可见行为变化——(a) 菜单侧做冲突检测,撞名的候选只保留一条或给非命中那条加区分配角(会改变「哪些素材能被 @ 到」);(b) 名字派生时保底唯一化(例如撞名时把 asset.id 片段接在名字后面),代价是显示名会带出 id,且要处理改名前后的稳定性。只做提示不做处理(例如菜单里标一句「同名」)是最小的第三选项。选 (a)/(b) 时要补 provider 用例。
Author
Member
  1. 当前使用字符串反解析的局限
1. 当前使用字符串反解析的局限
k88936 added 7 commits 2026-09-23 13:23:24 +08:00
resourceDisplayName 在文件名去掉扩展名后为空(`.env` / `.gitignore` 这类整名就是扩展名的文件)时回退 asset.id,回退值同样过 normalizeMentionName;空名字会让 token 退化成只有触发符的裸 `@`,候选菜单里是空芯片、粘贴解析还会认领正文里任何一处裸 `@`
normalizeMentionName 的注释改为「只做归一化、不做兜底」,把兜底的落点写明在名字的来源侧
新增用例:`.env` 与 `config/.gitignore` 的显示名回退 asset.id、引用标签不再为空;把该回退去掉后用例变红
同步决策记录(校正「不做 resourceId 兜底」一条)、AGC 聊天素材引用功能说明与引用粘贴解析实施计划
buildContentFromPastedText 组装候选时跳过 token 长度小于等于 1 的条目(引用名为空时 token 就是裸 `@` / `$`):边界规则会让它在正文里匹配到任何一处触发符,认下来等于把无关文字错认成一条引用
函数注释的保守规则从两条补成三条,写明空名字按「宁可不成 chip」的同一口径跳过
新增用例:空名字引用的 token 是 `@` 时不认领正文里的裸 `@`、其余 token 照常解析;去掉该跳过分支后用例变红
同步 AGC 聊天素材引用功能说明的粘贴解析小节
ResourceReferenceInput 的 PASTE_COMMAND 处理器原先先 preventDefault 再去插入:插入为空时这段文字既不进我们的插入、又被挡掉了默认导入。现在先做插入,只有插入确实发生了才 preventDefault 并返回 true,否则返回 false 交给 Lexical 的默认粘贴
$insertContentAtSelection 改为返回「插进去没有」:选区取不到、或每个 part 都没落下内容时返回 false
编辑器已在一次更新里时 editor.update 会把回调排队,这时按原口径先接管(ranSync 标记区分「回调已同步跑完」与「回调被排队」),避免误判成插入为空而放行默认粘贴造成双份插入
同步 AGC 聊天素材引用功能说明的粘贴解析小节
normalizeMentionName 的注释补上「归一化不保证名字唯一」:`hero v2` 与 `hero-v2` 折成同一个 token,候选菜单侧不做冲突检测、会显示同一个标签,碰撞由 buildContentFromPastedText 的「同名多候选一律按文本保留」兜住
同步 AGC 聊天素材引用功能说明的引用名空白不变量小节,写明这是已知残留
resourceReferences.ts 抽出 contentPartToken:part → 正文 token 的逐字投影,不补白、不裁剪(补白只在 joinMentionText 拼整段文本时加);directCodexContentToPromptText 与新导出 contentPartText 都走这一份
contentPartText 就是单个非文本 part 的文本形态:资源落 @resourceId、Skill 落 $名称、附件落 @附件名、运行区域落 @标签,文本 part 返回 null
$insertContentAtSelection 的兜底链改成 provider 的 mentionToken 之外再接 contentPartText,解析不出引用也一定有一段正常文本落到光标处,删掉「两条都答不出来就什么都不插」这条违背「粘贴内容逐字保留」的分支
新增单测覆盖四类 part 的文本形态与文本 part 返回 null;新增输入区集成用例(provider 的 toReference 与 mentionToken 都答不出来时粘贴仍按 @resourceId 落下),去掉兜底即变红
同步 AGC 聊天素材引用功能说明的粘贴解析小节,并在实施计划补记本轮评审的收口
ResourceReferenceInput 新增 mentionTokenOrText:provider 的 mentionToken,答不出来(契约被破坏)时退 contentPartText 的通用文本形态;粘贴插入、润色回写的候选扫描、整根替换的落点共用这一条
润色回写的候选不再跳过「答不出 token 的 part」:这类 part 在回包里没被提到时作为末尾孤儿补回来,而不是在翻译阶段直接消失
applyContentToRoot(初始草稿与润色回写共用)解析不出引用时落文本,不再是整条吃掉
新增两处输入区用例:provider 只答得出引用答不出 token 时润色回写仍保住引用;初始草稿里已解析不出的引用落成 @resourceId 文本。两处去掉兜底即变红
同步 AGC 聊天素材引用功能说明、引用粘贴解析实施计划与决策记录(落点兜底一条,含初始草稿行为变化的记录)
Merge remote-tracking branch 'origin/master' into feat/smart-paste
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m23s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m57s
Project CI / Backend tests (pull_request) Successful in 4m32s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m44s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m24s
Project CI / Frontend tests (pull_request) Successful in 2m37s
Project CI / Native shell tests (pull_request) Successful in 6m49s
Project CI / Repository checks (pull_request) Successful in 2m50s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m52s
cba98be35f
k88936 marked the pull request as ready for review 2026-09-23 13:23:34 +08:00
k88936 added 1 commit 2026-09-23 17:53:35 +08:00
Merge remote-tracking branch 'origin/master' into feat/smart-paste
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m14s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 56s
Project CI / Backend tests (pull_request) Successful in 4m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m4s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m15s
Project CI / Native shell tests (pull_request) Successful in 6m1s
Project CI / Frontend tests (pull_request) Successful in 2m26s
Project CI / Repository checks (pull_request) Successful in 2m33s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m31s
58c59b750e
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
lhk229 approved these changes 2026-09-23 18:12:14 +08:00
Dismissed
lhk229 left a comment
Member

评审结论:通过(Approve)

范围:feat/smart-pastemaster,引用输入区粘贴解析(@显示名 / $名称 → 原位重建 chip),15 个文件,+1257/-101。

已验证

  • 定向测试全绿:resourceReferences.test.ts / referenceSourceProviders.test.ts / resourceReferenceInput.test.tsx 共 79 条通过。
  • apps/ai-game-creator-shell tsc 通过;npm run check:encodinggit diff --check 通过。
  • 保守口径(同名多候选按文本保留、退化 token 跳过、不认别名/大小写/全角)有规则矩阵单测,且包含「显示 → 粘贴」逆运算用例。
  • 粘贴接管只在真的解析出引用且真的插进正文后 preventDefault;Lexical 负载、纯文本、图片粘贴放行默认导入,ranSync 排队场景的处理有注释说明。
  • mentionTokenOrText 一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。
  • normalizeMentionName 把「引用名无空白」前移到名字产生处,token 层幂等再折一次;.env / .gitignore 词干为空回退 asset.id 有回归用例。
  • matchfuzzyLookup / lookup 改名干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。

非阻塞 nit(可顺手修,不影响合并)

  • tests/resourceReferenceInput.test.tsx:467,498,873 三处测试桩写的是 fuzzy_lookup(snake_case),类型上是 fuzzyLookup。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成 fuzzyLookup 或删掉,避免误导后来者照着抄。

已知取舍(文档已声明,认可)

  • hero v2hero-v2 归一化撞名只在解析侧兜住,菜单侧不做冲突检测。
  • 历史文本里的 @hero v2 不再解析(内容不丢、位置可能退化)。
  • Skill 目录冷启动时粘贴 $名称 按字面保留。
## 评审结论:通过(Approve) 范围:`feat/smart-paste` → `master`,引用输入区粘贴解析(`@显示名` / `$名称` → 原位重建 chip),15 个文件,+1257/-101。 ### 已验证 - 定向测试全绿:`resourceReferences.test.ts` / `referenceSourceProviders.test.ts` / `resourceReferenceInput.test.tsx` 共 79 条通过。 - `apps/ai-game-creator-shell` tsc 通过;`npm run check:encoding`、`git diff --check` 通过。 - 保守口径(同名多候选按文本保留、退化 token 跳过、不认别名/大小写/全角)有规则矩阵单测,且包含「显示 → 粘贴」逆运算用例。 - 粘贴接管只在真的解析出引用且真的插进正文后 `preventDefault`;Lexical 负载、纯文本、图片粘贴放行默认导入,`ranSync` 排队场景的处理有注释说明。 - `mentionTokenOrText` 一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。 - `normalizeMentionName` 把「引用名无空白」前移到名字产生处,token 层幂等再折一次;`.env` / `.gitignore` 词干为空回退 `asset.id` 有回归用例。 - `match` → `fuzzyLookup` / `lookup` 改名干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。 ### 非阻塞 nit(可顺手修,不影响合并) - `tests/resourceReferenceInput.test.tsx:467,498,873` 三处测试桩写的是 `fuzzy_lookup`(snake_case),类型上是 `fuzzyLookup`。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成 `fuzzyLookup` 或删掉,避免误导后来者照着抄。 ### 已知取舍(文档已声明,认可) - `hero v2` 与 `hero-v2` 归一化撞名只在解析侧兜住,菜单侧不做冲突检测。 - 历史文本里的 `@hero v2` 不再解析(内容不丢、位置可能退化)。 - Skill 目录冷启动时粘贴 `$名称` 按字面保留。
lhk229 dismissed lhk229's review 2026-09-23 18:13:58 +08:00
Reason:

上一版评审正文乱码,作废重发

lhk229 approved these changes 2026-09-23 18:15:15 +08:00
lhk229 left a comment
Member

评审结论:通过(Approve)

范围:feat/smart-pastemaster,引用输入区粘贴解析(@显示名 / $名称 原位重建 chip),15 个文件,+1257/-101。

已验证

  • 定向测试全绿:resourceReferences.test.ts / referenceSourceProviders.test.ts / resourceReferenceInput.test.tsx 共 79 条通过。
  • apps/ai-game-creator-shell tsc 通过;npm run check:encodinggit diff --check 通过。
  • 保守口径(同名多候选按文本保留、退化 token 跳过、不认别名 / 大小写 / 全角)有规则矩阵单测,且包含「显示到粘贴」的逆运算用例。
  • 粘贴接管只在真的解析出引用且真的插进正文后才 preventDefault;Lexical 负载、纯文本、图片粘贴放行默认导入,ranSync 排队场景的处理有注释说明。
  • mentionTokenOrText 一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。
  • normalizeMentionName 把「引用名无空白」前移到名字产生处,token 层幂等再折一次;.env / .gitignore 词干为空回退 asset.id 有回归用例。
  • match 改名 fuzzyLookup / 新增 lookup 干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。

非阻塞 nit(可顺手修,不影响合并)

  • tests/resourceReferenceInput.test.tsx:467,498,873 三处测试桩写的是 fuzzy_lookup(snake_case),类型上是 fuzzyLookup。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成 fuzzyLookup 或删掉,避免误导后来者照着抄。

已知取舍(文档已声明,认可)

  • hero v2hero-v2 归一化撞名只在解析侧兜住,菜单侧不做冲突检测。
  • 历史文本里的 @hero v2 不再解析(内容不丢、位置可能退化)。
  • Skill 目录冷启动时粘贴 $名称 按字面保留。
## 评审结论:通过(Approve) 范围:`feat/smart-paste` → `master`,引用输入区粘贴解析(`@显示名` / `$名称` 原位重建 chip),15 个文件,+1257/-101。 ### 已验证 - 定向测试全绿:`resourceReferences.test.ts` / `referenceSourceProviders.test.ts` / `resourceReferenceInput.test.tsx` 共 79 条通过。 - `apps/ai-game-creator-shell` tsc 通过;`npm run check:encoding`、`git diff --check` 通过。 - 保守口径(同名多候选按文本保留、退化 token 跳过、不认别名 / 大小写 / 全角)有规则矩阵单测,且包含「显示到粘贴」的逆运算用例。 - 粘贴接管只在真的解析出引用且真的插进正文后才 `preventDefault`;Lexical 负载、纯文本、图片粘贴放行默认导入,`ranSync` 排队场景的处理有注释说明。 - `mentionTokenOrText` 一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。 - `normalizeMentionName` 把「引用名无空白」前移到名字产生处,token 层幂等再折一次;`.env` / `.gitignore` 词干为空回退 `asset.id` 有回归用例。 - `match` 改名 `fuzzyLookup` / 新增 `lookup` 干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。 ### 非阻塞 nit(可顺手修,不影响合并) - `tests/resourceReferenceInput.test.tsx:467,498,873` 三处测试桩写的是 `fuzzy_lookup`(snake_case),类型上是 `fuzzyLookup`。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成 `fuzzyLookup` 或删掉,避免误导后来者照着抄。 ### 已知取舍(文档已声明,认可) - `hero v2` 与 `hero-v2` 归一化撞名只在解析侧兜住,菜单侧不做冲突检测。 - 历史文本里的 `@hero v2` 不再解析(内容不丢、位置可能退化)。 - Skill 目录冷启动时粘贴 `$名称` 按字面保留。
k88936 added 1 commit 2026-09-23 18:50:11 +08:00
Remove redundant fuzzy_lookup methods from test ReferenceProviders for cleanup
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
136e1ebceb
k88936 added 1 commit 2026-09-23 18:50:47 +08:00
Merge remote-tracking branch 'origin/master' into feat/smart-paste
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
c0e233fe6c
k88936 merged commit 661867c307 into master 2026-09-23 18:51:24 +08:00
k88936 deleted branch feat/smart-paste 2026-09-23 18:51:24 +08:00
Sign in to join this conversation.