Skip to content

feat: integrate customizable smooth scroll duration and update contro… - #11

Merged
buivietphi merged 2 commits into
mainfrom
fix/console-all-events-ux-bugs
Jul 2, 2026
Merged

buivietphi merged 2 commits into
mainfrom
fix/console-all-events-ux-bugs

Conversation

@buivietphi

@buivietphi buivietphi commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

✨ Smooth Scrolling & Full Coverage
What
Adds a smooth scrolling feature for mouse wheel events across the entire application, along with configurable scroll animation duration.

Changes
Core
SmoothScrollController — Custom ScrollController that intercepts pointerScroll events and replaces the default instant-jump behavior with an animateTo() call using Curves.easeOutQuart, making every scroll feel fluid and natural. Reads both the enabled flag and duration directly from AppPreferences on each event so settings apply instantly without restart.
Settings
Toggle — On/Off switch under Appearance settings (default: off). Note warns users to disable if they experience performance issues.
Duration Slider — Appears when smooth scroll is enabled. Allows adjusting animation duration from 100ms to 1000ms (default: 250ms, steps of ~50ms).
Persistence — Both settings are saved via AppPreferences and backed by dedicated Riverpod providers: smoothScrollEnabledProvider and smoothScrollDurationProvider.
Full Scroll Coverage
Applied SmoothScrollController to every scrollable widget in the app:

Main event list views: All Events, Console, Network, State, Storage, Errors
Detail panels: Log detail, Network (Headers/Body/Timing tabs), State (Diff/Before/After tabs), Storage value panel, Error (Message/Stack Trace/Details tabs), Fallback event
Settings page, Database Viewer (vertical + horizontal), Performance profiler list, Memory Leaks (list + detail), Benchmark (list + detail)
Last Connected session: history list + all 5 event detail tabs
Shared viewer components: JsonViewer (tree), JsonPrettyViewer (code/raw)
Layout components: Sidebar navigation, Device connection bottom bar (horizontal)
UI Fix
Request Headers spacing — Fixed _SelectableEllipsis in TextComponent incorrectly reserving vertical space for non-overflowing text by setting maxLines: null for short values. Rows now collapse to their actual content height.
Localization
All 7 supported languages updated (EN, VI, JA, FR, ZH, ZH-CN, ZH-TW) for: smoothScrolling, smoothScrollingDesc, smoothScrollingDuration, smoothScrollingDurationDesc.
Developer HTTP terminology (Headers, Request, Response, Timing, etc.) kept as hardcoded English in both Network Inspector and All Events pages for consistency with standard tooling conventions.
Testing
flutter test passes — all tests green

Summary by CodeRabbit

  • New Features

    • Added a configurable smooth-scroll duration setting, with translated labels and descriptions.
    • Expanded log message displays with compact previews and richer JSON/tree/code views.
  • Bug Fixes

    • Improved scrolling behavior across sidebars, inspectors, viewers, and detail panels.
    • Fixed text rendering so selectable content behaves correctly when no truncation is needed.
  • Documentation

    • Updated localized in-app text for the new smooth-scroll duration option.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@buivietphi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99e6b30c-f894-42b3-afad-b39cb9c66fe1

📥 Commits

Reviewing files that changed from the base of the PR and between fcca61b and 72dd321.

📒 Files selected for processing (3)
  • lib/components/text/text_component.dart
  • lib/core/utils/log_message_summary.dart
  • lib/features/console/presentation/pages/console_page.dart
📝 Walkthrough

Walkthrough

This PR wires SmoothScrollController into scroll views across device bottom bar, sidebar, JSON viewer, console, error inspector, network inspector, state inspector, all-events, and storage viewer detail panels. It adds a configurable smooth-scroll duration preference (with settings UI and full localization), a summarizeLogMessage utility, a tabbed message renderer in console, hardcoded English labels in network inspector, and a maxLines tweak in _SelectableEllipsis.

Changes

Smooth Scroll & Log Summary Feature

Layer / File(s) Summary
Duration setting, application, and settings UI
lib/core/theme/theme_provider.dart, lib/core/utils/smooth_scroll_controller.dart, lib/features/settings/presentation/pages/settings_page.dart
Adds SmoothScrollDurationNotifier/smoothScrollDurationProvider backed by AppPreferences, applies the stored duration in animateTo, and adds a settings slider (100–1000ms) with live label.
Localization for duration setting
lib/l10n/app_localizations*.dart, lib/l10n/app_*.arb
Adds smoothScrollingDuration/smoothScrollingDurationDesc getters and ARB strings across en, fr, ja, vi, zh, zh_CN, zh_TW.
Layout widgets converted to stateful with scroll controllers
lib/components/layout/device_bottom_bar.dart, lib/components/layout/sidebar.dart
DeviceBottomBar and Sidebar become ConsumerStatefulWidgets owning disposed SmoothScrollControllers wired into their lists; callback/index access moved to widget.*.
JsonViewer/JsonPrettyViewer scroll wiring
lib/components/viewers/json_viewer.dart
JsonViewer becomes stateful; both it and JsonPrettyViewer gain disposed SmoothScrollControllers wired to their scroll views.
Log message summarization
lib/core/utils/log_message_summary.dart, lib/features/all_events/provider/all_events_provider.dart, lib/features/console/presentation/pages/console_page.dart
Adds summarizeLogMessage producing compact JSON previews, used for log titles/rows; console detail panel gains _LogMessageBlock with Tree/JSON/Code modes.
All Events detail panels scroll wiring
lib/features/all_events/presentation/pages/all_events_page.dart
Converts _LogDetail, _HeadersView, _BodyView, _TimingView, diff list, storage detail, and fallback detail to stateful widgets with disposed scroll controllers.
Error Inspector tab scroll controllers
lib/features/error_inspector/presentation/pages/error_inspector_page.dart
Adds independent scroll controllers for Message, Stack Trace, and Details tabs, disposed on teardown.
Network Inspector English labels and scroll wiring
lib/features/network_inspector/presentation/pages/network_inspector_page.dart
Replaces localized tab/section titles with hardcoded English, adds scroll controllers for Headers/Body/Timing tabs, and shrinks header key column width.
State Inspector & Storage Viewer scroll wiring
lib/features/state_inspector/presentation/pages/state_inspector_page.dart, lib/features/storage_viewer/presentation/pages/storage_viewer_page.dart
Adds disposed scroll controllers to JSON toggle view, diff view, and storage detail panel.
SelectableEllipsis maxLines tweak
lib/components/text/text_component.dart
Non-overflow render path now passes maxLines: null instead of the computed value.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

Poem

A scrollbar hops from panel to panel,
Body types and storage types, all in the funnel,
Smooth as silk with duration to tune,
Headers request, response — Request, Response, no dune (of localization) too soon.
🐰 Cross-platform hops, one commit, one boon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: customizable smooth-scroll duration with related control updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/console-all-events-ux-bugs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces customizable smooth scrolling duration settings and integrates SmoothScrollController across various scrollable views, converting several widgets to stateful to manage controller lifecycles. It also adds a utility to summarize JSON log messages for cleaner list previews. Key feedback highlights that hardcoding maxLines: null in _SelectableEllipsis breaks its truncation behavior, and warns of potential UI jank from synchronously decoding large JSON payloads on the main thread. Additionally, the reviewer recommends removing the duplicated log-summarization helper in console_page.dart in favor of the new shared utility.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

textAlign: textAlign,
textDirection: textDirection,
maxLines: maxLines,
maxLines: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Hardcoding maxLines: null inside _SelectableEllipsis disables the ellipsis truncation behavior globally and makes the maxLines parameter of this widget dead code. Any caller passing a specific maxLines constraint will have it silently ignored, which can cause text to wrap unexpectedly and break layouts. If the goal was to prevent vertical space reservation for short values, this should be handled conditionally or at the caller level rather than hardcoding null here.

Suggested change
maxLines: null,
maxLines: maxLines,

Comment on lines +19 to +21
dynamic parsed;
try {
parsed = jsonDecode(trimmed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Synchronously calling jsonDecode on potentially large log messages on the main thread can block the UI and cause noticeable jank, especially when mapping over many events in providers or lists. Consider adding a length threshold check (e.g., skipping parsing if trimmed.length > 5000) to protect against performance degradation with large payloads.

  if (trimmed.length > 5000) return message;\n\n  dynamic parsed;\n  try {\n    parsed = jsonDecode(trimmed);

Comment on lines +1 to +2
import 'dart:convert';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import summarizeLogMessage from the newly created utility file log_message_summary.dart instead of duplicating the logic locally.

import 'dart:convert';\n\nimport '../../../../core/utils/log_message_summary.dart';

const SizedBox(height: 6),
Text(
entry.message,
_summarizeLogMessage(entry.message),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the shared summarizeLogMessage utility function instead of the duplicated private helper _summarizeLogMessage.

Suggested change
_summarizeLogMessage(entry.message),
summarizeLogMessage(entry.message),

Comment on lines +1167 to +1198
/// Convert a raw log message into a one-line preview suitable for the
/// list row's `Text`. Special handling for JSON objects/arrays so the
/// user sees something like `Object {3 keys: foo, bar, baz}` instead of
/// just `{}` (the first character of the pretty-printed payload that the
/// SDK sent over the wire).
String _summarizeLogMessage(String message) {
final trimmed = message.trimLeft();
if (trimmed.isEmpty) return message;
if (trimmed[0] != '{' && trimmed[0] != '[') return message;

// Try to parse as JSON — RN's `toStr` (and Flutter's `jsonEncode`) ship
// pretty-printed payloads, so we can't rely on a single line.
dynamic parsed;
try {
parsed = jsonDecode(trimmed);
} catch (_) {
return message; // not valid JSON — show the original text
}

if (parsed is Map) {
final keys = parsed.keys.cast<String>().toList();
if (keys.isEmpty) return 'Object {}';
final preview = keys.take(3).join(', ');
final more = keys.length > 3 ? ', …' : '';
return 'Object {${keys.length} key${keys.length == 1 ? '' : 's'}: $preview$more}';
}
if (parsed is List) {
if (parsed.isEmpty) return 'Array []';
return 'Array [${parsed.length}]';
}
return message;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Remove the duplicated private helper _summarizeLogMessage since the same logic is now available in the shared summarizeLogMessage utility function.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
lib/core/utils/log_message_summary.dart (1)

12-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Logic looks solid; add unit tests for this new utility.

Edge cases (empty map/list, singular vs plural "key(s)", >3 keys truncation, invalid JSON) are handled correctly. Since this is a pure, easily-testable function feeding directly into list-row titles across platforms, it's worth locking down behavior with unit tests before other call sites build on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/core/utils/log_message_summary.dart` around lines 12 - 38, Add unit tests
for summarizeLogMessage in log_message_summary.dart to lock down the new
behavior. Cover empty/whitespace-only input, non-JSON text, valid JSON object
and array summaries, empty map/list, singular versus plural key wording, and
truncation when there are more than three object keys. Use summarizeLogMessage
as the target symbol and verify the returned strings exactly.
lib/features/console/presentation/pages/console_page.dart (3)

1217-1250: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

JSON is re-decoded from scratch on every tab switch.

jsonDecode(widget.message) runs unconditionally in build(), so tapping Tree→JSON→Code triggers setState → full rebuild → full re-parse of the same payload each time. This is exactly the large-JSON-payload scenario this feature targets, so it's worth parsing once and caching.

⚡ Cache the parsed payload
 class _LogMessageBlockState extends State<_LogMessageBlock> {
   /// 0 = Tree, 1 = JSON, 2 = Code.
   int _mode = 0;
+  dynamic _parsed;
+  bool _isJson = false;
+
+  `@override`
+  void initState() {
+    super.initState();
+    _parse();
+  }
+
+  `@override`
+  void didUpdateWidget(covariant _LogMessageBlock oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.message != widget.message) _parse();
+  }
+
+  void _parse() {
+    try {
+      _parsed = jsonDecode(widget.message);
+    } catch (_) {
+      _parsed = null;
+    }
+    _isJson = _parsed is Map || _parsed is List;
+  }

   `@override`
   Widget build(BuildContext context) {
     final isDark = widget.isDark;
-    dynamic parsed;
-    try {
-      parsed = jsonDecode(widget.message);
-    } catch (_) {
-      parsed = null;
-    }
-    final isJson = parsed is Map || parsed is List;
+    final parsed = _parsed;
+    final isJson = _isJson;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/console/presentation/pages/console_page.dart` around lines 1217
- 1250, The parsing logic in ConsolePage’s build method re-runs
jsonDecode(widget.message) on every tab switch, which causes repeated work for
the same payload. Move the JSON parsing out of build and cache the result in
ConsolePage or its state (for example by storing the parsed value keyed to
widget.message), then have the Tree/JSON branches reuse that cached parsed
payload instead of decoding again.

919-946: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_LogDetailPanel/_LogMessageBlock use plain State/StatefulWidget, not ConsumerStatefulWidget.

Both new stateful classes in this presentation-layer file skip the Riverpod-flavored base class used elsewhere in this stack (e.g. DeviceBottomBar/Sidebar were converted to ConsumerStatefulWidget). Even if neither currently reads a provider, keeping the pattern consistent avoids another StatefulWidget→ConsumerStatefulWidget rewrite the next time this panel needs ref.

As per path instructions, lib/features/**/presentation/**: "Follow ConsumerStatefulWidget + Riverpod patterns".

Also applies to: 1202-1214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/console/presentation/pages/console_page.dart` around lines 919 -
946, Convert the new presentation widgets `_LogDetailPanel` and
`_LogMessageBlock` from plain `StatefulWidget`/`State` to the Riverpod-style
`ConsumerStatefulWidget`/`ConsumerState` pattern used elsewhere in this stack.
Update the widget and state class declarations together so they follow the same
convention as `DeviceBottomBar` and `Sidebar`, keeping `ref` available for
future provider reads and avoiding another refactor later.

Source: Path instructions


1200-1391: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

_LogMessageBlock/_DetailTabBar duplicate the All Events detail panel's Tree/JSON/Code toggle.

The comments explicitly note this mirrors the pattern in all_events_page.dart. Consider extracting a shared widget (e.g. MessageModeBlock + DetailTabBar) into a common components location so future tweaks (colors, tab labels, mode logic) land once instead of twice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/console/presentation/pages/console_page.dart` around lines 1200
- 1391, The _LogMessageBlock and _DetailTabBar implementations duplicate the
Tree/JSON/Code toggle already used by the All Events detail panel, so extract
the shared mode-switching UI and tab bar into a common reusable
widget/component. Move the mode state/rendering logic from _LogMessageBlock and
the pill tab rendering from _DetailTabBar into a shared implementation with a
single API so both console_page.dart and the All Events panel use the same
source of truth for labels, colors, and mode behavior.
lib/features/settings/presentation/pages/settings_page.dart (1)

937-948: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Persist duration on drag end, not every tick.

onChanged fires continuously while dragging and calls smoothScrollDurationProvider.notifier.set(...) on each tick, which triggers an AppPreferences().set() file write per event. Consider updating local/ephemeral UI state in onChanged and only persisting via onChangeEnd.

♻️ Proposed fix
                   Slider(
                     value: ref.watch(smoothScrollDurationProvider).toDouble(),
                     min: 100,
                     max: 1000,
                     divisions: 18,
                     label: '${ref.watch(smoothScrollDurationProvider)}ms',
                     activeColor: ColorTokens.primary,
                     inactiveColor: isDark ? Colors.white12 : Colors.black12,
-                    onChanged: (v) => ref
-                        .read(smoothScrollDurationProvider.notifier)
-                        .set(v.round()),
+                    onChanged: (v) => ref
+                        .read(smoothScrollDurationProvider.notifier)
+                        .updateEphemeral(v.round()),
+                    onChangeEnd: (v) => ref
+                        .read(smoothScrollDurationProvider.notifier)
+                        .set(v.round()),
                   ),

Requires adding an updateEphemeral (state-only, no persist) method to SmoothScrollDurationNotifier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/settings/presentation/pages/settings_page.dart` around lines 937
- 948, The Slider in SettingsPage is persisting smooth scroll duration on every
drag tick through smoothScrollDurationProvider.notifier.set(...), causing
repeated AppPreferences writes. Update the SettingsPage Slider to use
ephemeral/local state during onChanged and only commit the value on onChangeEnd,
and add an updateEphemeral method to SmoothScrollDurationNotifier so the UI can
update without persisting until drag end.
lib/components/layout/device_bottom_bar.dart (1)

18-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Repeated scroll-controller boilerplate across ~12 widgets.

The final _scrollController = SmoothScrollController(); … dispose() { _scrollController.dispose(); super.dispose(); } pattern is duplicated near-identically in this file, sidebar.dart, json_viewer.dart (x2), all_events_page.dart (x7), error_inspector_page.dart (x3), network_inspector_page.dart (x3), and state_inspector_page.dart (x2). A small mixin (e.g. SmoothScrollControllerMixin) exposing a ready-to-use, auto-disposed controller would cut this duplication significantly across the whole PR.

♻️ Sketch of a reusable mixin
mixin SmoothScrollControllerMixin<T extends StatefulWidget> on State<T> {
  final scrollController = SmoothScrollController();

  `@override`
  void dispose() {
    scrollController.dispose();
    super.dispose();
  }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/components/layout/device_bottom_bar.dart` around lines 18 - 25, The
scroll controller setup is duplicated across multiple widgets, including
_DeviceBottomBarState and the other pages/components noted in the review.
Extract this repeated SmoothScrollController creation and disposal pattern into
a reusable mixin such as SmoothScrollControllerMixin that owns and disposes the
controller automatically, then update each affected State class to use the
shared mixin instead of defining its own _scrollController and dispose logic.
lib/features/state_inspector/presentation/pages/state_inspector_page.dart (1)

792-812: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same StableListView gap as the diff list in all_events_page.dart.

_DiffView's ListView.builder is being modified here (new _scrollController), but per path instructions this list view should use StableListView instead. Flagging alongside the equivalent case in lib/features/all_events/presentation/pages/all_events_page.dart since it's the same underlying pattern.

As per path instructions, "Use StableListView for list views" for lib/features/**/presentation/**.

Also applies to: 821-821

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/state_inspector/presentation/pages/state_inspector_page.dart`
around lines 792 - 812, _ DiffView in state_inspector_page.dart is introducing a
scroll-controlled ListView where the presentation layer requires StableListView
for list views. Update the diff list inside _DiffViewState to use StableListView
instead of the current ListView.builder pattern, and keep the scroll controller
handling aligned with that widget’s expected usage. Apply the same
StableListView pattern used in the equivalent all_events_page.dart diff list so
the two implementations stay consistent.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/components/layout/device_bottom_bar.dart`:
- Around line 18-25: The horizontal `ListView` in `DeviceBottomBar` still needs
an explicit desktop scroll behavior override because `SmoothScrollController`
does not change the default Shift+wheel handling. Update the `build` path for
`DeviceBottomBar`/`_DeviceBottomBarState` to wrap the horizontal list with a
`ScrollConfiguration` or custom `ScrollBehavior` so mouse-wheel input is handled
as intended on desktop, while keeping the existing `_scrollController` and
disposal logic intact.

In `@lib/features/console/presentation/pages/console_page.dart`:
- Line 854: The console page’s private _summarizeLogMessage helper duplicates
the shared summarizeLogMessage utility, so replace the local copy with calls to
the shared function from lib/core/utils/log_message_summary.dart. Update the
console list and all-events list paths in console_page.dart to reuse that
utility directly, and remove the duplicated doc comments/implementation so the
JSON-preview behavior stays consistent in one place.

In
`@lib/features/network_inspector/presentation/pages/network_inspector_page.dart`:
- Line 2299: The header-key column width is inconsistent between the live
Headers tab and the screenshot renderer. Update `_screenshotHeaderRow`, used by
`_buildFullScreenshotWidget` and `_buildTabScreenshotWidget`, to use the same
key-column width as the live view (170) instead of the hardcoded 180 so
request/response header alignment matches in screenshots.

---

Nitpick comments:
In `@lib/components/layout/device_bottom_bar.dart`:
- Around line 18-25: The scroll controller setup is duplicated across multiple
widgets, including _DeviceBottomBarState and the other pages/components noted in
the review. Extract this repeated SmoothScrollController creation and disposal
pattern into a reusable mixin such as SmoothScrollControllerMixin that owns and
disposes the controller automatically, then update each affected State class to
use the shared mixin instead of defining its own _scrollController and dispose
logic.

In `@lib/core/utils/log_message_summary.dart`:
- Around line 12-38: Add unit tests for summarizeLogMessage in
log_message_summary.dart to lock down the new behavior. Cover
empty/whitespace-only input, non-JSON text, valid JSON object and array
summaries, empty map/list, singular versus plural key wording, and truncation
when there are more than three object keys. Use summarizeLogMessage as the
target symbol and verify the returned strings exactly.

In `@lib/features/console/presentation/pages/console_page.dart`:
- Around line 1217-1250: The parsing logic in ConsolePage’s build method re-runs
jsonDecode(widget.message) on every tab switch, which causes repeated work for
the same payload. Move the JSON parsing out of build and cache the result in
ConsolePage or its state (for example by storing the parsed value keyed to
widget.message), then have the Tree/JSON branches reuse that cached parsed
payload instead of decoding again.
- Around line 919-946: Convert the new presentation widgets `_LogDetailPanel`
and `_LogMessageBlock` from plain `StatefulWidget`/`State` to the Riverpod-style
`ConsumerStatefulWidget`/`ConsumerState` pattern used elsewhere in this stack.
Update the widget and state class declarations together so they follow the same
convention as `DeviceBottomBar` and `Sidebar`, keeping `ref` available for
future provider reads and avoiding another refactor later.
- Around line 1200-1391: The _LogMessageBlock and _DetailTabBar implementations
duplicate the Tree/JSON/Code toggle already used by the All Events detail panel,
so extract the shared mode-switching UI and tab bar into a common reusable
widget/component. Move the mode state/rendering logic from _LogMessageBlock and
the pill tab rendering from _DetailTabBar into a shared implementation with a
single API so both console_page.dart and the All Events panel use the same
source of truth for labels, colors, and mode behavior.

In `@lib/features/settings/presentation/pages/settings_page.dart`:
- Around line 937-948: The Slider in SettingsPage is persisting smooth scroll
duration on every drag tick through
smoothScrollDurationProvider.notifier.set(...), causing repeated AppPreferences
writes. Update the SettingsPage Slider to use ephemeral/local state during
onChanged and only commit the value on onChangeEnd, and add an updateEphemeral
method to SmoothScrollDurationNotifier so the UI can update without persisting
until drag end.

In `@lib/features/state_inspector/presentation/pages/state_inspector_page.dart`:
- Around line 792-812: _ DiffView in state_inspector_page.dart is introducing a
scroll-controlled ListView where the presentation layer requires StableListView
for list views. Update the diff list inside _DiffViewState to use StableListView
instead of the current ListView.builder pattern, and keep the scroll controller
handling aligned with that widget’s expected usage. Apply the same
StableListView pattern used in the equivalent all_events_page.dart diff list so
the two implementations stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ba153a3-848b-44c9-8c40-ecec0dc1ded2

📥 Commits

Reviewing files that changed from the base of the PR and between ef7412d and fcca61b.

📒 Files selected for processing (28)
  • lib/components/layout/device_bottom_bar.dart
  • lib/components/layout/sidebar.dart
  • lib/components/text/text_component.dart
  • lib/components/viewers/json_viewer.dart
  • lib/core/theme/theme_provider.dart
  • lib/core/utils/log_message_summary.dart
  • lib/core/utils/smooth_scroll_controller.dart
  • lib/features/all_events/presentation/pages/all_events_page.dart
  • lib/features/all_events/provider/all_events_provider.dart
  • lib/features/console/presentation/pages/console_page.dart
  • lib/features/error_inspector/presentation/pages/error_inspector_page.dart
  • lib/features/network_inspector/presentation/pages/network_inspector_page.dart
  • lib/features/settings/presentation/pages/settings_page.dart
  • lib/features/state_inspector/presentation/pages/state_inspector_page.dart
  • lib/features/storage_viewer/presentation/pages/storage_viewer_page.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_ja.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_ja.dart
  • lib/l10n/app_localizations_vi.dart
  • lib/l10n/app_localizations_zh.dart
  • lib/l10n/app_vi.arb
  • lib/l10n/app_zh.arb
  • lib/l10n/app_zh_CN.arb
  • lib/l10n/app_zh_TW.arb

Comment on lines +18 to +25
class _DeviceBottomBarState extends ConsumerState<DeviceBottomBar> {
final _scrollController = SmoothScrollController();

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Flutter's default ScrollBehavior route unmodified vertical mouse wheel scrollDelta to horizontal-only ListViews, or does it require Shift by default?

💡 Result:

Yes, Flutter's default ScrollBehavior requires the Shift key to be held down when using a vertical mouse wheel to scroll a horizontal ListView [1][2]. This behavior is governed by the pointerAxisModifiers property in the ScrollBehavior class, which defaults to including LogicalKeyboardKey.shiftLeft and LogicalKeyboardKey.shiftRight [1][3]. When these keys are pressed in combination with a mouse scroll event, Flutter flips the scroll axes, allowing vertical wheel input to move a horizontal ScrollView [1][3]. This is considered the standard desktop convention for horizontal scrolling [2]. If you wish to modify this behavior, you can override the ScrollBehavior in your app (e.g., via ScrollConfiguration or within your MaterialApp/CupertinoApp) to customize the pointerAxisModifiers set [1][4][5]. However, simply clearing the set does not automatically cause a horizontal list to scroll with a vertical wheel; it effectively disables the axis-flipping mechanism [4][6]. Top results: [1], [2], [5]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and nearby scroll-related code.
git ls-files 'lib/components/layout/device_bottom_bar.dart' 'lib/**' | sed -n '1,120p'

echo
echo "== device_bottom_bar.dart =="
wc -l lib/components/layout/device_bottom_bar.dart
cat -n lib/components/layout/device_bottom_bar.dart | sed -n '1,220p'

echo
echo "== Search for SmoothScrollController usage =="
rg -n "SmoothScrollController|pointerScroll|ScrollBehavior|ScrollConfiguration|ListView\.separated|Axis\.horizontal" lib -S

Repository: ridelinktechs/devconnect-manage-kit

Length of output: 17929


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If the smooth-scrolling controller comes from a package or local helper, inspect its API shape.
rg -n "class SmoothScrollController|extends ScrollController|pointerScroll" . -S

Repository: ridelinktechs/devconnect-manage-kit

Length of output: 450


🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l lib/core/utils/smooth_scroll_controller.dart
cat -n lib/core/utils/smooth_scroll_controller.dart | sed -n '1,220p'

Repository: ridelinktechs/devconnect-manage-kit

Length of output: 4391


Horizontal wheel scrolling still needs a behavior override lib/components/layout/device_bottom_bar.dart:80-82 — Flutter keeps horizontal ListView wheel scrolling on Shift+wheel by default, so SmoothScrollController won’t change plain mouse-wheel input here. Add a ScrollBehavior/ScrollConfiguration if desktop wheel scrolling is expected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/components/layout/device_bottom_bar.dart` around lines 18 - 25, The
horizontal `ListView` in `DeviceBottomBar` still needs an explicit desktop
scroll behavior override because `SmoothScrollController` does not change the
default Shift+wheel handling. Update the `build` path for
`DeviceBottomBar`/`_DeviceBottomBarState` to wrap the horizontal list with a
`ScrollConfiguration` or custom `ScrollBehavior` so mouse-wheel input is handled
as intended on desktop, while keeping the existing `_scrollController` and
disposal logic intact.

Comment thread lib/features/console/presentation/pages/console_page.dart Outdated
children: [
SizedBox(
width: 180,
width: 170,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Header-key column width now mismatches the screenshot renderer.

This narrows the key column to 170 for the live Headers tab, but _screenshotHeaderRow (used by _buildFullScreenshotWidget/_buildTabScreenshotWidget) still hardcodes width: 180 for the same header data. Screenshots will now render request/response headers with a visibly different key-column alignment than what the user sees on screen.

🖼️ Suggested alignment fix
   Widget _screenshotHeaderRow(String key, String value, bool isDark) {
     return Padding(
       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
       child: Row(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
           SizedBox(
-            width: 180,
+            width: 170,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/features/network_inspector/presentation/pages/network_inspector_page.dart`
at line 2299, The header-key column width is inconsistent between the live
Headers tab and the screenshot renderer. Update `_screenshotHeaderRow`, used by
`_buildFullScreenshotWidget` and `_buildTabScreenshotWidget`, to use the same
key-column width as the live view (170) instead of the hardcoded 180 so
request/response header alignment matches in screenshots.

…event UI jank, while optimizing SelectableText height rendering
@buivietphi
buivietphi merged commit bf26697 into main Jul 2, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant