feat(tui): mode badge in normal footer + paused state indicator
- renderFooter: add mode badge (compact at <80 cols, full at ≥80 cols) to right side so active mode is always visible, not only during auto - renderAutoFooter: refactor to use shared renderModeBadge instead of duplicating badge logic inline - renderModeBadge: handle paused state — all badge parts dim, 'P!' prefix shown in compact form, 'paused ·' prefix shown in full form - getMode(): surface session.paused as a field on the returned mode object so badge renderers can reflect paused state without inspecting session directly - Export renderModeBadge from header.js; footer imports it via FOOTER_THEME adapter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
848ac0dd99
commit
9441022909
3 changed files with 48 additions and 27 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui";
|
||||
import { getAutoSession } from "../sf/auto/session.js";
|
||||
import { refreshGitStatus } from "./git.js";
|
||||
import { renderModeBadge } from "./header.js";
|
||||
|
||||
const RESET = "\x1b[0m";
|
||||
const BOLD = "\x1b[1m";
|
||||
|
|
@ -74,6 +75,12 @@ function join(parts) {
|
|||
function shorten(text, max) {
|
||||
return text.length > max ? `${text.slice(0, Math.max(0, max - 3))}...` : text;
|
||||
}
|
||||
|
||||
/** Minimal theme adapter so renderModeBadge (header.js) can run with footer's ANSI helpers. */
|
||||
const FOOTER_THEME = {
|
||||
fg: (tone, text) => ansiFg(toneHex(tone), text),
|
||||
bold: (text) => `${BOLD}${text}${RESET}`,
|
||||
};
|
||||
function getSessionStats(ctx) {
|
||||
let cost = 0;
|
||||
let tokens = 0;
|
||||
|
|
@ -98,6 +105,8 @@ function getSessionStats(ctx) {
|
|||
export function renderFooter(_theme, footerData, ctx, width) {
|
||||
const git = refreshGitStatus(process.cwd());
|
||||
const { cost, cxPct } = getSessionStats(ctx);
|
||||
const session = getAutoSession();
|
||||
const mode = session?.getMode?.();
|
||||
const leftParts = [];
|
||||
if (git.repo) {
|
||||
leftParts.push(ansiFg(SE.ember40, git.repo, true));
|
||||
|
|
@ -136,6 +145,9 @@ export function renderFooter(_theme, footerData, ctx, width) {
|
|||
leftParts.push(chip("status", statuses.join(" "), "accent"));
|
||||
}
|
||||
const rightParts = [];
|
||||
if (mode) {
|
||||
rightParts.push(renderModeBadge(FOOTER_THEME, mode, width < 80));
|
||||
}
|
||||
if (ctx.model) {
|
||||
rightParts.push(
|
||||
chip("model", `${ctx.model.provider}/${ctx.model.id}`, "text"),
|
||||
|
|
@ -180,19 +192,16 @@ export function renderAutoFooter(_theme, footerData, ctx, width) {
|
|||
modelMode: "smart",
|
||||
};
|
||||
|
||||
const leftParts = [`${BOLD}${ansiFg(SE.ember40, "SF")}`];
|
||||
leftParts.push(ansiFg(SE.ember40, mode.workMode));
|
||||
leftParts.push(ansiFg(SE.gray60, "·"));
|
||||
leftParts.push(ansiFg(SE.success, "∞"));
|
||||
leftParts.push(ansiFg(SE.gray60, "·"));
|
||||
leftParts.push(ansiFg(SE.warning, mode.permissionProfile));
|
||||
const badge = renderModeBadge(FOOTER_THEME, mode, width < 80);
|
||||
const leftParts = [`${BOLD}${ansiFg(SE.ember40, "SF")}`, badge].filter(
|
||||
Boolean,
|
||||
);
|
||||
|
||||
const statuses = Array.from(footerData.getExtensionStatuses().entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([, text]) => text.trim())
|
||||
.filter(Boolean);
|
||||
if (statuses.length) {
|
||||
leftParts.push(ansiFg(SE.gray60, "·"));
|
||||
leftParts.push(ansiFg(SE.gray60, statuses.join(" ")));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,27 +51,36 @@ function compactModelModeBadge(mm) {
|
|||
function renderModeBadge(theme, mode, compact) {
|
||||
if (!mode) return "";
|
||||
const th = theme;
|
||||
const paused = mode.paused === true;
|
||||
if (compact) {
|
||||
const badges = [
|
||||
th.fg("accent", compactModeBadge(mode.workMode)),
|
||||
paused ? th.fg("dim", "P!") : "",
|
||||
th.fg(paused ? "dim" : "accent", compactModeBadge(mode.workMode)),
|
||||
th.fg("dim", compactRunControlBadge(mode.runControl)),
|
||||
th.fg("warning", compactPermissionBadge(mode.permissionProfile)),
|
||||
th.fg("success", compactModelModeBadge(mode.modelMode)),
|
||||
];
|
||||
th.fg(
|
||||
paused ? "dim" : "warning",
|
||||
compactPermissionBadge(mode.permissionProfile),
|
||||
),
|
||||
th.fg(paused ? "dim" : "success", compactModelModeBadge(mode.modelMode)),
|
||||
].filter(Boolean);
|
||||
return `[${badges.join("")}]`;
|
||||
}
|
||||
const parts = [
|
||||
th.fg("accent", mode.workMode),
|
||||
paused ? th.fg("dim", "paused") : "",
|
||||
paused ? th.fg("dim", "·") : "",
|
||||
th.fg(paused ? "dim" : "accent", mode.workMode),
|
||||
th.fg("dim", "·"),
|
||||
th.fg("text", mode.runControl),
|
||||
th.fg("dim", mode.runControl),
|
||||
th.fg("dim", "·"),
|
||||
th.fg("warning", mode.permissionProfile),
|
||||
th.fg(paused ? "dim" : "warning", mode.permissionProfile),
|
||||
th.fg("dim", "·"),
|
||||
th.fg("success", mode.modelMode),
|
||||
];
|
||||
th.fg(paused ? "dim" : "success", mode.modelMode),
|
||||
].filter(Boolean);
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
export { renderModeBadge };
|
||||
|
||||
/**
|
||||
* Minimal auto-mode header — shows only mode badge + project name.
|
||||
* Keeps the user aware SF is running autonomously without full header noise.
|
||||
|
|
|
|||
|
|
@ -505,17 +505,20 @@ export class AutoSession {
|
|||
* Get current mode state as a canonical object.
|
||||
*/
|
||||
getMode() {
|
||||
return buildModeState({
|
||||
workMode: this.workMode,
|
||||
runControl: this.active
|
||||
? this.stepMode
|
||||
? "assisted"
|
||||
: "autonomous"
|
||||
: this.runControl,
|
||||
permissionProfile: this.permissionProfile,
|
||||
modelMode: this.modelMode,
|
||||
surface: this.surface,
|
||||
});
|
||||
return {
|
||||
...buildModeState({
|
||||
workMode: this.workMode,
|
||||
runControl: this.active
|
||||
? this.stepMode
|
||||
? "assisted"
|
||||
: "autonomous"
|
||||
: this.runControl,
|
||||
permissionProfile: this.permissionProfile,
|
||||
modelMode: this.modelMode,
|
||||
surface: this.surface,
|
||||
}),
|
||||
paused: this.paused,
|
||||
};
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue