From 28d57323b69ebe6aba87d14a9983321293c15b29 Mon Sep 17 00:00:00 2001 From: Brian Kirkpatrick Date: Wed, 4 Mar 2026 12:23:40 -0800 Subject: [PATCH] cleanup --- README.md | 207 +++++++++++++++++++++++++++++--- importmap.json | 9 -- store/idb.js | 33 +++++ store/index.js | 27 ++++- store/middleware/persistence.js | 15 +-- tasks.md | 60 +++++++++ 6 files changed, 316 insertions(+), 35 deletions(-) delete mode 100644 importmap.json create mode 100644 store/idb.js create mode 100644 tasks.md diff --git a/README.md b/README.md index f97c3b8..7f53eba 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,36 @@ -## Architecture +# Lit + Zustand Architecture -* Lit-based web components +A modern, no-build web application architecture using Lit web components with Zustand state management and IndexedDB persistence. -* No-build tooling (importmap only) +## For TSX/React Developers + +If you're coming from React/TSX, here's how this architecture maps to familiar concepts: + +| React/TSX Pattern | This Architecture | +|-------------------|-------------------| +| `useState` / `useReducer` | Zustand vanilla store | +| `useEffect` + state subscription | `StoreController` (ReactiveController) | +| Context API | Global Zustand store | +| `localStorage` / `sessionStorage` | IndexedDB via persist middleware | +| JSX | Lit's `html` tagged template literals | +| CSS-in-JS / CSS Modules | Lit's `css` tagged template literals | +| React Router | Simple route state in store | +| Build step (Vite/Webpack) | Import maps (no build!) | + +### Key Differences + +1. **No Virtual DOM**: Lit uses native Web Components with efficient DOM updates +2. **No Build Step**: Import maps let you use npm packages directly from CDN +3. **Reactive Controllers**: Replace hooks - attach to component lifecycle +4. **Tagged Templates**: Instead of JSX, use `html\`
...
\`` + +## Architecture Overview + +* **Lit-based web components** - Standards-based, framework-agnostic UI +* **No-build tooling** - Import maps only, no bundler required +* **Zustand state management** - Lightweight, vanilla JS store +* **IndexedDB persistence** - Automatic state persistence with idb-keyval -* Zustand-moderated state management with IndexedDB storage mechanism ## Layout @@ -29,15 +55,168 @@ index.html importmap.json ← extracted importmap (referenced via + + + + + + +``` + +## Benefits Over React/TSX + +✅ **No build step** - Edit and refresh, instant feedback +✅ **Smaller bundle** - No framework runtime, just standards +✅ **Better encapsulation** - Shadow DOM, scoped styles +✅ **Framework agnostic** - Works anywhere, even in React apps +✅ **Future-proof** - Built on web standards + +## See Also + +- [tasks.md](./tasks.md) - Current issues and improvements +- [Lit Documentation](https://lit.dev) +- [Zustand Documentation](https://zustand.docs.pmnd.rs) +- [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) diff --git a/importmap.json b/importmap.json deleted file mode 100644 index 73ca2e4..0000000 --- a/importmap.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "imports": { - "lit": "https://esm.sh/lit@3", - "lit/decorators.js": "https://esm.sh/lit@3/decorators.js", - "zustand": "https://esm.sh/zustand@5/vanilla", - "zustand/middleware": "https://esm.sh/zustand@5/middleware", - "idb-keyval": "https://esm.sh/idb-keyval@6" - } -} \ No newline at end of file diff --git a/store/idb.js b/store/idb.js new file mode 100644 index 0000000..e276f00 --- /dev/null +++ b/store/idb.js @@ -0,0 +1,33 @@ +// lib/idb.js +import { createStore, get, set, del, entries, clear } from 'idb-keyval' + +// Named stores — each maps to a distinct IDBObjectStore +export const itemsStore = createStore('app-db', 'items') +export const userStore = createStore('app-db', 'user') +export const cacheStore = createStore('app-db', 'cache') + +// Typed wrappers — keeps raw idb-keyval calls out of the rest of the app +// and gives you one place to add validation, logging, or migration logic + +export const db = { + items: { + getAll: () => entries(itemsStore), + get: (id) => get(id, itemsStore), + set: (id, value) => set(id, value, itemsStore), + remove: (id) => del(id, itemsStore), + clear: () => clear(itemsStore), + }, + + user: { + get: () => get('user', userStore), + set: (value) => set('user', value, userStore), + clear: () => del('user', userStore), + }, + + cache: { + get: (key) => get(key, cacheStore), + set: (key, value) => set(key, value, cacheStore), + remove: (key) => del(key, cacheStore), + clear: () => clear(cacheStore), + } +} \ No newline at end of file diff --git a/store/index.js b/store/index.js index 7a60893..5fdf883 100644 --- a/store/index.js +++ b/store/index.js @@ -1,6 +1,19 @@ import { createStore } from 'zustand/vanilla' -import { persist } from 'zustand/middleware' -import { idbStorage } from './middleware/persistence.js' +import { persist, createJSONStorage } from 'zustand/middleware' +import { get, set } from 'idb-keyval' + +const storage = createJSONStorage(() => ({ + getItem: async (name) => { + const value = await get(name) + return value ?? null + }, + setItem: async (name, value) => { + await set(name, value) + }, + removeItem: async (name) => { + await del(name) + }, +})) export const store = createStore( persist( @@ -16,9 +29,13 @@ export const store = createStore( }), { name: 'app-store', - storage: idbStorage, - onRehydrateStorage: () => (state) => { - if (state) state._hydrated = true + storage, + onRehydrateStorage: () => { + return (state, error) => { + if (!error) { + store.setState({ _hydrated: true }) + } + } }, } ) diff --git a/store/middleware/persistence.js b/store/middleware/persistence.js index ab68ec4..75552f1 100644 --- a/store/middleware/persistence.js +++ b/store/middleware/persistence.js @@ -1,8 +1,9 @@ -import { get, set, del } from 'idb-keyval' -import { createJSONStorage } from 'zustand/middleware' +// store/middleware/persistence.js +import { db } from '../idb.js' -export const idbStorage = createJSONStorage(() => ({ - getItem: (name) => get(name), - setItem: (name, value) => set(name, value), - removeItem: (name) => del(name), -})) \ No newline at end of file +export const makeIdbStorage = (storeName) => + createJSONStorage(() => ({ + getItem: (name) => db[storeName].get(name), + setItem: (name, value) => db[storeName].set(name, value), + removeItem: (name) => db[storeName].remove(name), + })) \ No newline at end of file diff --git a/tasks.md b/tasks.md new file mode 100644 index 0000000..edd6904 --- /dev/null +++ b/tasks.md @@ -0,0 +1,60 @@ +# Tasks & Issues + +## 1. Critical Issues + +- [ ] **1.1 Fix missing `del` import in store/index.js** + - Line 3 imports `get, set` from idb-keyval but `del` is used on line 13 + - Add `del` to the import statement + - This will cause a ReferenceError when removeItem is called + +- [ ] **1.2 Fix hydration flag initialization** + - The `onRehydrateStorage` callback uses `store.setState()` before `store` is fully initialized + - Creates a circular reference issue preventing app from loading + - **Options:** + - A) Initialize `_hydrated: true` by default (simplest) + - B) Use Zustand's persist middleware built-in hydration detection + - C) Move hydration callback outside store creation + +- [ ] **1.3 Consolidate duplicate storage implementation** + - Storage is defined in both `store/index.js` AND `store/middleware/persistence.js` + - The middleware file exports `idbStorage` but it's not being used + - **Decision needed:** Use one or the other, not both + +## 2. Architectural Improvements + +- [ ] **2.1 Add error handling** + - Add try/catch blocks around IndexedDB operations + - Implement fallback if IndexedDB fails or is unavailable + - Add user feedback for errors (toast notifications?) + +- [ ] **2.2 Add partialize option to persist middleware** + - Currently saves ALL state including `_hydrated` flag and `route` + - Should use `partialize: (state) => ({ user: state.user, items: state.items })` + - Prevents unnecessary data in IndexedDB + +- [ ] **2.3 Add basic CSS reset to index.html** + - No base styles, margins, or font settings currently + - Consider adding minimal reset or normalize.css + +- [ ] **2.4 Add loading states for async operations** + - No loading indicators for add/remove item operations + - Consider adding optimistic updates + +## 3. Nice to Have + +- [ ] **3.1 Add TypeScript types** (optional) + - JSDoc comments for better IDE support + - Or migrate to .ts files with no-build setup + +- [ ] **3.2 Add cross-tab synchronization** + - README mentions optional `sync.js` middleware + - Implement BroadcastChannel for cross-tab state sync + +- [ ] **3.3 Add route history management** + - Integrate with browser history API + - Support back/forward navigation + +- [ ] **3.4 Add unit tests** + - Test store actions and selectors + - Test component rendering + - Test persistence layer