feat(studio): clearer store + channel switcher + approvals & welcome polish
Some checks failed
Security / Security check (push) Failing after 2s

- store: repair the filter dialog crash (a Spacer lived directly in
  AlertDialog.actions, which is an OverflowBar, not a Flex — it threw
  and rendered a broken dialog). Buttons now sit in a Row.
- store: promote the module-store manager from a bare icon to a
  labelled 'Add store' button, and fully localize the dialog (DE/EN).
- store: add a curated 'Suggested stores' shelf with one-click
  add/remove (first entry: Recl∆Im). Each suggestion is probed for
  reachability and shows 'not available yet' until its index is
  published, instead of failing only on click. Fail-open on network
  errors so a transient hiccup never hides a real store.
- sidebar: the channel pill is now a one-click channel switcher
  (menu with per-channel running state + active check; switching
  writes ~/.chain/current-channel, restarts the daemon, and Studio
  repoints to the new channel).
- approvals: lead the card with the human prompt ('what am I
  releasing?') and demote the flow/step id to a metadata line; clear
  fallback when the step left the prompt empty.
- welcome: tidy the docs grid into equal-height paired rows with a
  full-width trailing card for the odd one out.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-18 21:29:39 +02:00
parent 62ed3951e5
commit 5220f19ff8
10 changed files with 865 additions and 148 deletions

View file

@ -234,10 +234,15 @@ class _StorePageState extends State<StorePage> {
onPressed: _openFilterDialog,
),
),
IconButton(
icon: const Icon(Icons.storefront_outlined, size: 18),
tooltip: 'Module stores',
onPressed: () => ChainStoresDialog.show(context),
Padding(
padding: const EdgeInsets.only(right: ChainSpace.sm),
child: OutlinedButton.icon(
icon: const Icon(Icons.add_business_outlined, size: 16),
label: Text(l.storesManagerButton),
onPressed: () => ChainStoresDialog.show(context),
style:
OutlinedButton.styleFrom(visualDensity: VisualDensity.compact),
),
),
IconButton(
icon: const Icon(Icons.refresh, size: 18),
@ -1139,38 +1144,49 @@ class _FilterDialogState extends State<_FilterDialog> {
],
),
),
// A single Row is the one action child: AlertDialog wraps
// `actions` in an OverflowBar (not a Flex), so a bare
// `Spacer()` here throws it needs a Flex parent. Nesting
// the buttons in a Row gives the Spacer a valid Flex
// ancestor and keeps Reset left / Cancel+Apply right.
actions: [
TextButton(
onPressed: () {
setState(() {
_status = '';
_source = '';
_installedOnly = false;
// Reset puts `installableOnly` back to its default
// (on) the operator hits Reset to escape a
// custom-filter state and expects the default view
// back, not an everything-including-planned view.
_installableOnly = true;
});
},
child: Text(l.storeFilterReset),
),
const Spacer(),
TextButton(
onPressed: () => Navigator.pop(context, null),
child: Text(l.buttonCancel),
),
FilledButton(
onPressed: () => Navigator.pop(
context,
_FilterOutcome(
status: _status,
source: _source,
installedOnly: _installedOnly,
installableOnly: _installableOnly,
Row(
children: [
TextButton(
onPressed: () {
setState(() {
_status = '';
_source = '';
_installedOnly = false;
// Reset puts `installableOnly` back to its
// default (on) the operator hits Reset to
// escape a custom-filter state and expects the
// default view back, not an everything-
// including-planned view.
_installableOnly = true;
});
},
child: Text(l.storeFilterReset),
),
),
child: Text(l.storeFilterApply),
const Spacer(),
TextButton(
onPressed: () => Navigator.pop(context, null),
child: Text(l.buttonCancel),
),
const SizedBox(width: ChainSpace.sm),
FilledButton(
onPressed: () => Navigator.pop(
context,
_FilterOutcome(
status: _status,
source: _source,
installedOnly: _installedOnly,
installableOnly: _installableOnly,
),
),
child: Text(l.storeFilterApply),
),
],
),
],
);