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>
365 lines
12 KiB
Rust
365 lines
12 KiB
Rust
use std::collections::BTreeSet;
|
|
|
|
use lumbridge_spike_model::PaneId as FixturePaneId;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
|
pub(crate) struct PanelId(u64);
|
|
|
|
impl PanelId {
|
|
pub(crate) const fn get(self) -> u64 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub(crate) enum PanelKind {
|
|
Terminal,
|
|
Browser,
|
|
Markdown,
|
|
Review,
|
|
}
|
|
|
|
impl PanelKind {
|
|
pub(crate) const ALL: [Self; 4] = [Self::Terminal, Self::Browser, Self::Markdown, Self::Review];
|
|
|
|
pub(crate) const fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Terminal => "Terminal",
|
|
Self::Browser => "Browser",
|
|
Self::Markdown => "Markdown",
|
|
Self::Review => "Review",
|
|
}
|
|
}
|
|
|
|
pub(crate) const fn description(self) -> &'static str {
|
|
match self {
|
|
Self::Terminal => "A real local shell with its own runtime actor",
|
|
// 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 => "local shell",
|
|
Self::Browser => "no surface attached",
|
|
Self::Markdown => "local document",
|
|
Self::Review => "working tree",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub(crate) enum SeedPane {
|
|
CodexRuntime,
|
|
ClaudeUi,
|
|
PiDocs,
|
|
Architecture,
|
|
AcpPreview,
|
|
RuntimeReview,
|
|
}
|
|
|
|
impl SeedPane {
|
|
pub(crate) const fn fixture_id(self) -> FixturePaneId {
|
|
match self {
|
|
Self::CodexRuntime => FixturePaneId::CodexRuntime,
|
|
Self::ClaudeUi => FixturePaneId::ClaudeUi,
|
|
Self::PiDocs => FixturePaneId::PiDocs,
|
|
Self::Architecture => FixturePaneId::Architecture,
|
|
Self::AcpPreview => FixturePaneId::AcpPreview,
|
|
Self::RuntimeReview => FixturePaneId::RuntimeReview,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub(crate) struct WorkspacePanel {
|
|
pub(crate) id: PanelId,
|
|
pub(crate) kind: PanelKind,
|
|
pub(crate) title: String,
|
|
pub(crate) target: String,
|
|
pub(crate) attached: bool,
|
|
pub(crate) seed: Option<SeedPane>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub(crate) struct PanelRegistry {
|
|
schema_version: u32,
|
|
panels: Vec<WorkspacePanel>,
|
|
selected: PanelId,
|
|
next_id: u64,
|
|
}
|
|
|
|
impl Default for PanelRegistry {
|
|
fn default() -> Self {
|
|
Self::first_run()
|
|
}
|
|
}
|
|
|
|
impl PanelRegistry {
|
|
/// 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, "Terminal 1", "local shell", None),
|
|
(PanelKind::Terminal, "Terminal 2", "local shell", None),
|
|
(PanelKind::Terminal, "Terminal 3", "local shell", None),
|
|
];
|
|
let panels = seeds
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(index, (kind, title, target, seed))| WorkspacePanel {
|
|
id: PanelId(index as u64 + 1),
|
|
kind,
|
|
title: title.to_owned(),
|
|
target: target.to_owned(),
|
|
attached: index < 5,
|
|
seed,
|
|
})
|
|
.collect();
|
|
Self {
|
|
schema_version: 1,
|
|
panels,
|
|
selected: PanelId(1),
|
|
next_id: 7,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn from_json(json: &str) -> Result<Self, String> {
|
|
let registry: Self = serde_json::from_str(json).map_err(|error| error.to_string())?;
|
|
registry.validate()?;
|
|
Ok(registry)
|
|
}
|
|
|
|
pub(crate) fn to_json(&self) -> Result<String, String> {
|
|
serde_json::to_string(self).map_err(|error| error.to_string())
|
|
}
|
|
|
|
fn validate(&self) -> Result<(), String> {
|
|
if self.schema_version != 1 {
|
|
return Err(format!(
|
|
"unsupported panel snapshot version {}",
|
|
self.schema_version
|
|
));
|
|
}
|
|
if self.panels.is_empty() || self.attached_count() == 0 {
|
|
return Err("a workspace must retain one attached panel".to_owned());
|
|
}
|
|
let ids = self
|
|
.panels
|
|
.iter()
|
|
.map(|panel| panel.id)
|
|
.collect::<BTreeSet<_>>();
|
|
if ids.len() != self.panels.len() || ids.iter().any(|id| id.get() == 0) {
|
|
return Err("panel IDs must be unique and non-zero".to_owned());
|
|
}
|
|
if self
|
|
.panel(self.selected)
|
|
.is_none_or(|panel| !panel.attached)
|
|
{
|
|
return Err("the selected panel must be attached".to_owned());
|
|
}
|
|
let max_id = ids.iter().map(|id| id.get()).max().unwrap_or(0);
|
|
if self.next_id <= max_id {
|
|
return Err("the next panel ID must exceed every persisted ID".to_owned());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn panels(&self) -> &[WorkspacePanel] {
|
|
&self.panels
|
|
}
|
|
|
|
pub(crate) fn panel(&self, id: PanelId) -> Option<&WorkspacePanel> {
|
|
self.panels.iter().find(|panel| panel.id == id)
|
|
}
|
|
|
|
pub(crate) fn selected(&self) -> PanelId {
|
|
self.selected
|
|
}
|
|
|
|
pub(crate) fn attached_ids(&self) -> Vec<PanelId> {
|
|
self.panels
|
|
.iter()
|
|
.filter(|panel| panel.attached)
|
|
.map(|panel| panel.id)
|
|
.collect()
|
|
}
|
|
|
|
pub(crate) fn detached_ids(&self) -> Vec<PanelId> {
|
|
self.panels
|
|
.iter()
|
|
.filter(|panel| !panel.attached)
|
|
.map(|panel| panel.id)
|
|
.collect()
|
|
}
|
|
|
|
pub(crate) fn attached_count(&self) -> usize {
|
|
self.panels.iter().filter(|panel| panel.attached).count()
|
|
}
|
|
|
|
pub(crate) fn select(&mut self, id: PanelId) -> bool {
|
|
if self.panel(id).is_none_or(|panel| !panel.attached) || self.selected == id {
|
|
return false;
|
|
}
|
|
self.selected = id;
|
|
true
|
|
}
|
|
|
|
pub(crate) fn select_attached_at(&mut self, index: usize) -> bool {
|
|
self.attached_ids()
|
|
.get(index)
|
|
.copied()
|
|
.is_some_and(|id| self.select(id))
|
|
}
|
|
|
|
pub(crate) fn move_horizontal(&mut self, delta: isize) -> bool {
|
|
let attached = self.attached_ids();
|
|
let Some(position) = attached.iter().position(|id| *id == self.selected) else {
|
|
return false;
|
|
};
|
|
let next = position as isize + delta;
|
|
if next < 0 || next >= attached.len() as isize {
|
|
return false;
|
|
}
|
|
self.select(attached[next as usize])
|
|
}
|
|
|
|
pub(crate) fn create(&mut self, kind: PanelKind) -> PanelId {
|
|
let id = PanelId(self.next_id);
|
|
self.next_id += 1;
|
|
let sequence = self
|
|
.panels
|
|
.iter()
|
|
.filter(|panel| panel.kind == kind)
|
|
.count()
|
|
+ 1;
|
|
let title = match kind {
|
|
PanelKind::Terminal => format!("Terminal · {sequence}"),
|
|
PanelKind::Browser => format!("Browser · {sequence}"),
|
|
PanelKind::Markdown => format!("Notes · {sequence}"),
|
|
PanelKind::Review => format!("Review · {sequence}"),
|
|
};
|
|
let selected_position = self
|
|
.panels
|
|
.iter()
|
|
.position(|panel| panel.id == self.selected)
|
|
.expect("selected panel exists");
|
|
self.panels.insert(
|
|
selected_position + 1,
|
|
WorkspacePanel {
|
|
id,
|
|
kind,
|
|
title,
|
|
target: kind.default_target().to_owned(),
|
|
attached: true,
|
|
seed: None,
|
|
},
|
|
);
|
|
self.selected = id;
|
|
id
|
|
}
|
|
|
|
pub(crate) fn detach(&mut self, id: PanelId) -> bool {
|
|
if self.attached_count() == 1 || self.panel(id).is_none_or(|panel| !panel.attached) {
|
|
return false;
|
|
}
|
|
let attached_before = self.attached_ids();
|
|
let detached_position = attached_before
|
|
.iter()
|
|
.position(|candidate| *candidate == id)
|
|
.expect("attached panel appears in attached IDs");
|
|
self.panels
|
|
.iter_mut()
|
|
.find(|panel| panel.id == id)
|
|
.expect("panel exists")
|
|
.attached = false;
|
|
if self.selected == id {
|
|
let remaining = self.attached_ids();
|
|
self.selected = remaining[detached_position.min(remaining.len() - 1)];
|
|
}
|
|
true
|
|
}
|
|
|
|
pub(crate) fn attach(&mut self, id: PanelId) -> bool {
|
|
let Some(panel) = self.panels.iter_mut().find(|panel| panel.id == id) else {
|
|
return false;
|
|
};
|
|
if panel.attached {
|
|
return false;
|
|
}
|
|
panel.attached = true;
|
|
self.selected = id;
|
|
true
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{PanelId, PanelKind, PanelRegistry};
|
|
|
|
#[test]
|
|
fn dynamic_panels_receive_monotonic_ids_and_insert_beside_selection() {
|
|
let mut registry = PanelRegistry::first_run();
|
|
let browser = registry.create(PanelKind::Browser);
|
|
let terminal = registry.create(PanelKind::Terminal);
|
|
assert_eq!(browser, PanelId(7));
|
|
assert_eq!(terminal, PanelId(8));
|
|
assert_eq!(registry.selected(), terminal);
|
|
assert_eq!(registry.panels()[2].id, terminal);
|
|
assert_eq!(registry.panels()[1].id, browser);
|
|
}
|
|
|
|
#[test]
|
|
fn identity_and_detached_state_round_trip_without_reuse() {
|
|
let mut registry = PanelRegistry::first_run();
|
|
let created = registry.create(PanelKind::Markdown);
|
|
assert!(registry.detach(created));
|
|
let json = registry.to_json().unwrap();
|
|
let mut restored = PanelRegistry::from_json(&json).unwrap();
|
|
assert!(restored.detached_ids().contains(&created));
|
|
assert_eq!(restored.create(PanelKind::Review), PanelId(8));
|
|
}
|
|
|
|
#[test]
|
|
fn detach_preserves_panel_and_reattach_restores_same_identity() {
|
|
let mut registry = PanelRegistry::first_run();
|
|
let id = registry.create(PanelKind::Terminal);
|
|
assert!(registry.detach(id));
|
|
assert!(registry.panel(id).is_some());
|
|
assert!(registry.attach(id));
|
|
assert_eq!(registry.selected(), id);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_or_empty_snapshots_are_rejected() {
|
|
assert!(PanelRegistry::from_json("{}").is_err());
|
|
assert!(
|
|
PanelRegistry::from_json(
|
|
r#"{"schema_version":1,"panels":[],"selected":1,"next_id":2}"#
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|