Skip to content
Open
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
299e88e
feat: Dashboard 配置页面中文化 + TUI i18n 架构补完
Vodkakakaka May 10, 2026
84fc9bb
feat: TUI 全面汉化——prompts/help/hotkeys/voice状态/组件标题/输入提示
Vodkakakaka May 10, 2026
8fc3602
fix: 补完 TUI 遗漏英文——Thinking标签/picker提示/Spawn tree/队列编辑
Vodkakakaka May 10, 2026
2390ff7
feat: core.ts 系统消息 i18n 化——确认对话框/复制/状态/历史等
Vodkakakaka May 10, 2026
d3e26d4
feat: ops.ts 系统消息 i18n 化——浏览器/回滚/委托/技能/replay
Vodkakakaka May 10, 2026
e365352
Merge branch 'NousResearch:main' into feat/tui-i18n-localization
Vodkakakaka May 11, 2026
757820b
fix: 修复 tz() 被当成字符串字面量的 Bug,消除硬编码中文和遗漏的英文描述
Vodkakakaka May 11, 2026
f61b6bf
Merge branch 'NousResearch:main' into feat/tui-i18n-localization
Vodkakakaka May 11, 2026
3af0674
fix(tui): 热键描述国际化 + padVerb display width 修复
Vodkakakaka May 11, 2026
922e0d3
merge main
Vodkakakaka May 11, 2026
d973e85
feat(dashboard): copy last response 按钮国际化
Vodkakakaka May 11, 2026
4b38d1c
merge main
Vodkakakaka May 11, 2026
e56099b
web: 切换到 pnpm 并补齐 i18n 缺少的 copyLastResponse 等字段
Vodkakakaka May 11, 2026
054c812
Merge branch 'main' into feat/tui-i18n-localization
Vodkakakaka May 11, 2026
f7d9af3
Merge branch 'main' into feat/tui-i18n-localization
Vodkakakaka May 12, 2026
08a6c1c
Merge branch 'main' into feat/tui-i18n-localization
Vodkakakaka May 12, 2026
e32f72c
fix(i18n): 修复硬编码 locale 和遗漏翻译
Vodkakakaka May 12, 2026
e6c173d
feat(i18n): web 侧 ChatSidebar/AutoField/ConfigPage 接入 i18n
Vodkakakaka May 12, 2026
4a4f92d
fix(i18n): messageLine/prompts/core 硬编码消除 + 新增翻译 key
Vodkakakaka May 12, 2026
fa0267d
feat(i18n): debug/session/todoPanel/appOverlays 接入 i18n
Vodkakakaka May 12, 2026
92f45b9
feat(i18n): thinking.tsx 状态标签 + i18n.tsx 扩展翻译表
Vodkakakaka May 12, 2026
76230f6
fix(i18n): setup.ts 接入 i18n + 补 ZH 翻译
Vodkakakaka May 12, 2026
259eb41
fix(tui): 中文模式下状态栏 verb padding 过长空格
Vodkakakaka May 12, 2026
c4500ec
fix(i18n): ops.ts 硬编码字符串接入翻译 + i18n.tsx 清理冗余 key
Vodkakakaka May 12, 2026
8d250cb
fix(web): 补全 14 个非英/中语言的 i18n 缺失字段
Vodkakakaka May 12, 2026
ced2d2d
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 13, 2026
1e28bd3
merge upstream main (104 commits, no i18n conflicts)
Vodkakakaka May 14, 2026
85b9d0e
Merge origin/main into feat/tui-i18n-localization
Vodkakakaka May 15, 2026
e7e472a
Merge origin/main into feat/tui-i18n-localization
Vodkakakaka May 15, 2026
9076f26
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 15, 2026
eb35162
Merge branch 'NousResearch:main' into feat/tui-i18n-localization
Vodkakakaka May 15, 2026
13350f0
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 15, 2026
e44fb65
Merge upstream/main: CJK/IME input fix
Vodkakakaka May 15, 2026
e03e0e6
Merge branch 'feat/tui-i18n-localization' of github.com:Vodkakakaka/h…
Vodkakakaka May 15, 2026
3190c7d
ci: auto-sync upstream/main into feat every 10 min
Vodkakakaka May 15, 2026
ac5537d
ci: change auto-sync interval to every 4 hours
Vodkakakaka May 15, 2026
3b6da7e
ci: remove redundant issue creation on conflict
Vodkakakaka May 15, 2026
e97fec4
🤖 auto-sync: merge upstream/main into feat/tui-i18n-localization
github-actions[bot] May 15, 2026
b19d937
merge: upstream 0.14.0 into feat/tui-i18n-localization
Vodkakakaka May 16, 2026
3352e7c
fix(qqbot): restore INTERACTION intent after 0.14.0 merge
Vodkakakaka May 16, 2026
afe0ff9
Revert "fix(qqbot): restore INTERACTION intent after 0.14.0 merge"
Vodkakakaka May 16, 2026
f61c501
Merge branch 'NousResearch:main' into feat/tui-i18n-localization
Vodkakakaka May 17, 2026
c84882a
chore: remove local-only branch maintenance files
Vodkakakaka May 17, 2026
3e3e1dc
Fix TUI i18n branch regressions
Vodkakakaka May 17, 2026
73b970e
merge: sync with upstream main (2026-05-20), resolve 6 conflicts
Vodkakakaka May 20, 2026
4ec383a
feat(web): i18n sidebar plugin nav labels (kanban, achievements, exam…
Vodkakakaka May 20, 2026
947bc53
fix(web): add missing locale prop to FieldHint in AutoField record br…
Vodkakakaka May 20, 2026
c7e5efd
feat(web): i18n loading-chat spinner text, add loadingChat key to all…
Vodkakakaka May 20, 2026
11c603a
feat(tui): 补齐 TUI 中文本地化覆盖——modelPicker/session/skills/agents/slash/qu…
Vodkakakaka May 20, 2026
e09d92f
feat(tui): 汉化欢迎界面、附件提示、消息截断文本
Vodkakakaka May 20, 2026
a912e82
feat: 输入框占位符汉化、工具集名称映射、CLI 新对话提示汉化
Vodkakakaka May 20, 2026
f8f19f3
fix(dashboard): 修复插件侧边栏 labelKey 缺失导致汉化不生效
Vodkakakaka May 20, 2026
724925c
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 20, 2026
c12bd7b
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 20, 2026
c5d17b1
fix(i18n): 回应 PR 审查反馈
Vodkakakaka May 20, 2026
2575033
fix(i18n): 收口本地化架构清理
Vodkakakaka May 20, 2026
5b092e3
fix(i18n): 清理 branding.tsx 废弃的 suffix 参数
Vodkakakaka May 20, 2026
d365e13
fix(i18n): TUI 中文翻译全面润色——修复 VERBS_ZH 错位、消除机翻味
Vodkakakaka May 20, 2026
aebfea4
fix(i18n): 补齐 zh.ts 漏译的 kanban 三个 key
CarbonFace May 20, 2026
2b6e6cd
fix(i18n): 统一 schemaZh.ts 中 specifier 译名与 kanban 段一致
CarbonFace May 20, 2026
eacc3b9
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 21, 2026
3e66211
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 21, 2026
3edc931
fix(i18n): Dashboard 8 组件补全 i18n 合约覆盖
Vodkakakaka May 21, 2026
8ef8b06
fix(i18n): 繁体中文划清边界——与日语同等处理,不映射到简体
Vodkakakaka May 21, 2026
f5bd885
refactor(i18n): 拆 TUI i18n 数据与逻辑,AutoField schema 标签走 locale lookup
Vodkakakaka May 21, 2026
26d9320
refactor(i18n): 修复 TUI TranslationKey 类型退化, SCHEMA_LABELS 提到模块级
Vodkakakaka May 21, 2026
ae22c51
refactor(i18n): 16-language TUI framework + cross-module vocabulary a…
Vodkakakaka May 21, 2026
585b224
fix(i18n): remove hard-coded zh from toolsetLabel and _tui_extra_meta
Vodkakakaka May 21, 2026
a88442c
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 21, 2026
ce07264
Merge upstream/main — #28269 remove unused Babel deps
Vodkakakaka May 21, 2026
38e0254
ci: auto-sync upstream/main into fork feat every 4 hours
Vodkakakaka May 21, 2026
47dc120
revert: remove fork auto-sync workflow
Vodkakakaka May 21, 2026
12605ec
merge: upstream/main into feat/tui-i18n-localization
Vodkakakaka May 22, 2026
09c26fa
Merge upstream/main into feat/tui-i18n-localization
Vodkakakaka May 22, 2026
772c66d
fix: align HOTKEY_DEFS → HOTKEYS export after upstream merge
Vodkakakaka May 22, 2026
b196274
chore: i18n audit — fix hardcoded strings, dead imports, and translat…
Vodkakakaka May 22, 2026
0176c47
fix: de-hardcode zh-only CJK check + remove stale CLI i18n from PR body
Vodkakakaka May 22, 2026
bb575cc
fix: make verbStyle language-pack-driven instead of hardcoding CJK list
Vodkakakaka May 22, 2026
21bcd7c
fix: VERB_PAD_LEN iterate all LOCALES instead of hardcoding en+zh
Vodkakakaka May 22, 2026
9155e6d
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 22, 2026
c8ff04b
fix(i18n): normalizeLocale 别名表覆盖全部 16 种语言,不再只有中英
Vodkakakaka May 22, 2026
9ddd740
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 22, 2026
fce6c4a
Merge 9ddd740dff4993264c8525c502079057dc7fbdce into 1e71b7180e5b4e849…
Vodkakakaka May 22, 2026
1d6dbd7
merge: 同步 upstream main 到 i18n 分支
Vodkakakaka May 23, 2026
f6d0cd9
merge: 解决 upstream/main 冲突(i18n + voiceTts + responsive banner)
Vodkakakaka May 24, 2026
7ed4dc4
fix: 补全上游新增的 setVoiceTts 到 voice 返回对象
Vodkakakaka May 24, 2026
1753e89
test(i18n): 43 tests covering key coverage, fallback, verbStyle, norm…
Vodkakakaka May 24, 2026
4b21372
docs: finalize PR #23243 body — bilingual, clean, no iteration scars
Vodkakakaka May 24, 2026
4e6b639
docs: fix English-unchanged wording — not zh-centric, locale-equal
Vodkakakaka May 24, 2026
7246320
docs: remove defensive if-zh phrasing — state what it does, not what …
Vodkakakaka May 24, 2026
354847c
docs(i18n): fix stale docstring — list all 16 supported languages
Vodkakakaka May 24, 2026
f686c94
Merge branch 'main' into feat/tui-i18n-localization
Vodkakakaka May 26, 2026
da2a3a2
fix(i18n): Dashboard LanguageSwitcher → config sync, TUI voice label …
Vodkakakaka May 26, 2026
267b496
merge: upstream/main into feat/tui-i18n-localization
Vodkakakaka May 29, 2026
96867f5
Merge remote-tracking branch 'origin/main' into feat/tui-i18n-localiz…
Vodkakakaka May 29, 2026
a076471
fix: remove unused createPortal import after merge conflict resolution
Vodkakakaka May 29, 2026
811d381
fix(tui): align status bar JSX structure with upstream to fix Box-ins…
Vodkakakaka May 29, 2026
c00d038
merge: 同步 upstream main 到 i18n 分支
Vodkakakaka May 31, 2026
cedac70
merge: 同步 upstream main 到 i18n 分支
Vodkakakaka Jun 1, 2026
c8c6694
fix(i18n): 让 Dashboard 初始语言跟随配置
Vodkakakaka Jun 1, 2026
7313356
merge: 同步上游 main 到 i18n PR
Vodkakakaka Jun 2, 2026
1f2a01f
merge: 同步 upstream main 到 i18n 分支
Vodkakakaka Jun 2, 2026
df98c80
fix: 补齐 Channels 页面国际化
Vodkakakaka Jun 2, 2026
51ab8e3
chore: 更新 i18n PR 说明与 Channels 文案替换
Vodkakakaka Jun 2, 2026
04db001
合并上游 main 并解决 PR 冲突
Vodkakakaka Jun 3, 2026
45be6e7
merge: 同步上游 main 到最新版本
Vodkakakaka Jun 4, 2026
234fe25
合并上游 main 并解决 PR 冲突
Vodkakakaka Jun 6, 2026
af9c288
merge: 同步上游 main 并解决状态栏冲突
Vodkakakaka Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hermes_cli/web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4104,6 +4104,7 @@ def _discover_dashboard_plugins() -> list:
plugins.append({
"name": name,
"label": data.get("label", name),
"labelKey": data.get("labelKey"),
"description": data.get("description", ""),
"icon": data.get("icon", "Puzzle"),
"version": data.get("version", "0.0.0"),
Expand Down
5 changes: 3 additions & 2 deletions plugins/example-dashboard/dashboard/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "example",
"label": "Example",
"description": "Example dashboard plugin used by test suite for auth coverage",
"description": "Example dashboard plugin \u2014 used by test suite for auth coverage",
"icon": "Sparkles",
"version": "1.0.0",
"tab": {
Expand All @@ -10,5 +10,6 @@
},
"slots": [],
"entry": "dist/index.js",
"api": "plugin_api.py"
"api": "plugin_api.py",
"labelKey": "example"
}
8 changes: 6 additions & 2 deletions plugins/hermes-achievements/dashboard/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"description": "Steam-style achievements for vibe coding and agentic Hermes workflows.",
"icon": "Star",
"version": "0.4.0",
"tab": { "path": "/achievements", "position": "after:analytics" },
"tab": {
"path": "/achievements",
"position": "after:analytics"
},
"entry": "dist/index.js",
"css": "dist/style.css",
"api": "plugin_api.py"
"api": "plugin_api.py",
"labelKey": "achievements"
}
5 changes: 3 additions & 2 deletions plugins/kanban/dashboard/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "kanban",
"label": "Kanban",
"description": "Multi-agent collaboration board drag-drop cards across columns, read comment threads, see which profile is running what",
"description": "Multi-agent collaboration board \u2014 drag-drop cards across columns, read comment threads, see which profile is running what",
"icon": "Package",
"version": "1.0.0",
"tab": {
Expand All @@ -10,5 +10,6 @@
},
"entry": "dist/index.js",
"css": "dist/style.css",
"api": "plugin_api.py"
"api": "plugin_api.py",
"labelKey": "kanban"
}
42 changes: 42 additions & 0 deletions tests/test_tui_gateway_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,48 @@ def test_complete_slash_includes_tui_mouse_command():
assert any(item["text"] == "/mouse" for item in resp["result"]["items"])


def test_complete_slash_tui_extra_meta_respects_language(monkeypatch):
monkeypatch.setattr(server, "resolve_language", lambda: "en")
resp_en = server.handle_request(
{"id": "1", "method": "complete.slash", "params": {"text": "/com"}}
)
assert isinstance(resp_en, dict)
result_en = resp_en.get("result")
assert isinstance(result_en, dict)

compact_en = next(item for item in result_en["items"] if item["text"] == "/compact")
assert compact_en["meta"] == "Toggle compact display mode"

monkeypatch.setattr(server, "resolve_language", lambda: "zh")
resp_zh = server.handle_request(
{"id": "2", "method": "complete.slash", "params": {"text": "/com"}}
)
assert isinstance(resp_zh, dict)
result_zh = resp_zh.get("result")
assert isinstance(result_zh, dict)

compact_zh = next(item for item in result_zh["items"] if item["text"] == "/compact")
assert compact_zh["meta"] == "切换紧凑显示模式"


def test_commands_catalog_tui_extra_meta_respects_language(monkeypatch):
monkeypatch.setattr(server, "resolve_language", lambda: "en")
resp_en = server.handle_request(
{"id": "1", "method": "commands.catalog", "params": {}}
)
tui_en = next(c for c in resp_en["result"]["categories"] if c["name"] == "TUI")
compact_en = next(item for item in tui_en["pairs"] if item[0] == "/compact")
assert compact_en[1] == "Toggle compact display mode"

