Skip to content

Commit 94a296b

Browse files
icecrasher321claude
andcommitted
fix(config): re-evaluate option lists when the host shape lands after mount
A host context served by an app version that predates the deployment field leaves the browser on the env fallback until a refetch carries the shape. Block option builders read the shape outside React, so the sub-block combobox now subscribes to it and keys its option memo on it; the reader hands out one stable fallback object per document so that dependency only changes when the shape does. The host-provider test now renders without query data first and then lets a refetch land, so the effect path that follows a later host context is exercised rather than the initial seed twice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 27b563a commit 94a296b

3 files changed

Lines changed: 36 additions & 7 deletions

File tree

apps/sim/app/workspace/[workspaceId]/providers/workspace-host-provider.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,18 @@ describe('WorkspaceHostProvider', () => {
114114
expect(getDeploymentShape()).toBe(HOST_CONTEXT.deployment)
115115
})
116116

117-
it('follows the refetched host context over the initial one', () => {
117+
it('follows a host context that arrives after mount over the initial seed', () => {
118+
renderProvider(HOST_CONTEXT)
119+
expect(textOf('context')).toBe('true')
120+
expect(getDeploymentShape().billingEnabled).toBe(true)
121+
118122
mockUseWorkspaceHostContextQuery.mockReturnValue({
119123
data: {
120124
...HOST_CONTEXT,
121125
deployment: { ...HOST_CONTEXT.deployment!, billingEnabled: false },
122126
},
123127
error: null,
124128
})
125-
126129
renderProvider(HOST_CONTEXT)
127130

128131
expect(textOf('context')).toBe('false')

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { Combobox, type ComboboxOption, cn } from '@sim/emcn'
33
import { Plus } from '@sim/emcn/icons'
44
import { useReactFlow } from '@xyflow/react'
5+
import { useDeploymentShape } from '@/lib/core/config/deployment-shape'
56
import type { SelectorKey } from '@/lib/selectors/manifest'
67
import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state'
78
import { getDependsOnFields } from '@/lib/workflows/subblocks/dependencies'
@@ -127,6 +128,13 @@ export const ComboBox = memo(function ComboBox({
127128
: undefined
128129
)
129130

131+
/**
132+
* Option builders such as the model list and the Function block's languages read the
133+
* deployment shape outside React, so the list is keyed on the subscribed shape as well:
134+
* a host context that lands after mount (an app version rolling out the field) must
135+
* re-evaluate them rather than leave the env fallback's list in place.
136+
*/
137+
const deploymentShape = useDeploymentShape()
130138
const staticOptions = useMemo(() => {
131139
const opts =
132140
typeof options === 'function'
@@ -138,7 +146,7 @@ export const ComboBox = memo(function ComboBox({
138146
}
139147

140148
return opts
141-
}, [options, blockValues, subBlockId, isModelUsable])
149+
}, [options, blockValues, subBlockId, isModelUsable, deploymentShape])
142150

143151
const [selectorSearch, setSelectorSearch] = useState('')
144152
const debouncedSelectorSearch = useDebounce(selectorSearch.trim(), SEARCH_DEBOUNCE_MS)

apps/sim/lib/core/config/deployment-shape.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ const useDeploymentShapeStore = create<DeploymentShapeState>()(
6161
)
6262
)
6363

64+
/**
65+
* The browser's env fallback, built once per document. The env constants it packages are
66+
* themselves frozen at module init, so caching changes nothing semantically, and it gives
67+
* {@link useDeploymentShape} a stable reference that memo dependencies can key on.
68+
*/
69+
let browserEnvFallback: DeploymentShape | null = null
70+
71+
function browserFallbackShape(): DeploymentShape {
72+
browserEnvFallback ??= resolveDeploymentShape()
73+
return browserEnvFallback
74+
}
75+
6476
/**
6577
* The shape this runtime's own configuration resolves to. On the server that is the
6678
* deployment's truth, and what the workspace host context projects. In the browser it
@@ -117,8 +129,9 @@ export function seedDeploymentShape(shape: DeploymentShape | undefined): void {
117129
seed(shape)
118130
}
119131

120-
/** Drops the seeded shape. For tests; the app never unseeds on purpose. */
132+
/** Drops the seeded shape and the cached fallback. For tests; the app never unseeds on purpose. */
121133
export function resetDeploymentShape(): void {
134+
browserEnvFallback = null
122135
useDeploymentShapeStore.getState().reset()
123136
}
124137

@@ -130,11 +143,16 @@ export function resetDeploymentShape(): void {
130143
*/
131144
export function getDeploymentShape(): DeploymentShape {
132145
if (typeof window === 'undefined') return resolveDeploymentShape()
133-
return useDeploymentShapeStore.getState().seeded ?? resolveDeploymentShape()
146+
return useDeploymentShapeStore.getState().seeded ?? browserFallbackShape()
134147
}
135148

136-
/** {@link getDeploymentShape} for components, subscribed to the seeded value. */
149+
/**
150+
* {@link getDeploymentShape} for components, subscribed to the seeded value. Returns the
151+
* same object until the shape actually changes, so it is safe as a memo dependency for
152+
* option lists and other derived values that read the shape outside React.
153+
*/
137154
export function useDeploymentShape(): DeploymentShape {
138155
const seeded = useDeploymentShapeStore((state) => state.seeded)
139-
return seeded ?? resolveDeploymentShape()
156+
if (seeded) return seeded
157+
return typeof window === 'undefined' ? resolveDeploymentShape() : browserFallbackShape()
140158
}

0 commit comments

Comments
 (0)