From 88bcb80f75fabc0a3c84bfc1f46de6c25e0a303d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 02:31:40 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Add=20in-memory=20manifest?= =?UTF-8?q?=20cache=20to=20LocalStorageThemeCache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced an in-memory `manifestCache` to avoid repeated and expensive JSON parsing from localStorage during cache operations. Co-authored-by: himattm <6266621+himattm@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../kotlin/halogen/engine/LocalStorageThemeCache.kt | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..69ba52b --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Avoid redundant JSON parsing in wasmJs LocalStorage caching +**Learning:** In the `wasmJs` target, the `LocalStorageThemeCache` previously parsed the JSON manifest array from `localStorage` on every operation (e.g. `size()`, `keys()`, `put()`, `evict()`), which is very expensive when done repeatedly. Caching the manifest array in memory significantly reduces overhead while still maintaining correctness for concurrent UI operations within a single session. +**Action:** Always consider the cost of JSON parsing in browser environments, particularly for frequently accessed metadata. Add a lightweight in-memory caching layer on top of `localStorage` to memoize the deserialized state when it is safe to do so within the bounds of a single user session. diff --git a/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt b/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt index aba1f32..0928c74 100644 --- a/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt +++ b/halogen-engine/src/wasmJsMain/kotlin/halogen/engine/LocalStorageThemeCache.kt @@ -48,21 +48,27 @@ public class LocalStorageThemeCache( private val _changes = MutableSharedFlow(extraBufferCapacity = 64) private val json = Json { ignoreUnknownKeys = true } private val manifestKey = "${prefix}__keys__" + private var manifestCache: MutableSet? = null // ── Manifest helpers ──────────────────────────────────────────────── private fun readManifest(): MutableSet { + manifestCache?.let { return it.toMutableSet() } + val raw = jsGetItem(manifestKey.toJsString())?.toString() ?: return mutableSetOf() - return try { + val parsed = try { json.decodeFromString>(raw).toMutableSet() } catch (_: Exception) { mutableSetOf() } + manifestCache = parsed.toMutableSet() + return parsed } private fun writeManifest(keys: Set) { val encoded = json.encodeToString(keys) jsSetItem(manifestKey.toJsString(), encoded.toJsString()) + manifestCache = keys.toMutableSet() } // ── Entry helpers ─────────────────────────────────────────────────── @@ -155,6 +161,7 @@ public class LocalStorageThemeCache( removeEntry("$prefix$key") } jsRemoveItem(manifestKey.toJsString()) + manifestCache = null _changes.tryEmit(CacheEvent.Cleared) }