Skip to content
Merged
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,48 @@
- `DebugSnapshot.forcedRebuildsOf` (debug callbacks): for each widget type
a parent's build rebuilt, the types whose builds forced it, framework
widgets included, and how often. `forcedTypesCapped` reports its caps.
- `SleuthThemeData.cardDivider`, the line under the status row. It
defaults to `0xFF4B5563` in dark and `0xFFB4BAC4` in light, `border` in
the high-contrast presets and `outlineVariant` in `fromColorScheme`.

### Changed

- The card header shows a run label, such as "Debug · VM" or "Profile · no
VM", in place of the VM+, FRAME and DBG badges. A narrow card drops the
"Sleuth" name first, then "· VM", and at last keeps only the dot, which
is filled with the VM and a ring without it. A screen reader always
hears the full label.
- A More button replaces the theme button. Its menu holds Startup (TTFF),
Rebuild stats (the route's count, opening the drilldown), Hidden issues
(with a count, only when there are any), Encyclopedia, Guide, Copy
snapshot and Theme (Auto, Light, Dark). It opens as a panel inside the
card. Back, Escape, a tap on the scrim or a tap on the app closes it,
and while it is open the app and the card body are hidden from screen
readers. Opening it moves keyboard focus to the first row that can act
and screen reader focus to the panel. More, the rows and the theme
segments draw a 2 px `checkboxActive` outline while the keyboard has
focus. A failed copy offers Try again.
- Tab and Shift+Tab move focus, and Enter and Space activate the focused
control, inside the card and its pages. The overlay sits above the
app's `WidgetsApp`, so the app's default shortcuts never reached it; a
focused text field keeps Enter and Space.
- The footer, the startup banner, the rebuild stats banner, the confidence
caption and the frame-rate detail row are gone. Hidden issues, Copy
snapshot, startup and rebuild stats moved into the More menu.
- The severity chips lead the status row ("2 critical"), with a filled
icon while on and an outlined one while off. The frame rate sits at the
end of the row. "Showing X of Y" appears only while a severity is off,
since the More menu counts hidden cards. A screen reader hears the line
change, and Show all moves its focus to the first chip.
- The card is at least 320 px wide, or the screen less 16 px margins on
a narrower screen, and stored narrower widths are raised. Its background
is opaque.
- A maximized card draws the resize grip's dots only, and a drag there
reaches the list. The list ends with 48 px of padding, so the grip does
not cover the last card.
- The overlay no longer uses `TweenAnimationBuilder`, so it is off the
debug instrumentation's framework denylist and an app's own uses count.

- `FrameVerdict.relatedIssues` lists only the issues that can explain the
verdict's phase: the issues of the phase's category, `event_loop_stall`
for a start delay, and none for a stall or an unknown phase. Jank
Expand Down
3 changes: 2 additions & 1 deletion doc/capture_procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
> 1. `cd example && fvm flutter run --profile --no-dds -d "iPhone 12" \
> --dart-define=SLEUTH_CAPTURE_MODE=true \
> --dart-define=SLEUTH_CAPTURE_DEVICE="iPhone 12"`. This installs
> and starts the app. The status row shows VM+ once Sleuth connects.
> and starts the app. The header run label reads "· VM" once Sleuth
> connects.
> 2. If you launched without `--no-dds`, quit `flutter run` (`q`). DDS
> stops with it.
> 3. In that case, reopen the app from the iPhone home screen. No DDS
Expand Down
18 changes: 9 additions & 9 deletions example/lib/demos/rebuild_hotspot_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import '../demo_scaffold.dart';
///
/// 1. Open the Sleuth overlay (paw icon).
/// 2. Tap "Start Ticker" and wait a few seconds.
/// 3. The "Rebuilds: N across M widgets" banner at the top of the card
/// counts every widget type built on this screen; tap it to expand
/// the top three.
/// 4. Tap "See all M" to open the Rebuild stats page, every
/// widget type sorted by count.
/// 3. Open the card's More menu: Rebuild stats counts every widget
/// built on this screen.
/// 4. Tap Rebuild stats to open the Rebuild stats page, every widget
/// type sorted by count.
///
/// In debug mode the banner stays empty (it reads the profile timeline).
/// In debug mode the row reads "No counts yet" (it reads the profile
/// timeline).
/// Sleuth instead counts a rebuild for the widget that started it: the
/// dashboard's `ValueListenableBuilder<int>` and the metric chips'
/// builders rebuild about 30 times a second together, the builder alert
Expand Down Expand Up @@ -104,9 +104,9 @@ class _RebuildHotspotDemoState extends State<RebuildHotspotDemo> {
'subscribes to the tick notifier, so each tick rebuilds one '
'widget.\n\n'
'Tap "Start Ticker", open the Sleuth overlay (paw icon) and '
'wait a few seconds. In PROFILE mode the "Rebuilds" banner at the '
'top of the card counts the widgets built on this screen; expand '
'it and tap "See all" for the Rebuild stats page. In DEBUG '
'wait a few seconds. In PROFILE mode Rebuild stats in the card\'s '
'More menu counts the widgets built on this screen. Tap it for '
'the Rebuild stats page. In DEBUG '
'mode Sleuth names the widget that starts each rebuild: a card '
'for ValueListenableBuilder<int>, whose detail counts the '
'dashboard widgets it rebuilt.',
Expand Down
14 changes: 7 additions & 7 deletions lib/sleuth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
/// ## Features
/// - 21 performance detectors (VM-powered, hybrid, structural, and runtime)
/// - Actionable fix hints for every issue
/// - In-app overlay with live FPS chart and issue dashboard
/// - In-app overlay with the frame rate and an issue dashboard
/// - Debug mode warning (run with --profile for accurate data)
/// - Completely disabled in release builds
///
/// ## Theming
///
/// The overlay auto-detects dark/light mode from the system brightness and
/// uses the high-contrast presets when the platform asks for high
/// contrast. The header toggle cycles System, Light and Dark (persisted in
/// `OverlayUiState.themeMode`). To force a specific theme or customize
/// colors:
/// contrast. Theme in the overlay's More menu picks Auto, Light or Dark
/// (persisted in `OverlayUiState.themeMode`). To force a specific theme or
/// customize colors:
///
/// ```dart
/// // Light theme for light-background apps
Expand Down Expand Up @@ -1060,9 +1060,9 @@ class Sleuth {
/// Update the overlay theme at runtime.
///
/// Passing a [SleuthThemeData] overrides the config theme and
/// auto-detection, and sets the header toggle to System so the theme
/// shows. Picking Light or Dark in the header takes precedence over it;
/// picking System shows it again. Passing `null` reverts to the config
/// auto-detection, and sets Theme in the More menu to Auto so the theme
/// shows. Picking Light or Dark there takes precedence over it; picking
/// Auto shows it again. Passing `null` reverts to the config
/// theme or auto-detection. Pass the same instance on every call (a
/// const preset or a theme built once): the overlay compares themes by
/// identity.
Expand Down
32 changes: 21 additions & 11 deletions lib/src/controller/sleuth_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -517,19 +517,18 @@ class SleuthController {
ValueNotifier(null);

/// Runtime theme override. Takes precedence over [config.theme] and
/// auto-detection when non-null; the header toggle's Light or Dark
/// takes precedence over it.
/// auto-detection when non-null; Light or Dark picked under Theme in
/// the More menu takes precedence over it.
final ValueNotifier<SleuthThemeData?> _themeOverride =
ValueNotifier<SleuthThemeData?>(null);

/// Listenable for the current theme override. Null means use config or
/// auto-detect.
ValueListenable<SleuthThemeData?> get themeOverride => _themeOverride;

/// Update the overlay theme at runtime. A non-null [theme] also sets the
/// header toggle to System so the theme shows; picking Light or Dark
/// in the header afterwards takes precedence until System is picked
/// again. An app that calls this at every startup therefore does not
/// Update the overlay theme at runtime. A non-null [theme] also sets
/// Theme in the More menu to Auto so the theme shows; picking Light or
/// Dark there afterwards takes precedence until Auto is picked again. An app that calls this at every startup therefore does not
/// keep a saved Light or Dark choice across launches. Pass `null` to
/// revert to the config theme or auto-detection.
void updateTheme(SleuthThemeData? theme) {
Expand Down Expand Up @@ -935,6 +934,17 @@ class SleuthController {
return result;
}

/// Whether this run counts rebuilds per route for Rebuild stats: a
/// profile run with [SleuthConfig.enableDeepDebugInstrumentation] whose
/// timeline drain is installed or waiting for the scheduler to go idle.
/// Debug runs count rebuilds for the detectors only.
bool get countsRouteRebuilds {
final coordinator = _debugCoordinator;
if (coordinator == null) return false;
return coordinator.isProfileModeInstalled ||
(!kDebugMode && config.enableDeepDebugInstrumentation);
}

/// Whether any heavy debug profiling flags are actually active.
///
/// Returns `true` only when at least one Flutter debug flag was saved and
Expand Down Expand Up @@ -5962,10 +5972,10 @@ class SleuthConfig {
/// brightness when `MediaQuery.highContrastOf` is true. If no
/// [MediaQuery] is available (rare), defaults to dark.
///
/// Precedence: the header toggle's Light or Dark mode
/// Precedence: Light or Dark picked under Theme in the More menu
/// (`OverlayUiState.themeMode`) > `Sleuth.updateTheme` > this theme >
/// auto-selection. Choosing Light or Dark in the header replaces this
/// theme with the Sleuth preset; choosing System restores it.
/// auto-selection. Choosing Light or Dark there replaces this theme with
/// the Sleuth preset; choosing Auto restores it.
///
/// ```dart
/// // Force light theme
Expand Down Expand Up @@ -6139,8 +6149,8 @@ class SleuthConfig {
/// Sleuth installs a `FlutterTimeline.debugCollect()` drain that records
/// per-widget rebuild counts and attributes them to the active
/// `RouteSession`'s `rebuildCountsByType` map. These counts power the
/// always-on `_RebuildStatsBanner` panel on the floating issues card
/// and the "See all N →" drilldown page (`RebuildStatsPage`).
/// More menu's Rebuild stats row on the floating issues card and its
/// drilldown page (`RebuildStatsPage`).
/// **Note:** profile-mode counts include initial widget inflations as
/// well as `setState`-driven rebuilds (the Flutter framework emits the
/// same `BUILD` timeline scope for both), so route entry shows
Expand Down
19 changes: 9 additions & 10 deletions lib/src/debug/debug_instrumentation_coordinator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ class DebugInstrumentationCoordinator {
/// uses Sleuth-named equivalents listed below, so app-owned rebuilds of
/// those widgets are counted.
static const Set<String> _frameworkWidgetDenyList = {
// --- Flutter framework widgets used in lib/src/ui/ (53) ---
// --- Flutter framework widgets used in lib/src/ui/ (52) ---
'Align',
'AnimatedBuilder',
'AnimatedRotation',
Expand Down Expand Up @@ -803,20 +803,13 @@ class DebugInstrumentationCoordinator {
'Stack',
'Text',
'TextField',
'TweenAnimationBuilder',
'ValueListenableBuilder',
'Wrap',
// --- Sleuth overlay widget classes (43) ---
'FloatingIssuesCard',
'_StatusRow',
'_ThroughputDetailRow',
'_FpsCell',
'_DebugModeBanner',
'_WarningBanners',
'_CardFooter',
'_IssuesSummaryBar',
'_StartupMetricsBanner',
'_RebuildStatsBanner',
'TriggerButton',
'SleuthOverlay',
'HighlightOverlay',
Expand All @@ -843,13 +836,19 @@ class DebugInstrumentationCoordinator {
'_HiddenRow',
'_TextAction',
'_SeverityChip',
'_SeverityChipPill',
'_EmptyListMessage',
'_ToastFade',
'_TriggerLayout',
'SleuthListenableBuilder',
'_AppSemanticsBlocker',
'_LeadTrailLine',
'SleuthMoreMenu',
'_MenuRow',
'_ThemeRow',
'_FpsText',
'_RunLabel',
'_ShowingRow',
'SleuthFocusInkWell',
'SleuthFocusOutline',
};

void _handleRebuildDirtyWidget(Element element, bool builtOnce) {
Expand Down
Loading
Loading