Drop the extension, and give the test suite a front door

Phase 6, the last of the fresh-install spec.

159 scripts lose their .sh: 110 contracts, 47 Vicinae commands, 2 compositor
contracts. A shebang and the executable bit already select the interpreter. The
extension only ever added something that had to stay in sync, and the rename
proved the point twice over in the space of an hour.

The spec's stated risk was Vicinae's script discovery. One script was renamed and
reloaded on its own before the other 46 followed; it came back as
scripts:panama.capture and all 47 resolve. What the probe turned up instead is
that the extension was never only a filename: Vicinae's command IDs embed it, so
every ID changed. Nothing in this repository refers to them, so nothing breaks.
The only trace is Vicinae's metadata.json, whose visited map had two Panama
entries that are now orphaned -- two commands lost their usage ranking and will
earn it back. Worth knowing before anyone renames these again on a machine that
has a keybind pointing at one.

Rewriting the references by exact filename missed two things it structurally
could not see: a name built from a variable, settings-$page.sh, and a glob,
-name '*.sh'. Both were in the contract that counts the generated commands, which
promptly reported 47 expected and 0 found. The mechanical part of a rename is the
part that looks finished.

The three subcommands. panama doctor fronts a health check that already existed
and already ran at the end of every install but could not be reached from a
terminal. panama upgrade re-runs the installer from anywhere. panama test runs
the suite, which had no entry point at all -- 121 files that were the main safety
net in this repository and were invisible in it.

Writing that runner found three tests nothing was running.
calendar_agenda_bridge_test, home_assistant_bridge_test and kdeconnect_bridge_test
are unittest suites without the executable bit, so no contract invoked them and
the first draft of the runner skipped them silently. All three pass, and have
passed unobserved for weeks. The runner collects *_test.py as well now, because a
runner with a blind spot is worse than no runner for the same reason a dependency
checker with one is: it reports PASS.

Six worktrees pruned. Each was re-checked rather than trusted to the spec's list,
and two needed it: panama-commands is not on feat/panama-commands but on
feat/gnome-tweaks-parity, and fix/panama-displays-review reads [ahead 3] -- ahead
of its remote, not of main, with every commit patch-equivalent to landed work.
roadmap-completion stays; it has five commits that are genuinely unlanded. The
branches are left alone: pruning a worktree costs nothing, deleting a branch is a
decision.

121 contracts pass.

Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
This commit is contained in:
Gabriel Brown
2026-08-20 21:55:55 -04:00
parent 47f29f9fa9
commit e1faaf7a76
185 changed files with 533 additions and 377 deletions
+238
View File
@@ -0,0 +1,238 @@
#!/usr/bin/env bash
# The Dock on an edge other than the bottom, and on chosen screens.
#
# The rules:
#
# 1. A dock spans the edge it lives on, which means anchoring BOTH ends of
# that edge. Anchoring one leaves the surface free to collapse to its
# implicit size on that axis -- the first side dock written here came out
# one pixel tall, looked like nothing had rendered, and passed every static
# check. So the geometry is measured, not read.
# 2. Bottom is unchanged. Somebody who never touches the setting must get the
# dock they already had, byte for byte in behaviour.
# 3. Only one axis gets an implicit size. Setting both fights the anchors.
# 4. An empty screen list means every screen. A list of names goes stale the
# moment a display is unplugged, so "all" must not be spelled as one.
# 5. Reordering pins never loses or duplicates an entry.
# 6. The drag grip sets preventStealing. Without it the settings page's own
# Flickable claims the vertical gesture and the row never moves -- which is
# the exact objection this feature was refused over for a long time.
# 7. The point that reveals the dock is still inside the input region once the
# dock has revealed. The region follows the body, and a bottom dock is
# centred while its reveal strip spans the whole edge -- so a region that
# forgets the body's own offset lands somewhere the pointer is not, hover
# drops on the frame the dock arrives, and it hides under a still cursor.
#
# The geometry checks launch isolated shells against a temporary config. The
# real settings are read to build them and never written.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
shell_dir="$repo_dir/config/dot/quickshell"
dock="$shell_dir/modules/dock/Dock.qml"
body="$shell_dir/modules/dock/DockBody.qml"
editor="$shell_dir/modules/settings/DockPinsEditor.qml"
fail() {
printf 'dock position contract: %s\n' "$1" >&2
exit 1
}
for path in "$dock" "$body" "$editor"; do
[[ -r "$path" ]] || fail "missing $path"
done
work="$(mktemp -d)"
trap 'rm -rf "$work" "$shell_dir/dock-position-probe.qml"' EXIT
# ── 3, 4, 6. What can be read ───────────────────────────────────────────────
grep -q 'implicitHeight: root.vertical ? 0 :' "$dock" \
|| fail 'the dock sets an implicit height on both orientations, which fights the anchors'
grep -q 'implicitWidth: root.vertical ? ' "$dock" \
|| fail 'the dock has no implicit width for a side position'
grep -q 'wanted.length === 0' "$dock" \
|| fail 'an empty screen list is not treated as every screen'
grep -q 'preventStealing: true' "$editor" \
|| fail 'the drag grip does not set preventStealing, so the page will scroll instead of reordering'
# The arrows are the keyboard-reachable path and predate the grip. A grip is not
# a replacement for them.
grep -q 'text: "↑"' "$editor" \
|| fail 'the move-up button was removed, leaving no keyboard-reachable reorder'
# ── 5. Reordering keeps every entry, exactly once ───────────────────────────
python3 - <<'PY' || fail 'reordering loses or duplicates a pinned application'
def drag_to(working, dragging, target):
if dragging < 0 or target == dragging:
return working, dragging
if target < 0 or target >= len(working):
return working, dragging
nxt = working[:]
nxt.insert(target, nxt.pop(dragging))
return nxt, target
base = ["a", "b", "c", "d", "e"]
for start, targets in [(0, [1, 2, 3, 4]), (4, [3, 2, 1, 0]), (2, [3]), (2, [1]),
(0, [3, 3, 3]), (1, [-1]), (3, [99])]:
working, dragging = base[:], start
for target in targets:
working, dragging = drag_to(working, dragging, target)
if sorted(working) != sorted(base) or len(working) != len(base):
raise SystemExit(f'starting at {start} through {targets} produced {working}')
PY
# ── 1, 2, 7. Geometry, measured ────────────────────────────────────────────────
cat >"$shell_dir/dock-position-probe.qml" <<'QML'
import Quickshell
import QtQuick
import qs.modules.dock
ShellRoot {
Dock { id: probe }
// The input region is measured in both states, because the bug it exists
// for lives in the transition between them: a region that stops covering
// the pointer the moment the dock arrives takes the hover away with it.
function report(state) {
const m = probe.mask.item;
console.warn("MASKGEOM " + probe.position + " " + state
+ " surfaceW=" + Math.round(probe.width)
+ " surfaceH=" + Math.round(probe.height)
+ " x=" + Math.round(m.x) + " y=" + Math.round(m.y)
+ " w=" + Math.round(m.width) + " h=" + Math.round(m.height));
}
Timer {
interval: 1200; running: true
onTriggered: {
console.warn("DOCKGEOM " + probe.position
+ " vertical=" + probe.vertical
+ " w=" + Math.round(probe.width)
+ " h=" + Math.round(probe.height));
probe.revealed = false;
hidden.start();
}
}
// Long enough for the slide to finish; the region follows the body, so
// measuring mid-animation measures nothing in particular.
Timer {
id: hidden
interval: 400
onTriggered: {
report("hidden");
probe.revealed = true;
shown.start();
}
}
Timer {
id: shown
interval: 400
onTriggered: {
report("revealed");
Qt.quit();
}
}
}
QML
settings_source="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
[[ -r "$settings_source" ]] || fail 'no settings to build a probe configuration from'
measure() {
local position="$1"
python3 - "$settings_source" "$work/panama/settings.json" "$position" <<'PY'
import json, pathlib, sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
data["dockPosition"] = sys.argv[3]
out = pathlib.Path(sys.argv[2])
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(data))
PY
( cd "$shell_dir" && XDG_CONFIG_HOME="$work" timeout 40 qs -p ./dock-position-probe.qml 2>&1 ) \
| grep -oE '(DOCKGEOM|MASKGEOM) .*'
}
bottom="$(measure bottom)"
[[ -n "$bottom" ]] || fail 'the bottom dock produced no geometry at all'
left="$(measure left)"
[[ -n "$left" ]] || fail 'the left dock produced no geometry at all'
right="$(measure right)"
[[ -n "$right" ]] || fail 'the right dock produced no geometry at all'
# The span checks read the window; the region checks read every line.
first_dock() { grep -o 'DOCKGEOM .*' <<<"$1" | head -1; }
python3 - "$(first_dock "$bottom")" "$(first_dock "$left")" <<'PY' || fail 'a dock does not span the edge it lives on'
import re, sys
def read(line):
m = re.search(r'DOCKGEOM (\w+) vertical=(\w+) w=(\d+) h=(\d+)', line)
if not m:
raise SystemExit(f'unreadable probe output: {line!r}')
return m.group(1), m.group(2) == 'true', int(m.group(3)), int(m.group(4))
_, bottom_vertical, bottom_w, bottom_h = read(sys.argv[1])
_, left_vertical, left_w, left_h = read(sys.argv[2])
if bottom_vertical:
raise SystemExit('the bottom dock reports itself as vertical')
if not left_vertical:
raise SystemExit('the left dock does not report itself as vertical')
# The bug this exists for: a side dock anchored at one end only collapses to a
# sliver on the axis it should span.
if left_h <= bottom_h:
raise SystemExit(f'the left dock is {left_h}px tall and does not span the screen')
if bottom_w <= left_w:
raise SystemExit(f'the bottom dock is {bottom_w}px wide and does not span the screen')
if left_w >= bottom_w or bottom_h >= left_h:
raise SystemExit('the two orientations are not thin on opposite axes')
PY
# ── 7. The pointer that reveals the dock is still inside the region ─────────
python3 - "$bottom" "$left" "$right" <<'REGION' || fail 'revealing the dock moves the input region off the pointer that revealed it'
import re, sys
FIELDS = re.compile(
r'MASKGEOM (\w+) (\w+) surfaceW=(\d+) surfaceH=(\d+) '
r'x=(-?\d+) y=(-?\d+) w=(-?\d+) h=(-?\d+)')
def regions(blob):
found = {}
for line in blob.splitlines():
m = FIELDS.search(line)
if m:
found[m.group(2)] = (m.group(1),) + tuple(int(g) for g in m.groups()[2:])
return found
# Where a hand actually goes to summon the dock: the middle of the edge it
# lives on, a pixel in from that edge.
def aim(position, surface_w, surface_h):
if position == "left":
return 1, surface_h / 2
if position == "right":
return surface_w - 1, surface_h / 2
return surface_w / 2, surface_h - 1
for blob in sys.argv[1:]:
found = regions(blob)
for state in ("hidden", "revealed"):
if state not in found:
raise SystemExit(f'the probe reported no {state} input region')
position, surface_w, surface_h, x, y, w, h = found[state]
px, py = aim(position, surface_w, surface_h)
if not (x <= px <= x + w and y <= py <= y + h):
raise SystemExit(
f'the {position} dock {state} takes input over x {x}..{x + w}, '
f'y {y}..{y + h}, which does not contain the pointer at '
f'({px:.0f}, {py:.0f})')
REGION
printf 'dock position contract: ok\n'