feat(session): first-boot welcome toast (#81)
All checks were successful
Check / eval (push) Successful in 4m23s

One-shot user unit fires a dismissible "You're set" notification on the
first graphical session (SUPER+M / SUPER+T / SUPER+? + Network + Doctor),
then writes settings.firstBootShown into the flake checkout. Skips the
live ISO hostname (hosts/live.nix keeps its own toast). Toggle:
nomarchy.firstBootWelcome.enable. checks.first-boot covers the gate.

Verified: V2 — flake check --no-build; checks.first-boot green; local
shim smoke; downstream-template-home ships the unit.
This commit is contained in:
Bernardo Magri
2026-07-11 10:19:46 +01:00
parent 073adf743d
commit 8f720b1078
12 changed files with 180 additions and 11 deletions

View File

@@ -240,6 +240,7 @@ two tables below are split along exactly that line.
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
| `nomarchy.swaync.enable` | `true` | swaync notifications, themed |
| `nomarchy.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery |
| `nomarchy.firstBootWelcome.enable` | `true` | One dismissible “you're set” toast on the first session (SUPER+M / SUPER+T / SUPER+? + network pointer); marker is `settings.firstBootShown` in the flake checkout |
| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 15 min — battery-only) |
| `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins |
| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute |

View File

@@ -55,13 +55,6 @@ runtime-computed `resume_offset` into the boot cmdline, and the dev box runs
exactly this layout (`/proc/cmdline` verified) but isn't Nomarchy. Steps in
HARDWARE-QUEUE. Close #76 on PASS.
### 81. First-boot welcome card (`VISION § B`)
One dismissible "you're set" toast on the first session (SUPER+M menu,
SUPER+T themes, SUPER+? keys, network pointer) — a notification, **not**
a wizard. Needs a one-shot marker that respects "no state outside the
checkout": key off HM generation №1 or a `settings.firstBootShown`
write. VM assert on the gate.
### 82. Generation readability (`VISION § B`)
"What changed last rebuild" in plain language: post-switch `nvd` /
`nix store diff-closures` summary (n packages up/down/added) surfaced
@@ -140,9 +133,9 @@ high-ROI, etc.) live in the journal + ROADMAP — not here.*
2026-07-10). Keep #59 commented install guidance; no
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
_(#80 battery health shipped 2026-07-11. Remaining promoted: first-boot
card, generation readability, pre-activate flag, dock life, hall of
fame → **NEXT #81#85**.)_
_(#80 battery health + #81 first-boot welcome shipped 2026-07-11.
Remaining promoted: generation readability, pre-activate flag, dock
life, hall of fame → **NEXT #82#85**.)_
### v1.0 pointer

View File

@@ -19,6 +19,21 @@ Template:
---
## 2026-07-11 — #81 first-boot welcome toast
- **Task:** NEXT #81 — dismissible “you're set” card (`VISION § B`).
- **Did:** `pkgs/nomarchy-first-boot` + user oneshot
(`modules/home/first-boot.nix`): notify once with SUPER+M/T/? + Network
+ Doctor pointers; marker `settings.firstBootShown` via theme-sync
`--no-switch` (in-checkout state). Skip live hostname. Option
`nomarchy.firstBootWelcome.enable` (default on). `checks.first-boot`.
ROADMAP/VISION/README; closed #81.
- **Verified:** **V2**`nix flake check --no-build`; `checks.first-boot`
green (toast + marker + silent re-run + live skip); local shim smoke;
downstream-template-home carries the user unit.
- **Pending:** V3 — real swaync rendering on first installed login
(toast body is pure text; not queued unless it misbehaves).
- **Next suggestion:** #82 generation readability or #85 docs hall of fame.
## 2026-07-11 — #80 doctor battery health readout
- **Task:** NEXT #80 — battery health in `nomarchy-doctor` (`VISION § C`).
- **Did:** Doctor row: `cycle_count` + retained capacity

View File

@@ -369,6 +369,13 @@ Design/decision records and a running log of shipped work (items marked
peripherals). Self-gates when attrs missing; soft warn below 70% of
design. `NOMARCHY_POWER_SUPPLY_ROOT` for the `checks.doctor` fixture
(`test_power` has no cycle/design attrs).
✓ **First-boot welcome toast (#81, `VISION § B`):** oneshot user unit
`nomarchy-first-boot` fires a dismissible “You're set” notify (SUPER+M /
SUPER+T / SUPER+? + Network + Doctor pointer) once, then writes
`settings.firstBootShown` into the flake checkout (`--no-switch`). Skips
`hostname == nomarchy-live` (live ISO keeps its own welcome). Toggle:
`nomarchy.firstBootWelcome.enable`. `checks.first-boot` gates toast /
marker / re-run silence / live skip.
- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS
passphrase already gates the machine, but locking hyprlock before sleep
meant a second password. Fixed by *not locking* before an encrypted

View File

@@ -69,7 +69,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4§5, §10.
| Idea | Intent | Notes |
|------|--------|-------|
| **Silent first-week card** | One dismissible “youre set” (menu, theme, wallpaper, network) — not a wizard | Prefer notify or doctor “first boot” section |
| **Silent first-week card** | One dismissible “youre set” (menu, themes, keys, network) — not a wizard | ✓ shipped #81`settings.firstBootShown` + notify; live ISO keeps its own toast |
| **Boreal as default** | Identity on first boot | BACKLOG PROPOSED already |
| **Generation readability** | “What changed last rebuild” in plain language | Rollback exists; story is incomplete |
| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Avoid permanent nag |

View File

@@ -86,6 +86,7 @@
nomarchy-doctor = final.callPackage ./pkgs/nomarchy-doctor { };
nomarchy-control-center = final.callPackage ./pkgs/nomarchy-control-center { };
nomarchy-battery-notify = final.callPackage ./pkgs/nomarchy-battery-notify { };
nomarchy-first-boot = final.callPackage ./pkgs/nomarchy-first-boot { };
nomarchy-detect-hw = final.callPackage ./pkgs/nomarchy-detect-hw { };
# pull / rebuild / home (+ legacy aliases) — also on HM so a home
# switch can refresh a broken system-package nomarchy-pull.
@@ -124,6 +125,7 @@
nomarchy-doctor = pkgs.nomarchy-doctor;
nomarchy-control-center = pkgs.nomarchy-control-center;
nomarchy-battery-notify = pkgs.nomarchy-battery-notify;
nomarchy-first-boot = pkgs.nomarchy-first-boot;
nomarchy-detect-hw = pkgs.nomarchy-detect-hw;
default = pkgs.nomarchy-theme-sync;
};
@@ -680,6 +682,70 @@
'';
};
# First-session welcome toast gate (#81): fires once when
# settings.firstBootShown is unset, writes the marker via
# theme-sync, silent on re-run; skips hostname nomarchy-live.
# notify-send + theme-sync shimmed on PATH (same pattern as
# battery-notify). Real toast rendering stays session/V3.
first-boot = pkgs.testers.runNixOSTest {
name = "nomarchy-first-boot";
nodes.machine = { ... }: {
environment.systemPackages = [
pkgs.nomarchy-first-boot
pkgs.nomarchy-theme-sync
pkgs.jq
];
};
testScript = ''
machine.wait_for_unit("multi-user.target")
# Writable flake checkout with a valid theme-state (template).
machine.succeed(
"mkdir -p /tmp/fake-nomarchy && "
"cp ${./templates/downstream/theme-state.json} "
"/tmp/fake-nomarchy/theme-state.json"
)
machine.succeed(
"mkdir -p /shim && "
"printf '#!/bin/sh\\necho \"$*\" >> /tmp/notifications\\n' > /shim/notify-send && "
"chmod +x /shim/notify-send"
)
env = (
"PATH=/shim:/run/current-system/sw/bin "
"NOMARCHY_PATH=/tmp/fake-nomarchy "
"HOME=/root"
)
# First run: toast + marker.
machine.succeed(f"{env} nomarchy-first-boot")
out = machine.succeed("cat /tmp/notifications")
assert "You're set" in out, f"welcome toast missing:\n{out}"
assert "SUPER+M" in out and "SUPER+T" in out, f"key pointers missing:\n{out}"
assert "Network" in out or "Wi" in out, f"network pointer missing:\n{out}"
shown = machine.succeed(
f"{env} nomarchy-theme-sync get settings.firstBootShown"
).strip()
assert shown == "true", f"marker not written: {shown!r}"
# Second run: silent (no second toast).
machine.succeed("rm -f /tmp/notifications")
machine.succeed(f"{env} nomarchy-first-boot")
machine.fail("test -s /tmp/notifications")
# Live ISO hostname: skip even with marker cleared.
machine.succeed(
f"{env} nomarchy-theme-sync --quiet set settings.firstBootShown false --no-switch"
)
machine.succeed("rm -f /tmp/notifications")
machine.succeed(f"{env} hostname nomarchy-live || true")
# uname -n is the kernel nodename set via hostnamectl/hostname.
machine.succeed("hostname nomarchy-live")
machine.succeed(f"{env} nomarchy-first-boot")
machine.fail("test -s /tmp/notifications")
'';
};
# Memory-pressure protection (oom.nix): earlyoom must kill a
# runaway allocator BEFORE the kernel thrash point — and must not
# take anything else with it. A bystander unit survives the kill,

