AI Coding 為什麼選擇 TUI,前端的新機會在哪裡?

>「作為一名前端搬磚工,我每天在 VS Code、Chrome 開發者工具、Figma 和終端機之間切換。直到開始使用 Claude Code,我才發現自己最沉浸的時刻,還是游標在黑色背景中閃爍的那個視窗。」

GUI 的黃金時代與隱形天花板

過去十五年,我們前端見證了圖形使用者介面(GUI)在前端工程領域的全面勝利。VS Code 用 Electron 證明了 Web 技術可以打造桌面級 IDE,Chrome 開發者工具將瀏覽器內部狀態可視化到了極致,Figma 讓協作設計擺脫了本地軟體的束縛。作為前端工程師,我們既是 GUI 的建構者,也是 GUI 最忠實的使用者。

但 GUI 的成功也暗含了它的結構性限制。從渲染管線的視角來看,GUI 的本質是一場像素預算的分配遊戲。每一個按鈕、每一行文字、每一個面板都在爭奪螢幕上的二維空間。這種競爭導致了三個深層問題:

第一,上下文碎片化。 當我們使用 VS Code 除錯一個 React 應用時,我們的注意力被分散在:左邊的檔案樹、中間的編輯器、底部的終端機、右邊的開發者工具面板,以及可能彈出的 Copilot 側邊欄。每個面板都是獨立的上下文容器,而人腦的工作記憶(working memory)只能同時保持 4±1 個組塊的資訊。GUI 的「所見即所得」,在某些場景下變成了「所見即所失」——我們看到的一切都在爭奪我們有限的注意力。

第二,滑鼠依賴的互動稅。 GUI 假設使用者的主要輸入裝置是滑鼠或觸控板。這看起來很自然,其實代價不小。如果要把一個想法轉化為程式碼,需要經歷:大腦構思 → 手部移動到滑鼠 → 定位游標 → 點擊/拖曳 → 回到鍵盤繼續輸入。這個切換過程在神經科學中被稱為「任務切換成本」(task-switching cost),每次切換都會消耗約 200-500 毫秒的注意力重建時間。對於一個每天寫程式 6 小時的前端工程師來說,這意味著累積數小時的純等待時間。

第三,語義間隙。 GUI 為了降低學習成本,大量使用隱喻(metaphor)——資料夾圖示代表目錄,垃圾桶圖示代表刪除。但這種隱喻在抽象層級上建立了一道屏障。當我們想批次重新命名 100 個元件檔案時,GUI 的「右鍵→重新命名」操作是災難性的;而在終端機中,一行 find src -name "*.tsx" | xargs rename 就能表達精確的意圖。命令列是人類意圖最接近機器執行的路徑。

用前端框架的術語來類比:GUI 的渲染管線像是一棵需要不斷進行重排(reflow)和重繪(repaint)的複雜 DOM 樹。每次打開新面板、調整視窗大小、彈出通知,都會觸發一次全局的樣式計算和佈局更新。而 TUI(文字使用者介面)則更像一個精心優化的 Canvas 渲染層——它知道自己的邊界是字元格,因此可以跳過大量的佈局協商,直接進行像素(字元)級別的繪製。

上圖展示了兩種互動範式的渲染複雜度差異。GUI 的輸入事件需要經歷完整的命中測試、事件冒泡、樣式重算和佈局重排,而 TUI 的輸入可以直接映射到狀態變更和單元格差異更新。這不是說 GUI 落後,而是不同的問題領域需要不同的抽象層級

終端機的復興:不是倒退,是螺旋上升

終端機並沒有消失,它只是暫時被 GUI 的光芒遮蔽了。當我們回顧 TUI 的技術演進,會發現一條清晰的螺旋上升曲線:

  • 1970s-1980s:實體終端機(VT100)時代,輸出是硬編碼的字元流,互動是單向的。
  • 1990s-2000s:curses 函式庫讓 C 程式擁有了終端機內的視窗管理能力,但 API 原始且平台相關。
  • 2010s:blessed 和 blessed-contrib 將 Node.js 帶入了 TUI 時代,但本質上仍是命令式程式設計。
  • 2020s:Ink、Ratatui 等現代 TUI 框架引入了宣告式元件模型,將 React/Vue 的程式設計範式帶入終端機。

2025 年至 2026 年,AI 程式設計助理的爆發將 TUI 推到了歷史前台。OpenAI Codex CLI、Google Gemini CLI、Anthropic Claude Code 和開源社群專案 Aider,四個最具影響力的 AI 程式設計工具不約而同地選擇了終端機作為主要互動介面。但它們的技術路線卻呈現出驚人的分化——這種分化恰恰揭示了 TUI 架構演進的深層邏輯。

上圖呈現了目前 AI 程式設計助理 TUI 的兩大技術陣營。左側的宣告式陣營(Claude Code、Gemini CLI)選擇將 React 元件模型移植到終端機;右側的命令式/原生陣營(Codex CLI、Aider)則直接使用各語言生態的原生 TUI 函式庫。這種分化不是偶然的偏好,而是對「TUI 應該是什麼」這一根本問題的不同回答。

Claude Code:自研 Ink 與 React 元件模型的終端化

Claude Code 的終端渲染引擎並非使用第三方 Ink 函式庫,而是在 src/ink/ 目錄下自研了一套完整的終端渲染系統。這套系統的核心是一個面向終端機的 React Reconciler——這不是修辭性的比喻,而是嚴格意義上的技術實作。

要理解這一點,我們需要回到 React 的架構本質。React 的核心並不是 DOM 操作,而是一個抽象的元件協調層(Reconciliation Layer)。自 React 16 引入 Fiber 架構以來,React 的渲染流程被清楚地劃分為兩個獨立階段:

  1. 協調階段(Reconciliation Phase):比較新舊虛擬樹,計算最小變更集。此階段可中斷、可恢復,支援優先級調度。
  2. 提交階段(Commit Phase):將協調結果同步套用到宿主環境。此階段不可中斷,確保視圖一致性。

