Skip to content

Conversation

@AustinChangLinksys
Copy link
Collaborator

@AustinChangLinksys AustinChangLinksys commented Jan 16, 2026

User description

Summary

This PR introduces a custom dashboard layout feature with the following capabilities:

  • Custom Layout Mode: Users can toggle between standard and custom dashboard layouts
  • Drag & Drop: Widgets can be repositioned via drag and drop
  • Resizable Widgets: Widgets can be resized within their defined constraints
  • Widget Display Modes: Support for Compact, Normal, and Expanded display modes
  • Edit Mode: Dedicated edit mode with save/cancel functionality
  • Layout Persistence: Custom layouts are saved and restored across sessions

Key Components

  • Fixed-layout widget system with isolated UI components
  • Dashboard widget specifications with size constraints
  • Layout item factory for default configurations
  • Quick Panel with beta features indicator

Testing

  • Manual testing recommended for drag/drop and resize interactions
  • Golden tests for visual regression

PR Type

Enhancement


Description

  • Introduces a comprehensive custom dashboard layout system with drag-and-drop and resizable widgets

  • Implements atomic widget components (CustomSpeedTest, CustomMasterNodeInfo, CustomWifiGrid, CustomQuickPanel, Ports, NetworkStats, InternetStatusOnly, Topology) for flexible dashboard composition

  • Adds SliverDashboardView with edit mode featuring toolbar controls, constraint enforcement, display mode switching, and layout persistence

  • Expands widget specifications with strict sizing strategy, atomic widget specs, and layout variations (horizontal, vertical, no-LAN)

  • Creates comprehensive theme studio with tabbed interface (Design, Palette, Status, Components, Topology) supporting import/export functionality

  • Implements DemoThemeConfig provider for dynamic theme configuration with JSON serialization

  • Refactors dashboard home view to support both custom (Sliver-based) and standard layouts with dynamic constraint updates

  • Adds fixed-layout widget components (FixedInternetConnectionWidget, FixedQuickPanel, FixedWifiGrid, FixedNetworks, FixedPortAndSpeed) for standard layout

  • Introduces SliverDashboardController for layout persistence using SharedPreferences with add/remove/update operations

  • Implements LayoutItemFactory using IoC pattern for converting widget specs to layout items

  • Adds GridLayoutResolver with height calculation enhancements for responsive grid systems

  • Includes comprehensive unit and integration tests for widget constraints, layout management, and theme configuration

  • Enhances speed test widget with compact display mode and result summary controls

  • Adds WiFi card display modes (compact/expanded) with device preview and QR code sharing


Diagram Walkthrough

flowchart LR
  A["Dashboard Home View"] -->|"supports both"| B["Standard Layout"]
  A -->|"supports both"| C["Custom Sliver Layout"]
  B -->|"uses"| D["Fixed Layout Widgets"]
  C -->|"uses"| E["Atomic Widgets"]
  C -->|"managed by"| F["Sliver Dashboard Controller"]
  F -->|"persists to"| G["SharedPreferences"]
  E -->|"specs from"| H["Dashboard Widget Specs"]
  D -->|"specs from"| H
  H -->|"converted by"| I["Layout Item Factory"]
  I -->|"creates"| J["Layout Items"]
  C -->|"renders"| J
  K["Theme Studio Panel"] -->|"configures"| L["Demo Theme Config"]
  L -->|"applies to"| A
  M["Edit Mode"] -->|"enables"| N["Drag Drop Resize"]
  N -->|"updates"| F
Loading

File Walkthrough

Relevant files
Enhancement
30 files
dashboard_widget_specs.dart
Expand widget specs with atomic components and strict sizing

lib/page/dashboard/models/dashboard_widget_specs.dart

  • Translated Chinese comments to English for better internationalization
  • Replaced HeightStrategy.intrinsic() with HeightStrategy.strict() for
    deterministic widget sizing
  • Added description and canHide fields to widget specifications
  • Introduced atomic widget specs (internetStatusOnly, masterNodeInfo,
    ports, speedTest, networkStats, topology) for custom/bento layouts
  • Added dynamic getPortsSpec() method with layout variations
    (horizontal, vertical, no-LAN)
  • Created separate customWidgets list and custom layout variants
    (wifiGridCustom, quickPanelCustom, vpnCustom)
  • Renamed all list to standardWidgets and created comprehensive all list
    combining both standard and atomic widgets
+454/-29
demo_theme_config_provider.dart
Add demo theme configuration provider with import/export 

lib/demo/providers/demo_theme_config_provider.dart

  • Created new provider for dynamic theme configuration in demo mode
  • Implemented DemoThemeConfig class with support for style, overlay,
    visual effects, seed colors, and granular Material colors
  • Added copyWith() method for immutable state updates with clear flags
  • Implemented JSON serialization/deserialization for theme import/export
    functionality
  • Created DemoThemeConfigNotifier with methods for updating semantic
    overrides and component-specific colors (loader, skeleton, toggle,
    toast, topology)
  • Provided helper methods for managing visual effects flags and color
    state
+597/-0 
speed_test.dart
Implement atomic speed test widget with display modes       

lib/page/dashboard/views/components/widgets/atomic/speed_test.dart

  • Created new atomic CustomSpeedTest widget for custom layout displaying
    speed test results
  • Implemented three display modes: compact (minimal stats), normal
    (meter view), expanded (detailed with history chart)
  • Added custom history chart painter with download/upload trend
    visualization
  • Integrated health check provider for speed test state and execution
  • Supported both internal speed test (with LAN) and external links
    (remote/legacy)
  • Included status tracking, big stats panel, and detailed information
    display
