-
Notifications
You must be signed in to change notification settings - Fork 83
Description
Task 8 — product-assistant (PR 9)
Gate: Phase 0 merged
Vuex module: frontend/src/store/modules/product/assistant/index.js
New file: frontend/src/stores/product-assistant.js
No persistence.
Cross-store dependency: immersiveInstance and immersiveDevice getters read from context store (already on Pinia after PR 6). allowedInboundOrigins getter depends on these. sendMessage action posts to the instance iframe.
8.1 — Create the Pinia store
The state and all actions are a direct port. The key things to note:
immersiveInstanceandimmersiveDevicegetters now importuseContextStore()directly instead of readingrootState.contextsendMessageusesmessagingService()— no change needed there- The
eventsRegistryconstant andALL_CONTEXT_OPTIONSarray can be moved to a separateproduct-assistant.constants.jsfile or kept at the top of the store file
// frontend/src/stores/product-assistant.js
import { defineStore } from 'pinia'
import messagingService from '../services/messaging.service.js'
import { useContextStore } from './context.js'
// eventsRegistry and ALL_CONTEXT_OPTIONS copied verbatim from Vuex module...
export const useProductAssistantStore = defineStore('product-assistant', {
state: () => ({
version: null,
supportedActions: {},
assistantFeatures: {},
palette: {},
scope: { target: 'nr-assistant', scope: 'flowfuse-expert', source: 'flowfuse-expert' },
nodeRedVersion: null,
selectedNodes: [],
selectedContext: [...ALL_CONTEXT_OPTIONS],
debugLog: [],
editorState: { /* initialised from eventsRegistry */ }
}),
getters: {
immersiveInstance: () => useContextStore().instance, // ← direct Pinia import
immersiveDevice: () => useContextStore().device, // ← direct Pinia import
hasUserSelection: (state) => state.selectedNodes.length > 0,
hasContextSelection: (state) => state.selectedContext.length > 0,
isFeaturePaletteEnabled: (state) => state.assistantFeatures?.commands?.['get-palette']?.enabled ?? false,
isFeatureDebugLogEnabled: (state) => state.assistantFeatures?.debugLog?.enabled ?? false,
availableContextOptions (state) { /* same logic */ },
paletteContribOnly (state) { /* same logic */ },
debugLog (state) { /* same logic */ },
allowedInboundOrigins () {
const allowed = [window.origin]
const instance = useContextStore().instance
const device = useContextStore().device
if (instance?.url) allowed.push(instance.url)
if (device?.editor?.url) allowed.push(device.editor.url)
return allowed
},
isEditorRunning: (state) => state.editorState?.flowsLoaded || state.editorState?.runtimeState?.state === 'start'
},
actions: {
handleMessage (payload) { /* same switch logic, dispatch → this.actionName() */ },
sendMessage (payload) {
const service = messagingService()
return service.sendMessage({
message: { ...payload, ...this.scope },
target: window.frames['immersive-editor-iframe'],
targetOrigin: this.immersiveInstance?.url
})
},
// all other actions are simple state assignments — port directly
reset () { Object.assign(this, initialState()) }
}
})8.2 — Find and update all consumers
grep -rl "product/assistant\|mapState.*assistant\|mapGetters.*assistant\|mapActions.*assistant" frontend/src/Primary consumers: expert assistant Vue components in frontend/src/components/expert/.
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.
Also update messaging.service.js:112: replace this.$store.dispatch('product/assistant/handleMessage', event) with useProductAssistantStore().handleMessage(event).
8.3 — Update context.js expert getter (partial)
Now that product-assistant is on Pinia, update the expert getter in context.js to import useProductAssistantStore() directly instead of reading from store.state.product.assistant. Remove the bridge reads for assistant data.
8.4 — Delete the Vuex module
Remove assistant from store/modules/product/index.js modules registration. Delete frontend/src/store/modules/product/assistant/index.js.
Logout bridge: uncomment useProductAssistantStore().$reset() in the Vuex logout action (Task 0.7).
8.5 — Write store tests
// frontend/src/tests/stores/product-assistant.spec.js
import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach } from 'vitest'
import { useProductAssistantStore } from '@/stores/product-assistant.js'
import { useContextStore } from '@/stores/context.js'
describe('product-assistant store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('immersiveInstance reads from contextStore', () => {
const store = useProductAssistantStore()
const contextStore = useContextStore()
contextStore.setInstance({ id: 'inst-1', url: 'http://localhost' })
expect(store.immersiveInstance).toEqual({ id: 'inst-1', url: 'http://localhost' })
})
it('allowedInboundOrigins includes window.origin and instance url', () => {
const store = useProductAssistantStore()
const contextStore = useContextStore()
contextStore.setInstance({ id: 'inst-1', url: 'http://node-red.local' })
const origins = store.allowedInboundOrigins
expect(origins).toContain(window.origin)
expect(origins).toContain('http://node-red.local')
})
it('reset clears state back to initial values', () => {
const store = useProductAssistantStore()
store.version = '2.0'
store.reset()
expect(store.version).toBeNull()
})
})8.6 — Export from stores index
export { useProductAssistantStore } from './product-assistant.js'Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Status