Startup slogans replace the configured TUI welcome line
启动 slogan 取代配置化的 TUI 欢迎语
The TUI header subtitle came from a `welcome` config the demo leaf set to "TUI agent ready. Give it a coding task." — instructional filler that told a returning user nothing, restated what the product is on every boot, and had a hardcoded twin (`'ready.'`) as the schema default in two packages. The product wanted a startup moment with some character instead of a static banner caption.
English
Problem
The TUI header subtitle came from a welcome config the demo leaf set to "TUI agent ready. Give it a coding task." — instructional filler that told a returning user nothing, restated what the product is on every boot, and had a hardcoded twin ('ready.') as the schema default in two packages. The product wanted a startup moment with some character instead of a static banner caption.
Decision
examples/tui-agent/cordis.ymlno longer configureswelcome; the config key stays for deployments and fixtures that need a fixed, deterministic subtitle (the Code Mode overlay and every snapshot/scripted fixture keep theirs).- When
welcomeis unset,dsh-tuipicks one member of an exportedSTARTUP_SLOGANSbank per boot (pickStartupSlogan, injectable random source) and reveals it with a typewriter animation: one character per 40 ms frame, a▌block cursor trailing until complete. The reveal starts only afterui.start()succeeds and its interval is cleared on dispose alongside the other listeners. - The slogan bank is presentation copy, deliberately not config: deployments that want controlled wording already have
welcome. Slogans are ASCII-only by contract because the reveal slices per character. dsh-tui-demoforwardswelcomeonly when configured instead of defaulting it, so the app no longer decides the TUI's idle subtitle.- The keyless PTY boot scenario now waits for the reveal cursor (
▌— the only source of that glyph in an empty transcript) instead of the removed welcome text.
The same change restores packages/ui/tui/src/index.ts to 100 % per-file coverage, which the color-scheme merge had broken on the integration branch: the editor border-color reassignment inside applyColorScheme was dead (the setStatus call right after re-derives it) and is removed, and the color-scheme query's .then/.catch arrows became named, tested handlers (applyReportedScheme, ignoreSchemeQueryFailure — the latter pinned by a test whose terminal throws on the DSR query write).
Alternatives considered
A fixed cooler slogan. Rejected: one string re-read on every boot decays into wallpaper exactly like the line it replaces; a small rotating bank keeps the moment alive at no complexity cost.
Making the bank and reveal speed configurable. Rejected: that is two new knobs for presentation copy; welcome is already the escape hatch for deployments with an opinion, and the no-hardcoded-tunables rule targets deployment-varying behavior, not brand copy.
Animating in HeaderComponent itself. Rejected: the component would need a TUI handle and its own lifecycle; the chat already owns a render loop, timers, and a disposal path, so the reveal lives beside the other createTuiChat effects and detachListeners clears it.
Consequences
- Boot output is no longer byte-deterministic when
welcomeis unset (random slogan, timed frames). Every recorded or snapshot surface pinswelcomeexplicitly, so no snapshot changed; the PTY smoke anchors on the reveal cursor and the session-id line instead. - The
welcomeschema default disappeared from bothdsh-tuianddsh-tui-demo; a direct caller passing no welcome now gets a slogan, not'ready.'. - Adding a slogan is a one-line bank edit; tests assert membership, not specific text.
Testing
packages/ui/tui/tests/tui.spec.ts pins deterministic bank selection with an injected random source, the reveal (a bank member fully rendered, cursor frames observed), the configured-welcome path rendering verbatim with no cursor, and dispose stopping a mid-reveal animation. examples/tui-agent/tests/tui-keyless-smoke.e2e.ts boots the real tree in a PTY and waits on the reveal cursor. Verified live in tmux (mid-reveal frame no map below▌ then the full slogan).
中文
Problem
TUI 头部副标题来自一个 welcome 配置,示例叶子配置把它设为 "TUI agent ready. Give it a coding task."——一句说明书式的填充语,对老用户毫无信息量,每次启动都在复述产品是什么,而且它还有一个硬编码的孪生兄弟('ready.')作为两个包里的 schema 默认值。产品需要的是一个有性格的启动时刻,而不是一条静态横幅说明。
Decision
examples/tui-agent/cordis.yml不再配置welcome;该配置键保留给需要固定、确定性副标题的部署与 fixture(Code Mode overlay 和所有快照/脚本化 fixture 都保留各自的欢迎语)。welcome未设置时,dsh-tui每次启动从导出的STARTUP_SLOGANS库里挑选一条(pickStartupSlogan,随机源可注入),并以打字机动画逐字显示:每帧 40 ms 一个字符,完成前尾随一个▌块状光标。动画只在ui.start()成功后启动,其定时器与其他监听器一起在 dispose 时清除。- slogan 库是展示文案,刻意不做成配置:想控制措辞的部署已经有
welcome这个出口。按契约 slogan 只含 ASCII,因为逐字显示按字符切片。 dsh-tui-demo只在配置了welcome时才转发它,不再填默认值,应用不再替 TUI 决定空闲副标题。- 无 key 的 PTY 启动场景改为等待逐字显示的光标(
▌——空 transcript 里该字形的唯一来源),不再等待已删除的欢迎文本。
同一变更把 packages/ui/tui/src/index.ts 恢复到 100% 的单文件覆盖率(颜色方案合并曾在集成分支上破坏它):applyColorScheme 里对编辑器边框颜色的重新赋值是死代码(紧随其后的 setStatus 调用会重新推导它),已删除;颜色方案查询的 .then/.catch 箭头函数改为具名、有测试的处理器(applyReportedScheme、ignoreSchemeQueryFailure——后者由一个让终端在 DSR 查询写入时抛错的测试固定)。
Alternatives considered
换一条更酷的固定 slogan。 否决:一条每次启动都重读的字符串会和它取代的那行一样退化成墙纸;一个小的轮换库以零复杂度代价让这个时刻保持新鲜。
把 slogan 库和显示速度做成配置。 否决:那是为展示文案新增两个旋钮;对措辞有主张的部署已经有 welcome 这个出口,而「插件里不许硬编码可调参数」规则针对的是随部署变化的行为,不是品牌文案。
在 HeaderComponent 内部做动画。 否决:组件将需要持有 TUI 句柄和自己的生命周期;聊天层已经拥有渲染循环、定时器和释放路径,所以逐字显示与 createTuiChat 的其他资源放在一起,由 detachListeners 清除。
Consequences
welcome未设置时启动输出不再字节级确定(随机 slogan、定时帧)。所有录制或快照表面都显式固定welcome,因此没有快照变化;PTY 冒烟测试改为锚定逐字显示光标和会话 id 行。welcome的 schema 默认值从dsh-tui和dsh-tui-demo中消失;不传 welcome 的直接调用方现在得到的是 slogan,而不是'ready.'。- 新增一条 slogan 只需在库里加一行;测试断言成员归属,不断言具体文本。
Testing
packages/ui/tui/tests/tui.spec.ts 固定以下行为:注入随机源后的确定性选取、逐字显示(库中某条完整渲染、观察到光标帧)、配置了 welcome 时逐字动画不启动且原文渲染、以及 dispose 停止进行中的动画。examples/tui-agent/tests/tui-keyless-smoke.e2e.ts 在 PTY 里启动真实配置树并等待显示光标。已在 tmux 中实机验证(中途帧 no map below▌,随后是完整 slogan)。