{"id":32503347,"url":"https://github.com/sorinirimies/flashkraft","last_synced_at":"2026-03-04T03:00:55.093Z","repository":{"id":320754809,"uuid":"1081217740","full_name":"sorinirimies/flashkraft","owner":"sorinirimies","description":"A lightning fast, small app size and low memory footprint,  no Electron bloat, OS image writer application built with Rust and the Iced GUI framework.","archived":false,"fork":false,"pushed_at":"2026-02-20T13:18:59.000Z","size":2824,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-20T14:40:29.965Z","etag":null,"topics":["balena-etcher","iced","iced-rs","iced-rust","imager","iso","os","raspberry-pi","rpi","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sorinirimies.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-22T13:25:29.000Z","updated_at":"2026-02-20T13:19:02.000Z","dependencies_parsed_at":"2025-10-25T18:27:15.982Z","dependency_job_id":"12148d6a-ec56-4569-9f66-7fe603f7e55f","html_url":"https://github.com/sorinirimies/flashkraft","commit_stats":null,"previous_names":["sorinirimies/flashkraft"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/sorinirimies/flashkraft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorinirimies%2Fflashkraft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorinirimies%2Fflashkraft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorinirimies%2Fflashkraft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorinirimies%2Fflashkraft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sorinirimies","download_url":"https://codeload.github.com/sorinirimies/flashkraft/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorinirimies%2Fflashkraft/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29860110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T08:51:08.701Z","status":"ssl_error","status_checked_at":"2026-02-26T08:50:19.607Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["balena-etcher","iced","iced-rs","iced-rust","imager","iso","os","raspberry-pi","rpi","rust"],"created_at":"2025-10-27T18:00:15.685Z","updated_at":"2026-02-26T16:26:12.289Z","avatar_url":"https://github.com/sorinirimies.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FlashKraft ⚡\n\n[![Crates.io](https://img.shields.io/crates/v/flashkraft)](https://crates.io/crates/flashkraft)\n[![Documentation](https://docs.rs/flashkraft/badge.svg)](https://docs.rs/flashkraft)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Release](https://github.com/sorinirimies/flashkraft/actions/workflows/release.yml/badge.svg)](https://github.com/sorinirimies/flashkraft/actions/workflows/release.yml)\n[![CI](https://github.com/sorinirimies/flashkraft/actions/workflows/ci.yml/badge.svg)](https://github.com/sorinirimies/flashkraft/actions/workflows/ci.yml)\n\nA lightning-fast, lightweight OS image writer built entirely in Rust. Choose your interface:\n\n| | `flashkraft` (GUI) | `flashkraft-tui` (TUI) |\n|---|---|---|\n| Framework | [Iced](https://github.com/iced-rs/iced) 0.13 | [Ratatui](https://github.com/ratatui-org/ratatui) 0.30 |\n| Input | Mouse + keyboard | Keyboard only |\n| Themes | 21 built-in Iced themes | Multiple themes via `tui-file-explorer` |\n| Best for | Desktop users | SSH / headless / minimal setups |\n\nNo Electron, no shell scripts, no external tooling — pure Rust from UI to block device.\n\n## Preview\n\n### GUI (Iced desktop)\n\n![flashkraft_demo](https://github.com/user-attachments/assets/76549cb3-a65e-4a99-b638-1aac6d50c553)\n\n### TUI (Ratatui terminal)\n\n#### Full workflow\n\n![tui-demo](crates/flashkraft-tui/examples/vhs/generated/tui-demo.gif)\n\n#### Flash progress (tui-slider)\n\n![flash-progress](crates/flashkraft-tui/examples/vhs/generated/flash-progress.gif)\n\n#### File explorer \u0026 theme switcher\n\n![theme-switcher](crates/flashkraft-tui/examples/vhs/generated/theme-switcher.gif)\n\n\u003e **Note:** Demo GIFs are stored with [Git LFS](https://git-lfs.github.com/).\n\u003e Run `git lfs install \u0026\u0026 git lfs pull` after cloning if the images appear broken.\n\n## Features\n\n### Shared (both interfaces)\n\n- ⚡ **Pure-Rust flash engine** — no `dd`, no bash scripts; writes directly to the block device using `std::fs`, `nix` ioctls, and `sha2` verification\n- 🔒 **Write verification** — SHA-256 of the source image is compared against a read-back of the device after every flash\n- 🔄 **Partition table refresh** — `BLKRRPART` ioctl ensures the kernel picks up the new partition layout immediately, so the USB boots first time\n- 🧲 **Lazy unmount** — all partitions are cleanly detached via `umount2(MNT_DETACH)` before writing\n- 📁 **Multiple image formats** — ISO, IMG, DMG, ZIP, and more\n- 💾 **Automatic drive detection** — removable drives refreshed on demand\n- 🛡️ **Safe drive selection** — system drives flagged, oversized drives warned, read-only drives blocked\n- 🎯 **Real-time progress** — stage-aware progress bar with live MB/s speed display\n- 🪶 **Tiny footprint** — Rust-compiled binary, C-like memory usage, no Electron runtime\n\n### GUI extras\n\n- 🎨 **21 beautiful Iced themes** to choose from, persisted across sessions via `sled`\n- 🖱️ **Native file picker** — powered by `rfd` for OS-native open dialogs\n\n### TUI extras\n\n- ⌨️ **Fully keyboard-driven** — vim-style `j/k` navigation, `b`/`Esc` to go back\n- 📂 **Built-in file explorer** — browse and pick ISO files without leaving the terminal (`Tab` / `Ctrl+F`)\n- 📊 **Pie-chart drive overview** — storage breakdown rendered inline using `tui-piechart`\n- ✅ **Checkbox confirmation screen** — safety checklist before every flash via `tui-checkbox`\n- 🎚️ **Slider progress bar** — smooth flash-progress widget via [`tui-slider`](https://crates.io/crates/tui-slider)\n- 🎨 **Multiple file-explorer themes** — switchable live with `t` / `[`, panel toggled with `T`, powered by [`tui-file-explorer`](https://crates.io/crates/tui-file-explorer)\n- 🖥️ **Works over SSH** — no display server required\n\n## How flashing works\n\nFlashKraft uses a **self-elevating pure-Rust helper** pattern. When you click/confirm Flash:\n\n```\nMain process (GUI or TUI)\n  └─ pkexec /path/to/flashkraft[−tui] --flash-helper \u003cimage\u003e \u003cdevice\u003e\n       └─ Runs as root, pure Rust, no shell\n            1. UNMOUNTING  — reads /proc/mounts, calls umount2(MNT_DETACH) per partition\n            2. WRITING     — streams image → block device in 4 MiB chunks, emits PROGRESS lines\n            3. SYNCING     — fsync(fd) + sync() to flush all kernel write-back caches\n            4. REREADING   — BLKRRPART ioctl so the kernel sees the new partition table\n            5. VERIFYING   — SHA-256(image) == SHA-256(device[0..image_size])\n            6. DONE        — UI shows success\n```\n\nThe same binary is re-executed with elevated privileges via `pkexec` — no separate helper binary needs to be installed. All output (progress, logs, errors) is written to stdout as structured lines that the UI reads in real time.\n\n### Why not `dd`?\n\n| | `dd` approach | FlashKraft |\n|---|---|---|\n| Shell dependency | ✗ requires bash, coreutils | ✓ pure Rust |\n| Progress format | `\\r`-terminated, locale-dependent | ✓ structured `PROGRESS:bytes:speed` |\n| Speed unit handling | ✗ kB/s / MB/s / GB/s mixed | ✓ always normalised to MB/s |\n| Write verification | ✗ none | ✓ SHA-256 read-back |\n| Partition table refresh | ✗ not done | ✓ `BLKRRPART` ioctl |\n| Error reporting | ✗ exit code only | ✓ `ERROR:\u003cmessage\u003e` on every failure path |\n\n## The Elm Architecture (GUI)\n\nThe GUI crate is built using **The Elm Architecture (TEA)**, which Iced embraces as its natural pattern for interactive applications.\n\n### Core Concepts\n\n#### 1. Model (State)\n\n```rust\nstruct FlashKraft {\n    selected_image: Option\u003cImageInfo\u003e,   // Currently selected image file\n    selected_target: Option\u003cDriveInfo\u003e,  // Currently selected target drive\n    available_drives: Vec\u003cDriveInfo\u003e,    // Detected drives\n    flash_progress: Option\u003cf32\u003e,         // Progress 0.0–1.0\n    flash_bytes_written: u64,            // Bytes written so far\n    flash_speed_mb_s: f32,              // Current transfer speed\n    error_message: Option\u003cString\u003e,       // Error message if any\n    flashing_active: bool,              // Subscription guard\n    flash_cancel_token: Arc\u003cAtomicBool\u003e, // Cancellation signal\n}\n```\n\n#### 2. Messages\n\n```rust\nenum Message {\n    SelectImageClicked,\n    TargetDriveClicked(DriveInfo),\n    FlashClicked,\n    CancelFlash,\n    ResetClicked,\n    ImageSelected(Option\u003cPathBuf\u003e),\n    DrivesRefreshed(Vec\u003cDriveInfo\u003e),\n    FlashProgressUpdate(f32, u64, f32),\n    FlashCompleted(Result\u003c(), String\u003e),\n    ThemeChanged(Theme),\n}\n```\n\n#### 3. Data Flow\n\n```\nUser Action → Message → Update → State\n                            ↓\n                         Task/Subscription\n                            ↓\n                    Async Result → Message → Update → State\n                                                ↓\n                                             View → UI\n```\n\n## TUI Screen Flow\n\nThe TUI is a multi-screen application driven entirely by keyboard input:\n\n```\nSelectImage ──(Enter/confirm)──► SelectDrive ──(Enter)──► DriveInfo\n     ▲                                ▲                        │\n     │  (Esc/b)                       │  (Esc/b)           (f/Enter)\n     │                                │                        ▼\n     │                           SelectDrive            ConfirmFlash\n     │                                                       │\n     │                                                   (y — flash)\n     │                                                       ▼\n     │                                                   Flashing\n     │                                                  (c — cancel)\n     │                                                       │\n     └──────────────────(r — reset)────────── Complete / Error\n```\n\n### TUI Key Bindings\n\n| Screen | Key | Action |\n|--------|-----|--------|\n| SelectImage | `i` / `Enter` | Enter editing mode |\n| SelectImage | `Tab` / `Ctrl+F` | Open built-in file browser |\n| SelectImage | `Esc` / `q` | Quit |\n| BrowseImage | `j` / `↓` | Move cursor down |\n| BrowseImage | `k` / `↑` | Move cursor up |\n| BrowseImage | `Enter` | Descend into directory / select file |\n| BrowseImage | `Backspace` | Ascend to parent directory |\n| BrowseImage | `Esc` / `q` | Dismiss without selecting |\n| SelectDrive | `j` / `↓` | Scroll drive list down |\n| SelectDrive | `k` / `↑` | Scroll drive list up |\n| SelectDrive | `Enter` / `Space` | Confirm selected drive |\n| SelectDrive | `r` / `F5` | Refresh drive list |\n| SelectDrive | `Esc` / `b` | Go back |\n| DriveInfo | `f` / `Enter` | Advance to ConfirmFlash |\n| DriveInfo | `Esc` / `b` | Go back |\n| ConfirmFlash | `y` / `Y` | Begin flashing |\n| ConfirmFlash | `n` / `Esc` / `b` | Go back |\n| Flashing | `c` / `Esc` | Cancel flash |\n| Complete | `r` / `R` | Reset to start |\n| Complete | `q` / `Esc` | Quit |\n| Error | `r` / `Enter` | Reset to start |\n| Error | `q` / `Esc` | Quit |\n| Any | `Ctrl+C` / `Ctrl+Q` | Force quit |\n\n## Building and Running\n\n### Prerequisites\n\n- Rust 1.70 or later\n- `pkexec` (part of `polkit`, available on all major Linux distributions)\n- For GUI: a running display server (X11 or Wayland)\n- For TUI: any terminal emulator (works over SSH)\n\n### Build\n\n```bash\ngit clone https://github.com/sorinirimies/flashkraft.git\ncd flashkraft\n\n# Build everything\ncargo build --release\n\n# Build only the GUI\ncargo build --release --bin flashkraft\n\n# Build only the TUI\ncargo build --release --bin flashkraft-tui\n```\n\n### Run\n\n```bash\n# Launch the GUI\ncargo run --bin flashkraft\n\n# Launch the TUI\ncargo run --bin flashkraft-tui\n```\n\n### Development\n\n```bash\n# Debug builds (faster compilation)\ncargo run --bin flashkraft\ncargo run --bin flashkraft-tui\n\n# Run all tests across the workspace\ncargo test\n\n# Check without building\ncargo check --workspace\n\n# Lint\ncargo clippy --workspace\n\n# With backtraces\nRUST_BACKTRACE=1 cargo run --bin flashkraft-tui\n```\n\n## Usage\n\n### GUI\n\n1. **Select Image** — click the `+` button and choose an ISO, IMG, or DMG file\n2. **Select Drive** — pick the target USB or SD card from the detected drives list\n3. **Flash** — click **Flash!**; authenticate with `pkexec` when prompted\n4. **Wait** — the progress bar shows live stage, bytes written, and MB/s\n5. **Done** — verification passes automatically; safely remove the drive\n\n### TUI\n\n1. **Select Image** — press `i` to start typing a path, or `Tab`/`Ctrl+F` to open the file browser\n2. **Select Drive** — use `j`/`k` to scroll, `r` to refresh, `Enter` to confirm\n3. **Review Drive Info** — inspect the storage pie-chart, then press `f` to proceed\n4. **Confirm** — read the safety checklist and press `y` to flash, or `b` to go back\n5. **Wait** — the slider progress bar shows live stage, bytes written, and MB/s\n6. **Done** — press `r` to reset or `q` to quit\n\n## Project Structure\n\nThis is a [Cargo workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) with three crates:\n\n```\nflashkraft/                              ← workspace root\n├── Cargo.toml                           ← workspace manifest (shared dep versions)\n│\n├── crates/\n│   │\n│   ├── flashkraft-core/                 ★ shared logic — no GUI/TUI deps\n│   │   └── src/\n│   │       ├── lib.rs\n│   │       ├── flash_helper.rs          ★ privileged flash pipeline (pkexec)\n│   │       ├── flash_writer.rs          ★ wire-protocol parser \u0026 speed normaliser\n│   │       ├── domain/\n│   │       │   ├── drive_info.rs\n│   │       │   ├── image_info.rs\n│   │       │   └── constraints.rs       drive/image compatibility checks\n│   │       ├── commands/\n│   │       │   └── drive_detection.rs   async /sys/block enumeration\n│   │       └── utils/\n│   │           └── logger.rs            debug_log!, flash_debug!, status_log! macros\n│   │\n│   ├── flashkraft-gui/                  Iced desktop application\n│   │   ├── examples/\n│   │   │   ├── basic_usage.rs\n│   │   │   ├── custom_theme.rs\n│   │   └── vhs/                    GUI VHS tapes\n│   │       ├── demo-basic.tape\n│   │       ├── demo-build.tape\n│   │       ├── demo-quick.tape\n│   │       └── generated/          output GIFs (Git LFS)\n│   │   └── src/\n│   │       ├── main.rs                  entry point + --flash-helper dispatch\n│   │       ├── lib.rs\n│   │       ├── view.rs                  view orchestration\n│   │       ├── core/                    Elm Architecture\n│   │       │   ├── state.rs             Model + TEA methods\n│   │       │   ├── message.rs           all Message variants\n│   │       │   ├── update.rs            state transition logic\n│   │       │   ├── storage.rs           sled-backed theme persistence\n│   │       │   ├── flash_subscription.rs Iced Subscription — streams FlashProgress\n│   │       │   └── commands/\n│   │       │       └── file_selection.rs async rfd file dialog\n│   │       └── components/              UI widgets\n│   │           ├── animated_progress.rs\n│   │           ├── device_selector.rs\n│   │           ├── header.rs\n│   │           ├── progress_line.rs\n│   │           ├── selection_panels.rs\n│   │           ├── status_views.rs\n│   │           ├── step_indicators.rs\n│   │           └── theme_selector.rs\n│   │\n│   └── flashkraft-tui/                  Ratatui terminal application\n│       ├── examples/\n│       │   ├── headless_demo.rs\n│       │   ├── tui_demo.rs\n│       │   ├── flash_progress_demo.rs  ← tui-slider progress showcase\n│       │   └── theme_demo.rs           ← file-explorer theme switcher showcase\n│       └── vhs/                    TUI VHS tapes\n│           ├── tui-demo.tape\n│           ├── tui-headless.tape\n│           ├── flash-progress.tape\n│           ├── theme-switcher.tape\n│           └── generated/          output GIFs (Git LFS)\n│       └── src/\n│           ├── main.rs                  entry point + --flash-helper dispatch\n│           ├── lib.rs\n│           ├── tui/\n│           │   ├── app.rs               App state + all screen transitions\n│           │   ├── ui.rs                ratatui Frame rendering (all screens)\n│           │   ├── events.rs            keyboard event handler per screen\n│           │   ├── flash_runner.rs      async pkexec supervisor + line parser\n│           │   └── mod.rs\n│           └── file_explorer/\n│               └── mod.rs              built-in keyboard-driven file browser\n│\n├── scripts/\n│   ├── bump_version.sh\n│   └── check_publish.sh\n│\n└── .github/workflows/\n    ├── ci.yml\n    └── release.yml\n```\n\nItems marked ★ form the flash pipeline and are described in detail above.\n\n## Dependencies\n\n### `flashkraft-core`\n\n| Crate | Version | Purpose |\n|-------|---------|---------|\n| `sysinfo` | 0.30 | Drive enumeration |\n| `nix` | 0.29 | `umount2`, `BLKRRPART` ioctl, `fsync` |\n| `sha2` | 0.10 | SHA-256 write verification |\n| `tokio` | 1 | Async runtime |\n| `futures` / `futures-timer` | 0.3 / 3.0 | Async channel primitives |\n| `sled` | 0.34 | Embedded key-value store |\n| `dirs` | 5.0 | XDG data directory resolution |\n| `anyhow` | 1 | Error handling |\n\n### `flashkraft-gui`\n\n| Crate | Version | Purpose |\n|-------|---------|---------|\n| `iced` | 0.13 | Cross-platform GUI framework (Elm Architecture) |\n| `iced_aw` | 0.12 | Additional Iced widgets |\n| `iced_fonts` | 0.1 | Bootstrap icon font |\n| `rfd` | 0.15 | Native file/folder dialogs |\n\n### `flashkraft-tui`\n\n| Crate | Version | Purpose |\n|-------|---------|---------|\n| `ratatui` | 0.30 | Terminal UI framework |\n| `crossterm` | 0.29 | Cross-platform terminal control |\n| `tui-slider` | git | Flash-progress slider widget |\n| `tui-piechart` | git | Drive storage pie-chart widget |\n| `tui-checkbox` | git | Drive-list and confirm-screen checkboxes |\n\n## Architecture Highlights\n\n- **Shared core crate** — `flashkraft-core` contains all flash logic; both UIs are thin frontends over the same engine\n- **Pure-Rust flash engine** — zero shell scripts or external binaries\n- **Self-elevating helper** — single binary per UI, no install-time setup beyond `polkit`\n- **Elm Architecture (GUI)** — unidirectional data flow, pure `update`/`view` functions\n- **Screen-based state machine (TUI)** — each `AppScreen` variant owns its event handler and render function\n- **0 warnings** — clean `cargo build --workspace` and `cargo test --workspace`\n\n## Demo GIFs \u0026 Git LFS\n\nAll generated GIFs under `examples/vhs/generated/` are tracked by [Git LFS](https://git-lfs.github.com/) (see `.gitattributes`).\n\n```bash\n# One-time setup after cloning\ngit lfs install \u0026\u0026 git lfs pull   # or: just lfs-pull\n\n# Regenerate all demos (requires vhs installed)\njust vhs-all\n\n# Regenerate TUI demos only  (output: crates/flashkraft-tui/examples/vhs/generated/)\njust vhs-tui\n\n# Regenerate GUI demos only  (output: crates/flashkraft-gui/examples/vhs/generated/)\njust vhs-gui\n\n# Render a single tape by name\njust vhs-tape tui-demo\njust vhs-tape flash-progress\njust vhs-tape theme-switcher\njust vhs-tape demo-basic\n\n# List all available tapes and generated GIFs\njust vhs-list\n```\n\n| Tape | What it shows |\n|---|---|\n| `tui-demo` | Full keyboard-driven wizard: image → drive → flash → complete |\n| `tui-headless` | Headless state-machine demo (no TTY required) |\n| `flash-progress` | Animated `tui-slider` progress bar during a simulated write |\n| `theme-switcher` | Live file-explorer theme cycling (`t` / `[`) and theme panel (`T`) |\n| `demo-basic` | GUI basic usage |\n| `demo-build` | GUI build walkthrough |\n| `demo-quick` | GUI quick-start |\n\nInstall VHS:\n\n```bash\nbrew install vhs                                   # macOS\ngo install github.com/charmbracelet/vhs@latest    # any platform\n```\n\nTo run Rust examples from the workspace root:\n\n```bash\n# Core examples\ncargo run -p flashkraft-core --example detect_drives\ncargo run -p flashkraft-core --example constraints_demo\ncargo run -p flashkraft-core --example flash_writer_demo\n\n# GUI examples  (crates/flashkraft-gui/examples/)\ncargo run -p flashkraft-gui --example basic_usage\ncargo run -p flashkraft-gui --example custom_theme\n\n# TUI examples  (crates/flashkraft-tui/examples/)\ncargo run -p flashkraft-tui --example headless_demo\ncargo run -p flashkraft-tui --example flash_progress_demo   # animated tui-slider demo\ncargo run -p flashkraft-tui --example theme_demo            # file-explorer theme switcher\n```\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Keep all flash logic in `flashkraft-core` — neither the GUI nor the TUI crate should contain flash pipeline code\n2. Follow the Elm Architecture pattern in the GUI — all state changes via `update`\n3. Follow the screen-state-machine pattern in the TUI — screen transitions via `App` methods\n4. Keep functions pure where possible\n5. Add unit tests for any new logic, especially in `flash_helper.rs`, `flash_writer.rs`, `app.rs`, and `events.rs`\n6. Run `cargo test --workspace` and `cargo clippy --workspace` before opening a PR\n\n## Learning Resources\n\n- [Iced Documentation](https://docs.rs/iced/)\n- [Ratatui Documentation](https://docs.rs/ratatui/)\n- [The Elm Architecture Guide](https://guide.elm-lang.org/architecture/)\n- [Iced Examples](https://github.com/iced-rs/iced/tree/master/examples)\n- [Ratatui Examples](https://github.com/ratatui-org/ratatui/tree/main/examples)\n- [The Rust Book](https://doc.rust-lang.org/book/)\n- [nix crate](https://docs.rs/nix/) — POSIX ioctls and syscalls from Rust\n\n## License\n\nMIT — see [LICENSE](LICENSE) for details.\n\n## Acknowledgments\n\n- GUI built with [Iced](https://github.com/iced-rs/iced)\n- TUI built with [Ratatui](https://github.com/ratatui-org/ratatui)\n- Follows [The Elm Architecture](https://guide.elm-lang.org/architecture/)\n- Flash pipeline design inspired by [Balena Etcher](https://github.com/balena-io/etcher)\n- Terminal widgets: [tui-slider](https://github.com/sorinirimies/tui-slider), [tui-piechart](https://github.com/sorinirimies/tui-piechart), [tui-checkbox](https://github.com/sorinirimies/tui-checkbox)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsorinirimies%2Fflashkraft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsorinirimies%2Fflashkraft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsorinirimies%2Fflashkraft/lists"}