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'
Task 5 —
context(PR 6)Gate: Phase 0 merged
Vuex module:
frontend/src/store/modules/context/index.jsNew file:
frontend/src/stores/context.jsNo persistence. No external dependencies for state/actions.
Note on
expertgetter: This getter reads fromaccount,product.assistant, andproduct.expert— all still on Vuex at this point. Use the bridge pattern to read from Vuex during migration; update the getter in PR 12 whenproduct-expertships.5.1 — Create the Pinia store
State and actions are simple — four properties, four actions. The
expertgetter is the only complex part. Port it with bridge reads for now: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— callscontext/updateRouteon every navigationpages/instance/— callssetInstance/clearInstancepages/device/— callssetDevicestore/modules/product/expert/index.js— readsrootGetters['context/expert']insendQuery(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 fromPINIA_COMPONENT_PATTERNS.md(mapState/mapActionsfrom Pinia) — this works correctly in all component types.5.3 — Delete the Vuex module
Remove the
contextimport and registration fromfrontend/src/store/index.js: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
5.5 — Export from stores index