| 1 | # siGit Code TUI features: tabs and thinking display |
| 2 | |
| 3 | Status: shipped, pending merge (sigit #25 and #26, 2026-07-05). Owner: product/eng. |
| 4 | Audience: internal (private repo). Scope: the **siGit Code** interactive terminal |
| 5 | UI (`src/chat.rs`, Unix-only). These two features were built from direct requests |
| 6 | rather than the parity roadmaps; this document records their specs after the |
| 7 | fact so the behavior has a source of truth alongside |
| 8 | [sigit-code-parity-roadmap.md](sigit-code-parity-roadmap.md) and |
| 9 | [sigit-code-copilot-cli-parity.md](sigit-code-copilot-cli-parity.md). |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 1. Spec: tabbed TUI (sigit #25) |
| 14 | |
| 15 | Inspired by GitHub Copilot CLI's June 2026 terminal redesign (Session, Gists, |
| 16 | Issues, and PR tabs switched with the Tab key). |
| 17 | |
| 18 | ### Behavior |
| 19 | |
| 20 | Three tabs, rendered as a bar at the top of the screen on every view: |
| 21 | |
| 22 | - **Session** (default): the chat exactly as before, messages, input, footer. |
| 23 | - **History**: the saved sessions from the session store |
| 24 | (`$SIGIT_CONFIG_DIR/sessions/*.jsonl`), listed newest first with id, coarse |
| 25 | age ("2m ago"), and message count. Up/Down selects. Enter restores the |
| 26 | selected session into the live backend, pushes a system message saying how |
| 27 | many messages were restored, and returns to the Session tab. `d` starts a |
| 28 | delete; a second `d` on the same selection confirms, any other key cancels. |
| 29 | The pending delete is keyed by session id, not list index, so a refresh can |
| 30 | never redirect the confirmation. `r` refreshes. An empty store shows "No |
| 31 | saved sessions yet." |
| 32 | - **Cloud**: siGit Code Cloud status and settings. Shows the signed-in account, |
| 33 | on-device vs remote inference, current model and engine state, the Local |
| 34 | Inference setting, the permission policy summary, and the config dir. `l` |
| 35 | toggles Local Inference, with a note that the toggle takes effect at the next |
| 36 | model selection because the running backend is not swapped. `r` refreshes. |
| 37 | |
| 38 | ### Interaction rules |
| 39 | |
| 40 | - Tab cycles Session, History, Cloud. It only cycles when the input buffer is |
| 41 | empty, so pasted text containing tabs cannot hijack navigation; on non-Session |
| 42 | tabs the input is inactive so Tab always cycles. Esc on a non-Session tab |
| 43 | returns to Session. |
| 44 | - Inference keeps streaming while the user is on another tab. Restoring history |
| 45 | sits behind the busy gate so the conversation cannot be swapped mid-turn. |
| 46 | - A tool approval prompt arriving while on another tab auto-switches to the |
| 47 | Session tab: a y/a/n prompt must never be invisible. |
| 48 | - The Cloud status fetch runs as a spawned task feeding the render loop over a |
| 49 | oneshot, because `account::status_line()` can hit the network and must not |
| 50 | freeze rendering. A dead fetch degrades to "press r to retry". |
| 51 | |
| 52 | ### Implementation notes |
| 53 | |
| 54 | Pure logic (the tab cycle, age and row formatting, `session_store::list`) lives |
| 55 | outside the Unix-only `mod tui` behind the same cfg-gate pattern as |
| 56 | `permissions::TUI_SESSION`, so all four CI targets compile and unit-test it. |
| 57 | |
| 58 | --- |
| 59 | |
| 60 | ## 2. Spec: thinking display with `/thinking` (sigit #26) |
| 61 | |
| 62 | Reasoning models (Qwen 3, DeepSeek-R1 distills) emit their thought process in |
| 63 | `<think>` tags. Before this feature the TUI hid reasoning behind the spinner |
| 64 | while streaming and then rendered it as an unconditional box on every finished |
| 65 | message. Both extremes were wrong: watching the model reason is useful, reading |
| 66 | every past thought is noise. |
| 67 | |
| 68 | ### Behavior |
| 69 | |
| 70 | - **While streaming**, if the turn is still inside its think block, the TUI |
| 71 | shows the last three wrapped lines of the reasoning live, dim italic, under |
| 72 | the thinking spinner. Once visible (non-think) text arrives, the reply |
| 73 | streams as before. |
| 74 | - **Finished replies** collapse the reasoning to a one-line dim indicator with |
| 75 | the line count, e.g. `· thought for a bit (12 lines), /thinking to show`. |
| 76 | - **`/thinking [on|off]`** (bare flips) expands the full reasoning block, dim |
| 77 | and visually separated, above each reply across the whole transcript, and |
| 78 | collapses it again when turned off. |
| 79 | |
| 80 | ### Boundaries |
| 81 | |
| 82 | - Display only. Nothing changes in what is sent to the model, what is saved to |
| 83 | the session store, or what goes over ACP, where reasoning stays stripped and |
| 84 | editors render their own thought UI. `/thinking` is TUI-only and not |
| 85 | advertised over ACP, the same precedent as `/resume`. |
| 86 | - Restored sessions re-split think blocks on entry, so `/resume` and the |
| 87 | History tab keep the indicator behavior. |
| 88 | - The TUI streams only when no tools are offered (on-device tool turns buffer |
| 89 | to detect tool calls), so the live tail appears exactly on the turns that can |
| 90 | stream, which are also the turns where think deltas exist. |
| 91 | |
| 92 | ### Implementation notes |
| 93 | |
| 94 | The live-tail extraction is a pure, width-aware function with its own greedy |
| 95 | word wrap (hard-splitting over-wide words), unit-tested without ratatui. |
| 96 | |
| 97 | --- |
| 98 | |
| 99 | ## 3. Follow-ups worth considering |
| 100 | |
| 101 | - Copilot CLI's Issues and Pull requests tabs have a natural sigit equivalent |
| 102 | once the MCP repo workflow tools (sigit-si #3) are consumed by the client: a |
| 103 | fourth tab listing issues and PRs for the current repo when its origin points |
| 104 | at sigit.si. |
| 105 | - A persistent setting for the thinking toggle (settings.toml) if the |
| 106 | per-session default proves annoying. |
| 107 | - Coarse ages in the History tab could show absolute timestamps on a keypress |
| 108 | if precision is ever needed. |