chain-studio-flow-editor/lib/src/quick_fix.dart
flemming-it ab97e5e834 feat: honest install badge — classify store resolvability before the click
Split each flow's missing capabilities into store-resolvable ones
(amber chip + Install link, which now installs only those) and a
quiet 'not in store' chip whose tooltip explains the three recovery
paths in place: local install via chain install --link, adding the
providing store, or configuring the MCP/n8n integration. The
analyzer's not-in-store message names the same three paths (EN+DE).
Previously the list's Install action covered every missing
capability and could end in the hub's 'no store entry' error.

Also replace a private-looking capability example name in a doc
comment and test with a neutral placeholder. (0.25.0)

Signed-off-by: flemming-it <sf@flemming.it>
2026-07-22 13:42:11 +02:00

141 lines
4.9 KiB
Dart

// Quick-fix model for the flow editor's diagnostics.
//
// Each analyzer issue can carry one or more `QuickFix` records
// describing a one-click remediation. The editor renders these
// as action buttons on the hover tooltip and inside the bottom
// diagnostic strip. Two categories exist today:
//
// * `ReplaceLineValueFix` — purely textual; the editor
// applies it directly by mutating the controller's
// buffer.
// * `InstallCapabilityFix` — needs the host (Studio) to talk
// to the Hub. The editor invokes the host-provided
// callback and reanalyzes once it returns.
//
// New fix kinds can be added by extending the sealed base; both
// surfaces (tooltip + strip) switch on the runtime type, so
// adding a kind also means adding a case there.
import 'dart:ui' show Offset;
import 'package:flutter/foundation.dart';
@immutable
sealed class QuickFix {
/// One-line button label rendered in the UI (verb-first,
/// imperative). Kept short so the strip layout doesn't reflow
/// when multiple fixes are present.
final String label;
const QuickFix(this.label);
}
/// Replace the trailing value of `[line]` with [replacement].
/// Used by the unknown-type warning to offer the closest
/// matching valid type token (e.g. `byes` → `bytes`).
@immutable
class ReplaceLineValueFix extends QuickFix {
/// Zero-based line index inside the document.
final int line;
/// The token to substitute for the existing value (the
/// substring after the colon, with surrounding whitespace
/// preserved by the applier).
final String replacement;
const ReplaceLineValueFix({
required this.line,
required this.replacement,
required String label,
}) : super(label);
}
/// Ask the host (Studio) to install the named capability via
/// the Hub's store-backed install. The editor delegates to the
/// [InstallCapabilityCallback] passed into `FlowEditorPage`
/// and reanalyzes the document once the host returns.
@immutable
class InstallCapabilityFix extends QuickFix {
/// The capability spec the operator wrote (with or without an
/// `@version` tail). The host is responsible for parsing the
/// version part if it matters; the editor never strips it.
final String capability;
const InstallCapabilityFix({
required this.capability,
required String label,
}) : super(label);
}
/// Ask the host to register a new module source for an unknown
/// capability — used when the capability isn't in the public
/// store. The host's handler typically prompts the operator
/// for a local path (`chain install --link`) or a URL
/// (`chain install <url>`), then installs and reanalyzes.
///
/// This is the recovery path for private modules: the public
/// store doesn't know about `acme.internal/directory-lookup`,
/// but the operator can point the hub at the local clone.
@immutable
class AddModuleSourceFix extends QuickFix {
/// The capability the operator wrote — the host uses it to
/// pre-fill its prompt ("Where can `<capability>` be
/// installed from?").
final String capability;
const AddModuleSourceFix({
required this.capability,
required String label,
}) : super(label);
}
/// Host-side install handler signature. Returns the new
/// installed-capability list after the install completes (used
/// by the editor to refresh its analyzer without round-tripping
/// through the parent widget tree). Returns `null` on failure;
/// in that case the editor keeps the prior list and the issue
/// stays visible.
typedef InstallCapabilityCallback =
Future<List<String>?> Function(String capability);
/// Host-side "add module source" handler. Receives the bare
/// capability spec the operator typed; expected to prompt the
/// operator for a local path / URL and then install it.
/// Same return contract as [InstallCapabilityCallback].
typedef AddModuleSourceCallback =
Future<List<String>?> Function(String capability);
/// Hover-tooltip request — the controller emits one of these
/// via a ValueNotifier when the pointer enters an issue range,
/// and clears it when the pointer leaves both the range and the
/// tooltip. The editor host listens and renders an overlay.
@immutable
class IssueHoverRequest {
/// Zero-based line of the issue the pointer is over.
final int line;
/// Human-readable issue message (lifted from
/// `Issue.message`).
final String message;
/// Issue severity. Drives the dot colour in the tooltip.
final IssueHoverSeverity severity;
/// Pre-computed quick fixes the tooltip should render as
/// action buttons. Empty when the editor has nothing
/// actionable to offer.
final List<QuickFix> fixes;
/// Global cursor position at hover time — the host overlay
/// uses it to anchor the tooltip near the pointer.
final Offset globalPosition;
const IssueHoverRequest({
required this.line,
required this.message,
required this.severity,
required this.fixes,
required this.globalPosition,
});
}
enum IssueHoverSeverity { error, warning, info }