View File

@@ -28,6 +28,7 @@
./mime.nix # default applications (mimeapps.list), degrades with the suite
./recording.nix # nomarchy-record: screen recording behind Capture + the bar ⏺
./battery-notify.nix # low-battery toasts at the bar's 25/10% thresholds
./first-boot.nix # one-shot "you're set" toast on first session (#81)
./satty.nix # satty screenshot annotation tool, themed
];

View File

@@ -0,0 +1,37 @@
# First-session welcome toast (nomarchy.firstBootWelcome) — one dismissible
# "you're set" notification pointing at SUPER+M / SUPER+T / SUPER+? and
# Network, then writes settings.firstBootShown into the flake checkout
# (GOALS: no state outside the checkout). Live ISO keeps its own toast
# (hosts/live.nix); the script self-skips on hostname nomarchy-live.
{ config, lib, pkgs, ... }:
{
config = lib.mkIf config.nomarchy.firstBootWelcome.enable {
systemd.user.services.nomarchy-first-boot = {
Unit = {
Description = "Nomarchy first-session welcome toast";
PartOf = [ "graphical-session.target" ];
# After the session (and preferably the notification daemon) is up.
After = [ "graphical-session.target" "graphical-session-pre.target" ];
};
Service = {
Type = "oneshot";
# PATH: real notify-send + theme-sync; VM check shims both via PATH.
# NOMARCHY_PATH matches home.sessionVariables so the marker lands
# in the user's flake checkout.
Environment = [
"PATH=${lib.makeBinPath [
pkgs.libnotify
pkgs.nomarchy-theme-sync
pkgs.coreutils
]}"
"NOMARCHY_PATH=%h/.nomarchy"
];
# Brief settle so swaync is accepting toasts (live ISO uses sleep 3).
ExecStartPre = "${pkgs.coreutils}/bin/sleep 2";
ExecStart = "${pkgs.nomarchy-first-boot}/bin/nomarchy-first-boot";
};
Install.WantedBy = [ "graphical-session.target" ];
};
};
}

