refactor: moved wait and analysis stores to pinia - #1918
Conversation
To support potential future work to move to Vue 3, Vuex needs to be replaced with Pinia. I started with a couple of easy stores (analysis and wait). Pinia convention recommends a flatter file structure than was needed with Vuex. However, I still kept the stores separated by folder to facilitate breaking them out into actions and getters if desired. Pinia-converted stores are kept in /stores to differentiate them as more stores are moved over. Reset logic was also reconfigured in store/index to account for the new stores not being in the old Vuex store tree. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
pedrolamas
left a comment
There was a problem hiding this comment.
Reviewed base cdefdcbd → head ac1f4f80. All 13 wait/* and analysis/* call sites are converted — no dangling $typedDispatch('wait/...'), $typedGetters['wait/...'] or dispatch: strings remain, and no spec touches these stores. Pinia activation ordering is correct: Vue.use(PiniaVuePlugin) runs at import of src/stores/pinia.ts, setActivePinia fires in the root beforeCreate during new Vue({ pinia }), and every useXStore() call site is lazy and post-mount (appInit() runs after $mount). vue-demi is already in pnpm-workspace.yaml allowBuilds, so its postinstall switch runs. No crash-level bugs found.
Assessment: performance and maintainability going forward
Performance — essentially neutral, slightly negative on bundle. Pinia 2 on Vue 2.7 sits on the same reactivity core (vue-demi → Vue 2.7's reactive/computed), so waits tracking behaves identically to the Vuex getters it replaces — I traced the hasWait method-style getter and confirmed the render watcher still collects state.waits at call time, so there's no reactivity regression. Costs added: ~5 kB gzip for pinia plus a duplicated vue-demi (see inline comment on the lockfile), Vuex stays resident for the whole migration so both systems ship simultaneously, and useStore() resolution moves onto the socket hot path and widget render paths. @vue/devtools-api should tree-shake in prod via Vite's process.env.NODE_ENV replacement, but worth confirming in a bundle report. None of this is material at Fluidd's scale; the honest summary is "no perf win, small perf tax, paid for architectural reasons".
Maintainability — right direction, but the seam needs hardening before it scales. Two stores out of 28 is a good, low-risk pilot and the conversions are faithful. The concern is the cost of the in-between state, which will last many PRs:
- The hand-maintained reset registry fails silently (see
src/store/index.ts) — this is the one thing I'd fix before merging, since it's the mechanism that will actually break as migration proceeds. - Two side-effect dispatch mechanisms now coexist in
socketActions.ts, which will multiply onceprinter/filesmove. - Migrated state loses Vuex's dev-only
strictmutation guard, with no Pinia equivalent. src/storevssrc/storesis a standing typo hazard (one character apart, both resolve).
Recommend landing this with an explicit migration order, a typed reset registry, and a decision on whether NotifyOptions.dispatch/commit are being retired — otherwise the half-migrated state becomes the steady state.
| // Reset our color set. | ||
| Vue.$colorset.forceResetAll() | ||
|
|
||
| const piniaStores: Record<string, () => StoreGeneric> = { |
There was a problem hiding this comment.
medium — correctness/maintainability. piniaStores is a hand-maintained duplicate of the module list, and misses fail silently.
MODULES_TO_RESET_ON_DROP (src/store/socket/actions.ts:14) and resetKlippy below pass module names as untyped strings; the loop underneath does if (this.hasModule(key)), which now returns false for a migrated store rather than erroring.
Scenario: the next store migrated to Pinia (this PR's stated plan) isn't added to this map — dispatch('reset', [...]) on socket drop / instance switch silently skips it, leaving stale data from the previous printer with no error anywhere.
Suggest a single registry so the string lists and the map can't drift — e.g. each Pinia store registers itself into one exported array, or reset iterates pinia._s directly.
There was a problem hiding this comment.
Updated Stores to register themselves on instantiation and import.meta.glob to instantiate them eagerly (necessary to use the dispatch/commit pipeline with minimal changes without instantiating every store manually). added a check to ensure either pinia or vuex handled a reset and if not pass a warn - tested invalid store names in untyped string calls to reset to validate.
| ...options | ||
| } | ||
| ) | ||
| ).then((result) => { |
There was a problem hiding this comment.
medium — maintainability. This and serverAnalysisProcess below are now the only actions in a ~90-action file that perform side effects via a .then() tail instead of the declarative dispatch:/commit: option, so the socket layer has two dispatch mechanisms.
Concrete consequence: the .then() side effect is unconditional and can no longer be overridden or suppressed by a caller passing options.dispatch, unlike every neighbouring action where dispatch: sits before ...options.
As more stores migrate this pattern will spread through socketActions.ts and the dispatch/commit plumbing in socketClient.ts will end up half-dead. Worth deciding now whether NotifyOptions.dispatch/commit get replaced wholesale or kept.
There was a problem hiding this comment.
See below comment for Changes and my proposed approach moving forward. This specific function was removed in reference to comments below.
| } | ||
| } | ||
| ) | ||
| ).then((result) => { |
There was a problem hiding this comment.
medium — maintainability. Same as above: the serverFilesMetadata re-fetch is now unconditional and can no longer be suppressed via options.dispatch, whereas the removed dispatch: 'analysis/onAnalysisProcess' sat before ...options and was overridable.
There was a problem hiding this comment.
Reverted to the original declarative approach and added a third option of pinia:. socketClient.ts now pushes the call to stores/helpers/parseVuexConventons.ts to split the namespace/action and process the call through the pinia store. This is repeatable with typed stores as well though neither wait nor analysis interact with that. I held off on making any other major change to NotifyOptions pending any decision on the direction you prefer for that.
| // Remove a wait if defined. | ||
| if (request?.wait?.length) { | ||
| this.store.typedCommit('wait/setRemoveWait', request.wait) | ||
| useWaitStore().removeWait(request.wait) |
There was a problem hiding this comment.
low — perf. useWaitStore() is now resolved on every keyed socket response (and on every emit, line 248), replacing a direct store.typedCommit.
useStore() isn't free: hasInjectionContext() → getCurrentInstance() → inject() → setActivePinia() → pinia._s.get(id), plus HMR bookkeeping in dev. Cheap per call, but this is a hot path under a burst of printer notifications — suggest caching the store in a lazily-initialised private field on WebSocketClient rather than resolving it per message.
There was a problem hiding this comment.
this and state.ts are now lazily-insansiating useWaitStore() ill keep this in mind moving forward, cheap calls certainly add up in volume.
| */ | ||
| hasWait (wait: string | string[]): boolean { | ||
| return this.$typedGetters['wait/hasWait'](wait) | ||
| return useWaitStore().hasWait(wait) |
There was a problem hiding this comment.
low — perf. Same per-call useStore() resolution as in socketClient.ts, but here hasWait/hasWaits/hasWaitsBy run inside the render path of many widgets, so a single dashboard re-render pays it dozens of times. Consider resolving the store once (e.g. a lazily-cached field) instead of per call.
| state: (): AnalysisState => ({ | ||
| status: null, | ||
| }), | ||
| getters: { |
There was a problem hiding this comment.
low. Empty getters: { } block is noise.
Also worth noting status is write-only: nothing in src/ reads useAnalysisStore().status, and serverAnalysisStatus (its only writer) has zero callers. Pre-existing dead state, but the migration is a good moment to delete it rather than port it.
There was a problem hiding this comment.
Removed getters, status, serverAnalysisStatus as suggested. the only outstanding action on useAnalysisStore() is now onAnalysisProcess - im wondering if it should be left as-is or folded into files, which is the only consumer, and dropping the analysis store entirely.
|
|
||
| export const useAnalysisStore = defineStore('analysis', { | ||
| state: (): AnalysisState => ({ | ||
| status: null, |
There was a problem hiding this comment.
low — lint. Trailing commas here and at lines 10 and 19 violate neostandard's @stylistic/comma-dangle: ['error', { objects: 'never' }], and CI runs lint --no-fix.
There was a problem hiding this comment.
corrected. worth noting that linting didn't pick up on my end at all when run locally. best I can tell is one of the typescript rules passes ignore for comma-dangle and is listed later thus supersedes neostandard.
| import { TinyColor } from '@ctrl/tinycolor' | ||
| import dbKey from '@/util/db-key' | ||
| import { useWaitStore } from '../../stores/wait' | ||
|
|
There was a problem hiding this comment.
low. Relative '../../stores/wait' here, but @/stores/analysis in src/store/index.ts (and '../stores/wait' on the line above it) — three styles for the same target.
Given src/stores/ sits one character away from the existing src/store/, mixed relative paths between the two trees are a real typo hazard: ../stores/… mistyped as ../store/… resolves to a different, existing directory and silently imports the wrong thing. Suggest standardising on @/stores/… everywhere, and possibly a less collision-prone directory name (src/pinia/).
There was a problem hiding this comment.
Yup, that's my error, ill standardize to '@/'
I see valid concern here about store/stores. I took the naming from Pinia's migration guide regarding stores. End state @/stores makes a more descriptive folder structure for maintainability long term and fits the style of the rest of the code base where folders are generally descriptive by function rather than library. How would you like to proceed?
There was a problem hiding this comment.
Thank you, please continue with @/stores plan, and keep an eye to make sure there are no leftovers on @/store after migration.
| dependencies: | ||
| vue: 2.7.16 | ||
|
|
||
| vue-demi@0.14.10(vue@2.7.16): |
There was a problem hiding this comment.
low — bundle size. Pinia pulls vue-demi@0.14.10 while vue-echarts@7.0.3 already pins vue-demi@0.13.11, so two copies now exist in the graph and both reach the bundle (pinia in the main chunk, vue-echarts in a lazy chunk).
The repo already uses overrides in pnpm-workspace.yaml for exactly this (glob, serialize-javascript) — adding vue-demi there would dedupe.
|
Hi @tworkman08, thank your for this Pull Request. This looks quite promising and I like it as a first approach to Pinia! I've pointed my Claude to this and it has posted a few comments from a first review that will need to be resolved. I would also prefer to avoid |
Thanks for the detailed reply @pedrolamas, I definitely wanted to start slow and easy. especially since I made assumptions during the initial pr write and wanted to get feedback on the approach. I'll take a look at the reviews above and make the necessary updates shortly. |
Addresses feedback from review on moved stores wait and analysis to pinia pr. File structure and naming now follows the reccomended structure from Pinia, rather than the previous vuex model: https://pinia.vuejs.org/cookbook/migration-vuex.html#Restructuring-Modules-to-Stores Standardized paths to @/ rather than relative paths. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adressing review feedback: - Created plugin to register pinia stores on creation + added import.meta.glob to eagerly instansiates Stores. creating a single source of truth without having to manually add stores to a list. - updated reset function to resetPiniaStores() iterating the actual plugin registry above, so a store can't be migrated to pinia and silently skipped on reset - dded check in reset when a reset key matches neither a Vuex nor a pinia store. - Replace serverAnalysisProcess's unconditional .then() with a declarative pinia: option on NotifyOptions, mirroring dispatch:/commit: and staying overridable by callers this is expandable to typedDispatch/commit as well - Lazily cache useWaitStore() in WebSocketClient and StateMixin instead of resolving it on every socket message/render - Dropped dead code in stores/analysis.ts: onAnalysisStatus, it's corresponding caller in SocketActions.ts, and Analysis Status which was set, but never read. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
pinned ^0.14.10 which is compatible with both echarts and pinia. signed-off-by: Tracy Workman <tworkman08@gmail.com>
To support potential future work to move to Vue 3, Vuex needs to be replaced with Pinia. I started with a couple of easy stores (analysis and wait). Pinia convention recommends a flatter file structure than was needed with Vuex. However, I still kept the stores separated by folder to facilitate breaking them out into actions and getters if desired. Pinia-converted stores are kept in /stores to differentiate them as more stores are moved over. Reset logic was also reconfigured in store/index to account for the new stores not being in the old Vuex store tree. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
Addresses feedback from review on moved stores wait and analysis to pinia pr. File structure and naming now follows the reccomended structure from Pinia, rather than the previous vuex model: https://pinia.vuejs.org/cookbook/migration-vuex.html#Restructuring-Modules-to-Stores Standardized paths to @/ rather than relative paths. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
Adressing review feedback: - Created plugin to register pinia stores on creation + added import.meta.glob to eagerly instansiates Stores. creating a single source of truth without having to manually add stores to a list. - updated reset function to resetPiniaStores() iterating the actual plugin registry above, so a store can't be migrated to pinia and silently skipped on reset - dded check in reset when a reset key matches neither a Vuex nor a pinia store. - Replace serverAnalysisProcess's unconditional .then() with a declarative pinia: option on NotifyOptions, mirroring dispatch:/commit: and staying overridable by callers this is expandable to typedDispatch/commit as well - Lazily cache useWaitStore() in WebSocketClient and StateMixin instead of resolving it on every socket message/render - Dropped dead code in stores/analysis.ts: onAnalysisStatus, it's corresponding caller in SocketActions.ts, and Analysis Status which was set, but never read. Signed-off-by: Tracy Workman <tworkman08@gmail.com>
pinned ^0.14.10 which is compatible with both echarts and pinia. signed-off-by: Tracy Workman <tworkman08@gmail.com>
https://github.com/tworkman08/fluidd into pinia-refactor signed-off-by: Tracy Workman <tworkman08@gmail.com>
signed-off-by: Tracy Workman <tworkman08@gmail.com>
To support potential future work to move to Vue 3, Vuex needs to be replaced with Pinia. I started with a couple of easy stores (analysis and wait). Pinia convention recommends a flatter file structure than was needed with Vuex. However, I still kept the stores separated by folder to facilitate breaking them out into actions and getters if desired. Pinia-converted stores are kept in /stores to differentiate them as more stores are moved over. Reset logic was also
reconfigured in store/index to account for the new stores not being in the old Vuex store tree.
Signed-off-by: Tracy Workman tworkman08@gmail.com