/// State model for the pql sidebar panel. /// /// Manages schema cache, query execution, file listing, and /// decision/ticket views. All data comes through pql.* IPC verbs. library; import 'dart:async'; import 'package:clide/kernel/kernel.dart'; import 'package:flutter/foundation.dart'; enum PqlView { files, query } class PqlController extends ChangeNotifier { PqlController({required this.ipc}); final DaemonClient ipc; PqlView _view = PqlView.files; PqlView get view => _view; String? _error; String? get error => _error; bool _loading = false; bool get loading => _loading; List> _results = const []; List> get results => _results; Map _planStatus = const {}; Map get planStatus => _planStatus; void switchView(PqlView v) { if (_view == v) return; _view = v; _results = const []; _error = null; notifyListeners(); switch (v) { case PqlView.files: unawaited(loadFiles()); case PqlView.query: break; } } Future loadFiles({String? glob}) async { _loading = true; notifyListeners(); final r = await ipc.request('pql.files', args: { if (glob != null) 'glob': glob, 'limit': 200, }); _loading = false; if (!r.ok) { _error = r.error?.message; notifyListeners(); return; } _error = null; _results = _castList(r.data['files']); notifyListeners(); } Future runQuery(String dsl) async { if (dsl.trim().isEmpty) return; _loading = true; _error = null; notifyListeners(); final r = await ipc.request('pql.query', args: { 'query': dsl, 'limit': 200, }); _loading = false; if (!r.ok) { _error = r.error?.message; _results = const []; notifyListeners(); return; } _results = _castList(r.data['results']); notifyListeners(); } Future loadPlanStatus() async { final r = await ipc.request('pql.plan.status'); if (r.ok) { _planStatus = r.data; notifyListeners(); } } void clearError() { if (_error == null) return; _error = null; notifyListeners(); } static List> _castList(Object? raw) { if (raw is! List) return const []; return [for (final e in raw) (e as Map).cast()]; } }