skills/zustand-store-scaffold/SKILL.md
Scaffold class-based Zustand stores with flattenActions: web (component-level store + Context + Provider) and core (slice-based store with immer). Class-based actions provide Go-to-Definition DX, #private field encapsulation, and prototype-safe slice composition.
npx skillsauth add adonis0123/adonis-skills zustand-store-scaffoldInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate type-safe class-based Zustand stores with flattenActions for prototype-safe slice composition.
python3 ~/.claude/skills/zustand-store-scaffold/scripts/scaffold_zustand_store.py \
--pattern web \
--name ToolList \
--path web/src/pages/_components/ToolList/store
Generates:
types.ts — StoreSetter<T> type definitionutils/flattenActions.ts — Prototype-safe action flattenerindex.ts — Class-based store with ActionImpl + flattenActionscontext.ts — React Context and typed useContext hookprovider.tsx — Memo-wrapped Provider componentSingle Slice:
python3 ~/.claude/skills/zustand-store-scaffold/scripts/scaffold_zustand_store.py \
--pattern core \
--name CoreAgent \
--path packages/ag-ui-view/src/core/helpers/isomorphic/store
Multiple Slices (Interactive):
python3 ~/.claude/skills/zustand-store-scaffold/scripts/scaffold_zustand_store.py \
--pattern core \
--name AppStore \
--path src/store
# Will prompt: Enter slice names (comma-separated, or press Enter for 'core'):
# Example input: auth,user,ui
Multiple Slices (Direct):
python3 ~/.claude/skills/zustand-store-scaffold/scripts/scaffold_zustand_store.py \
--pattern core \
--name AppStore \
--path src/store \
--slices auth,user,ui
Generates:
types.ts — StoreSetter<T> type definitionutils/flattenActions.ts — Prototype-safe action flattenerindex.ts — Store factory with flattenActions slice compositionslices/[name].ts — Class-based slice with ActionImpl + #private fields| Option | Required | Description | Example |
|--------|----------|-------------|---------|
| --pattern | Yes | Store pattern (web or core) | --pattern web |
| --name | Yes | Store name in PascalCase | --name ToolList |
| --path | Yes | Target directory path | --path src/store |
| --slices | No | Comma-separated slice names (core only) | --slices auth,user,ui |
| --force | No | Overwrite existing files | --force |
Note: For core pattern without --slices, the script interactively asks for slice names. Shared files (types.ts, utils/flattenActions.ts) are skipped if they already exist (unless --force).
| Pattern | Use When | Location Pattern | Slices |
|---------|----------|------------------|--------|
| web | Component-level state with Provider | web/src/pages/_components/**/store | N/A |
| core | Isomorphic/shared state with slices | packages/**/store | Single or multiple |
Use multiple slices to organize complex state by feature domains:
| Scenario | Recommended Slices |
|----------|-------------------|
| Authentication app | auth, user, session |
| E-commerce store | cart, products, user, ui |
| Dashboard | data, filters, ui, settings |
| Chat application | messages, contacts, ui, notifications |
| File | Purpose |
|------|---------|
| types.ts | StoreSetter<T> — type-safe overloaded setter matching Zustand internals |
| utils/flattenActions.ts | flattenActions<T> — walks prototype chain, binds methods, merges class instances |
| index.ts | Store factory combining initial state + flattenActions composition |
| slices/*.ts (core) | *ActionImpl class with #set/#get, exported via create*Slice |
| context.ts (web) | React Context + typed selector hook |
| provider.tsx (web) | Memo-wrapped Provider with ref-stable store creation |
Define state in *SliceState interface (core) or *StoreState interface (web):
export interface CoreSliceState {
agents: Agent[]
selectedId: string | null
}
Add action methods as arrow functions in *ActionImpl class:
export class CoreActionImpl {
readonly #set: StoreSetter<CoreSlice>
readonly #get: () => CoreSlice
// ...constructor
selectAgent = (id: string): void => {
this.#set({ selectedId: id })
}
getSelectedAgent = (): Agent | undefined => {
const { agents, selectedId } = this.#get()
return agents.find((a) => a.id === selectedId)
}
}
Set initial state in the store factory or config:
const store = createCoreAgentStore({
initialState: { agents: [], selectedId: null }
})
import Provider from './store/provider'
import { useToolListContext } from './store/context'
function ToolListPage() {
return (
<Provider>
<ToolListContent />
</Provider>
)
}
function ToolListContent() {
const toolList = useToolListContext((s) => s.toolList)
const setToolList = useToolListContext((s) => s.setToolList)
// Go to Definition on setToolList → lands directly on ActionImpl method
}
import { createCoreAgentStore } from './store'
const store = createCoreAgentStore({
initialState: { agents: [], selectedId: null }
})
// Vanilla JS
const state = store.getState()
state.selectAgent('agent-1')
// React (with useStore)
import { useStore } from 'zustand'
function AgentList() {
const agents = useStore(store, (s) => s.agents)
const selectAgent = useStore(store, (s) => s.selectAgent)
}
references/class-based-pattern.md for detailed pattern documentationreferences/store-patterns.md for complete code examplestools
Use when the user's pain is "adding/removing one more X means editing N files" and X is a recurring variant kind: popup, banner, modal, ad slot, payment method, AI model/tool, form field type, connector, sub-site, command, menu item, agent, extension point, or data source. Use when they want to design, refactor, review, name, or explain a pluggable mechanism using registry, interface/trait contract, runtime core, and convention folders; mention pluginize, pluggable, plugin architecture, extension point, registry pattern, or extensibility. Use when explaining the first-principles rationale, DDD/SOLID/OCP mapping, or industry analogies behind that structure. Use for cross-stack mapping to VSCode contributes, Webpack/Vite plugins, Rust/Tauri connectors, Python entry_points, or cargo features. Skip one variant's internals/styles/hooks/copy/bugs, and skip register/registry meaning DI container, user signup, or package registry.
development
Use BEFORE heavier workflow skills when route choice matters. Route creative work without a design doc/spec to Brainstorm; destructive or hard-to-reverse work to Discuss; unresolved decisions, Plan/Full fan-out, ship checks, unclear bugs, and fresh-eyes fix-then-re-review need this gate. Skip single-line read-only lookups, pure typo/formatting edits, trivial safe one-line fixes, and clearly safe named-skill requests. Outputs Route, Runtime skill, Fallback alias, and Execution path.
development
Cross-agent code review handoff and review-fix-re-review loop with persistent packet artifacts. Requires a git repo because packet addressing uses git rev-parse --show-toplevel. Use when the user asks for an independent, read-only second pair of eyes on a diff/branch/PR another agent or teammate implemented; asks to verify reviewer feedback before fixing; says a fix is done and wants scoped re-review; asks to continue the latest review packet; or asks for first-principles, DDD, high-cohesion/low-coupling review. Persists each loop under $repo_root/.review-handoff/active/ so agents can resume without copy-paste. Do NOT use for ordinary implementation, generic staged-change review, review-comment copy editing, non-git folders/zips/tarballs/temp dirs, or when the user names a different review skill.
testing
Enforces 'decide then plan' discipline - the pre-planning decision gate. Use when the user asks for a plan or starts a change while key decisions are unresolved: architecture tradeoffs, data flow, public interfaces, unclear requirements, multi-module scope, or roughly 5+ files affected. Also triggers when the user explicitly wants to discuss, compare options, or review architecture before committing. Core job: reduce incorrect-execution cost by confirming decisions before producing executable plans.