Skip to main content

max / alloy_tui

32.3 KB · 353 lines History Blame Raw
1 # Alloy Stack
2
3 Every pick with reasoning and what was rejected. Companion to [MANIFESTO.md]MANIFESTO.md.
4
5 ## Base
6
7 **Fedora Silverblue / rpm-ostree.** Atomic image-based base. The pivot point of the whole project: riding an existing immutable base instead of building a parallel ecosystem (the sap/mountaineer trap) is what makes Alloy a months-shaped project instead of a years-shaped one.
8
9 **ublue relationship: alongside, not downstream.** Preserves naming and architectural independence. The "Alloy" name was picked specifically to avoid the ublue "Blue\*" prefix. Tradeoff: no shared image-building infrastructure, so whatever Alloy ships for image composition is on Alloy to build/maintain. Upside: total architectural freedom and a clean brand.
10
11 **Composition strategy:** bootc + Containerfile, `FROM quay.io/fedora/fedora-bootc:42`. Source on `git.sr.ht/~maxmj/alloy`, CI on `builds.sr.ht`, published container image on `quay.io/alloy/alloy`. This is the ublue *convention* (Containerfile, OCI registry, CI-driven build) without ublue's base image, which is what "alongside ublue" resolves to in practice. Full delivery strategy and open questions in [IMAGE.md]IMAGE.md. Containerfile at the repo root; sr.ht CI at `.builds/alloy-image.yml`.
12
13 ## Compositor
14
15 **Sway.** Mature, i3-style manual tiling (workspaces plus split/tabbed/stacked containers), Wayland, well-packaged on Fedora. The i3 model is the tiling model Alloy wants: predictable, workspace-based, no infinite scroll. C rather than Rust, accepted as a knowing trade: no mature Rust i3-style tiler exists, and the model outweighs toolkit purity here. The whole sway* ecosystem (swaylock, swayidle, swayosd, swaybar) fits behind it with zero glue.
16
17 Rejected: Niri (Alloy's original pick; its scrolling-column model was the specific thing rejected in the 2026-07-17 pivot, see [MANIFESTO.md]MANIFESTO.md#status), Hyprland (governance contested, animation-forward against Alloy's understatement), river (tag-based/dwm-shaped, Zig, further from i3 than sway), dwl (tiny but too bare for a curated default).
18
19 ## Authored toolkit
20
21 **ratatui.** Alloy authors one thing, the `alloy` console and its `alloy_tui` design-system crate, and it authors in ratatui. Immediate-mode: every frame the UI code runs top to bottom from current state, no bindings, no observables, no retained UI state to diverge. Picked because **Alloy's principle is to avoid the reactive pattern** (staged appearance, async UI trickling, state-divergence bugs, the "web-shaped feel"), and immediate-mode rules those out by construction. The mountaineer-sysop `sysop-tui` pattern carries over directly as the seed of `alloy_tui`: palette, themed widgets, footer chrome, reserved keys, mock-or-real backend detection. See [CONSOLE.md]CONSOLE.md.
22
23 Conceptual coherence is a bonus: bottom, tuigreet, and the console all read as one ratatui family.
24
25 **No authored GUI.** The 2026-07-17 pivot dropped egui and the marquee-app plan (lockscreen, package GUI, notification daemon, egui bar). The graphical pieces a TUI cannot serve are adopted, not authored: swaylock, mako, swaybar. egui was the prior pick (also immediate-mode, MIT/Apache, and already used in audiofiles); the immediate-mode thesis is unchanged, only the render target moved from GPU surface to terminal cells. Rejected GUI toolkits, recorded in case authored GUI ever returns: Slint / iced / gpui / Floem / Dioxus (all reactive), gtk4-rs (couples to GNOME identity), Tauri (JS frontend conflicts with Rust-first).
26
27 ## Bar
28
29 **swaybar** (sway's built-in bar). Configured in the sway config's `bar {}` block with a `status_command`; no extra package or daemon. The v0 scaffold ships a minimal clock placeholder; a real status line (or the future `alloy` console status view) is a shaping task, not a toolkit decision.
30
31 Rejected: Ironbar (the prior pick, dropped with the GTK stack in the pivot), waybar (C++, and swaybar already covers the need), eww (Lisp/yuck config clashes with the stack), yambar (YAML, ruled out). Re-adopt a standalone bar only if swaybar's status protocol proves too limiting.
32
33 ## Lock
34
35 **swaylock** (C, canonical, minimal, security-audited). Bound Mod+Ctrl+L. A Wayland session-lock surface is inherently graphical and cannot be a TUI, so this is one of the pieces Alloy adopts rather than authors. The prior plan for a custom egui lockscreen (the first marquee app) was dropped in the pivot; lock crash-recovery is now swaylock/PAM/greetd's concern. fprintd unlock, if wanted, wires through swaylock's PAM stack.
36
37 ## Launcher
38
39 **None by default (terminal-driven).** anyrun (the prior GTK launcher) was dropped in the pivot. On a terminal-first system, launching is the shell: open apps from rio or from yazi. `Mod+D` is left unbound in the scaffold for a TUI launcher when one is chosen (a fuzzy picker run inside a terminal, e.g. via `rio -e`).
40
41 Rejected as graphical launchers: anyrun (GTK, dropped), fuzzel/tofi (C, and a graphical launcher is off-thesis now). Candidate TUI pickers to evaluate: a small nu/fzf app picker, or a dedicated `alloy` console verb.
42
43 ## Notification daemon
44
45 **mako** (C, by emersion, de facto Wayland notification daemon, lightweight). Notifications are a layer-shell surface, another graphical piece Alloy adopts rather than authors. The prior plan for a custom egui notification daemon (the third marquee app) was dropped in the pivot.
46
47 Rejected: swaync (C++), fnott (C), cosmic-notifications (drags libcosmic in; mako is lighter and already fits).
48
49 ## Screenshot stack
50
51 - **grim** for capture, **slurp** for region select. sway has no built-in screenshot (Niri did, which is why this stack changed in the pivot); grim+slurp is the canonical wlroots pairing. Bound in the sway config: Print (full), Shift/Ctrl+Print (region via slurp). Active-window grab wants jq to parse the tree, left as a documented optional.
52 - **satty** for annotation when needed (Rust, modern, replaces swappy). Mod+Print annotates the most recent capture.
53 - **wl-clipboard** for clipboard plumbing.
54
55 Rejected: swappy (dated annotator; satty replaces it), wayshot (grim is the more standard wlroots grabber).
56
57 ## File manager
58
59 **yazi** (Rust, async, plugin system, sixel/kitty/iTerm image preview). TUI-only, no GUI fallback shipped; the pivot's TUI-first line applies here the same way it applied to Rnote. Firefox and other GUI apps that need file dialogs go through xdg-desktop-portal, not a bundled file manager, so the daily case is covered.
60
61 No custom egui file manager planned. Scope is too large (file ops, permissions, drag-drop, thumbnails, archives, mounts, trash, search, batch ops, associations) for a marquee-app slot, and the pivot moved off graphical authored surfaces anyway.
62
63 Rejected: broot (Rust TUI, useful as a complement but a different model), nautilus / dolphin (not Rust), cosmic-files (was the pre-pivot GUI fallback; dropped along with the rest of the graphical stack). Users who want a graphical file manager install one themselves with `flatpak install flathub com.system76.CosmicFiles` (or thunar, nautilus), the same posture as ungoogled-chromium.
64
65 ## Text editor
66
67 **Helix.** Rust, modal (selection-first, Kakoune-shaped), LSP + tree-sitter + formatters batteries-included, TOML config, immediate-mode-stable, useful with zero config. On a terminal-first system the editor is a TUI; there is no graphical-editor default. Config and the Akari Dawn theme are covered under "system introspection" below.
68
69 The prior two-audience answer (Helix for terminal users, Gram for graphical) collapsed in the pivot: Gram (a Zed/gpui fork) was dropped along with the rest of the GUI stack. **deox**, Alloy's own Helix fork, becomes the intended default once it has a public home to build from; until then, stock Helix ships.
70
71 Rejected: neovim (modernity comes from importing a config framework, the pattern Alloy rejects), vim/kakoune (older or ancestral), Gram/Zed/cosmic-edit (graphical editors, off-thesis now). Users who want a different editor install it themselves.
72
73 ## Wallpapers
74
75 **Curated collection of public-domain impressionist paintings.** Reliably muted, atmospheric, never garish. Aligns with the design language on three axes: tonally restrained (no chrome-fighting saturation), atmospheric depth (mirrors the surface-elevation affordance vocabulary), and the impressionist obsession with *light as information* rhymes with the "color reserved for information" rule.
76
77 All major impressionist works are clean PD (Monet, the latest, died 1926). Sourcing priority goes to institutions that explicitly waive reproduction-photo rights:
78
79 - **Met Open Access** (CC0)
80 - **Art Institute of Chicago** (CC0; strong impressionist holdings)
81 - **Rijksmuseum Rijksstudio** (free high-res)
82 - **National Gallery of Art (Washington)** (open access)
83
84 Avoid Google Arts & Culture as a *source* (mixed rights); use it for discovery only. The wallpaper picker shows painter + title + year + source institution.
85
86 **Daemon: swww** (Rust, daemon + CLI, IPC-driven, soft-fade transitions, per-output). CLI-driven cycling is enough for a terminal-first system; a picker, if it ever lands, is an `alloy` console verb (metadata/curation in the console, surface management in the daemon over IPC), not an authored GUI. Soft fade on cycle is the tonally correct transition for impressionist art.
87
88 Rejected: wpaperd (Rust, more structured cycling-as-daemon-concern; second pick if picker descopes), cosmic-bg (Rust, System76, sway compatibility unverified, not worth the detour while swww exists), swaybg (C, no cycling/transitions; boring fallback only).
89
90 Honest gap: none of these handle ICC color management; that's a Wayland compositor-level concern still being finalized.
91
92 ## Terminal
93
94 **rio.** Rust, WGPU-backed, TOML config, kitty and sixel and iTerm2 graphics protocols. Ships in Fedora repos.
95
96 The pick is driven by the yazi commitment above. Yazi is the TUI primary, "where keyboard-driven Alloy users will live by default." A terminal without graphics protocols neuters the primary tool, so the question isn't "are previews a nice-to-have" but "does the primary tool run at full capability." That reframes the axis and makes rio the honest answer.
97
98 Secondary alignments:
99 - **TOML config.** No scripting language dependency, consistent with Alloy's rejection of Lua/yuck/similar config surfaces elsewhere in the stack.
100 - **Design-forward posture.** Rio treats the terminal as a displayed surface, not just a fast text renderer. That matches Alloy's design-system stance.
101 - **No multiplexing.** Tabs, splits, panes are sway's job. A terminal that also tiles is duplicated budget.
102
103 **yazi runs with kitty graphics under rio, not sixel.** Both work in rio; kitty is the modern protocol, sixel is the compat path. Yazi picks the graphics protocol at runtime by detecting terminal capability from `TERM` / `TERM_PROGRAM`, so the config work is small:
104
105 - Rio sets `TERM=xterm-256color` and identifies itself via `TERM_PROGRAM=rio`. Yazi's runtime detection picks kitty graphics from that.
106 - `yazi.toml` default: leave image previews on with `image_filter = "triangle"` and `image_quality = 75`. No explicit backend key needed.
107 - Verify at v0 packaging time by opening an image directory in yazi under rio and confirming inline preview renders (not falling back to chafa/half-block).
108
109 (Reduce to a real `yazi.toml` file when the v0 stack packaging lands. Documented here so the pick's implication isn't lost.)
110
111 Risk accepted: rio is ~2 years mature vs. alacritty's ~7. If rio proves flaky in real use, the fallback is alacritty, and the graphics gap becomes a knowing loss. Naming this trade-off here rather than pretending rio is drama-free.
112
113 Rejected:
114 - **alacritty.** Upstream has firmly refused sixel/kitty graphics for years: a definitional stance, not a pending PR. Kills yazi previews permanently.
115 - **wezterm.** Lua config violates the "no scripting-language configs" line held elsewhere in the stack. Its central pitch (built-in multiplexing, tabs, SSH client) duplicates sway's tiling and is spent budget in this stack.
116 - **cosmic-term.** Uses `alacritty_terminal` as the backend (the same graphics gap as alacritty) and additionally couples Alloy's default terminal to libcosmic's visual identity, which the design-system stance rejects.
117
118 No custom egui terminal: terminals are among the most complex userland software (ANSI, terminfo, sixel/kitty graphics, IME, OSC, performance under load). Wrong scope.
119
120 ## Browser
121
122 **Firefox (upstream) baked in as the default; ungoogled-chromium available as an opt-in Flatpak.** Alloy's identity work happens at the engine level, not the fork level, so upstream Firefox is the right base: no ESR lag, no fork-specific patch drift, the most-tested Gecko build shipping. Alloy owns the visual and behavioral layer via four files, each landing at the path Firefox actually reads:
123
124 - [`etc/firefox/policies/policies.json`]../etc/firefox/policies/policies.json: enterprise policy. Pins **uBlock Origin** as a force-installed, update-locked extension. Disables telemetry, Pocket, studies, sponsored tiles, new-tab feed, formfill, and password saving at the policy layer (higher-precedence than user prefs).
125 - [`usr/lib64/firefox/mozilla.cfg`]../usr/lib64/firefox/mozilla.cfg: system-wide default prefs, loaded via Firefox autoconfig. Configures compact UI density, blank new tab, DuckDuckGo suggestions off, quiet scroll, and enables `toolkit.legacyUserProfileCustomizations.stylesheets` so `userChrome.css` gets read. Every entry uses `defaultPref()` so users can still override in `about:config`.
126 - [`usr/lib64/firefox/defaults/pref/autoconfig.js`]../usr/lib64/firefox/defaults/pref/autoconfig.js: one-line pointer telling Firefox to load `mozilla.cfg` at startup.
127 - [`etc/skel/.mozilla/firefox/profiles.ini`]../etc/skel/.mozilla/firefox/profiles.ini + [`etc/skel/.mozilla/firefox/alloy.default/chrome/userChrome.css`]../etc/skel/.mozilla/firefox/alloy.default/chrome/userChrome.css: first-launch profile seed. `profiles.ini` names a fixed-path profile (`alloy.default/`) so the pre-seeded `chrome/userChrome.css` (Helium-style ultra-compact horizontal, Alloy light-mode tokens from [TOKENS.md]TOKENS.md) lands under it. Firefox opens the seeded profile on first launch instead of generating a random-suffix one.
128
129 Runs all modern sites (uBlock Origin blocks ads, never JS). Horizontal tabs only, per firm preference.
130
131 **Ungoogled-chromium** is offered as the second-engine escape valve for the rare site that only renders correctly under Blink, and for users who prefer Chromium ergonomics without Google telemetry. Not baked into the ISO. Install path:
132
133 ```
134 flatpak install flathub io.github.ungoogled_software.ungoogled_chromium
135 ```
136
137 No Alloy config is shipped for it. The point is a working Chromium engine on demand, not a second identity surface.
138
139 Accepted costs:
140 - **userChrome.css drifts** across Firefox major versions. Alloy owns the theme; re-verify after each Firefox major release. Bounded work (a few selectors per bump).
141 - **uBlock Origin pinning** is Alloy's enterprise policy, so Firefox will refuse to let the user disable or uninstall it via the extensions UI. Users who need to disable it can edit `/etc/firefox/policies/policies.json` themselves; Alloy documents the location.
142
143 Rejected:
144 - **Floorp.** Was the previous pick. Rough edges in daily use, ESR-based cadence lags Firefox security patches, fork-specific patches add drift Alloy doesn't own. Upstream Firefox with policies + userChrome.css captures the actual value (visual and behavioral configurability) at less cost.
145 - **LibreWolf.** Ships uBlock Origin preinstalled (a real win), but its hardening (`resistFingerprinting`, letterboxing, cookie clears on close, WebGL off) breaks modern sites. Un-hardening it to pass Alloy's "runs all modern sites" bar erases the reason to choose it over Firefox.
146 - **Zen Browser.** Gecko-based and design-forward, but its identity centers vertical tabs and sidebar-forward layout. Revisit only if Zen ships a first-class horizontal mode as a supported configuration.
147 - **Chromium as default, Brave, Vivaldi, Arc, Helium.** Alloy doesn't reinforce browser-engine monoculture. Ungoogled-chromium covers the "I need Blink" case without making it the default.
148
149 ## Shell
150
151 **Nushell as the login shell; bash unchanged as `/bin/sh` and `/bin/bash`.** Full rationale, architecture, and interoperability rules in [SHELL.md]SHELL.md.
152
153 The short version: Alloy's audience writes their own scripts, so nu's structured pipelines earn their ecosystem cost. Every daily tool-glue interaction compounds the advantage. The `curl | sh` install pattern is unaffected because it invokes `/bin/sh`, not the login shell; Alloy leaves bash in place at both `/bin/sh` and `/bin/bash`, and nu is added as an option in `/etc/shells`. Reversible with `chsh -s /bin/bash`.
154
155 Config lives at [`etc/skel/.config/nushell/`]../etc/skel/.config/nushell/: `env.nu`, `config.nu`, `aliases.nu`, plus a README covering install and verification.
156
157 **Prompt:** starship (Rust, cross-shell). Cross-shell prompt engines are exactly the kind of tool where "boring, works" earns its keep. A custom Alloy nu prompt is deferred as a design-system exercise for later.
158
159 Rejected: fish (safer pick but Alloy's audience is script-writers), zsh (raw zsh unpleasant; adopting oh-my-zsh/zinit imports someone else's opinions), bash-as-login (un-opinionated at a layer where Alloy is opinionated everywhere else).
160
161 ## Utility defaults: content viewers
162
163 Alloy ships defaults for the three content types users open constantly. Not shipping them cedes the choice to whatever a user's first web search returns (VLC for video, nomacs for images), which conflicts with the curated-defaults principle everywhere else.
164
165 ### Video: **mpv**
166
167 C, mature, keyboard-driven, no chrome by default, extensible via Lua. Handles local files, streams, and YouTube via yt-dlp. Zero visual identity out of the box, which is exactly what a video player should be. Config at [`etc/skel/.config/mpv/`]../etc/skel/.config/mpv/: hardware decode, no OSC/OSD chatter, screenshots to `~/Pictures/Screenshots`, yt-dlp capped at 1080p.
168
169 Rejected: VLC (retained-mode GTK UI, oversized surface), celluloid (adds retained UI on top of mpv, defeating the point), Haruna (Qt/KDE-shaped). No production-quality Rust video player exists.
170
171 ### Images: **imv**
172
173 C, Wayland-native, tiny, dedicated to viewing. Config at [`etc/skel/.config/imv/`]../etc/skel/.config/imv/: Alloy warm-cream background, overlay hidden by default, vi-like binds inherited from imv defaults.
174
175 Rejected: oculante (Rust and Alloy-toolkit-aligned via egui, but its RAW/EXIF/crop feature set drifts toward "photo tool" territory that overlaps GIMP/darktable's job; wrong scope for a base-image image viewer), swayimg (newer, less mature), nomacs (Qt, heavy), feh (X11).
176
177 Revisit oculante if daily-use workflow reveals gaps imv can't cover; the Rust-alignment case is real, outweighed here by focus-of-scope.
178
179 ### PDF: **zathura**
180
181 C, vim-like keybinds, MuPDF backend, tiny, extensible to djvu/ps/epub via plugins. Config at [`etc/skel/.config/zathura/`]../etc/skel/.config/zathura/: Alloy palette on chrome, `i` toggles recolor for reading dark PDFs on the cream background, statusbar-only chrome.
182
183 Rejected: sioyek (research-oriented, C++/Qt, great for papers but too specialized for base image; users who read lots of academic PDFs install it themselves), evince/okular (retained-mode, mouse-driven), mupdf viewer (even smaller than zathura but no config surface worth naming). No production-quality Rust PDF viewer exists.
184
185 **PDF annotation is not a shipped default.** zathura reads; the Rnote fold-to-notes workflow was shelved in the pivot (TUI-first clashes with a stylus GUI, see [MANIFESTO.md]MANIFESTO.md#status and [HARDWARE-FW12.md]HARDWARE-FW12.md). Do not add annotation to the zathura config.
186
187 ## Utility defaults: system introspection
188
189 The daily-use tools that replace legacy GNU-utils habits with keyboard-driven Rust equivalents. All three ship in the base image.
190
191 ### Editor: **helix**
192
193 Rust, modal (selection-first grammar, Kakoune-shaped), LSP + tree-sitter + formatters batteries-included, TOML config, zero-config to be useful. Config at [`etc/skel/.config/helix/`]../etc/skel/.config/helix/: editor UX in `config.toml`, Akari Dawn theme shipped verbatim at `themes/akari-dawn.toml` (with `akari-night.toml` alongside for dark mode) from Shu Kutsuzawa's [Akari]https://github.com/cappyzawa/akari-theme (MIT).
194
195 `EDITOR=hx` is set by `etc/skel/.config/nushell/env.nu`; git commit messages, `crontab -e`, and every other `$EDITOR`-respecting tool routes to helix.
196
197 Rejected:
198 - **neovim.** Ecosystem depth is real, but raw neovim isn't modern; modernity comes from importing a config framework (LazyVim, kickstart, AstroNvim), which is the same pattern Alloy rejected for zsh + oh-my-zsh. Alloy's curated-defaults ethos wants a tool whose defaults *are* the value; helix delivers that, neovim doesn't.
199 - **vim.** Older, less LSP-integrated, less tree-sitter-integrated. Zero Alloy alignment.
200 - **Kakoune.** Helix's direct ancestor in grammar; helix is the modern re-implementation with LSP built-in.
201 - **cosmic-edit.** Retained-mode iced UI, a different tool class (graphical editor). Not the shipped default; users install if wanted.
202
203 **Real cost:** vim muscle memory transfers imperfectly. Helix's selection-first grammar (`3wd` instead of `d3w`) is a genuine improvement but takes 1-2 weeks to internalize. Users who prefer vim install neovim via `dnf`.
204
205 ### Monitor: **bottom** (`btm`)
206
207 Rust, ratatui-based, TOML config. Config at [`etc/skel/.config/bottom/`]../etc/skel/.config/bottom/: Alloy palette on graphs and borders, click disabled (keyboard-only), CPU widget as the default focus.
208
209 Same ratatui toolkit family as the forthcoming `alloy_tui` crate, so bottom and `alloy console` read as design siblings.
210
211 Nu alias: `top = btm` (`etc/skel/.config/nushell/aliases.nu`).
212
213 Rejected: htop (C, canonical but less capable, no disk I/O), btop (C++, decorative; Alloy's aesthetic is understated), gtop (Node.js, not applicable).
214
215 ### Disk usage: **dua**
216
217 Rust, interactive terminal UI, vim-like navigation, marks-for-deletion. Minimal config surface (CLI flags at runtime). Nu alias: `du = dua interactive`; the interactive TUI is the daily-use mode.
218
219 Rejected: ncdu (C, works but not Rust), gdu (Go, no advantage over dua), dust (Rust, one-shot rather than interactive; a different tool for a different job, users install via `dnf` if they want scriptable disk summaries).
220
221 **dust deferred** rather than shipped: one tool per job is cleaner for the base image. Revisit if daily use reveals a real need for a scriptable companion.
222
223 ## Utility defaults: Wayland session glue
224
225 The small tools that translate keybinds and system state into user-visible behavior. Least aesthetic axis of the three utility clusters; picks are dictated by "plays well with sway" more than visual identity.
226
227 ### Clipboard history: **cliphist**
228
229 Go, wlroots-friendly, tiny. Text and image histories stored in a local DB, queryable via `cliphist list` and pasteable via `cliphist decode`. Interactive selection via `cliphist list` piped to a TUI picker (fzf/nu), or a future `alloy` console verb.
230
231 **Sway integration:** two watchers `exec`'d from the sway config:
232
233 ```
234 exec wl-paste --type text --watch cliphist store
235 exec wl-paste --type image --watch cliphist store
236 ```
237
238 No config file; cliphist's storage lives at `~/.local/share/cliphist/db` and needs no tuning.
239
240 Rejected: clipse (Go, a full TUI where a `cliphist list` pipe suffices), copyq (Qt, retained-mode GUI overkill).
241
242 ### Screen recorder: **wl-screenrec**
243
244 Rust, hardware-accelerated (VAAPI), small. CLI-driven, no config file.
245
246 Rejected: wf-recorder (C++, older, no hardware accel by default), OBS (way overscope for base image; power users install).
247
248 **Not in the v0 image (deferred 2026-07-19).** wl-screenrec is packaged in neither Fedora main nor Terra, so shipping it meant a dedicated `cargo install` build stage that pulled a Rust toolchain, clang, and the ffmpeg headers (912 packages) into every cold build for one binary. The pick stands; only the packaging cost is being refused. It returns when a repo carries it, or when there is a second cargo-built binary to share the stage's cost. Until then the image has no screen recorder and the `screenrec` Nu function is removed from `etc/skel/.config/nushell/aliases.nu`. Users who want it now: `cargo install wl-screenrec`.
249
250 ### Volume/brightness OSD: **swayosd**
251
252 Rust, systemd user daemon, GTK-rendered overlays for volume/brightness/caps-lock/num-lock. Config at [`etc/skel/.config/swayosd/`]../etc/skel/.config/swayosd/: Alloy palette CSS with amber `accent-warn` progress bar.
253
254 **Sway integration:** Fn keys bound to `swayosd-client --output-volume raise` and similar in the sway config (see the config for the block).
255
256 Rejected: avizo (Python, less maintained), custom mako notifications for the OSD (mako is for notifications, not indicator overlays; different job).
257
258 ### Media keys: **playerctl**
259
260 C, MPRIS client. CLI. No config; sway binds media keys directly to `exec playerctl play-pause` and similar. Handles Spotify, mpv, Firefox, and any MPRIS-compliant source.
261
262 Rejected: playerctld (still a playerctl variant), no serious alternative.
263
264 ### Night mode: **gammastep**
265
266 C, small daemon, Wayland-native color-temperature adjustment. Config at [`etc/skel/.config/gammastep/`]../etc/skel/.config/gammastep/: 3700K night / 6500K day with fade, manual location placeholder the user edits.
267
268 Rejected: redshift (X11), wlsunset (simpler but time-only, no location provider; gammastep can do both).
269
270 ## Cursor theme
271
272 **Bibata Modern Classic.** Classic tilted-arrow pointer shape (universally recognizable since Mac OS 1984), soft edges, subtle drop shadow, Wayland-friendly, actively maintained, Fedora repos. Its warmth pairs correctly with Akari Dawn's warm-clay palette without fighting the chrome.
273
274 Applied three ways because different apps read cursor state from different places:
275
276 - `~/.icons/default/index.theme` inherits from `Bibata-Modern-Classic`; most apps and sway itself resolve this.
277 - `gtk-cursor-theme-name` in `~/.config/gtk-{3.0,4.0}/settings.ini`: GTK apps.
278 - `XCURSOR_THEME` and `XCURSOR_SIZE` in `etc/skel/.config/nushell/env.nu`: everything that reads env vars.
279
280 Full config at [`etc/skel/.icons/default/`]../etc/skel/.icons/default/.
281
282 Rejected: DMZ-White/Black (the most historically classic Linux cursor, the X.Org/Ubuntu/Debian/Fedora default for 15+ years, but its high-contrast angularity fights Alloy's warm chrome; ship this only if the target aesthetic is "utilitarian Linux default"), Adwaita (GNOME's cool grey; fights warm palette), Bibata Modern Ice (too cool), Bibata Modern Amber (doubles with the accent-warn amber; reads too warm overall).
283
284 ## GTK theme: palette patch over adw-gtk3
285
286 **adw-gtk3 (base) + Alloy palette patch (layered).** Install `adw-gtk3-theme` from Fedora as the base, then override libadwaita's ~20 named color tokens (`window_bg_color`, `accent_color`, `card_bg_color`, etc.) with the Alloy light-mode ramp via `~/.config/gtk-{3.0,4.0}/gtk.css`.
287
288 Config at [`etc/skel/.config/gtk-3.0/`]../etc/skel/.config/gtk-3.0/ and [`etc/skel/.config/gtk-4.0/`]../etc/skel/.config/gtk-4.0/ with matching `gtk.css` and `settings.ini` per version.
289
290 **What this covers:** any GTK 3 or GTK 4 app that consumes libadwaita's named tokens: swayosd (GTK-rendered overlays), Firefox's system dialogs, xdg-desktop-portal-gtk dialogs, etc.
291
292 **What this doesn't cover:**
293 - Legacy GTK 3 apps with their own token sets (rare; most have migrated).
294 - Qt apps. Different theming pipeline entirely; Alloy's Qt surface is near-zero, so left unaddressed.
295 - Dark-mode variant. Light-first. Add `@media (prefers-color-scheme: dark)` blocks when needed.
296
297 **Ongoing maintenance:** libadwaita renames or adds tokens across GNOME releases (~2x/year). Small deltas, not rewrites. Read the libadwaita release notes when Fedora ships a new GNOME version.
298
299 Rejected:
300 - **Custom Alloy GTK theme (fork adw-gtk3 with Alloy tokens baked in).** A real project: hundreds of selectors, ongoing GTK-version drift. Deferred to v2+ if Alloy has resources for it. The palette-patch approach captures 90% of the visual win for 10% of the work.
301 - **libadwaita's own accent-color mechanism (GNOME 47+).** Only touches accent, leaves chrome greys untouched. Insufficient for a full palette swap to a curated theme like Akari.
302 - **Kvantum, Materia, Arc, Yaru, WhiteSur.** All ship their own visual identity that would have to be re-styled to match Alloy: the same total work as building the custom theme, without the base of libadwaita compatibility.
303
304 ## Fonts
305
306 Optimized for readability. A single fontconfig at `~/.config/fontconfig/fonts.conf` routes every app that asks for a generic family to Alloy's picks. Config at [`etc/skel/.config/fontconfig/`]../etc/skel/.config/fontconfig/.
307
308 **Departure Mono, Alloy's brand mark, is not in the fontconfig chain.** It stays reserved for authored surfaces (the console and `alloy_tui`, headers, brand elements) and is invoked by name where wanted. Nobody reads code or long text in Departure.
309
310 ### Monospace: **IosevkaTerm Nerd Font**
311
312 The Term variant of Iosevka drops ligatures, which is safe for TUIs (yazi, bottom, helix status columns) that count characters. The Nerd Font bundle carries the glyphs starship, yazi, and bottom need for prompt icons and file-type indicators. Iosevka's narrow proportions give more columns per line than JetBrains Mono or Fira Code, real screen-real-estate value in a tiling stack.
313
314 Applied at:
315 - `etc/skel/.config/mpv/mpv.conf`: `osd-font='IosevkaTerm Nerd Font'`.
316 - `etc/skel/.config/imv/config`: `overlay_font = IosevkaTerm Nerd Font:11`.
317 - Rio config (when it lands): `fonts.regular.family = "IosevkaTerm Nerd Font"`.
318 - fontconfig `monospace` alias: everything else that asks for mono.
319
320 Rejected: JetBrains Mono (wider characters cost columns per line), Fira Code (older, ligatures on by default), Cascadia Code (Microsoft-shaped, fine but less Alloy-neutral), Berkeley Mono (paid, can't ship).
321
322 ### Sans: **Atkinson Hyperlegible**
323
324 Designed by the Braille Institute specifically to maximize letter distinguishability (Il1, O0, cCG all made unambiguous). Optimized-for-readability by institutional charter, the honest answer to "make UI text as readable as possible." Fedora repos ship it.
325
326 Applied at:
327 - `etc/skel/.config/gtk-{3.0,4.0}/settings.ini`: `gtk-font-name = Atkinson Hyperlegible 11`.
328 - fontconfig `sans-serif` alias: everything else that asks for sans.
329
330 **One-line swap to Inter** if a more conventional interface font is preferred: Inter is tighter at UI sizes, more common in mainstream design, still highly legible. Replace the first `<family>` in the sans-serif alias and the `gtk-font-name` value. Alloy's shipped default is Atkinson because it matches the "optimize readability" thesis more literally.
331
332 Rejected: Inter (excellent but less legibility-maximized; kept as documented alternate), IBM Plex Sans (corporate-shaped), Roboto/Cantarell (default-neutral, no advantage over Atkinson), Iosevka Aile (family coherence appealing but Aile isn't as readable at small sizes as dedicated UI fonts).
333
334 ### Serif: **not shipped**
335
336 Alloy declines to prescribe. Nothing Alloy authors is serif; the browser and any long-form reading surface fall through to the system default (Source Serif 4 or Liberation Serif on Fedora). fontconfig alias covers the fallback chain politely without adopting a serif font as an Alloy commitment.
337
338 ## Greeter
339
340 **greetd + tuigreet.** Rust, minimal, ratatui-rendered. greetd is the daemon that owns VT1; tuigreet is the ratatui client that prompts for user/password and execs `sway` on successful auth. Config at [`etc/greetd/`]../etc/greetd/: `/etc/greetd/config.toml` sets up VT1 with tuigreet + Alloy palette applied via `--theme`.
341
342 Same ratatui toolkit family as `alloy console` and `bottom`; the login screen reads as a design sibling to the rest of the Alloy chrome.
343
344 Rejected:
345 - **gdm.** GNOME Display Manager pulls in the entire GNOME session infrastructure Alloy specifically removes. Non-starter.
346 - **sddm.** Qt-based, KDE default. Qt surface is near-zero elsewhere in Alloy; adopting it just for the login screen is unjustified.
347 - **lightdm.** X11-first, Wayland support via plugins; older shape.
348 - **ly.** TUI display manager (ncurses, C). Established but not Rust, no design-system alignment.
349 - **regreet.** Rust GTK greetd greeter; nicer visuals than tuigreet but drags GTK into the login layer. tuigreet's austerity is a feature.
350 - **agreety.** greetd's default plain-text prompt. Works but visually inconsistent with the rest of the stack.
351
352 **Later: a custom Alloy greetd greeter using `alloy_tui`.** A v2+ candidate alongside the console. Would put the login screen inside the same design system as everything else Alloy authors. Deferred until `alloy_tui` v1 lands.
353