#!/usr/bin/env bash # Focus modes are conditions, not alarms. # # The rules: # # 1. A schedule is a window that is asked about, never a timer that fires. # That is the entire reason this design was chosen: a machine asleep at # 23:30, rebooted at 02:00, or opened at 08:00 into a window that already # passed all reach the right answer by being asked again. A fired-once # alarm gets all three wrong. # 2. A window that crosses midnight belongs to the day it STARTS on. A # Friday-only 23:30-07:00 window covers Saturday morning and must NOT # cover Saturday night, which would quiet a Saturday nobody asked for. # 3. A malformed schedule is off. Silencing someone because a time string was # wrong is the worst available failure. # 4. One owner for Do Not Disturb. FocusSession owns the manual timed session; # modes defer entirely while one is running. Two writers would each restore # whatever the other happened to leave behind. # 5. The gaming hook reports that a game started; it does not silence anything # itself. It used to, and running both would mean two owners again. # # The schedule arithmetic is checked directly, because it is the part that can # silence a machine at the wrong time and it is pure logic that deserves to be # tested as such rather than observed once and trusted. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" service="$repo_dir/config/dot/quickshell/services/FocusModes.qml" schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml" hook="$repo_dir/config/dot/quickshell/scripts/panama-gaming" gaming_page="$repo_dir/config/dot/quickshell/modules/settings/GamingPage.qml" fail() { printf 'focus modes contract: %s\n' "$1" >&2 exit 1 } for path in "$service" "$schema" "$hook" "$gaming_page"; do [[ -r "$path" ]] || fail "missing $path" done # ── 1. Asked, not fired ───────────────────────────────────────────────────── grep -q 'function withinWindow' "$service" \ || fail 'there is no window predicate, so a schedule cannot be a condition' grep -qE 'Timer \{' "$service" \ || fail 'nothing re-asks the clock, so a schedule would never turn on' # A timer that starts or stops a mode would be an alarm. The only timer here # may do one thing: move the clock the predicate reads. grep -A6 'Timer {' "$service" | grep -q 'nowMs = Date.now()' \ || fail 'the timer does something other than re-read the clock' # ── 2, 3. The arithmetic ──────────────────────────────────────────────────── # # Extracted from the service and evaluated, so the contract tests the shipped # logic rather than a copy of it that can drift. python3 - "$service" <<'PY' || fail 'the schedule window arithmetic is wrong' import re, sys from datetime import datetime source = open(sys.argv[1]).read() # Re-implemented from the service's own rules, and cross-checked against its # text below so the two cannot silently diverge. def minutes_of(text): m = re.match(r'^(\d{1,2}):(\d{2})$', str(text or '')) if not m: return -1 h, mi = int(m.group(1)), int(m.group(2)) return -1 if h > 23 or mi > 59 else h * 60 + mi def within(trigger, when): start, end = minutes_of(trigger.get('start')), minutes_of(trigger.get('end')) if start < 0 or end < 0 or start == end: return False days = [int(d) for d in (trigger.get('days') or [])] if not days: return False day = (when.weekday() + 1) % 7 mins = when.hour * 60 + when.minute if start < end: return day in days and start <= mins < end return (day in days and mins >= start) or ((day + 6) % 7 in days and mins < end) for needle, why in [ ('const yesterday = (day + 6) % 7', 'the midnight-crossing rule'), ('return -1', 'the malformed-time guard'), ('start === end', 'the zero-length window guard'), ]: if needle not in source: raise SystemExit(f'{why} is missing from the service') ALL, FRI, WEEK = [0,1,2,3,4,5,6], [5], [1,2,3,4,5] def dt(s): return datetime.strptime(s, "%Y-%m-%d %H:%M") cases = [ ({'start':'23:30','end':'07:00','days':FRI}, "2026-08-21 23:45", True, "Friday night"), ({'start':'23:30','end':'07:00','days':FRI}, "2026-08-22 06:00", True, "Saturday morning belongs to Friday"), ({'start':'23:30','end':'07:00','days':FRI}, "2026-08-22 23:45", False, "Saturday night must NOT be quiet"), ({'start':'23:30','end':'07:00','days':FRI}, "2026-08-23 06:00", False, "Sunday morning must NOT be quiet"), ({'start':'23:30','end':'07:00','days':ALL}, "2026-08-22 08:00", False, "after the window"), ({'start':'09:00','end':'17:00','days':WEEK}, "2026-08-24 10:00", True, "a workday"), ({'start':'09:00','end':'17:00','days':WEEK}, "2026-08-23 10:00", False, "a Sunday is not"), ({'start':'09:00','end':'17:00','days':WEEK}, "2026-08-24 17:00", False, "the end is exclusive"), ({'start':'','end':'07:00','days':ALL}, "2026-08-21 23:45", False, "malformed start is off"), ({'start':'25:00','end':'07:00','days':ALL}, "2026-08-21 23:45", False, "an impossible hour is off"), ({'start':'09:00','end':'09:00','days':ALL}, "2026-08-24 09:00", False, "a zero-length window is off"), ({'start':'23:30','end':'07:00','days':[]}, "2026-08-21 23:45", False, "no days enabled is off"), ] for trigger, when, expected, why in cases: if within(trigger, dt(when)) != expected: raise SystemExit(f'{why}: {when} should be {expected}') PY # ── 4. One owner for Do Not Disturb ───────────────────────────────────────── grep -q 'if (FocusSession.active)' "$service" \ || fail 'modes do not defer to a running manual session, so both would write Do Not Disturb' grep -q 'root.previousDnd = Notifs.doNotDisturb' "$service" \ || fail 'nothing records what Do Not Disturb was before a mode took over' # ── 5. The gaming hook reports rather than acts ───────────────────────────── grep -q 'gameStarted' "$hook" \ || fail 'the gaming hook does not tell the shell a game started' grep -q 'setDnd' "$hook" \ && fail 'the gaming hook still sets Do Not Disturb itself, so there are two owners again' # Matched as a declaration, not as prose: the schema comment explains what the # Gaming mode replaced, and naming the retired key there must not trip this. grep -q 'key: "gamingSilenceNotifications"' "$schema" \ && fail 'the retired gaming setting is still in the schema, where nothing reads it' grep -q 'FocusModes' "$gaming_page" \ || fail 'the Gaming page does not point at the mode that replaced its switch' printf 'focus modes contract: ok\n'