sim / rules
simstudioai/sim/.cursor/rules/sim-stores.mdc
Zustand store patterns and the workflow value state invariants
Cursor rule30k starsChanged yesterday
What's in it
- Zustand Store Patterns
- Basic Store
- Persisted Store
- Rules
- Outside React
- Workflow value state invariants
---
description: "Zustand store patterns and the workflow value state invariants"
globs: ["apps/sim/**/store.ts","apps/sim/**/stores/**/*.ts"]
---
<!-- Generated from .claude/rules/sim-stores.md by `bun run skills:sync`. Edit the source, not this file. -->
# Zustand Store Patterns
Stores live in `stores/`. Complex stores split into `store.ts` + `types.ts`.
## Basic Store
```typescript
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import type { FeatureState } from '@/stores/<feature>/types'
const initialState = { items: [] as Item[], activeId: null as string | null }
export const useFeatureStore = create<FeatureState>()(
devtools(
(set, get) => ({
...initialState,
setItems: (items) => set({ items }),
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
reset: () => set(initialState),
}),
{ name: 'feature-store' }
)
)
```
## Persisted Store
```typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useFeatureStore = create<FeatureState>()(
persist(
(set) => ({
width: 300,
setWidth: (width) => set({ width }),
_hasHydrated: false,
setHasHydrated: (v) => set({ _hasHydrated: v }),
}),
{
name: 'feature-state',
partialize: (state) => ({ width: state.width }),
onRehydrateStorage: () => (state) => state?.setHasHydrated(true),
}
)
)
```
## Rules
1. Use `devtools` middleware (named stores)
2. Use `persist` only when data should survive reload
3. `persist` MUST use `partialize` with an explicit whitelist of the durable fields. Exclude transient flags (`isResizing`, drag/hover state) and `_hasHydrated` from the whitelist, and never spread the whole state (`{ ...state }`) — it leaks actions and transient state into storage
4. `_hasHydrated` pattern for persisted stores needing hydration tracking
5. Immutable updates only
6. `set((state) => ...)` when depending on previous state
7. Provide `reset()` action
## Outside React
```typescript
const items = useFeatureStore.getState().items
useFeatureStore.setState({ items: newItems })
```
## Workflow value state invariants
Workflow state is split across two stores on purpose: `useWorkflowStore` holds block
structure (plus a hydration-time copy of each subblock value) and `useSubBlockStore`
holds live values, so per-keystroke edits don't re-render the canvas. Rules that keep
this split correct:
- The structure's `subBlocks[*].value` is stale after any edit. Never read it directly
for a current value — merge via `mergeSubblockState` (`@/stores/workflows/utils`,
which reads the subblock store) or `mergeSubblockStateWithValues` (the single merge
implementation, in `@sim/workflow-persistence/subblocks`), or read the subblock store. Exception: condition/router dynamic-handle subblocks dual-write the
structure (`syncDynamicHandleSubblockValue`) and may be read from either source.
- Merge semantics are tri-state: a key present in the subblock store wins — including
`null`, which means "explicitly cleared". Absent/`undefined` falls back to the
structure. Do not add merge or precedence logic anywhere else; if a new reader needs
different semantics, extend the shared merge.
- Every subblock-store write must go through `collaborativeSetSubblockValue` (or a
batch equivalent) so the identical value is persisted via the realtime server. A
store write that skips persistence makes the client's merged state diverge from the
DB draft, which deploy snapshots — producing phantom "Update" states on the deploy
button that clear on refresh. Hydration-derived local-only writes are allowed only
when change detection compensates, and the exemption list in the store's own
docstring (`workflows/subblock/store.ts`) is the record of which ones do and why —
keep the two in step.
- Change detection compensates by resolving each subblock value to the configuration
it represents (`lib/workflows/canonical/`), so a blank value and a value equal to
the field's declared `defaultValue` compare equal. It does NOT compensate for a
local-only write of a value the user could have chosen. If you cannot name the
declared default your write matches, it is not exempt.
- Deploy materializes declared defaults into `webhook.providerConfig`, so anything
reading a derived artifact back into the store is writing values the DB draft does
not have. That circularity is the origin of this whole failure mode; prefer not
reading derived artifacts back at all.
More agent context in simstudioai/sim
63 other files this repository gives its agents, the first 60 shown.
AGENTS.md
CLAUDE.md
Cursor rule
- .cursor/rules/constitution.mdc
- .cursor/rules/emcn-components.mdc
- .cursor/rules/landing-seo-geo.mdc
- .cursor/rules/sim-api-contracts.mdc
- .cursor/rules/sim-architecture.mdc
- .cursor/rules/sim-caching.mdc
- .cursor/rules/sim-components.mdc
- .cursor/rules/sim-hooks.mdc
- .cursor/rules/sim-imports.mdc
- .cursor/rules/sim-integrations.mdc
- .cursor/rules/sim-list-ordering.mdc
- .cursor/rules/sim-queries.mdc
- .cursor/rules/sim-react-performance.mdc
- .cursor/rules/sim-sandbox.mdc
- .cursor/rules/sim-settings-pages.mdc
- .cursor/rules/sim-styling.mdc
- .cursor/rules/sim-testing.mdc
- .cursor/rules/sim-ui-copy.mdc
- .cursor/rules/sim-url-state.mdc
Skill
- add-block-preview.agents/skills/add-block-preview/SKILL.md
- add-block.agents/skills/add-block/SKILL.md
- add-column-type.agents/skills/add-column-type/SKILL.md
- add-connector.agents/skills/add-connector/SKILL.md
- add-enrichment.agents/skills/add-enrichment/SKILL.md
- add-feature-flag.agents/skills/add-feature-flag/SKILL.md
- add-hosted-key.agents/skills/add-hosted-key/SKILL.md
- add-integration.agents/skills/add-integration/SKILL.md
- add-managed-cli.agents/skills/add-managed-cli/SKILL.md
- add-model.agents/skills/add-model/SKILL.md
- add-permission-group-item.agents/skills/add-permission-group-item/SKILL.md
- add-selector.agents/skills/add-selector/SKILL.md
- add-settings-page.agents/skills/add-settings-page/SKILL.md
- add-tools.agents/skills/add-tools/SKILL.md
- add-trigger.agents/skills/add-trigger/SKILL.md
- babysit.agents/skills/babysit/SKILL.md
- cleanup.agents/skills/cleanup/SKILL.md
- council.agents/skills/council/SKILL.md
- db-migrate.agents/skills/db-migrate/SKILL.md
- design-taste-frontend.agents/skills/design-taste-frontend/SKILL.md
- emcn-design-review.agents/skills/emcn-design-review/SKILL.md
- emil-design-eng.agents/skills/emil-design-eng/SKILL.md
- make-interfaces-feel-better.agents/skills/make-interfaces-feel-better/SKILL.md
- memory-load-check.agents/skills/memory-load-check/SKILL.md
- migrate-application-operation.agents/skills/migrate-application-operation/SKILL.md
- react-query-best-practices.agents/skills/react-query-best-practices/SKILL.md
- ship.agents/skills/ship/SKILL.md
- test-audit.agents/skills/test-audit/SKILL.md
- tool-registry-boundary.agents/skills/tool-registry-boundary/SKILL.md
- v2-api-conventions.agents/skills/v2-api-conventions/SKILL.md
- validate-connector.agents/skills/validate-connector/SKILL.md
- validate-integration.agents/skills/validate-integration/SKILL.md
- validate-model.agents/skills/validate-model/SKILL.md
- validate-permission-group-item.agents/skills/validate-permission-group-item/SKILL.md
- validate-selector.agents/skills/validate-selector/SKILL.md
- validate-trigger.agents/skills/validate-trigger/SKILL.md
- you-might-not-need-a-callback.agents/skills/you-might-not-need-a-callback/SKILL.md
- you-might-not-need-a-comment.agents/skills/you-might-not-need-a-comment/SKILL.md
- you-might-not-need-a-memo.agents/skills/you-might-not-need-a-memo/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

