Architecture

August 16, 2026 · View on GitHub

dsh-tianshu-tui is a Cordis plugin (tui-runner) mounted on the official DeepSeek Harness. Core principle: pure presentation — it registers no prompts, tools, or context surfaces, and every piece of rendered state derives from the session event stream. The full boundary contract lives in ADAPTER.md.

Layer Overview

src/
├── index.ts                Plugin entry: declares dependencies, wires TuiRunnerConfig, mounts TuiApp
├── ui/app.ts               Core monolith (~3.6k lines): assembly, event subscriptions, key arbitration, render composition
├── ui/render.ts            Transcript rendering: messages → scrollback rows (markdown/thinking blocks/tool cards)
├── adapter/                Session-event-stream adapters (fold into display views)
│   ├── transcript.ts       Message/tool/reasoning folding (TextBlock → render rows)
│   ├── sessions.ts         Session list/restore (incl. title fold)
│   ├── send.ts / tool-view.ts / live.ts / session-title.ts
├── engine/                 Terminal rendering engine and input
│   ├── live-engine.ts      Live-region incremental redraw (ticker-driven, CPR self-heal)
│   ├── commit-engine.ts    Scrollback commits
│   ├── input-handler.ts / input-controller.ts / input-line.ts   Keyboard and input line
│   ├── stream-renderer.ts  Streaming text block writing
│   ├── overlay-engine.ts / overlay-controller.ts  Overlay lifecycle
│   ├── clipboard-image.ts / image-attach.ts / image-tool.ts / term-image.ts   Image pipeline
│   └── resize-handler.ts / write-batcher.ts / perf-monitor.ts
├── controllers/            Pending-interaction state machines
│   ├── question-controller.ts  Structured questions
│   ├── approval-controller.ts  Approvals (always-approve local state)
│   ├── btw-controller.ts       Background ask
│   └── session-manager.ts      Session management
├── format/                 Pure render functions (no I/O, all unit-testable)
│   ├── markdown.ts / diff.ts / tool-card.ts / tool-group.ts / tool-family.ts
│   ├── glance-bar.ts / top-bar.ts / prompt-footer.ts / welcome.ts
│   ├── reasoning.ts / turn-summary.ts / spinner-status.ts
│   ├── approval-card.ts / permission-diff.ts / question-related
│   ├── workflow-panel.ts / delegation-panel.ts / status-panel.ts / config-panel.ts
│   ├── pricing.ts / history-search-overlay.ts / keymap-panel.ts ...
├── render/                 Live snapshot and panel projection (live-panels / live-snapshot)
├── lsp/                    LSP diagnostics bridge (lazy server spawn; display-local cache only)
├── picker.ts               Interactive picker (issue #31)
├── command-palette.ts      Command palette
├── theme.ts / theme-palettes.ts / theme-detect.ts / theme-custom.ts   Themes
├── statusline.ts / restore-session.ts / self-update.ts / external-editor.ts
└── completion/             @ path completion

Data Flow

Session events (session/event)                    workflow/* / subagent/* / approval/request
      │                                                      │
      ▼                                                      ▼
adapter/transcript.ts (fold)                    app.ts subscription caches (workflowRuns / delegation tree / approvals)


Transcript view (messages / tools / reasoning / usage folds)

      ├── settled content → commit-engine → scrollback (main screen)
      └── in-flight content → renderLive → live region (bottom dynamic area, 120ms ticker redraw)
  • Scrollback: settled, stable content, written incrementally to the main screen.
  • Live region: in-flight tool cards, reasoning header lines, subagent run lines, question/approval cards, input rail, footer, metrics line. Row tracking is wrapping-aware; the cursor resides at the region's last line; a CPR probe self-heals foreign writes.
  • Event folds are pure functions: transcript/turn-summary/summary-state folds only read events and never write state back (testable, replayable).

Projection Layer

Some panel data arrives through the host sessionProjections bus (goal/todos/plan). When the bus is missing, local folds back it up (turn summaries, session-totals section). Wiring status: docs/projection-layer.md.

Controllers

Pending interactions are explicit state machines (not scattered across render callbacks):

  • QuestionController: question → options → settlement; overlap protection; plan-review feedback mode.
  • ApprovalController: approval card y/N/a; always-approve local short-circuit; non-current-session delegation.
  • BtwController: background-ask lifecycle (Esc folds the answer into the scrollback).
  • SessionManager: create/fork/switch/resume.

Overlay System

Full-screen overlays (command palette, keymap, history search, rewind, memory browser, picker) are managed by OverlayController: open enters the alt screen, activate a renderer, Esc/Ctrl+C closes, deferred scrollback is flushed on close, and the live region is redrawn in sync.

Themes & Terminal Adaptation

Two-stage: palette definitions (theme-palettes.ts, semantic tokens → color values + background + description) → semantic resolution (theme.ts). Auto terminal detection + 16-color degradation + ASCII degradation.

Key Design Decisions

  • Single-logical-line contract: every live-region line is one logical line; embedded newlines are normalized (stable display-width math).
  • Bounded caches: settled workflow runs capped at workflowHistoryLimit (default 50); at most 3 in-flight tool cards (LIVE_TOOL_CARD_MAX), overflow collapses.
  • Honest degradation: missing data → segment omitted (no fake 0% cache rate, no price guessing for unknown models); missing services → ⚠ warning, fails loud, never silent.
  • app.ts monolith (~3.6k lines): pending state machines are controller-based; render composition and key arbitration remain in app.ts. The C4 split (pure-function panel sections) is ongoing — most panel sections already live in format/.