feat(workspace): one global switcher anchor in the shell sidebar
The switcher used to be embedded per page (Flows/Runs/Audit/
Approvals) — invisible on the other five pages and sitting in a
different corner depending on the page (persona review 2026-08-27,
consensus finding). It now lives ONCE in the sidebar, above the
destinations: active project/area always visible, opens the same
menu everywhere, Cmd+P from anywhere. The shell listens to the
workspace, so the sidebar endpoint label can no longer lag a
sealed switch until the next health tick.
Also in this rebuild:
* Stopped sealed areas ask before starting ("Start area X?") —
a context switch must never boot a hub daemon as a click
side-effect; running areas keep switching with one click.
* The switcher tooltip told a wrong scope ("filters this view") —
it now says the choice applies everywhere and stamps new runs.
* The aggregated sealed row explains itself in place (names can
reveal client identities) and links to the Settings toggle
(Settings dialog gained an initialCategory jump).
* The active entry carries a checkmark in the menu.
* The Cmd+K palette knows projects and areas, ranked by recent
use; sealed names honour the privacy setting — while hidden,
the palette offers the guarded picker instead of the names.
* The runs empty state names the active project filter as the
cause ("No runs in project X" + show-all action) instead of
claiming the feature is off.
Tests updated to the anchor and made hermetic (scriptable
projects on the fake hub, sealed-area fake); new coverage for the
checkmark, the why-line, and the start confirmation.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
afe782e826
commit
64c2a77dc9
16 changed files with 988 additions and 324 deletions
|
|
@ -376,8 +376,6 @@ class _ApprovalsPageState extends State<ApprovalsPage> {
|
|||
),
|
||||
),
|
||||
actions: [
|
||||
const ChainWorkspaceSwitcher(),
|
||||
const SizedBox(width: ChainSpace.md),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline, size: 18),
|
||||
tooltip: AppLocalizations.of(context)!.helpTooltip,
|
||||
|
|
|
|||
|
|
@ -306,11 +306,9 @@ class _AuditPageState extends State<AuditPage> {
|
|||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.auditTitle),
|
||||
actions: [
|
||||
const ChainWorkspaceSwitcher(),
|
||||
const SizedBox(width: ChainSpace.md),
|
||||
// Narrow windows can't fit the inline chip row next to the
|
||||
// workspace switcher — collapse to a checkmark menu so the
|
||||
// app bar never overflows (responsive_test.dart pins this).
|
||||
// Narrow windows can't fit the inline chip row — collapse
|
||||
// to a checkmark menu so the app bar never overflows
|
||||
// (responsive_test.dart pins this).
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: ChainSpace.lg),
|
||||
child: MediaQuery.sizeOf(context).width < 900
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import '../data/store_caps.dart';
|
|||
import '../data/workspace.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../widgets/chain_install_confirm.dart';
|
||||
import '../widgets/chain_workspace_switcher.dart';
|
||||
|
||||
class FlowsPage extends StatefulWidget {
|
||||
/// Pre-load this flow when the editor first builds. Studio
|
||||
|
|
@ -220,11 +219,9 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
onInstallCapability: _onInstallCapability,
|
||||
onAddModuleSource: _onAddModuleSource,
|
||||
activeProject: Workspace.instance.activeSlug,
|
||||
// Same switcher as Audit/Approvals/Runs, hosted in the
|
||||
// editor's toolbar (the page's single header): it
|
||||
// filters the flow list and is the project a new flow
|
||||
// gets stamped with.
|
||||
toolbarTrailing: const ChainWorkspaceSwitcher(),
|
||||
// The workspace switcher lives ONCE in the shell sidebar
|
||||
// (global anchor) — the editor toolbar no longer hosts
|
||||
// its own copy.
|
||||
// Native file dialog for the Run tab's file inputs —
|
||||
// nobody should have to type an absolute path by hand.
|
||||
onPickFile: _pickFlowInputFile,
|
||||
|
|
|
|||
|
|
@ -254,6 +254,14 @@ class _RunsPageState extends State<RunsPage> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Display name of the filtered project ("General" localized).
|
||||
String _activeProjectLabel(AppLocalizations l) {
|
||||
final ws = Workspace.instance;
|
||||
final p = ws.active;
|
||||
if (p == null) return ws.activeSlug;
|
||||
return p.slug == 'general' ? l.workspaceDefaultProject : p.name;
|
||||
}
|
||||
|
||||
Future<void> _cancel(DetachedRun run) async {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
setState(() => _cancelling.add(run.id));
|
||||
|
|
@ -333,8 +341,6 @@ class _RunsPageState extends State<RunsPage> {
|
|||
appBar: AppBar(
|
||||
title: Text(l.runsTitle),
|
||||
actions: [
|
||||
const ChainWorkspaceSwitcher(),
|
||||
const SizedBox(width: ChainSpace.md),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline, size: 18),
|
||||
tooltip: l.helpTooltip,
|
||||
|
|
@ -361,22 +367,41 @@ class _RunsPageState extends State<RunsPage> {
|
|||
updating: _updatingHub,
|
||||
)
|
||||
: _runs.isEmpty
|
||||
? ChainEmptyState(
|
||||
icon: Icons.rocket_launch_outlined,
|
||||
title: l.runsEmptyTitle,
|
||||
// The hub reports whether the operator enabled the
|
||||
// feature — never claim "switched off" while it is on
|
||||
// and there simply are no runs yet (usertest finding).
|
||||
hint: _detachedEnabled ? l.runsEmptyEnabledHint : l.runsEmptyHint,
|
||||
// The guide carries the plain-language explanation plus
|
||||
// the exact operator steps (config snippet) — a click
|
||||
// target instead of a raw config key in the hint.
|
||||
action: OutlinedButton.icon(
|
||||
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
||||
label: Text(l.runsEmptyGuideButton),
|
||||
onPressed: () => showFaiDoc(context, 'runs'),
|
||||
),
|
||||
)
|
||||
// With an active project filter the honest cause is the
|
||||
// filter, not the feature — name the project and offer the
|
||||
// way out instead of the enable-the-feature guide
|
||||
// (persona finding: the old hint claimed a wrong cause).
|
||||
? (_detachedEnabled && !Workspace.instance.isAll
|
||||
? ChainEmptyState(
|
||||
icon: Icons.rocket_launch_outlined,
|
||||
title: l.runsEmptyFilteredTitle(_activeProjectLabel(l)),
|
||||
hint: l.runsEmptyFilteredHint,
|
||||
action: OutlinedButton.icon(
|
||||
icon: const Icon(Icons.grid_view_outlined, size: 16),
|
||||
label: Text(l.runsEmptyShowAll),
|
||||
onPressed: () => Workspace.instance.setActive(''),
|
||||
),
|
||||
)
|
||||
: ChainEmptyState(
|
||||
icon: Icons.rocket_launch_outlined,
|
||||
title: l.runsEmptyTitle,
|
||||
// The hub reports whether the operator enabled the
|
||||
// feature — never claim "switched off" while it is
|
||||
// on and there simply are no runs yet (usertest
|
||||
// finding).
|
||||
hint: _detachedEnabled
|
||||
? l.runsEmptyEnabledHint
|
||||
: l.runsEmptyHint,
|
||||
// The guide carries the plain-language explanation
|
||||
// plus the exact operator steps (config snippet) —
|
||||
// a click target instead of a raw config key in
|
||||
// the hint.
|
||||
action: OutlinedButton.icon(
|
||||
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
||||
label: Text(l.runsEmptyGuideButton),
|
||||
onPressed: () => showFaiDoc(context, 'runs'),
|
||||
),
|
||||
))
|
||||
: ListView.separated(
|
||||
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||
itemCount: _runs.length,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue