Skip to content

Devices and Dashboard related service extraction - #537

Merged
PeterJhongLinksys merged 4 commits into
dev-2.0.0from
006-dashboard-home-service-extraction
Dec 29, 2025
Merged

Devices and Dashboard related service extraction#537
PeterJhongLinksys merged 4 commits into
dev-2.0.0from
006-dashboard-home-service-extraction

Conversation

@HankYuLinksys

@HankYuLinksys HankYuLinksys commented Dec 29, 2025

Copy link
Copy Markdown
Collaborator

User description

  • Extract transformation logic to DashboardHomeService for three-layer architecture compliance
  • Remove JNAP model imports from provider and state files (RadioInfo, GuestRadioSettings)
  • Rename UI model classes per Constitution Article III Section 3.3.4:
    • DashboardSpeedItem → DashboardSpeedUIModel
    • DashboardWiFiItem → DashboardWiFiUIModel
  • Move factory methods from DashboardWiFiUIModel to service layer
  • Add comprehensive unit tests (72 tests total):
    • dashboard_home_service_test.dart (15 tests)
    • dashboard_home_state_test.dart (49 tests)
    • dashboard_home_provider_test.dart (8 tests)
  • Create test data builder: DashboardHomeTestData
  • Create DashboardManagerService to handle all JNAP communication
    • transformPollingData(): transforms CoreTransactionData to DashboardManagerState
    • checkRouterIsBack(): verifies router connectivity and serial number matching
    • checkDeviceInfo(): fetches device info with cache-first strategy
  • Refactor DashboardManagerProvider to delegate to service
    • Remove direct JNAP imports (actions, result, models)
    • Simplify build() to call service.transformPollingData()
    • Update checkRouterIsBack() and checkDeviceInfo() to use service
  • Add new ServiceError types for router operations
    • SerialNumberMismatchError: router SN doesn't match expected
    • ConnectivityError: router is unreachable
  • Create DeviceManagerService at lib/core/jnap/services/device_manager_service.dart
    • Move transformPollingData (formerly createState) with all helper methods
    • Move updateDeviceNameAndIcon, deleteDevices, deauthClient operations
    • Add ServiceError mapping for JNAP errors
    • Improve type safety with explicit JNAPSuccess checks
  • Refactor DeviceManagerNotifier to delegate to service
    • Remove all JNAP model/result/action imports (architecture compliance)
    • build() now delegates to service.transformPollingData()
    • All write operations delegate to service methods
    • Keep state query methods (getSsidConnectedBy, getBandConnectedBy, findParent)
  • Add comprehensive test coverage
    • Service tests: transformPollingData, updateDeviceNameAndIcon, deleteDevices, deauthClient
    • Provider tests: verify delegation to service
    • State tests: LinksysDevice, DeviceManagerState, WifiConnectionType
    • Test data builder: DeviceManagerTestData with JNAP response factories

PR Type

Enhancement, Tests


Description

Service Layer Extraction for Dashboard and Device Management

  • Extracted transformation logic to three new service classes (DashboardHomeService, DashboardManagerService, DeviceManagerService) for three-layer architecture compliance

  • Removed JNAP model imports from provider and state files, isolating JNAP communication to service layer only

  • Renamed UI model classes per Constitution Article III Section 3.3.4:

    • DashboardSpeedItemDashboardSpeedUIModel
    • DashboardWiFiItemDashboardWiFiUIModel
  • Moved factory methods from UI models to service layer for proper separation of concerns

  • Created DashboardManagerService with polling data transformation, router connectivity checks (checkRouterIsBack()), and device info retrieval with cache-first strategy

  • Created DeviceManagerService with JNAP communication isolation for device operations (update, delete, deauthenticate)

  • Created DashboardHomeService orchestrating WiFi list building, node status detection, and UI state composition

  • Added new ServiceError types: SerialNumberMismatchError and ConnectivityError for router operations

  • Refactored three providers (DashboardHomeNotifier, DashboardManagerNotifier, DeviceManagerNotifier) to delegate to service layer

  • Added comprehensive test coverage: 72 tests total across service, provider, and state layers

  • Created test data builders (DashboardHomeTestData, DashboardManagerTestData, DeviceManagerTestData) with JNAP response factories

  • Created detailed feature specifications, task breakdowns, service contracts, and implementation guides for all three service extractions


Diagram Walkthrough

flowchart LR
  JNAP["JNAP Layer<br/>Actions & Models"]
  DMS["DashboardManagerService"]
  DEMS["DeviceManagerService"]
  DHS["DashboardHomeService"]
  DMP["DashboardManagerProvider"]
  DEMP["DeviceManagerProvider"]
  DHP["DashboardHomeProvider"]
  UI["UI Layer<br/>DashboardHomeState"]
  
  JNAP -- "polling data" --> DMS
  JNAP -- "device operations" --> DEMS
  DMS -- "DashboardManagerState" --> DMP
  DEMS -- "DeviceManagerState" --> DEMP
  DMP -- "state" --> DHP
  DEMP -- "state" --> DHP
  DHS -- "transforms" --> UI
  DHP -- "delegates to" --> DHS
Loading

File Walkthrough

Relevant files
Tests
12 files
dashboard_home_state_test.dart
Dashboard home state model comprehensive unit tests           

test/page/dashboard/providers/dashboard_home_state_test.dart

  • Comprehensive unit tests for DashboardSpeedUIModel covering
    construction, copyWith, equality, and serialization
    (toMap/fromMap/toJson/fromJson)
  • Comprehensive unit tests for DashboardWiFiUIModel with all field
    variations and state transitions
  • Complete test suite for DashboardHomeState including defaults,
    copyWith operations, and round-trip serialization
  • Extension tests for DashboardHomeStateExt covering mainSSID and
    isBridgeMode computed properties
+835/-0 
device_manager_test_data.dart
Device manager test data builder with JNAP factories         

test/mocks/test_data/device_manager_test_data.dart

  • Factory methods for creating JNAP success responses
    (createGetDevicesSuccess, createGetNetworkConnectionsSuccess, etc.)
  • Complete polling data builder createCompletePollingData() with
    customizable parameters
  • Device factory methods: createMasterDevice(), createSlaveDevice(),
    createExternalDevice()
  • Default test data sets for common scenarios (empty polling, partial
    errors)
  • JNAP error response factory createJnapError()
+526/-0 
device_manager_state_test.dart
Device manager state model comprehensive unit tests           

test/core/jnap/providers/device_manager_state_test.dart

  • Tests for LinksysDevice fromMap/toMap conversion, copyWith, and
    computed properties (isMaster)
  • Tests for DeviceManagerState construction, copyWith, and computed
    properties (nodeDevices, externalDevices, mainWifiDevices,
    guestWifiDevices, masterDevice, slaveDevices)
  • Equatable tests verifying equality and hash code consistency
  • Serialization tests for toMap/fromMap round-trips with and without
    devices
  • Tests for WifiConnectionType enum values
+541/-0 
dashboard_home_test_data.dart
Dashboard home test data builder with state factories       

test/mocks/test_data/dashboard_home_test_data.dart

  • Factory methods for creating DashboardManagerState with default and
    custom values
  • Factory methods for creating DeviceManagerState with various
    configurations
  • RouterRadio builder supporting customizable radio parameters (band,
    SSID, passphrase)
  • GuestRadioInfo builder for guest network testing
  • Device builders: createMasterDevice(), createSlaveDevice(),
    createMainWifiDevice(), createGuestWifiDevice()
  • Helper factories for NodeDeviceInfo, RouterWANStatus, and band
    callback functions
+451/-0 
dashboard_home_service_test.dart
Dashboard home service transformation logic unit tests     

test/page/dashboard/services/dashboard_home_service_test.dart

  • Added 15 comprehensive unit tests for
    DashboardHomeService.buildDashboardHomeState() method
  • Tests cover main WiFi grouping by band, guest WiFi handling, node
    offline detection, and first polling state
  • Tests verify correct extraction of WAN type, device counts, port
    layout determination, and master icon selection
  • Includes edge cases like null deviceInfo and empty radio lists
+452/-0 
dashboard_manager_state_test.dart
Dashboard manager state model unit tests                                 

test/core/jnap/providers/dashboard_manager_state_test.dart

  • Added 20 unit tests for DashboardManagerState covering default values,
    equality, copyWith, and serialization
  • Tests verify state immutability, proper field copying, and JSON/Map
    round-trip serialization
  • Tests validate radio list handling and null value preservation
+367/-0 
dashboard_manager_service_test.dart
Dashboard manager service JNAP communication tests             

test/core/jnap/services/dashboard_manager_service_test.dart

  • Added 14 unit tests for DashboardManagerService covering data
    transformation and router connectivity checks
  • Tests verify transformPollingData() handles complete/partial/null
    polling data correctly
  • Tests validate checkRouterIsBack() serial number matching and error
    handling
  • Tests verify checkDeviceInfo() cache-first strategy and API fallback
    behavior
+364/-0 
dashboard_home_provider_test.dart
Dashboard home provider delegation tests                                 

test/page/dashboard/providers/dashboard_home_provider_test.dart

  • Added 11 unit tests for DashboardHomeNotifier verifying delegation to
    service layer
  • Tests confirm provider correctly passes state from
    dashboardManagerProvider and deviceManagerProvider to service
  • Tests validate provider reactivity to upstream provider changes and
    correct state propagation
+358/-0 
dashboard_manager_provider_test.dart
Dashboard manager provider service delegation tests           

test/core/jnap/providers/dashboard_manager_provider_test.dart

  • Added 13 unit tests for DashboardManagerNotifier verifying service
    delegation pattern
  • Tests cover transformPollingData() delegation, checkRouterIsBack()
    error propagation, and checkDeviceInfo() caching
  • Tests validate error handling for SerialNumberMismatchError and
    ConnectivityError
+324/-0 
dashboard_manager_test_data.dart
Dashboard manager test data factory builders                         

test/mocks/test_data/dashboard_manager_test_data.dart

  • Created comprehensive test data builder with factory methods for JNAP
    mock responses
  • Provides builders for individual responses (createDeviceInfoSuccess(),
    createRadioInfoSuccess(), etc.)
  • Provides combined polling data builders with partial error and invalid
    data scenarios
+280/-0 
device_manager_provider_test.dart
Device manager provider service delegation tests                 

