store sync bugfix

This commit is contained in:
2026-04-17 21:52:43 -04:00
parent 6e91767c14
commit 9ca7908c0e
94 changed files with 88 additions and 18478 deletions

View File

@@ -430,7 +430,7 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
function setupStoreWatchers() {
// Watch for shape store changes and apply to TradingView
watch(
() => shapeStore.shapes,
() => shapeStore.$state,
async (newShapes, oldShapes) => {
if (isUpdatingStore || !isChartReady) return

View File

@@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { CustomIndicatorMetadata } from './indicators'
export interface CustomIndicatorType {
@@ -11,7 +10,12 @@ export interface CustomIndicatorType {
modified_at: number
}
export const useIndicatorTypesStore = defineStore('indicator_types', () => {
const types = ref<Record<string, CustomIndicatorType>>({})
return { types }
// Options API: flat $state = Record<string, CustomIndicatorType> matches backend initial state {}.
// Composition API would produce $state = { types: {} }, causing deepReplace to delete 'types' on
// the initial empty snapshot from the backend, which breaks $subscribe reactivity tracking.
export const useIndicatorTypesStore = defineStore('indicator_types', {
state: (): Record<string, CustomIndicatorType> => ({}),
getters: {
types: (state): Record<string, CustomIndicatorType> => state,
},
})

View File

@@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface CustomIndicatorParam {
type: 'int' | 'float' | 'bool' | 'string'
@@ -76,48 +75,48 @@ export interface IndicatorInstance {
custom_metadata?: CustomIndicatorMetadata
}
export const useIndicatorStore = defineStore('indicators', () => {
const indicators = ref<Record<string, IndicatorInstance>>({})
// Options API: flat $state = Record<string, IndicatorInstance> matches backend initial state {}.
// Composition API would produce $state = { indicators: {} }, causing deepReplace to delete
// 'indicators' on the initial empty snapshot, which breaks $subscribe reactivity tracking.
// The 'indicators' getter maintains backwards compatibility with existing callers.
export const useIndicatorStore = defineStore('indicators', {
state: (): Record<string, IndicatorInstance> => ({}),
// Helper methods
const addIndicator = (indicator: IndicatorInstance) => {
indicators.value[indicator.id] = indicator
}
getters: {
indicators: (state): Record<string, IndicatorInstance> => state,
},
const updateIndicator = (id: string, updates: Partial<IndicatorInstance>) => {
if (indicators.value[id]) {
const updated = {
...indicators.value[id],
...updates,
modified_at: Math.floor(Date.now() / 1000)
actions: {
addIndicator(indicator: IndicatorInstance) {
this.$patch({ [indicator.id]: indicator } as Partial<Record<string, IndicatorInstance>>)
},
updateIndicator(id: string, updates: Partial<IndicatorInstance>) {
const existing = (this.$state as Record<string, IndicatorInstance>)[id]
if (existing) {
this.$patch({
[id]: { ...existing, ...updates, modified_at: Math.floor(Date.now() / 1000) }
} as Partial<Record<string, IndicatorInstance>>)
}
indicators.value[id] = updated
}
}
},
const removeIndicator = (id: string) => {
delete indicators.value[id]
}
removeIndicator(id: string) {
this.$patch((state) => {
delete (state as Record<string, IndicatorInstance>)[id]
})
},
const getIndicator = (id: string): IndicatorInstance | undefined => {
return indicators.value[id]
}
getIndicator(id: string): IndicatorInstance | undefined {
return (this.$state as Record<string, IndicatorInstance>)[id]
},
const getAllIndicators = (): IndicatorInstance[] => {
return Object.values(indicators.value)
}
getAllIndicators(): IndicatorInstance[] {
return Object.values(this.$state as Record<string, IndicatorInstance>)
},
const getIndicatorsBySymbol = (symbol: string): IndicatorInstance[] => {
return Object.values(indicators.value).filter(ind => ind.symbol === symbol)
}
return {
indicators,
addIndicator,
updateIndicator,
removeIndicator,
getIndicator,
getAllIndicators,
getIndicatorsBySymbol
}
getIndicatorsBySymbol(symbol: string): IndicatorInstance[] {
return Object.values(this.$state as Record<string, IndicatorInstance>)
.filter(ind => ind.symbol === symbol)
},
},
})

View File

@@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface ResearchType {
display_name: string
@@ -8,7 +7,11 @@ export interface ResearchType {
modified_at: number
}
export const useResearchTypesStore = defineStore('research_types', () => {
const types = ref<Record<string, ResearchType>>({})
return { types }
// Options API: flat $state matches backend initial state {}.
// See indicatorTypes.ts for explanation of why composition API breaks $subscribe.
export const useResearchTypesStore = defineStore('research_types', {
state: (): Record<string, ResearchType> => ({}),
getters: {
types: (state): Record<string, ResearchType> => state,
},
})

View File

@@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface ControlPoint {
time: number
@@ -21,44 +20,38 @@ export interface Shape {
original_id?: string // Original ID from backend/agent before TradingView assigns its own ID
}
export const useShapeStore = defineStore('shapes', () => {
const shapes = ref<Record<string, Shape>>({})
// Use Options API so $state is a flat Record<string, Shape> matching the backend's shape store structure.
// Composition API would produce $state = { shapes: {} } (extra nesting), which causes deepReplace in
// useStateSync to delete the 'shapes' key when the backend sends an empty {} snapshot, breaking $subscribe.
export const useShapeStore = defineStore('shapes', {
state: () => ({} as Record<string, Shape>),
// Helper methods
const addShape = (shape: Shape) => {
shapes.value[shape.id] = shape
}
actions: {
addShape(shape: Shape) {
this.$patch({ [shape.id]: shape } as Partial<Record<string, Shape>>)
},
const updateShape = (id: string, updates: Partial<Shape>) => {
if (shapes.value[id]) {
const updated = {
...shapes.value[id],
...updates,
modified_at: Math.floor(Date.now() / 1000)
updateShape(id: string, updates: Partial<Shape>) {
const existing = (this.$state as Record<string, Shape>)[id]
if (existing) {
this.$patch({
[id]: { ...existing, ...updates, modified_at: Math.floor(Date.now() / 1000) }
} as Partial<Record<string, Shape>>)
}
// Replace the entire shape object to ensure arrays are replaced atomically
shapes.value[id] = updated
}
}
},
const removeShape = (id: string) => {
delete shapes.value[id]
}
removeShape(id: string) {
this.$patch((state) => {
delete (state as Record<string, Shape>)[id]
})
},
const getShape = (id: string): Shape | undefined => {
return shapes.value[id]
}
getShape(id: string): Shape | undefined {
return (this.$state as Record<string, Shape>)[id]
},
const getAllShapes = (): Shape[] => {
return Object.values(shapes.value)
}
return {
shapes,
addShape,
updateShape,
removeShape,
getShape,
getAllShapes
}
getAllShapes(): Shape[] {
return Object.values(this.$state as Record<string, Shape>)
},
},
})

View File

@@ -1,5 +1,4 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface StrategyType {
display_name: string
@@ -8,7 +7,11 @@ export interface StrategyType {
modified_at: number
}
export const useStrategyTypesStore = defineStore('strategy_types', () => {
const types = ref<Record<string, StrategyType>>({})
return { types }
// Options API: flat $state matches backend initial state {}.
// See indicatorTypes.ts for explanation of why composition API breaks $subscribe.
export const useStrategyTypesStore = defineStore('strategy_types', {
state: (): Record<string, StrategyType> => ({}),
getters: {
types: (state): Record<string, StrategyType> => state,
},
})