feat(editor): graph-pulse on errors + store-aware install / add-source
Three connected improvements to the analyzer-driven diagnostics:
- **Pulsing halo on broken graph nodes**. Every step / pseudo-
node (inputs / outputs) carrying an analyzer issue now
breathes a red (error) or amber (warning) halo on the graph
tab — operator sees the problem on the canvas without
flipping to text. Honours prefers-reduced-motion: reduce-
motion users get a static halo at the same intensity. The
canvas reads severity via a new `stepIssueSeverity` map on
FlowEditorController; pseudo-nodes use `__inputs__` /
`__outputs__` sentinel ids.
- **Store-aware install button**. The analyzer now takes a
second closure, `storeCapabilities`, listing what the public
store can install. Unknown-capability issues only carry the
"Install …" quick-fix when the bare cap is in that list;
otherwise the issue carries an "Add source for …" fix
instead. Resolves the asymmetry the operator reported: the
Store didn't show `htw.digiscout/onet.lookup` but the editor
happily offered to install it (and would have failed). The
install path no longer lies about itself.
- **Did-you-mean suggestion**. When the unknown cap is within
edit-distance two of an installed or store cap (different
spelling — distance-0 stays hidden because that's an install
case, not a typo), the analyzer emits a `ReplaceLineValueFix`
suggesting the closest match. Preserves the version
constraint by reusing the installed spec when present
(e.g. `text.echi@^0.1` → `text.echo@^0.1`).
New public surface:
- `AddModuleSourceFix` + `AddModuleSourceCallback`
- `FlowEditorPage.storeCapabilities` + `onAddModuleSource`
- `FlowAnalyzer.stepSeverity` + the `kInputsNodeId` /
`kOutputsNodeId` sentinels
- `FlowIssueSeverity` enum + `FlowNode.issueSeverity`
Tests:
- Install fix only when in store
- AddModuleSourceFix as fallback when not in store
- Did-you-mean replaces install when a near miss exists
- stepSeverity populated for both step ids and pseudo-nodes
All 36 editor tests green. Bumped to 0.18.0.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
f43c1ac6cf
commit
efdfa7dd79
10 changed files with 497 additions and 45 deletions
|
|
@ -105,10 +105,11 @@ steps:
|
|||
expect(r.issues, isEmpty);
|
||||
});
|
||||
|
||||
test('attaches InstallCapabilityFix to unknown-capability issue',
|
||||
test('attaches InstallCapabilityFix when the capability is in the store',
|
||||
() async {
|
||||
final a = FlowAnalyzer(
|
||||
availableCapabilities: () => const ['debug.echo'],
|
||||
storeCapabilities: () => const ['text.classify'],
|
||||
);
|
||||
final r = await a.analyze(_wrap('''
|
||||
name: x
|
||||
|
|
@ -124,6 +125,69 @@ steps:
|
|||
expect((fix as InstallCapabilityFix).capability, 'text.classify@^1');
|
||||
});
|
||||
|
||||
test('falls back to AddModuleSourceFix when unknown and not in store',
|
||||
() async {
|
||||
final a = FlowAnalyzer(
|
||||
availableCapabilities: () => const ['debug.echo'],
|
||||
storeCapabilities: () => const ['text.classify'],
|
||||
);
|
||||
final r = await a.analyze(_wrap('''
|
||||
name: x
|
||||
steps:
|
||||
- id: c
|
||||
use: htw.private/secret@^0.1
|
||||
'''));
|
||||
expect(r.issues, hasLength(1));
|
||||
final fixes = a.fixesFor(r.issues.first);
|
||||
expect(fixes, hasLength(1));
|
||||
expect(fixes.first, isA<AddModuleSourceFix>());
|
||||
expect(r.issues.first.message, contains('Not in the store'));
|
||||
});
|
||||
|
||||
test('did-you-mean wins over install/add-source when a near miss exists',
|
||||
() async {
|
||||
// Installed bare `text.echo`; user typed `text.echi` (edit-distance 1).
|
||||
// The fix list should carry the Replace-as-primary; no install or
|
||||
// add-source needed because the analyzer already found the answer.
|
||||
final a = FlowAnalyzer(
|
||||
availableCapabilities: () => const ['text.echo@^0.1'],
|
||||
storeCapabilities: () => const [],
|
||||
);
|
||||
final r = await a.analyze(_wrap('''
|
||||
name: x
|
||||
steps:
|
||||
- id: t
|
||||
use: text.echi@^0.1
|
||||
'''));
|
||||
expect(r.issues, hasLength(1));
|
||||
final fixes = a.fixesFor(r.issues.first);
|
||||
expect(fixes.first, isA<ReplaceLineValueFix>());
|
||||
// Replacement preserves the version constraint by reusing the
|
||||
// installed entry's `@^0.1`.
|
||||
expect(
|
||||
(fixes.first as ReplaceLineValueFix).replacement,
|
||||
'text.echo@^0.1',
|
||||
);
|
||||
});
|
||||
|
||||
test('records worst severity per step for canvas highlighting',
|
||||
() async {
|
||||
final a = FlowAnalyzer(
|
||||
availableCapabilities: () => const ['debug.echo'],
|
||||
storeCapabilities: () => const [],
|
||||
);
|
||||
await a.analyze(_wrap('''
|
||||
name: x
|
||||
inputs:
|
||||
pdf: byes
|
||||
steps:
|
||||
- id: bad
|
||||
use: nope@^1
|
||||
'''));
|
||||
expect(a.stepSeverity['bad'], IssueType.error);
|
||||
expect(a.stepSeverity[kInputsNodeId], IssueType.warning);
|
||||
});
|
||||
|
||||
test('suggests closest valid type for a misspelled value', () async {
|
||||
final a = FlowAnalyzer(availableCapabilities: () => const []);
|
||||
final r = await a.analyze(_wrap('''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue