Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

os.mjs devices: m = make live USB, w = wipe+install (disambiguated)

User can now clearly choose between three operations per target:

c (non-boot internal disk) — clone: add boot tree alongside existing
filesystem (legacy, preserves data)
w (non-boot internal disk) — wipe + install ac native OS
(destructive, replaces existing)
m (non-boot removable) — make live USB
(destructive, produces bootable stick)
u (any non-boot) — update from cloud (OTA)

Key/target mismatches are refused with a low buzz so `w` can't
accidentally wipe a USB stick that the user meant to clone to, and `m`
can't nuke an internal disk. Confirm screen text branches on
operation kind: "install ac native?" vs "make live USB?" vs "clone os?",
each with a specific "ERASES ALL DATA on <this disk | this USB>"
warning.

Under the hood: m and w both route through the same clone-confirm →
flashUpdate path; the C flash_thread_fn detects the whole-disk target
and runs sfdisk + mkfs.vfat before the copy step. The os.mjs level
distinction is purely UI/safety.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+55 -25
+55 -25
fedac/native/pieces/os.mjs
··· 233 233 } 234 234 return; 235 235 } 236 - // 'w' to wipe + format + install onto a target disk. Works on: 237 - // - blank disks (target.device is already a whole disk) 238 - // - formatted disks (target.device is a partition like 239 - // /dev/nvme0n1p1; we derive the parent /dev/nvme0n1) 240 - // Refuses on the currently-booted device — wiping the disk we're 241 - // running from would brick the live system mid-flash. 242 - // 243 - // The C flash_thread_fn detects the whole-disk target and runs 244 - // sfdisk + mkfs.vfat before the normal copy step. 245 - if (e.is("keyboard:down:w")) { 236 + // 'w' (internal) / 'm' (removable) — wipe + install. Same flash code 237 + // path (C flash_thread_fn does sfdisk + mkfs.vfat on whole-disk 238 + // targets), but prompt text + intent differ so the user knows what 239 + // they're about to do: 240 + // w + non-removable → "install ac native OS" (replace Fedora/etc.) 241 + // m + removable → "make live USB" 242 + // Boot device is refused for both — wiping the disk we're running 243 + // from would brick the live system mid-flash. 244 + if (e.is("keyboard:down:w") || e.is("keyboard:down:m")) { 246 245 const tgt = targets[deviceIdx]; 247 246 if (!tgt) return; 248 247 const isBoot = tgt.device === bootDev; ··· 251 250 sound?.synth({ type: "square", tone: 110, duration: 0.15, volume: 0.10, attack: 0.005, decay: 0.12 }); 252 251 return; 253 252 } 253 + const pressedM = e.is("keyboard:down:m"); 254 + // Key/target semantics: 255 + // m only meaningful for removable; w only for internal. 256 + // Refuse the mismatched case with a low buzz so the keys can't be 257 + // swapped accidentally (e.g. `w` on the second USB would wipe it 258 + // in a way the user didn't expect). 259 + if (pressedM && !tgt.removable) { 260 + sound?.synth({ type: "square", tone: 110, duration: 0.15, volume: 0.10, attack: 0.005, decay: 0.12 }); 261 + return; 262 + } 263 + if (!pressedM && tgt.removable) { 264 + sound?.synth({ type: "square", tone: 110, duration: 0.15, volume: 0.10, attack: 0.005, decay: 0.12 }); 265 + return; 266 + } 254 267 // Derive whole-disk path. Partition nodes look like: 255 268 // /dev/nvme0n1p1 → /dev/nvme0n1 256 269 // /dev/mmcblk0p1 → /dev/mmcblk0 257 270 // /dev/sda1 → /dev/sda 258 271 let wholeDisk = tgt.device; 259 272 if (!tgt.blank) { 260 - // Strip trailing "p<N>" (NVMe/eMMC) or trailing digits (sd*). 261 273 wholeDisk = wholeDisk.replace(/p?\d+$/, ""); 262 274 } 263 - cloneTarget = { ...tgt, device: wholeDisk, blank: true }; // mark as wipe-target so the prompt says so 275 + cloneTarget = { 276 + ...tgt, 277 + device: wholeDisk, 278 + blank: true, 279 + operation: pressedM ? "live-usb" : "install", 280 + }; 264 281 state = "clone-confirm"; 265 282 sound?.synth({ type: "sawtooth", tone: 220, duration: 0.12, volume: 0.14, attack: 0.005, decay: 0.10 }); 266 283 return; ··· 677 694 write("tab/arrows: select", { x: pad, y: actY, size: 1, font }); 678 695 679 696 let hintY = actY + 14; 680 - if (sel?.blank) { 681 - // Blank disk — only meaningful action is wipe + format + install. 682 - ink(255, 180, 60); 683 - write("w: wipe + install (formats!)", { x: pad, y: hintY, size: 1, font }); 684 - hintY += 14; 685 - } else if (!isBootDev) { 686 - ink(100, 200, 140); 687 - write("c: clone current os", { x: pad, y: hintY, size: 1, font }); 688 - hintY += 14; 697 + if (sel && !isBootDev) { 698 + if (sel.removable) { 699 + // Removable — offer "make live USB" (format + install). 700 + ink(100, 200, 230); 701 + write("m: make live USB", { x: pad, y: hintY, size: 1, font }); 702 + hintY += 14; 703 + } else { 704 + // Internal — offer wipe + install AND clone (clone adds our 705 + // boot tree without wiping, for dual-boot scenarios). 706 + ink(255, 180, 60); 707 + write("w: wipe + install ac native", { x: pad, y: hintY, size: 1, font }); 708 + hintY += 14; 709 + ink(100, 200, 140); 710 + write("c: clone (preserves existing)", { x: pad, y: hintY, size: 1, font }); 711 + hintY += 14; 712 + } 689 713 } 690 714 ink(100, 160, 220); 691 715 write("u: update from cloud", { x: pad, y: hintY, size: 1, font }); 692 716 } 693 717 694 718 } else if (state === "clone-confirm") { 719 + const op = cloneTarget?.operation; // "install" | "live-usb" | undefined (clone) 720 + let heading = "clone os?"; 721 + let warn = "this will overwrite the target!"; 722 + if (op === "install") { heading = "install ac native?"; warn = "ERASES ALL DATA on this disk"; } 723 + else if (op === "live-usb") { heading = "make live USB?"; warn = "ERASES ALL DATA on this USB"; } 695 724 ink(T.warn[0], T.warn[1], T.warn[2]); 696 - write("clone os?", { x: pad, y: stateY, size: 2, font: "matrix" }); 725 + write(heading, { x: pad, y: stateY, size: 2, font: "matrix" }); 697 726 698 727 ink(T.fgMute + 20, T.fgMute + 20, T.fgMute); 699 728 write("from: " + (system?.bootDevice || "?"), { x: pad, y: stateY + 24, size: 1, font }); ··· 702 731 ink(T.fg); 703 732 write(currentVersion, { x: pad, y: stateY + 56, size: 1, font }); 704 733 705 - ink(255, 180, 60); 706 - write("this will overwrite the target!", { x: pad, y: stateY + 74, size: 1, font }); 734 + ink(255, 80, 80); 735 + write(warn, { x: pad, y: stateY + 74, size: 1, font }); 707 736 708 737 const pulse = Math.floor(200 + 55 * Math.sin(frame * 0.1)); 709 738 ink(pulse, 255, pulse); 710 - write("y: clone n: cancel", { x: pad, y: stateY + 92, size: 1, font }); 739 + const verb = op === "live-usb" ? "make" : (op === "install" ? "install" : "clone"); 740 + write(`y: ${verb} n: cancel`, { x: pad, y: stateY + 92, size: 1, font }); 711 741 712 742 } else if (state === "cloning") { 713 743 // Reuse flashing UI