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:
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user