Stop the shell asserting things that are not true
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
219c674aea
commit
834b73e831
@@ -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<ShellModel>
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
+112
-91
@@ -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<PanelId, usize>,
|
||||
active_worktree: usize,
|
||||
add_panel_chooser_open: bool,
|
||||
root_focus: FocusHandle,
|
||||
}
|
||||
@@ -339,6 +385,34 @@ fn terminal_paint_rows(snapshot: &TerminalSnapshot) -> Vec<Vec<TerminalPaintRun>
|
||||
.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<Store>, 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<Store>, PanelRegistry, String) {
|
||||
{
|
||||
return (
|
||||
None,
|
||||
PanelRegistry::seeded(),
|
||||
PanelRegistry::first_run(),
|
||||
format!("layout memory-only · {error}"),
|
||||
);
|
||||
}
|
||||
@@ -531,7 +609,7 @@ fn load_panel_registry() -> (Option<Store>, 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<Store>, 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>) {
|
||||
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>) {
|
||||
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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user