test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s
All checks were successful
Check / eval (push) Successful in 2m57s
- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
capture-float-classes.nix harness (softGL empty); residual portal
class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.
Verified: live-install-entry + windowrule-syntax green.
This commit is contained in:
@@ -28,40 +28,23 @@ in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in
|
||||
|
||||
## NEXT
|
||||
|
||||
### 14. Automated lock bumps — confirm the first real run
|
||||
Slices b+c shipped 2026-07-05 (`.gitea/workflows/bump.yml`): weekly
|
||||
schedule (Mon 05:17 UTC) + `workflow_dispatch` as the security
|
||||
fast-lane; `nix flake update` → eval gate → pathspec-limited lock
|
||||
commit pushed to `main` on green (that push triggers check.yml as the
|
||||
second net). Update/commit/push logic simulated locally end-to-end in
|
||||
a scratch clone, incl. a real update; today's bumped lock evals green.
|
||||
Remaining (not confirmable from a session): the first scheduled or
|
||||
dispatched run must land — watch that the runner picks up the cron and
|
||||
that the Actions token can push. Then delete this item. Item 20's KVM
|
||||
runner later upgrades the gate from eval-only to the VM suite.
|
||||
|
||||
### 20. KVM runner → VM suite in CI `[human]`
|
||||
The remaining stretch of the CI item — checks-on-push is live and
|
||||
**green** (run #58; runner = gitea/act_runner docker, eval tier).
|
||||
Register a second runner on a host with `/dev/kvm` + nix (host-mode
|
||||
label `nix-kvm`), then an agent uncomments the workflow's `vm-checks`
|
||||
job: the `checks.*` VM suite + real toplevel/HM builds on every push
|
||||
that later upgrades item 14's bump gate from eval-only to the full suite.
|
||||
that later upgrades the bump gate from eval-only to the full suite.
|
||||
|
||||
### 41. Floating-window audit — remaining (needs hardware class checks)
|
||||
(Raised by Bernardo, 2026-07-07 — item 11.) Broaden the `windowrule`
|
||||
float set beyond the conservative first cut.
|
||||
_Conservative cut shipped 2026-07-07 (iteration #63, on Bernardo's "take a
|
||||
conservative approach"):_ float + center the mixer (item 35), **blueman**
|
||||
(manager/adapters) and **system-config-printer** — the only shipped,
|
||||
unambiguous config dialogs whose classes are confident. **Remaining
|
||||
candidates, deliberately deferred because their window class can't be
|
||||
verified headlessly** (a guessed class = dead rule): the polkit auth
|
||||
prompt (hyprpolkitagent — no discoverable class in the package), GTK
|
||||
file-chooser portals (`xdg-desktop-portal-gtk`), and any pinentry dialog.
|
||||
Next slice: on hardware, run `hyprctl clients` while each is open, read
|
||||
the real `class`, then append rules. Also revisit whether blueman/s-c-p
|
||||
actually float once seen (regex tolerance for the `.…-wrapped` form).
|
||||
### 41. Floating-window audit — residual GTK portal class `[blocked:hw]`
|
||||
(Raised by Bernardo, 2026-07-07 — item 11.) Conservative cut + 2026-07-10
|
||||
slice shipped: mixer, blueman, system-config-printer, calendar,
|
||||
**hyprpolkitagent** + **pinentry-qt** (classes from package app-id
|
||||
strings; softGL capture harness could not materialize dialogs).
|
||||
**Still open:** GTK file-chooser portal (`xdg-desktop-portal-gtk`) —
|
||||
class not discoverable headlessly. On hardware: open a portal file
|
||||
picker, `hyprctl clients`, append float/center rules if needed; confirm
|
||||
polkit/pinentry/blueman actually float.
|
||||
|
||||
## LATER
|
||||
|
||||
@@ -475,7 +458,7 @@ _(User password minimum length = 8 → #54.)_
|
||||
#### Live ISO discoverability (one durable cue)
|
||||
|
||||
_(Live: one always-visible “Install Nomarchy” action → **#57** ✓ 2026-07-10;
|
||||
V2 pending: live ISO desktop entry smoke.)_
|
||||
V2: `checks.live-install-entry` on live HM gen.)_
|
||||
|
||||
#### Generated config / post-install polish
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ the failure as a NOW bug in BACKLOG.md. Machines: the **AMD dev box**
|
||||
QA machine), the **T14s** (webcam case).
|
||||
|
||||
## Any machine (dev box is fine)
|
||||
- [ ] **#41 float classes (polkit / pinentry / portal / blueman)** — after
|
||||
rebuild: (1) `pkexec true` or mount a disk → polkit dialog should
|
||||
float+center; if not, note `hyprctl clients` class while open.
|
||||
(2) `gpg --sign` or any pinentry prompt → same. (3) App file picker
|
||||
via portal → capture class for residual rule. (4) blueman-manager /
|
||||
system-config-printer still float (`.…-wrapped` tolerance).
|
||||
- [ ] **#55 fingerprint enroll on real reader** — with
|
||||
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
||||
Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional
|
||||
|
||||
@@ -17,6 +17,23 @@ Template:
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-10 — Pending closeout: #14, #57 V2, #41 slice
|
||||
- **Task:** pending V2/CI/float leftovers after A/B/C batch.
|
||||
- **Did:** **#14 closed** — first scheduled lock bump landed
|
||||
`8fded63` (2026-07-06 Mon 05:20 UTC) as `nomarchy-bump` /
|
||||
`chore(lock): scheduled upstream bump` (Actions token push works).
|
||||
**#57 V2** — `checks.live-install-entry` asserts live HM gen has
|
||||
`nomarchy-install.desktop` (Name/Terminal/Exec) + installer bin +
|
||||
Tools menu gate in `nomarchy-menu`. **#41 slice** — float/center rules
|
||||
for hyprpolkitagent + pinentry-qt from package app-id strings;
|
||||
`tools/capture-float-classes.nix` harness (softGL empty clients);
|
||||
residual GTK portal stays NEXT `#41 [blocked:hw]`. HARDWARE-QUEUE
|
||||
float confirm steps added.
|
||||
- **Verified:** **V2** live-install-entry green; windowrule-syntax green.
|
||||
SoftGL class capture inconclusive (empty clients). **V3** float confirm
|
||||
+ fingerprint/#60 still HARDWARE-QUEUE. **#20** still `[human]`.
|
||||
- **Next suggestion:** Bernardo: #20 KVM runner, or HARDWARE-QUEUE pass.
|
||||
|
||||
## 2026-07-10 — #55 fingerprint menu + #56 human rebuild errors
|
||||
- **Task:** NEXT #55 (System › Fingerprint) + #56 (rebuild failure UX).
|
||||
- **Did:** #55 — System row self-gated on fprintd-list; enroll/list/verify/
|
||||
|
||||
34
flake.nix
34
flake.nix
@@ -352,6 +352,40 @@
|
||||
# sheet to ✖/exit-1 and names the unit; with the failure
|
||||
# cleared it reports healthy/exit-0. Minimal node (just the
|
||||
# package) — the disk/flake/snapper checks self-skip in a VM.
|
||||
# Live-ISO install affordance (BACKLOG #57): desktop entry baked
|
||||
# into the live host's HM generation + installer on PATH + Tools
|
||||
# menu gate in nomarchy-menu. Builds the real nomarchy-live HM
|
||||
# package (not a full ISO) — V2 file-level smoke of the same
|
||||
# artifacts the ISO ships.
|
||||
live-install-entry =
|
||||
let
|
||||
liveHm =
|
||||
self.nixosConfigurations.nomarchy-live.config.home-manager.users.${username};
|
||||
gen = liveHm.home.activationPackage;
|
||||
in
|
||||
pkgs.runCommand "nomarchy-live-install-entry"
|
||||
{
|
||||
nativeBuildInputs = [ pkgs.gnugrep pkgs.findutils ];
|
||||
passAsFile = [ ];
|
||||
}
|
||||
''
|
||||
set -euo pipefail
|
||||
desk=$(find ${gen}/home-path/share/applications \
|
||||
-name nomarchy-install.desktop | head -1)
|
||||
test -n "$desk"
|
||||
grep -q '^Name=Install Nomarchy$' "$desk"
|
||||
grep -q '^Terminal=true$' "$desk"
|
||||
grep -q '^Exec=nomarchy-install$' "$desk"
|
||||
test -x ${nomarchyInstall}/bin/nomarchy-install
|
||||
# Tools › Install Nomarchy self-gate lives in nomarchy-menu
|
||||
# (rofi.nix), which is on the live HM path.
|
||||
menu=$(find ${gen}/home-path/bin -name nomarchy-menu | head -1)
|
||||
test -n "$menu"
|
||||
grep -q 'Install Nomarchy' "$menu"
|
||||
echo "live-install-entry: ok desk=$desk menu=$menu"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
doctor = pkgs.testers.runNixOSTest {
|
||||
name = "nomarchy-doctor";
|
||||
nodes.machine = { ... }: {
|
||||
|
||||
@@ -469,12 +469,12 @@ in
|
||||
# Class regexes use `(?i)` and tolerate the XWayland
|
||||
# `.…-wrapped` binary-name form.
|
||||
#
|
||||
# Conservative set on purpose (item 41): only windows the distro
|
||||
# actually ships, each an unambiguous dialog. Deliberately NOT
|
||||
# floated yet — needs on-hardware class verification: the polkit auth
|
||||
# prompt (hyprpolkitagent exposes no discoverable class) and GTK
|
||||
# file-chooser portals. A dead rule is harmless, but guessing a class
|
||||
# is worse than leaving it for a verified pass.
|
||||
# Conservative set (item 41): only dialogs we ship or can name from
|
||||
# package strings / desktop files. SoftGL capture harness could not
|
||||
# materialize polkit/pinentry windows (empty hyprctl clients) — classes
|
||||
# below are from binary/app-id strings (hyprpolkitagent, pinentry-qt).
|
||||
# Remaining: GTK file-chooser portal — class still unknown headlessly;
|
||||
# HARDWARE-QUEUE for live hyprctl clients confirmation.
|
||||
windowrule = [
|
||||
# Audio mixer (item 35): right-click the Waybar volume module.
|
||||
"float 1, match:class ^(com\\.saivert\\.pwvucontrol|org\\.pulseaudio\\.pavucontrol|pavucontrol)$"
|
||||
@@ -490,6 +490,13 @@ in
|
||||
"float 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||
"size 60% 65%, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||
"center 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||
|
||||
# Polkit auth (hyprpolkitagent — app id / binary name in the package)
|
||||
# and pinentry-qt (keys.nix default; org.gnupg.pinentry-qt desktop).
|
||||
"float 1, match:class (?i)^(hyprpolkitagent|\\.hyprpolkitagent-wrapped)$"
|
||||
"center 1, match:class (?i)^(hyprpolkitagent|\\.hyprpolkitagent-wrapped)$"
|
||||
"float 1, match:class (?i)^(pinentry(-qt)?|org\\.gnupg\\.pinentry-qt)$"
|
||||
"center 1, match:class (?i)^(pinentry(-qt)?|org\\.gnupg\\.pinentry-qt)$"
|
||||
];
|
||||
|
||||
# Rendered from ./keybinds.nix (the cheatsheet reads the same list),
|
||||
|
||||
124
tools/capture-float-classes.nix
Normal file
124
tools/capture-float-classes.nix
Normal file
@@ -0,0 +1,124 @@
|
||||
# Capture window classes for BACKLOG #41 float candidates (hyprpolkitagent,
|
||||
# pinentry-qt, optional portal). Headless softGL Hyprland; dumps hyprctl
|
||||
# clients while dialogs are open into $out/classes.json + classes.txt.
|
||||
#
|
||||
# nix build --impure -f tools/capture-float-classes.nix --no-link --print-out-paths
|
||||
#
|
||||
# Maintainer harness — not a checks.* gate (needs interactive dialogs;
|
||||
# softGL may drop some Qt windows).
|
||||
let
|
||||
flake = builtins.getFlake ("git+file://" + toString ../.);
|
||||
inherit (flake.inputs) nixpkgs home-manager;
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
overlays = [ flake.overlays.default ];
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "capture-float-classes";
|
||||
node.pkgsReadOnly = false;
|
||||
node.specialArgs = { username = "nomarchy"; };
|
||||
nodes.machine = { pkgs, lib, ... }: {
|
||||
imports = [ flake.nixosModules.nomarchy home-manager.nixosModules.home-manager ];
|
||||
virtualisation.memorySize = 4096;
|
||||
virtualisation.cores = 2;
|
||||
hardware.graphics.enable = true;
|
||||
environment.variables.LIBGL_ALWAYS_SOFTWARE = "1";
|
||||
boot.initrd.availableKernelModules = [ "virtio_gpu" ];
|
||||
virtualisation.qemu.options = [ "-vga none" "-device virtio-gpu-pci,xres=1280,yres=800" ];
|
||||
|
||||
users.users.nomarchy = {
|
||||
isNormalUser = true;
|
||||
password = "test";
|
||||
extraGroups = [ "wheel" "video" ];
|
||||
};
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
# PolicyKit test action so pkexec can raise the agent.
|
||||
security.polkit.enable = true;
|
||||
|
||||
nomarchy.system.stateFile = flake + "/theme-state.json";
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/theme-state.json"}"
|
||||
];
|
||||
services.greetd.settings.initial_session = {
|
||||
command = "start-hyprland";
|
||||
user = "nomarchy";
|
||||
};
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.users.nomarchy = {
|
||||
imports = [ flake.homeModules.nomarchy ];
|
||||
nomarchy.stateFile = flake + "/theme-state.json";
|
||||
nomarchy.idle.enable = false;
|
||||
# Ensure pinentry-qt is the active pinentry (keys.nix default).
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_file("/run/user/1000/hypr", 180)
|
||||
machine.sleep(15)
|
||||
|
||||
hy_env = "XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1)"
|
||||
hyctl = f"su - nomarchy -c '{hy_env} hyprctl"
|
||||
|
||||
def clients():
|
||||
return machine.succeed(f"{hyctl} clients -j'").strip()
|
||||
|
||||
dumps = []
|
||||
|
||||
# ── pinentry-qt (keys.nix default) ───────────────────────────────
|
||||
machine.execute(
|
||||
f"su - nomarchy -c '{hy_env} timeout 8 pinentry-qt' >/tmp/pinentry.log 2>&1 &"
|
||||
)
|
||||
machine.sleep(3)
|
||||
dumps.append(("pinentry", clients()))
|
||||
machine.execute("pkill -x pinentry-qt >/dev/null 2>&1 || true")
|
||||
machine.sleep(1)
|
||||
|
||||
# ── hyprpolkitagent via pkexec (passwordless sudo wheel — may still
|
||||
# open agent depending on polkit rules; best-effort) ────────────
|
||||
machine.execute(
|
||||
f"su - nomarchy -c '{hy_env} timeout 8 pkexec true' >/tmp/pkexec.log 2>&1 &"
|
||||
)
|
||||
machine.sleep(3)
|
||||
dumps.append(("pkexec", clients()))
|
||||
machine.execute("pkill -x hyprpolkitagent >/dev/null 2>&1 || true")
|
||||
machine.execute("pkill -f pkexec >/dev/null 2>&1 || true")
|
||||
machine.sleep(1)
|
||||
|
||||
# ── Always dump current clients for residual ─────────────────────
|
||||
dumps.append(("idle", clients()))
|
||||
|
||||
import json
|
||||
out_path = machine.succeed("mktemp -d").strip()
|
||||
summary = []
|
||||
for label, raw in dumps:
|
||||
path = f"{out_path}/{label}.json"
|
||||
machine.succeed(f"cat > {path} <<'EOF'\n{raw}\nEOF")
|
||||
try:
|
||||
data = json.loads(raw) if raw else []
|
||||
except Exception as e:
|
||||
data = []
|
||||
summary.append(f"{label}: parse error {e}")
|
||||
classes = sorted({
|
||||
c.get("class") or c.get("initialClass") or ""
|
||||
for c in (data if isinstance(data, list) else [])
|
||||
} - {""})
|
||||
titles = sorted({
|
||||
c.get("title") or c.get("initialTitle") or ""
|
||||
for c in (data if isinstance(data, list) else [])
|
||||
} - {""})
|
||||
summary.append(f"{label}: classes={classes} titles={titles}")
|
||||
|
||||
machine.succeed(
|
||||
"cat > /tmp/float-class-summary.txt <<'EOF'\n"
|
||||
+ "\n".join(summary)
|
||||
+ "\nEOF"
|
||||
)
|
||||
print("\n".join(summary))
|
||||
# Copy into the test result via machine.copy_from_vm if available —
|
||||
# at minimum assert pinentry produced *some* client dump JSON.
|
||||
machine.succeed("test -s /tmp/float-class-summary.txt")
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user