Lời nói dối về chế độ văn bản: Tại sao các TUI hiện đại lại là ác mộng đối với khả năng truy cập

03 tháng 5, 2026·8 phút đọc

Nhiều lập trình viên lầm tưởng rằng ứng dụng chạy trên terminal tự động thân thiện với người khiếm thị. Tuy nhiên, các giao diện người dùng văn bản (TUI) hiện đại sử dụng framework như Ink hay Bubble Tea lại gây ra trải nghiệm tồi tệ hơn cả giao diện đồ họa kém chất lượng do cách xử lý con trỏ và vẽ lại màn hình liên tục.

Lời nói dối về chế độ văn bản: Tại sao các TUI hiện đại lại là ác mộng đối với khả năng truy cập

Có một quan niệm sai lầm phổ biến giữa các lập trình viên có thị lực: nếu một ứng dụng chạy trong terminal, nó vốn dĩ đã dễ tiếp cận (accessible). Logic của họ cho rằng vì không có đồ họa, không có DOM phức tạp hay canvas WebGL, nội dung chỉ là văn bản ASCII thô mà trình đọc màn hình (screen reader) có thể dễ dàng phân tích.

Thực tế lại hoàn toàn khác. Hầu hết các Giao diện người dùng văn bản (Text User Interfaces - TUI) hiện đại thường thù địch với khả năng tiếp cận hơn cả những giao diện đồ họa được viết kém. Chính những công cụ được thiết kế để cải thiện trải nghiệm của nhà phát triển (DX) trong terminal — như các framework Ink (JS/React), Bubble Tea (Go), hay tcell — đang chủ động phá hủy trải nghiệm của người dùng khiếm thị.

Lỗi kiến trúc: Stream so với Grid

Để hiểu rõ sự thất bại này, chúng ta cần phân biệt hai khái niệm thường bị nhầm lẫn dưới cái tên "ứng dụng terminal": CLI và TUI.

CLI (Luồng dữ liệu): Hoạt động dựa trên mô hình đầu vào/ra tiêu chuẩn (stdin/stdout). Bạn nhập một lệnh, hệ thống thêm kết quả vào bên dưới và con trỏ di chuyển xuống. Đây là mô hình tuyến tính và theo trình tự thời gian. Đối với trình đọc màn hình, đặc biệt là các trình đọc cấp hạt nhân như Speakup trên Linux, đây là môi trường lý tưởng.

TUI (Lưới): Coi cửa sổ terminal không phải là luồng văn bản, mà là một lưới điểm ảnh 2D, trong đó mỗi ô ký tự là một điểm ảnh. Nó từ bỏ dòng chảy thời gian để lấy bố cục không gian.

Nỗi ám ảnh gemini-cli

Hãy xem một ví dụ cụ thể: gemini-cli, một công cụ được viết bằng Node.js sử dụng framework Ink. Bề ngoài, nó trông giống như một giao diện trò chuyện đơn giản. Nhưng bên dưới, Ink đang cố gắng chuyển đổi cây thành phần React vào một lưới terminal.

Khi sử dụng công cụ này với Speakup (Linux) hoặc NVDA (Windows), ứng dụng không chỉ thất bại mà còn gây nhiễu loạn.

Vì framework coi màn hình như một canvas phản ứng, mọi cập nhật đều kích hoạt việc vẽ lại. Khi AI đang "suy nghĩ", công cụ cập nhật bộ đếm thời gian hoặc biểu tượng xoay. Để làm điều này, nó di chuyển con trỏ phần cứng đến vị trí bộ đếm, ghi thời gian mới, rồi di chuyển lại.

Với người có thị lực, điều này diễn ra tức thì. Nhưng với người dùng trình đọc màn hình, đây là những gì bạn nghe thấy:

"Đang phản hồi... Thời gian trôi qua 1s... Đang phản hồi... Thời gian trôi qua 2s... [Mảnh lịch sử trò chuyện]... Đang phản hồi..."

Nó khiến trình đọc màn hình phát điên. Con trỏ dịch chuyển khắp màn hình để cập nhật trạng thái, biểu tượng xoay và lịch sử. Speakup cố gắng đọc bất cứ thứ gì nằm dưới con trỏ tại mili-giây đó. Bạn cuối cùng nghe thấy những mảnh vụn ngẫu nhiên của cuộc hội thoại lẫn lộn với cập nhật thời gian, khiến việc tập trung vào những gì bạn đang gõ trở nên bất khả thi.

Thậm chí tệ hơn, nếu bạn cố gắng dán một đoạn văn bản lỗi trên Windows vào terminal thông qua SSH, kết quả là sự cố ngay lập tức của trình đọc màn hình (NVDA) hoặc sự bất ổn lớn của hệ thống. Mỗi lần bạn gõ một ký tự, ứng dụng kích hoạt thay đổi trạng thái và cố gắng vẽ lại hoặc tính toán lại bố cục cho hàng ngàn dòng văn bản ngay lập tức.

Vòng lặp độ trễ

Hơn nữa, các framework như Ink chạy trên môi trường đơn luồng (như Node.js) chịu sự suy giảm hiệu suất nghiêm trọng khi lịch sử trò chuyện phát triển. Nếu bạn dán một khối văn bản lớn, hệ thống phải tính toán sự khác biệt (diff) cho hàng ngàn dòng.

