mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 17:09:13 -10:00
fix(tui): retain update notice in the metrics line
Show a quiet available-version notice on the normal and empty-session metrics line, without replacing permission or remote-control status. A staged unsigned update no longer claims it is ready. Existing hidden/mini/pet layouts remain unchanged and the notice is display-only; /update remains the action. Refs #7136. Verified on the frozen Linux source export: the existing empty-session render and status-item coverage checks passed (2 passed, 0 failed; 13,924 filtered in each). rustfmt and diff checks passed. First VM test attempt was killed at its memory limit; the one-job retry with swap passed. No installed binary, real update check, clickable control, or hosted CI was verified. Read-only design review by Claude.
This commit is contained in:
1 parent
8f131fbdd7
commit
71bc853fe6
6 files changed
+42
-22
No files matched your search
@@ -1669,9 +1669,9 @@ pub struct App {
|
||||
pub status_message: Option<String>,
|
||||
/// Recent status toasts (ephemeral, newest at back).
|
||||
pub status_toasts: VecDeque<StatusToast>,
|
||||
/// Header chip label (e.g. `↑ v0.9.5`) set once by the fire-and-forget
|
||||
/// Metrics-line label (e.g. `↑ v0.9.5`) set once by the fire-and-forget
|
||||
/// startup version check when a newer stable release exists. Drives the
|
||||
/// small persistent update chip in the header so the affordance survives
|
||||
/// persistent update notice so the affordance survives
|
||||
/// the transient toast without nagging (FINISH-0.9.4 #14).
|
||||
pub update_available: Option<String>,
|
||||
/// Sticky status toast used for important warnings/errors.
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
//! and at every fullness, not only from 50% up (#5950).
|
||||
//!
|
||||
//! Shed order as width drops: cache, output count and billing tier, the help
|
||||
//! hint, then rate and TTFT, then cost and balance
|
||||
//! hint, then rate and TTFT, then cost and balance, followed by goal and update
|
||||
//! ([`InfoSegmentId::shed_priority`]). The model and `ctx NN%` never shed; below that floor the row clips at its
|
||||
//! right edge.
|
||||
//!
|
||||
//! Which segments exist at all is the user's call: `/statusline` and
|
||||
//! `tui.status_items` compose the row, and [`crate::tui::ui::frame::info_segments`]
|
||||
//! builds only the ones that are on. Shedding decides what survives the
|
||||
//! `/statusline` and `tui.status_items` select the session metrics;
|
||||
//! [`crate::tui::ui::frame::info_segments`] also includes an active goal or
|
||||
//! an available-update notice. Shedding decides what survives the
|
||||
//! width that is left. `tui.metrics_line` sizes the row (#5950): `hidden`
|
||||
//! gives the line back to the transcript, and `compact` starts the shed
|
||||
//! pass with secondary counts and the help hint already gone; TTFT and rate
|
||||
|
||||
@@ -1445,11 +1445,11 @@ impl UpdateNotice {
|
||||
)
|
||||
}
|
||||
|
||||
/// Compact header chip label shown once the check has landed. Quiet by
|
||||
/// Compact metrics-line label shown once the check has landed. Quiet by
|
||||
/// design: no action verb, no repetition — the toast and transcript
|
||||
/// notice carry the update instructions (#14).
|
||||
fn chip_label(&self) -> String {
|
||||
if self.staged.is_some() {
|
||||
if self.staged == Some(true) {
|
||||
format!("↑ v{latest} ready", latest = self.latest)
|
||||
} else {
|
||||
format!("↑ v{latest}", latest = self.latest)
|
||||
|
||||
@@ -2239,7 +2239,7 @@ pub(crate) async fn run_event_loop(
|
||||
// Transient toast for immediate visibility, plus a durable
|
||||
// in-transcript notice so the prompt survives the toast TTL and
|
||||
// stays actionable during a busy session (#3961). The persistent
|
||||
// header chip keeps a quiet affordance after both (#14).
|
||||
// metrics-line notice keeps a quiet affordance after both (#14).
|
||||
// Which command to advertise depends on who owns this binary on
|
||||
// disk, so resolve that here rather than hardcoding our own
|
||||
// updater into the wording.
|
||||
|
||||
@@ -72,10 +72,9 @@ fn output_tokens(app: &App) -> Option<u64> {
|
||||
/// the git bottom view own them. Fleet, whale and automation counts left too —
|
||||
/// the posture bar's live counts own activity.
|
||||
///
|
||||
/// Composition is the user's (#5950): every segment here is gated on the
|
||||
/// matching [`StatusItem`] in `app.status_items`, which is what `/statusline`
|
||||
/// edits and `tui.status_items` persists. Between 0.9.12 and this change the
|
||||
/// row ignored that list entirely and the picker's toggles did nothing.
|
||||
/// Session metrics follow the matching [`StatusItem`] in `app.status_items`,
|
||||
/// which `/statusline` edits and `tui.status_items` persists. The active goal
|
||||
/// and available-update notice follow their own live state.
|
||||
pub(crate) fn info_segments(app: &App, width: u16) -> Vec<InfoSegment> {
|
||||
use codewhale_localization::MessageId;
|
||||
use codewhale_palette::ChromeInk;
|
||||
@@ -325,6 +324,15 @@ pub(crate) fn info_segments(app: &App, width: u16) -> Vec<InfoSegment> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(label) = app.update_available.as_deref() {
|
||||
segments.push(InfoSegment::new(
|
||||
InfoSegmentId::Update,
|
||||
"",
|
||||
label,
|
||||
ChromeInk::Info,
|
||||
));
|
||||
}
|
||||
|
||||
segments
|
||||
}
|
||||
|
||||
@@ -418,10 +426,9 @@ fn split_route_hitbox(
|
||||
}
|
||||
|
||||
/// Render the info line into its one row and record its segment
|
||||
/// hitboxes (spec §5b `Constraint::Length(1)`). The ONE header on every
|
||||
/// screen: the session shell and the launch screen both call this, so the
|
||||
/// brand lockup, contextual segments, and the pinned meter + clock never
|
||||
/// change identity between pre- and post-session states. Segment rects are
|
||||
/// hitboxes (spec §5b `Constraint::Length(1)`). The session shell and launch
|
||||
/// screen both use this row, keeping the visible identity and notice facts
|
||||
/// consistent between pre- and post-session states. Segment rects are
|
||||
/// recorded for hover (this frame's highlight resolves against the previous
|
||||
/// frame's rects, the standard one-frame-lag registry pattern) and for typed
|
||||
/// click routing.
|
||||
@@ -446,7 +453,10 @@ fn render_info_row(
|
||||
segments.retain(|segment| {
|
||||
matches!(
|
||||
segment.id,
|
||||
InfoSegmentId::Model | InfoSegmentId::Workspace | InfoSegmentId::GitBranch
|
||||
InfoSegmentId::Model
|
||||
| InfoSegmentId::Workspace
|
||||
| InfoSegmentId::GitBranch
|
||||
| InfoSegmentId::Update
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -3019,9 +3029,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every remaining status item owns a segment, and an empty list leaves
|
||||
/// the row with nothing but the help hint — no toggle in `/statusline`
|
||||
/// paints something no toggle can remove.
|
||||
/// Every selected status item owns its segment; without live notices an
|
||||
/// empty list leaves the metrics line empty.
|
||||
#[test]
|
||||
fn every_metrics_segment_answers_to_a_status_item() {
|
||||
let mut app = app_with_context_percent(60);
|
||||
@@ -3070,6 +3079,7 @@ mod tests {
|
||||
app.workspace = std::path::PathBuf::from("/fixture/checkout");
|
||||
app.workspace_context = Some("feature-6112 | clean".to_string());
|
||||
app.status_items = vec![StatusItem::Workspace, StatusItem::GitBranch];
|
||||
app.update_available = Some("↑ v9.9.9".to_string());
|
||||
app.metrics_line = codewhale_runtime::config::ChromeRowPreset::Compact;
|
||||
let backend = ratatui::backend::TestBackend::new(100, 1);
|
||||
let mut terminal = ratatui::Terminal::new(backend).unwrap();
|
||||
@@ -3088,6 +3098,15 @@ mod tests {
|
||||
.collect();
|
||||
assert!(rendered.contains("checkout"), "{rendered}");
|
||||
assert!(rendered.contains("feature-6112"), "{rendered}");
|
||||
assert!(rendered.contains("v9.9.9"), "{rendered}");
|
||||
let mut notice = super::super::UpdateNotice {
|
||||
current: "0.10.1".to_string(),
|
||||
latest: "0.10.2".to_string(),
|
||||
staged: Some(false),
|
||||
};
|
||||
assert_eq!(notice.chip_label(), "↑ v0.10.2");
|
||||
notice.staged = Some(true);
|
||||
assert_eq!(notice.chip_label(), "↑ v0.10.2 ready");
|
||||
}
|
||||
|
||||
/// #6112: the opt-in workspace and branch chips read cached state only —
|
||||
|
||||
+2
-1
@@ -469,6 +469,7 @@ pub enum MetricKind {
|
||||
Goal,
|
||||
Workspace,
|
||||
GitBranch,
|
||||
Update,
|
||||
}
|
||||
|
||||
impl MetricKind {
|
||||
@@ -482,7 +483,7 @@ impl MetricKind {
|
||||
Self::Rate | Self::Ttft | Self::Cost => 5,
|
||||
Self::OutputTokens | Self::BillingTier => 7,
|
||||
Self::Balance | Self::Workspace | Self::GitBranch => 4,
|
||||
Self::Goal => 3,
|
||||
Self::Goal | Self::Update => 3,
|
||||
Self::Model | Self::Context => 0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user