monkeypatch.setattr(server, "resolve_language", lambda: "zh")
resp_zh = server.handle_request(
{"id": "2", "method": "commands.catalog", "params": {}}
)
tui_zh = next(c for c in resp_zh["result"]["categories"] if c["name"] == "TUI")
compact_zh = next(item for item in tui_zh["pairs"] if item[0] == "/compact")
assert compact_zh[1] == "切换紧凑显示模式"


def test_complete_slash_details_args():
resp_root = server.handle_request(
{"id": "0", "method": "complete.slash", "params": {"text": "/details"}}
Expand Down
4 changes: 2 additions & 2 deletions tui_gateway/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import traceback

from tui_gateway import server
from tui_gateway.server import _CRASH_LOG, dispatch, resolve_skin, write_json
from tui_gateway.server import _CRASH_LOG, dispatch, resolve_language, resolve_skin, write_json
from tui_gateway.transport import TeeTransport


Expand Down Expand Up @@ -219,7 +219,7 @@ def main():
if not write_json({
"jsonrpc": "2.0",
"method": "event",
"params": {"type": "gateway.ready", "payload": {"skin": resolve_skin()}},
"params": {"type": "gateway.ready", "payload": {"language": resolve_language(), "skin": resolve_skin()}},
}):
_log_exit("startup write failed (broken stdout pipe before first event)")
sys.exit(0)
Expand Down
58 changes: 33 additions & 25 deletions tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,15 @@ def resolve_skin() -> dict:
return {}


def resolve_language() -> str:
"""Resolve the language code used by TUI-facing gateway metadata."""
try:
from agent.i18n import get_language
return get_language()
except Exception:
return "en"


def _resolve_model() -> str:
env = (
os.environ.get("HERMES_MODEL", "")
Expand Down Expand Up @@ -4399,12 +4408,28 @@ def _(rid, params: dict) -> dict:
}
)

