Skip to content

Pinia Task 5 - context #6822

@n-lark

Description

@n-lark

Task 5 — context (PR 6)

Gate: Phase 0 merged
Vuex module: frontend/src/store/modules/context/index.js
New file: frontend/src/stores/context.js
No persistence. No external dependencies for state/actions.
Note on expert getter: This getter reads from account, product.assistant, and product.expert — all still on Vuex at this point. Use the bridge pattern to read from Vuex during migration; update the getter in PR 12 when product-expert ships.

5.1 — Create the Pinia store

State and actions are simple — four properties, four actions. The expert getter is the only complex part. Port it with bridge reads for now:

// frontend/src/stores/context.js
import { defineStore } from 'pinia'
import store from '../store/index.js' // bridge — remove after all deps migrated

export const useContextStore = defineStore('context', {
    state: () => ({
        route: null,
        instance: null,
        device: null
    }),
    getters: {
        // Bridge version — reads from Vuex for account/assistant/expert data.
        // Update in PR 12 (product-expert) to use Pinia store imports directly.
        expert (state) {
            const rootState = store.state
            const rootGetters = store.getters

            if (!state.route) {
                return {
                    assistantVersion: rootState.product.assistant.version,
                    assistantFeatures: rootState.product.assistant.assistantFeatures,
                    palette: null,
                    debugLog: null,
                    userId: rootState.account?.user?.id || null,
                    teamId: rootState.account?.team?.id || null,
                    teamSlug: rootState.account?.team?.slug || null,
                    instanceId: null,
                    deviceId: null,
                    applicationId: null,
                    isTrialAccount: rootGetters['account/isTrialAccount'] || false,
                    nodeRedVersion: rootState.product.assistant.nodeRedVersion,
                    pageName: null,
                    rawRoute: {},
                    selectedNodes: null,
                    scope: 'ff-app'
                }
            }

            // ... paste full existing getter logic from context/index.js
            // replacing rootState/rootGetters references with store.state / store.getters
        }
    },
    actions: {
        updateRoute (route) { this.route = route },
        setInstance (instance) { this.instance = instance },
        setDevice (device) { this.device = device },
        clearInstance () { this.instance = null }
    }
})

5.2 — Find and update all consumers

grep -rl "context/updateRoute\|context/setInstance\|context/setDevice\|context/clearInstance\|mapState.*context\|mapActions.*context" frontend/src/

Key consumers:

  • routes.js — calls context/updateRoute on every navigation
  • pages/instance/ — calls setInstance / clearInstance
  • pages/device/ — calls setDevice
  • store/modules/product/expert/index.js — reads rootGetters['context/expert'] in sendQuery (this stays as-is until PR 12)

For each consumer, check if it's inside a mixin or rendered as <component :is="..."> inside <ff-dialog> — use Pattern C from PINIA_COMPONENT_PATTERNS.md (mapState / mapActions from Pinia) — this works correctly in all component types.

5.3 — Delete the Vuex module

Remove the context import and registration from frontend/src/store/index.js:

// Remove:
import context from './modules/context/index.js'

// Update modules:
modules: { account, product }  // context removed

Delete frontend/src/store/modules/context/index.js.

Logout bridge: uncomment useContextStore().$reset() in the Vuex logout action (Task 0.7).

5.4 — Write store tests

// frontend/src/tests/stores/context.spec.js
import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach } from 'vitest'
import { useContextStore } from '@/stores/context.js'

describe('context store', () => {
    beforeEach(() => {
        setActivePinia(createPinia())
    })

    it('initializes with null route, instance, device', () => {
        const store = useContextStore()
        expect(store.route).toBeNull()
        expect(store.instance).toBeNull()
        expect(store.device).toBeNull()
    })

    it('setInstance / clearInstance manage instance ref', () => {
        const store = useContextStore()
        const fakeInstance = { id: 'abc' }
        store.setInstance(fakeInstance)
        expect(store.instance).toEqual(fakeInstance)
        store.clearInstance()
        expect(store.instance).toBeNull()
    })

    it('setDevice sets the device ref', () => {
        const store = useContextStore()
        store.setDevice({ id: 'dev-1' })
        expect(store.device).toEqual({ id: 'dev-1' })
    })

    it('updateRoute updates the route', () => {
        const store = useContextStore()
        store.updateRoute({ name: 'Home' })
        expect(store.route).toEqual({ name: 'Home' })
    })
})

5.5 — Export from stores index

export { useContextStore } from './context.js'

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Status

Closed / Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions