fix: never claim 'not in store' while the store state is unknown

A failed/unloaded store snapshot used to be indistinguishable from
a known-empty store, so every missing capability was labelled 'not
in store' the moment the hub or store endpoint was unreachable — a
wrong claim. storeCapabilities is now nullable (null = unknown):
missing caps then get the plain missing chip with an honest
tooltip, no install offer and no not-in-store claim; the analyzer
message says the store cannot be checked right now (EN+DE). Split
and badge covered by new unit + widget tests.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-07-22 14:02:28 +02:00
parent c4a39a3779
commit 2535c28fce
6 changed files with 131 additions and 34 deletions

View file

@ -95,12 +95,12 @@ class FlowEditorPage extends StatefulWidget {
/// then call the Hub install API.
final AddModuleSourceCallback? onAddModuleSource;
/// Capabilities the public store knows how to install. The
/// analyzer uses this to decide whether to show "Install …"
/// (in store) or "Add source for …" (not in store) as the
/// quick-fix on an unknown `use:` line. Empty list = store
/// silent no install button offered.
final List<String> storeCapabilities;
/// Capabilities the store can actually install, or null when
/// the store state is UNKNOWN (snapshot not loaded / store
/// unreachable). Drives the analyzer's quick-fix choice and
/// the flow list's badge: in store → Install; known-absent →
/// "not in store" + recovery paths; unknown neither claim.
final List<String>? storeCapabilities;
/// Host-side native file picker for the Run tab's file inputs.
/// Studio passes a real file dialog; null keeps the manual
@ -141,7 +141,7 @@ class FlowEditorPage extends StatefulWidget {
this.style,
this.onInstallCapability,
this.onAddModuleSource,
this.storeCapabilities = const [],
this.storeCapabilities,
this.activeProject = '',
this.onSwitchToFileProject,
this.onPickFile,
@ -616,7 +616,9 @@ outputs:
installedNames: _installedNames(
widget.availableCapabilities,
),
storeNames: _installedNames(widget.storeCapabilities),
storeNames: widget.storeCapabilities == null
? null
: _installedNames(widget.storeCapabilities!),
activeProject: widget.activeProject,
onOpen: _openFile,
onRefresh: _refreshFiles,
@ -1399,10 +1401,12 @@ class _FileList extends StatefulWidget {
final Set<String> installedNames;
/// Bare capability NAMES a configured store can install
/// (host-filtered to installable entries). Missing caps
/// outside this set render the "not in store" state instead
/// of an install action that the hub would refuse.
final Set<String> storeNames;
/// (host-filtered to installable entries), or null when the
/// store state is unknown. Missing caps outside this set
/// render the "not in store" state instead of an install
/// action that the hub would refuse; with null neither claim
/// is made.
final Set<String>? storeNames;
/// Active workspace project slug; empty = all projects. Files
/// without a `project:` key count as `general`.
@ -1670,6 +1674,7 @@ class _FileListState extends State<_FileList> {
MissingModulesBadge(
installable: split.installable,
notInStore: split.notInStore,
unclassified: split.unclassified,
strings: strings,
// Install only what the store
// resolves the not-in-store