max / alloy
- Co-Authored-By
- Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 files changed,
+2510 insertions,
-25 deletions
| @@ -6,7 +6,7 @@ | |||
| 6 | 6 | edition = "2024" | |
| 7 | 7 | rust-version = "1.86" | |
| 8 | 8 | license = "GPL-3.0-or-later" | |
| 9 | - | repository = "https://github.com/maxjmath/alloy" | |
| 9 | + | repository = "https://git.sr.ht/~maxmj/alloy" | |
| 10 | 10 | authors = ["Max Johnson <me@maxj.phd>"] | |
| 11 | 11 | ||
| 12 | 12 | [workspace.dependencies] |
| @@ -23,4 +23,9 @@ | |||
| 23 | 23 | - [`docs/RESEARCH-IMMEDIATE-MODE-DESIGN.md`](docs/RESEARCH-IMMEDIATE-MODE-DESIGN.md) — late-term research sketch: what does Figma look like for immediate-mode UI? How do designers and engineers collaborate without designers writing Rust? | |
| 24 | 24 | - [`docs/DESIGN-LANGUAGE.md`](docs/DESIGN-LANGUAGE.md) — tinted-greyscale chrome, color reserved for information. | |
| 25 | 25 | - [`docs/COSMIC.md`](docs/COSMIC.md) — which System76/COSMIC components Alloy adopts, considers, and skips. | |
| 26 | + | - [`docs/CONSOLE.md`](docs/CONSOLE.md) — Alloy Console: the ratatui unified control surface (`alloy net`, `alloy audio`, `alloy config <path>` schema-driven editor). Replaces the "graphical settings app" question. | |
| 27 | + | - [`docs/CONTINUITY.md`](docs/CONTINUITY.md) — Tailscale and Syncthing as base-image citizens: an Alloy install should be rejoinable, not just installable. | |
| 28 | + | - [`docs/SHELL.md`](docs/SHELL.md) — Nushell as the login shell; bash unchanged as `/bin/sh` and `/bin/bash`. Architecture, interop rules, accepted costs. | |
| 29 | + | - [`docs/HARDWARE-FW12.md`](docs/HARDWARE-FW12.md) — Framework Laptop 12 as the first hardware target: 2-in-1 stance, touch/tablet/rotation/OSK/fingerprint decisions. | |
| 30 | + | - [`docs/IMAGE.md`](docs/IMAGE.md) — Image composition: bootc + Containerfile, source on `git.sr.ht`, CI on `builds.sr.ht`, container image on `quay.io`. | |
| 26 | 31 | - [`docs/todo.md`](docs/todo.md) — open questions and next steps. |
| @@ -8,6 +8,8 @@ | |||
| 8 | 8 | ||
| 9 | 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 — whatever Alloy ships for image composition is on Alloy to build/maintain. Upside: total architectural freedom and a clean brand. | |
| 10 | 10 | ||
| 11 | + | **Composition strategy:** bootc + Containerfile, `FROM quay.io/fedora/fedora-bootc:41`. 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 | + | ||
| 11 | 13 | ## Compositor | |
| 12 | 14 | ||
| 13 | 15 | **Niri.** Modern, well-featured, Rust, KDL config, in Fedora repos. Scrollable-column tiling carries part of the "opinionated defaults" weight upstream — Niri's own opinion about windowing means Alloy doesn't have to invent a tiling model. | |
| @@ -115,15 +117,256 @@ | |||
| 115 | 117 | ||
| 116 | 118 | Honest gap: none of these handle ICC color management; that's a Wayland compositor-level concern still being finalized. | |
| 117 | 119 | ||
| 118 | - | ## Terminal — deferred | |
| 120 | + | ## Terminal | |
| 119 | 121 | ||
| 120 | - | Three Rust candidates: | |
| 122 | + | **rio.** Rust, WGPU-backed, TOML config, kitty and sixel and iTerm2 graphics protocols. Ships in Fedora repos. | |
| 121 | 123 | ||
| 122 | - | - **alacritty** — "the terminal renders text fast." Minimal, GPU, TOML config, no multiplexing/tabs. Pair with zellij or tmux. | |
| 123 | - | - **wezterm** — "the terminal is a workstation." Multiplexing built in, Lua config, SSH client. Self-contained. | |
| 124 | - | - **rio** — "the terminal can be beautiful." Modern aesthetic, GPU, design-forward. | |
| 125 | - | - **cosmic-term** — third Rust option to consider when revisiting (libcosmic + cosmic-text). | |
| 124 | + | 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. | |
| 126 | 125 | ||
| 127 | - | The unresolved axis is whether sixel image previews in yazi are a hard requirement (rio wins) or a nice-to-have (alacritty is fine, paired with Niri's tiling + zellij/tmux). User postponed because the choice matters to them; revisit deliberately. | |
| 126 | + | Secondary alignments: | |
| 127 | + | - **TOML config.** No scripting language dependency, consistent with Alloy's rejection of Lua/yuck/similar config surfaces elsewhere in the stack. | |
| 128 | + | - **Design-forward posture.** Rio treats the terminal as a displayed surface, not just a fast text renderer. That matches Alloy's design-system stance. | |
| 129 | + | - **No multiplexing.** Tabs, splits, panes are Niri's job. A terminal that also tiles is duplicated budget. | |
| 130 | + | ||
| 131 | + | **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: | |
| 132 | + | ||
| 133 | + | - Rio sets `TERM=xterm-256color` and identifies itself via `TERM_PROGRAM=rio`. Yazi's runtime detection picks kitty graphics from that. | |
| 134 | + | - `yazi.toml` default: leave image previews on with `image_filter = "triangle"` and `image_quality = 75`. No explicit backend key needed. | |
| 135 | + | - 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). | |
| 136 | + | ||
| 137 | + | (Reduce to a real `yazi.toml` file when the v0 stack packaging lands. Documented here so the pick's implication isn't lost.) | |
| 138 | + | ||
| 139 | + | 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. | |
| 140 | + | ||
| 141 | + | Rejected: | |
| 142 | + | - **alacritty.** Upstream has firmly refused sixel/kitty graphics for years — definitional stance, not a pending PR. Kills yazi previews permanently. | |
| 143 | + | - **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 Niri's tiling and is spent budget in this stack. | |
| 144 | + | - **cosmic-term.** Uses `alacritty_terminal` as the backend — same graphics gap as alacritty — and additionally couples Alloy's default terminal to libcosmic's visual identity, which the design-system stance rejects. | |
| 128 | 145 | ||
| 129 | 146 | No custom egui terminal — terminals are among the most complex userland software (ANSI, terminfo, sixel/kitty graphics, IME, OSC, performance under load). Wrong scope. | |
| 147 | + | ||
| 148 | + | ## Browser | |
| 149 | + | ||
| 150 | + | **Floorp.** Firefox fork by Ablaze, Gecko-based, ships as Flatpak on Flathub. Picked over baseline Firefox because Floorp exposes materially more visual configurability through its own settings (density, tab layout, workspaces) without needing extensions, and picked over any Chromium fork because Alloy should not reinforce the Chromium monoculture — browser-engine diversity is load-bearing for the web, and Alloy shipping Gecko is a small vote in the right direction. | |
| 151 | + | ||
| 152 | + | Alloy preconfigures Floorp with: | |
| 153 | + | - Helium-style ultra-compact horizontal chrome (thin tab strip, compact address bar, no title bar, no menu bar, no bookmarks bar). | |
| 154 | + | - Alloy light-mode tokens applied via `userChrome.css` — Flatwhite-adjacent warm cream from the same H=80, C=0.012 ramp used everywhere else (see [TOKENS.md](TOKENS.md)). | |
| 155 | + | - Telemetry, Pocket, studies, sponsored tiles, and new-tab feed content off. | |
| 156 | + | - Blank new tab, DuckDuckGo search suggestions off, formfill off (browser is not a password manager). | |
| 157 | + | - No vertical tabs — horizontal only, per firm preference. | |
| 158 | + | ||
| 159 | + | Config lives at [`usr/share/alloy/floorp/`](../usr/share/alloy/floorp/) — `user.js` for prefs, `chrome/userChrome.css` for chrome overrides, and a README explaining install and verification. | |
| 160 | + | ||
| 161 | + | Accepted costs: | |
| 162 | + | - **userChrome.css drifts** across Firefox major versions. Alloy owns the theme; re-verify after each Floorp major release. Bounded work (a few selectors per bump). | |
| 163 | + | - **Floorp release cadence** lags upstream Firefox slightly (ESR-based). Security patches arrive a beat later. Trade accepted for the configurability. | |
| 164 | + | - **Fallback:** if Floorp stalls or drifts weirdly, Alloy falls back to plain Firefox + the same `userChrome.css`. The user-visible difference is smaller than it looks because Alloy owns the visual layer. | |
| 165 | + | ||
| 166 | + | Rejected: | |
| 167 | + | - **Chromium and any Chromium fork** (Brave, Vivaldi, Helium, Arc, Zen-if-it-were-Chromium). Alloy doesn't reinforce browser-engine monoculture. | |
| 168 | + | - **LibreWolf.** Stronger privacy defaults but less visual configurability than Floorp; the visual work would still need to happen and LibreWolf isn't friendlier to it. | |
| 169 | + | - **Zen Browser.** Gecko-based and design-forward, but the identity centers vertical tabs and sidebar-forward layout, which conflicts with the horizontal-tabs preference. Revisit only if Zen ships a first-class horizontal mode. | |
| 170 | + | ||
| 171 | + | ## Shell | |
| 172 | + | ||
| 173 | + | **Nushell as the login shell; bash unchanged as `/bin/sh` and `/bin/bash`.** Full rationale, architecture, and interoperability rules in [SHELL.md](SHELL.md). | |
| 174 | + | ||
| 175 | + | 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`. | |
| 176 | + | ||
| 177 | + | Config lives at [`etc/skel/.config/nushell/`](../etc/skel/.config/nushell/) — `env.nu`, `config.nu`, `aliases.nu`, plus a README covering install and verification. | |
| 178 | + | ||
| 179 | + | **Prompt:** starship (Rust, cross-shell). Cross-shell prompt engines are exactly the kind of tool where "boring, works" earns its keep. Bespoke Alloy nu prompt deferred as a design-system exercise for later. | |
| 180 | + | ||
| 181 | + | 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). | |
| 182 | + | ||
| 183 | + | ## Utility defaults — content viewers | |
| 184 | + | ||
| 185 | + | 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. | |
| 186 | + | ||
| 187 | + | ### Video: **mpv** | |
| 188 | + | ||
| 189 | + | 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. | |
| 190 | + | ||
| 191 | + | Rejected: VLC (retained-mode GTK UI, oversized surface), celluloid (adds retained UI on top of mpv — defeats the point), Haruna (Qt/KDE-shaped). No production-quality Rust video player exists. | |
| 192 | + | ||
| 193 | + | ### Images: **imv** | |
| 194 | + | ||
| 195 | + | 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. | |
| 196 | + | ||
| 197 | + | 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). | |
| 198 | + | ||
| 199 | + | Revisit oculante if daily-use workflow reveals gaps imv can't cover — the Rust-alignment case is real, just outweighed here by focus-of-scope. | |
| 200 | + | ||
| 201 | + | ### PDF: **zathura** | |
| 202 | + | ||
| 203 | + | 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. | |
| 204 | + | ||
| 205 | + | 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. | |
| 206 | + | ||
| 207 | + | **Clean split with Rnote:** zathura reads, Rnote annotates (fold-to-notes workflow, see [HARDWARE-FW12.md](HARDWARE-FW12.md)). Do not add annotation to the zathura config. | |
| 208 | + | ||
| 209 | + | ## Utility defaults — system introspection | |
| 210 | + | ||
| 211 | + | The daily-use tools that replace legacy GNU-utils habits with keyboard-driven Rust equivalents. All three ship in the base image. | |
| 212 | + | ||
| 213 | + | ### Editor: **helix** | |
| 214 | + | ||
| 215 | + | 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`, Alloy Flatwhite theme in `themes/alloy-flatwhite.toml` (40+ syntax scopes mapped to the light-mode palette). | |
| 216 | + | ||
| 217 | + | `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. | |
| 218 | + | ||
| 219 | + | Rejected: | |
| 220 | + | - **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. | |
| 221 | + | - **vim.** Older, less LSP-integrated, less tree-sitter-integrated. Zero Alloy alignment. | |
| 222 | + | - **Kakoune.** Helix's direct ancestor in grammar; helix is the modern re-implementation with LSP built-in. | |
| 223 | + | - **cosmic-edit.** Retained-mode iced UI — different tool class (graphical editor). Not the shipped default; users install if wanted. | |
| 224 | + | ||
| 225 | + | **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`. | |
| 226 | + | ||
| 227 | + | ### Monitor: **bottom** (`btm`) | |
| 228 | + | ||
| 229 | + | 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. | |
| 230 | + | ||
| 231 | + | Same ratatui toolkit family as the forthcoming `alloy_tui` crate — bottom and `alloy console` read as design siblings. | |
| 232 | + | ||
| 233 | + | Nu alias: `top = btm` (`etc/skel/.config/nushell/aliases.nu`). | |
| 234 | + | ||
| 235 | + | Rejected: htop (C, canonical but less capable, no disk I/O), btop (C++, decorative — Alloy's aesthetic is understated), gtop (Node.js, not applicable). | |
| 236 | + | ||
| 237 | + | ### Disk usage: **dua** | |
| 238 | + | ||
| 239 | + | 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. | |
| 240 | + | ||
| 241 | + | 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). | |
| 242 | + | ||
| 243 | + | **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. | |
| 244 | + | ||
| 245 | + | ## Utility defaults — Wayland session glue | |
| 246 | + | ||
| 247 | + | 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 Niri" more than visual identity. | |
| 248 | + | ||
| 249 | + | ### Clipboard history: **cliphist** | |
| 250 | + | ||
| 251 | + | Go, wlroots-friendly, tiny. Text and image histories stored in a local DB, queryable via `cliphist list` and pasteable via `cliphist decode`. Paired with anyrun for interactive selection (anyrun-plugin-cliphist exists). | |
| 252 | + | ||
| 253 | + | **Niri integration:** `spawn-sh-at-startup` two watchers in `niri.kdl`: | |
| 254 | + | ||
| 255 | + | ```kdl | |
| 256 | + | spawn-sh-at-startup "wl-paste --type text --watch cliphist store" | |
| 257 | + | spawn-sh-at-startup "wl-paste --type image --watch cliphist store" | |
| 258 | + | ``` | |
| 259 | + | ||
| 260 | + | No config file — cliphist's storage lives at `~/.local/share/cliphist/db` and needs no tuning. | |
| 261 | + | ||
| 262 | + | Rejected: clipse (Go, TUI-only, no anyrun integration), copyq (Qt, retained-mode GUI overkill). | |
| 263 | + | ||
| 264 | + | ### Screen recorder: **wl-screenrec** | |
| 265 | + | ||
| 266 | + | Rust, hardware-accelerated (VAAPI), small. CLI-driven — no config file. Nu function `screenrec` in `etc/skel/.config/nushell/aliases.nu` wraps the common invocations (region-select via slurp, timestamped output to `~/Videos/Screenrecords/`). | |
| 267 | + | ||
| 268 | + | Rejected: wf-recorder (C++, older, no hardware accel by default), OBS (way overscope for base image — power users install). | |
| 269 | + | ||
| 270 | + | ### Volume/brightness OSD: **swayosd** | |
| 271 | + | ||
| 272 | + | 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. | |
| 273 | + | ||
| 274 | + | **Niri integration:** Fn keys bound to `swayosd-client --output-volume=raise` and similar in `niri.kdl` (see the config's README for the block). | |
| 275 | + | ||
| 276 | + | Rejected: avizo (Python, less maintained), custom mako notifications for the OSD (mako is for notifications, not indicator overlays — different job). | |
| 277 | + | ||
| 278 | + | ### Media keys: **playerctl** | |
| 279 | + | ||
| 280 | + | C, MPRIS client. CLI. No config — Niri binds media keys directly to `spawn "playerctl" "play-pause"` and similar. Handles Spotify, mpv, Firefox/Floorp, and any MPRIS-compliant source. | |
| 281 | + | ||
| 282 | + | Rejected: playerctld (still a playerctl variant), no serious alternative. | |
| 283 | + | ||
| 284 | + | ### Night mode: **gammastep** | |
| 285 | + | ||
| 286 | + | 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. | |
| 287 | + | ||
| 288 | + | Rejected: redshift (X11), wlsunset (simpler but time-only, no location provider — gammastep can do both). | |
| 289 | + | ||
| 290 | + | ## Cursor theme | |
| 291 | + | ||
| 292 | + | **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 the H=80 palette without fighting the Flatwhite chrome. | |
| 293 | + | ||
| 294 | + | Applied three ways because different apps read cursor state from different places: | |
| 295 | + | ||
| 296 | + | - `~/.icons/default/index.theme` inherits from `Bibata-Modern-Classic` — most apps and Niri itself resolve this. | |
| 297 | + | - `gtk-cursor-theme-name` in `~/.config/gtk-{3.0,4.0}/settings.ini` — GTK apps. | |
| 298 | + | - `XCURSOR_THEME` and `XCURSOR_SIZE` in `etc/skel/.config/nushell/env.nu` — everything that reads env vars. | |
| 299 | + | ||
| 300 | + | Full config at [`etc/skel/.icons/default/`](../etc/skel/.icons/default/). | |
| 301 | + | ||
| 302 | + | Rejected: DMZ-White/Black (the most historically classic Linux cursor — 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). | |
| 303 | + | ||
| 304 | + | ## GTK theme — palette patch over adw-gtk3 | |
| 305 | + | ||
| 306 | + | **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`. | |
| 307 | + | ||
| 308 | + | 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. | |
| 309 | + | ||
| 310 | + | **What this covers:** any GTK 3 or GTK 4 app that consumes libadwaita's named tokens — Rnote, Ironbar, Floorp's system dialogs, cosmic-files if GTK-based, etc. | |
| 311 | + | ||
| 312 | + | **What this doesn't cover:** | |
| 313 | + | - Legacy GTK 3 apps with their own token sets (rare — most have migrated). | |
| 314 | + | - Qt apps. Different theming pipeline entirely; Alloy's Qt surface is near-zero, so left unaddressed. | |
| 315 | + | - Dark-mode variant. Light-first. Add `@media (prefers-color-scheme: dark)` blocks when needed. | |
| 316 | + | ||
| 317 | + | **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. | |
| 318 | + | ||
| 319 | + | Rejected: | |
| 320 | + | - **Bespoke Alloy GTK theme (fork adw-gtk3 with Alloy tokens baked in).** 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. | |
| 321 | + | - **libadwaita's own accent-color mechanism (GNOME 47+).** Only touches accent, leaves chrome greys untouched. Insufficient for the Flatwhite aesthetic. | |
| 322 | + | - **Kvantum, Materia, Arc, Yaru, WhiteSur.** All ship their own visual identity that would have to be re-styled to match Alloy — same total work as building the bespoke theme, without the base of libadwaita compatibility. | |
| 323 | + | ||
| 324 | + | ## Fonts | |
| 325 | + | ||
| 326 | + | 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/). | |
| 327 | + | ||
| 328 | + | **Departure Mono, Alloy's brand mark, is not in the fontconfig chain.** It stays reserved for authored surfaces (marquee UI, headers, brand elements) and is invoked by name where wanted. Nobody reads code or long text in Departure. | |
| 329 | + | ||
| 330 | + | ### Monospace: **IosevkaTerm Nerd Font** | |
| 331 | + | ||
| 332 | + | The Term variant of Iosevka drops ligatures — 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. | |
| 333 | + | ||
| 334 | + | Applied at: | |
| 335 | + | - `etc/skel/.config/mpv/mpv.conf` — `osd-font='IosevkaTerm Nerd Font'`. | |
| 336 | + | - `etc/skel/.config/imv/config` — `overlay_font = IosevkaTerm Nerd Font:11`. | |
| 337 | + | - Rio config (when it lands) — `fonts.regular.family = "IosevkaTerm Nerd Font"`. | |
| 338 | + | - fontconfig `monospace` alias — everything else that asks for mono. | |
| 339 | + | ||
| 340 | + | 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). | |
| 341 | + | ||
| 342 | + | ### Sans: **Atkinson Hyperlegible** | |
| 343 | + | ||
| 344 | + | Designed by the Braille Institute specifically to maximize letter distinguishability (Il1, O0, cCG all made unambiguous). Optimized-for-readability by literal institutional charter — the honest answer to "make UI text as readable as possible." Fedora repos ship it. | |
| 345 | + | ||
| 346 | + | Applied at: | |
| 347 | + | - `etc/skel/.config/gtk-{3.0,4.0}/settings.ini` — `gtk-font-name = Atkinson Hyperlegible 11`. | |
| 348 | + | - fontconfig `sans-serif` alias — everything else that asks for sans. | |
| 349 | + | ||
| 350 | + | **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. | |
| 351 | + | ||
| 352 | + | 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). | |
| 353 | + | ||
| 354 | + | ### Serif: **not shipped** | |
| 355 | + | ||
| 356 | + | 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. | |
| 357 | + | ||
| 358 | + | ## Greeter | |
| 359 | + | ||
| 360 | + | **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 `niri-session` on successful auth. Config at [`etc/greetd/`](../etc/greetd/) — `/etc/greetd/config.toml` sets up VT1 with tuigreet + Alloy palette applied via `--theme`. | |
| 361 | + | ||
| 362 | + | Same ratatui toolkit family as `alloy console` and `bottom`; the login screen reads as a design sibling to the rest of the Alloy chrome. | |
| 363 | + | ||
| 364 | + | Rejected: | |
| 365 | + | - **gdm.** GNOME Display Manager pulls in the entire GNOME session infrastructure Alloy specifically removes. Non-starter. | |
| 366 | + | - **sddm.** Qt-based, KDE default. Qt surface is near-zero elsewhere in Alloy; adopting it just for the login screen is unjustified. | |
| 367 | + | - **lightdm.** X11-first, Wayland support via plugins; older shape. | |
| 368 | + | - **ly.** TUI display manager (ncurses, C). Established but not Rust, no design-system alignment. | |
| 369 | + | - **regreet.** Rust GTK greetd greeter — nicer visuals than tuigreet but drags GTK into the login layer. tuigreet's austerity is a feature. | |
| 370 | + | - **agreety.** greetd's default plain-text prompt. Works but visually inconsistent with the rest of the stack. | |
| 371 | + | ||
| 372 | + | **Later: bespoke Alloy greetd greeter using `alloy_tui`.** Marquee-app candidate for v2+ alongside the lockscreen and console. Would put the login screen inside the same design system as everything else Alloy authors. Deferred until `alloy_tui` v1 lands. |
| @@ -2,25 +2,34 @@ | |||
| 2 | 2 | ||
| 3 | 3 | ## Open questions to resolve | |
| 4 | 4 | ||
| 5 | - | - [ ] Terminal pick: alacritty / wezterm / rio / cosmic-term. Key axis: sixel image previews in yazi (hard requirement → rio; nice-to-have → alacritty fine). | |
| 6 | 5 | - [ ] XDG icon theme for *adopted* apps (cosmic-files, file managers, MIME-type icons): build, adopt cosmic-icons, or pull from a standard set? Authored apps already covered by [ICONOGRAPHY.md](ICONOGRAPHY.md) — this question is only about icons consumed by third-party Linux apps that read `XDG_ICON_THEME`. | |
| 7 | 6 | - [ ] cosmic-notifications under Niri: verify whether it runs cleanly enough to be the v0/v1 daemon (would be more Rust-aligned than mako). | |
| 8 | 7 | ||
| 9 | 8 | ## Pre-v0 work | |
| 10 | 9 | ||
| 11 | - | - [ ] Decide image composition strategy (alongside-ublue means Alloy owns this — what tool?). | |
| 12 | - | - [x] Define the ramp concretely per [DESIGN-LANGUAGE.md](DESIGN-LANGUAGE.md): a sequence of (H, C, L) stops in OKLCH (or equivalent perceptual space), named semantically (`surface`, `surface-raised`, `text-primary`, `text-muted`, `border`, focus, etc). Light-mode definition is canonical; dark mode is the lightness-inverted derivative. → [TOKENS.md](TOKENS.md) | |
| 13 | - | - [x] Lock the fixed accent set (red / yellow / green / blue / magenta) to concrete OKLCH values at moderate saturation. These are global and apply identically across authored apps. → [TOKENS.md](TOKENS.md#the-information-palette) | |
| 14 | 10 | - [ ] Validate the dark-mode L stops against measured WCAG contrast on a calibrated display (deferred from TOKENS.md). | |
| 15 | - | - [x] Pick the working monospace family for all authored chrome. → **Iosevka** (nerd-patched, six weights restore weight as the primary hierarchy lever). See [TOKENS.md#working-font-iosevka-font-mono-working](TOKENS.md#working-font-iosevka-font-mono-working). | |
| 16 | - | - [x] Pick the display monospace for readouts and instrument values. → **Departure Mono** (single-weight, pixel-derived; mil-spec/pip-boy register). See [TOKENS.md#display-font-departure-mono-font-mono-display](TOKENS.md#display-font-departure-mono-font-mono-display). | |
| 17 | - | - [x] Decide whether to pair a humanist sans for prose surfaces. → **No.** Prose stays in the working mono. Mono-only is part of the brand. | |
| 18 | - | - [x] Verify nerd-patched Departure exists upstream. → Yes, `DepartureMonoNerdFontMono` from Nerd Fonts v3.4.0 (Departure v1.422). Locked in TOKENS.md. | |
| 19 | 11 | - [ ] Audit Departure's unicode coverage; document the fallback chain (Iosevka) for out-of-range code points. | |
| 20 | - | - [x] Set up the repo: git init, license, contribution model. → **GPLv3-or-later** (deliberate pick; egui is MIT/Apache so the choice was open). [LICENSE](../LICENSE), [CONTRIBUTING.md](../CONTRIBUTING.md). | |
| 21 | 12 | - [ ] Branding assets: wordmark/logotype, repo social card, favicon. Deferred until first user-facing surface ships. | |
| 22 | - | - [x] Scaffold the Alloy egui design-system crate. → `crates/alloy_ui/` with `tokens.toml` → `build.rs` → `src/generated/tokens.rs` pipeline, `apply_alloy_visuals`, starter primitives (`primary_button`, `section`, `field_row`, `SeverityTag`, `focus_ring`), and an `examples/showcase` binary. Widget set is intentionally minimal — grow it as marquee apps need it, not preemptively. | |
| 23 | - | - [x] Pick the first concrete deliverable to build. → **Lockscreen v1.** Smaller contained Wayland integration than the package GUI, security-critical scope favors simplicity, exercises both fonts in their canonical roles (Iosevka for the password field, Departure Mono for clock/date readouts), and shakes down `alloy_ui` on a small surface before the package GUI puts real load on it. Package GUI starts as the design-system reusability proof once the lockscreen is past first-light. See [MARQUEE-APPS.md](MARQUEE-APPS.md). | |
| 13 | + | - [ ] Push Alloy to `git.sr.ht/~maxmj/alloy` (or `~makecreative/alloy` — pick per open question in IMAGE.md). | |
| 14 | + | - [ ] Provision `quay.io/alloy` org and robot credentials for CI. | |
| 15 | + | - [ ] Populate sr.ht secrets: quay.io robot token, cosign private key. | |
| 16 | + | - [ ] First green build of `Containerfile` end-to-end. Audit which packages need COPRs vs. Fedora main. | |
| 17 | + | - [x] Repackage all `configs/*/` into `etc/skel/.config/*`, `etc/*`, and `usr/share/alloy/*` layout the Containerfile expects. | |
| 18 | + | - [x] Write systemd presets files (`etc/systemd/system-preset/50-alloy.preset` and `etc/systemd/user-preset/50-alloy.preset`) with the defaults per IMAGE.md and CONTINUITY.md. | |
| 19 | + | - [x] Draft `etc/os-release` skeleton (name, ID, URLs pointing at sr.ht, vendor = Make Creative). Full branding (wordmark, logo, plymouth) still deferred. | |
| 20 | + | - [ ] Verify `bootc-image-builder` produces a working installer ISO from the image. | |
| 21 | + | ||
| 22 | + | ## Hardware target: Framework 12 | |
| 23 | + | ||
| 24 | + | Full context in [HARDWARE-FW12.md](HARDWARE-FW12.md). Concrete work items below. | |
| 25 | + | ||
| 26 | + | - [ ] **`alloy-hinged` (Rust user daemon).** Reads `SW_TABLET_MODE` transitions; on fold, `niri msg action focus-workspace notes` (spawning Rnote fullscreen if not present); on unfold, `focus-workspace previous`. Single-purpose. ~100 LOC target. Ships as a systemd user service. | |
| 27 | + | - [ ] Reserve a named `notes` workspace in `niri.kdl` defaults. | |
| 28 | + | - [ ] Package Rnote in the v0 stack; set autosave defaults; document as the fold-to-notes app. | |
| 29 | + | - [ ] Verify whether the FW12 kernel auto-suppresses physical keyboard and trackpad on `SW_TABLET_MODE`; if not, add a libinput toggle to `alloy-hinged`. | |
| 30 | + | - [ ] Validate fractional 1.25x scale on the FW12 panel with real egui apps (audiofiles is the closest existing test surface). | |
| 31 | + | - [ ] Prototype fprintd challenge/response loop in an immediate-mode UI — required before v1 egui lockscreen is called done. | |
| 32 | + | - [ ] Measure s2idle drain baseline on FW12 running the Alloy image; decide whether Alloy ships any power tweaks or leaves it to Fedora defaults. | |
| 24 | 33 | ||
| 25 | 34 | ## v0 stack to package and document | |
| 26 | 35 | ||
| @@ -31,15 +40,35 @@ | |||
| 31 | 40 | - anyrun (curated plugin set) | |
| 32 | 41 | - mako (default config) | |
| 33 | 42 | - satty (annotation defaults) | |
| 34 | - | - yazi (preview/plugin defaults) | |
| 43 | + | - yazi (preview/plugin defaults, kitty-graphics previews via rio) | |
| 35 | 44 | - cosmic-files (Flatpak or layered?) | |
| 36 | - | - Terminal (pending decision) | |
| 45 | + | - rio (TOML config, kitty-graphics enabled) | |
| 46 | + | - Floorp (Flatpak; Alloy `user.js` + `userChrome.css` from `usr/share/alloy/floorp/`) | |
| 47 | + | - nushell as login shell + starship prompt (`etc/skel/.config/nushell/`); bash unchanged at `/bin/sh` and `/bin/bash` | |
| 48 | + | - helix (`etc/skel/.config/helix/` — Alloy Flatwhite theme, `EDITOR=hx` set by nu env) | |
| 49 | + | - mpv (`etc/skel/.config/mpv/`) | |
| 50 | + | - imv (`etc/skel/.config/imv/`) | |
| 51 | + | - zathura (`etc/skel/.config/zathura/`) | |
| 52 | + | - bottom (`etc/skel/.config/bottom/`; `top = btm` alias in nu) | |
| 53 | + | - dua (no config; `du = dua interactive` alias in nu) | |
| 54 | + | - cliphist (no config; `wl-paste --watch` spawn lines in `niri.kdl` startup) | |
| 55 | + | - wl-screenrec (no config; `screenrec` nu function in aliases.nu) | |
| 56 | + | - swayosd (`etc/skel/.config/swayosd/`; systemd user service + niri.kdl Fn-key binds) | |
| 57 | + | - playerctl (no config; niri.kdl media-key binds) | |
| 58 | + | - gammastep (`etc/skel/.config/gammastep/`; systemd user service) | |
| 59 | + | - Bibata Modern Classic cursor (`etc/skel/.icons/default/`; also declared in GTK settings.ini + XCURSOR env vars) | |
| 60 | + | - adw-gtk3 + Alloy palette patch (`etc/skel/.config/gtk-3.0/`, `etc/skel/.config/gtk-4.0/`) | |
| 61 | + | - IosevkaTerm Nerd Font (mono) + Atkinson Hyperlegible (sans) via `etc/skel/.config/fontconfig/fonts.conf` | |
| 62 | + | - greetd + tuigreet (`etc/greetd/`; systemd-enabled by default in the image) | |
| 63 | + | - restic (no config, no default policy; shipped in the base image for users to configure per CONTINUITY.md) | |
| 37 | 64 | ||
| 38 | 65 | ## v1 deliverables | |
| 39 | 66 | ||
| 40 | 67 | - [ ] egui design-system crate v1 (palette, type, themed widgets, extension traits). | |
| 41 | 68 | - [ ] Lockscreen (egui, ext-session-lock-v1, marquee app #1). | |
| 42 | 69 | - [ ] Package GUI (egui, marquee app #2 — pedagogical wedge). | |
| 70 | + | - [ ] **Alloy Console** — see [CONSOLE.md](CONSOLE.md). `alloy` binary with `net`/`audio`/`display`/`update`/`hinged`/`tail`/`sync` subcommands, plus `alloy config <path>` schema-driven TOML/KDL editor. Requires `alloy_tui` crate. v0.5 ships `alloy config` alone with schemas for the v0-adopted configs; full subcommand set at v1. | |
| 71 | + | - [ ] **Continuity stack** — see [CONTINUITY.md](CONTINUITY.md). Package Tailscale client + Syncthing user daemon in the base image, both disabled at first boot. First-boot flow (single screen, two rows) at v1 sits over `alloy tail` / `alloy sync` enrollments. | |
| 43 | 72 | ||
| 44 | 73 | ## Pre-stabilization revisits | |
| 45 | 74 | ||
| @@ -59,10 +88,6 @@ | |||
| 59 | 88 | ||
| 60 | 89 | - [ ] **`crucible`** (working name) — a design tool native to immediate-mode UI. Component as unit of work, token-first authoring, live render against sample data, composition grammar as the engineer-facing artifact. See [RESEARCH-IMMEDIATE-MODE-DESIGN.md](RESEARCH-IMMEDIATE-MODE-DESIGN.md). v4+ at earliest; its own sibling project if pursued. The architectural prereqs for keeping this door open are already in [COMPONENT-LIBRARY.md](COMPONENT-LIBRARY.md). | |
| 61 | 90 | ||
| 62 | - | ## v0 mockup | |
| 63 | - | ||
| 64 | - | - [x] HTML mockup of basic UX elements covering ramps, typography, control states, form elements, status readouts, hero iconography, dock, and a composed settings window. → [mockups/index.html](mockups/index.html). Placeholders for iso illustrations; v1 needs real drawings. | |
| 65 | - | ||
| 66 | 91 | ## Decisions log | |
| 67 | 92 | ||
| 68 | 93 | See [STACK.md](STACK.md) and [COSMIC.md](COSMIC.md). When a decision changes, update the doc and note the change in commit history rather than maintaining a separate changelog. |
| @@ -1,0 +1,67 @@ | |||
| 1 | + | # Alloy image build — builds.sr.ht | |
| 2 | + | # | |
| 3 | + | # Builds the Alloy bootable container image with podman, lints via | |
| 4 | + | # `bootc container lint`, signs with cosign, and publishes to | |
| 5 | + | # quay.io/alloy/alloy. | |
| 6 | + | # | |
| 7 | + | # Triggered by push to main + weekly cron for upstream base updates. | |
| 8 | + | # See docs/IMAGE.md for the full delivery strategy. | |
| 9 | + | ||
| 10 | + | image: fedora/41 | |
| 11 | + | ||
| 12 | + | packages: | |
| 13 | + | - podman | |
| 14 | + | - buildah | |
| 15 | + | - skopeo | |
| 16 | + | - cosign | |
| 17 | + | ||
| 18 | + | secrets: | |
| 19 | + | # sr.ht secret IDs (populated at repo-config time): | |
| 20 | + | # - <uuid> quay.io robot token (username + password) | |
| 21 | + | # - <uuid> cosign private key (base64-encoded) | |
| 22 | + | ||
| 23 | + | sources: | |
| 24 | + | - https://git.sr.ht/~maxmj/alloy | |
| 25 | + | ||
| 26 | + | environment: | |
| 27 | + | IMAGE: quay.io/alloy/alloy | |
| 28 | + | TAG_LATEST: latest | |
| 29 | + | TAG_VERSION: "41" | |
| 30 | + | ||
| 31 | + | tasks: | |
| 32 | + | - build: | | |
| 33 | + | cd alloy | |
| 34 | + | TAG_DATE="41-$(date -u +%Y%m%d)" | |
| 35 | + | podman build \ | |
| 36 | + | -t "$IMAGE:$TAG_LATEST" \ | |
| 37 | + | -t "$IMAGE:$TAG_VERSION" \ | |
| 38 | + | -t "$IMAGE:$TAG_DATE" \ | |
| 39 | + | . | |
| 40 | + | echo "$TAG_DATE" > /tmp/tag_date | |
| 41 | + | ||
| 42 | + | - lint: | | |
| 43 | + | # bootc container lint runs inside the Containerfile at build time, | |
| 44 | + | # but re-verify on the produced image explicitly. | |
| 45 | + | podman run --rm --entrypoint /usr/bin/bootc "$IMAGE:$TAG_LATEST" \ | |
| 46 | + | container lint | |
| 47 | + | ||
| 48 | + | - sign: | | |
| 49 | + | cd alloy | |
| 50 | + | # cosign key restored from the sr.ht secret to ./cosign.key | |
| 51 | + | export COSIGN_PASSWORD="" | |
| 52 | + | TAG_DATE="$(cat /tmp/tag_date)" | |
| 53 | + | for tag in "$TAG_LATEST" "$TAG_VERSION" "$TAG_DATE"; do | |
| 54 | + | cosign sign --yes --key=./cosign.key "$IMAGE:$tag" | |
| 55 | + | done | |
| 56 | + | ||
| 57 | + | - push: | | |
| 58 | + | # quay.io robot creds from sr.ht secret land in ~/.docker/config.json | |
| 59 | + | TAG_DATE="$(cat /tmp/tag_date)" | |
| 60 | + | for tag in "$TAG_LATEST" "$TAG_VERSION" "$TAG_DATE"; do | |
| 61 | + | podman push "$IMAGE:$tag" | |
| 62 | + | done | |
| 63 | + | ||
| 64 | + | triggers: | |
| 65 | + | - action: email | |
| 66 | + | condition: failure | |
| 67 | + | to: info@makenot.work |
| @@ -1,0 +1,125 @@ | |||
| 1 | + | # Alloy — bootable container image | |
| 2 | + | # | |
| 3 | + | # Built with `podman build`. Published to quay.io/alloy/alloy. | |
| 4 | + | # See docs/IMAGE.md for the composition strategy and rationale. | |
| 5 | + | # | |
| 6 | + | # Layer order optimizes rebuild speed: repos first (rarely change), | |
| 7 | + | # then package installs (change with STACK.md), then config tree | |
| 8 | + | # (changes most often, so lives at the tail). | |
| 9 | + | ||
| 10 | + | FROM quay.io/fedora/fedora-bootc:41 | |
| 11 | + | ||
| 12 | + | # ===================================================================== | |
| 13 | + | # Third-party repos | |
| 14 | + | # ===================================================================== | |
| 15 | + | # Tailscale is not in Fedora main; enable their yum repo. | |
| 16 | + | RUN dnf install -y dnf-plugins-core && \ | |
| 17 | + | dnf config-manager --add-repo https://pkgs.tailscale.com/stable/fedora/tailscale.repo | |
| 18 | + | ||
| 19 | + | # COPRs for packages not yet in Fedora main. Audit and prune as | |
| 20 | + | # packages migrate upstream. | |
| 21 | + | # RUN dnf copr enable -y <copr-owner>/<repo-name> | |
| 22 | + | # e.g. anyrun, satty may need this depending on Fedora version. | |
| 23 | + | ||
| 24 | + | # ===================================================================== | |
| 25 | + | # Package additions — full Alloy stack per docs/STACK.md | |
| 26 | + | # ===================================================================== | |
| 27 | + | RUN dnf install -y \ | |
| 28 | + | # Compositor and Wayland session | |
| 29 | + | niri \ | |
| 30 | + | xdg-desktop-portal xdg-desktop-portal-gtk xdg-desktop-portal-gnome \ | |
| 31 | + | # Bar, launcher, notifications, screenshot, wallpaper | |
| 32 | + | ironbar \ | |
| 33 | + | anyrun \ | |
| 34 | + | mako \ | |
| 35 | + | satty \ | |
| 36 | + | swww \ | |
| 37 | + | # Terminal, editor, shell, prompt | |
| 38 | + | rio \ | |
| 39 | + | helix \ | |
| 40 | + | nushell \ | |
| 41 | + | starship \ | |
| 42 | + | zoxide \ | |
| 43 | + | direnv \ | |
| 44 | + | # File managers | |
| 45 | + | yazi \ | |
| 46 | + | # cosmic-files (may need COPR or Flatpak — audit) | |
| 47 | + | # Content viewers | |
| 48 | + | mpv \ | |
| 49 | + | imv \ | |
| 50 | + | zathura zathura-pdf-mupdf \ | |
| 51 | + | # System introspection | |
| 52 | + | bottom \ | |
| 53 | + | dua-cli \ | |
| 54 | + | # Wayland session glue | |
| 55 | + | cliphist \ | |
| 56 | + | wl-screenrec \ | |
| 57 | + | swayosd \ | |
| 58 | + | playerctl \ | |
| 59 | + | gammastep \ | |
| 60 | + | # Notes app (fold-to-notes for FW12) | |
| 61 | + | rnote \ | |
| 62 | + | # Continuity | |
| 63 | + | tailscale \ | |
| 64 | + | syncthing \ | |
| 65 | + | restic \ | |
| 66 | + | # Greeter (pending final pick — greetd + tuigreet planned) | |
| 67 | + | greetd \ | |
| 68 | + | tuigreet \ | |
| 69 | + | # Cursor, GTK theme, fonts | |
| 70 | + | bibata-cursor-themes \ | |
| 71 | + | adw-gtk3-theme \ | |
| 72 | + | nerd-fonts-iosevkaterm \ | |
| 73 | + | atkinson-hyperlegible-fonts \ | |
| 74 | + | # Screenshot region-select helper | |
| 75 | + | slurp \ | |
| 76 | + | # Flatpak for shipping Floorp | |
| 77 | + | flatpak \ | |
| 78 | + | && dnf clean all | |
| 79 | + | ||
| 80 | + | # ===================================================================== | |
| 81 | + | # Package removals — stock desktop pieces Alloy replaces | |
| 82 | + | # ===================================================================== | |
| 83 | + | # fedora-bootc:41 is minimal and probably ships none of these, but keep | |
| 84 | + | # the remove line for safety in case the base grows. || true swallows | |
| 85 | + | # the "package not installed" error path. | |
| 86 | + | RUN dnf remove -y \ | |
| 87 | + | gdm \ | |
| 88 | + | gnome-shell \ | |
| 89 | + | gnome-session \ | |
| 90 | + | || true | |
| 91 | + | ||
| 92 | + | # ===================================================================== | |
| 93 | + | # Flathub + Floorp | |
| 94 | + | # ===================================================================== | |
| 95 | + | RUN flatpak remote-add --if-not-exists flathub \ | |
| 96 | + | https://dl.flathub.org/repo/flathub.flatpakrepo && \ | |
| 97 | + | flatpak install --system --noninteractive flathub one.ablaze.floorp | |
| 98 | + | ||
| 99 | + | # ===================================================================== | |
| 100 | + | # Config tree — the etc/ and usr/ trees in the repo map 1:1 into | |
| 101 | + | # the image. Per-user defaults live under etc/skel/.config/; | |
| 102 | + | # system-wide config under etc/; Alloy-owned templates under | |
| 103 | + | # usr/share/alloy/. See docs/IMAGE.md for the layout. | |
| 104 | + | # ===================================================================== | |
| 105 | + | COPY etc/ /etc/ | |
| 106 | + | COPY usr/ /usr/ | |
| 107 | + | ||
| 108 | + | # ===================================================================== | |
| 109 | + | # Systemd presets — shipped via etc/systemd/{system,user}-preset/ | |
| 110 | + | # in the config tree above. Split across system-preset (greetd, | |
| 111 | + | # tailscaled) and user-preset (swayosd, syncthing, gammastep, | |
| 112 | + | # alloy-hinged). See docs/CONTINUITY.md for rationale. | |
| 113 | + | # ===================================================================== | |
| 114 | + | ||
| 115 | + | # ===================================================================== | |
| 116 | + | # Branding | |
| 117 | + | # ===================================================================== | |
| 118 | + | # os-release, plymouth splash, wallpapers ship via the config tree | |
| 119 | + | # above. No additional layer needed here. | |
| 120 | + | ||
| 121 | + | # ===================================================================== | |
| 122 | + | # bootc validation — fails the build if the image isn't a valid | |
| 123 | + | # bootable container. | |
| 124 | + | # ===================================================================== | |
| 125 | + | RUN bootc container lint |
| @@ -1,0 +1,136 @@ | |||
| 1 | + | # Alloy Console | |
| 2 | + | ||
| 3 | + | A ratatui-based unified control surface for Alloy: the one place where system-level tasks (network, audio, display, updates, hinge daemon) and configuration editing happen. Companion to [STACK.md](STACK.md) and [COMPONENT-LIBRARY.md](COMPONENT-LIBRARY.md). | |
| 4 | + | ||
| 5 | + | Binary: `alloy` (single binary, subcommands). The *component* is called Alloy Console; the *invocation* is `alloy <subcommand>`. | |
| 6 | + | ||
| 7 | + | ## Thesis | |
| 8 | + | ||
| 9 | + | Distros grow graphical settings apps because Unix's underlying CLIs are excellent primitives but a terrible surface for casual use — nobody wants to look up `wpctl set-sink-volume @DEFAULT_AUDIO_SINK@ 0.5+` at 3am. GNOME's answer is a large graphical settings app. Alloy's answer is a small ratatui console that fronts those same CLIs with a shared visual identity and keymap. | |
| 10 | + | ||
| 11 | + | The elegant part is that "controlling the system" and "editing a config file" are the same design problem with two different data sources. Once the console has schema-driven form widgets for editing live state (interface toggles, sink volumes, output modes), pointing those same widgets at a TOML file gives a config editor for free. | |
| 12 | + | ||
| 13 | + | This kills the "graphical settings app" question permanently — Alloy has one settings surface, it's the console, and it's ratatui. | |
| 14 | + | ||
| 15 | + | ## Subcommand shape | |
| 16 | + | ||
| 17 | + | ``` | |
| 18 | + | alloy net # network: nmcli / iwd front | |
| 19 | + | alloy audio # audio: wpctl / pactl front | |
| 20 | + | alloy display # outputs: niri msg output / wlr-randr front | |
| 21 | + | alloy update # system updates: rpm-ostree front | |
| 22 | + | alloy hinged # alloy-hinged daemon status / toggle | |
| 23 | + | alloy tail # tailscale front (see CONTINUITY.md) | |
| 24 | + | alloy sync # syncthing front (see CONTINUITY.md) | |
| 25 | + | alloy config <path> # schema-driven TOML/KDL editor | |
| 26 | + | ``` | |
| 27 | + | ||
| 28 | + | Each subcommand is a small ratatui adapter over an existing CLI. The subcommands share: | |
| 29 | + | ||
| 30 | + | - The `alloy_tui` design-system crate (colors, typography, layout, form widgets). | |
| 31 | + | - A common navigation model (Tab across sections, Enter to activate, Esc to cancel — Alloy's keymap conventions live in one place). | |
| 32 | + | - A common status area (busy/error/dirty indicators). | |
| 33 | + | - A common command-log pane (every action shows the underlying CLI invocation, so users learn the primitive, not the wrapper). | |
| 34 | + | ||
| 35 | + | That last point is deliberate. Alloy Console is not trying to hide the CLI — it's trying to make the CLI *approachable*. Every action taken through the console prints the equivalent shell command in the log pane. Users graduate from console to CLI as they get comfortable; the tool teaches its own primitives. | |
| 36 | + | ||
| 37 | + | ## `alloy config` — schema-driven editor | |
| 38 | + | ||
| 39 | + | The config editor is the *same UI pattern* applied to file contents instead of live system state. Point it at a TOML or KDL file, get a navigable form. Save writes the file back. | |
| 40 | + | ||
| 41 | + | Behavior: | |
| 42 | + | ||
| 43 | + | - **Roundtrip-safe.** The editor preserves comments, formatting, and key order. It edits values in place, not "read → serialize → write." Uses `toml_edit` for TOML and a KDL equivalent for KDL. | |
| 44 | + | - **Schema-required.** No schema, no form — the editor refuses to render a config it doesn't have a schema for, rather than falling back to value-inferred forms that guess wrong. Falls back to a plain text edit pane (with syntax highlighting) as an explicit escape. | |
| 45 | + | - **Live validation.** Type errors, out-of-range values, missing required keys, and unknown keys all surface in the form as inline diagnostics — not on save. | |
| 46 | + | - **Documentation inline.** Schema entries carry a description string that shows in the form as help text next to the field. | |
| 47 | + | ||
| 48 | + | ## Schema strategy | |
| 49 | + | ||
| 50 | + | Three options considered. Committing to option 2 with 3 as opportunistic input. | |
| 51 | + | ||
| 52 | + | 1. **Value inference.** Read the file, infer form from value types (bool → toggle, string → input, table → subsection). Cheap. Loses everything about validation, enums, docs, defaults, sections. **Rejected** — produces "editable but not useful" forms, which is worse than no form. | |
| 53 | + | 2. **Explicit schema files, one per adopted config.** Alloy ships `schemas/rio.toml.schema`, `schemas/yazi.toml.schema`, `schemas/niri.kdl.schema`, etc. **Picked.** Alloy's whole thesis is "curated stack with authored defaults" — schemas are the natural extension of that. Bounded catalog: ~10 schemas for the adopted v0 stack. | |
| 54 | + | 3. **Upstream schemas (JSON Schema, KDL schema) where they exist.** Niri publishes a KDL schema; some tools ship JSON Schema; most don't. **Adopted opportunistically** — where an upstream schema exists and is maintained, Alloy imports/converts it rather than authoring its own. Falls back to option 2 for the rest. | |
| 55 | + | ||
| 56 | + | ### The schema format | |
| 57 | + | ||
| 58 | + | Alloy authors a small TOML dialect for describing TOML (and a parallel one for KDL). Not JSON Schema — JSON Schema is powerful but verbose and awkward for humans. Not TOML's own type system — insufficient (no enums, no docs, no cross-field constraints). | |
| 59 | + | ||
| 60 | + | Sketch: | |
| 61 | + | ||
| 62 | + | ```toml | |
| 63 | + | # schemas/rio.toml.schema | |
| 64 | + | [schema] | |
| 65 | + | target = "rio.toml" | |
| 66 | + | version = "0.2" | |
| 67 | + | ||
| 68 | + | [[field]] | |
| 69 | + | path = "colors.background" | |
| 70 | + | type = "color" | |
| 71 | + | default = "#0a0a0a" | |
| 72 | + | description = "Terminal background color." | |
| 73 | + | ||
| 74 | + | [[field]] | |
| 75 | + | path = "cursor.shape" | |
| 76 | + | type = "enum" | |
| 77 | + | values = ["block", "underline", "beam"] | |
| 78 | + | default = "block" | |
| 79 | + | description = "Cursor shape." | |
| 80 | + | ||
| 81 | + | [[field]] | |
| 82 | + | path = "window.opacity" | |
| 83 | + | type = "float" | |
| 84 | + | range = [0.0, 1.0] | |
| 85 | + | default = 1.0 | |
| 86 | + | description = "Window opacity (0 = transparent, 1 = opaque)." | |
| 87 | + | ``` | |
| 88 | + | ||
| 89 | + | Field types: `bool`, `int`, `float`, `string`, `color`, `path`, `enum`, `list<T>`, `table` (with nested fields). Constraints: `range`, `pattern`, `values`, `required`. | |
| 90 | + | ||
| 91 | + | Two properties this format has to hold: | |
| 92 | + | ||
| 93 | + | - **Readable and writable by hand.** Alloy will author dozens of these. If they take an hour each, the schema catalog is a weekend project. If they take a day each, it's a months-shaped commitment. | |
| 94 | + | - **Extensible without breaking editors.** New field types get added over time. The editor treats unknown types as a text-edit fallback rather than refusing to open the file. | |
| 95 | + | ||
| 96 | + | ## `alloy_tui` — the ratatui design system | |
| 97 | + | ||
| 98 | + | Sibling to `alloy_ui` (egui). Both consume the same `tokens.toml` (see [TOKENS.md](TOKENS.md)); the palette, typography, and semantic colors are unified across GUI and TUI. | |
| 99 | + | ||
| 100 | + | Contents (v1): | |
| 101 | + | - Themed `ratatui` widget wrappers: `AlloyBlock`, `AlloyList`, `AlloyForm`, `AlloyTable`, `AlloyStatusBar`, `AlloyLog`. | |
| 102 | + | - Extension traits on `ratatui::Frame` matching the pattern from `alloy_ui`. | |
| 103 | + | - Form-field widgets driven by the schema types above (`ColorField`, `EnumField`, `RangedNumberField`, etc.). | |
| 104 | + | - Consistent keymap constants (Tab, Shift-Tab, Enter, Esc, Ctrl-S, q). | |
| 105 | + | ||
| 106 | + | Coherence across GUI and TUI was already a claimed property of the immediate-mode paradigm in [STACK.md](STACK.md). `alloy_tui` alongside `alloy_ui` makes it a *shipped* property. | |
| 107 | + | ||
| 108 | + | ## Relationship to the rest of the stack | |
| 109 | + | ||
| 110 | + | - **Replaces the graphical settings app** Alloy would otherwise eventually be pressured into shipping. | |
| 111 | + | - **Complements yazi.** Yazi is for files; the console is for state and config. Different data, similar TUI aesthetic. | |
| 112 | + | - **Fronts, does not replace, the underlying CLIs.** `nmcli`, `wpctl`, `rpm-ostree`, `niri msg` all remain the ground truth. The console is a courteous surface. | |
| 113 | + | - **Design-system parity.** `alloy console` and `alloy` (egui apps) should feel like siblings — same palette, same typography weights, same information hierarchy — even though one draws with block characters and the other with pixels. | |
| 114 | + | ||
| 115 | + | ## Roadmap | |
| 116 | + | ||
| 117 | + | - **v0.5 (post-v0 stack, pre-v1 lockscreen).** `alloy config` alone, with schemas for the v0-adopted configs (rio, yazi, niri, mako, ironbar). No live-state subcommands yet. Ships as a way for users to configure the stack without hand-editing TOML. | |
| 118 | + | - **v1.** Add `alloy net`, `alloy audio`, `alloy display`, `alloy update`, `alloy tail`, `alloy sync`. Fills out the console as a system control surface. First-boot flow (see [CONTINUITY.md](CONTINUITY.md)) is a thin shim over `alloy tail` and `alloy sync` enrollments. | |
| 119 | + | - **v1.x.** `alloy hinged` for FW12 users. Additional adopted-tool schemas as the v0 stack grows. | |
| 120 | + | - **v2+.** Third-party subcommand registration (a well-known directory of ratatui adapters the console discovers at runtime), if a real ecosystem case emerges. Not planned. | |
| 121 | + | ||
| 122 | + | Positioned between v0 stack packaging and v1 marquee apps. Roughly 3-6 months of real work at the v1 scope. Smaller than v1 egui lockscreen individually but broader across subcommands. | |
| 123 | + | ||
| 124 | + | ## Non-goals | |
| 125 | + | ||
| 126 | + | - **Not a shell replacement.** Users still live in rio + zellij/tmux + helix. The console is invoked for specific tasks, then closed. | |
| 127 | + | - **Not a systemd control panel.** `systemctl` is fine. If a common systemd operation shows up in daily use, add a subcommand — don't build a general systemd UI. | |
| 128 | + | - **Not a launcher.** anyrun is the launcher. The console is not discovered via search — it's invoked by name. | |
| 129 | + | - **Not a package manager.** `alloy update` fronts `rpm-ostree` for atomic upgrades and rollbacks; installing individual packages is a rare enough action on Silverblue that keeping it out of the console is honest. | |
| 130 | + | ||
| 131 | + | ## Open questions | |
| 132 | + | ||
| 133 | + | - [ ] Schema format finalization — the sketch above is a working proposal; needs a design pass with a real target config (probably rio) to shake out gaps. | |
| 134 | + | - [ ] KDL editing story — `kdl` crate ecosystem in Rust is less mature than `toml_edit`. Verify roundtrip-safe editing is achievable before committing to the "one editor, both formats" pitch. If not, KDL configs get the text-edit fallback until it is. | |
| 135 | + | - [ ] Command-log pane visibility — always on, toggleable, or hidden by default? Argument for always-on: the pedagogical claim ("teaches its own primitives") only lands if the log is visible. Argument for hidden: reduces UI noise for repeat users. | |
| 136 | + | - [ ] Naming: `alloy config <path>` opens the editor. Should `alloy edit <path>` alias it, or is one entry point enough? One is probably right (fewer names to remember), but the mental model "config = editor" may feel awkward for KDL files that aren't strictly "configuration." |
| @@ -1,0 +1,93 @@ | |||
| 1 | + | # Continuity | |
| 2 | + | ||
| 3 | + | State continuity across machines is a first-class Alloy value. An Alloy install should be *rejoinable*, not just installable: flash the image, sign in to two things, and your files and your network appear. That is a different pitch from "runs your programs fast," and it is the right one for a personal machine that will be reinstalled, replaced, or added-to over time. | |
| 4 | + | ||
| 5 | + | This document collects the picks that carry that pitch. Companion to [STACK.md](STACK.md) and [CONSOLE.md](CONSOLE.md). | |
| 6 | + | ||
| 7 | + | ## Thesis | |
| 8 | + | ||
| 9 | + | The machine is disposable. The state is not. What survives a reinstall or a hardware upgrade should not depend on the user remembering to copy files, re-establish keys, or reconfigure a mesh. Alloy ships the two adopted tools that make that true — network identity and file synchronization — as base-image citizens, gated behind an explicit enrollment gesture and controlled through the Alloy Console. | |
| 10 | + | ||
| 11 | + | Everything in this document is a curated adoption of existing tools. Alloy authors nothing in this space. The Alloy contribution is: the tools are present at first boot, they enroll in one screen, and they are controlled through the same design-system surface as everything else. | |
| 12 | + | ||
| 13 | + | ## Tailscale | |
| 14 | + | ||
| 15 | + | **Pick: Tailscale client, shipped in the base image, disabled at first boot until the user enrolls.** | |
| 16 | + | ||
| 17 | + | - The client (`tailscaled` + `tailscale`) is open-source and works with either the tailscale.com control plane or a self-hosted Headscale server. | |
| 18 | + | - The control plane choice is exposed at enrollment time as a "Server" field, defaulting to tailscale.com. Users pointing at Headscale set `--login-server=<url>` — Alloy handles this as a first-class field in `alloy tail`, not a footnote. | |
| 19 | + | - Enabled via `alloy tail` (see below) or plain `sudo tailscale up`. Not enrolled by default. An OS that phones home before the user says yes is out of character for Alloy. | |
| 20 | + | ||
| 21 | + | **Not shipping the Headscale server itself.** That is fleet-admin work, not client-OS work. Users self-hosting Headscale run it on their infrastructure of choice; Alloy points at it. | |
| 22 | + | ||
| 23 | + | **Not building an alternative.** WireGuard's userland ecosystem has several candidates (headscale-tools, netbird, innernet). None reach Tailscale's install-and-forget property. Alloy is not competing on this axis; it is adopting. | |
| 24 | + | ||
| 25 | + | ## Syncthing | |
| 26 | + | ||
| 27 | + | **Pick: Syncthing user daemon, shipped in the base image, disabled at first boot until the user enrolls.** | |
| 28 | + | ||
| 29 | + | - Runs as a systemd user service (`syncthing.service`). Not root, not system-wide. | |
| 30 | + | - The daemon starts after `alloy sync` enrollment or an explicit `systemctl --user enable --now syncthing`. | |
| 31 | + | - Not enabled on-by-default with no folders configured; a running daemon with nothing to do is unjustified surface. | |
| 32 | + | - Web UI on `127.0.0.1:8384` remains reachable — Alloy does not disable it — but is not the recommended interface. `alloy sync` is. | |
| 33 | + | ||
| 34 | + | **Not shipping alternatives.** rsync-based sync is more mechanical and less mesh-shaped than Alloy wants; Resilio is proprietary; NextCloud/Seafile are servers, not sync tools. Syncthing is the honest pick for peer-to-peer file continuity. | |
| 35 | + | ||
| 36 | + | ## First-boot flow | |
| 37 | + | ||
| 38 | + | At first login, one screen with two rows: | |
| 39 | + | ||
| 40 | + | ``` | |
| 41 | + | +---------------------------------------------------------+ | |
| 42 | + | | Alloy first-boot | | |
| 43 | + | | | | |
| 44 | + | | Network identity [ Enroll Tailscale ] [ Skip ] | | |
| 45 | + | | | | |
| 46 | + | | File sync [ Enroll Syncthing ] [ Skip ] | | |
| 47 | + | | | | |
| 48 | + | | [ Done ] | | |
| 49 | + | +---------------------------------------------------------+ | |
| 50 | + | ``` | |
| 51 | + | ||
| 52 | + | Skip is fine and reversible — later, the same enrollments live under `alloy tail` and `alloy sync`. This is not a wizard, and Alloy does not grow one. Two rows, two actions, done. | |
| 53 | + | ||
| 54 | + | **Deferred to v1.** The first-boot flow itself is v1 scope. v0.5 users start the daemons manually via the console subcommands. Shipping the subcommands first, and the wizard after, is the correct ordering — the wizard is a thin shim over the enrollment actions the subcommands already expose. | |
| 55 | + | ||
| 56 | + | ## Console subcommands | |
| 57 | + | ||
| 58 | + | `alloy tail` and `alloy sync` live under Alloy Console. Full spec in [CONSOLE.md](CONSOLE.md); scope summary here so this document stands alone: | |
| 59 | + | ||
| 60 | + | - **`alloy tail`** — ratatui front over Tailscale. Peer list with online status, exit-node selection, MagicDNS lookup, share/unshare, enrollment flow with configurable login server (tailscale.com or Headscale). Replaces `tailscale status` as the daily-use surface. | |
| 61 | + | - **`alloy sync`** — ratatui front over Syncthing's REST API. Folder list with per-folder sync state, device list with connection state, add/remove folder, add/remove device, pause/resume. Does not try to replicate the Syncthing web UI's full feature surface — just the operations users actually perform. The web UI remains available for edge cases. | |
| 62 | + | ||
| 63 | + | ## Non-goals | |
| 64 | + | ||
| 65 | + | - **Secrets sync.** Bitwarden, pass, KeePassXC, etc. Too personal, and the security model varies too much to have a defensible default. Users bring their own. | |
| 66 | + | - **Dotfiles sync as an opinionated setup.** Syncthing *can* carry `~/.config`, but Alloy does not ship a preconfigured dotfile-sync arrangement. Provide the daemon; let the user decide what to sync. | |
| 67 | + | - **Backup (partial):** See [Backup](#backup) below — Alloy ships restic in the image but declines to prescribe a policy. Sync is mesh + latest-wins; backup is encrypted-at-rest + versioned. Different trust models; the two must not conflate. | |
| 68 | + | - **Cross-user or multi-account state.** Alloy is a personal-machine story. Multi-user file sharing is a Syncthing feature the user can configure themselves; Alloy does not prescribe. | |
| 69 | + | ||
| 70 | + | ## Honest costs | |
| 71 | + | ||
| 72 | + | - **Go in the base image.** Both tools are Go binaries. Alloy is Rust-forward but not Rust-exclusive (see [STACK.md](STACK.md) for other adopted C/Go tools). Naming the cost so it is not a surprise later. | |
| 73 | + | - **Two daemons at rest post-enrollment.** `tailscaled` (~30-50 MB RSS typical) and `syncthing` (~50-150 MB RSS depending on folder count). Acceptable for the value delivered; would be indefensible if unused, hence the opt-in gating. | |
| 74 | + | - **Tailscale control plane is proprietary.** Users who require a fully open control plane use Headscale. Alloy exposes the choice explicitly rather than pretending it does not exist. | |
| 75 | + | ||
| 76 | + | ## Backup | |
| 77 | + | ||
| 78 | + | **restic.** Go, CLI-first, encrypted, deduplicating, snapshot-based. Broad backend support (local, SFTP, S3, B2, rclone-anything). Stable repo format. Widest tutorial coverage of any modern backup tool. In Fedora repos. | |
| 79 | + | ||
| 80 | + | **Shipped in the base image. No default policy.** Alloy provides the tool; the user provides the folders, schedule, retention, and destination. Sync (Tailscale, Syncthing) has defensible defaults because enroll-and-it-works is the whole model; backup does not — "where do you back up to" is answerable only per-user. | |
| 81 | + | ||
| 82 | + | The forcing observation: when a user's laptop dies, they have their sync mesh (Syncthing pulled files back) and their network identity (Tailscale re-enrolls). What they don't have without configuring restic is *point-in-time recovery* — the "I deleted this file three weeks ago and just noticed" case that only versioned backup solves. | |
| 83 | + | ||
| 84 | + | Rejected: borg (Python; historical repo-format transitions), kopia (GUI-first; adds complexity Alloy doesn't need), duplicity (older, less-featured), rclone (sync, not backup — no snapshot history or dedup). | |
| 85 | + | ||
| 86 | + | **Deferred: `alloy backup` subcommand.** A ratatui front for `restic snapshots` / `restic backup` / `restic restore` fits the Alloy Console pattern (see [CONSOLE.md](CONSOLE.md)). v2+ candidate — worth building once someone is actually using restic daily and wants a nicer surface. | |
| 87 | + | ||
| 88 | + | ## Open questions | |
| 89 | + | ||
| 90 | + | - [ ] MagicDNS interaction with Fedora's `systemd-resolved` — verify no config conflict on a fresh Silverblue install with Tailscale enrolled. | |
| 91 | + | - [ ] Syncthing's `discosrv` and `relaysrv` — leave at defaults (public infrastructure) or expose configuration in `alloy sync` for users who self-host? Defaults are fine for v1; expose later if requests appear. | |
| 92 | + | - [ ] Headscale login-server field in `alloy tail` enrollment: preserve across `tailscale down` / `tailscale up` cycles automatically, or require re-entry? Automatic is the correct default; verify Tailscale client behavior supports it cleanly. | |
| 93 | + | - [ ] Whether the first-boot screen should ever appear again if the user skips both. Recommendation: no — it appears once. `alloy tail` and `alloy sync` are the paths after that. |
| @@ -1,0 +1,103 @@ | |||
| 1 | + | # Hardware target: Framework Laptop 12 | |
| 2 | + | ||
| 3 | + | The Framework 12 is the first hardware target Alloy is being built against on real metal. This document records what the device is, how the 2-in-1 form factor bends the stack, and every hardware-adjacent decision Alloy has to make earlier than it otherwise would. Companion to [STACK.md](STACK.md). | |
| 4 | + | ||
| 5 | + | Per-device docs are the pattern: this is `HARDWARE-FW12.md`. If Alloy is later validated against a Framework 13, a ThinkPad, or a Star Labs machine, each gets its own file. Cross-device concerns get promoted to a shared doc only when a second device demands it. | |
| 6 | + | ||
| 7 | + | ## Why this device, why now | |
| 8 | + | ||
| 9 | + | The Framework 12 is a 12.2" convertible (360-degree hinge, touchscreen, optional stylus) with the standard Framework expansion-card mainboard, x86_64 Intel silicon, and full Linux firmware support via LVFS. It is repairable, has good Linux upstream driver coverage in current Fedora kernels, and Framework itself validates Fedora as a supported distribution on the device. | |
| 10 | + | ||
| 11 | + | It is also the *most demanding* first target Alloy could pick: convertible hardware forces the project to answer questions a clamshell laptop lets it defer for a year (touch, on-screen keyboard, rotation, tablet-mode input gating). Answering these once, up front, is cheaper than answering them after the design system has already ossified around a keyboard-only assumption. | |
| 12 | + | ||
| 13 | + | ## Alloy's stance on the 2-in-1 form factor | |
| 14 | + | ||
| 15 | + | **Alloy stays keyboard-first. Folding the screen back is a single-purpose gesture: it opens a stylus notes canvas. That's the entire tablet-mode feature.** | |
| 16 | + | ||
| 17 | + | The manifesto commits Alloy to a keyboard-driven tiling environment. A 2-in-1 does not change that thesis. What it changes is the meaning of the fold-back hinge gesture: | |
| 18 | + | ||
| 19 | + | - **Clamshell mode is the entire Alloy experience.** Niri, Ironbar, anyrun, the full stack, unchanged. | |
| 20 | + | - **Touch and stylus in clamshell mode are pointer input, nothing more.** Tap to focus a window, drag to scroll, stylus behaves as a pointer. Niri and libinput already do this without Alloy code. | |
| 21 | + | - **Folding the screen back switches to a dedicated notes workspace** with a stylus notes app in fullscreen. Unfolding switches back to the previous workspace. No other tablet-mode behavior — no launcher shell, no touch-first UI, no OSK, no rotation (the notes app has infinite canvas; portrait is not needed). | |
| 22 | + | ||
| 23 | + | This stance is deliberate. Building a general-purpose touch UI is a years-shaped project (Phosh, GNOME Shell-for-mobile, Plasma Mobile). Alloy is a months-shaped project. The fold-to-notes gesture gives the 2-in-1 hardware a single, clear, high-value purpose without introducing a second UI shell. | |
| 24 | + | ||
| 25 | + | ## Display | |
| 26 | + | ||
| 27 | + | - **Native panel:** 12.2" 1920x1200 (16:10), matte, IPS-class, ~185 PPI. Sits in the awkward middle where 1.0x is too small and 2.0x is too big. | |
| 28 | + | - **Alloy default scale:** 1.25x fractional under Niri (`output "eDP-1" { scale 1.25 }` in `niri.kdl`). Niri's fractional-scale support handles the compositor side; egui apps need to honor `wp-fractional-scale-v1` (already handled by upstream egui/winit — verify at v0 sanity check). | |
| 29 | + | - **Contrast/color:** validate the DESIGN-LANGUAGE.md dark-mode L stops on this panel specifically (currently deferred in `todo.md`). Matte 400-nit IPS is roughly the panel class most Alloy users are likely to have; if the tokens miss here, they miss for the base audience. | |
| 30 | + | ||
| 31 | + | Rejected: 1.0x default (glyphs too small at arm's length on 12.2"), 1.5x default (wastes usable columns in a scrollable-tiling model where horizontal density matters). | |
| 32 | + | ||
| 33 | + | ## Touch and stylus as pointer | |
| 34 | + | ||
| 35 | + | Touch and stylus both work out of the box on Wayland via libinput. Niri routes them to clients as pointer events. No Alloy code required for the base case. | |
| 36 | + | ||
| 37 | + | - **Touch:** tap to focus, drag to scroll. Niri's built-in touch gestures cover column scrolling and workspace switching. | |
| 38 | + | - **Stylus:** USI 2.0 supported by the FW12 panel. Routes through libinput's tablet-tool path as pointer input. Pressure and tilt are available to any client that asks; Alloy's own widgets do not consume them. | |
| 39 | + | - **Palm rejection:** handled by libinput. No Alloy work. | |
| 40 | + | ||
| 41 | + | `alloy_ui` does not ship a Touch/Pointer input-mode split. Widgets render the same way regardless of input source; hit targets follow the design system's existing sizing tokens. If touch ergonomics become a real problem in practice, revisit — but not preemptively. | |
| 42 | + | ||
| 43 | + | ## Fold-to-notes | |
| 44 | + | ||
| 45 | + | The kernel exposes hinge state via `SW_TABLET_MODE` on Framework's ACPI/EC path. Alloy consumes it for one purpose: switching to a dedicated notes workspace on fold, and back on unfold. | |
| 46 | + | ||
| 47 | + | **Behavior:** | |
| 48 | + | ||
| 49 | + | - On fold: `niri msg action focus-workspace notes`. If the notes app is not already running on that workspace, spawn it. Fullscreen it. | |
| 50 | + | - On unfold: `niri msg action focus-workspace previous` (or the named workspace the user was on before). | |
| 51 | + | - Notes app keeps running in the background between folds. State preserved by the app's own autosave, not by Alloy. | |
| 52 | + | ||
| 53 | + | **Notes app pick: Rnote.** Rust, GTK4, purpose-built for stylus free-hand notes. Ships in Fedora repos. Infinite canvas, pressure and tilt, eraser, autosave, export to PDF/SVG. Fits Alloy's Rust-forward posture and the workflow exactly. | |
| 54 | + | ||
| 55 | + | Rejected: Xournal++ (mature but heavier and more PDF-annotation-shaped than free-writing-shaped), bespoke egui note app (real design-system win but a marquee-app-sized project for a feature Rnote already solves well; revisit only if Rnote hits a wall). | |
| 56 | + | ||
| 57 | + | **Daemon.** Small user-session Rust binary, single-purpose. Reads `SW_TABLET_MODE` transitions (via evdev or `iio-sensor-proxy`) and issues Niri IPC commands. Approximately 100 LOC. Ships as a systemd user service. Named `alloy-hinged` (working name). | |
| 58 | + | ||
| 59 | + | Not a general "device-adaptation layer" — deliberately single-purpose. If rotation, OSK, ambient light, or lid handling ever become real needs, they get their own daemons or get absorbed here with intent; not preemptively scaffolded. | |
| 60 | + | ||
| 61 | + | **Physical keyboard and trackpad when folded.** The kernel usually suppresses these on `SW_TABLET_MODE` transitions, but behavior varies by device. Verify on the FW12 specifically. If it doesn't, one extra libinput toggle in `alloy-hinged` on fold/unfold covers it. | |
| 62 | + | ||
| 63 | + | **Auto-rotation: deferred.** Rnote's infinite canvas makes landscape fine; no portrait requirement. If a user asks for it later, `iio-sensor-proxy` + a few lines in `alloy-hinged` adds it. | |
| 64 | + | ||
| 65 | + | ## Fingerprint reader | |
| 66 | + | ||
| 67 | + | Framework 12 ships a fingerprint reader on the power button. Upstream Linux support is via `libfprint`; enrollment through `fprintd`. Fedora Silverblue includes the stack. | |
| 68 | + | ||
| 69 | + | - Alloy configures PAM to allow fingerprint at sudo and at the lockscreen. | |
| 70 | + | - The v1 egui lockscreen must handle the fprintd path (not just password). Tracked here rather than in the lockscreen crate README so the FW12 target is the forcing function. | |
| 71 | + | ||
| 72 | + | ## Firmware (fwupd / LVFS) | |
| 73 | + | ||
| 74 | + | Framework publishes firmware via LVFS. Fedora Silverblue ships `fwupd` and enables LVFS by default. | |
| 75 | + | ||
| 76 | + | Alloy's contribution: nothing new, but the installer/setup flow should verify `fwupdmgr get-devices` succeeds and prompt for `fwupdmgr update` on first boot. A first-boot Alloy device with stale firmware is a bad first impression on a laptop this dependent on EC updates. | |
| 77 | + | ||
| 78 | + | ## Power management | |
| 79 | + | ||
| 80 | + | - **Suspend model:** s2idle (Modern Standby / S0ix) only. S3 is not available on the platform. Idle drain during s2idle has historically been a Linux pain point on Intel; Fedora's `power-profiles-daemon` + tuned defaults are the current best baseline. Track s2idle drain as an open concern; do not layer TLP on top of ppd. | |
| 81 | + | - **Profiles:** default to `balanced`. Expose profile switching in the (eventual) Alloy status bar; do not build a dedicated power tool. | |
| 82 | + | - **Lid close:** suspend, standard. | |
| 83 | + | ||
| 84 | + | ## Wi-Fi, Bluetooth, webcam, audio | |
| 85 | + | ||
| 86 | + | All expected to work with in-tree Fedora kernel drivers. No Alloy-specific handling planned. If a specific chipset in the FW12 turns out to need out-of-tree firmware, add a note here and to the image-composition manifest. | |
| 87 | + | ||
| 88 | + | Physical camera and microphone switches are on the chassis (Framework standard). Alloy does not need to plumb software mute — the hardware switch is authoritative. | |
| 89 | + | ||
| 90 | + | ## Expansion cards | |
| 91 | + | ||
| 92 | + | Framework's expansion-card system is a hardware concern, not a software one, but Alloy should: | |
| 93 | + | ||
| 94 | + | - Not assume a fixed USB-C port layout. UI that talks about "the left port" is wrong on a Framework. | |
| 95 | + | - Handle hot-plug of storage expansion cards cleanly (this is a udev + userspace-mount concern, mostly not Alloy's). | |
| 96 | + | ||
| 97 | + | ## Open questions | |
| 98 | + | ||
| 99 | + | - [ ] Does the FW12 kernel auto-suppress the physical keyboard and trackpad on `SW_TABLET_MODE`, or does `alloy-hinged` need to do it via libinput toggle? | |
| 100 | + | - [ ] Rnote fullscreen behavior on a Niri workspace — does it cooperate cleanly with `focus-workspace previous`, or does it need any config for kiosk-like behavior? | |
| 101 | + | - [ ] fprintd + Alloy egui lockscreen: what does the async challenge/response loop look like in an immediate-mode UI? Prototype required before v1 lockscreen is called done. | |
| 102 | + | - [ ] Fractional-scale validation on real egui apps (audiofiles is the closest existing test) at 1.25x on this panel — do glyph metrics survive? | |
| 103 | + | - [ ] s2idle drain measurement on FW12 running the Alloy image, baseline vs. tuned. If drain is bad enough to matter, revisit whether Alloy ships any power tweaks or leaves it to Fedora defaults. |
| @@ -1,0 +1,96 @@ | |||
| 1 | + | # Image composition | |
| 2 | + | ||
| 3 | + | How Alloy is built and delivered. bootc + Containerfile, source on `git.sr.ht`, CI on `builds.sr.ht`, container image on `quay.io`. Companion to [STACK.md](STACK.md). | |
| 4 | + | ||
| 5 | + | ## Landscape | |
| 6 | + | ||
| 7 | + | Silverblue and its atomic siblings don't have a mutable root filesystem. The OS is an immutable commit — a tree of files that swaps atomically at boot. Users can't `dnf install` into a running system; they either layer packages locally (rebuilding their image) or pull a pre-built image. | |
| 8 | + | ||
| 9 | + | For a distro builder, "image composition" is the question of how you produce that immutable commit and how users receive it. | |
| 10 | + | ||
| 11 | + | ## Options considered | |
| 12 | + | ||
| 13 | + | ### rpm-ostree compose (traditional) | |
| 14 | + | ||
| 15 | + | Classical Silverblue build. Treefile in YAML declares packages, kernel arguments, systemd presets. `rpm-ostree compose tree` produces an ostree commit, pushed to an ostree repository. Users pull via `rpm-ostree upgrade`. | |
| 16 | + | ||
| 17 | + | **Cost:** you host an ostree repository (real infrastructure — nginx serving `/var/ostree` or equivalent). Legacy-shaped; Fedora itself is migrating away. | |
| 18 | + | ||
| 19 | + | ### bootc + Containerfile (modern, where Fedora is going) | |
| 20 | + | ||
| 21 | + | The OS *is* an OCI container image. Distributed via any OCI registry. Users pull with `bootc switch <registry-url>` and reboot. Build is `podman build` from a Containerfile — same as building any Docker image. | |
| 22 | + | ||
| 23 | + | **Cost:** pre-1.0, occasional breaking changes as bootc matures. Alloy accepts drift maintenance the same way it does for nushell config and userChrome.css. | |
| 24 | + | ||
| 25 | + | ### ublue-downstream (rejected) | |
| 26 | + | ||
| 27 | + | Universal Blue's convention is exactly the bootc + Containerfile pattern above, using `FROM ghcr.io/ublue-os/main`. Battle-tested. But being downstream of ublue means inheriting ublue's opinions and coupling to their release cadence, which [STACK.md's Base section](STACK.md#base) explicitly rejects. | |
| 28 | + | ||
| 29 | + | **The nuance:** Alloy uses the ublue-style *convention* (Containerfile, OCI registry, CI-driven build) *without* using ublue's base image. `FROM quay.io/fedora/fedora-bootc:41`, not `FROM ghcr.io/ublue-os/main`. That's what "alongside ublue, not downstream" resolves to in practice. | |
| 30 | + | ||
| 31 | + | ### "Install Silverblue and run our script" (non-option) | |
| 32 | + | ||
| 33 | + | Zero build infrastructure. But users get a mutated Silverblue with an Alloy veneer — not shipped as "Alloy." Manifesto commitment breaks. Rejected. | |
| 34 | + | ||
| 35 | + | ## Pick: bootc + Containerfile | |
| 36 | + | ||
| 37 | + | The tool choice. Rationale summarized: aligns with where Fedora is going; build tooling is `podman build`; distribution is any OCI registry (free); Containerfile is a single-file OS specification. | |
| 38 | + | ||
| 39 | + | ## Delivery stack | |
| 40 | + | ||
| 41 | + | ### Source: `git.sr.ht/~maxmj/alloy` | |
| 42 | + | ||
| 43 | + | Primary remote. Alloy is source-hosted on sr.ht for the same reasons the rest of Make Creative's public work is: independence from big-tech-monopoly infrastructure at the layer where users install software. `srht` is a canonical remote for Alloy, not a backup. | |
| 44 | + | ||
| 45 | + | ### CI: `builds.sr.ht` | |
| 46 | + | ||
| 47 | + | sr.ht ships Fedora build VMs with `dnf`, `podman`, `buildah`, `skopeo` preinstalled. Fits Alloy's build needs without additional plumbing. The build file lives at `.builds/alloy-image.yml` in the repo root. | |
| 48 | + | ||
| 49 | + | ### Registry: `quay.io` | |
| 50 | + | ||
| 51 | + | Red Hat's container registry. Free for public images. Two reasons chosen over ghcr.io: | |
| 52 | + | ||
| 53 | + | 1. **Ecosystem coherence.** Alloy's base is `quay.io/fedora/fedora-bootc:41`; publishing to the same registry means `bootc switch quay.io/alloy/alloy:latest` reads consistent. No cross-registry indirection. | |
| 54 | + | 2. **Ideological consistency.** Source is on sr.ht partly to sit outside GitHub-adjacent infrastructure. Publishing containers to ghcr.io would reintroduce GitHub at exactly the layer where users install the OS. quay.io keeps that off the critical path. | |
| 55 | + | ||
| 56 | + | Rejected: ghcr.io (source-registry ecosystem mismatch), Docker Hub (pull rate limits, no advantage), self-hosted (real infra commitment, not worth v0 operational load — revisit v2+). | |
| 57 | + | ||
| 58 | + | ### Signing: cosign | |
| 59 | + | ||
| 60 | + | Runs as a CI task. Private key stored as an sr.ht secret; public key published in the Alloy repo. Users verify via `bootc switch --signature-source=<url>`. | |
| 61 | + | ||
| 62 | + | ## Layer structure | |
| 63 | + | ||
| 64 | + | The Containerfile at the repo root (see `Containerfile`) sketches: | |
| 65 | + | ||
| 66 | + | 1. **Base:** `FROM quay.io/fedora/fedora-bootc:41`. | |
| 67 | + | 2. **Third-party repos:** Tailscale, any COPRs Alloy depends on for packages not in Fedora main. | |
| 68 | + | 3. **Package additions:** the full Alloy stack from [STACK.md](STACK.md) — compositor, bar, launcher, notifications, terminal, editor, shell, viewers, utilities, continuity daemons, fonts, themes. | |
| 69 | + | 4. **Package removals:** stock Silverblue desktop pieces Alloy replaces (gnome-shell, gdm — the latter gated on the greeter pick). | |
| 70 | + | 5. **Config tree:** the tree at `etc/skel/.config/*` (new-user defaults), `etc/*` (system-wide), and `usr/share/alloy/*` (Alloy-owned templates) in the repo maps 1:1 into the image. | |
| 71 | + | 6. **Systemd presets:** which services are enabled by default (syncthing off by default, gammastep off until enrolled, alloy-hinged conditionally on FW12, etc.). | |
| 72 | + | 7. **Branding:** os-release, plymouth splash. | |
| 73 | + | 8. **Validation:** `bootc container lint` runs at build. | |
| 74 | + | ||
| 75 | + | Fedora Flatpaks (Floorp specifically) are installed system-wide in the image via `flatpak install --system flathub`, with the Flathub remote added first. This trades image size for zero-friction first-boot — user reboots into Alloy, opens Floorp, done. | |
| 76 | + | ||
| 77 | + | ## Update cadence | |
| 78 | + | ||
| 79 | + | The sr.ht build triggers on: | |
| 80 | + | ||
| 81 | + | - **Push to `main`** — Alloy commits. | |
| 82 | + | - **Weekly cron** — picks up upstream Fedora base image updates. sr.ht doesn't have webhook receivers for external triggers, so cron is the pragmatic answer. | |
| 83 | + | ||
| 84 | + | Users receive updates via `bootc upgrade` — no push notifications, no forced restart. Alloy's update UX is the standard bootc UX. | |
| 85 | + | ||
| 86 | + | ## Fedora version tracking | |
| 87 | + | ||
| 88 | + | Pinned to `:41` explicitly. Bumps to `:42` are a deliberate act — Alloy tests the new base, updates the Containerfile, ships. Not automatic. This is the "correctness over deadline" pattern applied to base tracking. | |
| 89 | + | ||
| 90 | + | ## Open questions | |
| 91 | + | ||
| 92 | + | - [ ] Verify which Alloy packages are in Fedora main vs. need COPRs. Candidates that may need COPRs: `anyrun`, `satty`, `wl-screenrec` (depending on Fedora version). Audit at v0 packaging time. | |
| 93 | + | - [ ] `bootc-image-builder` for ISO generation. First-time-user path is `bootc install` from a live environment; the ISO is what makes that a smooth experience. Verify the ISO builder handles Alloy's specific package set. | |
| 94 | + | - [ ] Single-stage vs. multi-stage Containerfile. Single-stage until rebuild time hurts. | |
| 95 | + | - [ ] sr.ht account: `~maxmj` vs. `~makecreative`. LLC-owned makes sense long-term; personal is simpler v0. Recommend `~maxmj` for v0, migrate later if it grows. | |
| 96 | + | - [ ] Whether to publish `:latest`, `:41`, and `:41-YYYYMMDD` tags. Recommend all three — `:latest` for casual users, `:41` for version-pinned users, `:41-YYYYMMDD` for reproducibility. |
| @@ -1,0 +1,68 @@ | |||
| 1 | + | # Shell | |
| 2 | + | ||
| 3 | + | Nushell as the login shell, bash unchanged as `/bin/sh` and `/bin/bash`. Companion to [STACK.md](STACK.md) and [CONSOLE.md](CONSOLE.md). | |
| 4 | + | ||
| 5 | + | ## Thesis | |
| 6 | + | ||
| 7 | + | Alloy's audience writes their own scripts and reaches for the shell as a tool-glue surface, not just an interactive prompt. That reframes the shell pick away from "safe universal default" and toward "which shell makes daily tool-glue actually good." Nushell wins that on structural grounds — structured pipelines with typed values instead of stringly-typed byte streams — and every daily interaction compounds the advantage. | |
| 8 | + | ||
| 9 | + | The pick is coherent with the rest of Alloy: `alloy console` treats configs as typed data, `alloy_ui` treats widgets as functions over state, and now the shell treats commands as functions over typed pipelines. Same principle at three layers. | |
| 10 | + | ||
| 11 | + | ## Architecture: nu is login-only | |
| 12 | + | ||
| 13 | + | The pick that keeps the ecosystem tax bounded: | |
| 14 | + | ||
| 15 | + | - **Login shell:** `nu`. What the user sees when they open rio, when they SSH in, when they open a scratch pane. | |
| 16 | + | - **`/bin/sh`:** bash, unchanged. Every `curl | sh` invokes this. Untouched. | |
| 17 | + | - **`/bin/bash`:** bash, unchanged. Every `#!/bin/bash` script runs here. Untouched. | |
| 18 | + | - **`/etc/shells`:** contains both nu and bash. `chsh -s /bin/bash` reverts a user to bash cleanly and reversibly. | |
| 19 | + | ||
| 20 | + | **Consequence: `curl -fsSL https://example.com/install.sh | sh` works identically to any other distro.** The login shell is not in the invocation path. This was the biggest concern about picking a non-POSIX shell, and it turns out to be a misread of how `curl | sh` actually resolves. | |
| 21 | + | ||
| 22 | + | **Consequence: every `#!/bin/bash` or `#!/usr/bin/env bash` script the user writes or downloads runs in bash.** Their own tool-glue scripts can be nu (`#!/usr/bin/env nu`) — that's where the structured-pipeline advantage lives — but nothing forces them to. | |
| 23 | + | ||
| 24 | + | ## What Alloy ships | |
| 25 | + | ||
| 26 | + | `etc/skel/.config/nushell/` in the Alloy repo: | |
| 27 | + | ||
| 28 | + | - `config.nu` — main nu configuration. Table style, history (sqlite backend), fuzzy completions, Alloy color palette applied to nu's own output, integration hooks for starship / zoxide / direnv (conditional on presence). | |
| 29 | + | - `env.nu` — environment. `EDITOR=hx`, `VISUAL=hx`, PATH additions for `~/.local/bin` and `~/.cargo/bin`, cache directory prep for integration inits. | |
| 30 | + | - `aliases.nu` — a small curated set (`ll`, `la`, `..`, `...`, git shortcuts). No plugin framework. | |
| 31 | + | ||
| 32 | + | The Alloy image installs these into `~/.config/nushell/` on first boot (or symlinks — packaging decision at v0). | |
| 33 | + | ||
| 34 | + | ## Interoperability | |
| 35 | + | ||
| 36 | + | Where daily-use friction shows up, and how Alloy handles it: | |
| 37 | + | ||
| 38 | + | - **Tool init lines.** `eval "$(starship init bash)"` and equivalents. Starship, zoxide, direnv, atuin, and carapace all ship `--init nu`. For the long tail of tools that don't, Alloy ships small wrapper snippets in `etc/skel/.config/nushell/integrations/` as they're needed — added on demand, not preemptively. | |
| 39 | + | - **Interactive one-liners.** `FOO=bar cmd && cmd2` becomes `FOO=bar; cmd; cmd2` (or `try / catch` for the conditional flavor). Muscle-memory tax; not a real barrier. | |
| 40 | + | - **`export`.** `$env.FOO = "bar"` in nu, `export FOO=bar` in bash. Both work in their respective contexts. | |
| 41 | + | - **Piped stdin.** Third-party CLIs that expect line-oriented text stdin: nu's default output is a table, but piping to an external command auto-flattens to text. Explicit control via `| to text` when needed. | |
| 42 | + | - **Command-substitution.** `` `foo` `` becomes `(foo)` in nu. Small learn. | |
| 43 | + | ||
| 44 | + | ## Accepted costs | |
| 45 | + | ||
| 46 | + | - **Pre-1.0 velocity.** Nushell is currently in the 0.100.x range. Breaking changes across releases have slowed dramatically since 0.75+ (era of "every release rewrites your config" is over), but a couple of key renames per year still happen. Alloy accepts drift maintenance for `config.nu` the same way it does for `userChrome.css`. | |
| 47 | + | - **Documentation tax.** Google results for "how do I X on the command line" assume bash. Fish translations are usually a nearby SO answer; nu translations often require a trip to nushell.sh's book. Real cost, absorbed by the "audience that reads docs deliberately" targeting. | |
| 48 | + | - **Command name collisions.** Nu's built-in `ls`, `cd`, `cp`, `ps` behave differently from GNU equivalents (structured output, different flags). Users learn the mapping. Strictly nicer for interactive use once learned. | |
| 49 | + | - **Ecosystem depth.** Fewer third-party CLIs ship `--init nu` than `--init fish` or `--init zsh`. Alloy backfills as needed via `etc/skel/.config/nushell/integrations/`. | |
| 50 | + | ||
| 51 | + | ## Prompt | |
| 52 | + | ||
| 53 | + | **starship** (Rust, cross-shell). Boring, works, ships `starship init nu` first-class. Alloy configures starship's own palette to match the tokens. | |
| 54 | + | ||
| 55 | + | Bespoke nu prompt as a small design-system exercise is a legitimate future project — nushell's prompt hook is rich enough to author an Alloy prompt directly — but not v0 scope. Deferred. | |
| 56 | + | ||
| 57 | + | ## Rejected | |
| 58 | + | ||
| 59 | + | - **fish.** Excellent shell, safer pick. Rejected because the audience is script-writers, not paste-and-forget users, so nu's structural advantages earn their cost. If nu turns out to be the wrong call in practice, fish is the fallback and the switch is small (login-shell only, `chsh -s /usr/bin/fish`). | |
| 60 | + | - **zsh.** Mostly-POSIX and powerful, but raw zsh is unpleasant and requires a config framework (oh-my-zsh, zinit) to be tolerable. Adopting a framework means importing someone else's opinions, which Alloy avoids everywhere else. | |
| 61 | + | - **bash.** No opinion, works. Best UX-out-of-box is worst-in-class. Alloy is opinionated everywhere; being un-opinionated at the shell layer specifically feels like leaving value on the table. | |
| 62 | + | - **elvish, xonsh, oil.** Interesting, small communities. Adopting them would put Alloy in a "maintaining half the ecosystem" position. | |
| 63 | + | ||
| 64 | + | ## Open questions | |
| 65 | + | ||
| 66 | + | - [ ] Nu vs. bash for root-level shells (`root`'s login shell). Bash is safer for recovery scenarios; nu would be consistent. Recommend bash — recovery is the wrong time to hit a shell surprise. Confirm. | |
| 67 | + | - [ ] History file backend: `sqlite` (better search, atuin-compatible) vs. `plaintext` (simpler, human-readable). Recommend sqlite; atuin is a natural future adoption for cross-machine history. | |
| 68 | + | - [ ] Whether Alloy ships atuin integration by default (encrypted history sync via Syncthing or atuin's own server). Adjacent to CONTINUITY.md's theme. Defer. |
| @@ -1,0 +1,19 @@ | |||
| 1 | + | # Alloy greetd configuration | |
| 2 | + | # | |
| 3 | + | # Boots into tuigreet on VT1. tuigreet prompts for user and password, | |
| 4 | + | # then execs niri-session. Alloy palette applied via tuigreet's --theme. | |
| 5 | + | ||
| 6 | + | [terminal] | |
| 7 | + | vt = 1 | |
| 8 | + | switch = true | |
| 9 | + | ||
| 10 | + | [default_session] | |
| 11 | + | command = """ | |
| 12 | + | tuigreet \ | |
| 13 | + | --time \ | |
| 14 | + | --remember \ | |
| 15 | + | --asterisks \ | |
| 16 | + | --cmd niri-session \ | |
| 17 | + | --theme 'container=white;border=dark_gray;text=black;time=dark_gray;prompt=yellow;input=black;action=dark_gray;button=yellow' | |
| 18 | + | """ | |
| 19 | + | user = "greeter" |
| @@ -1,0 +1,17 @@ | |||
| 1 | + | NAME="Alloy" | |
| 2 | + | PRETTY_NAME="Alloy 0.0 (pre-v0)" | |
| 3 | + | VERSION="0.0" | |
| 4 | + | VERSION_ID="0.0" | |
| 5 | + | ID=alloy | |
| 6 | + | ID_LIKE=fedora | |
| 7 | + | VARIANT="Base" | |
| 8 | + | VARIANT_ID=base | |
| 9 | + | ANSI_COLOR="0;38;2;209;162;52" | |
| 10 | + | LOGO=alloy | |
| 11 | + | DEFAULT_HOSTNAME=alloy | |
| 12 | + | HOME_URL="https://git.sr.ht/~maxmj/alloy" | |
| 13 | + | DOCUMENTATION_URL="https://git.sr.ht/~maxmj/alloy/tree/main/item/docs" | |
| 14 | + | SUPPORT_URL="https://git.sr.ht/~maxmj/alloy" | |
| 15 | + | BUG_REPORT_URL="https://todo.sr.ht/~maxmj/alloy" | |
| 16 | + | VENDOR_NAME="Make Creative, LLC" | |
| 17 | + | VENDOR_URL="https://makenot.work" |