_TUI_EXTRA: list[tuple[str, str, str]] = [
("/compact", "Toggle compact display mode", "TUI"),
("/logs", "Show recent gateway log lines", "TUI"),
("/mouse", "Toggle mouse/wheel tracking [on|off|toggle]", "TUI"),
_TUI_EXTRA_META: dict[str, tuple[str, str]] = {
"/compact": ("Toggle compact display mode", "切换紧凑显示模式"),
"/details": ("Control agent detail visibility", "控制 Agent 详情可见性"),
"/logs": ("Show recent gateway log lines", "显示最近的网关日志"),
"/mouse": (
"Toggle mouse/wheel tracking [on|off|toggle]",
"切换鼠标/滚轮追踪 [on|off|toggle]",
),
}

_TUI_EXTRA: list[tuple[str, str]] = [
("/compact", "TUI"),
("/logs", "TUI"),
("/mouse", "TUI"),
]


def _tui_extra_meta(command: str) -> str:
"""Return localized metadata for TUI-only slash commands."""
en, zh = _TUI_EXTRA_META[command]
return zh if resolve_language().lower().startswith("zh") else en

# Commands that queue messages onto _pending_input in the CLI.
# In the TUI the slash worker subprocess has no reader for that queue,
# so slash.exec rejects them → TUI falls through to command.dispatch.
Expand Down Expand Up @@ -4456,7 +4481,8 @@ def _(rid, params: dict) -> dict:
cat_order.append(cat)
cat_map[cat].append([c, desc])

for name, desc, cat in _TUI_EXTRA:
for name, cat in _TUI_EXTRA:
desc = _tui_extra_meta(name)
all_pairs.append([name, desc])
if cat not in cat_map:
cat_map[cat] = []
Expand Down Expand Up @@ -5262,26 +5288,8 @@ def _(rid, params: dict) -> dict:
][:30]
text_lower = text.lower()
extras = [
{
"text": "/compact",
"display": "/compact",
"meta": "Toggle compact display mode",
},
{
"text": "/details",
"display": "/details",
"meta": "Control agent detail visibility",
},
{
"text": "/logs",
"display": "/logs",
"meta": "Show recent gateway log lines",
},
{
"text": "/mouse",
"display": "/mouse",
"meta": "Toggle mouse/wheel tracking [on|off|toggle]",
},
{"text": command, "display": command, "meta": _tui_extra_meta(command)}
for command in _TUI_EXTRA_META
]
for extra in extras:
if extra["text"].startswith(text_lower) and not any(
Expand Down
2 changes: 1 addition & 1 deletion tui_gateway/ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async def handle_ws(ws: Any) -> None:
"method": "event",
"params": {
"type": "gateway.ready",
"payload": {"skin": server.resolve_skin()},
"payload": {"language": server.resolve_language(), "skin": server.resolve_skin()},
},
}
)
Expand Down
6 changes: 4 additions & 2 deletions ui-tui/src/__tests__/clipboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { describe, expect, it, vi } from 'vitest'