React 透過 Host Config 介面將這兩個階段與具體的渲染目標解耦。React DOM、React Native、React Three Fiber,以及 Claude Code 的 Ink,都是這個介面的不同實作。

Claude Code 的 src/ink/reconciler.ts 實作了完整的 Host Config:

typescript 体验AI代码助手 代码解读复制代码// 宿主節點建立:將 React 元件映射到終端機 DOM 節點
export const createInstance = (
  type: string,
  _props: Props,
  _root: FiberRoot,
  _hostContext: HostContext,
  _internalHandle: OpaqueHandle,
): DOMElement => {
  const node = createNode(type as ElementNames);
  node.internalHandle = _internalHandle;
  return node;
};

// 節點屬性的增刪改查
export const prepareUpdate = (
  _instance: DOMElement,
  _type: string,
  oldProps: Props,
  newProps: Props,
): null | Props => {
  const diff = diffProperties(oldProps, newProps);
  if (!diff) return null;
  return diff;
};

export const commitUpdate = (
  node: DOMElement,
  updatePayload: Props,
  _type: string,
  _oldProps: Props,
  _newProps: Props,
  _internalHandle: OpaqueHandle,
): void => {
  for (const [key, value] of Object.entries(updatePayload)) {
    if (key.startsWith('on')) {
      // 事件處理器的獨立儲存,避免屬性變更觸發髒檢測
      if (!node._eventHandlers) node._eventHandlers = {};
      node._eventHandlers[key] = value;
    } else {
      setAttribute(node, key, value as DOMNodeAttribute);
    }
  }
};

這套 Host Config 的精妙之處在於:它完全遵循 React 的架構契約,但將「宿主環境」從瀏覽器 DOM 替換為了終端機字元矩陣。createNode 函數建立的並非 HTMLDivElement,而是自訂的 DOMElement——其節點類型包括 ink-rootink-boxink-textink-linkink-virtual-textink-raw-ansiink-progress(出處:src/ink/dom.ts)。

這些節點構成了一棵終端機 DOM 樹,它們擁有與瀏覽器 DOM 節點類似的屬性結構:parentNodechildNodesattributesstyledirty 標記位。但它們不操作像素,而是操作字元單元格。每個 ink-box 對應一個 Flex 容器,每個 ink-text 對應一段文字內容——這與瀏覽器中的 divspan 語義完全平行。

更為關鍵的是 Yoga Layout 的整合。Claude Code 的 createLayoutNode()(出處:src/ink/layout/engine.ts)將 Facebook 的 Yoga 佈局引擎嵌入到終端機環境中:

typescript 体验AI代码助手 代码解读复制代码// ink-text 節點被賦予自訂測量函式
if (nodeName === 'ink-text') {
  node.yogaNode?.setMeasureFunc(measureTextNode.bind(null, node));
}

Yoga 是一個跨平台的 Flexbox 佈局引擎,它在 React Native 中負責將 CSS 樣式轉換為原生視圖的 frame。在 Claude Code 中,Yoga 負責將 Flexbox 約束(flexDirectionjustifyContentalignItemspaddingmargin)轉換為終端機字元格中的精確座標。這意味著:前端工程師在 Claude Code 中編寫終端機 UI 時,使用的佈局心智模型與編寫 CSS 完全一致。

上圖展示了 React Reconciler 作為通用協調抽象的架構本質。無論宿主環境是瀏覽器 DOM、行動原生視圖,還是終端機字元矩陣,Fiber 協調層的演算法(diff、優先級調度、並發中斷)都完全一致。Claude Code 的 Ink 不是「簡化版的 React DOM」,而是 React 多目標渲染能力的完整且專業的實作

OpenAI Codex CLI:Rust + Ratatui 的效能優先路線

與 Claude Code 的宣告式路線形成鮮明對比的是,OpenAI Codex CLI 在 2025 年經歷了一次從 TypeScript 到 Rust 的重寫。這一決策的官方理由被概括為「四大支柱」:

  1. 零依賴發佈:Rust 編譯為單一原生二進位檔,使用者無需安裝 Node.js 執行環境;而 Claude Code 的 npm 套件依賴 Node.js/Bun 環境。
  2. 原生安全:Rust 的型別系統可以在編譯期消除大量執行時錯誤;JavaScript 的沙箱限制使其難以繫結作業系統級安全機制(如 Linux seccomp)。
  3. 極致效能:Rust 無垃圾回收(GC)開銷,啟動速度約為 10ms,而 TypeScript 方案約為 100ms;記憶體佔用也顯著更低。
  4. 可擴充性:Rust 的 Wire Protocol 設計允許任何語言撰寫擴充,而非侷限於 Node.js 生態。

Codex CLI 的 TUI 採用 Ratatui——一個在 Rust 生態中迅速崛起的 TUI 函式庫。Ratatui 的架構與 Ink 截然不同:它不提供 React 式的宣告式元件模型,而是採用即時模式 UI(Immediate Mode UI)的渲染範式。在 Ratatui 中,開發者每一幀都重新建構整個 UI 樹,框架負責高效的差異更新。這與遊戲引擎中常見的 Dear ImGui 的哲學一致。

這兩種路線的差異不只是語言選擇,更是抽象層級的根本分歧。Claude Code 的 Ink 抽象層級更高:開發者撰寫 JSX,框架處理協調、佈局和渲染;Codex CLI 的 Ratatui 抽象層級更低:開發者直接操作緩衝區,對每一幀的像素(字元)有精確控制。這帶來了一個有趣的權衡:

  • Claude Code 路線:開發者體驗(DX)極佳,前端工程師可以幾乎零學習成本上手;但執行時依賴 Node.js/Bun,啟動延遲較高。
  • Codex CLI 路線:執行時效能極致,發佈輕量;但 Rust 的學習曲線陡峭,UI 程式碼更接近底層圖形程式設計而非前端開發。