View File

@@ -364,6 +364,7 @@ in
rofi.enable = lib.mkEnableOption "Nomarchy's themed rofi launcher + the nomarchy-menu dispatcher" // { default = true; };
swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; };
batteryNotify.enable = lib.mkEnableOption "low-battery notifications at the bar's thresholds (25% low, 10% critical that one stays up until dismissed); self-gating, a silent no-op on machines without a battery" // { default = true; };
firstBootWelcome.enable = lib.mkEnableOption "one dismissible \"you're set\" toast on the first session (menu/themes/keys + network pointer); marker is settings.firstBootShown in the flake checkout" // { default = true; };
idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; };
yazi.enable = lib.mkEnableOption "the yazi TUI file manager, themed with a curated plugin set" // { default = true; };
osd.enable = lib.mkEnableOption "swayosd on-screen display for volume/brightness/mute" // { default = true; };

View File

@@ -70,6 +70,8 @@ let
# name in hyprland.nix — the monitor twin of the keyboard graduation.
# settings.displayProfile / displayProfileAuto: active named layout +
# auto-switch on plug events (hyprland.nix / Display menu).
# settings.firstBootShown: first-session welcome toast gate (#81) —
# written true after the toast fires (nomarchy-first-boot).
# Defaulted so a sparse/older state file still evaluates; nomarchy.settings
# exposes them.
settings = {
@@ -82,6 +84,7 @@ let
autoTimezone = false;
displayProfile = "";
displayProfileAuto = false;
firstBootShown = false;
};
};

View File

@@ -0,0 +1,11 @@
# First-session "you're set" toast (nomarchy.firstBootWelcome). A package
# so checks.first-boot can exercise the gate on a minimal node — same
# pattern as nomarchy-battery-notify. libnotify + theme-sync stay on PATH
# (user unit / VM shim), not runtimeInputs.
{ writeShellApplication, coreutils }:
writeShellApplication {
name = "nomarchy-first-boot";
runtimeInputs = [ coreutils ];
text = builtins.readFile ./nomarchy-first-boot.sh;
}

View File

@@ -0,0 +1,34 @@
# nomarchy-first-boot — one-shot "you're set" toast on the first session.
# Marker is settings.firstBootShown in the flake's theme-state.json
# (in-checkout state; never ~/.local/state). notify-send and
# nomarchy-theme-sync come from PATH so the VM check can shim them.
# Live ISO already has its own welcome (hosts/live.nix); skip there so
# users aren't double-toasted and the live seed doesn't get a sticky
# firstBootShown write every boot.
# uname -n is coreutils; avoid depending on a separate hostname package.
hn=$(uname -n 2>/dev/null || true)
if [ "$hn" = nomarchy-live ]; then
exit 0
fi
# Already shown → silent. Missing key / no checkout → treat as not shown
# (theme-sync get exits non-zero when the key is absent).
shown=$(nomarchy-theme-sync get settings.firstBootShown 2>/dev/null || true)
case "$shown" in
true|1|yes) exit 0 ;;
esac
# Dismissible sheet, not a wizard. Body lines match SUPER+? mnemonics.
notify-send -a Nomarchy -u normal -t 0 \
"You're set" \
"SUPER+M menu · SUPER+T themes · SUPER+? keys
WiFi: System Network (or the bar tray)
Anything off? System Doctor"
# Persist in the checkout so re-login is silent. --no-switch: marker only.
if ! nomarchy-theme-sync --quiet set settings.firstBootShown true --no-switch; then
# No writable flake checkout (or tool missing) — still showed the toast;
# without a marker it may reappear next login. Don't fail the unit.
exit 0
fi