feat(editor): persistent diagnostic strip + run-block + copyable run errors

Three connected fixes that close the loop the analyzer started:

  - **Strip lives at page level, not text-tab**. Moved
    _DiagnosticStrip out of _textTab and into _tabbedBody
    below the TabBarView, so the same error list + quick-fix
    buttons are visible on Graph + Text + Run. The Graph-tab
    operator now sees both the pulsing node AND the message
    explaining what's broken, without flipping tabs. One strip,
    one source of truth.

  - **Run button disabled when there are analyzer errors**.
    FlowEditorController.analyzerErrorCount counts `IssueType.error`
    issues; the action strip's Run button + the RunTab's Start
    button both check it. When > 0:
      · button greys out + icon flips to Icons.block
      · tooltip names the count ('2 errors prevent the run')
      · the Run tab grows an inline red explanation next to
        the disabled Start so the operator isn't left guessing
    Warnings (orange) do NOT block — they're nudges. Operators
    who want to run a half-broken flow during dev can still
    silence the analyzer via the strip.

  - **Run failures are copyable everywhere**. New
    _CopyableErrorBox replaces both the per-run failure detail
    and the per-step failure suffix in the RunTab. Selectable
    monospace + explicit Copy button with 'Copied' feedback.
    Scrollbar-capped at 220 px so the box doesn't push outputs
    off-screen. Mirrors the operator-visible contract:
    'every error is copyable, always.'

Closes the operator-reported regression where a failed run
showed 'Lauf fehlgeschlagen' as plain Text, leaving the
operator unable to paste it into a bug report.

Bumped to 0.19.0.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-09 01:13:45 +02:00
parent efdfa7dd79
commit b6bb8741a9
4 changed files with 230 additions and 82 deletions

View file

@ -546,9 +546,11 @@ outputs:
tabs: _tabs,
saving: _controller.isSaving,
running: _controller.isRunning,
errorCount: _controller.analyzerErrorCount,
onAddStep: _controller.activeName != null ? _addStep : null,
onSave: _controller.activeName != null ? _save : null,
onRun: _controller.activeName != null
onRun: _controller.activeName != null &&
_controller.analyzerErrorCount == 0
? () => _tabs.animateTo(2)
: null,
),
@ -569,6 +571,15 @@ outputs:
],
),
),
// Persistent diagnostic strip lives below the tab bar
// so the same issue list is visible on Graph + Text +
// Run. The Graph tab no longer needs a separate
// per-node tooltip because the strip is right there with
// the message + quick-fix buttons.
_DiagnosticStrip(
controller: _controller.codeController,
onApplyFix: _applyQuickFix,
),
],
);
}
@ -714,41 +725,31 @@ outputs:
height: 1.45,
color: theme.colorScheme.onSurface,
);
// The bottom diagnostic strip now lives at the page level
// (one persistent strip across all three tabs) instead of
// being duplicated per-tab see _tabbedBody. That keeps the
// graph + run tabs informed too, not just text.
return CodeTheme(
data: CodeThemeData(styles: _yamlStyle(theme)),
child: Column(
children: [
Expanded(
child: CodeField(
controller: _controller.codeController,
textStyle: mono,
expands: true,
minLines: null,
maxLines: null,
gutterStyle: GutterStyle(
textStyle: mono.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
background: theme.colorScheme.surfaceContainer,
showLineNumbers: true,
// Disable the built-in error column entirely.
// The Icons.cancel pin is hard-positioned inside
// a 16-px slot, and its mouse-hover popup paints
// OVER the code area instead of beside it the
// overlap was the operator-visible glitch in
// editor 0.15.1. Issues remain surfaced via the
// wavy underline (FlowYamlCodeController) plus
// the bottom diagnostic strip below.
showErrors: false,
),
background: theme.colorScheme.surface,
),
child: CodeField(
controller: _controller.codeController,
textStyle: mono,
expands: true,
minLines: null,
maxLines: null,
gutterStyle: GutterStyle(
textStyle: mono.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
_DiagnosticStrip(
controller: _controller.codeController,
onApplyFix: _applyQuickFix,
),
],
background: theme.colorScheme.surfaceContainer,
showLineNumbers: true,
// Disable the built-in error column entirely its
// hard-positioned popup overlaps the code area.
// Issues are surfaced via the wavy underline + the
// page-level diagnostic strip.
showErrors: false,
),
background: theme.colorScheme.surface,
),
);
}
@ -939,6 +940,7 @@ class _TabActionStrip extends StatelessWidget {
final TabController tabs;
final bool saving;
final bool running;
final int errorCount;
final VoidCallback? onAddStep;
final VoidCallback? onSave;
final VoidCallback? onRun;
@ -947,6 +949,7 @@ class _TabActionStrip extends StatelessWidget {
required this.tabs,
required this.saving,
required this.running,
required this.errorCount,
required this.onAddStep,
required this.onSave,
required this.onRun,
@ -997,16 +1000,27 @@ class _TabActionStrip extends StatelessWidget {
label: Text(strings.save),
),
const SizedBox(width: FaiSpace.sm),
FilledButton.icon(
onPressed: onRun,
icon: running
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.play_arrow, size: 18),
label: Text(strings.run),
Tooltip(
message: errorCount > 0
? '$errorCount error${errorCount == 1 ? '' : 's'} '
'prevent the run — fix them first'
: '',
child: FilledButton.icon(
onPressed: onRun,
icon: running
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Icon(
errorCount > 0
? Icons.block
: Icons.play_arrow,
size: 18,
),
label: Text(strings.run),
),
),
],
const Spacer(),