feat: TUI completeness — identity, Seat Deck empty states, lean chrome (0.9.31)

Sanitize session titles, stamp CST on the topbar, fill work/sync/keys so
the sidebar never looks unfinished, drop duplicate ctx from the foot, and
tone down the scroll banner plus composer mode line.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-29 19:35:21 +08:00
co-authored by Cursor
parent 2ccad623e4
commit 5917c48bd0
4 changed files with 128 additions and 85 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

+1 -1
View File
@@ -3,7 +3,7 @@
#define CST_VERSION_MAJOR 0
#define CST_VERSION_MINOR 9
#define CST_VERSION_PATCH 30
#define CST_VERSION_PATCH 31
#define CST_VERSION_STR_(x) #x
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)
+101 -58
View File
@@ -2369,7 +2369,7 @@ static void approval_label(const tui_t *t, char *out, size_t sz) {
snprintf(out, sz, "ask");
}
/* Bottom row: where/status on the left, context + palette hint on the right. */
/* Bottom row: path / status left; palette hint right. CTX lives in Seat Deck. */
static void draw_status(tui_t *t) {
if (!t->status)
return;
@@ -2386,29 +2386,11 @@ static void draw_status(tui_t *t) {
rw = utf8_cols(right, strlen(right));
draw_ansi_at(t->status, 0, w - rw, rw, right, CP_MUTED, 0, 0);
} else {
long used;
int pct = ctx_percent(t, &used);
char tok[32];
fmt_tokens(used, tok, sizeof(tok));
char usage[64] = "";
cst_usage_t u;
cst_agent_usage(t->agent, &u, NULL);
if (u.requests > 0 && u.prompt > 0) {
int n = snprintf(usage, sizeof(usage), "%shit %ld%%", dot_sep(),
u.cached * 100 / u.prompt);
if (u.priced && n > 0 && (size_t)n < sizeof(usage))
snprintf(usage + n, sizeof(usage) - (size_t)n, "%s%s%s%.2f",
dot_sep(), g_ascii ? "~" : "≈", money_sym(u.currency),
u.cost);
}
snprintf(right, sizeof(right),
"%s (%d%%)%s%s\033[97mctrl+p\033[39m commands", tok, pct, usage,
dot_sep());
snprintf(right, sizeof(right), "\033[97mctrl+p\033[39m commands");
rw = vis_cols(right);
if (rw > w)
rw = w;
draw_ansi_at(t->status, 0, w - rw, rw, right,
pct >= 80 ? CP_ERROR : CP_MUTED, 0, 0);
draw_ansi_at(t->status, 0, w - rw, rw, right, CP_MUTED, 0, 0);
t->cmd_x1 = sx + w - 1;
t->cmd_x0 = t->cmd_x1 - (int)strlen("ctrl+p commands") + 1;
}
@@ -2428,7 +2410,7 @@ static void draw_status(tui_t *t) {
wrefresh(t->status);
}
/* "Build · model provider · effort" on the input box's last row. */
/* Short mode row: Build/Plan · effort — model lives in the topbar. */
static void draw_mode_line(tui_t *t, WINDOW *w, int y, int maxw) {
const cst_agent_config_t *cfg = cst_agent_config(t->agent);
if (!cfg)
@@ -2439,7 +2421,7 @@ static void draw_mode_line(tui_t *t, WINDOW *w, int y, int maxw) {
char right[160];
int rpair = CP_BOX_MUTED;
if (t->busy) {
snprintf(right, sizeof(right), "%s %.0fs esc interrupt", spinner_frame(t),
snprintf(right, sizeof(right), "%s %.0fs esc", spinner_frame(t),
since_ms(&t->turn_t0) / 1000.0);
rpair = CP_BOX_PRIMARY;
} else {
@@ -2455,10 +2437,6 @@ static void draw_mode_line(tui_t *t, WINDOW *w, int y, int maxw) {
}
x += draw_ansi_at(w, y, x, room, cfg->plan_mode ? "Plan" : "Build",
cfg->plan_mode ? CP_BOX_PRIMARY : CP_BOX_ACCENT, 1, 0);
x += draw_ansi_at(w, y, x, room - x + 2, dot_sep(), CP_BOX_MUTED, 0, 0);
x += draw_ansi_at(w, y, x, room - x + 2, cfg->model, CP_BOX_BRIGHT, 0, 0);
x += draw_ansi_at(w, y, x, room - x + 2, " ", CP_BOX, 0, 0);
x += draw_ansi_at(w, y, x, room - x + 2, cfg->provider, CP_BOX_MUTED, 0, 0);
if (eff) {
x += draw_ansi_at(w, y, x, room - x + 2, dot_sep(), CP_BOX_MUTED, 0, 0);
x += draw_ansi_at(w, y, x, room - x + 2, eff, CP_BOX_PRIMARY, 0, 0);
@@ -2466,7 +2444,7 @@ static void draw_mode_line(tui_t *t, WINDOW *w, int y, int maxw) {
int npick = cst_agent_pending_count(t->agent);
if (npick > 0) {
char mark[32];
snprintf(mark, sizeof(mark), "%d image%s", npick, npick > 1 ? "s" : "");
snprintf(mark, sizeof(mark), "%d img", npick);
x += draw_ansi_at(w, y, x, room - x + 2, dot_sep(), CP_BOX_MUTED, 0, 0);
draw_ansi_at(w, y, x, room - x + 2, mark, CP_BOX_PRIMARY, 0, 0);
}
@@ -2961,16 +2939,13 @@ static void draw_side(tui_t *t) {
y++;
}
/* WORK — todo + modified (quiet) */
int work_hdr = 0;
/* WORK — todo + modified, or a designed idle line (no empty void). */
if (y + 2 < bottom) {
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
int total = 0;
int todo_n = 0;
if (t->show_todo && t->todo && todo_open_items(t->todo, &total) >= 0 &&
total > 0 && y + 2 < bottom) {
if (!work_hdr) {
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
work_hdr = 1;
}
total > 0) {
for (const char *p = t->todo; *p && y < bottom - 1 && todo_n < 4;) {
const char *nl = strchr(p, '\n');
size_t len = nl ? (size_t)(nl - p) : strlen(p);
@@ -2992,16 +2967,10 @@ static void draw_side(tui_t *t) {
p = nl ? nl + 1 : p + len;
}
}
mod_file_t mods[16];
int nm = collect_modified(t, mods, 16);
if (nm > 5)
nm = 5;
if (nm > 0 && y + 1 < bottom) {
if (!work_hdr) {
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
work_hdr = 1;
}
for (int i = 0; i < nm && y < bottom; i++) {
char pl[16], mi[16];
snprintf(pl, sizeof(pl), "+%d", mods[i].add);
@@ -3013,6 +2982,29 @@ static void draw_side(tui_t *t) {
draw_ansi_at(s, y, px + 1, (int)strlen(mi), mi, CP_SIDE_PRIMARY, 0, 0);
y++;
}
if (todo_n == 0 && nm == 0 && y < bottom)
draw_ansi_at(s, y++, x, mw, "idle", CP_SIDE_MUTED, 0, 0);
y++;
}
/* SYNC — parliament quiet / pending */
if (y + 1 < bottom) {
draw_ansi_at(s, y++, x, mw, "sync", CP_SIDE_MUTED, 0, 0);
if (cst_parliament_sync_pending())
draw_ansi_at(s, y++, x, mw, "pending", CP_SIDE_PRIMARY, 0, 0);
else if (cst_parliament_is_active())
draw_ansi_at(s, y++, x, mw, "quiet", CP_SIDE_MUTED, 0, 0);
else
draw_ansi_at(s, y++, x, mw, "quiet", CP_SIDE_MUTED, 0, 0);
y++;
}
/* KEYS — always one designed hint row */
if (y + 1 < bottom) {
draw_ansi_at(s, y++, x, mw, "keys", CP_SIDE_MUTED, 0, 0);
draw_ansi_at(s, y++, x, mw,
g_ascii ? "tab mode . ^p cmd" : "tab mode · ^p cmd",
CP_SIDE_MUTED, 0, 0);
}
if (h >= 4) {
@@ -3133,16 +3125,13 @@ static void draw_log(tui_t *t) {
}
draw_selection(t, &dl, h, w);
if (t->log_scroll > 0 && h > 2) {
char more[128];
snprintf(more, sizeof(more),
g_ascii ? " -- %d more lines below - End/PgDn - %d above -- "
: " ── %d more lines below · End/PgDn · %d above ── ",
t->log_scroll, start);
char more[64];
snprintf(more, sizeof(more), g_ascii ? " v%d" : " ↓%d", t->log_scroll);
wmove(t->logw, h - 1, 0);
wclrtoeol(t->logw);
wattron(t->logw, A_REVERSE);
draw_ansi_line(t->logw, h - 1, w, more, CP_PRIMARY, 1, 0);
wattroff(t->logw, A_REVERSE);
int mw = utf8_cols(more, strlen(more));
draw_ansi_at(t->logw, h - 1, w - mw - 1 > 0 ? w - mw - 1 : 0, mw, more,
CP_MUTED, 0, 0);
}
dlines_free(&dl);
draw_completion(t, h - 1, 0, w < 90 ? w : 90, h - 1, 1);
@@ -3185,8 +3174,10 @@ static void draw_topbar(tui_t *t) {
if (hexish)
title = "New session";
}
/* Title + model only. Role lives in the Seat Deck — a lone chair/seat
* chip on the far right just duplicates it and looks stranded. */
/* CST mark + title + model. Role lives only in the Seat Deck. */
int x = 1;
x += draw_ansi_at(w, 0, x, 3, "CST", CP_TOPBAR_ACCENT, 1, 0);
x += draw_ansi_at(w, 0, x, 1, " ", CP_TOPBAR_MUTED, 0, 0);
int model_room = 0;
char mid[160] = "";
if (cfg) {
@@ -3194,12 +3185,12 @@ static void draw_topbar(tui_t *t) {
cfg->provider[0] ? " · " : "", cfg->provider);
model_room = utf8_cols(mid, strlen(mid)) + 2;
}
int title_max = bw - 2 - model_room;
int title_max = bw - x - 1 - model_room;
if (title_max < 8)
title_max = 8;
size_t tfit = utf8_fit_cols(title, strlen(title), title_max);
snprintf(left, sizeof(left), " %.*s", (int)tfit, title);
int x = draw_ansi_at(w, 0, 0, bw, left, CP_TOPBAR, 1, 0);
snprintf(left, sizeof(left), "%.*s", (int)tfit, title);
x += draw_ansi_at(w, 0, x, bw - x, left, CP_TOPBAR, 1, 0);
if (mid[0] && x + 2 < bw)
draw_ansi_at(w, 0, x + 2, bw - (x + 2), mid, CP_TOPBAR_MUTED, 0, 0);
wrefresh(w);
@@ -4556,17 +4547,69 @@ static int obtain_key(tui_t *t, const char *provider, char *out, size_t n,
/* --- slash commands ------------------------------------------------------- */
/* First-user dumps / JSON / ctl noise make a bad session chrome title. */
static int title_is_noisy(const char *s) {
if (!s || !s[0])
return 1;
while (*s == ' ' || *s == '\t')
s++;
if (*s == '{' || *s == '[')
return 1;
if (!strncmp(s, "```", 3))
return 1;
if (strstr(s, "\"version\"") || strstr(s, "task controls") ||
strstr(s, "\"revision\"") || strstr(s, "\"sequence\""))
return 1;
return 0;
}
/* One-line human summary into out; empty if nothing usable. */
static void title_summarize(char *out, size_t sz, const char *src) {
out[0] = 0;
if (!src || title_is_noisy(src))
return;
while (*src == ' ' || *src == '\t' || *src == '\n' || *src == '\r')
src++;
char buf[160];
size_t o = 0;
int sp = 0;
for (const char *p = src; *p && *p != '\n' && *p != '\r' && o + 1 < sizeof(buf);
p++) {
unsigned char c = (unsigned char)*p;
if (c == ' ' || c == '\t') {
if (o == 0 || sp)
continue;
buf[o++] = ' ';
sp = 1;
continue;
}
sp = 0;
buf[o++] = (char)c;
}
buf[o] = 0;
if (!buf[0] || title_is_noisy(buf))
return;
size_t fit = utf8_fit_cols(buf, strlen(buf), 40);
snprintf(out, sz, "%.*s", (int)fit, buf);
}
static void refresh_title(tui_t *t) {
const cst_agent_config_t *cfg = cst_agent_config(t->agent);
char sid[128];
cst_agent_session_id(t->agent, sid, sizeof(sid));
cst_session_info_t in;
memset(&in, 0, sizeof(in));
t->title[0] = 0;
if (cfg && cst_agent_store_info(cfg->workspace_root, sid, &in) == 0)
snprintf(t->title, sizeof(t->title), "%s",
in.title[0] ? in.title : in.first_user);
if (cfg)
(void)cst_agent_store_info(cfg->workspace_root, sid, &in);
if (in.title[0] && !title_is_noisy(in.title)) {
size_t fit = utf8_fit_cols(in.title, strlen(in.title), 40);
snprintf(t->title, sizeof(t->title), "%.*s", (int)fit, in.title);
} else {
title_summarize(t->title, sizeof(t->title), in.first_user);
}
if (!t->title[0])
snprintf(t->title, sizeof(t->title), "%.8s", sid);
snprintf(t->title, sizeof(t->title), "New session");
}
static void refresh_todo(tui_t *t) {