+593/-0 
sliver_dashboard_view.dart
Add sliver-based drag-drop dashboard with edit mode           

lib/page/dashboard/views/sliver_dashboard_view.dart

  • Created new SliverDashboardView for drag-and-drop dashboard using
    sliver_dashboard package
  • Implemented edit mode with toolbar containing optimize, settings,
    cancel, and save buttons
  • Added constraint enforcement in _handleResizeEnd() to respect min/max
    width and height ranges
  • Integrated display mode switching via popup menu with visual mode
    indicators
  • Implemented widget removal with canHide property check
  • Added layout persistence with snapshot/restore functionality for
    cancel operations
  • Included jiggle animation effect for widgets in edit mode
+554/-0 
master_node_info.dart
Implement atomic master node info widget with stats           

lib/page/dashboard/views/components/widgets/atomic/master_node_info.dart

  • Created new atomic CustomMasterNodeInfo widget displaying master
    router information
  • Implemented three display modes: compact (image + location), normal
    (standard details), expanded (large image with unified info sections)
  • Added system stats section with CPU and memory utilization bars
  • Integrated uptime display and connection status indicators
  • Included firmware update status widget and navigation to node details
  • Implemented info sections with label-value pairs in unified styling
+507/-0 
internet_status.dart
Add fixed internet connection status widget                           

lib/page/dashboard/views/components/fixed_layout/internet_status.dart

  • Created new FixedInternetConnectionWidget for displaying internet
    connection status
  • Implemented three display modes: compact (status + IP), normal (full
    display with router info), expanded (normal + uptime)
  • Added useAppCard parameter to optionally wrap content in AppCard
  • Integrated geolocation display and refresh functionality
  • Included master router information with connection type and firmware
    status
  • Added uptime formatting and display for expanded mode
+491/-0 
quick_panel.dart
Add optional card wrapping to quick panel widget                 

lib/page/dashboard/views/components/widgets/composite/quick_panel.dart

  • Added useAppCard parameter to control whether content wraps in AppCard
    (default true)
  • Refactored compact and normal views to extract content separately from
    card wrapping
  • Updated expanded view to use SingleChildScrollView with conditional
    card wrapping
  • Maintained all existing toggle functionality for instant privacy and
    night mode
  • Improved layout flexibility for integration with custom dashboard
    layouts
+187/-150
internet_status.dart
Internet Connection Status Widget with Multi-Mode Display

lib/page/dashboard/views/components/widgets/composite/internet_status.dart

  • New composite widget displaying internet connection status with three
    display modes (compact, normal, expanded)
  • Supports toggling between online/offline states with geolocation
    information
  • Includes refresh button for non-mobile platforms and uptime display in
    expanded mode
  • Integrates with dashboard loading wrapper and multiple providers for
    real-time data
+491/-0 
quick_panel.dart
Fixed Dashboard Quick Panel with Display Mode Support       

lib/page/dashboard/views/components/fixed_layout/quick_panel.dart

  • New fixed layout quick panel widget with three display modes (compact,
    normal, expanded)
  • Provides quick toggles for Instant Privacy and Night Mode features
    with BETA badge
  • Compact mode shows icon-only toggles, normal mode shows standard list,
    expanded mode shows full descriptions
  • Integrates with instant privacy and node light settings providers
+451/-0 
network_stats.dart
Network Statistics Widget with Chart Visualization             

lib/page/dashboard/views/components/widgets/atomic/network_stats.dart

  • New atomic widget displaying network statistics (nodes and devices
    count) for custom layout
  • Supports three display modes with visual blocks and donut charts in
    expanded mode
  • Includes legend items and detailed statistics breakdown by connection
    type
  • Provides custom painter for donut chart visualization
+466/-0 
networks.dart
Fixed Dashboard Networks Widget with Topology Display       

lib/page/dashboard/views/components/fixed_layout/networks.dart

  • New fixed layout networks widget showing topology and node information
    with three display modes
  • Compact mode displays node/device counts, normal mode shows topology
    tree, expanded mode shows full details
  • Integrates topology adapter and firmware update status indicators
  • Provides unified network header with layout variant detection
+432/-0 
dashboard_layout_settings_panel.dart
Simplified Dashboard Layout Settings Panel                             

lib/page/dashboard/views/components/settings/dashboard_layout_settings_panel.dart

  • Simplified settings panel to focus on custom layout toggle and reset
    functionality
  • Removed individual widget configuration controls (reorder, visibility,
    mode, width)
  • Added hidden widgets section to allow re-adding removed widgets
  • Integrated with dashboard controller for layout management and edit
    mode control
+159/-251
topology_tab.dart
Topology Theme Studio Tab with Live Preview                           

lib/demo/theme_studio/tabs/topology_tab.dart

  • New topology tab for theme studio demo with live preview of topology
    visualization
  • Provides controls for link animations, colors, and node renderer types
  • Includes compact color picker for customizing ethernet, WiFi, and node
    colors
  • Supports configuration of signal quality colors and animation types
+400/-0 
custom_quick_panel.dart
Custom Quick Panel Widget for Bento Grid Layout                   

lib/page/dashboard/views/components/widgets/atomic/custom_quick_panel.dart

  • New atomic quick panel widget for custom layout (Bento Grid) with
    three display modes
  • Supports compact icon-only toggles, normal toggle list, and expanded
    toggles with descriptions
  • Includes privacy and night mode toggles with BETA badge and helper
    methods
  • Extends DisplayModeConsumerStatefulWidget for consistent display mode
    handling
+380/-0 
wifi_card.dart
WiFi Card Widget with Compact and Expanded Modes                 

lib/page/dashboard/views/components/widgets/parts/wifi_card.dart

  • Added compact and expanded display modes to WiFi card widget
  • Compact mode shows minimal WiFi info with toggle in a single row
  • Expanded mode displays SSID, password controls, and connected device
    preview with avatars
  • Added password visibility toggle, copy, and QR code share
    functionality
  • Integrated device manager provider to show connected devices with
    icons
+344/-2 
port_and_speed.dart
Port and Speed Test Dashboard Widget                                         

lib/page/dashboard/views/components/fixed_layout/port_and_speed.dart

  • New fixed layout widget for port connections and speed test results
    with three display modes
  • Compact mode shows port status icons only, normal mode includes speed
    test, expanded mode shows detailed info
  • Implements IoC pattern with configuration provided by parent strategy
  • Supports both vertical and horizontal layout directions based on
    available space
+370/-0 
grid_layout_resolver.dart
Grid Layout Resolver Height Calculation Enhancement           

lib/page/dashboard/strategies/grid_layout_resolver.dart

  • Added new method resolveGridMainAxisCellCount() to calculate grid main
    axis cell count
  • Supports different height strategies (intrinsic, column-based, aspect
    ratio)
  • Returns null for intrinsic sizing or calculated height units based on
    strategy type
+29/-0   
custom_wifi_grid.dart
Atomic WiFi Grid Widget for Custom Dashboard Layout           

lib/page/dashboard/views/components/widgets/atomic/custom_wifi_grid.dart

  • New atomic WiFi grid widget supporting three display modes (Compact,
    Normal, Expanded)
  • Implements drag-drop compatible layout with responsive grid
    configurations
  • Handles WiFi toggle functionality with confirmation dialogs and error
    handling
  • Manages tooltip visibility state for QR code sharing across multiple
    items
+328/-0 
wifi_card.dart
WiFi Card Component with Toggle and Share Features             

lib/page/dashboard/views/components/fixed_layout/wifi_card.dart

  • Reusable WiFi card component displaying network info, toggle, and QR
    code sharing
  • Supports compact and normal card layouts with responsive design
  • Implements WiFi enable/disable with guest network warnings
  • Provides QR code generation and download functionality
+333/-0 
internet_status_only.dart
Atomic Internet Status Widget with Animations                       

lib/page/dashboard/views/components/widgets/atomic/internet_status_only.dart

  • New atomic internet status widget with animated data flow
    visualization
  • Three display modes: Compact (minimal), Normal (with geolocation),
    Expanded (animated)
  • Features animated connection dots and offline state indicators
  • Includes refresh button with polling integration
+350/-0 
sliver_dashboard_controller_provider.dart
Sliver Dashboard Controller Provider for Layout Management

lib/page/dashboard/providers/sliver_dashboard_controller_provider.dart

  • Provider managing drag-drop grid layout via DashboardController
  • Implements layout persistence using SharedPreferences with JSON
    serialization
  • Supports dynamic constraint resolution following IoC pattern for
    hardware-dependent specs
  • Provides methods for adding, removing, and updating widget constraints
+295/-0 
components_tab.dart
Theme Studio Components Configuration Tab                               

lib/demo/theme_studio/tabs/components_tab.dart

  • New theme studio tab for configuring component styles (Loader,
    Skeleton, Toggle, Toast)
  • Provides interactive color pickers and type selectors for each
    component
  • Displays live previews of component styling changes
  • Integrates with demoThemeConfigProvider for state management
+318/-0 
ports.dart
Atomic Port Status Widget with Adaptive Layout                     

lib/page/dashboard/views/components/widgets/atomic/ports.dart

  • New atomic port status widget displaying LAN and WAN connections
  • Adaptive layout (horizontal/vertical) based on container aspect ratio
    and device config
  • Three display modes: Compact (icons), Normal (adaptive), Expanded
    (detailed)
  • Handles dynamic port configuration with scrollable overflow handling
+261/-0 
speed_test_widget.dart
Speed Test Widget Enhancements for Compact Display             

lib/page/health_check/widgets/speed_test_widget.dart

  • Added showResultSummary parameter to control result display visibility
  • Enhanced meter display with responsive marker reduction for small
    sizes
  • Implemented compact result summary with download/upload icons and
    retry button
  • Improved text sizing and layout for small meter configurations
+151/-36
layout_item_factory.dart
Layout Item Factory for Dashboard Widget Specifications   

lib/page/dashboard/providers/layout_item_factory.dart

  • Factory for converting WidgetSpec constraints to LayoutItem format
  • Implements IoC pattern with optional WidgetSpecResolver for dynamic
    specs
  • Creates default dashboard layout with smart widget positioning
    algorithm
  • Handles constraint mapping from UI Kit specs to sliver_dashboard
    format
+209/-0 
wifi_grid.dart
Fixed Layout WiFi Grid Component                                                 

lib/page/dashboard/views/components/fixed_layout/wifi_grid.dart

  • Fixed-layout WiFi grid component supporting three display modes
  • Implements responsive grid with adaptive column counts based on device
    layout
  • Manages tooltip visibility state for QR code sharing
  • Provides loading state wrapper with calculated height
+200/-0 
theme_studio_panel.dart
Theme Studio Settings Panel with Configuration Management

lib/demo/theme_studio/theme_studio_panel.dart

  • New theme studio settings panel with tabbed interface (Design,
    Palette, Status, Components, Topology)
  • Implements import/export functionality for theme configuration via
    JSON
  • Provides reset-to-defaults capability with clipboard integration
  • Manages tab navigation and scrollable content layout
+248/-0 
dashboard_home_view.dart
Dashboard Home View Layout Strategy Refactoring                   

lib/page/dashboard/views/dashboard_home_view.dart

  • Refactored to support both custom (Sliver) and standard dashboard
    layouts
  • Added dynamic constraint updates for Ports widget based on hardware
    state
  • Replaced display mode logic with fixed Normal mode for standard layout
  • Integrated SliverDashboardView for custom layout rendering
+76/-41 
topology.dart
Atomic Topology Widget with Mesh Visualization                     

lib/page/dashboard/views/components/widgets/atomic/topology.dart

  • New atomic topology widget displaying mesh network structure
  • Three display modes: Compact (summary), Normal (tree view), Expanded
    (graph view)
  • Implements node tap callbacks with device count badges
  • Supports both tree and graph visualization modes with animation
    control
+209/-0 
demo_overrides.dart
Demo Provider Overrides for Router Integration                     

lib/demo/providers/demo_overrides.dart

  • Added router provider override using demoRouterProvider
  • Wraps router with ShellRoute for theme panel overlay integration
  • Maintains existing demo provider overrides for polling and geolocation
+5/-0     
Tests
2 files
widget_grid_constraints_test.dart
Widget Grid Constraints Unit Tests                                             

test/page/dashboard/models/widget_grid_constraints_test.dart

  • Comprehensive unit tests for WidgetGridConstraints model validation
  • Tests column scaling logic for responsive grid systems (12-column
    base)
  • Validates height strategy calculations (Strict, AspectRatio,
    Intrinsic)
  • Verifies equality and hash code implementations
+208/-0 
sliver_dashboard_controller_test.dart
Sliver Dashboard Controller Integration Tests                       

test/page/dashboard/providers/sliver_dashboard_controller_test.dart

  • Integration tests for SliverDashboardControllerNotifier layout
    management
  • Tests initialization from saved preferences and default layout
    creation
  • Validates widget add/remove operations with persistence verification
  • Tests constraint updates with width clamping and layout reset
    functionality
+181/-0 
Additional files
57 files
manual-testing.md +169/-0 
service-decoupling-audit.md +193/-0 
deploy-demo.yml +6/-0     
launch.json +3/-1     
dashboard_custom_layout_comprehensive_report_en.md +799/-0 
build_config.dart +2/-0     
demo_app.dart +43/-8   
demo_router_provider.dart +85/-0   
demo_ui_provider.dart +31/-0   
design_tab.dart +107/-0 
palette_tab.dart +167/-0 
status_tab.dart +99/-0   
theme_studio_fab.dart +23/-0   
color_circle.dart +57/-0   
color_picker_dialog.dart +97/-0   
compact_color_picker.dart +47/-0   
section_header.dart +17/-0   
simple_color_picker.dart +188/-0 
demo_theme_settings_fab.dart +0/-353 
app_en.arb +29/-0   
dashboard_widget_factory.dart +78/-0   
dashboard_layout_preferences.dart +52/-1   
display_mode.dart +5/-5     
height_strategy.dart +19/-13 
widget_grid_constraints.dart +45/-14 
widget_spec.dart +42/-8   
dashboard_home_state.dart +15/-1   
dashboard_preferences_provider.dart +21/-0   
dashboard_home_service.dart +2/-0     
custom_dashboard_layout_strategy.dart +0/-37   
dashboard_layout_context.dart +67/-15 
desktop_vertical_layout_strategy.dart +1/-1     
_components.dart +26/-8   
dashboard_loading_wrapper.dart +12/-4   
display_mode_widget.dart +140/-0 
loading_tile.dart +53/-26 
jiggle_shake.dart +101/-0 
external_speed_test_links.dart +139/-0 
internal_speed_test_result.dart +189/-0 
port_status_widget.dart +198/-0 
custom_vpn.dart +96/-0   
networks.dart +102/-70
port_and_speed.dart +95/-69 
wifi_grid.dart +46/-33 
home_title.dart +41/-12 
internet_status.dart +0/-476 
port_status_widget.dart +12/-4   
dashboard_menu_view.dart +0/-21   
router_provider.dart +46/-44 
pubspec.yaml +3/-4     
demo_color_picker_test.dart +86/-0   
demo_theme_config_test.dart +78/-0   
dashboard_home_view_test.dart +13/-13 
dashboard_layout_preferences_test.dart +154/-0 
layout_item_factory_test.dart +140/-0 
speed_test_view_test.dart +4/-5     
add_nodes_service_test.dart +0/-1     

- Create entry point files for 7 modules:
  - ip_getter, url_helper, export_selector, get_log_selector,
    client_type, assign_ip (new entry points)
- Add mobile_assign_ip.dart (no-op implementation for native)
- Update 17 consumer files to use clean single imports
- Add platform-conditional-exports-audit.md documentation

This eliminates exposed conditional imports in consumer files.
Consumers now only need to import the entry point file.

Modules encapsulated:
1. ip_getter (5 consumers)
2. url_helper (5 consumers)
3. export_selector (2 consumers)
4. get_log_selector (1 consumer)
5. client_type (1 consumer)
6. assign_ip (3 consumers)
- Add 6 new atomic widgets for Custom Layout:
  - DashboardInternetStatus (internet status only)
  - DashboardMasterNodeInfo (router details)
  - DashboardPorts (LAN/WAN port status)
  - DashboardSpeedTest (speed test results)
  - DashboardNetworkStats (nodes/devices count)
  - DashboardTopology (mesh tree view)

- Update DashboardWidgetSpecs:
  - Add specs for all 6 atomic widgets
  - Add standardWidgets and customWidgets lists

- Update DashboardLayoutContext:
  - Add atomic widget fields
  - Add _allAtomicWidgets map
  - Add orderedVisibleCustomSpecs getter

- Update CustomDashboardLayoutStrategy:
  - Use orderedVisibleCustomSpecs for Custom Layout

- Update DashboardLayoutSettingsPanel:
  - Show only atomic widgets in Custom mode (no VPN, no composites)
  - Add reorderCustomWidget for proper ordering

- Fix WiFi Grid truncation:
  - Add SingleChildScrollView for overflow handling
  - Increase HeightStrategy to 5.0 for 2-row default height
…us ui

- Implement resize lock for Topology in Expanded mode (8x5 fixed).
- Update Internet Status constraints: Compact MinHeight=1, Normal/Expanded MinHeight=2.
- Fix Internet Status Compact UI: Remove location info, reduce padding (cleaner look).
- Fix SliverDashboardView mapping: Use correct InternetConnectionWidget for internet_status item.
…t functionality

- Metadata: Added description and requirements to WidgetSpec
- IoC: Implemented WidgetRequirement enum for data-driven feature checks (e.g. VPN)
- Layout: Enabled composite widgets (port_and_speed, networks) in custom layout
- Edit Mode: Added 'Cancel' button with full state restoration (layout positions + view modes)
- Settings: Updated Hidden Widgets panel to respect requirements
- Refactor WiFi Grid Compact View to use AppSurface for unified styling
- Replace InkWell with AppInkWell in Topology, Master Node, and WiFi Grid for consistent interaction feedback
- Enable navigation for Topology Compact View and Master Node
- Implement WiFi band toggling in WiFi Grid Compact View
- Update Internet Status refresh icon color
- Optimize dashboard layout and item heights
- Add dynamic Ports widget constraints based on hardware state (hasLanPort, isHorizontalLayout)
- Add minHeightRows/maxHeightRows to Ports constraints for resize validation
- Update LayoutItemFactory to use IoC pattern with WidgetSpecResolver
- Auto-optimize layout on first use and reset
- Add Optimize button to edit toolbar (uses optimizeLayout())
- Reset button only enabled when custom layout is active
- Reset only resets layout positions and display modes, preserves custom layout toggle
- Fix edit mode state sync bugs when resetting or toggling custom layout
- Update default layout positioning to match target design
- Adjust widget constraints: QuickPanel(h=3), MasterNode(h=4), Topology(w=4,h=4)
- Use DashboardOverlay + SliverDashboard + CustomScrollView for proper scroll behavior
- Fix grid background to only appear in dashboard area (not TopBar/Title)
- Add SliverPadding for correct horizontal margins
- Fix WiFi Grid default height (use spec value instead of hardcoded h:2)
- Simplify DashboardHomeView custom layout branch
- TopBar, Edit Toolbar, Title fixed at top with dashboard grid scrollable
- Add custom_layout parameter to deploy-demo workflow
- Add 13 new localization keys for dashboard custom layout
- Replace hardcoded strings with loc(context) calls
- Fix localization tests for dashboard and speed test views
- Fix unused imports and deprecated debugState usage in tests
- Update UI Kit dependency to v2.10.6
@qodo-code-review
Copy link

qodo-code-review bot commented Jan 16, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Swallowed exceptions: The new code swallows exceptions (catch-all with no logging or user-visible fallback)
which can hide failures and makes production debugging difficult.

Referred Code
try {
  spec = DashboardWidgetSpecs.all.firstWhere((s) => s.id == item.id);
} catch (_) {
  return;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured debug logs: The new code uses unstructured debugPrint with raw exception objects during JSON
import/parsing which may leak user-provided content into logs and is not structured for
auditing.

Referred Code
void importConfig(Map<String, dynamic> json) {
  try {
    state = DemoThemeConfig.fromJson(json);
  } catch (e) {
    debugPrint('Error importing theme config: $e');
    // In a real app we might want to throw or return false
  }
}

void importConfigString(String jsonStr) {
  try {
    final json = jsonDecode(jsonStr);
    importConfig(json);
  } catch (e) {
    debugPrint('Error parsing theme config json: $e');
  }
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Weak input validation: The new JSON import path accepts arbitrary JSON input without schema/type validation
beyond best-effort parsing, so it may allow malformed or unexpected data to propagate into
runtime state.

Referred Code
void importConfig(Map<String, dynamic> json) {
  try {

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 16, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Avoid duplicating widgets for different layouts

Refactor the code to eliminate the parallel sets of "atomic" and "fixed_layout"
widgets and specifications. Instead, use a single, unified set of widgets and
specs for both standard and custom layouts.

Examples:

lib/page/dashboard/models/dashboard_widget_specs.dart [461-564]
  // Custom Layout Specific Widgets (Duplicated for Isolation)
  // ---------------------------------------------------------------------------

  /// Wi-Fi Grid for Custom Layout
  static const wifiGridCustom = WidgetSpec(
    id: 'wifi_grid_custom',
    displayName: 'Wi-Fi Networks',
    description: 'Overview of Wi-Fi networks and guest access.',
    constraints: {
      DisplayMode.compact: WidgetGridConstraints(

 ... (clipped 94 lines)
lib/page/dashboard/views/components/fixed_layout/quick_panel.dart [1-451]

Solution Walkthrough:

Before:

// lib/page/dashboard/models/dashboard_widget_specs.dart
abstract class DashboardWidgetSpecs {
  // Standard (composite) widget spec
  static const portAndSpeed = WidgetSpec(...);
  static const List<WidgetSpec> standardWidgets = [portAndSpeed, ...];

  // Atomic widget specs
  static const ports = WidgetSpec(...);
  static const speedTest = WidgetSpec(...);

  // Duplicated spec for custom layout
  static const quickPanelCustom = WidgetSpec(...);

  static const List<WidgetSpec> customWidgets = [ports, speedTest, quickPanelCustom, ...];
}

// Two sets of widgets are implemented for different layouts
// e.g., FixedDashboardQuickPanel and CustomQuickPanel

After:

// lib/page/dashboard/models/dashboard_widget_specs.dart
abstract class DashboardWidgetSpecs {
  // A single, unified set of widget specifications
  static const internetStatus = WidgetSpec(...);
  static const masterNodeInfo = WidgetSpec(...);
  static const ports = WidgetSpec(...);
  static const speedTest = WidgetSpec(...);
  // ... no more `...Custom` duplicates

  static const List<WidgetSpec> allWidgets = [
    internetStatus, masterNodeInfo, ports, speedTest, ...
  ];
}

// A single set of widgets is used for both layouts.
// The standard layout becomes a non-editable preset of the custom layout system.
// e.g., use CustomQuickPanel for both, configured by the layout view.
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical architectural flaw of massive code duplication across widget implementations and specifications, which will significantly increase maintenance costs.

High
Possible issue
Persist layout after correcting resize violations
Suggestion Impact:The commit removed the conditional else-branch saveLayout() call and instead calls saveLayout() unconditionally after handling any constraint violation updates, ensuring persistence in both valid and corrected resize cases.

code diff:

@@ -218,10 +218,10 @@
       ref
           .read(sliverDashboardControllerProvider.notifier)
           .updateItemSize(item.id, newW, newH);
-    } else {
-      // Save layout on normal resize end
-      ref.read(sliverDashboardControllerProvider.notifier).saveLayout();
-    }
+    }
+    // Always save the layout after a resize operation, whether it was
+    // a valid user resize or an automatic correction.
+    ref.read(sliverDashboardControllerProvider.notifier).saveLayout();
   }

Ensure the layout is always saved after a resize operation by calling
saveLayout() both when a user resize is valid and when an automatic correction
for a constraint violation occurs.

lib/page/dashboard/views/sliver_dashboard_view.dart [164-225]

 void _handleResizeEnd(BuildContext context, LayoutItem item) {
   // Reactive Constraint Enforcement
   // This fixes the issue where items can be resized smaller than minWidth against grid edges.
   final preferences = ref.read(dashboardPreferencesProvider);
   final mode = preferences.getMode(item.id);
 
   WidgetSpec? spec;
 
   // Special handling for 'ports' widget - use dynamic constraints
   if (item.id == DashboardWidgetSpecs.ports.id) {
     final dashboardState = ref.read(dashboardHomeProvider);
     final hasLanPort = dashboardState.lanPortConnections.isNotEmpty;
     final isHorizontal = hasLanPort && dashboardState.isHorizontalLayout;
     spec = DashboardWidgetSpecs.getPortsSpec(
       hasLanPort: hasLanPort,
       isHorizontal: isHorizontal,
     );
   } else {
     try {
       spec = DashboardWidgetSpecs.all.firstWhere((s) => s.id == item.id);
     } catch (_) {
       return;
     }
   }
 
   final constraints = spec.constraints[mode];
   if (constraints == null) return;
 
   bool violated = false;
   int newW = item.w;
   int newH = item.h;
 
   // Enforce Width Constraints
   if (item.w < constraints.minColumns) {
     newW = constraints.minColumns;
     violated = true;
   }
   if (item.w > constraints.maxColumns) {
     newW = constraints.maxColumns;
     violated = true;
   }
 
   // Enforce Height Constraints using minHeightRows/maxHeightRows range
   // This allows resizing within the defined range instead of locking to strict height
   if (item.h < constraints.minHeightRows) {
     newH = constraints.minHeightRows;
     violated = true;
   }
   if (item.h > constraints.maxHeightRows) {
     newH = constraints.maxHeightRows;
     violated = true;
   }
 
   if (violated) {
     ref
         .read(sliverDashboardControllerProvider.notifier)
         .updateItemSize(item.id, newW, newH);
-  } else {
-    // Save layout on normal resize end
-    ref.read(sliverDashboardControllerProvider.notifier).saveLayout();
   }
+  // Always save the layout after a resize operation, whether it was
+  // a valid user resize or an automatic correction.
+  ref.read(sliverDashboardControllerProvider.notifier).saveLayout();
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a logic bug where the layout is not saved after an automatic correction, which could lead to data loss and inconsistent state. Applying this fix is critical for the feature's reliability.

Medium
Prevent crash from empty list
Suggestion Impact:The commit refactored the topology access to store `instantTopologyProvider` in a variable, added an `isEmpty` check on `topology.root.children`, and returns `SizedBox.shrink()` when empty before using `.first`. This prevents a potential crash as suggested (applied in two locations).

code diff:

-    final master = ref.watch(instantTopologyProvider).root.children.first;
+    final topology = ref.watch(instantTopologyProvider);
+    // Guard against empty children list to prevent crash
+    if (topology.root.children.isEmpty) {
+      return const SizedBox.shrink();
+    }
+    final master = topology.root.children.first;
     final masterIcon = ref.watch(dashboardHomeProvider).masterIcon;
     final wanPortConnection =
         ref.watch(dashboardHomeProvider).wanPortConnection;
@@ -186,9 +192,7 @@
                           ref
                               .read(pollingProvider.notifier)
                               .forcePolling()
-                              .then((value) {
-                            controller.stop();
-                          });
+                              .whenComplete(() => controller.stop());
                         },
                       ),
                     );
@@ -295,7 +299,12 @@
     final wanStatus = ref.watch(internetStatusProvider);
     final isOnline = wanStatus == InternetStatus.online;
     final geolocationState = ref.watch(geolocationProvider);
-    final master = ref.watch(instantTopologyProvider).root.children.first;
+    final topology = ref.watch(instantTopologyProvider);
+    // Guard against empty children list to prevent crash
+    if (topology.root.children.isEmpty) {
+      return const SizedBox.shrink();
+    }
+    final master = topology.root.children.first;
     final masterIcon = ref.watch(dashboardHomeProvider).masterIcon;

Add a check to ensure the children list is not empty before accessing its first
element to prevent a potential crash.

lib/page/dashboard/views/components/widgets/composite/internet_status.dart [128]

-final master = ref.watch(instantTopologyProvider).root.children.first;
+final topology = ref.watch(instantTopologyProvider);
+if (topology.root.children.isEmpty) {
+  // Return an empty widget or a placeholder to avoid crashing.
+  return const SizedBox.shrink();
+}
+final master = topology.root.children.first;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential crash when accessing .first on an empty list and provides a robust solution, which is crucial for application stability.

Medium
Fix undefined showInfoPanel flag

Replace the undefined showInfoPanel flag with the correct showDetails widget
parameter to fix a compile error.

lib/page/health_check/widgets/speed_test_widget.dart [65-75]

 final mainContent = switch (healthCheckState.status) {
   HealthCheckStatus.idle => (showLatestOnIdle && latestPolledResult != null)
-      ? (showInfoPanel
+      ? (showDetails
           ? Column(
               children: [
                 _startButton(context, ref),
                 infoView(context, healthCheckState, latestPolledResult),
               ],
             )
           : _startButton(context, ref, lastResult: latestPolledResult))
       : _startButton(context, ref),
   ...
 };

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies and fixes a reference to a non-existent variable (showInfoPanel), which would cause a compilation error.

Medium
Reference correct healthCheckState variable

Replace the undefined state variable with the correct healthCheckState variable
inside the centerBuilder closure.

lib/page/health_check/widgets/speed_test_widget.dart [214-221]

 centerBuilder: (context, value) {
   final isSmall = (meterSize ?? 220) < 130;
-  final titleText = switch (state.step) {
+  final titleText = switch (healthCheckState.step) {
     HealthCheckStep.downloadBandwidth => loc(context).download,
     HealthCheckStep.uploadBandwidth => loc(context).upload,
     _ => '',
   };
   ...
 },

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies and fixes a reference to a non-existent variable (state), which would cause a compilation error.

Medium
Improve JSON parsing with granular error handling

Improve the robustness of the DemoThemeConfig.fromJson factory by adding
granular try-catch blocks for each nested fromJson call within the fallback
parsing logic.

lib/demo/providers/demo_theme_config_provider.dart [127-155]

 factory DemoThemeConfig.fromJson(Map<String, dynamic> json) {
   // ...
   AppThemeOverrides? parsedOverrides;
   if (json['overrides'] is Map<String, dynamic>) {
     final ovJson = json['overrides'] as Map<String, dynamic>;
-    // Reconstruct sub-objects (assuming UI Kit classes have fromJson or we use helper)
-    // Actually standard usage allows AppThemeOverrides.fromJson(ovJson)
     try {
       parsedOverrides = AppThemeOverrides.fromJson(ovJson);
     } catch (_) {
-      // Fallback manual parsing if needed, but lets rely on standard
+      // Fallback manual parsing with individual error handling
       final semanticJson = ovJson['semantic'] as Map<String, dynamic>?;
       final paletteJson = ovJson['palette'] as Map<String, dynamic>?;
       final surfaceJson = ovJson['surface'] as Map<String, dynamic>?;
       final componentJson = ovJson['component'] as Map<String, dynamic>?;
 
+      SemanticOverrides? semantic;
+      try {
+        if (semanticJson != null) {
+          semantic = SemanticOverrides.fromJson(semanticJson);
+        }
+      } catch (e) {
+        debugPrint('Error parsing semantic overrides: $e');
+      }
+
+      PaletteColorOverride? palette;
+      try {
+        if (paletteJson != null) {
+          palette = PaletteColorOverride.fromJson(paletteJson);
+        }
+      } catch (e) {
+        debugPrint('Error parsing palette overrides: $e');
+      }
+
+      SurfaceOverrides? surface;
+      try {
+        if (surfaceJson != null) {
+          surface = SurfaceOverrides.fromJson(surfaceJson);
+        }
+      } catch (e) {
+        debugPrint('Error parsing surface overrides: $e');
+      }
+
+      ComponentOverrides? component;
+      try {
+        if (componentJson != null) {
+          component = ComponentOverrides.fromJson(componentJson);
+        }
+      } catch (e) {
+        debugPrint('Error parsing component overrides: $e');
+      }
+
       parsedOverrides = AppThemeOverrides(
-        semantic: semanticJson != null
-            ? SemanticOverrides.fromJson(semanticJson)
-            : null,
-        palette: paletteJson != null
-            ? PaletteColorOverride.fromJson(paletteJson)
-            : null,
-        surface: surfaceJson != null
-            ? SurfaceOverrides.fromJson(surfaceJson)
-            : null,
-        component: componentJson != null
-            ? ComponentOverrides.fromJson(componentJson)
-            : null,
+        semantic: semantic,
+        palette: palette,
+        surface: surface,
+        component: component,
       );
     }
   }
   // ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a flaw in the error handling logic for JSON deserialization, which could cause a total import failure from a partial error. The proposed fix makes the parsing more robust and resilient.

Medium
Always stop refresh controller
Suggestion Impact:The commit updated forcePolling() callbacks from .then(...) to .whenComplete(() => controller.stop()) (in multiple places), ensuring the controller stop runs regardless of success/failure.

code diff:

                 controller.repeat();
-                ref.read(pollingProvider.notifier).forcePolling().then((_) {
-                  controller.stop();
-                });
+                ref
+                    .read(pollingProvider.notifier)
+                    .forcePolling()
+                    .whenComplete(() => controller.stop());
               },
             ),
           ),
@@ -125,7 +126,12 @@
     final wanStatus = ref.watch(internetStatusProvider);
     final isOnline = wanStatus == InternetStatus.online;
     final geolocationState = ref.watch(geolocationProvider);
-    final master = ref.watch(instantTopologyProvider).root.children.first;
+    final topology = ref.watch(instantTopologyProvider);
+    // Guard against empty children list to prevent crash
+    if (topology.root.children.isEmpty) {
+      return const SizedBox.shrink();
+    }
+    final master = topology.root.children.first;
     final masterIcon = ref.watch(dashboardHomeProvider).masterIcon;
     final wanPortConnection =
         ref.watch(dashboardHomeProvider).wanPortConnection;
@@ -186,9 +192,7 @@
                           ref
                               .read(pollingProvider.notifier)
                               .forcePolling()
-                              .then((value) {
-                            controller.stop();
-                          });
+                              .whenComplete(() => controller.stop());
                         },
                       ),
                     );
@@ -295,7 +299,12 @@
     final wanStatus = ref.watch(internetStatusProvider);
     final isOnline = wanStatus == InternetStatus.online;
     final geolocationState = ref.watch(geolocationProvider);
-    final master = ref.watch(instantTopologyProvider).root.children.first;
+    final topology = ref.watch(instantTopologyProvider);
+    // Guard against empty children list to prevent crash
+    if (topology.root.children.isEmpty) {
+      return const SizedBox.shrink();
+    }
+    final master = topology.root.children.first;
     final masterIcon = ref.watch(dashboardHomeProvider).masterIcon;
     final wanPortConnection =
         ref.watch(dashboardHomeProvider).wanPortConnection;
@@ -370,9 +379,7 @@
                           ref
                               .read(pollingProvider.notifier)
                               .forcePolling()
-                              .then((_) {
-                            controller.stop();
-                          });
+                              .whenComplete(() => controller.stop());
                         },

Use whenComplete instead of then to ensure the refresh animation controller is
always stopped, even if the polling operation fails.

lib/page/dashboard/views/components/widgets/composite/internet_status.dart [100-102]

-ref.read(pollingProvider.notifier).forcePolling().then((_) {
-  controller.stop();
-});
+ref.read(pollingProvider.notifier)
+  .forcePolling()
+  .whenComplete(() => controller.stop());

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using then can leave the UI in a permanent loading state on error. Using whenComplete improves robustness and user experience.