Điều này gây ra độ trễ đầu vào. Bạn nhấn một phím và phải chờ. Bạn có thể chờ đến 10 giây chỉ để một ký tự được hiển thị lại. Hệ thống quá bận rộn tính toán cách vẽ lại màn hình nên không thể xử lý đầu vào của bạn.

Tại sao các công cụ "lão làng" lại hoạt động tốt

Các lập trình viên có thị lực thường hỏi: "Nếu TUI tệ như vậy, tại sao bạn lại dùng nano, vim hay menuconfig?"

Câu trả lời không phải là các công cụ này xử lý con trỏ hoàn hảo theo mặc định. Câu trả lời là chúng cho phép bạn ẩn con trỏ hoàn toàn.

1. Ẩn con trỏ (nano, vim): Trong các công cụ như nano hoặc vim, khả năng sử dụng phụ thuộc vào việc tắt các tính năng theo dõi vị trí con trỏ. Các công cụ cũ này thành công vì chúng cho phép bạn tắt tiếng ồn này. Bạn có thể cấu hình chúng để chặn cập nhật con trỏ hình ảnh hoặc thanh trạng thái, buộc trình đọc màn hình phải dựa vào luồng đầu vào ký tự thay vì các cập nhật tọa độ ồn ào. Các framework hiện đại hiếm khi cung cấp chế độ "không con trỏ" hay "headless"; chúng giả định con trỏ hình ảnh là thiết yếu.

2. Tập trung cột đơn (menuconfig): Các công cụ như menuconfig của nhân Linux hoạt động vì chúng áp đặt sự tập trung nghiêm ngặt vào một cột đơn. Dù có viền và tiêu đề, vùng hoạt động là một danh sách dọc. Con trỏ giữ chặt vào danh sách đó. Nó không nhảy xuống dưới cùng bên phải để cập nhật đồng hồ rồi lên trên cùng bên trái để cập nhật tiêu đề. Độ phức tạp không gian được giữ ở mức thấp đủ để trình đọc màn hình không bao giờ bị "lạc".

3. Nghệ thuật quên lãng của Vùng cuộn (Irssi): Irssi là tiêu chuẩn vàng cho việc trò chuyện dễ tiếp cận, không phải do may mắn. Irssi được xây dựng trong hơn 20 năm với một engine kết xuất tùy chỉnh sử dụng Vùng cuộn VT100. Khi một tin nhắn mới đến, nó yêu cầu trình điều khiển terminal cuộn nội dung lên và vẽ văn bản mới ở dưới cùng. Quan trọng là, nó xử lý điều này theo cách giảm thiểu sự can thiệp với dòng nhập liệu. Các framework hiện đại phớt lờ các tính năng phần cứng này để ủng hộ việc "diff" trạng thái màn hình và ghi đè ký tự, vốn tốn nhiều tài nguyên tính toán và thù địch với khả năng tiếp cận.

Sự phớt lờ của các nhà bảo trì

Google và những người bảo trì gemini-cli giả vờ quan tâm đến khả năng tiếp cận. Nếu nhìn vào kho lưu trữ, các lỗi nghiêm trọng về khả năng tiếp cận như Issue #3435 và #11305 đã bị bỏ mặc. Không có thảo luận, không có lộ trình, không có bản sửa lỗi.

Thậm chí tệ hơn là số phận của Issue #1553, vốn được cho là theo dõi các thất bại này. Nó không được giải quyết; nó bị bịt miệng. Nó bị đóng tự động bởi một bot với lý do từ chối chung chung:

"Xin chào! Như một phần của nỗ lực giữ cho danh sách công việc của chúng tôi dễ quản lý và tập trung vào các vấn đề hoạt động nhất, chúng tôi đang dọn dẹp các báo cáo cũ hơn. Có vẻ như vấn đề này không hoạt động trong một thời gian, vì vậy chúng tôi sẽ đóng nó vào lúc này."

Đóng một báo cáo về khả năng tiếp cận chỉ vì những người bảo trì không chạm vào nó trong vài tháng không phải là "dọn dẹp"; đó là che giấu bằng chứng. Nó hiệu quả nói rằng nếu một lỗi bị bỏ qua đủ lâu, nó sẽ ngừng tồn tại.

Kết luận

Nếu bạn đang xây dựng ứng dụng cho terminal và thực sự quan tâm đến khả năng tiếp cận, hãy ngừng sử dụng các framework UI khai báo coi terminal như một canvas.

Ngăn xếp TUI "hiện đại" đã tối ưu hóa cho khả năng viết mã kiểu React của nhà phát triển với chi phí là khả năng hiển thị văn bản hiệu quả của máy.

Nếu bạn không thể đảm bảo rằng ứng dụng của mình cho phép người dùng ẩn con trỏ, hoặc nếu bạn dựa vào việc vẽ lại màn hình liên tục để hiển thị các biểu tượng xoay và bộ đếm thời gian, bạn đang xây dựng một công cụ không thể tiếp cận.

Đối với người dùng khiếm thị, một luồng CLI tuyến tính "ngu ngốc" vượt trội hơn vô cùng so với một TUI "thông minh" bị lag, spam và scatter con trỏ khắp màn hình.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