fix(ui-primitives): end bare URLs before astral symbols and add Web snapshot

This commit is contained in:
Turtle
2026-10-26 19:10:55 +08:00
parent bdfc0c1d12
commit ca40dc544e
16 changed files with 242 additions and 19 deletions
@@ -0,0 +1,50 @@
/** A bare URL in a settled CJK reply ends before the following full-width punctuation. */
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium, type Browser, type Page } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
assertFixtureInventory, compareOrRefreshGolden,
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/autolink-cjk-punctuation', import.meta.url))
const MODE = webSnapshotMode()
const URL_TEXT = 'http://127.0.0.1:8790/'
describe('web e2e: bare URL before CJK punctuation', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
beforeAll(async () => {
scaffold = await launchWebScaffold({})
await seedSession(scaffold, await readFile(join(SNAPSHOT_DIR, 'session.v4.jsonl'), 'utf8'), 'autolink-cjk-punctuation')
browser = await chromium.launch()
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.locator('[role="treeitem"]').first().click()
await page.locator('[role="treeitem"]').nth(1).click()
await page.getByRole('link', { name: URL_TEXT, exact: true }).waitFor()
}, 120_000)
afterAll(async () => {
await browser?.close()
await scaffold?.close()
})
it('links only the URL and renders the following sentence as text', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-autolink-cjk-punctuation'))
const link = page.getByRole('link', { name: URL_TEXT, exact: true })
expect(await link.getAttribute('href')).toBe(URL_TEXT)
const prose = await link.locator('..').ariaSnapshot()
await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'reply.expected.md'), prose, MODE)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
await assertFixtureInventory(SNAPSHOT_DIR, ['session.v4.jsonl', 'reply.expected.md'])
})
})
+1
View File
@@ -163,6 +163,7 @@
"tests/present.e2e.ts",
"tests/present-svg.e2e.ts",
"tests/present-after-closing-reply.e2e.ts",
"tests/autolink-cjk-punctuation.e2e.ts",
"tests/composer-draft-scroll.e2e.ts",
"tests/composer-focus.e2e.ts",
"tests/composer-placeholder.e2e.ts",
@@ -54,8 +54,8 @@
en: ca75c51c89c2c9b0
zh: de54f3467ef3b9be
/deepseek-ai-dsh-client-ui-primitives/known-limitations-and-deferred-work:
en: 9c5bc17e35af9d0b
zh: 4f1a142c7d56cf72
en: 2f2db9565903b4e6
zh: ca9d8fed836d857c
/deepseek-ai-dsh-client-ui-primitives/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: 3bb0c20c93f16f98
+1
View File
@@ -220,6 +220,7 @@ These limits define how the atoms behave at the edges; they are current package
- **Streaming defers cross-boundary reference resolution** — a reference-style link or footnote whose definition sits on the other side of the incremental freeze boundary renders as literal text while the reply streams; the settled full parse at finalize resolves it.
- **A long highlighted fence retains its complete token DOM** — streaming avoids re-parsing, re-tokenizing, and reconciling the completed prefix, but it does not discard old colors or virtualize token spans. Final DOM cardinality therefore still follows the fence's token count; nested/container fences and a pathological single long line remain on the general tail path.
- **Overlong lines stay uncolored** — a source line of 1,000 or more UTF-16 units renders as one plain run in every highlighted surface, because TextMate scanning cost grows superlinearly with line length and a single generated line can otherwise block the page for seconds. The grammar state skips that line, so a comment or template literal that opens or closes on it mis-colors the following lines. Highlighting still runs synchronously during render, so a file with thousands of ordinary lines costs proportionally more.
- **Bare URLs end before non-ASCII punctuation** — the [tokenizer patch](../../../patches/micromark-extension-gfm-autolink-literal@2.1.0.patch) and the [transform patch](../../../patches/mdast-util-gfm-autolink-literal@2.0.1.patch) end a GFM autolink literal at non-ASCII punctuation, symbols, or whitespace, so a URL with an unencoded character such as `(` or `・` in its path links only up to it; write such a URL as `<…>` or `[…](…)`. A URL followed directly by CJK letters still links them, because paths such as `/wiki/中文` are valid. Both patches are keyed to exact versions: an upgrade of either package must re-apply or retire its patch, otherwise the boundary rule silently disappears.
- **The Go struct-field rule is patched locally** — when the Go grammar loads, its repeated one-line struct-field rule is replaced with the rule [worlpaker/go-syntax#25](https://github.com/worlpaker/go-syntax/pull/25) merged, because the vendored rule backtracks exponentially on lines such as a brace-bearing comment after `struct {`. If a dependency upgrade removes that rule, loading the grammar throws and Go code renders as plain text; delete the patch once `@shikijs/langs` ships the upstream rule.
- **The fish logo is a redrawn approximation** — its font glyph has no exportable vector geometry in the local design data, so a hand-authored recreation stands in until an exact export path exists.
- **`Pill` and `Input` have no design source** — both atoms are self-defined; the sidebar search field and view-tab strip that resemble them are consumer-owned compositions, not these atoms.
@@ -220,6 +220,7 @@ kind: "package-library"
- **流式期间跨边界引用解析被推迟**:定义落在增量冻结边界另一侧的引用式链接或脚注,在回复流式输出期间渲染为字面文本;定稿时的全量解析会将其解析。
- **长高亮 fence 会保留完整 token DOM**:流式路径避免重新解析、重新 tokenize 和 reconcile 已完成前缀,但不会丢弃旧颜色或虚拟化 token span。因此最终 DOM 数量仍随 fence 的 token 数增长;嵌套/容器内 fence 与病态的单个超长行仍走通用尾部路径。
- **超长行不着色**:所有高亮界面中,长度达到 1,000 个 UTF-16 单元的源码行渲染为一段纯文本,因为 TextMate 扫描成本随行长超线性增长,单个生成行否则会阻塞页面数秒。语法状态会跳过该行,因此在该行开始或结束的注释、模板字符串会使后续行着色错误。高亮仍在渲染期间同步执行,因此数千个普通行的文件仍按比例增加开销。
- **裸 URL 在非 ASCII 标点前结束**:[tokenizer 补丁](../../../patches/micromark-extension-gfm-autolink-literal@2.1.0.patch)和[转换补丁](../../../patches/mdast-util-gfm-autolink-literal@2.0.1.patch)让 GFM 自动链接在非 ASCII 标点、符号或空白处结束,因此路径中含未编码 `(`、`・` 等字符的 URL 只链接到该字符之前;这类 URL 请写成 `<…>` 或 `[…](…)`。URL 后直接跟中日韩文字时仍会并入链接,因为 `/wiki/中文` 这类路径是合法的。两个补丁都绑定精确版本:升级任一依赖时必须重新应用或撤销对应补丁,否则该边界规则会悄然失效。
- **Go 结构体字段规则在本地打了补丁**:Go 语法加载时,重复匹配单行结构体字段的规则会替换为 [worlpaker/go-syntax#25](https://github.com/worlpaker/go-syntax/pull/25) 合入的规则,因为内置规则在 `struct {` 之后含花括号注释等行上会指数级回溯。若依赖升级后该规则消失,加载语法会抛错,Go 代码以纯文本显示;`@shikijs/langs` 发布上游规则后删除此补丁。
- **鱼形标志是重新绘制的近似版本**:它来自字体字形,而本地设计数据无法导出其矢量几何;在获得精确导出路径前,使用手工重建版本代替。
- **`Pill` 与 `Input` 没有设计来源**:两个原子组件均自行定义;与其相似的侧边栏搜索字段和视图标签条由消费方组合,不是这些原子组件。
@@ -43,6 +43,16 @@
<path d="M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138" stroke="currentColor">
#text "https://autolink.example.com"
#text " literal."
<p>
#text "中文 "
<a href="https://autolink.example.com/a" rel="noopener noreferrer" target="_blank">
<svg aria-hidden="true" class="_linkIcon_404681" fill="none" height="14" stroke-width="1.3" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99986 14.0887C11.3626 14.0887 14.0886 11.3627 14.0886 7.99998C14.0886 4.63727 11.3626 1.91125 7.99986 1.91125C4.63715 1.91125 1.91113 4.63727 1.91113 7.99998C1.91113 11.3627 4.63715 14.0887 7.99986 14.0887Z" stroke="currentColor">
<path d="M2.34619 8H13.6538" stroke-linecap="square" stroke="currentColor">
<path d="M7.99976 14.0889C9.23509 14.0889 10.1743 11.3629 10.1743 8.00006C10.1743 4.63739 9.23509 1.91138 7.99976 1.91138" stroke="currentColor">
<path d="M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138" stroke="currentColor">
#text "https://autolink.example.com/a"
#text "。后文 ends before CJK punctuation."
<p>
#text "[spaces encoded]("
<a href="https://example.com/a" rel="noopener noreferrer" target="_blank">
@@ -43,6 +43,16 @@
<path d="M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138" stroke="currentColor">
#text "https://autolink.example.com"
#text " literal."
<p>
#text "中文 "
<a href="https://autolink.example.com/a" rel="noopener noreferrer" target="_blank">
<svg aria-hidden="true" class="_linkIcon_404681" fill="none" height="14" stroke-width="1.3" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99986 14.0887C11.3626 14.0887 14.0886 11.3627 14.0886 7.99998C14.0886 4.63727 11.3626 1.91125 7.99986 1.91125C4.63715 1.91125 1.91113 4.63727 1.91113 7.99998C1.91113 11.3627 4.63715 14.0887 7.99986 14.0887Z" stroke="currentColor">
<path d="M2.34619 8H13.6538" stroke-linecap="square" stroke="currentColor">
<path d="M7.99976 14.0889C9.23509 14.0889 10.1743 11.3629 10.1743 8.00006C10.1743 4.63739 9.23509 1.91138 7.99976 1.91138" stroke="currentColor">
<path d="M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138" stroke="currentColor">
#text "https://autolink.example.com/a"
#text "。后文 ends before CJK punctuation."
<p>
#text "[spaces encoded]("
<a href="https://example.com/a" rel="noopener noreferrer" target="_blank">
@@ -23,8 +23,17 @@ describe.each([parseGfm, parseGfmWithMath])('autolink literals in %o', (parse) =
const qq = 'https://qm.qq.com/cgi-bin/qm/qr?k=Abc%2BDef%2Fghi%3D%3D&authKey=tok%252Fvalue%253D&noverify=0'
expect(phrasing(parse(`加入群聊:${qq},欢迎。`))).toEqual(['加入群聊:', { link: qq }, ',欢迎。'])
// After an unbalanced `[`, the literal comes from the text transform instead of the tokenizer.
// Its case-insensitive Unicode regex must keep ASCII `s` and `k`, which fold with `ſ` and `K`.
expect(phrasing(parse('[x https://example.com/docs/Kit?s=K。后'))).toEqual(
['[x ', { link: 'https://example.com/docs/Kit?s=K' }, '。后'])
for (const prefix of ['', '[x ']) {
expect(phrasing(parse(`${prefix}https://example.com/a😀下一句`))).toEqual(
[`${prefix}`, { link: 'https://example.com/a' }, '😀下一句'].filter(part => part !== ''))
expect(phrasing(parse(`${prefix}https://example.com/a\u3000下一句`))).toEqual(
[`${prefix}`, { link: 'https://example.com/a' }, '\u3000下一句'].filter(part => part !== ''))
expect(phrasing(parse(`${prefix}https://example.com/a𝒳b ok`))).toEqual(
[`${prefix}`, { link: 'https://example.com/a𝒳b' }, ' ok'].filter(part => part !== ''))
}
// Unencoded non-ASCII punctuation inside a URL also ends it.
expect(phrasing(parse('https://zh.wikipedia.org/wiki/中文(消歧义)'))).toEqual(
[{ link: 'https://zh.wikipedia.org/wiki/中文' }, '(消歧义)'])
@@ -171,6 +171,8 @@ const CORPUS: Record<string, string> = {
'',
'<https://deepseek.com> and bare autolink https://autolink.example.com literal.',
'',
'中文 https://autolink.example.com/a。后文 ends before CJK punctuation.',
'',
'[spaces encoded](https://example.com/a b)',
'',
'[![linked badge](https://example.com/badge.png)](https://example.com/target) image-only anchors carry no leading glyph.',
@@ -1,5 +1,5 @@
diff --git a/lib/index.js b/lib/index.js
index c5ca771c24dd914e342f791716a822431ee32b3a..11265b3fe41bb41985f149e2bd8f2fc2b6f85e2e 100644
index c5ca771c24dd914e342f791716a822431ee32b3a..b6fb7f576043878d53d449f8bb6036566ecf89d2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -131,7 +131,8 @@ function transformGfmAutolinkLiterals(tree) {
@@ -7,8 +7,8 @@ index c5ca771c24dd914e342f791716a822431ee32b3a..11265b3fe41bb41985f149e2bd8f2fc2
tree,
[
- [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl],
+ // Non-ASCII punctuation and symbols, such as `。` or `,`, end the path in CJK prose.
+ [/(https?:\/\/|www(?=\.))([-.\w]+)((?:[!-~]|(?![\p{P}\p{S}])[^ \t\r\n])*)/giu, findUrl],
+ // Non-ASCII punctuation, symbols, and whitespace, such as `。`, `,`, or ` `, end the path in CJK prose.
+ [/(https?:\/\/|www(?=\.))([-.\w]+)((?:[!-~]|(?![\p{P}\p{S}\s])[^ \t\r\n])*)/giu, findUrl],
[/(?<=^|\s|\p{P}|\p{S})([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/gu, findEmail]
],
{ignore: ['link', 'linkReference']}
@@ -1,8 +1,23 @@
diff --git a/dev/lib/syntax.js b/dev/lib/syntax.js
index 904e27200ae4980183beda525d33feef70c2e7f7..f05dff0e41b478e856b8419a306dfba163e9a9af 100644
index 904e27200ae4980183beda525d33feef70c2e7f7..1e05880bc1e7df0e05137811cd182f10872985df 100644
--- a/dev/lib/syntax.js
+++ b/dev/lib/syntax.js
@@ -662,7 +662,8 @@ function tokenizePath(effects, ok) {
@@ -16,6 +16,7 @@ const wwwPrefix = {tokenize: tokenizeWwwPrefix, partial: true}
const domain = {tokenize: tokenizeDomain, partial: true}
const path = {tokenize: tokenizePath, partial: true}
const trail = {tokenize: tokenizeTrail, partial: true}
+const astralPunctuation = {tokenize: tokenizeAstralPunctuation, partial: true}
const emailDomainDotTrail = {
tokenize: tokenizeEmailDomainDotTrail,
partial: true
@@ -659,10 +660,15 @@ function tokenizePath(effects, ok) {
return effects.check(trail, ok, pathAtPunctuation)(code)
}
+ if (highSurrogate(code)) {
+ return effects.check(astralPunctuation, ok, pathAtPunctuation)(code)
+ }
+
if (
code === codes.eof ||
markdownLineEndingOrSpace(code) ||
@@ -12,7 +27,7 @@ index 904e27200ae4980183beda525d33feef70c2e7f7..f05dff0e41b478e856b8419a306dfba1
) {
return ok(code)
}
@@ -762,7 +763,8 @@ function tokenizeTrail(effects, ok, nok) {
@@ -762,11 +768,16 @@ function tokenizeTrail(effects, ok, nok) {
// So is whitespace.
code === codes.eof ||
markdownLineEndingOrSpace(code) ||
@@ -22,7 +37,15 @@ index 904e27200ae4980183beda525d33feef70c2e7f7..f05dff0e41b478e856b8419a306dfba1
) {
return ok(code)
}
@@ -932,6 +934,17 @@ function previousEmail(code) {
+ if (highSurrogate(code)) {
+ return effects.check(astralPunctuation, ok, nok)(code)
+ }
+
return nok(code)
}
@@ -932,6 +943,56 @@ function previousEmail(code) {
return !(code === codes.slash || gfmAtext(code))
}
@@ -36,24 +59,77 @@ index 904e27200ae4980183beda525d33feef70c2e7f7..f05dff0e41b478e856b8419a306dfba1
+function nonAsciiPunctuation(code) {
+ return code !== codes.eof && code > 127 && unicodePunctuation(code)
+}
+
+/**
+ * Whether `code` is a UTF-16 high surrogate, which starts a character outside
+ * the BMP such as `😀`.
+ *
+ * @param {Code} code
+ * @returns {boolean}
+ */
+function highSurrogate(code) {
+ return code !== codes.eof && code >= 0xd800 && code <= 0xdbff
+}
+
+/**
+ * Check whether a surrogate pair is punctuation or a symbol.
+ *
+ * @this {TokenizeContext}
+ * @type {Tokenizer}
+ */
+function tokenizeAstralPunctuation(effects, ok, nok) {
+ /** @type {number} */
+ let high
+
+ return start
+
+ /** @type {State} */
+ function start(code) {
+ high = /** @type {number} */ (code)
+ effects.consume(code)
+ return low
+ }
+
+ /** @type {State} */
+ function low(code) {
+ return code !== codes.eof && code >= 0xdc00 && code <= 0xdfff &&
+ /[\p{P}\p{S}]/u.test(String.fromCharCode(high, code))
+ ? ok(code)
+ : nok(code)
+ }
+}
+
/**
* @param {Code} code
* @returns {boolean}
diff --git a/lib/syntax.js b/lib/syntax.js
index dffbaf15146b34d3b3f634286629ccb148866f4c..00c336f17c389929d58dab156a72d8961a45caf8 100644
index dffbaf15146b34d3b3f634286629ccb148866f4c..60b1d5d43811b31592728b4539c5f4d43174dd35 100644
--- a/lib/syntax.js
+++ b/lib/syntax.js
@@ -584,7 +584,7 @@ function tokenizePath(effects, ok) {
@@ -19,6 +19,10 @@ const trail = {
tokenize: tokenizeTrail,
partial: true
};
+const astralPunctuation = {
+ tokenize: tokenizeAstralPunctuation,
+ partial: true
+};
const emailDomainDotTrail = {
tokenize: tokenizeEmailDomainDotTrail,
partial: true
@@ -584,7 +588,10 @@ function tokenizePath(effects, ok) {
if (code === 33 || code === 34 || code === 38 || code === 39 || code === 41 || code === 42 || code === 44 || code === 46 || code === 58 || code === 59 || code === 60 || code === 63 || code === 93 || code === 95 || code === 126) {
return effects.check(trail, ok, pathAtPunctuation)(code);
}
- if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
+ if (highSurrogate(code)) {
+ return effects.check(astralPunctuation, ok, pathAtPunctuation)(code);
+ }
+ if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || nonAsciiPunctuation(code)) {
return ok(code);
}
effects.consume(code);
@@ -665,7 +665,7 @@ function tokenizeTrail(effects, ok, nok) {
@@ -665,9 +672,12 @@ function tokenizeTrail(effects, ok, nok) {
// `<` is an end.
code === 60 ||
// So is whitespace.
@@ -61,8 +137,13 @@ index dffbaf15146b34d3b3f634286629ccb148866f4c..00c336f17c389929d58dab156a72d896
+ code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || nonAsciiPunctuation(code)) {
return ok(code);
}
+ if (highSurrogate(code)) {
+ return effects.check(astralPunctuation, ok, nok)(code);
+ }
return nok(code);
@@ -816,6 +816,17 @@ function previousEmail(code) {
}
@@ -816,6 +826,52 @@ function previousEmail(code) {
return !(code === 47 || gfmAtext(code));
}
@@ -76,6 +157,41 @@ index dffbaf15146b34d3b3f634286629ccb148866f4c..00c336f17c389929d58dab156a72d896
+function nonAsciiPunctuation(code) {
+ return code !== null && code > 127 && unicodePunctuation(code);
+}
+
+/**
+ * Whether `code` is a UTF-16 high surrogate, which starts a character outside
+ * the BMP such as `😀`.
+ *
+ * @param {Code} code
+ * @returns {boolean}
+ */
+function highSurrogate(code) {
+ return code !== null && code >= 0xd800 && code <= 0xdbff;
+}
+
+/**
+ * Check whether a surrogate pair is punctuation or a symbol.
+ *
+ * @this {TokenizeContext}
+ * @type {Tokenizer}
+ */
+function tokenizeAstralPunctuation(effects, ok, nok) {
+ /** @type {number} */
+ let high;
+ return start;
+
+ /** @type {State} */
+ function start(code) {
+ high = /** @type {number} */code;
+ effects.consume(code);
+ return low;
+ }
+
+ /** @type {State} */
+ function low(code) {
+ return code !== null && code >= 0xdc00 && code <= 0xdfff && /[\p{P}\p{S}]/u.test(String.fromCharCode(high, code)) ? ok(code) : nok(code);
+ }
+}
+
/**
* @param {Code} code
+6 -6
View File
@@ -19,8 +19,8 @@ patchedDependencies:
app-builder-lib@26.17.0: b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348
exceljs@4.4.0: c1bf8ac8f319dab382f08e3a669b715e691d46c703ac5a4163eb383fbedd1d48
lexical@0.49.0: 54c4413db208cf234cb68c6a837f2523d3b319aa17911f1da385af08362e1356
mdast-util-gfm-autolink-literal@2.0.1: 52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46
micromark-extension-gfm-autolink-literal@2.1.0: d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2
mdast-util-gfm-autolink-literal@2.0.1: 7fa6fc7927128a45c3f148d31faf388806895e2de39c14a144200edbc1e7aaa0
micromark-extension-gfm-autolink-literal@2.1.0: 906ae7c0cd1f69e7ee308a41bc0542e0085276ac7517e56e96f0ccaf5a89327d
node-pty@1.2.0-beta.15: 9d30053266d0f39d3debe894c675867f6782da6bcadbc94a693b3cfb9eff8ed0
importers:
@@ -31120,7 +31120,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
mdast-util-gfm-autolink-literal@2.0.1(patch_hash=52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46):
mdast-util-gfm-autolink-literal@2.0.1(patch_hash=7fa6fc7927128a45c3f148d31faf388806895e2de39c14a144200edbc1e7aaa0):
dependencies:
'@types/mdast': 4.0.4
ccount: 2.0.1
@@ -31168,7 +31168,7 @@ snapshots:
mdast-util-gfm@3.1.0(supports-color@9.4.0):
dependencies:
mdast-util-from-markdown: 2.0.3(supports-color@9.4.0)
mdast-util-gfm-autolink-literal: 2.0.1(patch_hash=52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46)
mdast-util-gfm-autolink-literal: 2.0.1(patch_hash=7fa6fc7927128a45c3f148d31faf388806895e2de39c14a144200edbc1e7aaa0)
mdast-util-gfm-footnote: 2.1.0(supports-color@9.4.0)
mdast-util-gfm-strikethrough: 2.0.0(supports-color@9.4.0)
mdast-util-gfm-table: 2.0.0(supports-color@9.4.0)
@@ -31272,7 +31272,7 @@ snapshots:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-autolink-literal@2.1.0(patch_hash=d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2):
micromark-extension-gfm-autolink-literal@2.1.0(patch_hash=906ae7c0cd1f69e7ee308a41bc0542e0085276ac7517e56e96f0ccaf5a89327d):
dependencies:
micromark-util-character: 2.1.1
micromark-util-sanitize-uri: 2.0.1
@@ -31321,7 +31321,7 @@ snapshots:
micromark-extension-gfm@3.0.0:
dependencies:
micromark-extension-gfm-autolink-literal: 2.1.0(patch_hash=d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2)
micromark-extension-gfm-autolink-literal: 2.1.0(patch_hash=906ae7c0cd1f69e7ee308a41bc0542e0085276ac7517e56e96f0ccaf5a89327d)
micromark-extension-gfm-footnote: 2.1.0
micromark-extension-gfm-strikethrough: 2.1.0
micromark-extension-gfm-table: 2.1.1
@@ -0,0 +1,5 @@
- paragraph:
- text: 如果组会要现场演示,工作台还在
- link "http://127.0.0.1:8790/":
- /url: http://127.0.0.1:8790/
- text: 。建议先按时间轴彩排一遍;如果超过 18 分钟,优先删 Step 6 和 Step 8 的备用段,不动第 11 节。
@@ -0,0 +1,10 @@
{"version":4,"isSeeded":false,"createdAt":0,"cwd":"{{cwd}}","delegationDepth":0,"type":"session","id":"{{session:1}}"}
{"type":"turn/start","data":{"turn":1}}
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:1}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"How should I prepare the meeting demo?"}],"source":{"kind":"user","rpcId":"{{rpc:1}}"},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"}
{"type":"session/title","data":{"title":"Meeting demo","messageSeqs":[3],"source":{"kind":"fallback"}}}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"原综述顶部也加了讲稿版入口;完整版仍然保留,方便会后补细节。\n\n如果组会要现场演示,工作台还在 http://127.0.0.1:8790/。建议先按时间轴彩排一遍;如果超过 18 分钟,优先删 Step 6 和 Step 8 的备用段,不动第 11 节。"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"stream":[]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -0,0 +1,7 @@
version: 1
scenario: autolink-cjk-punctuation
profile: web
composition: web-default
recording: authored
header:
class: web-default
+1
View File
@@ -155,6 +155,7 @@
"apps/web/tests/present.e2e.ts",
"apps/web/tests/present-svg.e2e.ts",
"apps/web/tests/present-after-closing-reply.e2e.ts",
"apps/web/tests/autolink-cjk-punctuation.e2e.ts",
"apps/web/tests/composer-draft-scroll.e2e.ts",
"apps/web/tests/composer-focus.e2e.ts",
"apps/web/tests/composer-placeholder.e2e.ts",