Skip to content

Conversation

@lollipopkit
Copy link
Owner

@lollipopkit lollipopkit commented Aug 13, 2025

Summary by Sourcery

Add scope filter functionality to the systemd page, refactor unit fetching and parsing logic in the provider, improve UI loading indicators and SSH prompt context handling, tighten GitHub workflow triggers, and bump fl_lib dependency.

New Features:

  • Introduce scope filter chips on the systemd page to select All, System, or User units
  • Expose a filteredUnits getter in SystemdProvider to apply the selected scope filter

Enhancements:

  • Refactor SystemdProvider to unify system and user unit parsing, skip empty segments, and switch warning logs to dprint
  • Improve the loading spinner layout by wrapping it in a square SizedBox and update animated container dimensions
  • Enhance SSHClient extension to resolve context from focus manager when prompting for a password

CI:

  • Restrict Claude GitHub actions to runs triggered by a specific actor

Chores:

  • Bump fl_lib dependency reference from v1.0.327 to v1.0.329

@sourcery-ai
Copy link

sourcery-ai bot commented Aug 13, 2025

Reviewer's Guide

This PR enhances the Systemd page by adding scope-based filtering to the UI and provider, refactors unit parsing and logging logic in the SystemdProvider, makes targeted UI layout fixes (spinner sizing, server tab loading), tightens CI workflow filters for the Claude bot, and updates package versions.

Class diagram for SystemdProvider and SystemdScopeFilter changes

classDiagram
  class SystemdProvider {
    +VNode<Server> _si
    +VNode<bool> isBusy
    +VNode<List<SystemdUnit>> units
    +VNode<SystemdScopeFilter> scopeFilter
    +List<SystemdUnit> filteredUnits
    +void dispose()
    +Future<void> getUnits()
    +Future<List<SystemdUnit>> _parseUnitObj(List<String>, SystemdUnitScope)
  }
  class SystemdScopeFilter {
    +all
    +system
    +user
    +String displayName
  }
  SystemdProvider --> SystemdScopeFilter
  SystemdProvider --> SystemdUnit
Loading

File-Level Changes

Change Details Files
Scope-based filtering for systemd units
  • Added SystemdScopeFilter enum with displayName
  • Introduced scopeFilter VNode and filteredUnits getter in provider
  • Extracted _buildScopeFilterChips and integrated filter into _buildUnitList
  • Adjusted spinner container to set both height and width
lib/view/page/systemd.dart
lib/data/provider/systemd.dart
lib/data/model/server/systemd.dart
Refactor unit parsing and classification in SystemdProvider
  • Switched path checks from startsWith to contains for system/user detection
  • Streamlined shell script separator logic and commented out error-prone paths
  • Improved _parseUnitObj to skip empty lines and replaced Loggers.app calls with dprint
lib/data/provider/systemd.dart
Tighten CI workflow triggers for Claude bot
  • Added github.actor filter in claude.yml
  • Restricted claude-review job to specific PR author in claude-code-review.yml
.github/workflows/claude.yml
.github/workflows/claude-code-review.yml
Improve SSHClient password dialog context resolution
  • Fallback to primaryFocus context if direct context is null
  • Ensure context is mounted before showing password dialog
lib/core/extension/ssh_client.dart
Server tab loading indicator layout fix
  • Wrapped SizedLoading in SizedBox.square for consistent dimensions
  • Adjusted padding for loading component
lib/view/page/server/tab/content.dart
Clean up debug prints and bump dependency
  • Commented out unnecessary dprint in ServerProvider
  • Updated fl_lib reference from v1.0.327 to v1.0.329
lib/data/provider/server.dart
pubspec.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@lollipopkit lollipopkit changed the title opt.: systemd page Fixes #848 opt.: systemd page Aug 13, 2025
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @lollipopkit - I've reviewed your changes - here's some feedback:

  • Consider setting isBusy.value = true at the beginning of getUnits to ensure the loading spinner appears while units are being fetched.
  • In _buildUnitList you can drop the outer listenVal on units (allUnits) since it’s not used and simplify by listening only to scopeFilter or filteredUnits.
  • Instead of nesting your filter chips and spinner inside a single SliverToBoxAdapter Column, consider using separate slivers (e.g., a SliverPadding or SliverToBoxAdapter per widget) for better scroll performance.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider setting isBusy.value = true at the beginning of getUnits to ensure the loading spinner appears while units are being fetched.
- In _buildUnitList you can drop the outer listenVal on units (allUnits) since it’s not used and simplify by listening only to scopeFilter or filteredUnits.
- Instead of nesting your filter chips and spinner inside a single SliverToBoxAdapter Column, consider using separate slivers (e.g., a SliverPadding or SliverToBoxAdapter per widget) for better scroll performance.

## Individual Comments

### Comment 1
<location> `lib/data/provider/systemd.dart:49` </location>
<code_context>
       final systemUnits = <String>[];
       for (final unit in units) {
-        if (unit.startsWith('/etc/systemd/system')) {
+        final maybeSystem = unit.contains('/systemd/system');
+        final maybeUser = unit.contains('/.config/systemd/user');
+        if (maybeSystem && !maybeUser) {
           systemUnits.add(unit);
-        } else if (unit.startsWith('~/.config/systemd/user')) {
+        } else {
           userUnits.add(unit);
-        } else if (unit.trim().isNotEmpty) {
</code_context>

<issue_to_address>
The logic for classifying system/user units may misclassify units with overlapping paths.

Using contains for path matching can lead to false positives if both substrings appear or if the path format changes. More precise matching, such as startsWith or regular expressions, is recommended.
</issue_to_address>

### Comment 2
<location> `lib/data/provider/systemd.dart:83` </location>
<code_context>
     final parsedUnits = <SystemdUnit>[];
-    for (final unit in units) {
-      final parts = unit.split('\n');
+    for (final unit in units.skipWhile((e) => e.trim().isEmpty)) {
+      final parts = unit.split('\n').skipWhile((e) => e.trim().isEmpty);
+      if (parts.isEmpty) continue;
       var name = '';
       var type = '';
</code_context>

<issue_to_address>
Skipping empty units and lines may inadvertently skip valid but empty entries.

Using skipWhile only removes leading empty entries. To exclude all empty entries, use where or another filtering method.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    for (final unit in units.skipWhile((e) => e.trim().isEmpty)) {
      final parts = unit.split('\n').skipWhile((e) => e.trim().isEmpty);
      if (parts.isEmpty) continue;
      var name = '';
      var type = '';
      var state = '';
=======
    for (final unit in units.where((e) => e.trim().isNotEmpty)) {
      final parts = unit
          .split('\n')
          .where((e) => e.trim().isNotEmpty)
          .toList();
      if (parts.isEmpty) continue;
      var name = '';
      var type = '';
      var state = '';
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `lib/data/provider/systemd.dart:158` </location>
<code_context>

 String _getIniVal(String line) {
-  return line.split('=').last;
+  final idx = line.indexOf('=');
+  if (idx < 0) return line;
+  return line.substring(idx + 1).trim();
 }
</code_context>

<issue_to_address>
The _getIniVal function may return the entire line if '=' is not found.

Consider returning an empty string or null when '=' is missing, or handle this case explicitly at the call site to avoid propagating unintended values.
</issue_to_address>

### Comment 4
<location> `lib/view/page/systemd.dart:80` </location>
<code_context>
+  Widget _buildUnitList() {
</code_context>

<issue_to_address>
Nesting listenVal calls may lead to unnecessary rebuilds.

Consider refactoring _buildUnitList to use a single listenVal with a derived value, reducing redundant rebuilds and improving performance.

Suggested implementation:

```
  Widget _buildUnitList() {
    return listenVal2(_pro.units, _pro.scopeFilter, (allUnits, filter) {
      final filteredUnits = _pro.getFilteredUnits(allUnits, filter);
      if (filteredUnits.isEmpty) {
        return SliverToBoxAdapter(child: CenterGreyTitle(libL10n.empty).paddingSymmetric(horizontal: 13));
      }
      return SliverList(
        delegate: SliverChildBuilderDelegate((context, index) {
          final unit = filteredUnits[index];
          return ListTile(

```

You will need to implement the `listenVal2` function if it does not already exist. It should take two listenable values and a callback, and rebuild when either changes.

Also, replace `_pro.filteredUnits` with a method like `_pro.getFilteredUnits(allUnits, filter)` that computes the filtered units from the provided values, rather than relying on possibly stale state.

If you have a utility for combining listenables, use that instead of `listenVal2`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

);
}).toList(),
).paddingSymmetric(horizontal: 13, vertical: 8);
});
Copy link

Choose a reason for hiding this comment

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

suggestion (performance): Nesting listenVal calls may lead to unnecessary rebuilds.

Consider refactoring _buildUnitList to use a single listenVal with a derived value, reducing redundant rebuilds and improving performance.

Suggested implementation:

  Widget _buildUnitList() {
    return listenVal2(_pro.units, _pro.scopeFilter, (allUnits, filter) {
      final filteredUnits = _pro.getFilteredUnits(allUnits, filter);
      if (filteredUnits.isEmpty) {
        return SliverToBoxAdapter(child: CenterGreyTitle(libL10n.empty).paddingSymmetric(horizontal: 13));
      }
      return SliverList(
        delegate: SliverChildBuilderDelegate((context, index) {
          final unit = filteredUnits[index];
          return ListTile(

You will need to implement the listenVal2 function if it does not already exist. It should take two listenable values and a callback, and rebuild when either changes.

Also, replace _pro.filteredUnits with a method like _pro.getFilteredUnits(allUnits, filter) that computes the filtered units from the provided values, rather than relying on possibly stale state.

If you have a utility for combining listenables, use that instead of listenVal2.

@lollipopkit lollipopkit merged commit ed8a1d1 into main Aug 13, 2025
1 of 2 checks passed
@lollipopkit lollipopkit deleted the lollipopkit/issue848 branch August 13, 2025 14:17
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.

2 participants