diff --git a/desktop/package.json b/desktop/package.json index c8902b1..fa19b94 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,7 +1,7 @@ { "name": "codbash-desktop", "productName": "codbash", - "version": "7.16.0", + "version": "7.17.0", "private": true, "description": "Desktop shell (Electron) for codbash — wraps the codbash server in a native window.", "main": "main.js", diff --git a/package.json b/package.json index b7ab9a6..89b60fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codbash-app", - "version": "7.16.0", + "version": "7.17.0", "description": "Dashboard + CLI for AI coding agents — Claude Code, Codex, Cursor, OpenCode, Kiro. View, search, resume, convert, sync sessions.", "bin": { "codbash": "./bin/cli.js", diff --git a/src/changelog.js b/src/changelog.js index f4beece..f802bc1 100644 --- a/src/changelog.js +++ b/src/changelog.js @@ -1,6 +1,15 @@ 'use strict'; const CHANGELOG = [ + { + version: '7.17.0', + date: '2026-07-30', + title: 'Name your terminals — and dimmed text is readable again', + changes: [ + 'Terminal panes can be named: click the ✎ in a pane\'s title bar (or double-click the title) to label it, e.g. "API" and "worker" for two shells in the same folder. An empty name restores the automatic label (agent → folder → ~), and the name survives reloads, saved layouts and restored sessions', + 'Fixed dimmed text on a filled background rendering as an empty highlighted bar with no text at all — some rows (e.g. the grey "message above" bars a coding agent draws) lost their text entirely in a codbash pane while staying legible in iTerm. Terminal panes now enforce a minimum contrast ratio, so a dimmed foreground is never washed out to invisible', + ], + }, { version: '7.16.0', date: '2026-07-30', diff --git a/src/frontend/styles.css b/src/frontend/styles.css index 31f8b35..ca847ad 100644 --- a/src/frontend/styles.css +++ b/src/frontend/styles.css @@ -3387,6 +3387,20 @@ body[data-view="overview"] .toolbar { display: none; } cursor: pointer; } .ws-pane-bm:hover { color: var(--accent-orange, #f59e0b); border-color: var(--accent-orange, #f59e0b); } +/* Rename this terminal — same chrome as the bookmark button next to it. */ +.ws-pane-ren { + font-size: 12px; + line-height: 1; + width: 22px; height: 22px; + background: transparent; + color: var(--text-muted); + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; +} +.ws-pane-ren:hover { color: var(--accent, #3b82f6); border-color: var(--accent, #3b82f6); } +/* The pane title doubles as a rename target (double-click). */ +.ws-pane-status { cursor: default; } .ws-pane-close { font-size: 15px; line-height: 1; diff --git a/src/frontend/workspace.js b/src/frontend/workspace.js index a6a1c93..3c2b525 100644 --- a/src/frontend/workspace.js +++ b/src/frontend/workspace.js @@ -178,7 +178,7 @@ function _wsSaveSession() { var snap = _wsCaptureLayout(); // Don't persist a lone empty pane — that's just the default blank state. var meaningful = snap.tabs.some(function (t) { - return t.panes.some(function (p) { return p.cmd || p.prefill || p.cwd || p.enteredCmd; }); + return t.panes.some(function (p) { return p.cmd || p.prefill || p.cwd || p.enteredCmd || p.name; }); }) || snap.tabs.length > 1 || (snap.tabs[0] && snap.tabs[0].panes.length > 1); var sig = meaningful ? JSON.stringify(snap) : ''; if (sig === _wsLastSessionSig) return; @@ -210,7 +210,7 @@ function _wsRestoreTabsFromSession(sess) { .slice(0, MAX_WS_PANES) .map(function (p) { var restoreCmd = (p && (p.cmd || p.enteredCmd || p.detectedCmd || p.prefill)) || ''; - return { id: 'p' + (++_wsPaneSeq), cmd: null, prefill: null, restoreCmd: restoreCmd || null, wantCwd: (p && p.cwd) || null }; + return { id: 'p' + (++_wsPaneSeq), cmd: null, prefill: null, restoreCmd: restoreCmd || null, wantCwd: (p && p.cwd) || null, name: (p && p.name) || '' }; }); return { id: 't' + (++_wsTabSeq), name: t.name || ('Tab ' + (ti + 1)), panes: panes, cols: Array.isArray(t.cols) ? t.cols.slice() : null, rows: Array.isArray(t.rows) ? t.rows.slice() : null }; @@ -529,6 +529,8 @@ function _wsShortCwd(cwd) { // A short, human label for a pane's title bar: the running agent (if any), // otherwise the folder name (e.g. "CoWork"), or "~" for the home directory. function _wsPaneLabel(pane) { + // A name the user typed always wins — it's the whole point of renaming. + if (pane && pane.name) return pane.name; if (pane && pane.cmd) { // Strip leading `VAR=value` env assignments (value may be quoted and hold // secrets, e.g. HTTPS_PROXY='http://user:pass@host') so the label is the @@ -552,7 +554,16 @@ function _wsConnectPane(pane) { var term = new Terminal({ cursorBlink: _tp.cursorBlink, cursorStyle: _tp.cursorStyle, fontSize: _tp.fontSize, fontFamily: _tp.fontFamily, - theme: _wsTermTheme(), scrollback: 5000, allowProposedApi: true + theme: _wsTermTheme(), scrollback: 5000, allowProposedApi: true, + // Guarantee readable text against whatever background a TUI paints. + // Without this (xterm's default is 1 = no adjustment) *dimmed* text on a + // filled background renders as an apparently EMPTY highlighted bar — the + // WebGL renderer applies dim by cutting the foreground alpha, so e.g. the + // grey "message above" rows Claude Code draws lost their text entirely, + // while the same output stayed legible in iTerm. 4.5 is the WCAG AA ratio; + // xterm only nudges a foreground when it falls below it, so normal colors + // are left alone. + minimumContrastRatio: 4.5 }); var fit = new FitAddon.FitAddon(); term.loadAddon(fit); @@ -734,7 +745,11 @@ function _wsPaneMarkup(pane) { return '' + '