Google Gemini CLI:第三方 Ink 的開放生態路線

Google 的 Gemini CLI 選擇了與 Claude Code 相同的技術棧——TypeScript + React + Ink——但關鍵差異在於:Gemini CLI 使用的是第三方 Ink 函式庫(vadimdemedes/ink),而非自研。這一選擇體現了 Google 的開放生態哲學:善用社群成熟方案,專注業務邏輯而非基礎設施。

然而,從架構深度的視角來看,使用第三方 Ink 意味著失去了對渲染管線的完全控制。Claude Code 的自研 Ink 可以實現社群函式庫無法提供的深度優化:

  • 物件池化CharPoolHyperlinkPoolStylePool 的跨幀重用(出處:src/ink/screen.ts),將字串配置的開銷降至接近零。
  • 幀循環節流FRAME_INTERVAL_MS 精確控制渲染頻率,避免 CPU 空轉。
  • 雙緩衝螢幕(Double Buffering)frontFramebackFrame 的交替渲染(出處:src/ink/ink.tsx),消除閃爍。
  • 佈局變更偵測didLayoutShift() 標記位(出處:src/ink/render-node-to-output.ts),在佈局未變更時啟用 O(1) 的差異傳輸。
  • 終端機硬體捲動優化SCROLL_HINT 與 DECSTBM 序列(出處:src/ink/render-node-to-output.ts),利用終端機原生捲動能力取代全螢幕重繪。

這些優化不是錦上添花,而是將終端機 UI 的渲染延遲從「可感知」降低到「不可感知」的關鍵。在 AI 程式設計助理高頻互動的場景中(每秒數次的串流 Token 輸出、工具執行狀態的即時更新),渲染管線的每一毫秒都直接影響使用者體驗。

Aider:Python Rich/Textual 的多模型相容路線

Aider 作為社群驅動的開源專案,選擇了 Python 生態中的 RichTextual 函式庫。這一選擇由 Aider 的核心定位決定:它不是某個大模型廠商的官方工具,而是一個多模型相容的程式設計助理(支援 OpenAI、Anthropic、Google、Ollama 等數十個模型供應商)。

Rich 提供了精美的表格、面板、進度條和 Markdown 渲染,Textual 在此基礎上增加了事件迴圈和元件系統。但與 Ink 或 Ratatui 相比,Textual 的架構更接近傳統 GUI 框架:它使用 CSS 子集進行樣式定義,採用非同步事件迴圈處理互動,支援滑鼠和鍵盤輸入的完整抽象。

Aider 的技術路線提醒我們一個重要事實:TUI 不是單一的技術範式,而是一個光譜。 從底層的 ANSI 跳脫序列操作(printf "\033[31mRed\033[0m"),到中層的佈局框架(curses、blessed),再到高層的宣告式元件系統(Ink、Textual),再到原生的即時模式渲染(Ratatui、Dear ImGui)——每個層級都有其適用場景。

上圖展示了 TUI 技術的五個抽象層級。目前的主流工具分布在 Level 2 到 Level 4 之間,而 Level 5——AI-Native TUI——仍然是一片待開墾的荒地。這正是我們需要深入探討的:TUI 的未來將走向何方?

為什麼是終端機?上下文密度、鍵盤效率與心流狀態

理解四種技術路線的差異後,我們需要回到「人」的層面:為什麼這些 AI 工具不約而同地選擇了終端機?

上下文密度:資訊熵的最大化

終端機螢幕上的每一個字元都是資訊載體。一個 80×24 的標準終端機視窗可以顯示 1920 個字元。如果每個字元平均攜帶 5 bit 的資訊(26 個字母 + 符號),那麼一個終端機螢幕的理論資訊容量約為 9600 bit。而一個 1920×1080 的 GUI 視窗,如果大部分區域被空白、邊距、陰影和裝飾性元素佔據,其有效資訊密度可能遠低於終端機。

這種高密度帶來的好處是認知連續性。當我們在終端機中查看一個目錄結構時,ls -la 的輸出直接呈現了我們需要的全部中繼資料:權限、擁有者、大小、修改時間。而在 GUI 檔案管理器中,我們需要:把滑鼠移到檔案上 → 等待懸浮提示或按右鍵看屬性 → 在彈出的對話框中讀取資訊。資訊取得的路徑被拉長了。

在 Claude Code 中,這種高密度被發揮到了極致。當我們在終端機上打出「分析目前專案的相依關係」時,Claude 可以在終端機中直接輸出結構化的分析結果,同時保留我們之前的命令歷史和檔案上下文。這種垂直資訊流(scrolling history)是終端機獨有的優勢——GUI 的面板切換是水平的空間消耗,而終端機的捲動是垂直的時間累積。

用資訊論的術語來說:終端機的 信噪比(SNR) 天然高於 GUI。每一個 ANSI 跳脫序列都有其明確的語義目的(設定顏色、移動游標、清除螢幕),而 GUI 中的每一個像素可能服務於資訊傳達、視覺層級、品牌識別或純粹的裝飾。在開發者場景下,傳達資訊是介面的首要目的,終端機的極簡性反而成為了一種優勢。

鍵盤效率:輸入頻寬的最大化

人腦思考的速度遠遠快於手部操作的速度。一個熟練的程式設計師每分鐘可以思考數十個邏輯步驟,但打字速度通常在 60-100(字/分鐘)之間。GUI 的滑鼠操作將這個瓶頸進一步收窄:把滑鼠移到螢幕角落的平均耗時約為 1.5 秒,而我們在 VS Code 中按 Ctrl+Shift+P 打開命令面板的耗時約為 0.3 秒。

