The Storage App browses the SD card: folders first with sizes and dates, three sorts, one item at a time with a clipboard (c, x, v), rename, delete after counting what's inside, new folder, details. A listing holds 256 entries and says when a folder has more. FileOps does the card's work for the App and the console alike, one operation at a time on the storage task in turns of about 150 ms, so Logs and Captures are still written during a long copy. A copy shows progress, can be cancelled (what it wrote is taken back) and compares sizes after. The read-only rules are checked there: the firmware's top-level folders, /gemini/cache, and files being written (a Track, a Capture, an upload, today's IRC Logs). A listing reads the folder straight from FatFs: through the Arduino File, 329 entries took over two seconds. Viewers by type: text read a screen at a time whatever the file's size (logs open at the end), a hex dump, a Capture's packets as the LoRa Scanner lists them, a Track's summary, and an Update File checked as an install would check it, without writing anything. Tab shows any file as hex or text. Settings > Storage is gone: usage, Storage Clean-up and Erase are the App's Maintenance, behind a warning. The Storage Warning points there. The Clock sets the system time whatever its source, so files are dated correctly with a GNSS Fix alone (Q137). Console: cp, mv, mkdir, du, cancel; rm takes folders and follows the rules; ls shows dates; Debug Builds get `sd fill`. 424 host tests. Checked on the device: docs/milestones/F1.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
9.4 KiB
F1 — Files and Notes
Status: in progress (branch f1): the Storage App (issue #3) is built and checked on the device, not merged yet. Notes (#19) and the card as a USB drive (#1) come after.
Goal: get at what's on the SD card from the device itself: browse it, look inside the files the firmware writes, copy, move, rename and delete, and keep notes. A side milestone, like G1 and S1; Files and Notes were M3's original second half (Q30, Q89).
The Storage App (issue #3)
Until now the card could be looked at only through the Debug Console (ls, get, put), and Settings > Storage could only delete whole categories by age.
On the card today: six top-level folders, irc, wifi, updates, gnss, gemini and captures. No notes yet; settings are in flash, not on the card.
Decisions (design round 2026-10-06)
| # | Decision |
|---|---|
| Q128 | An App of its own, Storage, in the Launcher. Settings > Storage goes away: its usage figures, Storage Clean-up and "Erase SD card" move into the App, under Maintenance, behind a warning that these delete things for good. |
| Q129 | A row shows the name, then the size or "folder", then the date modified. Folders first, then by name; s cycles the sort (name, date, size). The top line shows the path and the card's free space. |
| Q130 | Nothing is hidden. Read-only: /gemini/cache; any file the firmware has open right now (today's IRC log, a Track or Capture being recorded, a file being received); and the top-level folders themselves, which can't be renamed or deleted though their contents can. Everything else, the user's own data included, can be renamed, moved or deleted, always after a confirmation. |
| Q131 | One item at a time, with a clipboard: Enter opens; Back goes up, and leaves the App at the top; c copy, x cut, v paste into the current folder; r rename; d delete; n new folder; i details. |
| Q132 | Copy, move and delete work on folders too, recursively. The confirmation says what's inside: "Delete saved and its 42 files?". |
| Q133 | A copy is a job on the storage task in 4 KB pieces, with a progress Toast; Back cancels it. It checks free space first and asks before replacing anything. Afterwards the sizes are compared, not the contents: the driver is trusted since v0.6.1 (ADR 0007). A move within the card is a rename. |
| Q134 | Viewers by type. Text (.txt, .log, .gmi, .csv, .gpx, and anything that looks like text): read from the card as you scroll, so size doesn't matter; logs open at the end. .pcap: the LoRa Scanner's packet list. .gpx: a summary (start, duration, points, distance), Tab for the text. .ota: version, size, whether the signature is valid; Enter installs through Update from SD. Anything else: a hex dump. |
| Q135 | No editing: that comes with Notes (#19). |
| Q136 | A listing holds up to 256 entries, packed, about 10 KB; a bigger folder shows the first 256 by name and says how many more there are. The App refuses to open below the memory floors (Q86). |
| Q137 | The Clock also sets the system time, so files are dated correctly with GNSS alone and not only after NTP. A file dated before 2020 shows "-". |
| Q138 | Console: cp, mv and mkdir, next to ls and rm. |
| Q139 | Left out, each with its issue: selecting several items (#41), finding files by name (#42), opening a .gmi in the Gemini App (#43), a table view for .csv (#44), images (#45). |
| Q140 | Ships as v0.9.0 when done and checked. |
Done when
- The Storage App lists any folder of the card with sizes and dates, sorted three ways, and says so when a folder has more than 256 entries.
- A file can be copied, moved, renamed and deleted, and a folder too; a new folder can be made. Each destructive action asks first; a copy shows progress and can be cancelled.
- The read-only rules of Q130 hold, with a reason given when something is refused.
- Each viewer of Q134 opens its type, and a 1 MB text file scrolls without loading whole.
- Maintenance shows the card's usage and does what Settings > Storage did, behind its warning; Settings no longer has a Storage row; the Storage Warning points at the Storage App.
- A file written with only a GNSS Fix (no Wi-Fi) is dated correctly.
- Free heap stays above the floors with the App open, Wi-Fi and IRC on TLS.
Work breakdown
- Model (host-tested): paths and names, the read-only rules, the packed listing and its sorts, file types, sizes and dates for display, the GPX summary.
- Card operations: listing a folder, copy, move, delete (recursive, counted), new folder, as storage jobs with progress and cancel;
cp,mv,mkdir; the Clock sets the system time. - The App: browsing, the clipboard, dialogs, details.
- Maintenance: usage, Clean-up and Erase moved in from Settings, with the warning.
- Viewers: text, hex,
.pcap,.gpx,.ota. - Checks on the device, recorded here.
As built
FileOps(src/services/file_ops) does the card's work for the App and for the console alike: list, count, copy, move, delete, new folder. One operation at a time on the storage task, in turns of about 150 ms that queue themselves again, so Log lines and a Capture are written in between. The rules of Q130 are checked there, whoever asks.- A listing reads the folder straight from FatFs. Through the Arduino
File, every entry was looked up by name again for its size and again for its date: 329 entries took over two seconds. One pass now, and it's there before the screen has redrawn. Counting, copying and deleting still walk withFile; they show progress and can be stopped. - A copy shows its progress in a box in the App, not a Toast (Q133): it has a bar and says Back cancels. A cancelled or failed copy deletes what it had written. The copy gets today's date, like
cp. - The viewers (
src/apps/file_viewer, models inlib/files): text throughTextPager, which reads about a kilobyte around the screen and wraps at spaces, 38 columns; going back a line wraps the paragraph before again, so a file reads the same in both directions. A.pcap, a.gpxand an.otaare read through once by a storage job, in the same 150 ms turns. An Update File is fed to the installer's own parser with a sink that writes nothing, so "would it install" is the same answer an install gives. - Tab in a viewer shows the same file as hex, or as text (not in Q134).
- Maintenance is the last row at the top of the card, and
manywhere in the App. It's the old Settings > Storage page behind a dialog. - The Storage Warning was only ever a Toast; "selecting it opens Storage Clean-up" (CONTEXT.md) was never built. It now reads "SD card over 80% full: see Storage".
- Console:
cp,mv,mkdir(Q138), andrmandduthrough the same code, sormnow takes folders and follows the rules;lsshows dates. Debug Builds:sd fill <folder> <count>makes test files.
Checks on the device (2026-10-06, v0.8.1-2 Debug Build)
All in a scratch folder, /f1test, removed afterwards.
| Check | Result |
|---|---|
| Host tests | 424 pass (411 before the viewers' models) |
| Browsing | Folders first, sizes and dates, the three sorts; a 300-file and a 329-file folder show "first 256 of 300" and "of 329" |
| New folder, rename, copy, cut and paste, delete | Each works on a file and on a folder; a copy next to its original is named (2); a name in the way asks "Replace it?" |
| A folder of 11 files, 8.4 MB, copied | 19.4 s, 435 KB/s, the bar moving; two Log lines queued meanwhile were written |
| The same copy cancelled at 1.8 MB | "Cancelled: nothing was copied", and nothing was left behind |
| Delete | 341 files in 9.7 s; the dialog had counted them first |
| Read-only rules | /irc, /gnss (top-level folders), /, /gemini/cache and a folder made inside it, a folder into itself, a name with :; a Capture being recorded and the folder holding it; a folder under /irc while IRC runs. Each refused with its reason; the Capture could still be copied |
| Text | A 1 MB log opens at its last line at once; top, pages, lines; a file without an extension that looks like text opens as text |
| Hex | A 5 KB binary file; Tab from any other viewer |
.pcap |
A LoRa Capture: 3 packets as the Scanner lists them, Enter shows the Meshtastic header and bytes |
.gpx |
400 points: start, 33 min 15 s, 4.68 km; Tab shows the text |
.ota |
A signed file: version, "intact", "older than what's running", Enter asks to install (not confirmed). A tampered one: "image corrupted (hash mismatch)" |
| Maintenance | The warning, then usage, Clean-up's categories and Erase (not run) |
| Date with GNSS only | NTP pointed at an address that doesn't answer, restart: the Clock came from the Fix, and a folder made then is dated 2026-10-06 08:39. A Track from the day before, written the same way by v0.8.1, shows "-" |
| Memory | IRC connected, the App open on 256 entries: 61 KB free (70 KB before opening). Lowest since boot 29.7 KB, during IRC's TLS handshake |
| Stacks | storage 3.1 KB free of 6 KB at worst, loopTask 1.5 KB |
Not checked by hand: how the keys feel on the device itself; everything above was driven through the Debug Console's key command and screenshots.
One slip during the checks: a scripted key sequence ran in the wrong folder and renamed /gemini/saved to saved2, then copied it to the top of the card. Both were put right at once (renamed back, the copy deleted; 7 files, 53,798 bytes, as before).