diff --git a/README.md b/README.md index f032072..e1bdc37 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index d5e4620..8192304 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -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 diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index ca8c828..f4c180d 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -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 diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index a55c6ac..d43e907 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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 diff --git a/docs/VISION.md b/docs/VISION.md index fe0ad5f..344e3a2 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -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 | diff --git a/flake.nix b/flake.nix index 09e243f..7254318 100644 --- a/flake.nix +++ b/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, diff --git a/modules/home/default.nix b/modules/home/default.nix index 7c53bbc..be86649 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -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 ]; diff --git a/modules/home/first-boot.nix b/modules/home/first-boot.nix new file mode 100644 index 0000000..722cc39 --- /dev/null +++ b/modules/home/first-boot.nix @@ -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" ]; + }; + }; +} diff --git a/modules/home/options.nix b/modules/home/options.nix index 4adbfa0..df2aa69 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -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; }; diff --git a/modules/home/theme.nix b/modules/home/theme.nix index 6663fd3..a7cd0d4 100644 --- a/modules/home/theme.nix +++ b/modules/home/theme.nix @@ -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; }; }; diff --git a/pkgs/nomarchy-first-boot/default.nix b/pkgs/nomarchy-first-boot/default.nix new file mode 100644 index 0000000..a6f86cf3 --- /dev/null +++ b/pkgs/nomarchy-first-boot/default.nix @@ -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; +} diff --git a/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh b/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh new file mode 100644 index 0000000..b83354e --- /dev/null +++ b/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh @@ -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