Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
374 changes: 73 additions & 301 deletions CHANGELOG.md

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions CLAUDE.md

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![Pub Version](https://img.shields.io/pub/v/sleuth)](https://pub.dev/packages/sleuth)
[![Flutter](https://img.shields.io/badge/Flutter-3.32%2B-blue?logo=flutter)](https://flutter.dev)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://img.shields.io/badge/tests-4%2C761_passing-brightgreen)]()
[![Tests](https://img.shields.io/badge/tests-5%2C099_passing-brightgreen)]()
[![Analysis](https://img.shields.io/badge/analysis-0_issues-brightgreen)]()

Sleuth is an in-app performance diagnostics overlay for Flutter. It shows jank, memory leaks, slow network requests, GPU pressure and widget anti-patterns inside your app, and every issue comes with a fix hint.
Expand All @@ -26,7 +26,7 @@ Where Sleuth helps more than DevTools:
- **Structural checks.** It flags problems DevTools does not: non-lazy lists, images decoded above display size, missing `RepaintBoundary`, intrinsic layout and retained stream subscriptions.
- **Explained issues.** Each card names the widget and its source line (`file.dart:42`), gives a fix hint, and says what evidence it is based on and how confident it is: confirmed, likely or possible.
- **Causes linked to effects.** 42 causal rules link a root cause to the issues it causes, so a card shows why an issue matters.
- **Fix verification and trends.** Capture a baseline, apply the fix and compare. A `Seen X/Y` badge and a trend label show whether an issue is getting better or worse across scans.
- **Fix verification and trends.** Capture a baseline, apply the fix and compare. Each card's meta line ("seen 19 of 20 scans, persistent") shows whether an issue is getting better or worse across scans.
- **AI help.** Ask the AI provider of your choice about any issue from its card, or let an assistant query the running app through the `sleuth_mcp` sidecar.

What DevTools still does better:
Expand Down Expand Up @@ -87,15 +87,15 @@ Both modes run the full overlay, all 21 detectors and the AI chat. They differ i
| VM timeline (build, layout and paint durations) | Yes | Yes | n/a |
| Source location in issues (`file.dart:42`) | Yes | Yes, except iOS builds from `flutter build ios` or `ipa` | n/a |
| Per-widget rebuild and paint issues (`enableDebugCallbacks`) | Yes (opt-in) | No | n/a |
| Rebuild stats panel with per-widget rebuild counts (`enableDeepDebugInstrumentation`) | No | Yes (opt-in) | n/a |
| Rebuild stats page (More menu) with per-widget rebuild counts (`enableDeepDebugInstrumentation`) | No | Yes (opt-in) | n/a |
| Widget dirty-state arguments on timeline events (`DebugInstrumentationConfig.timelineEnrichment`) | Yes (opt-in) | No | n/a |

Use profile mode to measure, and debug mode with `enableDebugCallbacks: true` to find which widget rebuilds or repaints. Then check the timing fix in profile mode. Both instrumentation options add overhead and are off by default:

```dart
SleuthConfig(
enableDebugCallbacks: true, // debug: per-widget rebuild and paint counts (widgets your code creates)
enableDeepDebugInstrumentation: true, // per-widget build timeline events; in profile mode, the Rebuild stats panel
enableDeepDebugInstrumentation: true, // per-widget build timeline events; in profile mode, the Rebuild stats page
)
```

Expand Down Expand Up @@ -211,11 +211,11 @@ final markdown = Sleuth.exportSummary(topN: 5); // paste into Slack or a PR desc
final score = Sleuth.routeHealthScore('/settings'); // 0 to 100
```

The dashboard's Export button copies the JSON snapshot to the clipboard. These calls return `null` in release mode, before `track()` is called, or after the overlay is disposed.
Copy snapshot in the dashboard's More menu copies the JSON snapshot to the clipboard. These calls return `null` in release mode, before `track()` is called, or after the overlay is disposed.

- **Routes.** Sleuth detects route changes from the element tree, with no `NavigatorObserver`, and keeps a `RouteSession` per route with FPS, jank ratio, issues and a health score. Tab shells (`IndexedStack`, `StatefulShellRoute.indexedStack`, `CupertinoTabScaffold`) get one session per tab. [Internals](https://github.com/Harrys76/sleuth/blob/main/doc/internals.md#route-sessions) covers the session keys and tracking options.
- **Fix verification.** Call `Sleuth.captureBaseline()` before a fix and `Sleuth.compareToBaseline()` after it. An issue counts as resolved after it stays absent for five scans, with a three-scan grace period after hot reload.
- **Recurrence.** The `Seen X/Y` badge counts how many of the last 60 scans saw the issue, and its label (worsening, persistent, stable, improving, flaky) shows the recent trend. [Internals](https://github.com/Harrys76/sleuth/blob/main/doc/internals.md#recurrence-badge) has the thresholds.
- **Recurrence.** The card's meta line counts how many of the last 60 scans saw the issue ("seen 12 of 20 scans"), and its label (worsening, persistent, stable, improving, flaky) shows the recent trend. [Internals](https://github.com/Harrys76/sleuth/blob/main/doc/internals.md#recurrence) has the thresholds.
- **Startup.** Call `Sleuth.init()` as the first line of `main()` to measure time to first frame, split into engine and Dart phases. [Internals](https://github.com/Harrys76/sleuth/blob/main/doc/internals.md#startup-tracing) explains each metric.

## Confidence and ranking
Expand All @@ -233,7 +233,7 @@ Every detector declares the evidence behind its thresholds, and a CI audit enfor
## Limitations

- Sleuth does not replace DevTools heap snapshots or interactive flame charts.
- Per-widget attribution depends on the mode. Debug mode gives per-widget rebuild and paint counts. Profile mode gives per-widget rebuild counts only in the Rebuild stats panel, and its rebuild and repaint issues measure the share of UI-thread time, not single widgets.
- Per-widget attribution depends on the mode. Debug mode gives per-widget rebuild and paint counts. Profile mode gives per-widget rebuild counts only on the Rebuild stats page, and its rebuild and repaint issues measure the share of UI-thread time, not single widgets.
- VM full mode depends on the platform and on how the app was launched.
- Memory detection watches young-generation GC frequency, heap growth and, when you set a budget, process memory. When the heap grows it adds per-class allocation deltas to the issue, but it does not track individual object leaks or retention paths.
- CPU attribution of jank frames is off by default (`SleuthConfig(cpuAttribution: true)` turns it on): the VM builds each answer on the app's UI isolate, which froze the UI for about 300 ms per request on an iPhone 12. It samples at about 1 kHz, so functions that run for less than 1 ms may not appear.
Expand All @@ -252,7 +252,7 @@ cd example && flutter run --profile --no-dds
`package:sleuth/sleuth.dart` follows semantic versioning, so a breaking change needs a major release. These are outside that promise and can change in a minor release:

- `package:sleuth/validation.dart`, the capture and validation tooling the example's capture screens use, and main-API members typed with its names (such as `Sleuth.repaintDetector`).
- Members marked `@experimental` (the capture statics on `Sleuth`) or `@visibleForTesting`.
- Members marked `@experimental` (the capture statics on `Sleuth`) or `@visibleForTesting`. Their signatures can change in a minor release, but removing one from `Sleuth` or another `sleuth.dart` class waits for a major release, because the CI API diff counts that removal as breaking.
- New values in `DetectorType`, `IssueCategory`, `ObservationSource`, `PipelinePhase`, `InteractionContext` and `FixEffort`. A `switch` over them needs a default branch.

`ParsedTimelineData` carries `vm_service` types, so a `vm_service` major release means a Sleuth major release. [The API list](https://github.com/Harrys76/sleuth/blob/main/doc/api_1_0_0.md) lists every public name and its library.
Expand Down
10 changes: 5 additions & 5 deletions doc/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ Toasts are live regions and stay three times longer while a screen reader or oth

## Text size

Overlay text follows the system setting between 0.8× and 2.0×; the app below keeps its own scale. The chrome (card header, status row, summary bar, footer, badges, trigger) stops growing at 1.3× so the issue list stays visible; above that, issue titles take two lines and detail text wraps.
Overlay text follows the system setting between 0.8× and 2.0×; the app below keeps its own scale. The chrome (card header with its run label, status row with the severity chips, trigger) stops growing at 1.3× so the issue list stays visible; above that, issue titles take two lines and detail text wraps.

Fixed heights grow with the chrome but never past the screen (on a landscape phone, for example), and the status row and banners scroll when they would squeeze the list. Moving or resizing the card, by touch or with a screen-reader action, keeps the whole card on screen above the keyboard. In AI chat the issue context gives way to the input and Send button when the keyboard is up. A card resized under large text returns to its own height at 1.0×. The smallest text is 10 px.

## Touch targets

Controls are at least 48 × 48 dp. The card header's compact controls (highlight, theme, minimize, maximize, restore) are 36 × 48 so that they fit the default 300 dp card, which is above the WCAG 2.5.8 minimum of 24 dp. Below 280 dp the minimize and maximize controls are hidden. Close is 48 × 48.
Controls are at least 48 × 48 dp. The card header's compact controls (Mark flagged widgets, More, minimize, maximize, restore) are 36 × 48 so that they fit the default 320 dp card, which is above the WCAG 2.5.8 minimum of 24 dp. A card narrower than 280 dp, which only a very narrow screen allows, hides the minimize and maximize controls. Close is 48 × 48.

## Contrast

Text tokens meet WCAG AA (4.5:1) on every overlay surface in the dark, light and high-contrast themes. Badges draw primary text on a light tint of their colour, with a 1 px border in that colour.

## High contrast

`SleuthThemeData.highContrastDark()` and `highContrastLight()` raise secondary text, strengthen borders, make badge fills opaque and widen the source accent. Sleuth picks them when `MediaQuery.highContrastOf` is true (reported on iOS) and no theme is set, and for the toggle's Light or Dark. On other platforms, pass one to `Sleuth.updateTheme`. State cues (chip borders, chevrons, the pin) stay at full opacity.
`SleuthThemeData.highContrastDark()` and `highContrastLight()` raise secondary text, strengthen borders and the card divider, and make badge fills opaque. Sleuth picks them when `MediaQuery.highContrastOf` is true (reported on iOS) and no theme is set, and for Light or Dark under Theme in the More menu. On other platforms, pass one to `Sleuth.updateTheme`. State cues such as the chip borders stay at full opacity.

## Reduced motion

Sleuth honours both the Android animator duration scale (`disableAnimations`) and iOS Reduce Motion (`AccessibilityFeatures.reduceMotion`). Page entrances, expand and collapse, scrolls to an entry, the toast fade, severity chips and the rebuild count change at once, and the Ask AI shimmer stops. An animation that is already running when the setting changes finishes at its old speed.
Sleuth honours both the Android animator duration scale (`disableAnimations`) and iOS Reduce Motion (`AccessibilityFeatures.reduceMotion`). Page entrances, the More menu, expand and collapse, scrolls to an entry and the toast fade change at once. An animation that is already running when the setting changes finishes at its old speed.

## Keyboard

Escape first unfocuses a focused overlay text field, then closes the open page, then the dashboard. A focused text field or an open dialog or sheet in your app keeps its Escape.
Escape first unfocuses a focused overlay text field, then closes the open page, the More menu or the highlight bar, then the dashboard. An open dialog or sheet in your app keeps its Escape, and so does a focused app text field unless the highlight bar is up.
4 changes: 2 additions & 2 deletions doc/api_1_0_0.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Every `export` line in both libraries has a `show` list. `test/validation/public

## Moved to `package:sleuth/validation.dart`

Each of these is marked `@experimental`, as are the `validationMetadata` getters of the main-API detectors. The CI API diff leaves `validation.dart` out and rates changes to `@experimental` declarations as minor. It fails only on a breaking change without a major version bump.
Each of these is marked `@experimental`, as are the `validationMetadata` getters of the main-API detectors. The CI API diff leaves `validation.dart` out. It rates a change to an `@experimental` declaration, or to a member of one, as minor. Removing an `@experimental` member of a class that is not experimental, such as a capture static on `Sleuth`, counts as breaking. The diff fails only on a breaking change without a major version bump.

| Name | Kind | Used by |
|---|---|---|
Expand All @@ -55,7 +55,7 @@ Each of these is marked `@experimental`, as are the `validationMetadata` getters
| `kSleuthPackageVersion` | const | Capture exports (the sidecar reads the source, not the export) |
| `PollTimings` | class | `Sleuth.lastPollTimings` |
| `StreamResourcePollResult` | class | `StreamResourceDetector` capture polls |
| `RepaintDetector`, `MemoryPressureDetector`, `StartupDetector`, `TrackedResourceDetector`, `ImageMemoryDetector`, `GpuPressureDetector`, `FrameTimingDetector` | class | Example capture screens (imported from `src/` today) |
| `RepaintDetector`, `MemoryPressureDetector`, `StartupDetector`, `TrackedResourceDetector`, `ImageMemoryDetector`, `GpuPressureDetector`, `FrameTimingDetector` | class | Example capture screens |

## No longer exported

Expand Down
Loading
Loading