終端機的命令列介面本質上是一個無限寬度的命令空間。透過 Shell 的補完、歷史記錄、別名和腳本,熟練使用者可以用極少的按鍵表達複雜的意圖。這種效率在前端開發中尤為重要:當我們需要執行 npm run build,然後檢查 dist/ 目錄,再比較 Git 差異時,終端機允許我們將這些操作串聯成一行管道命令。

Claude Code 的 Vim 模式(src/components/VimTextInput.tsx)和快捷鍵系統(src/keybindings/)進一步放大了這種效率。它的 TextInput 元件不只支援一般輸入,還整合了命令歷史的方向鍵導覽(useArrowKeyHistory.tsx)、自動完成(useTypeahead.tsx)和全局搜尋(useGlobalKeybindings.tsx)。這些在前端 Web 應用中常見的互動模式,被精準地移植到了終端機環境中。

寫到這裡的時候,我發一個很有意思的對比是:VS Code 的快捷鍵系統有數百個組合鍵,但是我通常也只是掌握其中的 10-20 個常用快捷鍵,這些快捷鍵就能讓我用 VS Code 特別順;而終端機的 Shell 允許我定義任意數量的別名和函式,所以有時候,我寧願使用 Shell 去處理一些事情,但是在別人看來,我好像是在裝逼。其實仔細想,這個過程是 每個使用者都在持續累積個人化命令詞彙的過程。這種累積不是學習成本,而是一種 複利式的效率投資 ——越早開始,收益越大。

心流狀態:認知負荷的最小化

心理學家米哈里·契克森米哈伊提出的「心流」 Flow 理論指出,最優體驗發生在「挑戰與技能平衡」的狀態中。GUI 的多面板設計雖然功能強大,但頻繁的任務切換會破壞心流狀態。

終端機的單視窗、全鍵盤互動模式天然更適合進入心流。當我們沉浸在寫程式中時,終端機成為思維的直接延伸——我們輸入命令,系統回饋結果,我們根據回饋調整下一步操作。這種 緊湊回饋迴圈 是心流狀態的核心支撐。

AI 程式設計助理的加入進一步強化了這個迴圈。在 Claude Code 中,回饋迴圈變成了:我們輸入意圖 → AI 理解意圖 → AI 執行工具呼叫 → 終端機展示執行結果 → 我們確認或修正。整個迴圈發生在同一個上下文視窗中,沒有面板切換的認知稅。

上圖展示了 Claude Code 中開發者與 AI 的互動迴圈。關鍵洞察在於:所有互動都發生在終端機的同一個字元矩陣中,沒有彈窗、沒有面板切換、沒有模態對話框打斷注意力流。這在前端工程中有直接對應——我們追求的 「無縫使用者體驗」 ,在終端機中以最純粹的形式實現了。

更微妙的是終端機的不透明性對心流的保護作用。GUI 的通知系統(桌面通知、Badge 數字、閃爍圖示)是持續的外部干擾源;而終端機是全螢幕或半螢幕的獨占介面,天然屏蔽了作業系統的通知干擾。在終端機中工作時,我們進入了一個受保護的認知空間——這正是深度工作(Deep Work)所要求的條件。

面向終端機的 React Reconciler

在前面的討論中,我們多次提到 Claude Code 實現了「面向終端機的 React Reconciler」。讓我們來進一步看看這件有意思的事情。

React Reconciler

React 16 引入的 Fiber 架構將渲染流程解耦為兩個獨立階段,並透過 Host Config 介面暴露給具體的渲染目標。React 官方提供了 react-reconciler 套件,其中定義了 Host Config 必須實作的 20+ 個函式。這些函式構成了 React 與宿主環境之間的「契約」:

Host Config 函式語意React DOM 實作Claude Code Ink 實作createInstance建立宿主節點document.createElement``createNode(type)``createTextInstance建立文字節點document.createTextNode``createNode('#text')``appendInitialChild追加子節點parent.appendChild``appendChildNode``removeChild移除子節點parent.removeChild``removeChildNode``insertBefore插入子節點parent.insertBefore``insertBeforeNode``prepareUpdate計算屬性差異比較 DOM 屬性diffProperties``commitUpdate套用屬性更新setAttribute setAttribute``commitTextUpdate更新文字內容textNode.data``setTextNodeValue``finalizeInitialChildren初始化完成觸發資源載入計算初始佈局getRootHostContext取得根上下文document終端機尺寸getChildHostContext取得子上下文命名空間繼承父層shouldSetTextContent是否直接設定文字特定標籤優化節點類型判斷resetTextContent重置文字內容清空子節點清空文字值clearContainer清空容器innerHTML = ''重置根節點這張對比表揭示了 Ink 作為 Host Config 實作的完備性。Claude Code 的 reconciler.ts 並不是一個簡化版或玩具實作,而是嚴格遵循 React 官方契約的生產級實作。它支援 React 的全部核心特性:Hooks(useState、useEffect、useMemo、useCallback)、Context、Refs、Suspense、並發模式(Concurrent Mode)——這些都不是 Ink 函式庫(第三方)提供的額外功能,而是 React 核心協調層天然具備的能力,只要 Host Config 正確實作,它們就會自動生效。

從 JSX 到 ANSI 的完整渲染管線

為了更深刻地理解 Ink 的渲染機制,我們可以嘗試追蹤一幀的完整渲染管線。從開發者的 JSX 程式碼到最終輸出到終端機的 ANSI 跳脫序列,中間經歷了哪些階段?

上圖展示了從 JSX 到 ANSI 的五階段渲染管線。讓我們深入每個階段的工程細節。

Phase 1:JSX 編譯——與普通 React 應用無異。Babel 或 TypeScript 將 JSX 轉換為 React.createElement 呼叫。Claude Code 的 tsconfig.json 中配置了 "jsx": "react-jsx",因此實際生成的是 _jsx(Box, {...}) 呼叫。這一步的產物是一個巢狀的 JavaScript 物件樹,也就是「虛擬 DOM」。

