ui_commenter 0.1.1 copy "ui_commenter: ^0.1.1" to clipboard
ui_commenter: ^0.1.1 copied to clipboard

Debug-only Flutter tool: tap any widget in the running app, comment on it, and copy a ready-to-paste prompt (source location, type, key, ancestry) for a coding agent.

ui_commenter #

A debug-only, in-app tool for any Flutter project. Tap any widget in the running app, see it highlighted, type a comment, and copy a ready-to-paste prompt describing the widget (source location, type, key, ancestry) plus your comment — then paste it into a coding agent (Claude Code, Codex, etc.) to drive a change against that exact widget.

It's the Flutter equivalent of the web "click-to-component + comment" tooling: the overlay hit-tests the render tree (like document.elementFromPoint) and reads each widget's compiler-injected creation location (like a data-source attribute), exposed via --track-widget-creation (the default in debug).

Why #

Pointing a coding agent at "that button" usually means hunting for the file and line yourself. ui_commenter closes that loop from inside the running app: tap the widget, say what you want, paste the result. The prompt already carries the exact file:line:column, so the agent edits the right widget on the first try.

Install #

ui_commenter is a debug-only dev tool, so add it under dev_dependencies:

dev_dependencies:
  ui_commenter: ^0.1.0

Usage #

Wrap your app content once. The natural seam is MaterialApp.builder, so the commenter sits above the app's Navigator/Overlay and can highlight anything beneath it:

import 'package:ui_commenter/ui_commenter.dart';

MaterialApp(
  builder: (context, child) => UiCommenter(
    enabled: kMyDevFlag, // your own debug-menu / --dart-define toggle
    config: const UiCommenterConfig(appName: 'Acme Flutter app'),
    child: child!,
  ),
  // ...
);

That is the only required host-app change. No analyzer suppressions, build flags (beyond the default --track-widget-creation), or folder conventions are imposed on your project.

A runnable demo lives in example/.

At runtime (debug build) #

  1. With enabled on, a small draggable FAB appears — tap it to arm select mode.
  2. Tap a widget → it's highlighted, a comment is appended to the list, and a sheet slides up.
  3. Type your comment. Tap more widgets to accumulate more; prev/next navigate the list with an INDEX/TOTAL indicator, and edits survive navigation.
  4. Copy Prompt writes a single prompt covering every comment to the clipboard and clears the list. Paste it into your coding agent.

Clear All empties the list; the per-comment trash removes just the current one; tapping outside hides the sheet (dropping the current comment only if its field is empty).

Widget inspector #

The identity frame in the comment sheet carries a small inspect button (ruler icon, top-right). Tapping it opens a read-only Widget Inspector sheet for the selected widget — an in-app analogue of the DevTools Layout Explorer:

  • a box diagram of the widget nested in its parent, with per-side padding and the widget's own w/h;
  • the geometry (size, global offset, incoming constraints, parent offset, flex, render-object type);
  • a default-aware property table (debugFillProperties output, with a default badge on properties left at their default);
  • copy shortcuts for the source location and the property dump.

It is purely diagnostic — it produces no prompt output and changes nothing about the capture/copy pipeline. Geometry needs a laid-out RenderBox; rich properties and source locations need debug + --track-widget-creation. When any of those is missing the sheet degrades gracefully (identity-only). Like the rest of the tool it is debug-only and never reaches release builds.

Configuration #

UiCommenterConfig keeps the tool reusable without forking. Every field has a sensible default, so const UiCommenterConfig() works out of the box.

Field Default Purpose
appName generic phrasing Woven into the default prompt header. Ignored when promptBuilder is set.
promptBuilder built-in formatter String Function(List<WidgetComment>) — full override of the copied prompt text.
highlightColor Color(0xFF2196F3) Color of the selection highlight box and select-mode tint.
localPathPredicate built-in heuristic bool Function(String absolutePath) — override "is this file part of my project?" for monorepos/melos.
maxAncestors 4 Cap on the ancestor chain length in the prompt.
brightness null (follow app) Force the tool UI's Brightness independent of the host app.

Theming #

By default the tool UI follows your app's theme — it reads Material color roles (colorScheme.surface, inverseSurface, error, …), so it renders light in a light app and dark in a dark app, and flips with themeMode. Only the selection accent is fixed, via highlightColor.

To pin the tool's appearance regardless of the app (e.g. always-dark debug chrome), set brightness. It re-themes only the commenter overlay; your app subtree keeps its own theme.

UiCommenterConfig(brightness: Brightness.dark);

Exported types #

For a custom promptBuilder, the package exports two read-only data types:

  • SelectedWidget — immutable capture of one tapped widget: file?, line?, column?, type, key?, ancestry, bounds, plus hasLocation and relativeLocation helpers.
  • WidgetComment — a SelectedWidget paired with the typed text.
UiCommenterConfig(
  promptBuilder: (comments) => comments
      .map((c) => '${c.widget.relativeLocation}: ${c.text}')
      .join('\n'),
);

Monorepo / melos layouts #

The built-in heuristic treats a lib/ Dart source as "local" unless it is the Flutter SDK, a pub package, or ui_commenter itself. If your project's code lives across several packages (melos), tell the tool which paths count as yours:

UiCommenterConfig(
  localPathPredicate: (absolutePath) =>
      absolutePath.contains('/packages/') && absolutePath.contains('/lib/'),
);

Output prompt #

The default formatter produces, for multiple comments, a single prompt with a count header and one numbered block per widget:

You are editing the Acme Flutter app. The developer selected 2 widgets in the
running app and left a comment about each.

Widget 1:
- File: lib/features/history/presentation/home_summary_widget.dart:128:14
- Type: ElevatedButton
- Key:  ValueKey('refresh')
- Path: HomeScreen › HomeSummaryWidget › Card › ElevatedButton
- Comment: Make this button full-width and use the primary color.

Widget 2:
- File: lib/features/history/presentation/home_screen.dart:236:25
- Type: Text
- Path: HomeScreen › Column › Text
- Comment: Increase the font size and bold it.

Debug-only & --track-widget-creation #

  • The overlay is mounted only when enabled && kDebugMode. In any release or profile build, UiCommenter returns its child untouched and the rest is tree-shaken out — zero impact on shipped apps. enabled is your own dev flag (a --dart-define, flavor constant, or debug-menu toggle); the default true is safe because the kDebugMode gate already blocks release leakage.
  • Source locations come from --track-widget-creation, which flutter run enables by default in debug. Without it (or in profile/release), the tool degrades gracefully: it still emits type + key + ancestry, noting that the source location is unavailable.
  • Monorepos / melos: the built-in "is this file local?" heuristic keys off a single lib/. If your local code lives outside one lib/, supply config.localPathPredicate to classify it yourself.

Supported platforms #

Platform Status
iOS / Android Supported (simulators and devices), debug builds.
macOS Supported — verified to build and run from the example app.
Windows Supported — desktop runner scaffolded; same platform-agnostic capture path as macOS.
Web / Linux Not verified. Web is the main unknown: --track-widget-creation and creation-location paths differ under the web toolchain, so the File: line may not resolve.

The capture pipeline uses only platform-agnostic framework symbols, so desktop behaves like mobile. The one platform-specific concern is path formatting: Windows creation paths use backslashes, which the local-file heuristic and the emitted File: location both normalize. Non-standard layouts can still override classification via config.localPathPredicate.

Manual verification #

Unit/widget tests cover formatting, path classification, hit testing, and the overlay's list/navigation behavior. The end-to-end capture — that a tap on a real lib/** widget yields the correct File: *.dart:line:col — depends on --track-widget-creation and is best confirmed on a simulator:

cd example
flutter run            # debug; track-widget-creation is on by default
flutter run -d macos   # or run on the desktop runner

Then arm select mode, tap a widget, and check the File: line in the sheet points at the right source. The package is built and tested against Flutter 3.38.x / Dart 3.10.x.

Flutter version coupling #

The capture pipeline relies on a few framework symbols that are stable but not part of the public stable API surface — re-verify these on a Flutter upgrade:

  • RenderObject.debugCreatorDebugCreator.element
  • WidgetInspectorService.instance.selection.currentElement
  • WidgetInspectorService.instance.getSelectedWidget (@protected; called behind one scoped // ignore: invalid_use_of_protected_member)

They are touched in only two files: lib/src/creation_location.dart and lib/src/widget_locator.dart.

Contributing #

The design and rationale live in docs/ui-commenter.md. Run flutter test and flutter analyze before opening a PR.

License #

MIT

0
likes
160
points
4
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Debug-only Flutter tool: tap any widget in the running app, comment on it, and copy a ready-to-paste prompt (source location, type, key, ancestry) for a coding agent.

Repository (GitHub)
View/report issues

Topics

#debugging #devtools #widget-inspector #developer-tools

License

MIT (license)

Dependencies

flutter

More

Packages that depend on ui_commenter