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_MAJOR 0
|
||||||
#define CST_VERSION_MINOR 9
|
#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) #x
|
||||||
#define CST_VERSION_STR(x) CST_VERSION_STR_(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");
|
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) {
|
static void draw_status(tui_t *t) {
|
||||||
if (!t->status)
|
if (!t->status)
|
||||||
return;
|
return;
|
||||||
@@ -2386,29 +2386,11 @@ static void draw_status(tui_t *t) {
|
|||||||
rw = utf8_cols(right, strlen(right));
|
rw = utf8_cols(right, strlen(right));
|
||||||
draw_ansi_at(t->status, 0, w - rw, rw, right, CP_MUTED, 0, 0);
|
draw_ansi_at(t->status, 0, w - rw, rw, right, CP_MUTED, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
long used;
|
snprintf(right, sizeof(right), "\033[97mctrl+p\033[39m commands");
|
||||||
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());
|
|
||||||
rw = vis_cols(right);
|
rw = vis_cols(right);
|
||||||
if (rw > w)
|
if (rw > w)
|
||||||
rw = w;
|
rw = w;
|
||||||
draw_ansi_at(t->status, 0, w - rw, rw, right,
|
draw_ansi_at(t->status, 0, w - rw, rw, right, CP_MUTED, 0, 0);
|
||||||
pct >= 80 ? CP_ERROR : CP_MUTED, 0, 0);
|
|
||||||
t->cmd_x1 = sx + w - 1;
|
t->cmd_x1 = sx + w - 1;
|
||||||
t->cmd_x0 = t->cmd_x1 - (int)strlen("ctrl+p commands") + 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);
|
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) {
|
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);
|
const cst_agent_config_t *cfg = cst_agent_config(t->agent);
|
||||||
if (!cfg)
|
if (!cfg)
|
||||||
@@ -2439,7 +2421,7 @@ static void draw_mode_line(tui_t *t, WINDOW *w, int y, int maxw) {
|
|||||||
char right[160];
|
char right[160];
|
||||||
int rpair = CP_BOX_MUTED;
|
int rpair = CP_BOX_MUTED;
|
||||||
if (t->busy) {
|
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);
|
since_ms(&t->turn_t0) / 1000.0);
|
||||||
rpair = CP_BOX_PRIMARY;
|
rpair = CP_BOX_PRIMARY;
|
||||||
} else {
|
} 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",
|
x += draw_ansi_at(w, y, x, room, cfg->plan_mode ? "Plan" : "Build",
|
||||||
cfg->plan_mode ? CP_BOX_PRIMARY : CP_BOX_ACCENT, 1, 0);
|
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) {
|
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, dot_sep(), CP_BOX_MUTED, 0, 0);
|
||||||
x += draw_ansi_at(w, y, x, room - x + 2, eff, CP_BOX_PRIMARY, 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);
|
int npick = cst_agent_pending_count(t->agent);
|
||||||
if (npick > 0) {
|
if (npick > 0) {
|
||||||
char mark[32];
|
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);
|
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);
|
draw_ansi_at(w, y, x, room - x + 2, mark, CP_BOX_PRIMARY, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -2961,47 +2939,38 @@ static void draw_side(tui_t *t) {
|
|||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WORK — todo + modified (quiet) */
|
/* WORK — todo + modified, or a designed idle line (no empty void). */
|
||||||
int work_hdr = 0;
|
if (y + 2 < bottom) {
|
||||||
int total = 0;
|
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
|
||||||
int todo_n = 0;
|
int total = 0;
|
||||||
if (t->show_todo && t->todo && todo_open_items(t->todo, &total) >= 0 &&
|
int todo_n = 0;
|
||||||
total > 0 && y + 2 < bottom) {
|
if (t->show_todo && t->todo && todo_open_items(t->todo, &total) >= 0 &&
|
||||||
if (!work_hdr) {
|
total > 0) {
|
||||||
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
|
for (const char *p = t->todo; *p && y < bottom - 1 && todo_n < 4;) {
|
||||||
work_hdr = 1;
|
const char *nl = strchr(p, '\n');
|
||||||
}
|
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
||||||
for (const char *p = t->todo; *p && y < bottom - 1 && todo_n < 4;) {
|
if (len >= 3) {
|
||||||
const char *nl = strchr(p, '\n');
|
int pair = CP_SIDE;
|
||||||
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
const char *mark = "[ ]";
|
||||||
if (len >= 3) {
|
if (strncmp(p, "[x]", 3) == 0)
|
||||||
int pair = CP_SIDE;
|
pair = CP_SIDE_MUTED, mark = g_ascii ? "[x]" : "[✓]";
|
||||||
const char *mark = "[ ]";
|
else if (strncmp(p, "[~]", 3) == 0)
|
||||||
if (strncmp(p, "[x]", 3) == 0)
|
pair = CP_SIDE_PRIMARY, mark = g_ascii ? "[~]" : "[•]";
|
||||||
pair = CP_SIDE_MUTED, mark = g_ascii ? "[x]" : "[✓]";
|
const char *txt = p + 3;
|
||||||
else if (strncmp(p, "[~]", 3) == 0)
|
while (txt < p + len && *txt == ' ')
|
||||||
pair = CP_SIDE_PRIMARY, mark = g_ascii ? "[~]" : "[•]";
|
txt++;
|
||||||
const char *txt = p + 3;
|
snprintf(line, sizeof(line), "%s %.*s", mark, (int)(p + len - txt),
|
||||||
while (txt < p + len && *txt == ' ')
|
txt);
|
||||||
txt++;
|
draw_ansi_at(s, y++, x, mw, line, pair, 0, 0);
|
||||||
snprintf(line, sizeof(line), "%s %.*s", mark, (int)(p + len - txt),
|
todo_n++;
|
||||||
txt);
|
}
|
||||||
draw_ansi_at(s, y++, x, mw, line, pair, 0, 0);
|
p = nl ? nl + 1 : p + len;
|
||||||
todo_n++;
|
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
mod_file_t mods[16];
|
||||||
|
int nm = collect_modified(t, mods, 16);
|
||||||
|
if (nm > 5)
|
||||||
|
nm = 5;
|
||||||
for (int i = 0; i < nm && y < bottom; i++) {
|
for (int i = 0; i < nm && y < bottom; i++) {
|
||||||
char pl[16], mi[16];
|
char pl[16], mi[16];
|
||||||
snprintf(pl, sizeof(pl), "+%d", mods[i].add);
|
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);
|
draw_ansi_at(s, y, px + 1, (int)strlen(mi), mi, CP_SIDE_PRIMARY, 0, 0);
|
||||||
y++;
|
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) {
|
if (h >= 4) {
|
||||||
@@ -3133,16 +3125,13 @@ static void draw_log(tui_t *t) {
|
|||||||
}
|
}
|
||||||
draw_selection(t, &dl, h, w);
|
draw_selection(t, &dl, h, w);
|
||||||
if (t->log_scroll > 0 && h > 2) {
|
if (t->log_scroll > 0 && h > 2) {
|
||||||
char more[128];
|
char more[64];
|
||||||
snprintf(more, sizeof(more),
|
snprintf(more, sizeof(more), g_ascii ? " v%d" : " ↓%d", t->log_scroll);
|
||||||
g_ascii ? " -- %d more lines below - End/PgDn - %d above -- "
|
|
||||||
: " ── %d more lines below · End/PgDn · %d above ── ",
|
|
||||||
t->log_scroll, start);
|
|
||||||
wmove(t->logw, h - 1, 0);
|
wmove(t->logw, h - 1, 0);
|
||||||
wclrtoeol(t->logw);
|
wclrtoeol(t->logw);
|
||||||
wattron(t->logw, A_REVERSE);
|
int mw = utf8_cols(more, strlen(more));
|
||||||
draw_ansi_line(t->logw, h - 1, w, more, CP_PRIMARY, 1, 0);
|
draw_ansi_at(t->logw, h - 1, w - mw - 1 > 0 ? w - mw - 1 : 0, mw, more,
|
||||||
wattroff(t->logw, A_REVERSE);
|
CP_MUTED, 0, 0);
|
||||||
}
|
}
|
||||||
dlines_free(&dl);
|
dlines_free(&dl);
|
||||||
draw_completion(t, h - 1, 0, w < 90 ? w : 90, h - 1, 1);
|
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)
|
if (hexish)
|
||||||
title = "New session";
|
title = "New session";
|
||||||
}
|
}
|
||||||
/* Title + model only. Role lives in the Seat Deck — a lone chair/seat
|
/* CST mark + title + model. Role lives only in the Seat Deck. */
|
||||||
* chip on the far right just duplicates it and looks stranded. */
|
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;
|
int model_room = 0;
|
||||||
char mid[160] = "";
|
char mid[160] = "";
|
||||||
if (cfg) {
|
if (cfg) {
|
||||||
@@ -3194,12 +3185,12 @@ static void draw_topbar(tui_t *t) {
|
|||||||
cfg->provider[0] ? " · " : "", cfg->provider);
|
cfg->provider[0] ? " · " : "", cfg->provider);
|
||||||
model_room = utf8_cols(mid, strlen(mid)) + 2;
|
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)
|
if (title_max < 8)
|
||||||
title_max = 8;
|
title_max = 8;
|
||||||
size_t tfit = utf8_fit_cols(title, strlen(title), title_max);
|
size_t tfit = utf8_fit_cols(title, strlen(title), title_max);
|
||||||
snprintf(left, sizeof(left), " %.*s", (int)tfit, title);
|
snprintf(left, sizeof(left), "%.*s", (int)tfit, title);
|
||||||
int x = draw_ansi_at(w, 0, 0, bw, left, CP_TOPBAR, 1, 0);
|
x += draw_ansi_at(w, 0, x, bw - x, left, CP_TOPBAR, 1, 0);
|
||||||
if (mid[0] && x + 2 < bw)
|
if (mid[0] && x + 2 < bw)
|
||||||
draw_ansi_at(w, 0, x + 2, bw - (x + 2), mid, CP_TOPBAR_MUTED, 0, 0);
|
draw_ansi_at(w, 0, x + 2, bw - (x + 2), mid, CP_TOPBAR_MUTED, 0, 0);
|
||||||
wrefresh(w);
|
wrefresh(w);
|
||||||
@@ -4556,17 +4547,69 @@ static int obtain_key(tui_t *t, const char *provider, char *out, size_t n,
|
|||||||
|
|
||||||
/* --- slash commands ------------------------------------------------------- */
|
/* --- 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) {
|
static void refresh_title(tui_t *t) {
|
||||||
const cst_agent_config_t *cfg = cst_agent_config(t->agent);
|
const cst_agent_config_t *cfg = cst_agent_config(t->agent);
|
||||||
char sid[128];
|
char sid[128];
|
||||||
cst_agent_session_id(t->agent, sid, sizeof(sid));
|
cst_agent_session_id(t->agent, sid, sizeof(sid));
|
||||||
cst_session_info_t in;
|
cst_session_info_t in;
|
||||||
|
memset(&in, 0, sizeof(in));
|
||||||
t->title[0] = 0;
|
t->title[0] = 0;
|
||||||
if (cfg && cst_agent_store_info(cfg->workspace_root, sid, &in) == 0)
|
if (cfg)
|
||||||
snprintf(t->title, sizeof(t->title), "%s",
|
(void)cst_agent_store_info(cfg->workspace_root, sid, &in);
|
||||||
in.title[0] ? in.title : in.first_user);
|
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])
|
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) {
|
static void refresh_todo(tui_t *t) {
|
||||||
|
|||||||
Reference in New Issue
Block a user