按 review 收口状态条与对话 Markdown 的容错
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m7s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m26s
Project CI / Backend tests (pull_request) Successful in 3m51s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m10s
Project CI / Native shell tests (pull_request) Successful in 6m3s
Project CI / Frontend tests (pull_request) Successful in 2m16s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m26s
Project CI / Repository checks (pull_request) Successful in 1m52s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m7s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m26s
Project CI / Backend tests (pull_request) Successful in 3m51s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m10s
Project CI / Native shell tests (pull_request) Successful in 6m3s
Project CI / Frontend tests (pull_request) Successful in 2m16s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m26s
Project CI / Repository checks (pull_request) Successful in 1m52s
- apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx 围栏归一化补「同一行出现第二段围栏串就跳过」判据:行内代码 `文本 ```x``` ` 的末尾那截曾被当成收场围栏拆开,凭空造出一个开场围栏、把后面的正文全变成代码 - apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx 注释补归一化顺序与行内代码判据 - apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectTurnStatus.ts 修正 displayBusy 的说明:原注释仍写着卡片只读 nativeRunning,与现状相反 - apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx 新增行内代码用例(去掉新判据即红) - docs/project-memory/shared-memory/decision-log.md 新增 2026-09-24 条:记录卡片口径翻转(更正 2026-09-22「卡片口径取保守」那条)并处置其「running 在渲染层就没有消费者」的预言,同时记录状态条几何约束与对话 Markdown 容错口径 - docs/project-memory/shared-memory/pitfalls.md 围栏归一化条补行内代码判据
This commit is contained in:
@@ -49,21 +49,30 @@ function normalizeMarkdownBlankLines(text: string) {
|
||||
* 别的字符(`const s = "```";`)也不会被拆开。
|
||||
*
|
||||
* 引用块与列表项开头的围栏(`> ```js`、`- ```js`)是**合法结构**,不是粘住的:整行跳过,
|
||||
* 拆开只会把它们从引用块 / 列表项里挪出来。
|
||||
* 拆开只会把它们从引用块 / 列表项里挪出来。同一行出现第二段围栏串(行内代码
|
||||
* ``文本 ```x``` ``)时同样跳过——末尾那截不是收场围栏。
|
||||
*/
|
||||
const GLUED_FENCE_LINE =
|
||||
/([^\s`~])[ \t]*(`{3,}|~{3,})([A-Za-z0-9+#._-]*)[ \t]*$/;
|
||||
const BLOCK_MARKER_LINE = /^\s*(?:[-*+]|\d+[.)]|>)\s/;
|
||||
const FENCE_RUN_TOKEN = /(`{3,}|~{3,})/g;
|
||||
|
||||
function normalizeMarkdownFences(text: string) {
|
||||
return text
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.split('\n')
|
||||
.map((line) =>
|
||||
BLOCK_MARKER_LINE.test(line)
|
||||
? line
|
||||
: line.replace(GLUED_FENCE_LINE, '$1\n$2$3'),
|
||||
)
|
||||
.map((line) => {
|
||||
if (BLOCK_MARKER_LINE.test(line)) {
|
||||
return line;
|
||||
}
|
||||
// 行里还有第二条围栏串时不动:那是行内代码(`文本 ```x``` `),末尾那截不是收场围栏。
|
||||
// 拆开它会凭空多出一个开场围栏,把后面的正文全变成代码。
|
||||
const runs = line.match(FENCE_RUN_TOKEN);
|
||||
if (runs && runs.length > 1) {
|
||||
return line;
|
||||
}
|
||||
return line.replace(GLUED_FENCE_LINE, '$1\n$2$3');
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
@@ -368,6 +377,7 @@ function ChatMarkdownMessageImpl({
|
||||
streaming ? streamingMarkdownComponents : markdownComponents
|
||||
}
|
||||
>
|
||||
{/* 顺序有讲究:先拆粘住的围栏,空行压缩的 ` ```…``` ` 配对才认得出真正的代码块。 */}
|
||||
{preserveBlankLines
|
||||
? normalizeMarkdownFences(text)
|
||||
: normalizeMarkdownBlankLines(normalizeMarkdownFences(text))}
|
||||
|
||||
+5
-3
@@ -13,11 +13,13 @@ import type {
|
||||
* 一次讲清楚,组件只读这一个对象:
|
||||
*
|
||||
* - `nativeRunning`:**原生真相**。只由订阅 reducer 的 `turnRunning` 给出(`turn.started`
|
||||
* 已到、`turn.completed` 未到)。它决定"陶泥儿正在处理"这类原生过程提示。
|
||||
* 已到、`turn.completed` 未到)。
|
||||
* - `commandInFlight`:**本地真相**。本次会话的发送命令是否在飞(写权限门 → invoke →
|
||||
* 收尾);它从按下发送那一刻就为真,与原生是否已经开始无关。
|
||||
* - `displayBusy`:header / composer 该读的忙态,就是两者的并集:只要有一条成立就不能再
|
||||
* 接受新的发送。
|
||||
* - `displayBusy`:header / composer / 「陶泥儿正在处理」卡片该读的忙态,就是两者的并集:
|
||||
* 只要有一条成立就不能再接受新的发送。卡片读它而不是 `nativeRunning`:`turn.started`
|
||||
* 要等宿主应答返回才发出,只认原生真相会让模型首 token 之前那十来秒没有任何「正在处理」
|
||||
* 的交代(2026-09-24 口令)。
|
||||
* - `latestTurnState`:最新一轮在界面上的三态(投影结果);没有回合时为 null。
|
||||
*
|
||||
* 约定:新增"忙/在跑"类判据一律先落进这里,不要在组件里再拼布尔。
|
||||
|
||||
@@ -230,6 +230,15 @@ describe('ChatMarkdownMessage', () => {
|
||||
expect(container.textContent).toContain('行内 code 保持原样');
|
||||
});
|
||||
|
||||
it('行内代码里的三段反引号保持原样,不凭空造出围栏', () => {
|
||||
const { container } = render(
|
||||
<ChatMarkdownMessage role="assistant" text={'文本 ```x```\n'} />,
|
||||
);
|
||||
|
||||
expect(container.querySelector('p code')?.textContent).toBe('x');
|
||||
expect(container.querySelectorAll('pre')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('引用块与列表项开头的围栏是合法结构,不做拆分', () => {
|
||||
const { container } = render(
|
||||
<ChatMarkdownMessage
|
||||
|
||||
Reference in New Issue
Block a user