test/core/jnap/providers/device_manager_provider_test.dart

  • Added 11 unit tests for DeviceManagerNotifier verifying delegation to
    DeviceManagerService
  • Tests cover transformPollingData(), updateDeviceNameAndIcon(),
    deleteDevices(), and deauthClient() delegation
  • Tests validate state query methods and error propagation from service
    layer
+280/-0 
device_manager_service_test.dart
Device manager service data transformation tests                 

test/core/jnap/services/device_manager_service_test.dart

  • Added 8 unit tests for DeviceManagerService.transformPollingData()
    method
  • Tests verify correct state transformation, device categorization, and
    wireless connection mapping
  • Tests cover null/empty polling data and partial error scenarios
+132/-0 
Enhancement
9 files
device_manager_provider.dart
Device manager provider refactored to service delegation 

lib/core/jnap/providers/device_manager_provider.dart

  • Removed direct JNAP imports (actions, models, results) for
    architecture compliance
  • Refactored build() method to delegate to
    DeviceManagerService.transformPollingData()
  • Simplified updateDeviceNameAndIcon() to call service and update local
    state
  • Simplified deleteDevices() and deauthClient() to delegate to service
    layer
  • Removed createState() and all helper methods (_getWirelessConnections,
    _getDeviceListAndLocations, etc.)
  • Kept state query methods (getSsidConnectedBy, getBandConnectedBy,
    findParent)
+45/-346
device_manager_service.dart
Device manager service layer with JNAP isolation                 

lib/core/jnap/services/device_manager_service.dart

  • New service class extracting all JNAP communication and data
    transformation logic
  • transformPollingData() method converts CoreTransactionData to
    DeviceManagerState with safe result extraction
  • Helper methods for wireless connections, device list building, WAN
    status, and backhaul info processing
  • updateDeviceNameAndIcon() operation with property updates and device
    list refresh
  • deleteDevices() and deauthClient() write operations with error mapping
  • JNAP error mapping to ServiceError types for consistent error handling
+458/-0 
dashboard_manager_service.dart
Dashboard manager service layer with router connectivity checks

lib/core/jnap/services/dashboard_manager_service.dart

  • New service class for dashboard management with JNAP communication
    isolation
  • transformPollingData() method extracts and transforms device info,
    radio settings, system stats, and ethernet port connections
  • Helper methods _getMainRadioList() and _getGuestRadioList() for radio
    data extraction
  • checkRouterIsBack() method verifying router connectivity and serial
    number matching with SerialNumberMismatchError and ConnectivityError
    handling
  • checkDeviceInfo() method with cache-first strategy for device
    information retrieval
  • JNAP error mapping to ServiceError types for consistent error handling
+211/-0 
dashboard_manager_provider.dart
Dashboard manager provider service extraction refactor     

lib/core/jnap/providers/dashboard_manager_provider.dart

  • Refactored DashboardManagerNotifier.build() to delegate to
    DashboardManagerService.transformPollingData()
  • Removed 80+ lines of JNAP model parsing logic (createState() method
    and helpers)
  • Updated checkRouterIsBack() and checkDeviceInfo() to delegate to
    service methods
  • Removed imports of JNAP models, actions, and result types
+7/-122 
dashboard_home_state.dart
UI model class renaming per constitution compliance           

lib/page/dashboard/providers/dashboard_home_state.dart

  • Renamed DashboardSpeedItem to DashboardSpeedUIModel per Constitution
    Article III Section 3.3.4
  • Renamed DashboardWiFiItem to DashboardWiFiUIModel per Constitution
    Article III Section 3.3.4
  • Removed factory methods fromMainRadios() and fromGuestRadios() from
    DashboardWiFiUIModel (moved to service)
  • Updated all references and serialization methods to use new class
    names
+21/-50 
dashboard_home_service.dart
Dashboard home service extraction with transformation logic

lib/page/dashboard/services/dashboard_home_service.dart

  • Created new DashboardHomeService class encapsulating dashboard state
    transformation logic
  • Implemented buildDashboardHomeState() orchestrating WiFi list
    building, node status detection, and icon/layout determination
  • Moved factory methods from DashboardWiFiUIModel to private service
    methods _createWiFiItemFromMainRadios() and
    _createWiFiItemFromGuestRadios()
  • Added Riverpod provider dashboardHomeServiceProvider for dependency
    injection
+164/-0 
dashboard_home_provider.dart
Dashboard home provider service extraction refactor           

lib/page/dashboard/providers/dashboard_home_provider.dart

  • Refactored DashboardHomeNotifier.build() to delegate to
    DashboardHomeService.buildDashboardHomeState()
  • Removed 60+ lines of transformation logic from createState() method
  • Removed imports of JNAP models, utility functions, and health check
    state
  • Simplified provider to pass dependencies (dashboardManagerState,
    deviceManagerState, getBandForDevice callback) to service
+11/-79 
service_error.dart
New service error types for router operations                       

lib/core/errors/service_error.dart

  • Added SerialNumberMismatchError for router serial number validation
    failures
  • Added ConnectivityError for router unreachability scenarios
  • Both errors extend ServiceError base class for consistent error
    handling
+17/-0   
wifi_grid.dart
WiFi grid component type reference update                               

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

  • Updated WiFiCard widget to use renamed DashboardWiFiUIModel type
    instead of DashboardWiFiItem
