Skip to content

refactor: moved wait and analysis stores to pinia - #1918

Open
tworkman08 wants to merge 14 commits into
fluidd-core:developfrom
tworkman08:pinia-refactor
Open

refactor: moved wait and analysis stores to pinia#1918
tworkman08 wants to merge 14 commits into
fluidd-core:developfrom
tworkman08:pinia-refactor

Conversation

@tworkman08

@tworkman08 tworkman08 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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


Open with GitKraken

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 pedrolamas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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:

  1. 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.
  2. Two side-effect dispatch mechanisms now coexist in socketActions.ts, which will multiply once printer/files move.
  3. Migrated state loses Vuex's dev-only strict mutation guard, with no Pinia equivalent.
  4. src/store vs src/stores is 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.

Comment thread src/store/index.ts Outdated
// Reset our color set.
Vue.$colorset.forceResetAll()

const piniaStores: Record<string, () => StoreGeneric> = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/api/socketActions.ts Outdated
...options
}
)
).then((result) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@tworkman08 tworkman08 Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See below comment for Changes and my proposed approach moving forward. This specific function was removed in reference to comments below.

Comment thread src/api/socketActions.ts Outdated
}
}
)
).then((result) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/plugins/socketClient.ts Outdated
// Remove a wait if defined.
if (request?.wait?.length) {
this.store.typedCommit('wait/setRemoveWait', request.wait)
useWaitStore().removeWait(request.wait)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this and state.ts are now lazily-insansiating useWaitStore() ill keep this in mind moving forward, cheap calls certainly add up in volume.

Comment thread src/mixins/state.ts Outdated
*/
hasWait (wait: string | string[]): boolean {
return this.$typedGetters['wait/hasWait'](wait)
return useWaitStore().hasWait(wait)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/stores/analysis.ts Outdated
state: (): AnalysisState => ({
status: null,
}),
getters: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/stores/analysis.ts Outdated

export const useAnalysisStore = defineStore('analysis', {
state: (): AnalysisState => ({
status: null,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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/).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you, please continue with @/stores plan, and keep an eye to make sure there are no leftovers on @/store after migration.

Comment thread src/stores/wait.ts
Comment thread pnpm-lock.yaml
dependencies:
vue: 2.7.16

vue-demi@0.14.10(vue@2.7.16):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@pedrolamas

Copy link
Copy Markdown
Member

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 .then() when possible and instead use modern async... await where possible, so that might be a good target of refactoring too!

@tworkman08

Copy link
Copy Markdown
Contributor Author

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 .then() when possible and instead use modern async... await where possible, so that might be a good target of refactoring too!

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.

tworkman08 and others added 13 commits August 9, 2026 19:44
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>
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