Files
roro9stack/docs/milestones/F1.md
T

13 KiB

F1 — Files and Notes

Status: in progress. The Storage App (issue #3) shipped as v0.9.0 on 2026-10-06. Notes (#19) is being built on branch notes. The card as a USB drive (#1) comes 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

  1. 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.
  2. 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.
  3. The App: browsing, the clipboard, dialogs, details.
  4. Maintenance: usage, Clean-up and Erase moved in from Settings, with the warning.
  5. Viewers: text, hex, .pcap, .gpx, .ota.
  6. 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 with File; 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 in lib/files): text through TextPager, 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 .gpx and an .ota are 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 m anywhere 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), and rm and du through the same code, so rm now takes folders and follows the rules; ls shows 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).

Found on the way: a panic at Wi-Fi join, there since v0.7.0 (SNTP started twice, issue #46). Fixed in v0.9.0.

Notes (issue #19)

Plain text notes on the SD card, written on the device. Q30 settled the base: .txt files in /notes, created, edited and deleted from the device, never offered by Storage Clean-up.

Decisions (design round 2026-10-06)

# Decision
Q141 A Notes App in the Launcher. One row per note: its first line as the title, then the date. Newest first; s switches to by name. n new, Enter opens, d deletes after a confirmation, r renames the file.
Q142 A new note's file name is never typed: it comes from the first line when the note is first saved (shopping-list.txt), or note-20261006-0919.txt if that line is empty. It doesn't change afterwards unless the note is renamed.
Q143 Autosave, no "discard changes?" prompt: five seconds after the last key, on leaving the note or the App, and when the screen turns off. A save writes a temporary file and renames it over the note, so a power cut loses the last few seconds at most. A temporary file left behind is offered back at the next open.
Q144 The whole note is in memory while it's edited, up to 16 KB. A bigger text file opens read-only in the Storage App's viewer. The App refuses to open below the memory floors (Q86). Editing files of any size must come in a later release: issue #47.
Q145 The editor wraps at spaces, 38 columns by 8 rows, with a line for the name and the state. Enter is a new line, Del deletes backwards, Fn+arrows move (the Text Entry rule), Ctrl+A and Ctrl+E go to the start and the end of the line, Tab types two spaces, Back saves and returns. The Compose Key works as elsewhere.
Q146 The Storage App's text viewer gets e: edit this file with the same editor, for a text file up to 16 KB that isn't read-only. That lifts Q135 without Apps opening each other (#43 stays).
Q147 The list is flat: the files directly in /notes. Sub-folders are reached through the Storage App.
Q148 UTF-8, LF line ends; a file with CRLF is saved back with LF. Characters the font lacks are kept on save.
Q149 Left out, each with its issue: editing files of any size (#47), searching inside notes (#48), undo (#49), selecting and copying text (#50).
Q150 Ships as v0.10.0 when built and checked on the device.

Done when

  • A note can be started, typed with accents, left and found again in the list under its first line; renamed; deleted after a confirmation.
  • What's typed is on the card five seconds after the last key, and after Back, Home, or the screen turning off, without a prompt.
  • Pulling the power while typing loses a few seconds at most, and the note is never left empty or half-written.
  • The cursor moves by character and by line through wrapped text, and the screen follows it; a 16 KB note edits without lag.
  • A note at 16 KB refuses more text and says so; a bigger file opens read-only.
  • e in the Storage App's text viewer edits a file; a read-only one is refused with its reason.
  • Free heap stays above the floors with a 16 KB note open and IRC connected.

Work breakdown

  1. Model (host-tested): the text buffer with its cursor, wrapping and scrolling; file names from first lines.
  2. The editor on the device: loading, drawing, keys, autosave through a temporary file, recovery.
  3. The Notes App: the list with titles, new, rename, delete.
  4. e in the Storage App.
  5. Checks on the device, recorded here.