Medium
General
Add user feedback for copy action

Show a SnackBar notification to confirm that the password has been copied to the
clipboard.

lib/page/dashboard/views/components/widgets/parts/wifi_card.dart [454-461]

 AppIconButton.small(
   styleVariant: ButtonStyleVariant.text,
   icon: const Icon(Icons.copy, size: 18),
   onTap: () {
     // Copy to clipboard
     Clipboard.setData(ClipboardData(text: widget.password));
+    // Show feedback to the user
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(
+        content: AppText.bodyMedium('Password copied to clipboard'),
+        duration: const Duration(seconds: 2),
+      ),
+    );
   },
 ),

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion improves user experience by providing essential feedback for an otherwise invisible action, which is a good practice for usability.

Low
Use type-safe casting

Improve type safety by casting the result of exportLayout() to List<Map<String,
dynamic>> before mapping over it.

lib/page/dashboard/views/components/settings/dashboard_layout_settings_panel.dart [131-133]

-final currentLayout = controller.exportLayout();
+final currentLayout = controller.exportLayout().cast<Map<String, dynamic>>();
 final currentIds =
-    currentLayout.map((e) => (e as Map)['id'] as String).toSet();
+    currentLayout.map((e) => e['id'] as String).toSet();

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion improves code safety and readability by replacing an unsafe map access with a type-safe cast, which is a good practice for maintainability.

Low
Use theme-consistent fallback color

Replace the hardcoded Colors.green fallback with a theme-consistent color like
Theme.of(context).colorScheme.primary.

lib/page/dashboard/views/components/fixed_layout/port_and_speed.dart [163-169]

 color: isConnected
     ? Theme.of(context)
             .extension<AppDesignTheme>()
             ?.colorScheme
             .semanticSuccess ??
-        Colors.green
+        Theme.of(context).colorScheme.primary
     : Theme.of(context).colorScheme.onSurfaceVariant,

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that using a theme color is better than a hardcoded color for consistency, which improves code quality and maintainability.

Low
  • Update

- Fix saveLayout() not called after constraint violation correction in resize
- Add empty children guard to prevent crash in InternetConnectionWidget
- Change .then() to .whenComplete() for error resilience in refresh action
This commit refactors the NodeLightSettings feature to follow Clean Architecture principles, decoupling the UI from JNAP data models.

Changes:

- Introduce NodeLightState UI model

- Update NodeLightSettingsService for Model-State conversion

- Update NodeLightSettingsProvider to expose NodeLightState

- Update consumers (NodeDetailView, QuickPanel) to use NodeLightState

- Update Unit and Widget Tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants