code-quality batch (T-112)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m7s
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m7s
Seven small consultant findings, one commit:
1. TreeSitterLib stores last dlopen error + path in static fields
instead of swallowing them. Callers that observe a null instance
can now read the diagnostic.
2. Drop the Cmsghdr alias in libc.dart — back-compat shim with no
callers; CLAUDE.md forbids those in a solo repo.
3. Drop EditorController._events field + the unused_field
suppression. The constructor still subscribes via `events.on<...>`
for _eventSub; the field itself was speculative retention.
4. Replace inline hex / errno literals in native_pty.dart with
PosixErrno.{eintr,ebadf,epipe} and new libc.{pollin, pollAnyErr,
sighup, sigkill, sigwinch}. PosixErrno gains eintr.
5. ExtensionManager records activate/deactivate exceptions in a
`_failed` map exposed as `failedExtensions` + `didFail(id)`.
Listeners are notified on entry/exit; cleared on a clean
activate. UI surfaces the degraded state instead of pretending
everything is fine.
6. file_tree_view imports FileEntry via the clide.dart barrel
instead of `package:clide/src/files/listing.dart` directly — the
leak the consultant flagged (barrel already re-exports it).
7. test_app branch in main.dart wrapped in `if (kDebugMode)` so
release tree-shaker elides the test harness from shipping
binaries. Source import stays; tree-shake handles the rest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,9 @@ class ExtensionManager extends ChangeNotifier {
|
||||
|
||||
final Map<String, ClideExtension> _known = {};
|
||||
final Set<String> _activated = {};
|
||||
// Extensions whose activate() / deactivate() threw — exposed so the
|
||||
// UI can show a "degraded" status next to them.
|
||||
final Map<String, Object> _failed = {};
|
||||
|
||||
void register(ClideExtension ext) {
|
||||
if (_known.containsKey(ext.id)) {
|
||||
@@ -94,6 +97,12 @@ class ExtensionManager extends ChangeNotifier {
|
||||
Iterable<ClideExtension> get all => _known.values;
|
||||
bool isActivated(String id) => _activated.contains(id);
|
||||
|
||||
/// Map of extension id → most recent activate/deactivate error.
|
||||
/// Cleared for an extension when it activates cleanly. UI surfaces
|
||||
/// read this for the "degraded" indicator.
|
||||
Map<String, Object> get failedExtensions => Map.unmodifiable(_failed);
|
||||
bool didFail(String id) => _failed.containsKey(id);
|
||||
|
||||
bool isEnabled(String id) {
|
||||
final v = settings.get<bool>('app.extensions.$id.enabled');
|
||||
return v ?? true;
|
||||
@@ -138,11 +147,14 @@ class ExtensionManager extends ChangeNotifier {
|
||||
_applyContribution(c);
|
||||
}
|
||||
_activated.add(id);
|
||||
_failed.remove(id);
|
||||
events.emit(ExtensionActivated(id: id));
|
||||
notifyListeners();
|
||||
log.info('extensions', 'activated $id');
|
||||
} catch (e, st) {
|
||||
_failed[id] = e;
|
||||
log.error('extensions', 'activate failed for $id', error: e, stackTrace: st);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +172,9 @@ class ExtensionManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
log.info('extensions', 'deactivated $id');
|
||||
} catch (e, st) {
|
||||
_failed[id] = e;
|
||||
log.error('extensions', 'deactivate failed for $id', error: e, stackTrace: st);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,15 @@ class TreeSitterLib {
|
||||
@visibleForTesting
|
||||
static TreeSitterLib fromDynamicLibrary(DynamicLibrary lib) => TreeSitterLib._(lib);
|
||||
|
||||
/// Most recent error raised while trying to dlopen the tree-sitter
|
||||
/// library, paired with the path that was attempted. Cleared on a
|
||||
/// successful open. Null means "no attempt failed yet."
|
||||
///
|
||||
/// Callers that observe a null `instance` after `init()` should read
|
||||
/// this for the diagnostic instead of guessing.
|
||||
static Object? lastOpenError;
|
||||
static String? lastOpenErrorPath;
|
||||
|
||||
static DynamicLibrary? _openLibrary() {
|
||||
final libName = Platform.isLinux
|
||||
? 'libtree-sitter.so'
|
||||
@@ -251,12 +260,22 @@ class TreeSitterLib {
|
||||
: Platform.isWindows
|
||||
? 'tree-sitter.dll'
|
||||
: null;
|
||||
if (libName == null) return null;
|
||||
if (libName == null) {
|
||||
lastOpenError = 'unsupported platform ${Platform.operatingSystem}';
|
||||
lastOpenErrorPath = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try standard dlopen path first (works when lib is in bundle/lib/).
|
||||
try {
|
||||
return DynamicLibrary.open(libName);
|
||||
} catch (_) {}
|
||||
final lib = DynamicLibrary.open(libName);
|
||||
lastOpenError = null;
|
||||
lastOpenErrorPath = null;
|
||||
return lib;
|
||||
} catch (e) {
|
||||
lastOpenError = e;
|
||||
lastOpenErrorPath = libName;
|
||||
}
|
||||
|
||||
// Try next to executable.
|
||||
final exe = File(Platform.resolvedExecutable).parent.path;
|
||||
@@ -264,8 +283,14 @@ class TreeSitterLib {
|
||||
final path = '$dir/$libName';
|
||||
if (File(path).existsSync()) {
|
||||
try {
|
||||
return DynamicLibrary.open(path);
|
||||
} catch (_) {}
|
||||
final lib = DynamicLibrary.open(path);
|
||||
lastOpenError = null;
|
||||
lastOpenErrorPath = null;
|
||||
return lib;
|
||||
} catch (e) {
|
||||
lastOpenError = e;
|
||||
lastOpenErrorPath = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user