forked from 1b-agent/fieldcraft-upstream
UI: ask once about anonymous usage; Help › Settings › Privacy (#238)
The first run asks whether to share anonymous usage (only when the host says this run may), the answer and a random install id are kept in the preferences, and Help › Settings… changes it. Commands are counted by where they came from (ui, palette, script; mcp when the MCP server says so), and cancelled forms and clicks on commands that cannot run yet are recorded. The preferences go through a PrefsStore (a file, or the browser's storage). Control channel: ui.settings, ui.prompt answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
162bb421f1
commit
79d583ea0f
8 files changed
+444
-36
No files matched your search
@@ -158,6 +158,18 @@ fn select(app: &mut ModelApp, p: &Value) -> Result<Value, String> {
|
||||
Ok(selection_json(app))
|
||||
}
|
||||
|
||||
/// The privacy choices, for `ui.inspect` and `ui.prompt`.
|
||||
fn privacy_json(app: &ModelApp) -> Value {
|
||||
let p = app.prefs();
|
||||
json!({
|
||||
"available": app.usage_available(),
|
||||
"usage_sharing": p.usage_sharing,
|
||||
"usage_automation": p.usage_automation,
|
||||
"recording": fieldcraft_engine::usage::enabled(),
|
||||
"queued": fieldcraft_engine::usage::queued(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) -> Outcome {
|
||||
let p = &req.params;
|
||||
let s = |k: &str| p.get(k).and_then(Value::as_str);
|
||||
@@ -206,11 +218,18 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
crate::files::Prompt::Delete(d) => json!({"delete": d.question(), "kind": d.kind, "name": d.name, "users": d.users, "allowed": d.allowed()}),
|
||||
crate::files::Prompt::Crashed(r) => json!({"crashed": r}),
|
||||
crate::files::Prompt::ReportBug => json!({"report_bug": app.report.as_ref().and_then(|f| f.drafted_json())}),
|
||||
crate::files::Prompt::Consent => json!({"consent": crate::privacy::QUESTION}),
|
||||
crate::files::Prompt::Settings => json!("settings"),
|
||||
}),
|
||||
"privacy": privacy_json(app),
|
||||
"recent": app.recent.paths,
|
||||
"version": crate::VERSION,
|
||||
})),
|
||||
"ui.toolbar" => ok(crate::toolbar::describe(app)),
|
||||
"ui.settings" => {
|
||||
app.open_settings();
|
||||
ok(privacy_json(app))
|
||||
}
|
||||
"ui.select" if p.get("kind").is_none() && VIEWPORT_SELECT.iter().any(|k| p.get(*k).is_some()) => wrap(select(app, p)),
|
||||
"ui.selection" => ok(selection_json(app)),
|
||||
"ui.select" => {
|
||||
@@ -388,6 +407,23 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
}
|
||||
// The form's fields; sending opens the browser, which only a click does.
|
||||
(crate::files::Prompt::ReportBug, Some("set")) => wrap(app.set_report(p)),
|
||||
(crate::files::Prompt::Consent, Some(a @ ("share" | "decline"))) => {
|
||||
app.answer_consent(a == "share");
|
||||
ok(privacy_json(app))
|
||||
}
|
||||
(crate::files::Prompt::Settings, Some("set")) => {
|
||||
if !app.usage_available() {
|
||||
return err("this run cannot share usage (no analytics key, or FIELDCRAFT_TELEMETRY=0, CI or automation)");
|
||||
}
|
||||
let sharing = p.get("usage_sharing").and_then(Value::as_bool).unwrap_or(app.prefs().usage_sharing == Some(true));
|
||||
let automation = p.get("usage_automation").and_then(Value::as_bool).unwrap_or(app.prefs().usage_automation);
|
||||
app.set_usage(sharing, automation);
|
||||
ok(privacy_json(app))
|
||||
}
|
||||
(crate::files::Prompt::Settings, Some("close" | "cancel")) => {
|
||||
app.prompt = None;
|
||||
ok(Value::Null)
|
||||
}
|
||||
(crate::files::Prompt::ReportBug, Some("close" | "cancel")) => {
|
||||
app.prompt = None;
|
||||
app.report = None;
|
||||
@@ -401,7 +437,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
None => err("missing `id` (from file.recovery.list)"),
|
||||
},
|
||||
(_, a) => err(format!(
|
||||
"`answer` {a:?} does not answer this prompt (unsaved: save, discard, cancel; design: new, keep, cancel; replace_mesh: replace, cancel; delete: delete, cancel; recover: recover with id, close; samples: open with name, close; crashed: report, close; report_bug: set with title/observed/share_session/include_model, close)"
|
||||
"`answer` {a:?} does not answer this prompt (unsaved: save, discard, cancel; design: new, keep, cancel; replace_mesh: replace, cancel; delete: delete, cancel; recover: recover with id, close; samples: open with name, close; crashed: report, close; report_bug: set with title/observed/share_session/include_model, close; consent: share, decline; settings: set with usage_sharing/usage_automation, close)"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user