feat(terminal): replace ghostty-web with an in-repo libghostty-vt adapter
The terminal ran on the ghostty-web npm package plus a hand-written patch, and every rendering bug (recycled rows, duplicated reflow fragments, prompt artifacts) had to be worked around from outside. The emulator now is the official libghostty-vt C ABI compiled to WebAssembly, driven by a browser adapter ported from T3 Code (MIT, notice in LICENSE-T3CODE) and owned in packages/ui/src/lib/ghostty. The artifact is reproducible with scripts/build-libghostty-wasm.sh, including a workaround for Zig 0.15.2 on macOS 27 SDKs. On top of the port: one WASM instance per page with every tab kept mounted and hidden tabs paused; history replayed at the PTY size it was drawn for; shells spawned only after the first fitted grid so zsh never prints the PROMPT_SP marker; box drawing, block elements and Powerline arrows drawn procedurally to the exact cell so TUI borders and block logos have no gaps between rows; a software-rasterized canvas so Gecko renders every tab's text with the same smoothing; the symbols-only Nerd Font bundled instead of a CDN fetch; touch selection and scrolling driven through the surface API; a copy button in the tab strip for touch hosts; localized aria labels. Testing: bun tests run the real WASM (reflow, palette, replay isolation, recycled rows, box glyph geometry); viewport and view tests use a surface double; verified in Chromium and Zen (windowed and headless) for crisp text, new tabs, panel reopen, resize and box glyph rendering; package type-check, oxlint/eslint on new files, web build.
This commit is contained in:
@@ -1646,6 +1646,7 @@ export const dict = {
|
||||
'terminalView.tabs.closeTabTitle': 'Registerkarte schließen',
|
||||
'terminalView.tabs.newTabTitle': 'Neue Registerkarte',
|
||||
'terminalView.viewport.inputAria': 'Terminal-Eingabe',
|
||||
'terminalView.viewport.scrollbarAria': 'Terminal-Verlauf',
|
||||
'directoryExplorerDialog.title': 'Projektverzeichnis hinzufügen',
|
||||
'directoryExplorerDialog.description': 'Wählen Sie einen Ordner aus, der als Projekt hinzugefügt werden soll.',
|
||||
'directoryExplorerDialog.toggle.showHidden': 'Versteckte anzeigen',
|
||||
@@ -2979,6 +2980,9 @@ export const dict = {
|
||||
'quota.window.completions': 'Vervollständigungen',
|
||||
'quota.window.premiumInteractions': 'KI-Guthaben',
|
||||
'terminalView.actions.attachSelection': 'Ausgewählte Ausgabe anhängen',
|
||||
'terminalView.actions.copySelection': 'Ausgewählte Ausgabe kopieren',
|
||||
'terminalView.toast.selectionCopied': 'Ausgabe kopiert',
|
||||
'terminalView.toast.copyFailed': 'Kopieren fehlgeschlagen',
|
||||
'terminalView.actions.restart': 'Terminal neu starten',
|
||||
'chat.message.terminalContext': '{terminal}, Zeilen {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Kommentar zu {file}, Zeilen {start}-{end}',
|
||||
|
||||
@@ -8,6 +8,9 @@ export const dict = {
|
||||
...linearIssuePickerI18n.en,
|
||||
...linearPanelI18n.en,
|
||||
'terminalView.actions.attachSelection': 'Attach selected output',
|
||||
'terminalView.actions.copySelection': 'Copy selected output',
|
||||
'terminalView.toast.selectionCopied': 'Output copied',
|
||||
'terminalView.toast.copyFailed': 'Copy failed',
|
||||
'terminalView.actions.restart': 'Restart terminal',
|
||||
'chat.message.terminalContext': '{terminal}, lines {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Comment on {file}, lines {start}-{end}',
|
||||
@@ -1847,6 +1850,7 @@ export const dict = {
|
||||
'terminalView.tabs.closeTabTitle': 'Close tab',
|
||||
'terminalView.tabs.newTabTitle': 'New tab',
|
||||
'terminalView.viewport.inputAria': 'Terminal input',
|
||||
'terminalView.viewport.scrollbarAria': 'Terminal scrollback',
|
||||
'directoryExplorerDialog.title': 'Add project directory',
|
||||
'directoryExplorerDialog.description': 'Choose a folder to add as a project.',
|
||||
'directoryExplorerDialog.toggle.showHidden': 'Show hidden',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n.es,
|
||||
...linearPanelI18n.es,
|
||||
'terminalView.actions.attachSelection': 'Adjuntar salida seleccionada',
|
||||
'terminalView.actions.copySelection': 'Copiar salida seleccionada',
|
||||
'terminalView.toast.selectionCopied': 'Salida copiada',
|
||||
'terminalView.toast.copyFailed': 'Error al copiar',
|
||||
'terminalView.actions.restart': 'Reiniciar terminal',
|
||||
'chat.message.terminalContext': '{terminal}, líneas {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Comentario en {file}, líneas {start}-{end}',
|
||||
@@ -1825,6 +1828,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"terminalView.tabs.closeTabTitle": "Cerrar pestaña",
|
||||
"terminalView.tabs.newTabTitle": "Nueva pestaña",
|
||||
"terminalView.viewport.inputAria": "Entrada de terminal",
|
||||
"terminalView.viewport.scrollbarAria": "Historial del terminal",
|
||||
"directoryExplorerDialog.title": "Añadir directorio de proyecto",
|
||||
"directoryExplorerDialog.description": "Elige una carpeta para añadir como proyecto.",
|
||||
"directoryExplorerDialog.toggle.showHidden": "Mostrar ocultos",
|
||||
|
||||
@@ -8,6 +8,9 @@ export const dict = {
|
||||
...linearIssuePickerI18n.fr,
|
||||
...linearPanelI18n.fr,
|
||||
'terminalView.actions.attachSelection': 'Joindre la sortie sélectionnée',
|
||||
'terminalView.actions.copySelection': 'Copier la sortie sélectionnée',
|
||||
'terminalView.toast.selectionCopied': 'Sortie copiée',
|
||||
'terminalView.toast.copyFailed': 'Échec de la copie',
|
||||
'terminalView.actions.restart': 'Redémarrer le terminal',
|
||||
'chat.message.terminalContext': '{terminal}, lignes {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Commentaire sur {file}, lignes {start}-{end}',
|
||||
@@ -1604,6 +1607,7 @@ export const dict = {
|
||||
'terminalView.tabs.closeTabTitle': 'Fermer l\'onglet',
|
||||
'terminalView.tabs.newTabTitle': 'Nouvel onglet',
|
||||
'terminalView.viewport.inputAria': 'Entrée de borne',
|
||||
'terminalView.viewport.scrollbarAria': 'Historique du terminal',
|
||||
'directoryExplorerDialog.title': 'Ajouter un répertoire de projet',
|
||||
'directoryExplorerDialog.description': 'Choisissez un dossier à ajouter en tant que projet.',
|
||||
'directoryExplorerDialog.toggle.showHidden': 'Afficher masqué',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n.ja,
|
||||
...linearPanelI18n.ja,
|
||||
'terminalView.actions.attachSelection': '選択した出力を添付',
|
||||
'terminalView.actions.copySelection': '選択した出力をコピー',
|
||||
'terminalView.toast.selectionCopied': '出力をコピーしました',
|
||||
'terminalView.toast.copyFailed': 'コピーに失敗しました',
|
||||
'terminalView.actions.restart': 'ターミナルを再起動',
|
||||
'chat.message.terminalContext': '{terminal}、{start}〜{end}行',
|
||||
'chat.message.context.codeComment': '{file} の {start}〜{end} 行へのコメント',
|
||||
@@ -1843,6 +1846,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'terminalView.tabs.closeTabTitle': 'タブを閉じる',
|
||||
'terminalView.tabs.newTabTitle': '新しいタブ',
|
||||
'terminalView.viewport.inputAria': 'ターミナル入力',
|
||||
'terminalView.viewport.scrollbarAria': 'ターミナルのスクロールバック',
|
||||
'directoryExplorerDialog.title': 'プロジェクトディレクトリを追加',
|
||||
'directoryExplorerDialog.description': 'プロジェクトとして追加するフォルダを選択してください。',
|
||||
'directoryExplorerDialog.toggle.showHidden': '隠しファイルを表示',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n.ko,
|
||||
...linearPanelI18n.ko,
|
||||
'terminalView.actions.attachSelection': '선택한 출력 첨부',
|
||||
'terminalView.actions.copySelection': '선택한 출력 복사',
|
||||
'terminalView.toast.selectionCopied': '출력을 복사했습니다',
|
||||
'terminalView.toast.copyFailed': '복사 실패',
|
||||
'terminalView.actions.restart': '터미널 다시 시작',
|
||||
'chat.message.terminalContext': '{terminal}, {start}-{end}행',
|
||||
'chat.message.context.codeComment': '{file} {start}-{end}행에 대한 댓글',
|
||||
@@ -1849,6 +1852,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'terminalView.tabs.closeTabTitle': '탭 닫기',
|
||||
'terminalView.tabs.newTabTitle': '새 탭',
|
||||
'terminalView.viewport.inputAria': '터미널 입력',
|
||||
'terminalView.viewport.scrollbarAria': '터미널 스크롤백',
|
||||
'directoryExplorerDialog.title': '프로젝트 디렉터리 추가',
|
||||
'directoryExplorerDialog.description': '프로젝트로 추가할 폴더를 선택하세요.',
|
||||
'directoryExplorerDialog.toggle.showHidden': '숨김 항목 표시',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n.pl,
|
||||
...linearPanelI18n.pl,
|
||||
'terminalView.actions.attachSelection': 'Dołącz zaznaczone dane wyjściowe',
|
||||
'terminalView.actions.copySelection': 'Kopiuj zaznaczone dane wyjściowe',
|
||||
'terminalView.toast.selectionCopied': 'Skopiowano dane wyjściowe',
|
||||
'terminalView.toast.copyFailed': 'Kopiowanie nie powiodło się',
|
||||
'terminalView.actions.restart': 'Uruchom terminal ponownie',
|
||||
'chat.message.terminalContext': '{terminal}, wiersze {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Komentarz do {file}, wiersze {start}-{end}',
|
||||
@@ -3043,6 +3046,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'terminalView.tabs.closeTabTitle': 'Close tab',
|
||||
'terminalView.tabs.newTabTitle': 'New tab',
|
||||
'terminalView.viewport.inputAria': 'Terminal input',
|
||||
'terminalView.viewport.scrollbarAria': 'Historia terminala',
|
||||
'textarea.resizeHandleAria': 'Resize textarea',
|
||||
'updateDialog.actions.copied': 'Skopiowano!',
|
||||
'updateDialog.actions.copyCommand': 'Kopiuj polecenie',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n['pt-BR'],
|
||||
...linearPanelI18n['pt-BR'],
|
||||
'terminalView.actions.attachSelection': 'Anexar saída selecionada',
|
||||
'terminalView.actions.copySelection': 'Copiar saída selecionada',
|
||||
'terminalView.toast.selectionCopied': 'Saída copiada',
|
||||
'terminalView.toast.copyFailed': 'Falha ao copiar',
|
||||
'terminalView.actions.restart': 'Reiniciar terminal',
|
||||
'chat.message.terminalContext': '{terminal}, linhas {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Comentário em {file}, linhas {start}-{end}',
|
||||
@@ -1825,6 +1828,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"terminalView.tabs.closeTabTitle": "Fechar aba",
|
||||
"terminalView.tabs.newTabTitle": "Nova aba",
|
||||
"terminalView.viewport.inputAria": "Entrada de terminal",
|
||||
"terminalView.viewport.scrollbarAria": "Histórico do terminal",
|
||||
"directoryExplorerDialog.title": "Adicionar diretório de projeto",
|
||||
"directoryExplorerDialog.description": "Escolha uma pasta para adicionar como projeto.",
|
||||
"directoryExplorerDialog.toggle.showHidden": "Mostrar ocultos",
|
||||
|
||||
@@ -8,6 +8,9 @@ export const dict = {
|
||||
...linearIssuePickerI18n.tr,
|
||||
...linearPanelI18n.tr,
|
||||
'terminalView.actions.attachSelection': 'Seçili çıktıyı ekle',
|
||||
'terminalView.actions.copySelection': 'Seçili çıktıyı kopyala',
|
||||
'terminalView.toast.selectionCopied': 'Çıktı kopyalandı',
|
||||
'terminalView.toast.copyFailed': 'Kopyalama başarısız',
|
||||
'terminalView.actions.restart': 'Terminali yeniden başlat',
|
||||
'chat.message.terminalContext': '{terminal}, {start}-{end}. satırlar',
|
||||
'chat.chatInput.terminalContext': '{terminal}, {start}-{end}. satırlar',
|
||||
@@ -1809,6 +1812,7 @@ export const dict = {
|
||||
'terminalView.tabs.closeTabTitle': 'Sekmeyi kapat',
|
||||
'terminalView.tabs.newTabTitle': 'Yeni sekme',
|
||||
'terminalView.viewport.inputAria': 'Terminal girişi',
|
||||
'terminalView.viewport.scrollbarAria': 'Terminal geçmişi',
|
||||
'directoryExplorerDialog.title': 'Proje dizini ekle',
|
||||
'directoryExplorerDialog.description': 'Proje olarak eklemek için bir klasör seçin.',
|
||||
'directoryExplorerDialog.toggle.showHidden': 'Gizli dosyaları göster',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n.uk,
|
||||
...linearPanelI18n.uk,
|
||||
'terminalView.actions.attachSelection': 'Прикріпити вибраний вивід',
|
||||
'terminalView.actions.copySelection': 'Скопіювати вибраний вивід',
|
||||
'terminalView.toast.selectionCopied': 'Вивід скопійовано',
|
||||
'terminalView.toast.copyFailed': 'Не вдалося скопіювати',
|
||||
'terminalView.actions.restart': 'Перезапустити термінал',
|
||||
'chat.message.terminalContext': '{terminal}, рядки {start}-{end}',
|
||||
'chat.message.context.codeComment': 'Коментар до {file}, рядки {start}-{end}',
|
||||
@@ -1825,6 +1828,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"terminalView.tabs.closeTabTitle": "Закрити вкладку",
|
||||
"terminalView.tabs.newTabTitle": "Нова вкладка",
|
||||
"terminalView.viewport.inputAria": "Ввід терміналу",
|
||||
"terminalView.viewport.scrollbarAria": "Історія термінала",
|
||||
"directoryExplorerDialog.title": "Додати каталог проєкту",
|
||||
"directoryExplorerDialog.description": "Виберіть папку, щоб додати її як проєкт.",
|
||||
"directoryExplorerDialog.toggle.showHidden": "Показати приховані",
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n['zh-CN'],
|
||||
...linearPanelI18n['zh-CN'],
|
||||
'terminalView.actions.attachSelection': '附加所选输出',
|
||||
'terminalView.actions.copySelection': '复制所选输出',
|
||||
'terminalView.toast.selectionCopied': '已复制输出',
|
||||
'terminalView.toast.copyFailed': '复制失败',
|
||||
'terminalView.actions.restart': '重启终端',
|
||||
'chat.message.terminalContext': '{terminal},第 {start}-{end} 行',
|
||||
'chat.message.context.codeComment': '对 {file} 第 {start}-{end} 行的评论',
|
||||
@@ -1813,6 +1816,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'terminalView.tabs.closeTabTitle': '关闭标签页',
|
||||
'terminalView.tabs.newTabTitle': '新建标签页',
|
||||
'terminalView.viewport.inputAria': '终端输入',
|
||||
'terminalView.viewport.scrollbarAria': '终端回滚历史',
|
||||
'directoryExplorerDialog.title': '添加项目目录',
|
||||
'directoryExplorerDialog.description': '选择一个文件夹添加为项目。',
|
||||
'directoryExplorerDialog.toggle.showHidden': '显示隐藏项',
|
||||
|
||||
@@ -9,6 +9,9 @@ export const dict: Record<I18nKey, string> = {
|
||||
...linearIssuePickerI18n['zh-TW'],
|
||||
...linearPanelI18n['zh-TW'],
|
||||
'terminalView.actions.attachSelection': '附加所選輸出',
|
||||
'terminalView.actions.copySelection': '複製所選輸出',
|
||||
'terminalView.toast.selectionCopied': '已複製輸出',
|
||||
'terminalView.toast.copyFailed': '複製失敗',
|
||||
'terminalView.actions.restart': '重新啟動終端',
|
||||
'chat.message.terminalContext': '{terminal},第 {start}-{end} 行',
|
||||
'chat.message.context.codeComment': '對 {file} 第 {start}-{end} 行的評論',
|
||||
@@ -1817,6 +1820,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'terminalView.tabs.closeTabTitle': '關閉分頁',
|
||||
'terminalView.tabs.newTabTitle': '新增分頁',
|
||||
'terminalView.viewport.inputAria': '終端機輸入',
|
||||
'terminalView.viewport.scrollbarAria': '終端機回捲歷史',
|
||||
'directoryExplorerDialog.title': '新增專案目錄',
|
||||
'directoryExplorerDialog.description': '選擇一個資料夾新增為專案。',
|
||||
'directoryExplorerDialog.toggle.showHidden': '顯示隱藏項目',
|
||||
|
||||
Reference in New Issue
Block a user