feat(session): first-boot welcome toast (#81)
All checks were successful
Check / eval (push) Successful in 4m23s
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:
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -69,7 +69,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4–§5, §10.
|
||||
|
||||
| Idea | Intent | Notes |
|
||||
|------|--------|-------|
|
||||
| **Silent first-week card** | One dismissible “you’re set” (menu, theme, wallpaper, network) — not a wizard | Prefer notify or doctor “first boot” section |
|
||||
| **Silent first-week card** ✓ | One dismissible “you’re 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 |
|
||||
|
||||
66
flake.nix
66
flake.nix
@@ -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,
|
||||
|
||||
@@ -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
|
||||
];
|
||||
|
||||
|
||||
37
modules/home/first-boot.nix
Normal file
37
modules/home/first-boot.nix
Normal 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" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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; };
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
11
pkgs/nomarchy-first-boot/default.nix
Normal file
11
pkgs/nomarchy-first-boot/default.nix
Normal 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;
|
||||
}
|
||||
34
pkgs/nomarchy-first-boot/nomarchy-first-boot.sh
Normal file
34
pkgs/nomarchy-first-boot/nomarchy-first-boot.sh
Normal 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
|
||||
Wi‑Fi: 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
|
||||
Reference in New Issue
Block a user