+1/-1     
Documentation
17 files
spec.md
Device manager service extraction feature specification   

specs/001-device-manager-service-extraction/spec.md

  • Created comprehensive feature specification for device manager service
    extraction
  • Documents user scenarios, functional requirements, success criteria,
    and edge cases
  • Defines acceptance scenarios for data transformation isolation, device
    updates, deletion, and deauthentication
  • Includes clarifications from design sessions and dependency
    documentation
+133/-0 
tasks.md
Dashboard Manager Service Extraction Task Breakdown           

specs/005-dashboard-service-extraction/tasks.md

  • Created comprehensive task breakdown for Dashboard Manager Service
    extraction with 80 tasks organized into 7 phases
  • Defined user stories (US1-US4) with priorities and acceptance criteria
    for polling data transformation, router connectivity, device info
    retrieval, and provider refactoring
  • Established phase dependencies, parallel execution opportunities, and
    MVP-first implementation strategy
  • Included detailed task descriptions with exact file paths and test
    coverage requirements (≥90% service, ≥85% provider)
+319/-0 
tasks.md
Device Manager Service Extraction Task Completion Record 

specs/001-device-manager-service-extraction/tasks.md

  • Documented 53 completed tasks across 7 phases for Device Manager
    Service extraction
  • Organized tasks by user story (US1-US4) with parallel execution
    opportunities identified
  • Defined foundational test data builder requirements and implementation
    order
  • Included verification commands and architecture compliance checks
+256/-0 
tasks.md
DashboardHome Service Extraction Task Breakdown                   

specs/006-dashboard-home-service-extraction/tasks.md

  • Created task breakdown for DashboardHome Service extraction with 34
    tasks across 5 phases
  • Defined two user stories: Architecture Compliance Refactoring (P1) and
    Service Layer Testability (P2)
  • Established test data builder requirements and service implementation
    tasks with exact file paths
  • Included parallel execution opportunities and MVP-first strategy
+236/-0 
dashboard_manager_service_contract.md
Dashboard Manager Service Contract Definition                       

specs/005-dashboard-service-extraction/contracts/dashboard_manager_service_contract.md

  • Defined service contract for DashboardManagerService with three public
    methods: transformPollingData(), checkRouterIsBack(),
    checkDeviceInfo()
  • Documented method signatures, input/output specifications, and error
    handling with ServiceError types
  • Provided usage examples and testing contract requirements
  • Specified JNAP actions processed and dependency injection pattern
+240/-0 
spec.md
Dashboard Manager Service Feature Specification                   

specs/005-dashboard-service-extraction/spec.md

  • Created feature specification with four user stories covering polling
    data transformation, router connectivity, device info retrieval, and
    provider architecture compliance
  • Defined functional requirements (FR-001 through FR-010) and success
    criteria (SC-001 through SC-005)
  • Documented edge cases and assumptions for JNAP error handling and
    state management
  • Established measurable outcomes for architecture compliance and test
    coverage
+142/-0 
device_manager_service_contract.md
Device Manager Service Contract Definition                             

specs/001-device-manager-service-extraction/contracts/device_manager_service_contract.md

  • Documented service contract for DeviceManagerService with four public
    methods for data transformation and write operations
  • Specified JNAP actions consumed, error mapping strategy, and usage
    patterns
  • Provided method details for transformPollingData(),
    updateDeviceNameAndIcon(), deleteDevices(), and deauthClient()
  • Included dependency list and prohibited imports for architecture
    compliance
+211/-0 
research.md
Device Manager Service Extraction Research Findings           

specs/001-device-manager-service-extraction/research.md

  • Documented seven key architectural decisions for service extraction
    with rationale and alternatives considered
  • Addressed service location (lib/core/jnap/services/), state class
    location, JNAP model handling, and helper method placement
  • Established error handling strategy and test data builder pattern per
    constitution requirements
  • Resolved provider-service dependency direction using ref.read()
    pattern
+192/-0 
dashboard_home_service_contract.md
Dashboard Home Service Contract Definition                             

specs/006-dashboard-home-service-extraction/contracts/dashboard_home_service_contract.md

  • Defined service contract for DashboardHomeService with single public
    method buildDashboardHomeState()
  • Documented private helper methods for WiFi item building, node status
    checking, and icon/layout determination
  • Specified required imports (JNAP models allowed in service layer) and
    edge case handling
  • Included testing contract with unit test requirements and mock
    specifications
+210/-0 
data-model.md
Dashboard Home Service Data Model Documentation                   

specs/006-dashboard-home-service-extraction/data-model.md

  • Documented existing data models (DashboardHomeState,
    DashboardWiFiUIModel, DashboardSpeedUIModel) with no structural
    changes
  • Specified input models from JNAP layer (DashboardManagerState,
    DeviceManagerState) consumed by service
  • Provided data flow diagram showing transformation from JNAP layer
    through service to UI layer
  • Included validation rules and state transition documentation
+156/-0 
quickstart.md
Dashboard Manager Service Extraction Quickstart Guide       

specs/005-dashboard-service-extraction/quickstart.md

  • Provided step-by-step implementation guide for Dashboard Manager
    Service extraction
  • Documented eight implementation steps from service file creation
    through architecture compliance verification
  • Included code examples for service creation, provider refactoring, and
    error type additions
  • Listed verification commands and common pitfalls with solutions
+180/-0 
data-model.md
Dashboard Manager Service Data Model Documentation             

specs/005-dashboard-service-extraction/data-model.md

  • Documented existing DashboardManagerState entity with no structural
    changes required
  • Specified JNAP models referenced by service only (not provider):
    NodeDeviceInfo, RouterRadio, GuestRadioInfo, SoftSKUSettings
  • Provided before/after data flow diagrams showing service layer
    insertion between polling and provider
  • Included state transition lifecycle and validation rules for all
    fields
+147/-0 
spec.md
Dashboard Home Service Feature Specification                         

specs/006-dashboard-home-service-extraction/spec.md

  • Created feature specification with two user stories: Architecture
    Compliance Refactoring (P1) and Service Layer Testability (P2)
  • Defined functional requirements (FR-001 through FR-010) for service
    creation and provider/state refactoring
  • Documented success criteria including zero JNAP imports in provider
    layer and ≥90% test coverage
  • Specified edge cases and assumptions for data transformation logic
+87/-0   
quickstart.md
Dashboard Home Service Extraction Quickstart Guide             

specs/006-dashboard-home-service-extraction/quickstart.md

  • Provided step-by-step implementation guide for Dashboard Home Service
    extraction
  • Documented five implementation steps from service file creation
    through state file refactoring
  • Included code examples for service creation, provider delegation, and
    test data builder setup
  • Listed verification commands and common issues with solutions
+190/-0 
quickstart.md
Device Manager Service Extraction Quickstart Guide             

specs/001-device-manager-service-extraction/quickstart.md

  • Provided concise implementation guide for Device Manager Service
    extraction
  • Documented implementation order and file creation/modification
    requirements
  • Included code examples for service structure, provider refactoring,
    and import removal
  • Listed verification commands and common pitfalls with solutions
+148/-0 
service-contract.md
Dashboard Home Service Contract Quality Checklist               

specs/006-dashboard-home-service-extraction/checklists/service-contract.md

  • Created quality checklist for service contract requirements with 31
    items across 7 categories
  • Defined verification gates for requirement completeness, breaking
    changes prevention, clarity, and consistency
  • Included acceptance criteria quality checks and scenario coverage
    validation
  • Provided dependency and assumption verification items
+87/-0   
plan.md
Dashboard Home Service Extraction Implementation Plan       

specs/006-dashboard-home-service-extraction/plan.md

  • Created implementation plan with technical context, constitution
    compliance check, and project structure
  • Documented complexity tracking and directory organization for source
    code and tests
  • Verified all applicable constitution articles (I, III, V, VI, VII,
    VIII, IX, XI) with pass status
  • Established scope as single feature refactoring (~100 lines of code
    moved)
+81/-0   
Additional files
9 files
implementation.md +96/-0   
requirements.md +37/-0   
data-model.md +141/-0 
plan.md +76/-0   
requirements.md +37/-0   
plan.md +76/-0   
research.md +99/-0   
requirements.md +37/-0   
research.md +118/-0 

- Create DeviceManagerService at lib/core/jnap/services/device_manager_service.dart
  - Move transformPollingData (formerly createState) with all helper methods
  - Move updateDeviceNameAndIcon, deleteDevices, deauthClient operations
  - Add ServiceError mapping for JNAP errors
  - Improve type safety with explicit JNAPSuccess checks

- Refactor DeviceManagerNotifier to delegate to service
  - Remove all JNAP model/result/action imports (architecture compliance)
  - build() now delegates to service.transformPollingData()
  - All write operations delegate to service methods
  - Keep state query methods (getSsidConnectedBy, getBandConnectedBy, findParent)

- Add comprehensive test coverage
  - Service tests: transformPollingData, updateDeviceNameAndIcon, deleteDevices, deauthClient
  - Provider tests: verify delegation to service
  - State tests: LinksysDevice, DeviceManagerState, WifiConnectionType
  - Test data builder: DeviceManagerTestData with JNAP response factories

- Update implementation checklist and tasks.md
…hboardManagerNotifier

- Create DashboardManagerService to handle all JNAP communication
  - transformPollingData(): transforms CoreTransactionData to DashboardManagerState
  - checkRouterIsBack(): verifies router connectivity and serial number matching
  - checkDeviceInfo(): fetches device info with cache-first strategy

- Refactor DashboardManagerProvider to delegate to service
  - Remove direct JNAP imports (actions, result, models)
  - Simplify build() to call service.transformPollingData()
  - Update checkRouterIsBack() and checkDeviceInfo() to use service

- Add new ServiceError types for router operations
  - SerialNumberMismatchError: router SN doesn't match expected
  - ConnectivityError: router is unreachable

- Add comprehensive test coverage (41 tests)
  - DashboardManagerService tests (15): transformPollingData, checkRouterIsBack, checkDeviceInfo
  - DashboardManagerProvider tests (9): delegation verification
  - DashboardManagerState tests (17): default values, equality, serialization

- Add DashboardManagerTestData builder for test data generation

- Add feature spec documentation
  - Service contract with method signatures
  - Data model flow diagrams
  - Implementation plan and tasks
…HomeNotifier

- Extract transformation logic to DashboardHomeService for three-layer architecture compliance
- Remove JNAP model imports from provider and state files (RadioInfo, GuestRadioSettings)
- Rename UI model classes per Constitution Article III Section 3.3.4:
  - DashboardSpeedItem → DashboardSpeedUIModel
  - DashboardWiFiItem → DashboardWiFiUIModel
- Move factory methods from DashboardWiFiUIModel to service layer
- Add comprehensive unit tests (72 tests total):
  - dashboard_home_service_test.dart (15 tests)
  - dashboard_home_state_test.dart (49 tests)
  - dashboard_home_provider_test.dart (8 tests)
- Create test data builder: DashboardHomeTestData
- Add specs documentation for feature 006
@qodo-code-review

qodo-code-review Bot commented Dec 29, 2025

Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive data exposure

Description: The service populates DashboardWiFiUIModel.password directly from router passphrases
(wpaPersonalSettings.passphrase / guestWPAPassphrase), which can lead to sensitive Wi‑Fi
credential exposure if this UI model/state is logged, serialized, cached, or persisted
(tests indicate these models support JSON/map serialization).
dashboard_home_service.dart [137-162]

Referred Code
  return DashboardWiFiUIModel(
    ssid: radio.settings.ssid,
    password: radio.settings.wpaPersonalSettings?.passphrase ?? '',
    radios: radios.map((e) => e.radioID).toList(),
    isGuest: false,
    isEnabled: radio.settings.isEnabled,
    numOfConnectedDevices: connectedDevices,
  );
}

