From 834b73e831e52acef787e0fa069cbb39fa2f6fd9 Mon Sep 17 00:00:00 2001 From: Metal Agent Date: Mon, 31 Aug 2026 22:54:24 -0700 Subject: [PATCH] Stop the shell asserting things that are not true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The footer was rebuilt on a real ledger two commits ago. The rest of the shell was never audited the same way, and a multi-agent pass over it found the same class of defect everywhere else: - A sidebar card reading "Buzz · lumbridgecode / connected · signed identity" in the success colour. lumbridge-buzz is not a dependency of this binary. - A saved host "amd-server", and a WORKTREES section with five entries and a working selector, backed by a lumbridge-git crate that does not exist. - A first-run workspace of six panes announcing "Codex · runtime / metal · Tailscale SSH", "Claude Code · UI / MacBook Air · local" and a Pi pane on a saved host. Every one of them was a /bin/sh, and the machine names were this developer's. - A declared "Pi · spark-1 · laguna-s-2.1" usage profile with no probe of any kind behind it. Declaring a profile promises the gap is real; that one could never be filled. - FOOTER_CENTER = "Codex · ChatGPT subscription · 62% window remaining", rendered by the Floem shell. Decision 0013 names that exact form as the thing that must never be shown. - The header's PTY count painted green unconditionally, so "0/5 LIVE PTYS" read as success. runtime_rows already had the right rule three hundred lines away. - A browser panel describing itself as "An isolated system-web-engine surface" on the chooser screen where you pick it. There is no web engine in this build. First run is now three real local shells, and a pane claims a harness when one has actually been launched into it. The seed mapping stays for when that is possible. Also removes the only unsafe block in the shell: a test set LUMBRIDGE_*_PROBE through the environment, which needs unsafe under edition 2024 and silently disabled both probes for every other test in the binary. Replaced with UsageFeedOptions passed to start_with. Clippy pedantic on the spike goes 79 -> 15 against root CI's -D warnings, so graduating it into the workspace is not gated on a warning cleanup. The four remaining too_many_lines are the render split, which the sidebar work needs to do anyway. Co-Authored-By: Claude Opus 5 (1M context) --- spikes/floem-shell/src/main.rs | 32 ++-- spikes/gpui-shell/src/main.rs | 203 +++++++++++++----------- spikes/gpui-shell/src/panel_registry.rs | 79 ++++----- spikes/gpui-shell/src/usage_feed.rs | 84 +++++++--- spikes/ui-shell-model/src/lib.rs | 26 +-- 5 files changed, 223 insertions(+), 201 deletions(-) diff --git a/spikes/floem-shell/src/main.rs b/spikes/floem-shell/src/main.rs index c31dfcc..f6dc4cf 100644 --- a/spikes/floem-shell/src/main.rs +++ b/spikes/floem-shell/src/main.rs @@ -12,8 +12,7 @@ use floem::{ window::WindowConfig, }; use lumbridge_spike_model::{ - FOOTER_CENTER, FOOTER_RIGHT, FocusDirection, PaneId, PaneState, ShellAction, ShellModel, - SurfaceKind, WORKSPACES, + FOOTER_FIXTURE_NOTICE, FocusDirection, PaneId, PaneState, ShellAction, ShellModel, SurfaceKind, }; const BG: Color = Color::from_rgb8(9, 12, 18); @@ -132,22 +131,16 @@ fn render_pane_card(pane: PaneState, selected: bool, model: RwSignal } fn sidebar() -> impl IntoView { - let worktrees = Stack::from_iter(WORKSPACES.into_iter().enumerate().map(|(index, name)| { - Stack::vertical(( - Label::new(name), - Label::new(if index == 0 { "main · metal" } else { "" }) - .style(|s| s.font_size(10.0).color(MUTED).margin_top(2.0)), - )) - .style(move |s| { - s.width_full() - .padding_vert(7.0) - .padding_horiz(10.0) - .margin_bottom(3.0) - .color(if index == 0 { TEXT } else { MUTED }) - .apply_if(index == 0, |s| s.background(PANEL_ALT).border_radius(5.0)) - }) - })) - .style(|s| s.flex_col().padding_horiz(8.0)); + // The worktree list this shell used to render came from a five-entry + // fixture naming this developer's machines, backed by no git integration. + // The GPUI shell's copy was deleted in the same pass; this one renders the + // workspace it is actually open on. + let worktrees = Stack::vertical((Label::new("Lumbridge Code"),)).style(|s| { + s.flex_col() + .padding_horiz(8.0) + .padding_vert(7.0) + .color(TEXT) + }); Stack::vertical(( Label::new("ATTENTION · 1").style(|s| s.font_size(10.0).color(ATTENTION).padding(12.0)), @@ -434,8 +427,7 @@ fn app_view() -> impl IntoView { }) }) .style(|s| s.flex_grow(1.0)), - Label::new(FOOTER_CENTER).style(|s| s.flex_grow(1.0)), - Label::new(FOOTER_RIGHT), + Label::new(FOOTER_FIXTURE_NOTICE).style(|s| s.flex_grow(1.0)), )) .style(|s| { s.height(32.0) diff --git a/spikes/gpui-shell/src/main.rs b/spikes/gpui-shell/src/main.rs index 5f9257c..4139333 100644 --- a/spikes/gpui-shell/src/main.rs +++ b/spikes/gpui-shell/src/main.rs @@ -16,7 +16,7 @@ use lumbridge_runtime::{ }; use lumbridge_spike_model::{ ActionOutcome, OutputSource, PaneId as FixturePaneId, PaneStatus, ShellAction, ShellModel, - SurfaceKind, WORKSPACES, + SurfaceKind, }; use lumbridge_storage::Store; use lumbridge_terminal::{ @@ -28,16 +28,63 @@ use lumbridge_terminal::{ use panel_registry::{PanelId, PanelKind, PanelRegistry, SeedPane}; use usage_feed::{UsageFeed, UsageSegment}; +// A colour is read as six hex digits, so `0x8b_c8ff` would be harder to check +// against a design token than `0x8bc8ff`, not easier. Scoped to the palette and +// the two terminal colour tables; every other literal keeps the lint. +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const BG: u32 = 0x090c12; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const PANEL: u32 = 0x101620; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const PANEL_ALT: u32 = 0x151d29; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const PANEL_ACTIVE: u32 = 0x182334; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const BORDER: u32 = 0x263246; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const BORDER_QUIET: u32 = 0x1c2636; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const TEXT: u32 = 0xdbe5f4; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const MUTED: u32 = 0x8290a8; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const ACCENT: u32 = 0x68b5f8; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const ATTENTION: u32 = 0xf1b96a; +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] const SUCCESS: u32 = 0x70d6a8; const TIMING_SAMPLE_LIMIT: usize = 256; const RUNTIME_POLL_INTERVAL: Duration = Duration::from_millis(16); @@ -94,7 +141,6 @@ struct LumbridgeShell { /// Which decision-shelf choice each panel has selected. Selecting one is /// inert by design: it prepares nothing and runs nothing. shelf_choice: BTreeMap, - active_worktree: usize, add_panel_chooser_open: bool, root_focus: FocusHandle, } @@ -339,6 +385,34 @@ fn terminal_paint_rows(snapshot: &TerminalSnapshot) -> Vec .collect() } +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] +/// Narrows an already-clamped dimension. +/// +/// Every caller clamps into `2.0..=u16::MAX` first, so this is a narrowing of a +/// value known to fit — but `as` would silently produce garbage if a caller ever +/// stopped clamping, and a NaN would become zero. This saturates instead, which +/// is why the `with_cell_size` expect below is honest rather than hopeful. +fn clamp_to_u16(value: f32) -> u16 { + if value.is_nan() { + return 1; + } + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "clamped into u16 range on the line above" + )] + { + value.clamp(0.0, f32::from(u16::MAX)) as u16 + } +} + +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] fn terminal_color(color: TerminalColor) -> u32 { match color { TerminalColor::Rgb { red, green, blue } => { @@ -377,6 +451,10 @@ fn terminal_color(color: TerminalColor) -> u32 { } } +#[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" +)] fn indexed_terminal_color(index: u8) -> u32 { const ANSI: [u32; 16] = [ 0x1d2430, 0xff6b6b, 0x70d6a8, 0xf1c76a, 0x68b5f8, 0xc79bf2, 0x63d5da, 0xdbe5f4, 0x6d7a91, @@ -427,10 +505,10 @@ fn terminal_dimensions_for_window( .floor() .clamp(20.0, f32::from(u16::MAX)); TerminalDimensions::with_cell_size( - rows as u16, - columns as u16, - TERMINAL_CELL_WIDTH.round() as u16, - TERMINAL_CELL_HEIGHT.round() as u16, + clamp_to_u16(rows), + clamp_to_u16(columns), + clamp_to_u16(TERMINAL_CELL_WIDTH.round()), + clamp_to_u16(TERMINAL_CELL_HEIGHT.round()), ) .expect("geometry clamps terminal dimensions above zero") } @@ -513,7 +591,7 @@ fn load_panel_registry() -> (Option, PanelRegistry, String) { let Some(path) = workspace_database_path() else { return ( None, - PanelRegistry::seeded(), + PanelRegistry::first_run(), "layout memory-only · no data directory".to_owned(), ); }; @@ -522,7 +600,7 @@ fn load_panel_registry() -> (Option, PanelRegistry, String) { { return ( None, - PanelRegistry::seeded(), + PanelRegistry::first_run(), format!("layout memory-only · {error}"), ); } @@ -531,7 +609,7 @@ fn load_panel_registry() -> (Option, PanelRegistry, String) { Err(error) => { return ( None, - PanelRegistry::seeded(), + PanelRegistry::first_run(), format!("layout memory-only · {error}"), ); } @@ -541,18 +619,18 @@ fn load_panel_registry() -> (Option, PanelRegistry, String) { Ok(panels) => (Some(store), panels, "layout restored · SQLite".to_owned()), Err(error) => ( Some(store), - PanelRegistry::seeded(), + PanelRegistry::first_run(), format!("invalid layout ignored · {error}"), ), }, Ok(None) => ( Some(store), - PanelRegistry::seeded(), + PanelRegistry::first_run(), "layout ready · SQLite".to_owned(), ), Err(error) => ( Some(store), - PanelRegistry::seeded(), + PanelRegistry::first_run(), format!("layout read failed · {error}"), ), } @@ -642,7 +720,6 @@ impl LumbridgeShell { usage: UsageFeed::start(), surfaces: BTreeMap::new(), shelf_choice: BTreeMap::new(), - active_worktree: 0, add_panel_chooser_open: false, root_focus, } @@ -655,7 +732,7 @@ impl LumbridgeShell { let result = self .panels .to_json() - .map_err(|error| error.to_owned()) + .map_err(|error| error.clone()) .and_then(|json| { store .save_workspace_snapshot(WORKSPACE_SNAPSHOT_ID, &json) @@ -908,13 +985,6 @@ impl LumbridgeShell { cx.notify(); } - fn select_worktree(&mut self, index: usize, window: &mut Window, cx: &mut Context) { - self.timing.mark_dispatch(); - self.active_worktree = index.min(WORKSPACES.len().saturating_sub(1)); - window.focus(&self.root_focus); - cx.notify(); - } - fn select_pane_at(&mut self, index: usize, window: &mut Window, cx: &mut Context) { self.timing.mark_dispatch(); if self.panels.select_attached_at(index) { @@ -1204,7 +1274,7 @@ impl LumbridgeShell { || run.style.contains(TerminalCellStyle::DOTTED_UNDERLINE) || run.style.contains(TerminalCellStyle::DASHED_UNDERLINE) || run.hyperlink, - |view| view.underline(), + gpui::Styled::underline, ) .when(run.style.contains(TerminalCellStyle::STRIKEOUT), |view| { view.line_through() @@ -1247,7 +1317,7 @@ impl LumbridgeShell { "BROWSER", vec![ "about:blank".to_owned(), - "Content process: isolated".to_owned(), + "No web engine is embedded in this build.".to_owned(), "Choose a URL or open externally ↗".to_owned(), ], ), @@ -1256,7 +1326,7 @@ impl LumbridgeShell { "MARKDOWN", vec![ "# Untitled note".to_owned(), - "".to_owned(), + String::new(), "Local-first workspace document.".to_owned(), ], ), @@ -1264,7 +1334,7 @@ impl LumbridgeShell { SurfaceKind::Review, "REVIEW", vec![ - "Working tree ready for review.".to_owned(), + "No working tree is loaded.".to_owned(), "No approval has been requested.".to_owned(), "0 files selected".to_owned(), ], @@ -2069,18 +2139,10 @@ impl LumbridgeShell { .count(); let total = self.live_terminals.len(); let local = RuntimeRow { - label: "metal", + label: "this machine", detail: format!("{running}/{total} live PTYs"), tone: if running == 0 { MUTED } else { SUCCESS }, }; - // Remote runtimes are a Phase 1 deliverable. Until the SSH stdio - // transport exists, the only honest status for a saved host is that - // nothing is connected to it. - let remote = RuntimeRow { - label: "amd-server", - detail: "saved host · no runtime connected".to_owned(), - tone: MUTED, - }; // The usage adapter is neither a host nor an endpoint, but its health // belongs beside them: it is the reason the footer can or cannot answer. let adapter = RuntimeRow { @@ -2097,7 +2159,7 @@ impl LumbridgeShell { SUCCESS }, }; - vec![local, remote, adapter] + vec![local, adapter] } /// The standing of every harness, always, regardless of what is selected. @@ -2373,41 +2435,6 @@ impl Render for LumbridgeShell { shell.select_pane(id, window, cx); })) })) - .child( - div() - .px_4() - .py_2() - .text_xs() - .text_color(rgb(MUTED)) - .child("WORKTREES"), - ) - .children(WORKSPACES.into_iter().enumerate().map(|(index, name)| { - let active = index == self.active_worktree; - div() - .id(("worktree", index as u64)) - .cursor_pointer() - .mx_2() - .mb_1() - .px_3() - .py_2() - .rounded(px(5.0)) - .when(active, |view| view.bg(rgb(PANEL_ALT)).text_color(rgb(TEXT))) - .when(!active, |view| view.text_color(rgb(MUTED))) - .hover(|view| view.bg(rgb(PANEL_ACTIVE)).text_color(rgb(TEXT))) - .child(name) - .when(active, |view| { - view.child( - div() - .mt_1() - .text_xs() - .text_color(rgb(MUTED)) - .child("main · metal"), - ) - }) - .on_click(cx.listener(move |shell, _, window, cx| { - shell.select_worktree(index, window, cx); - })) - })) .child( div() .mt_3() @@ -2460,23 +2487,7 @@ impl Render for LumbridgeShell { shell.attach_panel(pane, window, cx); })) })) - .child(div().flex_1()) - .child( - div() - .m_3() - .p_3() - .rounded(px(5.0)) - .bg(rgb(PANEL_ALT)) - .text_xs() - .text_color(rgb(MUTED)) - .child("Buzz · lumbridgecode") - .child( - div() - .mt_1() - .text_color(rgb(SUCCESS)) - .child("connected · signed identity"), - ), - ); + .child(div().flex_1()); let tabs = div() .flex() @@ -2499,7 +2510,7 @@ impl Render for LumbridgeShell { .border_b_2() .border_color(rgb(ACCENT)) .text_sm() - .child(WORKSPACES[self.active_worktree]), + .child("Lumbridge Code"), ) .child(div().flex_1()) .child(div().px_3().text_xs().text_color(rgb(MUTED)).child(format!( @@ -2627,15 +2638,21 @@ impl Render for LumbridgeShell { .ml_3() .text_sm() .text_color(rgb(MUTED)) - .child(format!("{} / main", WORKSPACES[self.active_worktree])), + .child("Lumbridge Code"), ) .child(div().flex_1()) + // The same tone rule runtime_rows applies: nothing live is + // not a success, and "0/5 LIVE PTYS" in green said it was. .child( div() .mr_4() .text_xs() - .text_color(rgb(SUCCESS)) - .child(format!("metal · {runtime_summary}")), + .text_color(rgb(if running_runtime_count == 0 { + MUTED + } else { + SUCCESS + })) + .child(runtime_summary), ) .child( div() @@ -2921,6 +2938,10 @@ mod tests { } #[test] + #[allow( + clippy::unreadable_literal, + reason = "six-digit colour hex reads whole" + )] fn xterm_color_cube_and_grayscale_are_deterministic() { assert_eq!(indexed_terminal_color(16), 0x000000); assert_eq!(indexed_terminal_color(231), 0xffffff); diff --git a/spikes/gpui-shell/src/panel_registry.rs b/spikes/gpui-shell/src/panel_registry.rs index 28065b8..f1d39d1 100644 --- a/spikes/gpui-shell/src/panel_registry.rs +++ b/spikes/gpui-shell/src/panel_registry.rs @@ -36,18 +36,26 @@ impl PanelKind { pub(crate) const fn description(self) -> &'static str { match self { Self::Terminal => "A real local shell with its own runtime actor", - Self::Browser => "An isolated system-web-engine surface", + // Not a web engine. There is no browser in this build; the panel + // renders a placeholder surface and says so. + Self::Browser => "A placeholder surface. No web engine is embedded yet", Self::Markdown => "Local notes, plans, and architecture", Self::Review => "Repository changes and approval boundaries", } } + /// What a newly created panel is pointed at. + /// + /// Every one of these used to name a specific machine — this developer's, + /// as it happens. A panel's target is whatever it was actually launched + /// against, and a new panel has been launched against nothing but the + /// local shell. const fn default_target(self) -> &'static str { match self { - Self::Terminal => "metal · local runtime", - Self::Browser => "isolated system web engine", - Self::Markdown => "lumbridge-code · local document", - Self::Review => "lumbridge-code · working tree", + Self::Terminal => "local shell", + Self::Browser => "no surface attached", + Self::Markdown => "local document", + Self::Review => "working tree", } } } @@ -96,49 +104,26 @@ pub(crate) struct PanelRegistry { impl Default for PanelRegistry { fn default() -> Self { - Self::seeded() + Self::first_run() } } impl PanelRegistry { - pub(crate) fn seeded() -> Self { + /// The workspace a first run opens with. + /// + /// Three real local shells and nothing else. This used to seed six panes + /// announcing "Codex · runtime / metal · Tailscale SSH", "Claude Code · UI + /// / `MacBook` Air · local" and a Pi pane on a saved host — none of which + /// existed. Every one was a `/bin/sh`. A pane claims a harness when one has + /// actually been launched into it, and until then it is a terminal. + /// + /// Three, not one: `visible_panel_count` branches at 1100 px and 2800 px, + /// so a single-panel default renders one pane on an ultrawide. + pub(crate) fn first_run() -> Self { let seeds = [ - ( - PanelKind::Terminal, - "Codex · runtime", - "metal · Tailscale SSH", - SeedPane::CodexRuntime, - ), - ( - PanelKind::Terminal, - "Claude Code · UI", - "MacBook Air · local", - SeedPane::ClaudeUi, - ), - ( - PanelKind::Terminal, - "Pi · docs", - "amd-server · OpenSSH", - SeedPane::PiDocs, - ), - ( - PanelKind::Markdown, - "Architecture.md", - "lumbridge-code · worktree", - SeedPane::Architecture, - ), - ( - PanelKind::Browser, - "Preview · ACP docs", - "isolated system web engine", - SeedPane::AcpPreview, - ), - ( - PanelKind::Review, - "Changes · lumbridge-runtime", - "metal · worktree remote-runtime", - SeedPane::RuntimeReview, - ), + (PanelKind::Terminal, "Terminal 1", "local shell", None), + (PanelKind::Terminal, "Terminal 2", "local shell", None), + (PanelKind::Terminal, "Terminal 3", "local shell", None), ]; let panels = seeds .into_iter() @@ -149,7 +134,7 @@ impl PanelRegistry { title: title.to_owned(), target: target.to_owned(), attached: index < 5, - seed: Some(seed), + seed, }) .collect(); Self { @@ -335,7 +320,7 @@ mod tests { #[test] fn dynamic_panels_receive_monotonic_ids_and_insert_beside_selection() { - let mut registry = PanelRegistry::seeded(); + let mut registry = PanelRegistry::first_run(); let browser = registry.create(PanelKind::Browser); let terminal = registry.create(PanelKind::Terminal); assert_eq!(browser, PanelId(7)); @@ -347,7 +332,7 @@ mod tests { #[test] fn identity_and_detached_state_round_trip_without_reuse() { - let mut registry = PanelRegistry::seeded(); + let mut registry = PanelRegistry::first_run(); let created = registry.create(PanelKind::Markdown); assert!(registry.detach(created)); let json = registry.to_json().unwrap(); @@ -358,7 +343,7 @@ mod tests { #[test] fn detach_preserves_panel_and_reattach_restores_same_identity() { - let mut registry = PanelRegistry::seeded(); + let mut registry = PanelRegistry::first_run(); let id = registry.create(PanelKind::Terminal); assert!(registry.detach(id)); assert!(registry.panel(id).is_some()); diff --git a/spikes/gpui-shell/src/usage_feed.rs b/spikes/gpui-shell/src/usage_feed.rs index 156ca7c..751690e 100644 --- a/spikes/gpui-shell/src/usage_feed.rs +++ b/spikes/gpui-shell/src/usage_feed.rs @@ -34,9 +34,15 @@ struct DeclaredProfile { account: &'static str, } -/// The Codex pane maps onto the probe's own primary-window profile, so a live -/// reading lands under the pane that produced it. The rest are declarations -/// with no adapter behind them yet. +/// Only harnesses with a real adapter behind them. +/// +/// A third entry declared "Pi · spark-1 · laguna-s-2.1 · self-hosted" with no +/// probe of any kind, so the strip carried a permanent empty rail for a harness +/// the user was not running. Declaring a profile is a promise that the gap is +/// real; declaring one nothing can ever fill is just a fabricated row. +/// +/// The seed mapping stays: it is how a live reading lands under the pane that +/// produced it, once a harness can actually be launched into a pane. const DECLARED: &[DeclaredProfile] = &[ DeclaredProfile { seed: SeedPane::CodexRuntime, @@ -58,14 +64,6 @@ const DECLARED: &[DeclaredProfile] = &[ model: "five-hour window", account: "subscription", }, - DeclaredProfile { - seed: SeedPane::PiDocs, - id: "spark-1-local", - harness: "Pi", - provider: "spark-1", - model: "laguna-s-2.1", - account: "self-hosted", - }, ]; pub(crate) struct UsageFeed { @@ -78,8 +76,45 @@ pub(crate) struct UsageFeed { probe_status: String, } +/// Which probes to start. +/// +/// Passed in rather than read from the environment inside the constructor: +/// a test that wanted a probe-free feed used to set a process-wide environment +/// variable, which required `unsafe` and silently disabled both probes for +/// every other test in the binary. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct UsageFeedOptions { + pub(crate) codex: bool, + pub(crate) claude: bool, +} + +impl UsageFeedOptions { + /// Reads the documented opt-out switches. The only place the environment + /// is consulted, so a caller can always ask for something else. + fn from_env() -> Self { + let enabled = |name: &str| std::env::var_os(name).is_none_or(|value| value != "0"); + Self { + codex: enabled("LUMBRIDGE_CODEX_PROBE"), + claude: enabled("LUMBRIDGE_CLAUDE_PROBE"), + } + } + + /// Declares every profile and starts nothing. + #[cfg(test)] + pub(crate) const fn none() -> Self { + Self { + codex: false, + claude: false, + } + } +} + impl UsageFeed { pub(crate) fn start() -> Self { + Self::start_with(UsageFeedOptions::from_env()) + } + + pub(crate) fn start_with(options: UsageFeedOptions) -> Self { let mut profiles = BTreeMap::new(); let mut seeds = Vec::new(); for declared in DECLARED { @@ -105,8 +140,8 @@ impl UsageFeed { health: BTreeMap::new(), probe_status: "no usage adapter running".to_owned(), }; - feed.start_codex_probe(); - feed.start_claude_probe(); + feed.start_codex_probe(options.codex); + feed.start_claude_probe(options.claude); feed } @@ -115,8 +150,8 @@ impl UsageFeed { /// A failure here is not an error state for the shell: the profiles stay /// declared and render as unavailable, which is exactly what they should do /// when no adapter can run. - fn start_codex_probe(&mut self) { - if std::env::var_os("LUMBRIDGE_CODEX_PROBE").is_some_and(|value| value == "0") { + fn start_codex_probe(&mut self, enabled: bool) { + if !enabled { self.probe_status = "codex probe disabled".to_owned(); return; } @@ -143,8 +178,12 @@ impl UsageFeed { /// This one reads files rather than launching anything, so it has no /// harness to fail to find — a missing projects directory simply reports /// nothing until Claude Code creates it. - fn start_claude_probe(&mut self) { - if std::env::var_os("LUMBRIDGE_CLAUDE_PROBE").is_some_and(|value| value == "0") { + fn start_claude_probe(&mut self, enabled: bool) { + if !enabled { + // Say so, rather than leaving whatever the Codex path last wrote. + // A disabled probe that reports nothing about itself is + // indistinguishable from one that failed to start. + self.probe_status = "claude probe disabled".to_owned(); return; } match ClaudeCodeProbe::start(ClaudeCodeProbeOptions::default()) { @@ -415,19 +454,14 @@ pub(crate) struct UsageSegment { #[cfg(test)] mod tests { - use super::{DECLARED, UsageFeed}; + use super::{DECLARED, UsageFeed, UsageFeedOptions}; use crate::panel_registry::SeedPane; use lumbridge_core::UsageProvenance; /// Never starts a probe, so the test cannot depend on an installed harness, /// on the user's transcripts, or on a status-line bridge being present. fn declared_only_feed() -> UsageFeed { - // SAFETY-FREE: this only sets environment variables for this process. - unsafe { - std::env::set_var("LUMBRIDGE_CODEX_PROBE", "0"); - std::env::set_var("LUMBRIDGE_CLAUDE_PROBE", "0"); - } - UsageFeed::start() + UsageFeed::start_with(UsageFeedOptions::none()) } #[test] @@ -485,7 +519,7 @@ mod tests { let strip = feed.strip(); assert_eq!(strip.len(), DECLARED.len()); let labels: Vec<&str> = strip.iter().map(|segment| segment.label.as_str()).collect(); - assert_eq!(labels, ["CODEX", "CLAUDE CODE", "PI"]); + assert_eq!(labels, ["CODEX", "CLAUDE CODE"]); for segment in &strip { assert!( segment.headline.is_none() && segment.reset.is_none(), diff --git a/spikes/ui-shell-model/src/lib.rs b/spikes/ui-shell-model/src/lib.rs index 9ab81f1..2eb70d3 100644 --- a/spikes/ui-shell-model/src/lib.rs +++ b/spikes/ui-shell-model/src/lib.rs @@ -198,25 +198,15 @@ pub const PANES: [PaneFixture; 6] = [ }, ]; -pub const WORKSPACES: [&str; 5] = [ - "Lumbridge Code", - "Runtime / metal", - "UI spikes", - "ACP adapters", - "Usage telemetry", -]; -/// Static footer strings retained only by the Floem comparison shell. +/// The one footer string the frozen Floem shell renders. /// -/// These are layout fixtures, not usage. They are not a usage source and must -/// not be rendered as one: the GPUI shell now derives its footer from -/// `lumbridge_core::UsageLedger`, where every value carries a provenance and a -/// missing fact renders as missing. Delete these once Floem consumes the ledger -/// or the candidate is retired. See decision 0012. -pub const FOOTER_LEFT: &str = "6 panes · 3 remote · 1 needs input"; -/// See [`FOOTER_LEFT`]: a layout fixture, not a usage reading. -pub const FOOTER_CENTER: &str = "Codex · ChatGPT subscription · 62% window remaining"; -/// See [`FOOTER_LEFT`]: a layout fixture, not a usage reading. -pub const FOOTER_RIGHT: &str = "burn 8.4%/hr · resets in 2h 14m"; +/// It replaces three that read like data — a pane census, "Codex · ChatGPT +/// subscription · 62% window remaining", and "burn 8.4%/hr · resets in 2h 14m". +/// Decision 0013 names that exact form as the thing that must never be shown, +/// and a comparison shell rendering a fabricated quota beside a real one is +/// worse than a comparison shell with no footer numbers at all. The GPUI shell +/// derives its footer from `lumbridge_core::UsageLedger`. See decision 0012. +pub const FOOTER_FIXTURE_NOTICE: &str = "layout fixture · no usage source"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct PaneState {