From abf2ee5d25f936e85287a7492b3f43a706ad451a Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 20 Jul 2026 22:02:16 +0300 Subject: [PATCH 1/2] fix(docs): polish homepage source pills and install guides Use real brand marks, level hero snippets, Blume package-install tabs, and fix PersistableSource subscribe shapes in docs examples. --- apps/docs/content/guides/getting-started.mdx | 11 ++--- apps/docs/content/guides/idb-react.mdx | 4 +- apps/docs/content/guides/migrating.mdx | 8 +++- apps/docs/content/recipes/wrapping-stores.mdx | 4 +- apps/docs/pages/_home/Hero.astro | 13 +++--- apps/docs/pages/_home/InstallBox.astro | 6 ++- apps/docs/pages/_home/Seams.astro | 8 ++-- apps/docs/pages/_home/source-snippets.ts | 41 +++++++++--------- apps/docs/public/brands/jotai.png | Bin 0 -> 5006 bytes apps/docs/public/brands/mobx.svg | 1 + apps/docs/public/brands/tanstack.svg | 1 + apps/docs/public/brands/valtio.svg | 6 +++ apps/docs/public/brands/zustand.png | Bin 0 -> 7215 bytes 13 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 apps/docs/public/brands/jotai.png create mode 100644 apps/docs/public/brands/mobx.svg create mode 100644 apps/docs/public/brands/tanstack.svg create mode 100644 apps/docs/public/brands/valtio.svg create mode 100644 apps/docs/public/brands/zustand.png diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx index b47135d..7a11ce5 100644 --- a/apps/docs/content/guides/getting-started.mdx +++ b/apps/docs/content/guides/getting-started.mdx @@ -9,15 +9,16 @@ You already have a store. You need durable state without a first-paint flash — ## Install -```bash -bun add @stainless-code/persist +```package-install +npm i @stainless-code/persist ``` Core is zero-dep. Each subpath is an optional peer — install only what you import. Full table: [Entry points](/concepts/entry-points). -```bash -# peers for the quick-start imports below -bun add seroval idb-keyval @tanstack/store react +Peers for the quick-start imports below: + +```package-install +npm i seroval idb-keyval @tanstack/store react ``` ## Quick start diff --git a/apps/docs/content/guides/idb-react.mdx b/apps/docs/content/guides/idb-react.mdx index 558f5da..88431e2 100644 --- a/apps/docs/content/guides/idb-react.mdx +++ b/apps/docs/content/guides/idb-react.mdx @@ -7,8 +7,8 @@ search: IndexedDB reads are Promise-backed — they cannot settle before first paint. This guide wires `createIdbStorage`, TanStack Store, and a React hydration gate. -```bash -bun add @stainless-code/persist @tanstack/store react idb-keyval +```package-install +npm i @stainless-code/persist @tanstack/store react idb-keyval ``` **Store module** — `createIdbStorage` runs structured-clone mode: `Set` / `Map` / `Date` round-trip natively, no codec. The persist + hydration signal live at module scope for an app-lifetime singleton store. diff --git a/apps/docs/content/guides/migrating.mdx b/apps/docs/content/guides/migrating.mdx index 64991d7..0c9b08c 100644 --- a/apps/docs/content/guides/migrating.mdx +++ b/apps/docs/content/guides/migrating.mdx @@ -91,7 +91,9 @@ const reduxSource = { getState: () => store.getState(), setState: (updater) => store.dispatch({ type: "PERSIST_SET", payload: updater(store.getState()) }), - subscribe: (listener) => store.subscribe(listener), + subscribe: (listener) => ({ + unsubscribe: store.subscribe(listener), + }), }; const persist = persistSource(reduxSource, { name: "root", @@ -142,7 +144,9 @@ const piniaSource = { setState: (updater) => { piniaStore.$state = updater(piniaStore.$state); }, - subscribe: (listener) => piniaStore.$subscribe(() => listener()), + subscribe: (listener) => ({ + unsubscribe: piniaStore.$subscribe(() => listener()), + }), }; const persist = persistSource(piniaSource, { name: "prefs", diff --git a/apps/docs/content/recipes/wrapping-stores.mdx b/apps/docs/content/recipes/wrapping-stores.mdx index 49ae74b..78bb9e3 100644 --- a/apps/docs/content/recipes/wrapping-stores.mdx +++ b/apps/docs/content/recipes/wrapping-stores.mdx @@ -81,7 +81,9 @@ const persist = persistSource( { getState: () => myStore.getState(), setState: (updater) => myStore.setState(updater), - subscribe: (listener) => myStore.subscribe(() => listener()), + subscribe: (listener) => ({ + unsubscribe: myStore.subscribe(() => listener()), + }), }, { name: "app:custom:v1", diff --git a/apps/docs/pages/_home/Hero.astro b/apps/docs/pages/_home/Hero.astro index fdc7e56..1c80810 100644 --- a/apps/docs/pages/_home/Hero.astro +++ b/apps/docs/pages/_home/Hero.astro @@ -3,14 +3,13 @@ import CodeBlock from "blume/components/content/CodeBlock.astro"; import Icon from "blume/components/Icon.astro"; import { contentHref } from "blume/components/content/base-href.ts"; import InstallBox from "./InstallBox.astro"; -import { sourceSnippets } from "./source-snippets.ts"; +import { defaultSourceId, sourceSnippets } from "./source-snippets.ts"; const pillBase = "cursor-pointer rounded-full border px-3 py-1 text-xs transition active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"; const pillActive = "border-accent bg-accent/10 font-medium text-accent"; const pillMuted = "border-border text-muted-foreground hover:border-foreground/40 hover:text-foreground"; -const defaultSource = "tanstack-store"; ---
@@ -56,12 +55,16 @@ const defaultSource = "tanstack-store"; { sourceSnippets.map((snippet) => ( )) @@ -72,7 +75,7 @@ const defaultSource = "tanstack-store"; { sourceSnippets.map((snippet) => (
diff --git a/apps/docs/pages/_home/InstallBox.astro b/apps/docs/pages/_home/InstallBox.astro index cfce402..e7633a2 100644 --- a/apps/docs/pages/_home/InstallBox.astro +++ b/apps/docs/pages/_home/InstallBox.astro @@ -1,7 +1,11 @@ --- import Icon from "blume/components/Icon.astro"; +import { defaultSourceId, sourceSnippets } from "./source-snippets.ts"; -const installCommand = "bun add @stainless-code/persist"; +// First paint — Hero's click handler swaps the command later. +const installCommand = + sourceSnippets.find((s) => s.id === defaultSourceId)?.installCommand ?? + "bun add @stainless-code/persist"; ---
localStorage), -}); -export const prefsHydration = toHydrationSignal(persist);`, + storage: createJSONStorage(() => localStorage), +});`, }, { id: "zustand", - label: "zustand", - icon: "boxes", + label: "Zustand", + icon: "/brands/zustand.png", lang: "ts", installCommand: "bun add @stainless-code/persist zustand", code: `import { create } from "zustand"; @@ -46,8 +45,8 @@ persistStore(usePrefs, { }, { id: "jotai", - label: "jotai", - icon: "atom", + label: "Jotai", + icon: "/brands/jotai.png", lang: "ts", installCommand: "bun add @stainless-code/persist jotai", code: `import { atom, createStore } from "jotai"; @@ -63,8 +62,8 @@ persistAtom(store, themeAtom, { }, { id: "valtio", - label: "valtio", - icon: "activity", + label: "Valtio", + icon: "/brands/valtio.svg", lang: "ts", installCommand: "bun add @stainless-code/persist valtio", code: `import { proxy } from "valtio"; @@ -79,8 +78,8 @@ persistProxy(prefs, { }, { id: "mobx", - label: "mobx", - icon: "network", + label: "MobX", + icon: "/brands/mobx.svg", lang: "ts", installCommand: "bun add @stainless-code/persist mobx", code: `import { observable } from "mobx"; @@ -103,9 +102,11 @@ persistObservable(prefs, { persistSource( { - getState: () => myStore.get(), - setState: (next) => myStore.set(next), - subscribe: (listener) => myStore.subscribe(listener), + getState: () => myStore.getState(), + setState: (updater) => myStore.setState(updater), + subscribe: (listener) => ({ + unsubscribe: myStore.subscribe(() => listener()), + }), }, { name: "app:prefs:v1", diff --git a/apps/docs/public/brands/jotai.png b/apps/docs/public/brands/jotai.png new file mode 100644 index 0000000000000000000000000000000000000000..58fce5d39197f477de234df29d318ed112898fa8 GIT binary patch literal 5006 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 diff --git a/apps/docs/public/brands/tanstack.svg b/apps/docs/public/brands/tanstack.svg new file mode 100644 index 0000000..9c596b7 --- /dev/null +++ b/apps/docs/public/brands/tanstack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/docs/public/brands/valtio.svg b/apps/docs/public/brands/valtio.svg new file mode 100644 index 0000000..a476bc6 --- /dev/null +++ b/apps/docs/public/brands/valtio.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/public/brands/zustand.png b/apps/docs/public/brands/zustand.png new file mode 100644 index 0000000000000000000000000000000000000000..c458b052021e63edfd34f8d348f7982aee6dab5a GIT binary patch literal 7215 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 Date: Mon, 20 Jul 2026 22:05:30 +0300 Subject: [PATCH 2/2] fix(docs): make migrating examples match real adapters Stay on zustand for the zustand-persist path, define the Pinia store, and use $patch for setState. --- apps/docs/content/guides/migrating.mdx | 93 ++++++++++++++------------ 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/apps/docs/content/guides/migrating.mdx b/apps/docs/content/guides/migrating.mdx index 0c9b08c..16a2f08 100644 --- a/apps/docs/content/guides/migrating.mdx +++ b/apps/docs/content/guides/migrating.mdx @@ -37,19 +37,17 @@ export const usePrefs = create( }), ); -// @stainless-code/persist -import { Store } from "@tanstack/store"; +// @stainless-code/persist — same zustand store, adapter instead of middleware +import { create } from "zustand"; import { createJSONStorage, toHydrationSignal } from "@stainless-code/persist"; -import { persistStore } from "@stainless-code/persist/sources/tanstack-store"; -import { useHydrated } from "@stainless-code/persist/frameworks/react"; +import { persistStore } from "@stainless-code/persist/sources/zustand"; -export const prefsStore = new Store({ theme: "light" }); -const persist = persistStore(prefsStore, { +export const usePrefs = create(() => ({ theme: "light" })); +const persist = persistStore(usePrefs, { name: "app:prefs:v1", storage: createJSONStorage(() => localStorage), }); export const prefsHydration = toHydrationSignal(persist); -// const { hydrated } = useHydrated(prefsHydration); ``` ## Migrating from redux-persist @@ -73,6 +71,7 @@ redux-persist reconciles whole reducer trees implicitly; here `merge` is explici ```ts // redux-persist +import { createStore } from "redux"; import { persistReducer, persistStore } from "redux-persist"; import storage from "redux-persist/lib/storage"; @@ -80,25 +79,32 @@ const persistedReducer = persistReducer({ key: "root", storage }, rootReducer); export const store = createStore(persistedReducer); persistStore(store); -// @stainless-code/persist — wrap the redux store (dispatch ↔ setState) +// @stainless-code/persist — plain store + PersistableSource (dispatch ↔ setState) +import { createStore } from "redux"; import { createJSONStorage, persistSource, toHydrationSignal, } from "@stainless-code/persist"; -const reduxSource = { - getState: () => store.getState(), - setState: (updater) => - store.dispatch({ type: "PERSIST_SET", payload: updater(store.getState()) }), - subscribe: (listener) => ({ - unsubscribe: store.subscribe(listener), - }), -}; -const persist = persistSource(reduxSource, { - name: "root", - storage: createJSONStorage(() => localStorage), -}); +export const store = createStore(rootReducer); // rootReducer handles PERSIST_SET +const persist = persistSource( + { + getState: () => store.getState(), + setState: (updater) => + store.dispatch({ + type: "PERSIST_SET", + payload: updater(store.getState()), + }), + subscribe: (listener) => ({ + unsubscribe: store.subscribe(listener), + }), + }, + { + name: "root", + storage: createJSONStorage(() => localStorage), + }, +); export const rootHydration = toHydrationSignal(persist); ``` @@ -120,38 +126,43 @@ pinia-persist is a Pinia plugin; here persistence is a middleware call on any re ```ts // pinia-persist -import { createPinia } from "pinia"; +import { createPinia, defineStore } from "pinia"; import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; const pinia = createPinia(); -pinia.use( - piniaPluginPersistedstate({ - key: "prefs", - storage: localStorage, - paths: ["theme"], - }), -); +pinia.use(piniaPluginPersistedstate); +export const usePrefsStore = defineStore("prefs", { + state: () => ({ theme: "light", draft: "" }), + persist: { key: "prefs", paths: ["theme"] }, +}); // @stainless-code/persist — wrap $state / $subscribe +import { defineStore } from "pinia"; import { createJSONStorage, persistSource, toHydrationSignal, } from "@stainless-code/persist"; -const piniaSource = { - getState: () => piniaStore.$state, - setState: (updater) => { - piniaStore.$state = updater(piniaStore.$state); - }, - subscribe: (listener) => ({ - unsubscribe: piniaStore.$subscribe(() => listener()), - }), -}; -const persist = persistSource(piniaSource, { - name: "prefs", - storage: createJSONStorage(() => localStorage), - partialize: (s) => ({ theme: s.theme }), +export const usePrefsStore = defineStore("prefs", { + state: () => ({ theme: "light", draft: "" }), }); +const prefsStore = usePrefsStore(); +const persist = persistSource( + { + getState: () => prefsStore.$state, + setState: (updater) => { + prefsStore.$patch(updater(prefsStore.$state)); + }, + subscribe: (listener) => ({ + unsubscribe: prefsStore.$subscribe(() => listener()), + }), + }, + { + name: "prefs", + storage: createJSONStorage(() => localStorage), + partialize: (s) => ({ theme: s.theme }), + }, +); export const prefsHydration = toHydrationSignal(persist); ```