/// Creates a [DashboardWiFiUIModel] from guest radio list.
///
/// This method replaces the `DashboardWiFiUIModel.fromGuestRadios()` factory method.
DashboardWiFiUIModel _createWiFiItemFromGuestRadios(
  List<GuestRadioInfo> radios,
  int connectedDevices,
) {
  final radio = radios.first;
  return DashboardWiFiUIModel(
    ssid: radio.guestSSID,
    password: radio.guestWPAPassphrase ?? '',


 ... (clipped 5 lines)
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: 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 Logging Practices

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

Status: Passed

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

🔴
Generic: Comprehensive Audit Trails

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

Status: 🏷️
Missing audit logging: Critical router/device write operations (update properties, delete devices, deauth client)
are executed without any audit trail logging (user/context, timestamp, action, outcome).

Referred Code
/// Updates device name and/or icon.
///
/// [targetId] - Device ID to update
/// [newName] - New display name for the device
/// [isLocation] - If true, also updates userDeviceLocation
/// [icon] - Optional icon category to set
///
/// Returns: List of updated device properties
///
/// Throws: [ServiceError] on JNAP failure
Future<List<RawDeviceProperty>> updateDeviceNameAndIcon({
  required String targetId,
  required String newName,
  required bool isLocation,
  IconDeviceCategory? icon,
}) async {
  List<RawDeviceProperty> properties = [
    RawDeviceProperty(name: 'userDeviceName', value: newName),
    if (isLocation)
      RawDeviceProperty(name: 'userDeviceLocation', value: newName),
    if (icon != null)


 ... (clipped 75 lines)

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: 🏷️
Incomplete error checks: Some external-operation paths lack robust handling (e.g., deauthClient does not validate
non-throwing failure results and deleteDevices lacks exception mapping/guarding), which
may lead to unclear or unhandled failure states.

Referred Code
Future<Map<String, bool>> deleteDevices(List<String> deviceIds) async {
  if (deviceIds.isEmpty) {
    return {};
  }

  final dataResults = await _routerRepository.deleteDevices(deviceIds);
  final idResults = Map.fromIterables(deviceIds, dataResults)
      .entries
      .map((entry) => MapEntry(entry.key, entry.value.value.result == 'OK'));
  return Map.fromEntries(idResults);
}

/// Deauthenticates a client device.
///
/// [macAddress] - MAC address of device to disconnect
///
/// Throws: [ServiceError] on JNAP failure
Future<void> deauthClient(String macAddress) async {
  try {
    await _routerRepository.send(
      JNAPAction.clientDeauth,


 ... (clipped 11 lines)

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: 🏷️
Potential detail leakage: Service errors include internal result codes and may pass through raw JNAP error strings
(e.g., InvalidInputError(message: error.error)), which could become user-visible depending
on UI handling.

Referred Code
    if (result.result != 'OK') {
      throw UnexpectedError(
        originalError: result,
        message: 'Failed to update device properties: ${result.result}',
      );
    }

    return properties;
  } on JNAPError catch (e) {
    throw _mapJnapError(e);
  }
}

/// Deletes devices from the network.
///
/// [deviceIds] - List of device IDs to delete
///
/// Returns: Map of deviceId → success/failure status
///
/// Behavior:
/// - Empty list returns empty map immediately (no-op)


 ... (clipped 43 lines)

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: 🏷️
Missing input validation: External inputs used for privileged router operations (targetId, newName, deviceIds,
macAddress) are sent to JNAP without explicit validation/sanitization in the new service
layer.

Referred Code
Future<List<RawDeviceProperty>> updateDeviceNameAndIcon({
  required String targetId,
  required String newName,
  required bool isLocation,
  IconDeviceCategory? icon,
}) async {
  List<RawDeviceProperty> properties = [
    RawDeviceProperty(name: 'userDeviceName', value: newName),
    if (isLocation)
      RawDeviceProperty(name: 'userDeviceLocation', value: newName),
    if (icon != null)
      RawDeviceProperty(name: 'userDeviceType', value: icon.name),
  ];

  try {
    final result = await _routerRepository.send(
      JNAPAction.setDeviceProperties,
      data: {
        'deviceID': targetId,
        'propertiesToModify': properties.map((e) => e.toMap()).toList(),
      },


 ... (clipped 64 lines)

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

qodo-code-review Bot commented Dec 29, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Ensure non-null band string

Ensure the getBandConnectedBy method always returns a non-nullable String by
handling potential null values from its logic branches.

lib/core/jnap/providers/device_manager_provider.dart [30-39]

 String getBandConnectedBy(LinksysDevice device) {
-  final wirelessConnections = state.wirelessConnections;
-  final wirelessData = wirelessConnections[device.getMacAddress()];
-  return device.connectedWifiType == WifiConnectionType.guest
-      ? state.guestRadioSettings?.radios.firstOrNull?.guestSSID
-      : state.radioInfos[radioID]?.settings.ssid;
+  final wirelessData = state.wirelessConnections[device.getMacAddress()];
+  final band = device.connectedWifiType == WifiConnectionType.guest
+    ? state.guestRadioSettings?.radios.firstOrNull?.guestSSID
+    : state.radioInfos[wirelessData?.radioID]?.settings.ssid;
+  return band ?? '';
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential null pointer exception because the method can return null despite its non-nullable String return type, and the fix prevents a runtime crash.

Medium
Fix incorrect date time parsing

To correctly parse 24-hour timestamps from the API, change the DateFormat
pattern from hh (12-hour) to HH (24-hour).

lib/core/jnap/services/dashboard_manager_service.dart [103-108]

 // Try to parse the time string, fallback to current time if parsing fails
 DateTime? parsedTime;
 if (timeString != null) {
-  parsedTime = DateFormat("yyyy-MM-ddThh:mm:ssZ").tryParse(timeString);
+  parsedTime = DateFormat("yyyy-MM-ddTHH:mm:ssZ").tryParse(timeString);
 }
 final localTime = (parsedTime ?? DateTime.now()).millisecondsSinceEpoch;
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This is a valid and subtle bug fix that prevents incorrect parsing of 24-hour format timestamps, which would cause the application to silently fall back to the current time.

Medium
Avoid concurrent modification during iteration

To prevent state mutation, create a copy of the wirelessConnections map before
modifying it within the _getBackhaulInfoData method, ensuring immutability.

lib/core/jnap/services/device_manager_service.dart [223-293]

 DeviceManagerState _getBackhaulInfoData(
   DeviceManagerState state,
   Map<String, dynamic>? data,
 ) {
   var newState = state.copyWith(
     backhaulInfoData: List.from(
       data?['backhaulDevices'] ?? [],
     ).map((e) => BackHaulInfoData.fromMap(e)).toList(),
   );
   // Update IP address
   newState = newState.copyWith(
     deviceList: newState.deviceList.map((device) {
       final deviceId = device.deviceID;
       final backhaulInfo = newState.backhaulInfoData
           .firstWhereOrNull((backhaul) => backhaul.deviceUUID == deviceId);
       if (backhaulInfo != null && device.isOnline()) {
         // Replace the IP in Devices with the one from BackhaulInfo
         final updatedConnections = device.connections
             .map(
               (connection) => connection.copyWith(
                 ipAddress: backhaulInfo.ipAddress,
               ),
             )
             .toList();
         final newDevice = device.copyWith(
           connections: updatedConnections,
           wirelessConnectionInfo: backhaulInfo.wirelessConnectionInfo,
           speedMbps: backhaulInfo.speedMbps,
           connectionType: backhaulInfo.connectionType,
         );
         return newDevice;
       }
       return device;
     }).toList(),
   );
-  final wirelessConnectionInfo = newState.wirelessConnections;
+  final newWirelessConnections =
+      Map<String, WirelessConnection>.from(newState.wirelessConnections);
   newState.backhaulInfoData
       .where((element) =>
           element.connectionType == 'Wireless' &&
           element.wirelessConnectionInfo != null)
       .forEach((element) {
     final mac = element.wirelessConnectionInfo?.stationBSSID;
     final rssi = element.wirelessConnectionInfo?.stationRSSI;
     final band = element.wirelessConnectionInfo?.radioID;
     final bssid = element.wirelessConnectionInfo?.apBSSID;
     if (mac != null && rssi != null) {
-      wirelessConnectionInfo[mac] = WirelessConnection(
+      newWirelessConnections[mac] = WirelessConnection(
         bssid: bssid ?? '',
         isGuest: false,
         radioID: 'RADIO_${band}z',
         band: '${band}z',
         signalDecibels: rssi,
       );
     }
   });
-  newState = newState.copyWith(wirelessConnections: wirelessConnectionInfo);
+  newState = newState.copyWith(wirelessConnections: newWirelessConnections);
 
   // update wireless signal for each device
   final devices = newState.deviceList
       .map((e) => e.copyWith(
             signalDecibels: e.wirelessConnectionInfo?.stationRSSI ??
                 _getWirelessSignalOf(e, newState),
             connectedDevices: e.connectedDevices
                 .map((e) =>
                     e.copyWith(signalDecibels: _getWirelessSignalOf(e, newState)))
                 .toList(),
           ))
       .toList();
   newState = newState.copyWith(deviceList: devices);
   return newState;
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a state mutation issue by modifying the wirelessConnections map in place; the proposed fix correctly uses a copy to ensure immutability, which is crucial for this state management architecture.

Medium
Handle non-JNAP exceptions

Add a generic catch block to checkDeviceInfo to handle non-JNAPError exceptions
and wrap them in a ConnectivityError for consistent error handling.

lib/core/jnap/services/dashboard_manager_service.dart [184-200]

 Future<NodeDeviceInfo> checkDeviceInfo(
     NodeDeviceInfo? cachedDeviceInfo) async {
   if (cachedDeviceInfo != null) {
     return cachedDeviceInfo;
   }
 
   try {
     final result = await _routerRepository.send(
       JNAPAction.getDeviceInfo,
       retries: 0,
       timeoutMs: 3000,
     );
     return NodeDeviceInfo.fromJson(result.output);
   } on JNAPError catch (e) {
     throw _mapJnapError(e);
+  } catch (e) {
+    throw ConnectivityError(message: e.toString());
   }
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly improves error handling by catching generic exceptions and mapping them to a specific ConnectivityError, making the service more robust against unexpected network issues.

Medium
General
Map delete errors to ServiceError

Add a try-catch block to the deleteDevices method to handle JNAPError and map it
to a ServiceError for consistent error handling.

lib/core/jnap/services/device_manager_service.dart [416-426]

 Future<Map<String, bool>> deleteDevices(List<String> deviceIds) async {
   if (deviceIds.isEmpty) {
     return {};
   }
-
-  final dataResults = await _routerRepository.deleteDevices(deviceIds);
-  final idResults = Map.fromIterables(deviceIds, dataResults)
-      .entries
-      .map((entry) => MapEntry(entry.key, entry.value.value.result == 'OK'));
-  return Map.fromEntries(idResults);
+  try {
+    final dataResults = await _routerRepository.deleteDevices(deviceIds);
+    final idResults = Map.fromIterables(deviceIds, dataResults)
+        .entries
+        .map((entry) => MapEntry(entry.key, entry.value.value.result == 'OK'));
+    return Map.fromEntries(idResults);
+  } on JNAPError catch (e) {
+    throw _mapJnapError(e);
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion improves robustness by adding explicit error handling for JNAPError, ensuring that API failures are consistently mapped to ServiceError as intended by the service's design.

Medium
Trigger poll after update

After updating a device's name or icon in updateDeviceNameAndIcon, trigger a
polling refresh to ensure the application state remains synchronized with the
backend.

lib/core/jnap/providers/device_manager_provider.dart [104-125]

-Future<void> updateDeviceNameAndIcon({
-  required String targetId,
-  required String newName,
-  required bool isLocation,
-  IconDeviceCategory? icon,
-}) async {
-  final service = ref.read(deviceManagerServiceProvider);
-  final properties = await service.updateDeviceNameAndIcon(
-    targetId: targetId,
-    newName: newName,
-    isLocation: isLocation,
-    icon: icon,
-  );
-  // Update local state with the new properties
-  final newList = state.deviceList.fold(<LinksysDevice>[], (list, element) {
-    if (element.deviceID == targetId) {
-      list.add(element.copyWith(properties: properties));
-    } else {
-      list.add(element);
-    }
-    return list;
-  });
-  state = state.copyWith(deviceList: newList);
-}
+// ... after setting state ...
+state = state.copyWith(deviceList: newList);
+// Trigger polling refresh for full update
+await ref.read(pollingProvider.notifier).forcePolling();

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that forcing a poll after an update ensures UI consistency, aligning updateDeviceNameAndIcon with the behavior of other state-modifying methods in the class.

Low
Add super constructor call

In SerialNumberMismatchError, pass a formatted error message to the ServiceError
super constructor.

lib/core/errors/service_error.dart [299-303]

 final class SerialNumberMismatchError extends ServiceError {
   final String expected;
   final String actual;
-  const SerialNumberMismatchError({required this.expected, required this.actual});
+  const SerialNumberMismatchError({required this.expected, required this.actual})
+      : super('Serial number mismatch: expected $expected, got $actual');
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion improves error handling by passing a more descriptive, formatted message to the base ServiceError constructor, which aids in debugging and logging.

Low
Forward message to super

Refactor ConnectivityError to remove its message field and pass the constructor
argument directly to the ServiceError super constructor.

lib/core/errors/service_error.dart [306-309]

 final class ConnectivityError extends ServiceError {
-  final String? message;
-  const ConnectivityError({this.message});
+  const ConnectivityError([String? message]) : super(message);
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This is a good refactoring that simplifies the ConnectivityError class by removing a redundant field and correctly utilizing inheritance to pass the message to the superclass.

Low
  • Update

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@PeterJhongLinksys PeterJhongLinksys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good.

@PeterJhongLinksys
PeterJhongLinksys merged commit d190be5 into dev-2.0.0 Dec 29, 2025
2 checks passed
@PeterJhongLinksys
PeterJhongLinksys deleted the 006-dashboard-home-service-extraction branch December 29, 2025 08:12
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