feat(studio): inline flow editor — YAML highlighting + Run (v0.50.0)
Some checks failed
Security / Security check (push) Failing after 1s

New top-level destination "Editor" (Cmd+5) ships as Studio's
fifth surface. The editor reads + writes flow YAML directly
under ~/.fai/data/flows/ via dart:io — the hub picks up the
changes on the next listFlows / runSavedFlow call.

Layout: two-pane shell. Left (240 px) is the file list; right
flexes to the code pane and an optional results column when
Run produces output. Top toolbar exposes:
  * filename + dirty-mark
  * New flow (scaffolds from a debug.echo template)
  * Save (writes the active file to disk)
  * Run (saves first if dirty, calls
    HubService.runSavedFlow, surfaces typed FlowOutputs in
    a side panel)
  * Refresh

YAML highlighting via flutter_code_editor + the highlight
package's yaml language. Lightweight style map mapping the
five token classes that actually appear in flow YAML
(attr / string / number / comment / subst for the
${{ ... }} template syntax) to FaiTheme colors — keeps the
editor visually consistent with the rest of Studio.

New-flow naming uses a FilteringTextInputFormatter that
restricts the name to [a-z0-9_-]. A "name already exists"
SnackBar surfaces the conflict instead of silently
overwriting.

Bilingual strings shipped (en.arb + de.arb) for every
operator-facing string: toolbar buttons, dialogs, empty
states, file-exists error, run-output header.

New deps:
  * flutter_code_editor ^0.3.5
  * highlight (transitive — pinned as direct so the
    yaml-language import has its declared dependency).

Smoke-test (test/flow_editor_test.dart) pumps the page and
asserts the empty-state + toolbar render without throwing
on hosts that don't have ~/.fai/data/flows yet. The full
file-list + open-on-tap flow needs a writable HOME override
which dart:io's read-only Platform.environment doesn't allow
inside a test isolate — that path lives in the integration
suite as a follow-up.

Version bumps:
  * pubspec.yaml: 0.49.1 → 0.50.0
  * main.dart kStudioVersion: 0.42.0 → 0.50.0 (had drifted
    behind pubspec; brought back into sync as part of this
    bump)

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-30 01:10:05 +02:00
parent 570c38b238
commit bb793e64a9
15 changed files with 1291 additions and 5 deletions

View file

@ -3727,6 +3727,132 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Temporal'**
String get storeSourceTemporal;
/// No description provided for @navFlowEditor.
///
/// In en, this message translates to:
/// **'Editor'**
String get navFlowEditor;
/// No description provided for @flowEditorNew.
///
/// In en, this message translates to:
/// **'New flow'**
String get flowEditorNew;
/// No description provided for @flowEditorSave.
///
/// In en, this message translates to:
/// **'Save'**
String get flowEditorSave;
/// No description provided for @flowEditorRun.
///
/// In en, this message translates to:
/// **'Run'**
String get flowEditorRun;
/// No description provided for @flowEditorRefresh.
///
/// In en, this message translates to:
/// **'Refresh file list'**
String get flowEditorRefresh;
/// No description provided for @flowEditorRunOutput.
///
/// In en, this message translates to:
/// **'RUN OUTPUT'**
String get flowEditorRunOutput;
/// No description provided for @flowEditorEmptyTitle.
///
/// In en, this message translates to:
/// **'No flow open'**
String get flowEditorEmptyTitle;
/// No description provided for @flowEditorEmptyBody.
///
/// In en, this message translates to:
/// **'Pick one from the left, or click New flow to start a fresh one.'**
String get flowEditorEmptyBody;
/// No description provided for @flowEditorListEmptyTitle.
///
/// In en, this message translates to:
/// **'No saved flows'**
String get flowEditorListEmptyTitle;
/// No description provided for @flowEditorListEmptyBody.
///
/// In en, this message translates to:
/// **'Click New flow to scaffold one from a template.'**
String get flowEditorListEmptyBody;
/// No description provided for @flowEditorDiscardTitle.
///
/// In en, this message translates to:
/// **'Discard unsaved changes?'**
String get flowEditorDiscardTitle;
/// No description provided for @flowEditorDiscardBody.
///
/// In en, this message translates to:
/// **'Switching files will throw away the edits you haven\'t saved.'**
String get flowEditorDiscardBody;
/// No description provided for @flowEditorDiscardKeep.
///
/// In en, this message translates to:
/// **'Keep editing'**
String get flowEditorDiscardKeep;
/// No description provided for @flowEditorDiscardThrow.
///
/// In en, this message translates to:
/// **'Throw away'**
String get flowEditorDiscardThrow;
/// No description provided for @flowEditorNewDialogTitle.
///
/// In en, this message translates to:
/// **'New flow'**
String get flowEditorNewDialogTitle;
/// No description provided for @flowEditorNewDialogLabel.
///
/// In en, this message translates to:
/// **'Flow name'**
String get flowEditorNewDialogLabel;
/// No description provided for @flowEditorNewDialogHelper.
///
/// In en, this message translates to:
/// **'lowercase letters, digits, hyphen, underscore'**
String get flowEditorNewDialogHelper;
/// No description provided for @flowEditorNewDialogCancel.
///
/// In en, this message translates to:
/// **'Cancel'**
String get flowEditorNewDialogCancel;
/// No description provided for @flowEditorNewDialogCreate.
///
/// In en, this message translates to:
/// **'Create'**
String get flowEditorNewDialogCreate;
/// No description provided for @flowEditorNewTemplateComment.
///
/// In en, this message translates to:
/// **'Generated by Studio\'s Flow editor. Edit, save, run.'**
String flowEditorNewTemplateComment(String name);
/// No description provided for @flowEditorAlreadyExists.
///
/// In en, this message translates to:
/// **'A flow named \"{name}\" already exists.'**
String flowEditorAlreadyExists(String name);
}
class _AppLocalizationsDelegate