fix(tui): tighten welcome layout and acceptance

This commit is contained in:
NI0317
2026-07-30 23:09:58 +08:00
parent eda7a4a3a7
commit 31f66dbd54
12 changed files with 520 additions and 382 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-versioned-tui-first-run-welcome.md
2026-07-30-versioned-tui-first-run-welcome.md: d5635aebd643f3a0c24ab6e0cb952b15f36aa9cb
2026-07-30-versioned-tui-first-run-welcome.zh.md: 7760ebde0e117e0f03d572b8263330b24c5a81ab
2026-07-30-versioned-tui-first-run-welcome.md: 35ac5718a7b7665e903a3975acffbc2711000aa1
2026-07-30-versioned-tui-first-run-welcome.zh.md: f6edf354e42f2797e1227ed04a3fbc9701a04b8b

View File

@@ -18,7 +18,7 @@ The marker is launcher state rather than session persistence because eligibility
The supplied official `24x24` DeepSeek SVG is committed as the visual source. Static full, compact, and minimal terminal rasters sample that exact path at decreasing square resolutions; they do not redraw the contour. Unicode `▀`/`▄`/`█` cells preserve two vertical source pixels per terminal cell, while an explicitly ASCII-only locale uses the bit-equivalent `'`/`_`/`#` fallback. ANSI styling stays outside both the SVG and editable copy: `ctx.tui` supplies a semantic `brand` role, using the official `#4D6BFE` ink when truecolor is available, standard ANSI blue otherwise, and plain text when color is disabled. The normal startup banner retains its existing gradient.
The overlay is centered and consumes the available terminal width. Wide terminals place the full icon beside the title and prose; medium and narrow terminals stack the compact or minimal icon above them; low height removes the icon before reducing prose space. The prose scrolls while the title and only action remain fixed. The quotation is promoted to its own visual paragraph without changing the centrally owned Chinese string. Closing through Enter returns modal ownership to the existing FIFO manager, which restores the editor and leaves the normal startup banner, transcript, and focus behavior intact.
The overlay is centered and consumes the available terminal width, while its height follows actual content and treats 90% of the viewport only as an upper bound. Wide terminals place the full icon beside the title and prose; medium and narrow terminals stack the compact or minimal icon above them; low height removes the icon before reducing prose space. The prose scrolls while the title and only action remain fixed. The quotation is promoted to its own visual paragraph without changing the centrally owned Chinese string. Closing through Enter returns modal ownership to the existing FIFO manager, which restores the editor and leaves the normal startup banner, transcript, and focus behavior intact.
## Verification

View File

@@ -18,7 +18,7 @@ Status: implemented
指定的官方 `24x24` DeepSeek SVG 作为视觉真源提交。静态的完整、紧凑和最小终端栅格图以逐级降低的方形分辨率对该精确路径取样不会重新绘制轮廓。Unicode `▀`/`▄`/`█` 单元格让每个终端单元格保留两个垂直方向的源像素;明确仅支持 ASCII 的 locale 则使用位级等价的 `'`/`_`/`#` 回退。ANSI 样式与 SVG 和可编辑文案完全分离:`ctx.tui` 提供语义化 `brand` 角色,在真彩色可用时使用官方 `#4D6BFE` 色值,否则使用标准 ANSI 蓝色;禁用颜色时则使用纯文本。普通启动 banner 保留现有渐变。
浮层居中显示,并使用可用的终端宽度。宽终端将完整图标置于标题与正文旁边;中等和窄终端把紧凑或最小图标堆叠在正文上方;高度不足时,先移除图标,再减少正文空间。正文可以滚动,而标题和唯一操作保持固定。引用内容会提升为独立的视觉段落,但不会改变集中持有的中文字符串。通过 Enter 关闭浮层后,模态所有权会交还给现有 FIFO 管理器;该管理器恢复编辑器,同时保持正常的启动 banner、transcript文本记录和焦点行为不变。
浮层居中显示,并使用可用的终端宽度;高度则跟随实际内容,仅将 viewport 的 90% 作为上限。宽终端将完整图标置于标题与正文旁边;中等和窄终端把紧凑或最小图标堆叠在正文上方;高度不足时,先移除图标,再减少正文空间。正文可以滚动,而标题和唯一操作保持固定。引用内容会提升为独立的视觉段落,但不会改变集中持有的中文字符串。通过 Enter 关闭浮层后,模态所有权会交还给现有 FIFO 管理器;该管理器恢复编辑器,同时保持正常的启动 banner、transcript文本记录和焦点行为不变。
## 验证

View File

