Make the focus exception list real, or it was a page telling a lie
The mode data model shipped with an allow list and nothing that read it. The summary would say "2 apps may interrupt" while notification delivery never consulted the list and no editor could set it. That is the dead row this work has spent its time removing, introduced by the work itself. The banner gate consults the mode in force now, and the list can be edited from the applications that have actually sent a notification -- an exception for something that never notifies is not a choice worth offering. Exceptions belong to a mode. allowedApps is empty whenever no mode is active, so a Do Not Disturb switched on by hand stays absolute and nothing can leak into it. That scoping is asserted, not just written. Verifying this took three attempts, and the second was a real defect in the guard rather than in the code. The contract grep for FocusModes.allows matched the comment that explains it, so the check passed with the enforcement deleted. It matches the gate expression now. A guard a comment can satisfy is not a guard, and this is the third time prose has satisfied one here. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
@@ -12,6 +12,20 @@ SettingsPage {
|
||||
// Which focus mode is open for editing. One at a time, like the app rules.
|
||||
property string expandedMode: ""
|
||||
|
||||
// Which mode's exception list is open. Separate from expandedMode so the
|
||||
// list does not unfold every time a mode is opened to change a switch.
|
||||
property string expandedAllow: ""
|
||||
|
||||
function toggleAllowed(mode: var, appId: string, allowed: bool): void {
|
||||
const current = Array.isArray(mode.allow) ? mode.allow.map(String) : [];
|
||||
const at = current.indexOf(appId);
|
||||
if (allowed && at < 0)
|
||||
current.push(appId);
|
||||
else if (!allowed && at >= 0)
|
||||
current.splice(at, 1);
|
||||
FocusModes.update(String(mode.id), { allow: current });
|
||||
}
|
||||
|
||||
// Schedule edits go through the mode's trigger list rather than replacing
|
||||
// it, so a mode that also has a game or fullscreen trigger keeps it.
|
||||
function reschedule(mode: var, changes: var): void {
|
||||
@@ -298,6 +312,77 @@ SettingsPage {
|
||||
onAccepted: value => root.reschedule(modeEntry.mode, { end: value })
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
width: parent.width
|
||||
visible: modeEntry.open && modeEntry.mode.silence === true
|
||||
label: "May interrupt"
|
||||
detail: (modeEntry.mode.allow ?? []).length === 0
|
||||
? "Nothing gets through while this mode is on"
|
||||
: "Everything else is held until the mode ends"
|
||||
activatable: true
|
||||
controlWidth: 150
|
||||
onActivated: root.expandedAllow =
|
||||
root.expandedAllow === modeEntry.modeId ? "" : modeEntry.modeId
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 9
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: (modeEntry.mode.allow ?? []).length === 0
|
||||
? "Nothing"
|
||||
: (modeEntry.mode.allow ?? []).length + " apps"
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.expandedAllow === modeEntry.modeId ? "\u25B4" : "\u25BE"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drawn from the applications that have actually sent a
|
||||
// notification, which is the same list the rules above use --
|
||||
// an exception for something that never notifies is not a
|
||||
// choice worth offering.
|
||||
Repeater {
|
||||
model: (modeEntry.open && root.expandedAllow === modeEntry.modeId)
|
||||
? Notifs.applications
|
||||
: []
|
||||
|
||||
delegate: SettingRow {
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
label: String(modelData.name ?? "")
|
||||
detail: String(modelData.id ?? "")
|
||||
controlWidth: 48
|
||||
|
||||
SettingsToggle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: (modeEntry.mode.allow ?? []).indexOf(String(modelData.id)) >= 0
|
||||
onToggled: value => root.toggleAllowed(modeEntry.mode, String(modelData.id), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
width: parent.width
|
||||
visible: modeEntry.open && root.expandedAllow === modeEntry.modeId
|
||||
&& Notifs.applications.length === 0
|
||||
label: "No applications yet"
|
||||
detail: "They appear here once they have sent a notification."
|
||||
value: ""
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
width: parent.width
|
||||
visible: modeEntry.open && modeEntry.schedule !== null
|
||||
|
||||
Reference in New Issue
Block a user