Return `null` from `RootInfo.package` for non-`package:` root libraries (#10033)

Avoid returning invalid package data from service manager.

This is just a simple fix where we return null of a library URI is not a `package:` URI.

Fixes https://github.com/flutter/devtools/issues/6734
diff --git a/packages/devtools_app/lib/src/screens/memory/framework/memory_controller.dart b/packages/devtools_app/lib/src/screens/memory/framework/memory_controller.dart
index 62c0bb9..99eb263 100644
--- a/packages/devtools_app/lib/src/screens/memory/framework/memory_controller.dart
+++ b/packages/devtools_app/lib/src/screens/memory/framework/memory_controller.dart
@@ -21,6 +21,7 @@
 import '../panes/diff/controller/diff_pane_controller.dart';
 import '../panes/profile/profile_pane_controller.dart';
 import '../panes/tracing/tracing_pane_controller.dart';
+import '../shared/primitives/memory_utils.dart';
 import 'offline_data/offline_data.dart';
 
 /// Screen controller for the Memory screen.
@@ -129,7 +130,7 @@
     chart = MemoryChartPaneController(data: offlineData?.chart ?? ChartData());
 
     final rootPackage = isConnected
-        ? serviceConnection.serviceManager.rootInfoNow().package!
+        ? serviceConnection.serviceManager.rootInfoNow().rootPackagePrefix!
         : null;
 
     diff =
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
index cfc008b..c46a531 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
@@ -12,6 +12,7 @@
 import '../../../../../shared/primitives/utils.dart';
 import '../../../../../shared/table/table.dart';
 import '../../../../../shared/table/table_data.dart';
+import '../../../shared/primitives/memory_utils.dart';
 import '../../../shared/primitives/simple_elements.dart';
 import '../../../shared/widgets/class_filter.dart';
 import '../../../shared/widgets/shared_memory_widgets.dart';
@@ -53,7 +54,9 @@
       theClass: data.className,
       showCopyButton: isRowSelected,
       copyGaItem: gac.MemoryEvents.diffClassDiffCopy.name,
-      rootPackage: serviceConnection.serviceManager.rootInfoNow().package,
+      rootPackage: serviceConnection.serviceManager
+          .rootInfoNow()
+          .rootPackagePrefix,
     );
   }
 
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
index 460ebf9..3e5b022 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
@@ -12,6 +12,7 @@
 import '../../../../../shared/primitives/utils.dart';
 import '../../../../../shared/table/table.dart';
 import '../../../../../shared/table/table_data.dart';
+import '../../../shared/primitives/memory_utils.dart';
 import '../../../shared/primitives/simple_elements.dart';
 import '../../../shared/widgets/class_filter.dart';
 import '../../../shared/widgets/shared_memory_widgets.dart';
@@ -50,7 +51,9 @@
       theClass: data.className,
       showCopyButton: isRowSelected,
       copyGaItem: gac.MemoryEvents.diffClassSingleCopy.name,
-      rootPackage: serviceConnection.serviceManager.rootInfoNow().package,
+      rootPackage: serviceConnection.serviceManager
+          .rootInfoNow()
+          .rootPackagePrefix,
     );
   }
 
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart b/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
index 0465b9f..fd274c0 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
@@ -18,6 +18,7 @@
 import '../../../../shared/table/table_data.dart';
 import '../../../../shared/ui/common_widgets.dart';
 import '../../shared/heap/class_filter.dart';
+import '../../shared/primitives/memory_utils.dart';
 import '../../shared/primitives/simple_elements.dart';
 import '../../shared/widgets/class_filter.dart';
 import '../../shared/widgets/shared_memory_widgets.dart';
@@ -146,7 +147,9 @@
       theClass: data.heapClass,
       showCopyButton: isRowSelected,
       copyGaItem: gac.MemoryEvents.diffClassSingleCopy.name,
-      rootPackage: serviceConnection.serviceManager.rootInfoNow().package,
+      rootPackage: serviceConnection.serviceManager
+          .rootInfoNow()
+          .rootPackagePrefix,
     );
   }
 
diff --git a/packages/devtools_app/lib/src/screens/memory/shared/heap/sampler.dart b/packages/devtools_app/lib/src/screens/memory/shared/heap/sampler.dart
index 36b72c1..f322719 100644
--- a/packages/devtools_app/lib/src/screens/memory/shared/heap/sampler.dart
+++ b/packages/devtools_app/lib/src/screens/memory/shared/heap/sampler.dart
@@ -13,6 +13,7 @@
 import '../../../../shared/memory/heap_data.dart';
 import '../../../../shared/memory/heap_object.dart';
 import '../../../../shared/utils/vm_utils.dart';
+import '../primitives/memory_utils.dart';
 
 class _HeapObjects {
   _HeapObjects(this.objects, this.heap);
@@ -106,7 +107,7 @@
 
   bool get isEvalEnabled =>
       heapClass.classType(
-        serviceConnection.serviceManager.rootInfoNow().package,
+        serviceConnection.serviceManager.rootInfoNow().rootPackagePrefix,
       ) !=
       ClassType.runtime;
 
diff --git a/packages/devtools_app/lib/src/screens/memory/shared/primitives/memory_utils.dart b/packages/devtools_app/lib/src/screens/memory/shared/primitives/memory_utils.dart
index b14327f..f3ebf7c 100644
--- a/packages/devtools_app/lib/src/screens/memory/shared/primitives/memory_utils.dart
+++ b/packages/devtools_app/lib/src/screens/memory/shared/primitives/memory_utils.dart
@@ -2,10 +2,43 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
 
+/// @docImport '../../../../shared/memory/class_name.dart';
+library;
+
+import 'package:devtools_app_shared/service.dart' show RootInfo;
 import 'package:vm_service/vm_service.dart';
 
 import '../../../../shared/globals.dart';
 
+/// Memory screen specific information derived from the [RootInfo] of the
+/// connected app.
+extension RootInfoMemoryExtension on RootInfo {
+  /// The prefix of the URIs of the libraries that belong to the root package of
+  /// the connected app, or `null` if the root library is unknown.
+  ///
+  /// The Memory screen passes this prefix as `rootPackage` to
+  /// [HeapClassName.classType], which classifies a class as
+  /// [ClassType.rootPackage] (a "project" class) if the URI of its library
+  /// starts with the prefix.
+  ///
+  /// For the root library `package:my_app/main.dart`, the prefix is
+  /// `package:my_app`.
+  ///
+  /// Unlike [RootInfo.package], the prefix is not `null` when the root library
+  /// is not a `package:` URI. For example, the root library of a Dart CLI app
+  /// started with `dart run` is a `file:` URI like `file:///app/bin/main.dart`,
+  /// and the prefix is `file:`. Without a prefix, the classes defined in the
+  /// files of such an app would be classified as [ClassType.runtime], which
+  /// disables evaluating them in the live app.
+  String? get rootPackagePrefix {
+    final library = this.library;
+    if (library == null) return null;
+
+    final slashIndex = library.indexOf('/');
+    return slashIndex == -1 ? library : library.substring(0, slashIndex);
+  }
+}
+
 Future<HeapSnapshotGraph?> snapshotMemoryInSelectedIsolate() async {
   final isolate =
       serviceConnection.serviceManager.isolateManager.selectedIsolate.value;
diff --git a/packages/devtools_app/test/screens/memory/framework/memory_controller_test.dart b/packages/devtools_app/test/screens/memory/framework/memory_controller_test.dart
index e0282d6..528cbdb 100644
--- a/packages/devtools_app/test/screens/memory/framework/memory_controller_test.dart
+++ b/packages/devtools_app/test/screens/memory/framework/memory_controller_test.dart
@@ -7,6 +7,7 @@
 
 import 'package:devtools_app/devtools_app.dart';
 import 'package:devtools_app/src/screens/memory/shared/heap/class_filter.dart';
+import 'package:devtools_app_shared/service.dart' show RootInfo;
 import 'package:devtools_app_shared/utils.dart';
 import 'package:devtools_test/devtools_test.dart';
 import 'package:devtools_test/helpers.dart';
@@ -177,4 +178,26 @@
       expect(scene.controller.diff.core.snapshots.value.length, 1);
     });
   });
+
+  group('root package', () {
+    for (final (rootLibrary, expectedRootPackage) in const [
+      ('package:my_app/main.dart', 'package:my_app'),
+      // The root library of a Dart CLI app started with `dart run` is a `file:`
+      // URI, not a `package:` URI.
+      ('file:///app/bin/main.dart', 'file:'),
+    ]) {
+      testWidgets(
+        'is $expectedRootPackage when the root library is $rootLibrary',
+        (WidgetTester tester) async {
+          scene = MemoryDefaultScene();
+          await scene.setUp(
+            classList: classList,
+            rootInfo: RootInfo(rootLibrary),
+          );
+
+          expect(scene.controller.trace!.rootPackage, expectedRootPackage);
+        },
+      );
+    }
+  });
 }
diff --git a/packages/devtools_app/test/screens/memory/shared/primitives/memory_utils_test.dart b/packages/devtools_app/test/screens/memory/shared/primitives/memory_utils_test.dart
new file mode 100644
index 0000000..b6dd9f0
--- /dev/null
+++ b/packages/devtools_app/test/screens/memory/shared/primitives/memory_utils_test.dart
@@ -0,0 +1,58 @@
+// Copyright 2026 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import 'package:devtools_app/src/screens/memory/shared/primitives/memory_utils.dart';
+import 'package:devtools_app/src/shared/memory/class_name.dart';
+import 'package:devtools_app_shared/service.dart' show RootInfo;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('RootInfoMemoryExtension.rootPackagePrefix', () {
+    for (final (rootLibrary, expectedPrefix) in const [
+      ('package:my_app/main.dart', 'package:my_app'),
+      ('package:my_app/src/nested/file.dart', 'package:my_app'),
+      ('package:my_app', 'package:my_app'),
+      // The root library of a Dart CLI app started with `dart run`.
+      ('file:///app/bin/main.dart', 'file:'),
+      ('org-dartlang-app:///lib/main.dart', 'org-dartlang-app:'),
+      (null, null),
+    ]) {
+      test('is $expectedPrefix for $rootLibrary', () {
+        expect(RootInfo(rootLibrary).rootPackagePrefix, expectedPrefix);
+      });
+    }
+
+    test('classifies classes of a file: root library as project classes', () {
+      const rootLibrary = 'file:///app/bin/main.dart';
+      final heapClass = HeapClassName(
+        library: rootLibrary,
+        className: 'MyClass',
+      );
+
+      expect(
+        heapClass.classType(RootInfo(rootLibrary).rootPackagePrefix),
+        ClassType.rootPackage,
+      );
+    });
+
+    test('keeps classes of other libraries out of the project', () {
+      final rootPrefix = RootInfo('package:my_app/main.dart').rootPackagePrefix;
+
+      expect(
+        HeapClassName(
+          library: 'package:my_app/src/model.dart',
+          className: 'Model',
+        ).classType(rootPrefix),
+        ClassType.rootPackage,
+      );
+      expect(
+        HeapClassName(
+          library: 'package:other_package/other.dart',
+          className: 'Other',
+        ).classType(rootPrefix),
+        ClassType.dependency,
+      );
+    });
+  });
+}
diff --git a/packages/devtools_app/test/test_infra/scenes/memory/default.dart b/packages/devtools_app/test/test_infra/scenes/memory/default.dart
index 58cf958..95a0246 100644
--- a/packages/devtools_app/test/test_infra/scenes/memory/default.dart
+++ b/packages/devtools_app/test/test_infra/scenes/memory/default.dart
@@ -7,6 +7,7 @@
 import 'package:devtools_app/src/screens/memory/panes/diff/controller/diff_pane_controller.dart';
 import 'package:devtools_app/src/screens/memory/panes/profile/profile_pane_controller.dart';
 import 'package:devtools_app/src/screens/memory/shared/heap/class_filter.dart';
+import 'package:devtools_app_shared/service.dart' show RootInfo;
 import 'package:devtools_app_shared/ui.dart';
 import 'package:devtools_app_shared/utils.dart';
 import 'package:devtools_shared/devtools_shared.dart';
@@ -97,9 +98,11 @@
   ///
   /// [classList] will be returned by VmService.getClassList.
   /// [heapProviders] will be used to for heap snapshotting.
+  /// [rootInfo] will be returned by ServiceManager.rootInfoNow.
   Future<void> setUp({
     ClassList? classList,
     List<HeapProvider>? heapProviders,
+    RootInfo? rootInfo,
   }) async {
     heapProviders = heapProviders ?? MemoryDefaultSceneHeaps.all;
 
@@ -129,6 +132,7 @@
         allocationData: allocationJson,
         classList: classList,
       ),
+      rootInfo: rootInfo,
     );
     final app = fakeServiceConnection.serviceManager.connectedApp!;
     mockConnectedApp(app, isProfileBuild: true);
diff --git a/packages/devtools_app_shared/CHANGELOG.md b/packages/devtools_app_shared/CHANGELOG.md
index 4965452..683bc2b 100644
--- a/packages/devtools_app_shared/CHANGELOG.md
+++ b/packages/devtools_app_shared/CHANGELOG.md
@@ -10,6 +10,9 @@
 * Safely handle RPC errors and unexpected exceptions when calling service extensions in `ServiceExtensionManager`.
 * The minimum Dart SDK version is bumped to 3.11.0.
 * The minimum Flutter SDK version is bumped to 3.41.0.
+* Fix `RootInfo.package` to be `null` when the root library is not a `package:` URI,
+  such as the `file:` URI of a Dart CLI app started with `dart run`. Previously it
+  was the scheme of the URI, such as `file:`, which is not a package.
 * Updates `devtools_shared` constraint to `^14.0.1`.
 
 ## 0.5.1
diff --git a/packages/devtools_app_shared/lib/src/service/isolate_state.dart b/packages/devtools_app_shared/lib/src/service/isolate_state.dart
index d08e05e..4f7a107 100644
--- a/packages/devtools_app_shared/lib/src/service/isolate_state.dart
+++ b/packages/devtools_app_shared/lib/src/service/isolate_state.dart
@@ -69,14 +69,29 @@
   }
 }
 
+/// Information about the root library of an isolate of the connected app.
+///
+/// The root library is the library that contains the `main` function of the
+/// isolate. See also [Isolate.rootLib].
 class RootInfo {
   RootInfo(this.library) : package = _libraryToPackage(library);
 
+  /// The URI of the root library, or `null` if it is unknown.
+  ///
+  /// This is not necessarily a `package:` URI. For example, the root library of
+  /// a Dart CLI app started with `dart run` is a `file:` URI like
+  /// `file:///app/bin/main.dart`.
   final String? library;
+
+  /// The package that contains the root library, in the form `package:name`, or
+  /// `null` if it is unknown.
+  ///
+  /// This is `null` if [library] is `null` or is not a `package:` URI (for
+  /// example, a `file:` URI), since the name of the package is not known then.
   final String? package;
 
   static String? _libraryToPackage(String? library) {
-    if (library == null) return null;
+    if (library == null || !library.startsWith('package:')) return null;
     final slashIndex = library.indexOf('/');
     if (slashIndex == -1) return library;
     return library.substring(0, slashIndex);
diff --git a/packages/devtools_app_shared/test/service/isolate_state_test.dart b/packages/devtools_app_shared/test/service/isolate_state_test.dart
new file mode 100644
index 0000000..1fcbe63
--- /dev/null
+++ b/packages/devtools_app_shared/test/service/isolate_state_test.dart
@@ -0,0 +1,45 @@
+// Copyright 2026 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import 'package:devtools_app_shared/src/service/isolate_state.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('RootInfo', () {
+    test('handles package URIs', () {
+      final info = RootInfo('package:my_package/main.dart');
+      expect(info.library, 'package:my_package/main.dart');
+      expect(info.package, 'package:my_package');
+    });
+
+    test('handles package URIs without path', () {
+      final info = RootInfo('package:my_package');
+      expect(info.library, 'package:my_package');
+      expect(info.package, 'package:my_package');
+    });
+
+    test('handles null library', () {
+      final info = RootInfo(null);
+      expect(info.library, isNull);
+      expect(info.package, isNull);
+    });
+
+    test('handles file URIs (non-package)', () {
+      final info = RootInfo(
+        'file:///Users/viktor/Projects/tests/gui_1/gui_1_server/bin/main.dart',
+      );
+      expect(
+        info.library,
+        'file:///Users/viktor/Projects/tests/gui_1/gui_1_server/bin/main.dart',
+      );
+      expect(info.package, isNull);
+    });
+
+    test('handles dart: URIs (non-package)', () {
+      final info = RootInfo('dart:core');
+      expect(info.library, 'dart:core');
+      expect(info.package, isNull);
+    });
+  });
+}
diff --git a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart
index 5b948b5..bb2b821 100644
--- a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart
+++ b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart
@@ -28,6 +28,7 @@
     List<String> availableServices = const [],
     List<String> availableLibraries = const [],
     String? rootLibrary,
+    RootInfo? rootInfo,
   }) {
     _serviceManager = FakeServiceManager(
       service: service,
@@ -36,6 +37,7 @@
       availableLibraries: availableLibraries,
       availableServices: availableServices,
       rootLibrary: rootLibrary,
+      rootInfo: rootInfo,
     );
     for (final screen in ScreenMetaData.values) {
       final screenId = screen.id;
@@ -92,9 +94,11 @@
     this.onVmServiceOpened,
     Map<String, Response>? serviceExtensionResponses,
     String? rootLibrary,
+    RootInfo? rootInfo,
     bool hasConnection = true,
   }) : serviceExtensionResponses =
            serviceExtensionResponses ?? _defaultServiceExtensionResponses,
+       _rootInfo = rootInfo ?? RootInfo('package:myPackage/myPackage.dart'),
        _isolateManager = FakeIsolateManager(rootLibrary: rootLibrary) {
     this.service = service ?? createFakeService();
     serviceUri = this.service!.wsUri;
@@ -179,8 +183,15 @@
   @override
   ConnectedApp? connectedApp = MockConnectedApp();
 
+  /// The [RootInfo] returned by [rootInfoNow].
+  ///
+  /// This is independent of the `rootLibrary` constructor argument, which only
+  /// configures the root library of the fake main isolate. Some tests depend on
+  /// this defaulting to a fixed `package:` root library.
+  final RootInfo _rootInfo;
+
   @override
-  RootInfo rootInfoNow() => RootInfo('package:myPackage/myPackage.dart');
+  RootInfo rootInfoNow() => _rootInfo;
 
   @override
   Future<RootInfo?> tryToDetectMainRootInfo() => Future.value(rootInfoNow());