feat: add TodoMVC example with editorial design

Full TodoMVC implementation using Pocketbook's synced atom:
- Add, toggle, edit (double-click), destroy todos
- Filter: All / Active / Completed
- Toggle all, clear completed
- Live sync status indicator (online/offline/pending)
- Data persists in IndexedDB, syncs to server via Transit

Design: Ink & Paper aesthetic — cream paper background with grain
texture, Instrument Serif italic header, Newsreader body text,
DM Mono UI chrome, terracotta accent with green completion marks,
deckled left border on card.

Also:
- Server now serves static files from resources/public (/ → todomvc.html)
- Fix CLJS compilation: resolve close!/put! naming conflicts with
  core.async, use qualified async/close!, add clojure.string require
- Fix unbalanced parens in do-pull!
- Remove old placeholder example
This commit is contained in:
Florian Schroedl
2026-04-04 16:45:14 +02:00
parent 55cddf751b
commit ab68a21dd6
10 changed files with 770 additions and 156 deletions

View File

@@ -11,7 +11,8 @@
"
(:require [pocketbook.idb :as idb]
[pocketbook.sync :as sync]
[cljs.core.async :refer [go go-loop <! >! chan put! close! timeout alts!]]))
[clojure.string :as str]
[cljs.core.async :as async :refer [go go-loop <! >! chan put! timeout alts!]]))
;; ---------------------------------------------------------------------------
;; Connection (IDB handle)
@@ -25,17 +26,17 @@
(go
(let [db (<! (idb/open db-name))]
(>! ch {:db db :db-name db-name})
(close! ch)))
(async/close! ch)))
ch))
(defn close!
(defn shutdown!
"Close a Pocketbook connection."
[{:keys [db atoms]}]
;; Stop all sync loops
(doseq [[_ sa] @(or atoms (atom {}))]
(when-let [stop (:stop-fn sa)]
(stop)))
(idb/close! db))
(idb/close-db! db))
;; ---------------------------------------------------------------------------
;; Synced Atom — implements IAtom semantics
@@ -122,7 +123,7 @@
(.now js/Date))
(defn- doc-in-group? [group id]
(clojure.string/starts-with? id (prefix-str group)))
(str/starts-with? id (prefix-str group)))
;; ---------------------------------------------------------------------------
;; IDB ↔ Atom sync
@@ -151,7 +152,7 @@
(str "last-sync:" (.-group sa))))]
(reset! (.-last_sync sa) (or ls 0)))
(put! ch true)
(close! ch)))
(async/close! ch)))
ch))
(defn- write-doc-to-idb!
@@ -188,12 +189,12 @@
[sa]
(go
(when-let [opts (.-server_opts sa)]
(let [since @(.-last_sync sa)
(let [since @(.-last_sync sa)
result (<! (sync/pull! opts (.-group sa) since))]
(when (:ok result)
(let [docs (:docs result)
max-ts (reduce max @(.-last_sync sa)
(map :updated docs))]
(let [docs (:docs result)
max-ts (reduce max @(.-last_sync sa)
(map :updated docs))]
;; Merge each doc into cache
(doseq [doc docs]
(let [id (:id doc)]
@@ -215,7 +216,7 @@
;; Update last-sync
(reset! (.-last_sync sa) max-ts)
(idb/set-meta! (:db (.-conn sa))
(str "last-sync:" (.-group sa)) max-ts)))
(str "last-sync:" (.-group sa)) max-ts))
true)))))
(defn- do-push!
@@ -325,7 +326,7 @@
(go
(<! (load-from-idb! sa))
(put! ready-ch true)
(close! ready-ch)
(async/close! ready-ch)
;; Initial sync
(when server-opts
(<! (do-sync! sa))