Skip to content

Replace legacy FC/GUI singletons with independent Pinia stores (single source of truth) #4800

@Erics1337

Description

@Erics1337

Is your feature request related to a problem? Please describe

Summary

Recent work (for example, PR #4781) migrated the GPS tab UI from jQuery to Vue and introduced Pinia stores that currently proxy legacy global singletons (FC, GUI, CONFIGURATOR, etc.) via computed getters and setters. This issue tracks the follow-up migration to make Pinia the authoritative, independent single source of truth (SSoT), remove the legacy singletons, and centralize side effects (MSP, persistence) inside Pinia actions.

Motivation

  • Cleaner architecture and separation of concerns
  • More predictable and robust reactivity (avoid subtle proxy edge cases)
  • Easier unit testing and component isolation
  • Simplified future development and reduced long-term technical debt

Goals

  • Create independent Pinia stores for core domains (flight controller, GPS, connection, navigation, dialogs, sensors, features, etc.)
  • Ensure UI components read from and write to Pinia directly
  • Provide a deterministic hydration and sync adapter to stage migration from legacy singletons to Pinia
  • Centralize MSP, network, and persistence logic inside Pinia actions
  • Remove legacy singletons when safe

Describe the solution you'd like

Stores and APIs

Implement dedicated Pinia stores per domain with stable public APIs:

  • flightControllerStore
    • state: { config, gpsConfig, gpsData, features, compassConfig, sensorData, ... }
    • actions: load(), save(), poll(), hydrateFromLegacy(), plus focused actions like loadGpsConfig() and saveGpsConfig()
  • connectionStore
    • state + actions to manage connection lifecycle, port info, reconnect flow, etc.
  • navigationStore, dialogStore, gpsStore (map UI state), featuresStore, etc.

Hydration strategy

  • Add explicit store.hydrateFromLegacy(legacySource) actions that copy a snapshot of legacy singletons into Pinia on app start or on-demand (for example, tab mount).
  • Keep hydration deterministic and explicit (no implicit copying).

Synchronization adapter (migration bridge)

  • Implement a central adapter module that handles write-through from Pinia to legacy singletons during the transition.
  • The adapter is the only code path that writes to legacy singletons.
  • Pinia actions call the adapter when necessary.
  • Debounce and throttle adapter writes for high-frequency updates.
  • Expose a small API, for example:
    • syncToLegacy(storeName, changes)
    • syncFromLegacy(...) (optional, when needed)

Centralize side effects

  • Move MSP calls, persistence (mspHelper), and long-running polling or intervals into store actions so components remain pure consumers.
  • Example:
    • flightControllerStore.actions.loadGpsConfig() and saveGpsConfig() wrap MSP.promise calls.

Migration workflow (incremental)

  • New Vue components and new work read from Pinia only.
  • Legacy code can keep read-only access for a transition window.
  • Writes funnel through Pinia actions, which use the adapter so legacy code sees updates.
  • Migrate one tab fully to Pinia-first (GPS is a strong candidate after PR Migrate GPS tab to Vue component #4781), validate behavior, then migrate remaining tabs in small PRs.
  • After most call sites (for example, >90%) use Pinia, flip authoritative direction: Pinia becomes SSoT and the adapter stops writing back.
  • Remove legacy singletons and proxy code after verification.

Testing, observability, and ergonomics

  • Unit tests for store actions and adapter behavior
  • Integration tests for MSP and polling flows, plus map and telemetry behavior
  • Optional migration logging to detect stale-access patterns during transition
  • Document store public APIs and a migration guide
  • Use PR labels like pinia-migration to track progress

Describe alternatives you've considered

Keep proxying legacy singletons (status quo)

  • Pros: lowest short-term risk, fastest for UI shipping, minimal churn
  • Cons: preserves technical debt, continued coupling to globals, fragile reactive behavior, harder to test
  • Verdict: acceptable short term, not a long-term solution

Event bus or message-passing bridge

  • Pros: decouples without immediate singleton replacement
  • Cons: harder to reason about state, higher race risk, still not a clean SSoT
  • Verdict: useful for one-off cases, not ideal for full migration

Convert legacy singletons to reactive wrappers

  • Pros: minimal refactor, existing singleton usage remains
  • Cons: global source of truth persists, legacy coupling remains, removal becomes harder
  • Verdict: short-term mitigation only

One big Pinia-first rewrite

  • Pros: fastest path to clean end state
  • Cons: very high risk, massive PRs, difficult review and QA
  • Verdict: too risky, prefer incremental

Switch state managers (Vuex or custom)

  • Pros: potentially fits constraints if Pinia cannot
  • Cons: unnecessary overhead since Pinia is already adopted and appropriate
  • Verdict: stick with Pinia

Proposed migration plan (incremental, low-risk)

  1. Accept current Vue and Pinia migrations that proxy legacy singletons (surface changes)
  2. Create independent Pinia stores with stable public APIs per domain
  3. Add explicit hydrateFromLegacy(legacySource) actions per store
  4. Implement a centralized synchronization adapter (Pinia ↔ legacy) with debouncing and throttling
  5. Move MSP and network write logic into Pinia actions (no direct MSP calls from components)
  6. Migrate one tab fully to Pinia-first (GPS first) and verify behavior
  7. Migrate remaining Vue tabs to Pinia-first in small PRs (one tab per PR)
  8. Migrate legacy tabs incrementally to read from Pinia (or mount Vue equivalents)
  9. Flip authoritative direction: Pinia becomes SSoT, disable adapter write-backs
  10. Remove legacy singletons and proxy code
  11. Full regression testing and QA across supported platforms (desktop, PWA, Android native)

Implementation details and best practices

  • Store actions own side effects; components are pure
  • Hydration is explicit (hydrateFromLegacy()), deterministic, and discoverable
  • Single-writer policy: Pinia actions are canonical writer, adapter forwards only during migration
  • Debounce expensive writes and high-frequency polling; centralize intervals inside stores
  • Add unit tests for actions and integration tests for MSP and polling flows
  • Document store APIs and migration steps for contributors

Acceptance criteria

  • New Vue components and migrated tabs read from and write to Pinia stores
  • MSP, network, and persistence side effects are centralized in store actions
  • Legacy non-Vue code continues to function during migration via the adapter
  • No regressions in telemetry, map behavior, or configuration persistence
  • Migration checklist completed and documented
  • Legacy singleton accessors removed and tests updated

Risks and mitigations

  • Race conditions (legacy vs Pinia): single-writer policy, debounced adapter, explicit sync points
  • Large scope: small per-tab PRs and staged rollout
  • Performance impact: throttling and centralizing polling and sync
  • Map and UI regressions: focused QA for map, telemetry, and config persistence on all supported platforms

Suggested metadata

  • Labels: refactor, tech-debt, pinia, vue, pinia-migration
  • Assignees: pick an owner (for example, haslinghuis or Erics1337)
  • Milestone: next maintenance release or dedicated state-migration milestone
  • Estimated effort: medium to large (multiple small PRs over a few sprints)

Checklist

  • Create a migration tracking epic or milestone (for example, "Pinia SSoT migration")
  • Design Pinia store public APIs for each domain (state, getters, actions)
  • Implement hydrateFromLegacy(legacySource) for each store
  • Implement a centralized synchronization adapter (Pinia ↔ legacy) with debouncing and throttling
  • Move MSP and network write logic into Pinia actions (avoid direct MSP calls from components)
  • Migrate one tab fully to Pinia-first and verify behavior (GPS tab first candidate)
  • Add unit tests for new store actions and integration tests for polling and MSP flows
  • Migrate remaining Vue tabs to Pinia-first in small PRs (one tab per PR recommended)
  • Migrate legacy tabs incrementally to read from Pinia (or mount Vue equivalents)
  • Flip authoritative direction: make Pinia the SSoT and disable write-backs to legacy singletons
  • Remove legacy singleton proxies and delete legacy singleton modules
  • Full regression testing and QA across platforms (desktop, PWA, Android native)
  • Update contributor documentation (how to use Pinia stores and migration strategy)

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions