Skip to content

Commit b341fe8

Browse files
edyedy
authored andcommitted
fix(tui): per-file polish round 8 — prompt part/frecency
expandPastedTextPlaceholders passed user-pasted text as the replacement string to String.replace, so pasted content containing $&, $', $$ or $n was interpreted as substitution patterns and corrupted on the open-editor and copy paths; switch to a function replacer and add a regression test. Frecency pruning called setStore with a plain object, which Solid merges rather than replaces, so pruned paths were never removed (unbounded in-memory growth past the cap); reconcile instead.
1 parent b76b4f7 commit b341fe8

4 files changed

Lines changed: 20 additions & 4 deletions

File tree

MERGE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ fork 与上游改了同一处(常见于 TUI 视觉/UX、core 加固逻辑)
203203
| 逐文件打磨轮5:`routes/session/{index,dialog-message}.tsx` || 保留 bug 修复:TextPart `tableOptions` 由 setup 期字面量改 `createMemo`(原 `theme.border` 快照在主题切换后冻结表格边框色);`index.tsx` revert 分支与 `dialog-message.tsx` revert/copy/fork 四处 `sync.data.part[id].reduce``?? []`(消息 part 未加载时 `.reduce` 抛错);moveChild 方向语义(`- direction`)疑似与命令名相反但属上游行为,择要跳过(上游若已做可取上游版本) |
204204
| 逐文件打磨轮6:`feature-plugins/system/diff-viewer-ui.tsx``routes/session/{dialog-timeline,permission,dialog-fork-from-timeline,subagent-footer,question}.tsx`+`routes/home*.tsx`+`feature-plugins/{system/*,home/*}` 已审计) || 保留 bug 修复:`Panel` `borderProps.borderColor` 由 setup 期快照改 getter(`theme.border` 随对象展开保持响应式,原主题切换后 diff 查看器面板边框色冻结,`Separator` 已用 `() =>` 正确);fork `.data!.id` 无守卫(与 `dialog-message.tsx` fork 同款上游模式)择要跳过;`diff-viewer-file-tree-utils.moveFileTreeSelectionToFile` 仅测试引用但恐留待键位,暂不删(上游若已做可取上游版本) |
205205
| 逐文件打磨轮7:`component/dialog-model.tsx``dialog-{agent,move-session,theme-list,variant,session-delete-failed,session-list,skill,stash,workspace-file-changes,workspace-list}.tsx`+`command-palette.tsx` 已审计) || 保留 bug 修复:收藏/最近模型选项由可选的 `model.id` 改用可靠的映射键 `item.modelID``id?: string` 缺省时 `model.id.includes("-nano")` 抛错、选中项 modelID 变 undefined;并与 providerOptions 分支用键一致);`dialog-theme-list` `theme.all()` setup 期快照(需订阅 `subscribeThemes` 才能修,`all()` 非响应式且启动后基本不变)择要跳过(上游若已做可取上游版本) |
206+
| 逐文件打磨轮8:`prompt/{part.ts,frecency.tsx}` + `test/prompt/part.test.ts``prompt/{display,history,stash,traits}` + `component/prompt/{history,frecency,stash}` 再导出壳已审计) || 保留 bug 修复:`expandPastedTextPlaceholders``String.replace(needle, part.text)` 改函数替换 `() => part.text`(粘贴含 `$&`/`$'`/`$$`/`$n` 的文本经 open-editor/copy 路径被当替换模式解释而损坏,补回归测试);`frecency` 剪枝 `setStore("data", obj)``reconcile(obj)`(Solid store 合并语义使被剪路径永不删除,会话内 map 无限增长、每次 update 全量重写 jsonl)(上游若已做可取上游版本) |
206207

207208
### 其他
208209

packages/tui/src/prompt/frecency.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from "path"
22
import { onMount } from "solid-js"
3-
import { createStore } from "solid-js/store"
3+
import { createStore, reconcile } from "solid-js/store"
44
import { createSimpleContext } from "../context/helper"
55
import { useTuiPaths } from "../context/runtime"
66
import { appendText, readText, writeText } from "../util/persistence"
@@ -64,7 +64,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont
6464
const sorted = Object.entries(store.data)
6565
.sort(([, a], [, b]) => b.lastOpen - a.lastOpen)
6666
.slice(0, MAX_FRECENCY_ENTRIES)
67-
setStore("data", Object.fromEntries(sorted))
67+
setStore("data", reconcile(Object.fromEntries(sorted)))
6868
writeText(
6969
frecencyPath,
7070
sorted.map(([entryPath, entry]) => JSON.stringify({ path: entryPath, ...entry })).join("\n") + "\n",

packages/tui/src/prompt/part.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function stripPromptPartIDs<Part extends { id: string; messageID: string;
88
export function expandPastedTextPlaceholders(text: string, parts: readonly unknown[]) {
99
return parts.reduce<string>((result, part) => {
1010
if (!isPastedTextPart(part)) return result
11-
return result.replace(part.source.text.value, part.text)
11+
return result.replace(part.source.text.value, () => part.text)
1212
}, text)
1313
}
1414

packages/tui/test/prompt/part.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from "bun:test"
2-
import { expandTrackedPastedText, stripPromptPartIDs } from "../../src/prompt/part"
2+
import { expandPastedTextPlaceholders, expandTrackedPastedText, stripPromptPartIDs } from "../../src/prompt/part"
33

44
describe("prompt part", () => {
55
test("strips persisted IDs from reused parts", () => {
@@ -50,4 +50,19 @@ describe("prompt part", () => {
5050
]),
5151
).toBe(`keep ${marker} then alpha\nbeta\ngamma tail`)
5252
})
53+
54+
test("inserts pasted text with $ patterns literally", () => {
55+
const marker = "[Pasted ~2 lines]"
56+
const pasted = "sed 's/a/$&/' && echo $'x' cost $$5"
57+
58+
expect(
59+
expandPastedTextPlaceholders(`before ${marker} after`, [
60+
{
61+
type: "text",
62+
text: pasted,
63+
source: { text: { value: marker } },
64+
},
65+
]),
66+
).toBe(`before ${pasted} after`)
67+
})
5368
})

0 commit comments

Comments
 (0)