Do not offer an import where one cannot be done

The Import button went into the library header unconditionally, so an Android
user got a page that opens, finds nothing, and cannot be pressed — worse than
no page at all, because it reads as broken rather than as absent.

`dr_plat::imports_supported()` answers the question the interface actually has,
which is not "did we find a volume". An empty list on Linux means plug one in;
false here means it cannot be done on this device however hard the user tries.
It is false on Android for two reasons that both have to be fixed before it
changes: there is no mount table to read and no path to type, and nothing
implements WritableStorage except LocalStorage.

The button is hidden rather than disabled. The buttons beside it come and go
with the selection — unavailable now, available in a moment — where this one
never will be here, and a permanently disabled control teaches the reader that
the row lies.

What this gates is the interface, not the engine. dr-ingest takes storage
traits and never a path, and cross-compiles to aarch64-linux-android today; it
should need no changes when a SAF implementation lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 19:19:40 +02:00
co-authored by Claude Opus 5
parent f4c21c4e2c
commit b1bf68022d
3 changed files with 55 additions and 2 deletions
+17 -1
View File
@@ -660,6 +660,15 @@ component HeaderActions inherits HorizontalLayout {
callback change-library();
callback toggle-pin-scope();
callback sync-now();
/// TRACES: FR-CAT-10
/// Whether this platform can reach a card at all (`dr_plat::imports_supported`).
///
/// Hidden rather than disabled, unlike the buttons above that come and go
/// with the selection: those are unavailable *now* and will be available
/// in a moment, where this one never will be on this device. A permanently
/// disabled control teaches the reader that the row lies.
in property <bool> can-import: false;
callback rescan();
callback open-import();
callback open-settings();
@@ -796,7 +805,7 @@ component HeaderActions inherits HorizontalLayout {
// for the same reason Rescan is — an import writes files the running scan
// would then half-see, and the two disagreeing about what is in a folder
// is a worse outcome than waiting.
if !root.scanning: Button {
if !root.scanning && root.can-import: Button {
text: "Import";
y: root.centred ? (root.row-height - self.height) / 2 : 0;
clicked => { root.open-import(); }
@@ -938,6 +947,11 @@ export component LibraryGrid inherits Rectangle {
/// Back to the launch screen, to change library or account.
callback change-library();
/// Open the settings page.
/// TRACES: FR-CAT-10
/// Whether this platform can import at all. Forwarded to the header, which
/// hides the button rather than disabling it.
in property <bool> can-import: false;
callback open-import();
callback open-settings();
@@ -1350,6 +1364,7 @@ export component LibraryGrid inherits Rectangle {
scanning: root.scanning;
syncing: root.syncing;
scope-pinned: root.scope-pinned;
can-import: root.can-import;
settings-armed: root.settings-armed;
settings-summary: root.settings-summary;
exporting: root.exporting;
@@ -1425,6 +1440,7 @@ export component LibraryGrid inherits Rectangle {
scanning: root.scanning;
syncing: root.syncing;
scope-pinned: root.scope-pinned;
can-import: root.can-import;
settings-armed: root.settings-armed;
settings-summary: root.settings-summary;
exporting: root.exporting;