Phase 2:Fiber 協調——這是 React 的核心。當狀態變更(如 setState 或新的串流 Token 到達)觸發重新渲染時,React 的 Fiber 協調器會遍歷虛擬樹,比較新舊兩版,計算最小變更集。這個過程是可中斷的——如果終端機視窗 resize 事件發生,協調器可以暫停目前的 diff 工作,優先處理高優先級的更新。這與瀏覽器中的 Concurrent Mode 完全一致。

Phase 3:佈局計算——這是 Ink 與 React DOM 差異最大的階段。在瀏覽器中,佈局計算由瀏覽器引擎(Blink/WebKit/Gecko)完成,涉及 CSS 盒模型、浮動、定位、堆疊上下文等複雜規則;而在 Ink 中,佈局計算由 Yoga 完成,只處理 Flexbox 約束。

Claude Code 的 renderer.ts(出處:src/ink/renderer.ts)中,每一幀渲染首先呼叫 Yoga 的 calculateLayout()

typescript 体验AI代码助手 代码解读复制代码node.yogaNode?.calculateLayout(
  terminalWidth,   // 可用寬度
  terminalRows,    // 可用高度
  Direction.LTR    // 書寫方向
);

Yoga 將 Flexbox 約束(flexDirectionjustifyContentalignItemsflexWrappaddingmarginborderwidthheight 等)轉換為每個節點的精確位置和尺寸。這些數值以 終端機單元格 為單位——例如,一個寬度為 40 的節點表示占據 40 個字元寬度。這與瀏覽器中以 像素 為單位的佈局計算形成了有趣的平行:兩者都是將抽象約束轉換為具體座標,只是度量單位不同。

Phase 4:像素(字元)渲染——這是 Ink 最具工程巧思的階段。renderNodeToOutput 函式(出處:src/ink/render-node-to-output.ts)遞迴遍歷 Yoga 計算後的節點樹,將每個節點的內容寫入一個二維的字元矩陣(Screen)。

Screen 的資料結構(出處:src/ink/screen.ts)被設計為極致高效:

typescript 体验AI代码助手 代码解读复制代码// 字元單元格:32 位元打包字
//  bits 0-15  : charId(字元池索引)
//  bits 16-27 : styleId(樣式池索引)
//  bits 28-30 : width(單元格寬度:0=空,1=普通,2=寬字元)
//  bit 31     : 保留

每個字元單元格被壓縮為一個 32 位元整數。charId 指向一個全局共享的 CharPool——透過字串駐留(interning)消除重複字元的記憶體開銷。styleId 指向一個 StylePool,其中每個樣式是 ANSI 跳脫序列的陣列。這種位元打包(bit packing)設計使得螢幕緩衝區的記憶體佔用降至最低:一個 80×24 的終端機螢幕只需要 80×24×4 = 7680 位元組。

更為精妙的是樣式池的設計。StylePool.intern() 函式(出處:src/ink/screen.ts)不只將 ANSI 代碼陣列雜湊為唯一 ID,還在 ID 的最低位(bit 0)編碼了「該樣式是否在空格上可見」的資訊:

typescript 体验AI代码助手 代码解读复制代码id = (rawId << 1) | (styles.length > 0 && hasVisibleSpaceEffect(styles) ? 1 : 0)

這意味著渲染器在遍歷螢幕時,可以透過簡單的位元遮罩檢查(styleId & 1)來判斷一個空格字元是否需要輸出——如果樣式包含背景色、反相或底線,即使字元是空格也必須渲染;否則空格可以被跳過以減少 ANSI 輸出。這是將執行時判斷轉化為編譯期位元運算的經典效能優化。

