Two things, because the second could not be built on the first. The schema stamp was part of the same execute_batch as the CREATE TABLE IF NOT EXISTS statements, and it wrote unconditionally. Opening an older file therefore added no columns but flipped the version forward anyway; opening a *newer* file stamped it back down and then wrote rows the newer build could not read. Both produced a database whose recorded version was a lie, and every future schema change would have inherited it. Now the version is read before anything is applied, migrations are ordered and forward-only inside one transaction, a newer file is refused with SchemaTooNew rather than downgraded, and a supported version raised without a step to reach it fails at the first open instead of claiming success. Tested by stamping a file at version 99 and asserting both the refusal and that the stamp is left untouched. lumbridge-settings resolves compiled default -> settings.toml -> environment. The environment sits above the file deliberately: decision 0016 calls LUMBRIDGE_CLAUDE_OAUTH=0 "one switch off", and a switch a config file can silently re-enable is not a switch. A pinned value renders disabled and names the variable, rather than accepting an edit that would do nothing. Every field carries a WriteAuthority. Routing all writes through Configure is the obvious design and would hand a layout-only agent the program every future pane launches — the guarantee decision 0006 exists to make. Anything naming a program, path or destination is Human-only, asserted by a test that reads the path rather than trusting the author. Four paths are permanently not settings, with the reason recorded beside each and a test asserting their absence: the usage endpoint URL, the credentials path, the client identity, and the shell program. A configuration file that can redirect where an access token is sent is a credential exfiltration path with a friendly name. Environment access is a trait rather than std::env, because the workspace forbids unsafe, set_var is unsafe in Rust 2024, and the layering rule has to be testable without mutating the process running the test. Verified live with LUMBRIDGE_CLAUDE_OAUTH=0: the account-endpoint row reads off, greyed, "pinned by LUMBRIDGE_CLAUDE_OAUTH". The Advanced page names every file, endpoint and child process Lumbridge touches and states that nothing is sent anywhere else — as a fact, not as a toggle nobody can flip. File loading, comment-preserving writes and editable controls are not in this pass; 0022 records why that order is the honest one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
237 lines
8.0 KiB
Rust
237 lines
8.0 KiB
Rust
//! The settings pane.
|
|
//!
|
|
//! Four pages that describe code which actually runs. Every row says three
|
|
//! things a settings interface usually leaves out: where the value came from,
|
|
//! when a change takes effect, and who is allowed to make it.
|
|
//!
|
|
//! A control whose value was pinned by the environment is drawn disabled with
|
|
//! the variable named, rather than accepting an edit that would silently do
|
|
//! nothing — decision 0016 calls that switch "one switch off", and an interface
|
|
//! that lets a file or a click override it has quietly broken the promise.
|
|
|
|
use gpui::prelude::*;
|
|
use gpui::{div, px};
|
|
use lumbridge_settings::{Page, SETTINGS, SettingItem, Settings, WriteAuthority};
|
|
|
|
use crate::theme::ThemeColors;
|
|
|
|
/// Everything one row needs to say.
|
|
struct Row<'a> {
|
|
item: &'a SettingItem,
|
|
value: String,
|
|
origin: String,
|
|
editable: bool,
|
|
}
|
|
|
|
/// One row: what it is, where it came from, when it applies.
|
|
fn setting_row(row: &Row<'_>, theme: ThemeColors) -> gpui::AnyElement {
|
|
let Row {
|
|
item,
|
|
value,
|
|
origin,
|
|
editable,
|
|
} = row;
|
|
let (label, detail, takes_effect, authority) = (
|
|
item.label,
|
|
item.detail,
|
|
item.takes_effect.label(),
|
|
item.authority,
|
|
);
|
|
let editable = *editable;
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap(px(2.0))
|
|
.px_3()
|
|
.py_2()
|
|
.border_b_1()
|
|
.border_color(theme.border_quiet)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_2()
|
|
.child(
|
|
div()
|
|
.flex_1()
|
|
.min_w_0()
|
|
.truncate()
|
|
.text_sm()
|
|
.text_color(if editable { theme.text } else { theme.muted })
|
|
.child(label.to_owned()),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex_none()
|
|
.px(px(5.0))
|
|
.rounded(px(3.0))
|
|
.text_xs()
|
|
.bg(theme.surface_raised)
|
|
.text_color(if editable { theme.accent } else { theme.muted })
|
|
.child(value.to_owned()),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(theme.muted)
|
|
.child(detail.to_owned()),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_2()
|
|
.text_xs()
|
|
.text_color(theme.muted)
|
|
.child(origin.to_owned())
|
|
.child("·")
|
|
.child(takes_effect.to_owned())
|
|
.when(authority == WriteAuthority::Human, |view| {
|
|
view.child("·").child(
|
|
div()
|
|
.text_color(theme.attention)
|
|
.child("a person must set this"),
|
|
)
|
|
}),
|
|
)
|
|
.into_any_element()
|
|
}
|
|
|
|
/// Renders one page.
|
|
pub(crate) fn page(page: Page, settings: &Settings, theme: ThemeColors) -> gpui::AnyElement {
|
|
let value_for = |path: &str| -> (String, String, bool) {
|
|
match path {
|
|
"appearance.theme" => (
|
|
settings.theme.value.clone(),
|
|
settings.theme.origin.label(),
|
|
settings.theme.origin.is_editable(),
|
|
),
|
|
"appearance.accent" => (
|
|
settings.accent.value.clone(),
|
|
settings.accent.origin.label(),
|
|
settings.accent.origin.is_editable(),
|
|
),
|
|
"usage.claude_account_endpoint" => (
|
|
on_off(settings.claude_account_endpoint.value),
|
|
settings.claude_account_endpoint.origin.label(),
|
|
settings.claude_account_endpoint.origin.is_editable(),
|
|
),
|
|
"usage.claude_transcripts" => (
|
|
on_off(settings.claude_transcripts.value),
|
|
settings.claude_transcripts.origin.label(),
|
|
settings.claude_transcripts.origin.is_editable(),
|
|
),
|
|
"usage.codex_app_server" => (
|
|
on_off(settings.codex_app_server.value),
|
|
settings.codex_app_server.origin.label(),
|
|
settings.codex_app_server.origin.is_editable(),
|
|
),
|
|
"usage.claude_rate_limit_feed" => (
|
|
settings
|
|
.claude_rate_limit_feed
|
|
.value
|
|
.clone()
|
|
.unwrap_or_else(|| "wherever the bridge was installed".to_owned()),
|
|
settings.claude_rate_limit_feed.origin.label(),
|
|
settings.claude_rate_limit_feed.origin.is_editable(),
|
|
),
|
|
"terminal.allow_osc52_clipboard" => (
|
|
on_off(settings.allow_osc52_clipboard.value),
|
|
settings.allow_osc52_clipboard.origin.label(),
|
|
settings.allow_osc52_clipboard.origin.is_editable(),
|
|
),
|
|
_ => ("—".to_owned(), "unknown".to_owned(), false),
|
|
}
|
|
};
|
|
|
|
let rows: Vec<gpui::AnyElement> = SETTINGS
|
|
.iter()
|
|
.filter(|item| item.page == page)
|
|
.map(|item| {
|
|
let (value, origin, editable) = value_for(item.path);
|
|
setting_row(
|
|
&Row {
|
|
item,
|
|
value,
|
|
origin,
|
|
editable,
|
|
},
|
|
theme,
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.size_full()
|
|
.overflow_hidden()
|
|
.children(rows)
|
|
.when(page == Page::Advanced, |view| {
|
|
view.child(what_lumbridge_reads(settings, theme))
|
|
})
|
|
.into_any_element()
|
|
}
|
|
|
|
fn on_off(value: bool) -> String {
|
|
if value { "on" } else { "off" }.to_owned()
|
|
}
|
|
|
|
/// The Advanced page's disclosure.
|
|
///
|
|
/// Stated as fact rather than offered as a toggle. "No telemetry" is not a
|
|
/// setting anyone can turn on, so presenting it as one would be theatre.
|
|
fn what_lumbridge_reads(settings: &Settings, theme: ThemeColors) -> gpui::AnyElement {
|
|
let reads = [
|
|
"~/.claude/projects — session transcripts, four token counters per record",
|
|
"the status-line feed — the two subscription windows, written by the installed bridge",
|
|
"~/.claude/.credentials.json — the access token, only when the account endpoint is on",
|
|
"GET api.anthropic.com/api/oauth/usage — your own account, identified as lumbridge",
|
|
"codex app-server — launched as a child, asked only for its quota windows",
|
|
];
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap(px(2.0))
|
|
.px_3()
|
|
.py_2()
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(theme.muted)
|
|
.child("WHAT LUMBRIDGE READS"),
|
|
)
|
|
.children(reads.into_iter().map(|line| {
|
|
div()
|
|
.text_xs()
|
|
.text_color(theme.muted)
|
|
.child(line.to_owned())
|
|
}))
|
|
.child(
|
|
div()
|
|
.mt_2()
|
|
.text_xs()
|
|
.text_color(theme.success)
|
|
.child("Nothing is sent anywhere else. There is no telemetry to turn off."),
|
|
)
|
|
.when(!settings.unknown.is_empty(), |view| {
|
|
view.child(
|
|
div()
|
|
.mt_2()
|
|
.text_xs()
|
|
.text_color(theme.attention)
|
|
.child(format!(
|
|
"{} key(s) in settings.toml were not understood by this build and were left alone: {}",
|
|
settings.unknown.len(),
|
|
settings
|
|
.unknown
|
|
.iter()
|
|
.map(|key| key.path.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)),
|
|
)
|
|
})
|
|
.into_any_element()
|
|
}
|