Files
Nomarchy/agent/BACKLOG.md
Bernardo Magri 6bd03747c9
All checks were successful
Check / eval (push) Successful in 2m59s
fix(waybar): inhibited notification state reads by shape, not color alone
Item 28 color-only accessibility sweep. Swept every Waybar status
indicator for shape/glyph redundancy: all already carry non-color
distinction (updates shows a count + self-hides; vpn/doctor/nightlight/
recording self-gate; battery/idle/muted are glyph-distinct) except the
swaync notification module — its `inhibited-*` states reused the normal
bells (󰂜/󰂚), so an app-inhibited (suppressed) state was distinguished
from a normal one by color alone.

Route every suppressed state (DND + inhibited) to the bell-off glyph 󰂛
+ @muted, so "notifications are off right now" reads by SHAPE. Applied
to the generated bar and all four whole-swaps (summer-day/night use
their own bell-off glyph; executive-slate/boreal use 󰂛) — parity rule.
Reuses the glyph DND already ships, so no new codepoint / tofu risk.

Verified: V0 (flake check --no-build; 4 jsoncs valid JSON) + V1 (built
homeConfigurations.nomarchy.activationPackage — the built config has
inhibited-* = U+F009B and style.css routes .inhibited-* → @muted).
V3 pending: live inhibited-state render with an app holding an inhibitor
(HARDWARE-QUEUE). MEMORY gains the "status is never color-only" invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:07:10 +01:00

372 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backlog — the prioritized task queue
The forward-looking half of the old docs/ROADMAP.md, reworked as a queue
agents can execute. Detailed design notes and decision records stay in
`docs/ROADMAP.md` (referenced as "ROADMAP § <item>"); this file is *what's
next, in what order*.
**Rules:**
- Agents take the topmost actionable item (see LOOP.md). Finished items
are **deleted** here — the journal + git log are the record; durable
design notes get a ✓-entry in docs/ROADMAP.md if worth keeping.
- Item numbers are **stable IDs** — never renumbered or reused. A gap in
the sequence means shipped (or dropped) work; new items take the next
free number regardless of tier.
- Tags: `[blocked:hw]` needs real hardware (see HARDWARE-QUEUE.md) ·
`[human]` needs Bernardo · `[stuck]` two failed attempts, needs help ·
`[big]` must be split before starting.
- Agents may append to **PROPOSED** and **Decisions** freely; only
Bernardo moves items *out* of PROPOSED into the tiers.
---
## NOW
*(empty — NEXT's top item is the queue head)*
## NEXT
### 29. Laptop docking / external-monitor UX `[human]` (needs hardware)
(Raised by Bernardo, 2026-07-07.) The shipped display application is
weak and unintuitive, and Hyprland's model for workspaces across
multiple monitors is unclear to users. Make docking a laptop to
external monitors first-class:
- **Unplug → restore the panel:** when the external monitor is removed
and the laptop's own panel is off (was the only-active output while
docked), turn the internal panel back on *immediately* — never leave
the user staring at a dead lid.
- **Plug → extend by default:** a newly connected external monitor
should *extend* the laptop panel (not mirror), out of the box.
- **HDMI audio:** an easy way to route audio to the monitor when a
display with audio (HDMI/DP) is connected — a menu toggle / quick
action, not manual pavucontrol digging.
- **Workspaces + multi-monitor:** document (cheatsheet + menu) and,
where sensible, sane defaults for how Hyprland binds workspaces to
outputs so multi-monitor behavior is predictable.
Likely a `hyprland`/`kanshi`-style profile layer plus a menu surface;
split into slices before starting. `[blocked:hw]` for the real
dock/undock + HDMI-audio verification (see HARDWARE-QUEUE.md).
### 28. Theme UI review — make every surface stunning `[big]`
(Raised by Bernardo, 2026-07-05 — placed at the head of NEXT; reorder
freely.) A thorough visual pass over the whole themed UI — Waybar (the
generated bar AND the summer whole-swaps), rofi menus, swaync,
hyprlock, the cheatsheet, ghostty/btop, GTK/Qt, boot splash, greeter —
with the bar set at *stunning*, not merely consistent (pillar 4).
Grounded in design theory, not taste alone:
- **Color:** palette-role relationships that demonstrably work
together — harmony models (analogous/complementary accents),
perceptual lightness ordering of base→surface→overlay→text (check in
OKLCH, not raw hex), accent/warn/bad distinguishable for color-blind
users; extend tools/check-theme-contrast.py to encode the adopted
rules (it currently guards legibility pairs only).
- **Typography & rhythm:** a consistent type scale across bar/menus/
OSD, spacing on the ui.gapsIn/gapsOut grid, aligned paddings (the
bar's per-module padding vs rofi rows vs swaync toasts).
- **Hierarchy:** what reads first — bar module ordering/weights, menu
title vs rows, toast title vs body; icon weights consistent with the
adjacent text.
Bernardo has offered image-expert models for designed graphics
(wallpapers, logos, glyphs, preview/branding art): an agent slice that
needs artwork must STOP and ask ("say the word") instead of shipping
programmer-art.
Slices: (a) ✓ the audit (2026-07-05, iteration #31 — findings below);
(b) palette-level fixes + the extended contrast/harmony checker —
P1s ✓ shipped 2026-07-05 (iteration #32): summer-day + flexoki-light
subtext/surface/overlay/muted retuned (flexoki to its canonical
upstream tones), miasma + kanagawa surfaces raised (kanagawa to
sumiInk4); `subtext/base ≥ 3` now GATED in check-theme-contrast.py
(all 21 pass). P2s ✓ shipped 2026-07-05 (iteration #33): 18 hex
retunes across 9 themes (OKLCH hue-preserving minimal raises;
canonical where upstream defines one — gruvbox bg4, nord #616e88,
latte overlay1, flexoki 600-series statuses for light mode) and the
four floors GATED: muted ≥ 2, text/surface ≥ 4.5, accentAlt ≥ 3,
warn ≥ 2.5 (21×7 pairings green; audit contrast findings 20 → 1 —
miasma's earthy `bad` stays as identity, good/bad floors stay
audit-only so no exemption list was needed). Remaining (b): nothing —
further palette work graduates from slices (c)/(d) findings;
(c) per-surface polish slices — capture harness ✓ shipped 2026-07-05
(iteration #34): `tools/theme-shot.nix` boots the themed desktop
headlessly at 1080p and screenshots desktop + open root menu (proven
on tokyo-night); per-surface punch items from the first full-res
review:
-**rofi keybind hints** (shipped iteration #37): dimmed pango
hints on root/Tools/System rows, generated from keybinds.nix via
the cheatsheet's prettyKeys (single source, can't drift) —
capture-verified on tokyo-night.
- **Icon weight mix `[human]`:** full-color Papirus icons in the
menus vs all-monochrome bar glyphs — pick: symbolic/monochrome
menu icons for one system, or color as the menu's identity.
A/B captured on lumon (iteration #46, side-by-side:
https://claude.ai/code/artifact/1ccd10fe-4b6f-454a-888d-e7fdf2362b03):
B (symbolic) matches the bar's weight but Papirus symbolic
coverage has holes (preferences-desktop-theme-symbolic missing —
Look & Feel lost its icon) and rofi tints symbolics a fixed dim
gray, not the theme text color — going B needs an icon audit +
recolor slice. A costs nothing. Awaiting the call; A stays until
then.
-**Bar right-cluster rhythm** (shipped iteration #36): group
margins before pulseaudio/powerprofile/battery — heads that
self-hide degrade to the uniform spacing.
-**Semantic dim** (shipped iteration #36): workspaces + dnd +
muted-volume now use `@muted` (floor-guaranteed by 28b) instead
of `alpha(@text, 0.5)` tints; the CSS comment now documents the
new invariant.
-**compositor backdrop** (shipped iteration #35): Hyprland
`misc:background_color` now the theme's base — no more black flash
behind a missing/slow wallpaper (glaring on light themes in the
summer-day capture).
- summer-day whole-swap capture reviewed (iteration #35): the
authored slate menu + island-pill bar survive the 28b retunes and
read as one coherent identity — keep. ✓ placeholder-dim nit fixed
(iteration #38: both summer rasi, bg0 at 60% alpha).
- latte capture reviewed (iteration #38): the generated menu is
fully coherent on light themes (zebra rows, retuned surface, blue
selected, dimmed hints) — the audit's "dark terminal on latte"
preview concern was a stale preview, not a defect. Backdrop
probe: misc:background_color IS applied (hyprctl getoption =
0xFFEFF1F5); captures show dark because awww's empty-state layer
paints over it in the wallpaper-less VM — on hardware the fix
covers its intended window (the pre-awww startup gap).
- ✓ Harness gap fixed (iteration #44): guest `~/.nomarchy` seeded
via tmpfiles BEFORE greetd starts the session, so the session's
own exec-once wallpaper paint runs (a post-boot testScript seed
was too late for exec-once, and a manual re-run fails without
WAYLAND_DISPLAY — awww's client socket is display-named).
Wallpaper-complete summer-night capture reviewed: lakeside night
scene + cream inverted bar + menu-over-scene all cohere; the
placeholder dim (#2d353b99) renders. Note: summer-night's cream
bar/menu on dark backdrop is the authored inverted design (fg as
surface), not a rendering bug.
- ✓ Identity-theme capture + stub purge (iteration #45): the lumon
capture exposed that catppuccin/lumon/nord/retro-82 shipped stub
waybar.css files (214 lines, from the f211ef0 rewrite import) —
and a theme waybar.css whole-swaps the ENTIRE generated
stylesheet, so those four ran an unstyled default bar. Stubs
deleted; re-capture confirms lumon falls back to the generated
styled bar (workspace pill, padded right cluster). Their full
~110-line rofi.rasi designs kept. Real whole-swaps remain only
summer-day/night (+ nord/retro-82 lost nothing: the stubs had no
design).
(d) the whole-swap themes brought to the same bar (module parity
verified textually 2026-07-05: whole-swaps carry every functional
module; omitting cpu/memory is identity). Visual work is
V3-bound by nature — screenshots are the review artifact, hardware
confirms.
**Audit findings (slice a — `tools/audit-theme-design.py` over all 21
palettes + visual pass over previews; identity-aware curation):**
- ~~P1 · invisible-secondary-text palettes~~ ✓ fixed iteration #32
(summer-day subtext #6e828a, surface #efe7d0; flexoki-light to
canonical Flexoki: surface #E6E4D9, overlay #B7B5AC, subtext
#6F6E69, muted #878580). Gate added.
- ~~P1 · inverted bg stacks~~ ✓ fixed iteration #32 (miasma surface
#2e2e2e; kanagawa surface #2a2a37 = upstream sumiInk4). Note
learned: in this palette model `overlay` is a MID-TONE
(borders/dim, catppuccin semantics), not a third bg tier — the
dark-theme overlay==muted pattern is intentional.
- **P2 · muted floor:** muted-on-base < 2.0 on gruvbox (1.27),
everforest (1.55), lumon (1.68), nord (1.69), white (1.82), latte
(1.91), retro-82 (1.93) — dimmed workspaces/inactive items barely
render (visible on the lumon preview). Adopt ≥ 2.0 and retune.
- **P2 · warn barely reads:** warn-on-base ≈ 2.02.3 on rose-pine,
summer-day, latte, flexoki-light — this is the bar's 25%-battery
color. Floor 2.5 (or see the color-only rule below).
- **P2 · text-on-surface < 4.5:** ristretto 3.88, latte 4.39 (menus
and chips draw text on surface).
- **P3 · accentAlt-on-base < 3:** latte 2.34, summer-day 2.83.
- ~~**Design rule (systemic): status is never color-only.**~~ ✓ swept
iteration #69 (2026-07-08). good/warn collapse under protanopia and
good/bad under deuteranopia is inherent to red/green/yellow, not
fixable per-palette — but the sweep found every Waybar status
indicator already carries non-color redundancy: **updates** shows a
count (`󰚰 N`) and self-hides; **vpn/doctor/nightlight/recording**
self-gate (presence *is* the state); **battery** changes glyph by
level + bolt-on-charge; **idle_inhibitor** (󰅶/󰾪) and
**pulseaudio.muted** (󰝟) are glyph-distinct. The one real gap: swaync
**inhibited-** states reused the normal bells (󰂜/󰂚), so "notifications
suppressed by an inhibitor" was distinguished from normal by *color
alone* — fixed by routing every suppressed state (DND or inhibited) to
the bell-off glyph + @muted, in the generated bar and all four
whole-swaps (parity). V3-pending: confirm the muted bell-off renders
while an app holds a notification inhibitor (HARDWARE-QUEUE).
- **Identity exemptions, made explicit:** vantablack + white
(deliberately hueless statuses), lumon (all-blue), hackerman
(matrix green statuses), matte-black/retro-82 (off-family hues) are
identity, not defects — slice (b)'s checker gets a per-theme
exemption list instead of silently lowered floors.
- **ANSI / greeter notes:** latte, rose-pine and white ship
`ansi[0]` LIGHTER than `ansi[15]` — decide the light-theme ANSI
convention (terminals conventionally keep black dark) before the
greeter/tty theming ships to them. tokyo-night/summer-night
`ansi[7]-on-ansi[0]` ≈ 2.83.0 = the greeter's muted text; fine as
muted, watch on hardware.
- **Visual pass (480×270 previews — too small for type/spacing; the
deep per-surface pass needs 1080p captures):** the generated bar's
right cluster runs ~9 modules with uniform spacing — no grouping
rhythm (consider micro-gaps between tray/net · stats · toggles ·
battery/notify groups); latte's preview shows a DARK terminal on
the light desktop — verify ghostty consumes the latte palette (or
the preview is stale); summer-day's island-pill whole-swap bar and
lumon's globe identity are strong — preserve through any polish.
### 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.
### 33. rofi page-overflow hint (scrollbar)
(Raised by Bernardo, 2026-07-07 — item 3.) When a list/grid overflows
onto another page, nothing signals there's more below. Add a scrollbar
(rofi's `scrollbar` element) or a "more ↓" hint, themed to the palette,
on the launcher grid and the long text menus. Check rofi 2.0.0's
scrollbar support/theming.
_Scoped 2026-07-07:_ rofi only draws the scrollbar when the list actually
overflows (`lines < rows`), so enabling it self-hides on short menus —
the right primitive. But it's a **visual, ~10-file slice**: the generated
palette theme (`rofi.nix`, `scrollbar = false`) **plus every authored
`.rasi`** (boreal/nord/lumon/kanagawa/retro-82/neon-glass/executive-slate/
summer-day/summer-night) — all deliberately set `scrollbar { width:0 }`
today, each needs a per-theme handle width/colour. Payoff is V3-only
(rendering), so it wants a themed session, not a headless edit.
### 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).
## LATER
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
still slow after.
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
impermanence, BIOS/legacy boot.
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
- **Night-light geo mode**: lat/long auto sunset/sunrise (means wlsunset).
- **Per-theme icon overrides** / more icon packs (ROADMAP § Icon themes).
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **VPN exit-node richer display** (country/city) (optional).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying (NOW#3 should
also soften that blocker class).
## PROPOSED (agent suggestions — await human triage)
*Agents: append here with a one-paragraph pitch (what/why/cost). Do not
implement. Bernardo moves accepted items into a tier.*
- **`home.stateVersion` should be `mkDefault`** (surfaced writing
docs/MIGRATION.md, 2026-07-08). `modules/home/default.nix:51` sets
`home.stateVersion = "26.05"` as a *plain* assignment, so a downstream
home.nix that needs a different value (anyone migrating an existing NixOS
install — their original install's stateVersion) can only override it with
`lib.mkForce`, which reads as a hack. Cheap fix: make it
`lib.mkDefault "26.05"` so a plain downstream assignment wins cleanly,
matching the mkDefault discipline the rest of the distro follows. (The
system side is already fine — the downstream owns system.nix outright.)
- **Battery charge-limit — make the toggle instant** `[blocked:hw]`
(follow-up to the preset toggle shipped 2026-07-07, iteration #55).
The control-center now has a proper toggle (Off / 80% / 90% / 60% /
Custom…) writing `settings.power.batteryChargeLimit` — but that's a
baked NixOS option, so it only lands on the next `sys-rebuild`. To make
it instant (no rebuild): add a udev rule making
`/sys/class/power_supply/BAT*/charge_control_end_threshold`
group-writable (`MODE`/`GROUP`) so the menu can `echo N >` it live
alongside persisting state (the sysfs oneshot in power.nix still owns
boot + AC-replug re-apply), and/or surface a rofi-menu quick row or a
Waybar action. `[blocked:hw]`: the sysfs write + no-rebuild effect can
only be confirmed on a laptop exposing the threshold. Decide first
whether the small privilege grant (a local user can set the charge cap)
is acceptable.
- **Portal/Flatpak camera picker still lists the internal IR sensor**
(ROADMAP § Webcam follow-up). The shipped IR-hide is a *WirePlumber
v4l2* rule, but Flatpak/portal apps consume cameras via the
**libcamera** path, where both sensors remain visible — a Flatpak
Zoom user can still pick the black IR "camera". Options, roughly
ascending cost: (a) do nothing — document it (portal camera support
is still rare in practice); (b) a WirePlumber *libcamera* monitor
rule disabling GREY-only nodes — needs verifying that libcamera
monitor rules can match early enough (the v4l2 investigation found
only `device.api` binds pre-rule, which is why surgical scoping
failed before — same wall likely applies); (c) a libcamera
configuration/udev quirk hiding the IR sensor at the libcamera layer
itself. Cost: (b)/(c) need a T14s-style RGB+IR machine to verify →
pairs with a hardware-queue session. Recommend (a) now, (b)
investigated when the T14s is next available.
## Decisions `[human]`
Open calls only Bernardo can make; agents add options/evidence but never
decide.
- **Formatter adoption:** repo deliberately has none; `nixfmt-rfc-style`
would flatten the aligned hand-formatting of ~33 files. Adopt or
declare never?
- **Docs site vs Markdown-in-repo** (from the docs-review item).
- **zram swap:** faster under pressure and pairs with NOW#3, but it
interacts with the hibernation-swapfile story (resume device/priority
ordering) — adopt, adopt-with-hibernation-guard, or skip?
- **Default browser:** the template comments Firefox out. The shipped
mime defaults (item 8, done) point `text/html`/http(s) at
`firefox.desktop` as *inert* entries — they activate the moment
Firefox is installed and are skipped otherwise, so the remaining call
is only: ship a browser active in the suite, or stay
browserless-by-default?
- **Default power backend — PPD vs TLP:** (raised by Bernardo 2026-07-08:
would TLP be more power-efficient, and should it be the default?)
TLP does get more *idle* battery life, but only from device-level knobs
PPD deliberately omits — PCIe ASPM, disk/NVMe link PM, USB autosuspend,
runtime PM — which are the same knobs behind NVMe/USB flakiness that
pillar 1 (rock-stable, never fight your machine) guards against.
**Evidence (2026-07-08):** no credible hard-watt benchmarks exist — TLP's
own maintainer declines to quote any ("measure on your hardware") and says
PPD's power-saver gives *similar* savings under load; TLP's edge is
idle-only (linrunner.de/tlp/faq/ppd.html — PPD covers a *subset* of TLP,
"no settings to reduce consumption when the CPU is idle"). Our sibling
distro **Omarchy** ran this exact experiment — a "replace Power Profiles
with TLP for battery" guide — and the author **reverted to PPD** ("more
headaches and weird issues"); Omarchy shipped PPD auto-switching instead
(basecamp/omarchy#3907). PPD 3.4.0 now does AC/battery auto-switching,
closing part of TLP's UX gap. **Cost of TLP-default:** TLP has no live
D-Bus profile API, so the `powermgmt` menu + Waybar profile indicator +
low-battery auto power-saver (all `powerprofilesctl`) would need a rework —
it's not a one-line backend swap. **Agent rec (evidence, not a decision):
keep PPD default;** chase battery via targeted low-risk tweaks (PCIe ASPM
policy, battery-side EPP, the charge *start* threshold, PPD auto-switching)
and keep TLP the documented one-line opt-in it already is
(`nomarchy.system.power.backend = "tlp"`). Options: (i) status quo +
targeted tweaks [rec]; (ii) TLP default (reworks the profile UX);
(iii) nothing.