Phase 5:差異輸出——這是 Ink 的「最後一哩」。Ink 不每幀都輸出完整的螢幕內容,而是比較目前幀與前一幀的 Screen 緩衝區,只輸出發生變化的單元格。這個差異演算法(出處:src/ink/screen.ts 中的 blitRegionsetCellAt)需要處理:

  • 游標移動優化:如果變更區域連續,使用 \033[<n>C(游標右移)而非重複定位。
  • 樣式過渡快取:StylePool.transition(fromId, toId) 預先計算並快取任意兩種樣式之間的 ANSI 跳脫序列差值。
  • 行內差異:在同一行內,只重繪變更的欄位範圍,而非整行。
  • 全螢幕捲動優化:當 ScrollBoxscrollTop 變化時,使用 DECSTBM(設定捲動區域)和 SU/SD(上/下捲動)硬體指令,讓終端機模擬器在 GPU 層面完成捲動,而非逐行重繪。

這種差異輸出機制使得 Ink 在渲染靜態內容時幾乎零開銷——只有動態變化的部分(如閃爍的游標、旋轉的 spinner、串流輸出的文字)會觸發實際的 ANSI 輸出。

與 React Native 的深度平行

理解 Ink 的渲染管線後,一個自然的問題是:它與 React Native 有多相似?答案是:它們共享幾乎完全相同的架構模式,只是宿主環境不同。

架構維度React NativeClaude Code Ink協調層React FiberReact Fiber佈局引擎YogaYoga節點抽象UIView / View``DOMElement(ink-box/text/link)樣式系統CSS 子集(Flexbox)CSS 子集(Flexbox)渲染目標GPU 紋理/原生視圖字元矩陣/ANSI 序列差異演算法原生視圖的屬性 diff字元矩陣的單元格 diff度量單位密度無關像素(dp)終端機單元格(字元)執行緒模型JS 執行緒 + UI 執行緒單執行緒(Node.js/Bun 事件迴圈)手勢處理觸控事件系統鍵盤事件 + 滑鼠追蹤這張對比表揭示了 Ink 作為React Native 的終端機變體的深層結構。兩者都使用 Yoga 進行 Flexbox 佈局,都使用 React Fiber 進行協調,都將樣式約束轉換為具體座標——唯一的差別在於「像素」的定義:React Native 的像素是螢幕上的物理點,而 Ink 的像素是終端機中的字元單元。

這種平行性具有重要的工程意義:它意味著前端工程師為 React Native 累積的技能(Flexbox 佈局、元件生命週期、Hooks 心智模型)可以直接遷移到 TUI 開發。Claude Code 的工程團隊顯然就是這樣做的——src/components/ 目錄中的大量元件(BoxTextSpinnerMarkdownTextInput)與 React Native 的元件命名和 API 設計幾乎完全一致。

更進一步,這種架構平行性揭示了 React 作為跨平台 UI 抽象的真正潛力。React 最初被設計為瀏覽器 UI 函式庫,但隨著 Reconciler 架構的成熟,它已經演變為一種通用的介面描述語言。只要某個環境能夠提供:

  1. 一個可以被建立、刪除、修改的樹狀節點系統;
  2. 一個將抽象約束(如 Flexbox)轉換為具體座標的佈局引擎;
  3. 一個將節點內容轉換為環境特定輸出(像素、字元、3D 頂點)的渲染層;

React 就可以在該環境中執行。Claude Code 的 Ink 證明了:終端機環境完全滿足這三個條件。這也解釋了為什麼 React Three Fiber(3D 渲染)、React PDF(PDF 文件生成)、React Terminal(終端機 UI)等專案都能在同一個 React 核心之上繁榮生長。

AI 時代終端機的重新定義:從傳聲筒到協作者

傳統上,終端機被定義為「命令列直譯器的輸入輸出裝置」——一個被動的人機介面。但在 AI 時代,這個定義需要被徹底重寫。

四種 AI 程式設計助理的終端機介面展示了三種不同的 TUI 狀態

方式一:滾動回退友好型 TUI(Claude Code / Gemini CLI)

Claude Code 和 Gemini CLI 的 TUI 採用了增量式輸出模型:它們將內容追加到終端機的滾動回退緩衝區中,只在必要時(如動畫 spinner、輸入框)使用游標定位和清除操作進行局部重繪。這種模式的優點是:

  • 保留了終端機的原生能力:使用者可以使用終端機模擬器自帶的搜尋(Cmd+F)、捲動、選取、複製功能。
  • 與 Unix 哲學相容:輸出可以被管道傳遞給其他命令(claude-code | grep "error"),可以被重新導向到檔案。
  • 崩潰安全:即使 TUI 程式異常退出,之前的輸出仍然保留在滾動回退中。

但這種模式也有它的限制:它沒辦法建立複雜的重疊 UI(如模態對話框覆蓋在主內容之上),因為終端機的字元矩陣是平面的,沒有 z-index 的概念。Claude Code 透過巧妙的交替螢幕緩衝區(Alternate Screen Buffer)切換來解決這個問題:當需要顯示對話框時,切換到 alt-screen 進行全螢幕渲染;關閉對話框後,切回主螢幕恢復之前的捲動狀態。

方式二:全螢幕獨占型 TUI(Codex CLI / Ratatui 應用)

Codex CLI 採用 Ratatui 的全螢幕獨占模式:它完全接管終端機視口,將其當作一個像素(字元)緩衝區。這種模式的優點是:

  • 完全的控制權:開發者可以精確控制每一個字元的位置、顏色、樣式,實現複雜的佈局(如側邊欄 + 主內容區 + 底部狀態列的三欄佈局)。
  • 一致的視覺體驗:不受終端機模擬器的字型、配色方案影響,UI 外觀完全由應用控制。
  • 更接近 GUI 的互動密度:可以實現分頁、樹狀控制項、表格、圖表等複雜 UI 元素。

但代價同樣明顯:

  • 失去滾動回退:所有內容都在 alt-screen 中,使用者無法向上捲動查看歷史輸出。
  • 失去原生搜尋:必須自行實作搜尋功能,且通常不如終端機模擬器的搜尋強大。
  • 滑鼠捲動體驗差:需要自行將滑鼠滾輪事件映射為內部捲動邏輯,與終端機模擬器的原生捲動行為有微妙差異。

方式三:混合漸進型 TUI(Aider / Rich)

Aider 的 Rich/Textual 採用了一種漸進增強的策略:基礎輸出是一般的串流文字(保留滾動回退),在需要時插入精美的面板、表格和進度條。這些面板是「有狀態的」——它們可以在後續輸出中被更新(如進度條從 0% 到 100%),但最終都轉化為終端機輸出流的一部分。

這種模式的優點是漸進性:使用者即使沒有安裝 Aider,只是查看其輸出日誌,也能獲得完整的可讀資訊;而使用 Aider 互動時,又能獲得豐富的視覺化回饋。

上圖展示了三種 TUI 的方式。目前主流 AI 程式設計助理使用不同的 TUI 方式,而這種分布反映了它們對產品定位的不同理解:

  • Claude Code 選擇滾動回退友好型,因為它將自己定位為「開發者的對話夥伴」——對話需要歷史記錄的可追溯性。
  • Codex CLI 選擇全螢幕獨占型,因為它追求「IDE 的終端機化」——將 GUI IDE 的複雜介面壓縮到終端機中。
  • Aider 選擇混合漸進型,因為它服務於「多工具整合」的場景——輸出需要被其他工具消費。

TUI 的未來發展方向

站在 2026 年的視角,我們可以清楚地看到 TUI 正在經歷的五個深層變革。這些變革不只是技術演進,更是人機互動範式的重新想像,也是我自己對前端的一些想像(不一定對,因為這只是基於我的經驗和思考得來的)。

方向一:從「宣告式 TUI」到「智慧感知 TUI」

目前的 TUI 框架(Ink、Ratatui、Textual)都是被動渲染的:開發者描述 UI 應該長什麼樣子,框架負責將其繪製到終端機。未來的 TUI 將是智慧感知的——它能夠理解終端機內容的語義,並據此調整渲染策略。

一個具體的場景是:當 AI 助理輸出一段程式碼時,智慧 TUI 可以自動偵測程式語言類型,即時呼叫語法高亮服務,並將高亮後的 ANSI 序列注入輸出流。這不需要開發者預先設定「這段文字是 Python」,而是由 TUI 執行時的語義分析層自動完成。

更激進的想像是:TUI 可以作為模型的「視覺皮層」。目前的大型語言模型(LLM)是「盲」的——它們只能看到文字輸入,無法感知終端機中正在渲染的視覺結構。但終端機的字元矩陣本質上是一個離散的二維語義空間:每個字元有其座標、樣式、所屬的面板或元件。如果 TUI 框架能夠提供一個結構化終端機描述協議(例如「目前螢幕包含:一個頂部標題列('Project: my-app')、一個主內容區(一段 Python 程式碼,第 3 行有錯誤底線)、一個底部狀態列('3 tasks running')」),LLM 就可以基於這種結構化的視覺理解做出更精準的互動決策。

這正是 Gemini CLI 在 2025 年 10 月引入PTY 支援的方向。PTY(Pseudo Terminal)不只提供了真實的終端機會話,還允許 AI 代理「看到」完整的終端機狀態——包括游標位置、目前行內容、螢幕尺寸。這是從「文字流互動」到「視覺場域互動」的關鍵一步。

方向二:從「單色文字」到「多模態終端機」

傳統的 TUI 被限制在「字元 + 16 色 + 粗體/底線」的表達能力中。但現代終端機模擬器(iTerm2、Ghostty、Kitty、WezTerm、Windows Terminal)早已突破了這些限制:

  • 24 位元真彩色:支援 1670 萬色的 RGB 顏色。
  • 圖片協定:iTerm2 的 Inline Images Protocol、Kitty 的 Graphics Protocol 允許在終端機中直接渲染 PNG/JPEG 圖片。
  • 超連結:OSC 8 協定允許文字攜帶可點擊的 URL 中繼資料。
  • Unicode 15:支援表情符號、數學符號、方塊元素、框線字元。
  • 可變字型:Kitty 等終端機支援字型連字(ligatures),使得 => 自動渲染為箭頭符號。

這些能力的聚合意味著:終端機正在成為「像素化的圖形介面」。Claude Code 的 src/ink/screen.ts 中已經對超連結(OSC 8)和 Unicode 寬字元(CJK 雙寬、emoji)進行了精細處理,但它仍然停留在「字元作為基本單元」的模型中。

未來的 TUI 可能會引入混合渲染模式:文字內容使用傳統的字元格渲染,而圖片、圖表、數學公式使用終端機圖片協定直接嵌入。這會模糊 TUI 與 GUI 的邊界——但關鍵差異仍然存在:TUI 的內容是結構化可存取的(螢幕閱讀器可以讀取每個字元),而 GUI 的像素內容對於輔助技術來說往往是「黑盒」。

方向三:WebAssembly 化的 TUI 執行時

目前 TUI 框架與程式語言的綁定很緊:Ink 綁定 JavaScript/TypeScript,Ratatui 綁定 Rust,Textual 綁定 Python。但 WebAssembly(Wasm)正在創造一種新的可能性:語言無關的 TUI 執行時

想像一個由 Wasm 模組構成的 TUI 引擎:核心渲染管線(協調、佈局、差異輸出)編譯為 Wasm,以接近原生的速度執行;而 UI 元件可以用任何支援 Wasm 的語言編寫——TypeScript、Rust、Python、Go、Zig。元件透過標準化的介面(如 WASI 元件模型)與執行時通訊。

最近兩年也能看到一些苗頭:Ratzilla(Ratatui 的 WebAssembly 瀏覽器版本)允許 Rust TUI 應用在瀏覽器中執行;Textual 也實驗性地支援了 Wasm 目標。這種跨平台能力意味著:為終端機撰寫的 TUI 應用,可以幾乎零成本地部署到 Web 環境——因為兩者的渲染目標(字元格)是統一的。

方向四:TUI 標準化協定與互操作性

目前的 TUI 生態是碎片化的:每個框架都有自己的元件 API、事件系統和樣式語法。這種情況和 2010 年前的前端生態驚人地相似——當時 jQuery、Dojo、Prototype、YUI 等函式庫各自為政,直到 Web Components 標準和 React/Vue/Angular 的崛起才逐漸收斂。

TUI 領域正在呼喚類似的標準化。一個可能的方向是終端機元件協定(Terminal Component Protocol, TCP):類似於 Web 的 DOM + CSS + JS 三劍客,定義一套跨框架的終端機元件標準:

  • 終端機 DOM(TDOM):標準化的節點類型(t-boxt-textt-imaget-link)和屬性集。
  • 終端機樣式表(TCSS):標準化的樣式屬性(layoutcolorborderscroll)和選擇器語法。
  • 終端機事件系統(TEvents):標準化的事件類型(keymouseresizefocus)和冒泡機制。

這種標準化不會消滅框架競爭,而是將競爭提升到更高的抽象層級——就像 Web Standards 沒有消滅 React 和 Vue,但為它們提供了共同的根基。MCP(Model Context Protocol)在 AI 工具互操作性方面的成功,為 TUI 標準化提供了一個可參照的範例。

方向五:AI-Native TUI——從「人類設計介面」到「模型生成介面」

最終極的變革方向是:TUI 不再由人類開發者手工設計,而是由 AI 模型根據任務上下文動態生成。

目前的 Claude Code、Codex CLI 等工具,其 TUI 是固定的——無論使用者執行什麼任務,介面結構(輸入框、訊息列表、狀態列)都保持不變。但 AI-Native TUI 會根據當前任務的性質即時重組介面

  • 當使用者在除錯程式碼時,TUI 自動生成一個帶有行號、中斷點標記和變數監看面板的程式碼檢視器。
  • 當使用者在分析效能時,TUI 自動生成一個帶有進度條、即時圖表和摘要統計的面板佈局。
  • 當使用者在撰寫文件時,TUI 自動生成一個帶有 Markdown 預覽和即時同步的編輯介面。

這種「生成式 UI」(Generative UI)的概念在 GUI 領域已有探索(如 Vercel 的 v0),但在 TUI 領域可能更有優勢:因為 TUI 的「像素」是離散的字元單元,生成和驗證的成本遠低於 GUI 的連續像素空間。一個 LLM 可以可靠地生成「一個 3 欄的表格,帶邊框」的 TUI 描述,但生成「一個圓角陰影卡片,帶漸層背景」的 GUI 描述則容易出錯。

上圖展示了從「固定 TUI」到「動態生成 TUI」的範式轉變。這個方向的實現依賴於兩個技術前提:

  1. TUI 描述語言的標準化:模型需要一種簡潔可靠的方式來描述介面結構(如 JSON 或專門的 DSL)。
  2. TUI 執行時的安全沙箱:生成的介面描述必須在受控環境中執行,防止惡意程式碼透過 UI 注入攻擊使用者終端機。

Claude Code 的 src/skills/loadSkillsDir.ts 中已經展現了這種能力的雛形:技能以 Markdown + Frontmatter 的形式定義,Frontmatter 可以包含動態的 Shell 命令和參數替換。這本質上是一種宣告式 UI 生成的早期形態——只是目前生成的是命令序列而非介面元件。

結語:前端工程師為什麼應該重新理解終端機

作為前端工程師,我們習慣於用像素、色彩、動畫和互動回饋來思考介面。但終端機提醒我們:介面的本質不是視覺的豐富,而是資訊的精確。 一個精心設計的 TUI,其資訊傳達效率可以遠超同等面積的 GUI。

四種 AI 程式設計助理的 TUI 實踐給了我們五個具體的啟示:

第一,渲染抽象的可遷移性。 React 的宣告式元件模型可以從瀏覽器無縫遷移到終端機。Claude Code 的 src/ink/reconciler.ts 中自訂的 React Reconciler 證明了:只要提供適當的宿主環境適配,同一套程式設計範式可以在完全不同的渲染目標上工作。這對前端框架的設計有深遠影響——未來的 UI 框架可能是目標無關的(target-agnostic)。

第二,佈局引擎的通用性。 Yoga Flexbox 引擎同時服務於 React Native(行動端)、React PDF(文件生成)和 Claude Code Ink(終端機渲染)。這揭示了 Flexbox 作為一種跨平台佈局約束語言的普適價值。前端工程師投資 Flexbox 的深度理解,回報是多平台的。

第三,效能優化的層級思維。 Claude Code 的 Ink 展示了效能優化的完整層級:從位元打包的記憶體結構(32 位元單元格),到物件池化的配置策略(CharPool/StylePool),到差異演算法的輸出優化(blitRegion/DECSTBM),再到幀循環的調度策略(FRAME_INTERVAL_MS)。每一層優化都建立在前一層的基礎之上,這種分層優化的思維模式可以直接遷移到前端效能工程。

第四,互動密度的永恆追求。 無論是 Web 應用還是終端機應用,優秀的互動設計都在追求同一個目標:在使用者意圖和系統回應之間建立最短路徑。終端機透過全鍵盤、高密度、無切換的互動模式,在這個維度上達到了理論最優。

第五,技術選型的權衡藝術。 四種工具選擇了四種不同的技術路線(TypeScript+自研 Ink、Rust+Ratatui、TypeScript+第三方 Ink、Python+Rich),沒有一種是絕對正確的。技術選型永遠是場景、團隊、生態、效能、體驗五維空間中的帕累托最優解。理解這些權衡,比記住某個「正確答案」更有價值。

AI 的崛起不是 GUI 的終結,也不是 TUI 的回歸,而是兩者在更高維度上的融合。Claude Code 的終端機介面中,既有結構化文字的純粹,也有智能體協作的溫度;Codex CLI 的全螢幕介面中,既有 Rust 效能的冷峻,也有 Ratatui 渲染的精緻;Aider 的串流輸出中,既有 Python 生態的包容,也有 Rich 美學的優雅。

它們共同指向一個未來:終端機不再是傳統意義上的 TUI,而是一種以文字為基底、以智能為增強、以多模態為擴展的新型介面範式。 在這個範式中,前端工程師的宣告式程式設計思維、元件化設計方法和效能優化技巧,都將找到新的用武之地。

作為前端工程師,理解這種範式轉變,不只是為了更好地使用工具,更是為了在未來的介面設計中,做出更明智的架構選擇。畢竟,我們既是 GUI 的建造者,也應該是 TUI 的開拓者。

這篇是關於 Vibe Coding 思考的第一篇,也特別感謝 Claude Code 的程式碼外洩,讓我們看到了如何巧妙地設計和架構思想。Claude Code 程式碼外洩以來,陸陸續續看了原始碼將近一個多月,一開始我對它的程式碼和設計很困惑,邊看程式碼邊除錯邊思考,真正經歷了看山是山,看水是水;看山不是山,看水不是水;再到看山是山,看水是水的螺旋過程。歡迎關注我,關注這個專欄,這是新開的一個坑,關於 AI,關於 Vibe Coding,都在這裡


原文出處:https://juejin.cn/post/7639311759745925130


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬11   ❤️1
539
🥈
alicec
📝1   ❤️2
77
🥉
我愛JS
💬2  
7
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登