import { isUsableClipboardText, readClipboardText, writeClipboardText } from '../lib/clipboard.js'

const POWERSHELL_READ_CLIPBOARD = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw'

describe('readClipboardText', () => {
it('reads text from pbpaste on macOS', async () => {
const run = vi.fn().mockResolvedValue({ stdout: 'hello world\n' })
Expand All @@ -20,7 +22,7 @@ describe('readClipboardText', () => {
await expect(readClipboardText('win32', run)).resolves.toBe('from windows\r\n')
expect(run).toHaveBeenCalledWith(
'powershell',
['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'],
['-NoProfile', '-NonInteractive', '-Command', POWERSHELL_READ_CLIPBOARD],
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
)
})
Expand All @@ -33,7 +35,7 @@ describe('readClipboardText', () => {
)
expect(run).toHaveBeenCalledWith(
'powershell.exe',
['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'],
['-NoProfile', '-NonInteractive', '-Command', POWERSHELL_READ_CLIPBOARD],
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
)
})
Expand Down
10 changes: 5 additions & 5 deletions ui-tui/src/__tests__/constants.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'

import { FACES } from '../content/faces.js'
import { HOTKEYS } from '../content/hotkeys.js'
import { HOTKEY_DEFS } from '../content/hotkeys.js'
import { PLACEHOLDERS } from '../content/placeholders.js'
import { TOOL_VERBS, VERBS } from '../content/verbs.js'
import { ROLE } from '../domain/roles.js'
Expand All @@ -19,17 +19,17 @@ describe('constants', () => {
}
})

it('HOTKEYS are [key, desc] pairs', () => {
HOTKEYS.forEach(([k, d]) => {
it('HOTKEY_DEFS are [key, TranslationKey] pairs', () => {
HOTKEY_DEFS.forEach(([k, d]) => {
expect(typeof k).toBe('string')
expect(typeof d).toBe('string')
})
})

it('documents Ctrl/Cmd+L as non-destructive redraw', () => {
const hotkey = HOTKEYS.find(([k]) => k.endsWith('+L'))
const hotkey = HOTKEY_DEFS.find(([k]) => k.endsWith('+L'))
expect(hotkey).toBeDefined()
expect(hotkey?.[1]).toBe('redraw / repaint')
expect(hotkey?.[1]).toBe('hotkey.redraw')
})

it('TOOL_VERBS maps known tools (verb-only, no emoji)', () => {
Expand Down
2 changes: 1 addition & 1 deletion ui-tui/src/__tests__/createSlashHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ describe('createSlashHandler', () => {
expect(createSlashHandler(ctx)('/voice on')).toBe(true)
await vi.waitFor(() => {
expect(ctx.transcript.sys).toHaveBeenCalledWith('Voice mode enabled')
expect(ctx.transcript.sys).toHaveBeenCalledWith(' Alt+R to start/stop recording')
expect(ctx.transcript.sys).toHaveBeenCalledWith('Alt+R to start/stop recording')
})
expect(ctx.voice.setVoiceRecordKey).toHaveBeenCalledWith(expect.objectContaining({ ch: 'r', mod: 'alt' }))
})
Expand Down
4 changes: 2 additions & 2 deletions ui-tui/src/__tests__/gatewayClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ describe('GatewayClient websocket attach mode', () => {
const next = gw.request('session.create', {})

await expect(stale).rejects.toThrow(/gateway attach url changed/)
await vi.waitFor(() => expect(FakeWebSocket.instances).toHaveLength(2))
await vi.waitFor(() => expect(FakeWebSocket.instances.length).toBeGreaterThanOrEqual(2))

const secondSocket = FakeWebSocket.instances[1]!
const secondSocket = FakeWebSocket.instances.at(-1)!
expect(secondSocket.url).toContain('gateway-new.test')

secondSocket.open()
Expand Down
4 changes: 2 additions & 2 deletions ui-tui/src/__tests__/statusBarTicker.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest'

import { padVerb, VERB_PAD_LEN } from '../components/appChrome.js'
import { displayWidth, padVerb, VERB_PAD_LEN } from '../components/appChrome.js'
import { VERBS } from '../content/verbs.js'

describe('FaceTicker verb padding', () => {
it('pads every verb to the same width', () => {
for (const verb of VERBS) {
expect(padVerb(verb)).toHaveLength(VERB_PAD_LEN)
expect(displayWidth(padVerb(verb))).toBe(VERB_PAD_LEN)
}
})

Expand Down
21 changes: 12 additions & 9 deletions ui-tui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import { $uiState } from './app/uiStore.js'
import { useMainApp } from './app/useMainApp.js'
import { AppLayout } from './components/appLayout.js'
import type { GatewayClient } from './gatewayClient.js'
import { I18nProvider } from './i18n.js'

export function App({ gw }: { gw: GatewayClient }) {
const { appActions, appComposer, appProgress, appStatus, appTranscript, gateway } = useMainApp(gw)
const { mouseTracking } = useStore($uiState)
const { locale, mouseTracking } = useStore($uiState)

return (
<GatewayProvider value={gateway}>
<AppLayout
actions={appActions}
composer={appComposer}
mouseTracking={mouseTracking}
progress={appProgress}
status={appStatus}
transcript={appTranscript}
/>
<I18nProvider locale={locale}>
<AppLayout
actions={appActions}
composer={appComposer}
mouseTracking={mouseTracking}
progress={appProgress}
status={appStatus}
transcript={appTranscript}
/>
</I18nProvider>
</GatewayProvider>
)
}
Loading