@@ -115,9 +115,12 @@ export async function acknowledgeTuiFirstRunWelcome(
try {
handle = await open(path, 'wx', 0o600)
} catch (error) {
/* v8 ignore else -- the only expected race is another creator publishing this exact marker */
if ((error as NodeJS.ErrnoException | null)?.code === 'EEXIST') {
/* v8 ignore else -- EEXIST is accepted only after the winner is verified as a regular marker */
if (await hasTuiFirstRunWelcomeAcknowledgement(dshHome, version)) return
}
/* v8 ignore next -- unexpected filesystem failures pass through unchanged */
throw error
}
try {
@@ -127,10 +130,14 @@ export async function acknowledgeTuiFirstRunWelcome(
await created.close()
await syncDirectory(directory)
} catch (error) {
await rm(path, { force: true })
/* v8 ignore start -- fault-injected UI coverage proves failed acknowledgements stay uncommitted and retryable */
try {
await handle?.close()
} finally {
await rm(path, { force: true })
}
throw error
} finally {
await handle?.close()
/* v8 ignore stop */
}
}
@@ -170,7 +177,7 @@ export function tuiFirstRunWelcomeArtTier(
innerWidth: number,
viewportRows: number,
): TuiFirstRunWelcomeArtTier | undefined {
if (innerWidth >= 96 && viewportRows >= 22) return 'full'
if (innerWidth >= 96 && viewportRows >= 23) return 'full'
if (innerWidth >= 64 && viewportRows >= 18) return 'compact'
if (innerWidth >= 48 && viewportRows >= 14) return 'minimal'
return undefined
@@ -250,12 +257,13 @@ export class TuiFirstRunWelcomeComponent implements TuiComponent, TuiFocusable {
body = proseLines(this.copy, innerWidth, this.host)
}
this.bodyCapacity = Math.max(1, availableRows - 5 - fixedHeader.length)
const compositionCapacity = Math.max(1, availableRows - 5)
const bodyLimit = Math.max(1, compositionCapacity - fixedHeader.length)
this.bodyCapacity = Math.min(body.length, bodyLimit)
const maxOffset = Math.max(0, body.length - this.bodyCapacity)
this.maxScrollOffset = maxOffset
this.scrollOffset = Math.min(this.scrollOffset, maxOffset)
const visibleBody = body.slice(this.scrollOffset, this.scrollOffset + this.bodyCapacity)
while (visibleBody.length < this.bodyCapacity) visibleBody.push('')
const top = this.host.theme.dim(`${'─'.repeat(Math.max(0, frameWidth - 2))}`)
const separator = this.host.theme.dim(`${'─'.repeat(Math.max(0, frameWidth - 2))}`)
@@ -274,8 +282,9 @@ export class TuiFirstRunWelcomeComponent implements TuiComponent, TuiFocusable {
const composition = fullArt === undefined
? [...fixedHeader, ...visibleBody]
: visibleBody.map((line, index) => {
: Array.from({ length: Math.max(fullArt.length, visibleBody.length) }, (_, index) => {
const art = fullArt[index] ?? ''
const line = visibleBody[index] ?? ''
const left = `${art}${' '.repeat(Math.max(0, fullArtWidth - visibleWidth(art)))}`
return `${left} ${line}`
})
@@ -346,7 +355,7 @@ export function apply(ctx: Context, config: Config): void {
width: '100%',
maxHeight: '90%',
anchor: 'center',
margin: 1,
margin: 0,
},
})
}

View File

@@ -44,7 +44,9 @@ while time.monotonic() < deadline:
action = actions[action_index]
if action.get("delayMs", 0) > 0:
time.sleep(action["delayMs"] / 1000)
if "writeFile" in action:
if "signal" in action:
os.kill(pid, getattr(signal, action["signal"]))
elif "writeFile" in action:
target = os.path.join(cwd, action["writeFile"]["path"])
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, "w", encoding="utf-8") as handle:
@@ -75,6 +77,7 @@ if actual_exit != int(expected_exit):
/** One terminal input or workspace mutation performed after its marker renders. */
type TuiPtyAction =
| { readonly waitFor: string; readonly occurrence?: number; readonly send: string; readonly delayMs?: number }
| { readonly waitFor: string; readonly occurrence?: number; readonly signal: 'SIGTERM'; readonly delayMs?: number }
| {
readonly waitFor: string
readonly occurrence?: number
@@ -190,7 +193,9 @@ async function runWindowsPtySmoke(
&& output.split(actions[actionIndex]!.waitFor).length - 1 >= (actions[actionIndex]!.occurrence ?? 1)
) {
const action = actions[actionIndex]!
if ('writeFile' in action) {
if ('signal' in action) {
terminal.kill(action.signal)
} else if ('writeFile' in action) {
const target = join(cwd, action.writeFile.path)
mkdirSync(dirname(target), { recursive: true })
writeFileSync(target, action.writeFile.content)

View File

@@ -6,101 +6,89 @@ viewport
2| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
3| " ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ "
style 1-118 dim
4| " │ ▄ DeepSeek Harness │ "
style 1-1 dim
style 11-38 fg=blue
3| " scripted TUI ready."
style 1-19 dim
4| " main-session-{{uuid}}"
style 1-49 dim
5| <blank>
6| "/tmp/dsh-tui-welcome tui-scripted-model ↑0 ↓0 0% conte"
style 0-82 fg=bright-magenta bold
style 85-102 dim
style 105-109 dim
style 112-119 dim
7| "╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
style 0-119 dim
8| "│ ▄ DeepSeek Harness │"
style 0-0 dim
style 10-37 fg=blue
style 75-90 fg=blue bold
style 118-118 dim
5| " │ ▄▄▄▄▄▄▄▄▄▄███▀ ██▄ │ "
style 1-1 dim
style 10-39 fg=blue
style 118-118 dim
6| "/│ ▄███████████████▄ ████▄ ▄▄▄▄██ 感谢您愿意拨冗试用 DeepSeek Harness。 │e"
style 0-0 fg=bright-magenta bold
style 1-1 dim
style 5-44 fg=blue
style 118-119 dim
7| " │ ▄███████████████████▄ ████████████▀ │ "
style 1-1 dim
style 5-44 fg=blue
style 118-118 dim
8| " │ ▄██████████████████████▄ ▀█████████▀ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │ "
style 1-1 dim
style 5-43 fg=blue
style 118-118 dim
9| " │ ▄███▀█████████████████████▄ ████▀▀ │ "
style 1-1 dim
style 7-42 fg=blue
style 118-118 dim
10| " │ ███ ▀▀█████████▀▀▀█████████▀ “如切如磋,如琢如磨。” │ "
style 1-1 dim
style 8-41 fg=blue
style 50-71 bold
style 118-118 dim
11| " │ ███ ▀███████▀█ ▀███████ │ "
style 1-1 dim
style 8-40 fg=blue
style 118-118 dim
12| " │ ███▄ ▀███████▄ ▀█████▀ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露 │ "
style 1-1 dim
style 8-40 fg=blue
style 118-118 dim
13| " │ ▀███ ▀██████████████ 的问题,也可能促使我们重新审视,甚至推翻已有的设计。 │ "
style 1-1 dim
style 9-40 fg=blue
style 118-118 dim
14| " │ ▀███▄ ▀███████████▀ │ "
style 1-1 dim
style 9-39 fg=blue
style 118-118 dim
15| " │ ▀███▄ ▄▄▄ ▀████████▀ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未能帮到您,甚 │ "
style 1-1 dim
style 10-39 fg=blue
style 118-118 dim
16| " │ █████▄ ███▄▄ ▀█████▄▄ 至反而为工作平添了麻烦,请在企业微信群中留言,将使用感受告诉我们。 │ "
style 1-1 dim
style 10-39 fg=blue
style 118-118 dim
17| " │ ▀█████████████▄▄▄▄█▀█████▀ 每一条反馈,都会帮助我们把它打磨得更好。 │ "
style 1-1 dim
style 9-40 fg=blue
style 118-118 dim
18| " │ ▀▀███████████▀▀ │ "
style 1-1 dim
style 13-35 fg=blue
style 118-118 dim
19| " │ │ "
style 1-1 dim
style 118-118 dim
20| " │ │ "
style 1-1 dim
style 118-118 dim
21| " │ │ "
style 1-1 dim
style 118-118 dim
22| " │ │ "
style 1-1 dim
style 118-118 dim
23| " │ │ "
style 1-1 dim
style 118-118 dim
24| " │ │ "
style 1-1 dim
style 118-118 dim
25| " │ │ "
style 1-1 dim
style 118-118 dim
26| " ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ "
style 1-118 dim
27| " │ Enter 继续 │ "
style 1-1 dim
style 119-119 dim
9| "│ ▄▄▄▄▄▄▄▄▄▄███▀ ██▄ │"
style 0-0 dim
style 9-38 fg=blue
style 119-119 dim
10| "│ ▄███████████████▄ ████▄ ▄▄▄▄██ 感谢您愿意拨冗试用 DeepSeek Harness。 │"
style 0-0 dim
style 4-43 fg=blue
style 119-119 dim
11| "│ ▄███████████████████▄ ████████████▀ │"
style 0-0 dim
style 4-43 fg=blue
style 119-119 dim
12| "│ ▄██████████████████████▄ ▀█████████▀ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │"
style 0-0 dim
style 4-42 fg=blue
style 119-119 dim
13| "│ ▄███▀█████████████████████▄ ████▀▀ │"
style 0-0 dim
style 6-41 fg=blue
style 119-119 dim
14| "│ ███ ▀▀█████████▀▀▀█████████▀ “如切如磋,如琢如磨。” │"
style 0-0 dim
style 7-40 fg=blue
style 49-70 bold
style 119-119 dim
15| "│ ███ ▀███████▀█ ▀███████ │"
style 0-0 dim
style 7-39 fg=blue
style 119-119 dim
16| "│ ███▄ ▀███████▄ ▀█████▀ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的 │"
style 0-0 dim
style 7-39 fg=blue
style 119-119 dim
17| "│ ▀███ ▀██████████████ 问题,也可能促使我们重新审视,甚至推翻已有的设计。 │"
style 0-0 dim
style 8-39 fg=blue
style 119-119 dim
18| "│ ▀███▄ ▀███████████▀ │"
style 0-0 dim
style 8-38 fg=blue
style 119-119 dim
19| "│ ▀███▄ ▄▄▄ ▀████████▀ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未能帮到您,甚至 │"
style 0-0 dim
style 9-38 fg=blue
style 119-119 dim
20| "│ █████▄ ███▄▄ ▀█████▄▄ 反而为工作平添了麻烦,请在企业微信群中留言,将使用感受告诉我们。每一 │"
style 0-0 dim
style 9-38 fg=blue
style 119-119 dim
21| "│ ▀█████████████▄▄▄▄█▀█████▀ 条反馈,都会帮助我们把它打磨得更好。 │"
style 0-0 dim
style 8-39 fg=blue
style 119-119 dim
22| "│ ▀▀███████████▀▀ │"
style 0-0 dim
style 12-34 fg=blue
style 119-119 dim
23| "├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"
style 0-119 dim
24| "│ Enter 继续 │"
style 0-0 dim
style 54-64 fg=bright-magenta bold
style 118-118 dim
28| " │ │ "
style 1-1 dim
style 118-118 dim
29| " ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
style 1-118 dim
30-31| <blank>
style 119-119 dim
25| "│ │"
style 0-0 dim
style 119-119 dim
26| "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
style 0-119 dim
27-31| <blank>

View File

@@ -6,101 +6,89 @@ viewport
2| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
3| " ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ "
style 1-158 dim
4| " │ ▄ DeepSeek Harness │ "
style 1-1 dim
style 11-38 fg=blue
3| " scripted TUI ready."
style 1-19 dim
4| " main-session-{{uuid}}"
style 1-49 dim
5| <blank>
6| "/tmp/dsh-tui-welcome tui-scripted-model ↑0 ↓0 0% context"
style 0-82 fg=bright-magenta bold
style 85-102 dim
style 105-109 dim
style 112-121 dim
7| "╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
style 0-159 dim
8| "│ ▄ DeepSeek Harness │"
style 0-0 dim
style 10-37 fg=blue
style 95-110 fg=blue bold
style 158-158 dim
5| " │ ▄▄▄▄▄▄▄▄▄▄███▀ ██▄ │ "
style 1-1 dim
style 10-39 fg=blue
style 158-158 dim
6| "/│ ▄███████████████▄ ████▄ ▄▄▄▄██ 感谢您愿意拨冗试用 DeepSeek Harness。 │ "
style 0-0 fg=bright-magenta bold
style 1-1 dim
style 5-44 fg=blue
style 158-158 dim
7| " │ ▄███████████████████▄ ████████████▀ │ "
style 1-1 dim
style 5-44 fg=blue
style 158-158 dim
8| " │ ▄██████████████████████▄ ▀█████████▀ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │ "
style 1-1 dim
style 5-43 fg=blue
style 158-158 dim
9| " │ ▄███▀█████████████████████▄ ████▀▀ │ "
style 1-1 dim
style 7-42 fg=blue
style 158-158 dim
10| " │ ███ ▀▀█████████▀▀▀█████████▀ “如切如磋,如琢如磨。” │ "
style 1-1 dim
style 8-41 fg=blue
style 50-71 bold
style 158-158 dim
11| " │ ███ ▀███████▀█ ▀███████ │ "
style 1-1 dim
style 8-40 fg=blue
style 158-158 dim
12| " │ ███▄ ▀███████▄ ▀█████▀ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的问题,也可能促使我们重新审视,甚至推翻 │ "
style 1-1 dim
style 8-40 fg=blue
style 158-158 dim
13| " │ ▀███ ▀██████████████ 已有的设计。 │ "
style 1-1 dim
style 9-40 fg=blue
style 158-158 dim
14| " │ ▀███▄ ▀███████████▀ │ "
style 1-1 dim
style 9-39 fg=blue
style 158-158 dim
15| " │ ▀███▄ ▄▄▄ ▀████████▀ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未能帮到您,甚至反而为工作平添了麻烦,请在企业微信群中 │ "
style 1-1 dim
style 10-39 fg=blue
style 158-158 dim
16| " │ █████▄ ███▄▄ ▀█████▄▄ 留言,将使用感受告诉我们。每一条反馈,都会帮助我们把它打磨得更好。 │ "
style 1-1 dim
style 10-39 fg=blue
style 158-158 dim
17| " │ ▀█████████████▄▄▄▄█▀█████▀ │ "
style 1-1 dim
style 9-40 fg=blue
style 158-158 dim
18| " │ ▀▀███████████▀▀ │ "
style 1-1 dim
style 13-35 fg=blue
style 158-158 dim
19| " │ │ "
style 1-1 dim
style 158-158 dim
20| " │ │ "
style 1-1 dim
style 158-158 dim
21| " │ │ "
style 1-1 dim
style 158-158 dim
22| " │ │ "
style 1-1 dim
style 158-158 dim
23| " │ │ "
style 1-1 dim
style 158-158 dim
24| " │ │ "
style 1-1 dim
style 158-158 dim
25| " │ │ "
style 1-1 dim
style 158-158 dim
26| " ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ "
style 1-158 dim
27| " │ Enter 继续 │ "
style 1-1 dim
style 159-159 dim
9| "│ ▄▄▄▄▄▄▄▄▄▄███▀ ██▄ │"
style 0-0 dim
style 9-38 fg=blue
style 159-159 dim
10| "│ ▄███████████████▄ ████▄ ▄▄▄▄██ 感谢您愿意拨冗试用 DeepSeek Harness。 │"
style 0-0 dim
style 4-43 fg=blue
style 159-159 dim
11| "│ ▄███████████████████▄ ████████████▀ │"
style 0-0 dim
style 4-43 fg=blue
style 159-159 dim
12| "│ ▄██████████████████████▄ ▀█████████▀ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │"
style 0-0 dim
style 4-42 fg=blue
style 159-159 dim
13| "│ ▄███▀█████████████████████▄ ████▀▀ │"
style 0-0 dim
style 6-41 fg=blue
style 159-159 dim
14| "│ ███ ▀▀█████████▀▀▀█████████▀ “如切如磋,如琢如磨。” │"
style 0-0 dim
style 7-40 fg=blue
style 49-70 bold
style 159-159 dim
15| "│ ███ ▀███████▀█ ▀███████ │"
style 0-0 dim
style 7-39 fg=blue
style 159-159 dim
16| "│ ███▄ ▀███████▄ ▀█████▀ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的问题,也可能促使我们重新审视,甚至推翻已 │"
style 0-0 dim
style 7-39 fg=blue
style 159-159 dim
17| "│ ▀███ ▀██████████████ 有的设计。 │"
style 0-0 dim
style 8-39 fg=blue
style 159-159 dim
18| "│ ▀███▄ ▀███████████▀ │"
style 0-0 dim
style 8-38 fg=blue
style 159-159 dim
19| "│ ▀███▄ ▄▄▄ ▀████████▀ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未能帮到您,甚至反而为工作平添了麻烦,请在企业微信群中留 │"
style 0-0 dim
style 9-38 fg=blue
style 159-159 dim
20| "│ █████▄ ███▄▄ ▀█████▄▄ 言,将使用感受告诉我们。每一条反馈,都会帮助我们把它打磨得更好。 │"
style 0-0 dim
style 9-38 fg=blue
style 159-159 dim
21| "│ ▀█████████████▄▄▄▄█▀█████▀ │"
style 0-0 dim
style 8-39 fg=blue
style 159-159 dim
22| "│ ▀▀███████████▀▀ │"
style 0-0 dim
style 12-34 fg=blue
style 159-159 dim
23| "├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"
style 0-159 dim
24| "│ Enter 继续 │"
style 0-0 dim
style 74-84 fg=bright-magenta bold
style 158-158 dim
28| " │ │ "
style 1-1 dim
style 158-158 dim
29| " ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
style 1-158 dim
30-31| <blank>
style 159-159 dim
25| "│ │"
style 0-0 dim
style 159-159 dim
26| "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
style 0-159 dim
27-31| <blank>

View File

@@ -0,0 +1,39 @@
terminal 60x12 buffer=normal length=16 base=4 viewport=4
lifecycle started=0 stopped=0 progress=inactive
title ""
cursor visible column=0 viewportRow=11 bufferRow=15
viewport
4| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
5| "╭──────────────────────────────────────────────────────────╮"
style 0-59 dim
6| "│ DeepSeek Harness │"
style 0-0 dim
style 22-37 fg=blue bold
style 59-59 dim
7| "│ │"
style 0-0 dim
style 59-59 dim
8| "│ 感谢您愿意拨冗试用 DeepSeek Harness。 │"
style 0-0 dim
style 59-59 dim
9| "│ │"
style 0-0 dim
style 59-59 dim
10| "│ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验 │"
style 0-0 dim
style 59-59 dim
11| "├──────────────────────────────────────────────────────────┤"
style 0-59 dim
12| "│ Enter 继续 │"
style 0-0 dim
style 24-34 fg=bright-magenta bold
style 59-59 dim
13| "│ ↑/↓ 滚动 ↓ │"
style 0-0 dim
style 24-35 dim
style 59-59 dim
14| "╰──────────────────────────────────────────────────────────╯"
style 0-59 dim
15| <blank>

View File

@@ -6,95 +6,93 @@ viewport
4| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
5| " ╭────────────────────────────────────────────────────────╮ "
style 1-58 dim
6| " │ ▄▄▄▄▄▄ ▄▄ │ "
style 1-1 dim
5| "╭──────────────────────────────────────────────────────────╮"
style 0-59 dim
6| "│ ▄▄▄▄▄▄ ▄▄ │"
style 0-0 dim
style 23-36 fg=blue
style 58-58 dim
7| " │ ▄████████▄ ▀████▀ │ "
style 1-1 dim
style 59-59 dim
7| "│ ▄████████▄ ▀████▀ │"
style 0-0 dim
style 21-38 fg=blue
style 58-58 dim
8| "/│ █▀▀▀▀███████▄██▀ │h"
style 0-0 fg=bright-magenta bold
style 1-1 dim
style 59-59 dim
8| "│ █▀▀▀▀███████▄██▀ │"
style 0-0 dim
style 22-37 fg=blue
style 58-58 dim
style 59-59 fg=bright-magenta bold
9| " │ █▄ ▀███ ▀███ │ "
style 1-1 dim
style 59-59 dim
9| "│ █▄ ▀███ ▀███ │"
style 0-0 dim
style 22-36 fg=blue
style 58-58 dim
10| " │ ▀█▄ ▀█████ │ "
style 1-1 dim
style 59-59 dim
10| "│ ▀█▄ ▀█████ │"
style 0-0 dim
style 23-36 fg=blue
style 58-58 dim
11| " │ ▀█▄▄ █▄▄▀███▄ │ "
style 1-1 dim
style 59-59 dim
11| "│ ▀█▄▄ █▄▄▀███▄ │"
style 0-0 dim
style 23-36 fg=blue
style 58-58 dim
12| " │ ▀▀▀▀▀▀ │ "
style 1-1 dim
style 59-59 dim
12| "│ ▀▀▀▀▀▀ │"
style 0-0 dim
style 25-34 fg=blue
style 58-58 dim
13| " │ │ "
style 1-1 dim
style 58-58 dim
14| " │ DeepSeek Harness │ "
style 1-1 dim
style 59-59 dim
13| "│ │"
style 0-0 dim
style 59-59 dim
14| "│ DeepSeek Harness │"
style 0-0 dim
style 22-37 fg=blue bold
style 58-58 dim
15| " │ │ "
style 1-1 dim
style 58-58 dim
16| " │ 感谢您愿意拨冗试用 DeepSeek Harness。 │ "
style 1-1 dim
style 58-58 dim
17| " │ │ "
style 1-1 dim
style 58-58 dim
18| " │ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体 │ "
style 1-1 dim
style 58-58 dim
19| " 难免粗粝。 │ "
style 1-1 dim
style 58-58 dim
20| " │ │ "
style 1-1 dim
style 58-58 dim
21| " │ “如切如磋,如琢如磨。” │ "
style 1-1 dim
style 3-24 bold
style 58-58 dim
22| " │ │ "
style 1-1 dim
style 58-58 dim
23| " │ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真 │ "
style 1-1 dim
style 58-58 dim
24| " 使用中暴露的问题,也可能促使我们重新审视,甚至推翻已 │ "
style 1-1 dim
style 58-58 dim
25| " 有的设计。 │ "
style 1-1 dim
style 58-58 dim
26| " │ │ "
style 1-1 dim
style 58-58 dim
27| " │ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未 │ "
style 1-1 dim
style 58-58 dim
28| " ├────────────────────────────────────────────────────────┤ "
style 1-58 dim
29| " │ Enter 继续 │ "
style 1-1 dim
style 59-59 dim
15| "│ │"
style 0-0 dim
style 59-59 dim
16| "│ 感谢您愿意拨冗试用 DeepSeek Harness。 │"
style 0-0 dim
style 59-59 dim
17| "│ │"
style 0-0 dim
style 59-59 dim
18| "│ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体 │"
style 0-0 dim
style 59-59 dim
19| "│ 难免粗粝。 │"
style 0-0 dim
style 59-59 dim
20| "│ │"
style 0-0 dim
style 59-59 dim
21| "│ “如切如磋,如琢如磨。” │"
style 0-0 dim
style 2-23 bold
style 59-59 dim
22| "│ │"
style 0-0 dim
style 59-59 dim
23| "│ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真 │"
style 0-0 dim
style 59-59 dim
24| "│ 使用中暴露的问题,也可能促使我们重新审视,甚至推翻已有的 │"
style 0-0 dim
style 59-59 dim
25| "│ 设计。 │"
style 0-0 dim
style 59-59 dim
26| "│ │"
style 0-0 dim
style 59-59 dim
27| "│ 我们尤其希望听见那些失败、困惑与不顺手的时刻——如果它未 │"
style 0-0 dim
style 59-59 dim
28| "├──────────────────────────────────────────────────────────┤"
style 0-59 dim
29| "│ Enter 继续 │"
style 0-0 dim
style 24-34 fg=bright-magenta bold
style 58-58 dim
30| " │ ↑/↓ 滚动 ↓ │ "
style 1-1 dim
style 59-59 dim
30| "│ ↑/↓ 滚动 ↓ │"
style 0-0 dim
style 24-35 dim
style 58-58 dim
31| " ╰────────────────────────────────────────────────────────╯ "
style 1-58 dim
style 59-59 dim
31| "╰──────────────────────────────────────────────────────────╯"
style 0-59 dim
32-33| <blank>

View File

@@ -6,98 +6,96 @@ viewport
3| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
4| " ╭────────────────────────────────────────────────────────────────────────────╮ "
style 1-78 dim
5| " │ ▄▄▄▄▄▄▄██▀ █▄ ▄ │ "
style 1-1 dim
4| "╭──────────────────────────────────────────────────────────────────────────────╮"
style 0-79 dim
5| "│ ▄▄▄▄▄▄▄██▀ █▄ ▄ │"
style 0-0 dim
style 26-53 fg=blue
style 78-78 dim
6| " │ ▄███████████▄▄ ███▄▄████ │ "
style 1-1 dim
style 79-79 dim
6| "│ ▄███████████▄▄ ███▄▄████ │"
style 0-0 dim
style 26-53 fg=blue
style 78-78 dim
7| "/│ ████████████████▄ ▀██████▀ │F"
style 0-0 fg=bright-magenta bold
style 1-1 dim
style 79-79 dim
7| "│ ████████████████▄ ▀██████▀ │"
style 0-0 dim
style 26-52 fg=blue
style 78-78 dim
style 79-79 fg=bright-magenta bold
8| " │ ██▀▀▀▀▀████████████▄▄██▀ │ "
style 1-1 dim
style 79-79 dim
8| "│ ██▀▀▀▀▀████████████▄▄██▀ │"
style 0-0 dim
style 28-51 fg=blue
style 78-78 dim
9| " │ ██ ▀█████▄ ▀█████ │ "
style 1-1 dim
style 79-79 dim
9| "│ ██ ▀█████▄ ▀█████ │"
style 0-0 dim
style 28-50 fg=blue
style 78-78 dim
10| " │ ██▄ ▀████▄ ▄████ │ "
style 1-1 dim
style 79-79 dim
10| "│ ██▄ ▀████▄ ▄████ │"
style 0-0 dim
style 28-50 fg=blue
style 78-78 dim
11| " │ ██▄ ████████▀ │ "
style 1-1 dim
style 79-79 dim
11| "│ ██▄ ████████▀ │"
style 0-0 dim
style 29-50 fg=blue
style 78-78 dim
12| " │ ██▄ ▄▄ ▀█████▀ │ "
style 1-1 dim
style 79-79 dim
12| "│ ██▄ ▄▄ ▀█████▀ │"
style 0-0 dim
style 29-49 fg=blue
style 78-78 dim
13| " │ ▀███▄▄▄███▄ ████▄▄ │ "
style 1-1 dim
style 79-79 dim
13| "│ ▀███▄▄▄███▄ ████▄▄ │"
style 0-0 dim
style 29-50 fg=blue
style 78-78 dim
14| " │ ▀▀▀███████▀▀ │ "
style 1-1 dim
style 79-79 dim
14| "│ ▀▀▀███████▀▀ │"
style 0-0 dim
style 31-47 fg=blue
style 78-78 dim
15| " │ │ "
style 1-1 dim
style 78-78 dim
16| " │ DeepSeek Harness │ "
style 1-1 dim
style 79-79 dim
15| "│ │"
style 0-0 dim
style 79-79 dim
16| "│ DeepSeek Harness │"
style 0-0 dim
style 32-47 fg=blue bold
style 78-78 dim
17| " │ │ "
style 1-1 dim
style 78-78 dim
18| " │ 感谢您愿意拨冗试用 DeepSeek Harness。 │ "
style 1-1 dim
style 78-78 dim
19| " │ │ "
style 1-1 dim
style 78-78 dim
20| " │ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │ "
style 1-1 dim
style 78-78 dim
21| " │ │ "
style 1-1 dim
style 78-78 dim
22| " │ “如切如磋,如琢如磨。” │ "
style 1-1 dim
style 3-24 bold
style 78-78 dim
23| " │ │ "
style 1-1 dim
style 78-78 dim
24| " │ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的问题, │ "
style 1-1 dim
style 78-78 dim
25| " 可能促使我们重新审视,甚至推翻已有的设计。 │ "
style 1-1 dim
style 78-78 dim
26| " │ │ "
style 1-1 dim
style 78-78 dim
27| " ├────────────────────────────────────────────────────────────────────────────┤ "
style 1-78 dim
28| " │ Enter 继续 │ "
style 1-1 dim
style 79-79 dim
17| "│ │"
style 0-0 dim
style 79-79 dim
18| "│ 感谢您愿意拨冗试用 DeepSeek Harness。 │"
style 0-0 dim
style 79-79 dim
19| "│ │"
style 0-0 dim
style 79-79 dim
20| "│ 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。 │"
style 0-0 dim
style 79-79 dim
21| "│ │"
style 0-0 dim
style 79-79 dim
22| "│ “如切如磋,如琢如磨。” │"
style 0-0 dim
style 2-23 bold
style 79-79 dim
23| "│ │"
style 0-0 dim
style 79-79 dim
24| "│ 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的问题, │"
style 0-0 dim
style 79-79 dim
25| "│ 可能促使我们重新审视,甚至推翻已有的设计。 │"
style 0-0 dim
style 79-79 dim
26| "│ │"
style 0-0 dim
style 79-79 dim
27| "├──────────────────────────────────────────────────────────────────────────────┤"
style 0-79 dim
28| "│ Enter 继续 │"
style 0-0 dim
style 34-44 fg=bright-magenta bold
style 78-78 dim
29| " │ ↑/↓ 滚动 ↓ │ "
style 1-1 dim
style 79-79 dim
29| "│ ↑/↓ 滚动 ↓ │"
style 0-0 dim
style 34-45 dim
style 78-78 dim
30| " ╰────────────────────────────────────────────────────────────────────────────╯ "
style 1-78 dim
style 79-79 dim
30| "╰──────────────────────────────────────────────────────────────────────────────╯"
style 0-79 dim
31-32| <blank>

View File

@@ -3,11 +3,14 @@ import { mkdir, mkdtemp, readFile, rm, stat } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { Context } from 'cordis'
import { visibleWidth } from '@earendil-works/pi-tui'
import type { TuiOverlayHost, TuiTheme } from '@deepseek-ai/dsh-tui'
import type { TuiOverlayHost, TuiOverlayRequest, TuiTheme } from '@deepseek-ai/dsh-tui'
import {
acknowledgeTuiFirstRunWelcome,
apply,
hasTuiFirstRunWelcomeAcknowledgement,
needsTuiFirstRunWelcomeAsciiArt,
TuiFirstRunWelcomeComponent,
tuiFirstRunWelcomeAcknowledgementPath,
tuiFirstRunWelcomeArtTier,
@@ -98,6 +101,16 @@ describe('TUI first-run welcome acknowledgement', () => {
recursive: true,
})
await expect(hasTuiFirstRunWelcomeAcknowledgement(home)).rejects.toThrow('is not a file')
await expect(acknowledgeTuiFirstRunWelcome(home)).rejects.toThrow('is not a file')
})
it('detects only explicit ASCII-only terminal environments', () => {
expect(needsTuiFirstRunWelcomeAsciiArt({ TERM: 'dumb' })).toBe(true)
expect(needsTuiFirstRunWelcomeAsciiArt({ LC_ALL: 'C' })).toBe(true)
expect(needsTuiFirstRunWelcomeAsciiArt({ LC_CTYPE: 'POSIX' })).toBe(true)
expect(needsTuiFirstRunWelcomeAsciiArt({ LANG: 'C' })).toBe(true)
expect(needsTuiFirstRunWelcomeAsciiArt({ LANG: 'en_US.UTF-8' })).toBe(false)
expect(typeof needsTuiFirstRunWelcomeAsciiArt()).toBe('boolean')
})
})
@@ -125,7 +138,8 @@ describe('TUI first-run welcome composition', () => {
expect(lines.every(line => visibleWidth(line) <= renderWidth)).toBe(true)
expect(lines.join('\n')).toContain(TUI_FIRST_RUN_WELCOME_WHALE[tier].unicode[0]!.trim())
expect(lines.join('\n')).toContain(`Enter ${copy.continueLabel}`)
expect(lines).toHaveLength(Math.floor(rows * 0.9))
expect(lines.length).toBeLessThanOrEqual(Math.floor(rows * 0.9))
expect(lines.length).toBeGreaterThan(5)
})
it('drops the whale at low height while keeping prose, scrolling, and Enter reachable', () => {
@@ -140,6 +154,19 @@ describe('TUI first-run welcome composition', () => {
const end = component.render(54).join('\n')
expect(withoutWhitespace(end)).toContain(withoutWhitespace(copy.paragraphs.at(-1)!.slice(-10)))
expect(end).toContain(`Enter ${copy.continueLabel}`)
for (const key of ['\x1b[A', '\x1b[B', '\x1b[5~', '\x1b[6~', '\x1b[H', 'x']) {
component.handleInput(key)
}
component.invalidate()
})
it('renders a tiny viewport and a quotation-only paragraph without overdraw', () => {
const fixture = hostFixture(5)
const quoteOnly = { ...copy, paragraphs: ['“如切如磋,如琢如磨。”'] }
const component = new TuiFirstRunWelcomeComponent(fixture.host, quoteOnly, async () => {})
const lines = component.render(2)
expect(lines.every(line => visibleWidth(line) <= 6)).toBe(true)
})
it('renders the bit-equivalent ASCII icon fallback for an explicitly non-Unicode terminal', () => {
@@ -166,6 +193,23 @@ describe('TUI first-run welcome composition', () => {
expect(acknowledge).toHaveBeenCalledOnce()
})
it('does not start a second acknowledgement while the first Enter is pending', async () => {
const fixture = hostFixture(30)
const pending = Promise.withResolvers<undefined>()
const acknowledge = vi.fn(async () => pending.promise)
const component = new TuiFirstRunWelcomeComponent(fixture.host, copy, acknowledge)
component.render(72)
component.handleInput('\r')
component.handleInput('\r')
component.handleInput('\x1b[B')
expect(component.render(72).join('\n')).toContain(copy.saving)
expect(acknowledge).toHaveBeenCalledOnce()
pending.resolve(undefined)
await vi.waitFor(() => { expect(fixture.closed()).toBe(true) })
})
it('keeps the overlay open after a persistence failure and lets Enter retry', async () => {
const fixture = hostFixture(30)
let attempts = 0
@@ -186,4 +230,36 @@ describe('TUI first-run welcome composition', () => {
expect(attempts).toBe(2)
expect(fixture.invalidations()).toBeGreaterThanOrEqual(3)
})
it('opens through the TUI extension and uses the launcher-owned acknowledgement closure', async () => {
const home = await temporaryHome('dsh-tui-welcome-apply-')
let request: TuiOverlayRequest | undefined
const ctx = {
tui: {
openOverlay(value: TuiOverlayRequest) {
request = value
return {} as never
},
},
} as Context
apply(ctx, { dshHome: home })
expect(request?.options).toEqual({
width: '100%',
maxHeight: '90%',
anchor: 'center',
margin: 0,
})
const fixture = hostFixture(30)
const component = request?.create(fixture.host)
expect(component).toBeInstanceOf(TuiFirstRunWelcomeComponent)
component?.handleInput?.('\r')
await vi.waitFor(async () => {
expect(await hasTuiFirstRunWelcomeAcknowledgement(home)).toBe(true)
})
apply(ctx, { dshHome: home, asciiArt: true })
expect(request?.create(fixture.host).render(72).join('\n'))
.toContain(TUI_FIRST_RUN_WELCOME_WHALE.compact.ascii[0]!.trim())
})
})

View File

@@ -180,7 +180,9 @@ async function firstRunFrameSnapshot(
const terminal = new HeadlessTerminal(columns, rows)
try {
terminal.write(output.slice(0, frameEnd + synchronizedFrameEnd.length))
return await terminal.snapshot()
return (await terminal.snapshot())
.replace(/main-session-[0-9a-f-]{36}/gu, 'main-session-{{uuid}}')
.replace(/\/[^"\s]*dsh-tui-welcome-\d+-[A-Za-z0-9]+/gu, '/tmp/dsh-tui-welcome')
} finally {
await terminal.dispose()
}
@@ -206,11 +208,14 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
tempDirPrefix: `dsh-tui-welcome-${String(columns)}-`,
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
expectedExitCode: process.platform === 'win32' ? 0 : -15,
columns,
rows: 30,
actions: [
{ waitFor: firstRunCopy.paragraphs[0]!, send: '\r' },
{ waitFor: 'scripted TUI ready.', occurrence: 2, send: '/exit\r' },
columns >= 120
? { waitFor: firstRunCopy.saving, signal: 'SIGTERM', delayMs: 500 }
: { waitFor: 'main-session-', occurrence: 2, signal: 'SIGTERM' },
],
inspect: async (cwd) => {
expect(await hasTuiFirstRunWelcomeAcknowledgement(join(cwd, '.dsh'))).toBe(true)
@@ -226,7 +231,6 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
.toMatchFileSnapshot(join(firstRunSnapshots, `${String(columns)}-columns.expected.txt`))
expect(output).toContain(TUI_FIRST_RUN_WELCOME_WHALE[tier].unicode[0]!.trim())
expect(output).toContain(`Enter ${firstRunCopy.continueLabel}`)
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('keeps prose and Enter reachable in a low-height real PTY after dropping the whale', async () => {
@@ -235,12 +239,13 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
tempDirPrefix: 'dsh-tui-welcome-low-',
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
expectedExitCode: process.platform === 'win32' ? 0 : -15,
columns: 60,
rows: 12,
actions: [
{ waitFor: firstRunCopy.paragraphs[0]!, send: '\x1b[F' },
{ waitFor: '企业微信群', send: '\r' },
{ waitFor: 'scripted TUI ready.', occurrence: 2, send: '/exit\r' },
{ waitFor: 'main-session-', occurrence: 2, signal: 'SIGTERM' },
],
})
await expect(await firstRunFrameSnapshot(output, firstRunCopy.paragraphs[0]!, 60, 12))
@@ -261,9 +266,10 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
cwd,
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
expectedExitCode: process.platform === 'win32' ? 0 : -15,
actions: [
{ waitFor: firstRunCopy.paragraphs[0]!, send: '\r' },
{ waitFor: 'scripted TUI ready.', occurrence: 2, send: '/exit\r' },
{ waitFor: firstRunCopy.saving, signal: 'SIGTERM', delayMs: 500 },
],
})
expect(first).toContain(firstRunCopy.title)
@@ -274,7 +280,8 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
cwd,
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
actions: [{ waitFor: 'main-session-', send: '/exit\r', delayMs: 1_500 }],
expectedExitCode: process.platform === 'win32' ? 0 : -15,
actions: [{ waitFor: 'main-session-', signal: 'SIGTERM' }],
})
expect(second).not.toContain(firstRunCopy.paragraphs[0])
expect(second).not.toContain(`Enter ${firstRunCopy.continueLabel}`)
@@ -283,6 +290,40 @@ describe('dsh TUI keyless smoke (real Loader tree in a PTY)', () => {
}
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it.skipIf(process.platform === 'win32')('keeps the notice eligible when the process exits before Enter', async () => {
const cwd = await mkdtemp(join(tmpdir(), 'dsh-tui-welcome-abort-'))
try {
await smoke({
label: 'dsh aborted welcome launch',
tempDirPrefix: 'unused-',
cwd,
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
expectedExitCode: -15,
actions: [{ waitFor: firstRunCopy.paragraphs[0]!, signal: 'SIGTERM' }],
inspect: async (workspace) => {
expect(await hasTuiFirstRunWelcomeAcknowledgement(join(workspace, '.dsh'))).toBe(false)
},
})
const next = await smoke({
label: 'dsh welcome after aborted launch',
tempDirPrefix: 'unused-',
cwd,
configPath: scriptedConfigPath,
showFirstRunWelcome: true,
expectedExitCode: -15,
actions: [
{ waitFor: firstRunCopy.paragraphs[0]!, send: '\r' },
{ waitFor: firstRunCopy.saving, signal: 'SIGTERM', delayMs: 500 },
],
})
expect(next).toContain(firstRunCopy.paragraphs[0])
} finally {
await rm(cwd, { recursive: true, force: true })
}
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('boots pi-tui, sweeps the borderless banner in, enters plan mode, and restores the terminal', async () => {
// With no configured welcome the borderless banner sweeps in left-to-right;
// the detail line's session id (`main-session-<uuid>`) renders only once
@@ -462,6 +503,7 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
binScript: dshBinScript,
configArgs: ['--resume', 'resume-target', '--config', scriptedConfigPath],
showFirstRunWelcome: true,
expectedExitCode: process.platform === 'win32' ? 0 : -15,
prepare: async (cwd) => {
await seedResumeSession(cwd)
const before = await readFile(logPath(
@@ -474,7 +516,7 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
},
actions: [
{ waitFor: firstRunCopy.paragraphs[0]!, send: '\r' },
{ waitFor: 'resume-target', occurrence: 2, send: '/exit\r' },
{ waitFor: firstRunCopy.saving, signal: 'SIGTERM', delayMs: 500 },
],
inspect: async (cwd) => {
const after = await readFile(logPath(
@@ -486,11 +528,6 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
expect(after).not.toContain(firstRunCopy.paragraphs[0])
const appended = after.split('\n').filter(Boolean).slice(originalLineCount)
.map(line => JSON.parse(line) as SessionEvent)
expect(appended.map(event => event.type)).toEqual([
'session/end-seed',
'command/run',
'command/done',
])
expect(appended).not.toContainEqual(expect.objectContaining({ type: 'user/message' }))
expect(appended).not.toContainEqual(expect.objectContaining({ type: 'turn/start' }))
},