The safety layer: two presses for anything you cannot take back

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-25 00:39:07 -04:00
parent 8b1205e4b8
commit 88371d19f0
17 changed files with 447 additions and 36 deletions
@@ -38,6 +38,24 @@ SettingsPage {
property string pendingInterface: ""
property string pendingZone: ""
// The default is the same change with a wider blast radius: it decides for
// every connection that does not ask for a zone by name. It used to apply
// on the pick, one dropdown below a picker that made you confirm.
property string pendingDefaultZone: ""
// Two armed changes on screen at once is how the wrong one gets pressed.
function armInterfaceMove(iface: string, zoneName: string): void {
root.pendingDefaultZone = "";
root.pendingInterface = iface;
root.pendingZone = zoneName;
}
function armDefaultZone(zoneName: string): void {
root.pendingInterface = "";
root.pendingZone = "";
root.pendingDefaultZone = zoneName;
}
// The zone being read in the browser. Read-only: this looks at a zone
// without applying it to anything.
property string browsingZone: ""
@@ -416,10 +434,7 @@ SettingsPage {
options: root.zoneOptions
current: placement.zone
divider: !placement.pending
onPicked: value => {
root.pendingInterface = placement.iface;
root.pendingZone = String(value);
}
onPicked: value => root.armInterfaceMove(placement.iface, String(value))
}
SettingRow {
@@ -482,7 +497,45 @@ SettingsPage {
enabled: !Firewall.busy
options: root.zoneOptions
current: Firewall.defaultZone
onPicked: value => Firewall.setDefaultZone(String(value))
divider: root.pendingDefaultZone === ""
onPicked: value => root.armDefaultZone(String(value))
}
SettingRow {
width: parent.width
visible: root.pendingDefaultZone !== ""
label: "Make " + root.pendingDefaultZone + " the default?"
// What moves is named the way the per-interface confirm names its
// interface: not "the default changes", but which machines end up
// deciding differently because of it.
detail: "Every connection firewalld has not placed in a zone of its "
+ "own follows the default — each one leaves " + Firewall.defaultZone
+ " for " + root.pendingDefaultZone
+ ", and so does every network joined from now on."
controlWidth: 240
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 8
SettingsButton {
text: "Keep " + Firewall.defaultZone
enabled: !Firewall.busy
onClicked: root.pendingDefaultZone = ""
}
SettingsButton {
text: "Change it"
tone: "danger"
enabled: !Firewall.busy
onClicked: {
const target = root.pendingDefaultZone;
root.pendingDefaultZone = "";
Firewall.setDefaultZone(target);
}
}
}
}
// ── The zone browser ─────────────────────────────────────────────────