v2.6.1: Scalable History & Performance Overhaul
PromptLab v2.6.1 is a critical stability and performance release focused on large-scale generation history management, instant UI rendering, and crash-proof data portability. This release resolves the RangeError: Invalid string length crash on large history exports by introducing a Content-Addressable Deduplicated Image Pool (v1.1) and Chunked Streaming Blob Serialization, slashes card render latency across deep history sets through Excerpt Regex Pre-Slicing and Pre-Mapped Sorting Comparators, and establishes a robust, Idempotent History Import Pipeline with pool-first IndexedDB hydration.
✨ Highlights
🛡️ Crash-Proof History Export & Chunked Streaming Serialization
Exporting extensive generation history containing multi-modal reference images across hundreds of records previously caused a fatal browser crash (RangeError: Invalid string length) due to full Base64 image strings being duplicated across every referencing history record into a single monolithic string passed to JSON.stringify:
- 📦 Content-Addressable Image Pool (Schema v1.1) —
exportHistoryToJSONnow extracts unique reference images into a top-levelimages: Record<string, string>pool keyed by SHA-256contentHash. Individual history items store lightweight pointers withbase64: "". When prompts reuse reference images across multiple generations, total backup payload sizes shrink by 90%–95%. - 🌊 Chunked Streaming Blob Serialization — replaced monolithic stringification with streaming chunk serialization, writing JSON structural fragments, metadata, the image pool, and individual history items directly as an array of discrete chunks to
new Blob(chunks, { type: "application/json" }). This completely bypasses V8's single-string heap limits, enabling effortless export of 500+ history records containing dozens of multi-modal images. - 🛡️ Fault-Tolerant Image Resolution — if an image was purged or is unavailable in IndexedDB, the exporter logs a diagnostic warning and proceeds without failing the entire export.
⚡ History Modal & Card Rendering Performance Overhaul
Navigating large history archives is now significantly faster and more responsive thanks to targeted algorithmic optimizations and memory cleanup:
- ✂️ Card Excerpt Regex Pre-Slicing — pre-slices
rawOutputto 250–400 characters before stripping markdown syntax (replace(/[#*_>~-]/g, " ")). Eliminates multi-pass regex scanning across full-length generation outputs (often 5,000–30,000+ characters) on every card render cycle inHistoryListSidebar,HistoryCardSummary, andHistorySection`, cutting CPU cycles and garbage collection overhead. - 🚪 Modal Closed-State Guards & Memory Cleanup —
HistoryViewerModalnow guards derived filter and sorting computations withif (!isOpen)checks, preventing background calculations while typing prompts in the main workspace. Automatically clears resolved base64 images from memory upon closing the modal, eliminating lingering heap pressure. - ⚡ Parallelized Reference Image Loading — refactored sequential
for...ofimage retrieval from IndexedDB to usePromise.allacross valid reference images, eliminating slot-switching lag for records with multiple reference images. - 🏎️ Pre-Mapped Sorting Comparator (Schwartzian Transform) — re-architected
sortHistoryItemswith a Schwartzian transform pattern inlib/history-grouping.ts. Pre-mapping sort keys (parsed dates, numeric costs, and lowercase titles) once per item reduces comparator work from$O(N \log N)$ down to$O(N)$ linear passes, with an instant early-exit guard for short lists. - 🦥 Lazy JSON Extraction & Formatting — deferred
extractCleanJsoninHistoryOutputViewerviauseMemoso that JSON parsing and syntax highlighting only run when the user explicitly switches to JSON view mode (viewMode === "json"). Default raw monospace and formatted markdown views completely bypass JSON parsing overhead. - 🔍 Search Substring Fast-Path — reordered
matchesSearchQueryinlib/search-utils.tsto test direct case-insensitive substring matching before executing multi-pass text normalization, skipping 4 regex replacements per target string on direct hits.
🔄 Robust & Idempotent History Import Pipeline
Restoring generation history and syncing archives across multiple browsers is now safe, non-blocking, and idempotent:
- 🛡️ Defensive File-Type Verification — validates
parsedData.typeupfront, immediately surfacing clear guidance if a user accidentally attempts to import a Project (promptlab_project) or Asset Library (promptlab_asset_library) JSON backup instead of a History export. - 💾 Pool-First IndexedDB Hydration — pre-hydrates the incoming
imagespool into IndexedDB viasaveStoredImage. Leverages PromptLab's SHA-256contentHashindex so that images already stored in the browser link viadedupRefIdwith zero redundant disk writes. - 🔁 Idempotent Duplicate Detection — checks incoming records against existing workspace history by unique
id(with a legacy signature fallback for older exports). Skips already-existing records so that round-tripping backups between devices never produces duplicate cards. - ⏳ Non-Blocking Batching (60 FPS) — processes incoming records in batches of 20 with event-loop yielding (
setTimeout(0)), guaranteeing the UI remains silky-smooth and responsive without triggering "Page Unresponsive" warnings even when importing 500+ records. - 🧠 Metadata Preservation — preserves thinking traces (
thinkingResult), detailed token metrics (tokenUsage), and computed cost metrics (estimatedCost) intact across export and import cycles. - 📊 Clear UI Feedback — the status banner in
HistoryViewerModalreports both newly imported and skipped duplicate counts (e.g."Successfully imported 10 history record(s) (500 already up-to-date skipped)!").
✨ What's New
- 📦 History Export Schema v1.1 — content-addressable top-level image pool keyed by SHA-256 hash.
- 🌊 Chunked Streaming Blob Export — export generation history of arbitrary volume without V8 string length crashes.
- 🔄 Idempotent History Import — smart duplicate skipping and defensive backup type validation.
- ⚡ Non-Blocking History Import Batching — imports hundreds of items smoothly with event-loop yielding.
- 🏷️ Import Status Feedback — explicit notification of imported vs skipped duplicate records.
🔧 Fixed & Optimized
- 🐛 Fixed
RangeError: Invalid string lengthon history export with large multi-modal datasets. - 🐛 Fixed React Hook Synchronous State Update Lint Warning — relocated
setResolvedImages({})into theuseEffectcleanup return inHistoryViewerModal. - ⚡ Optimized Card Excerpt Regex Cleaning — pre-slices output text to 250–400 characters, eliminating heavy regex passes over full outputs on every card render.
- ⚡ Optimized History Sorting — pre-mapped Schwartzian transform reduces sort comparator operations to
$O(N)$ . - ⚡ Optimized Search Matching — added case-insensitive substring fast-path prior to full text normalization.
- ⚡ Optimized Reference Image Resolution — parallelized IndexedDB queries using
Promise.all. - ⚡ Optimized History Modal Lifecycle — bypasses filtering derivations when closed and flushes base64 image memory on exit.
- ⚡ Optimized Output Viewer JSON Parsing — deferred JSON extraction and formatting until JSON view mode is actively toggled.
📁 Files Affected
package.json&package-lock.json— version bump to2.6.1CHANGELOG.md— added[v2.6.1]release entry with performance, fixed, and added sectionsAGENTS.md— updated Rule M documentation covering streaming chunk exports, Schwartzian sorting, and excerpt pre-slicinglib/history-export.ts— implemented v1.1 deduplicated image pool export, chunked streaming Blob serialization, defensive file validation, pool-first IndexedDB hydration, and idempotent batched importlib/history-grouping.ts— refactoredsortHistoryItemswith pre-mapped Schwartzian transform comparator and early-exit guardlib/search-utils.ts— added case-insensitive direct substring fast-path tomatchesSearchQuerycomponents/HistoryViewerModal.tsx— added closed-state memoization guards, memory cleanup on close, parallel image resolution withPromise.all, import status banner, and hook cleanup fixcomponents/history/HistoryListSidebar.tsx— pre-sliced card excerpt before regex formatting (250 chars)components/HistoryCardSummary.tsx— pre-sliced card excerpt before regex formatting (250 chars)components/HistorySection.tsx— pre-sliced card excerpt before regex formatting (400 chars)components/history/HistoryOutputViewer.tsx— wrappedextractCleanJsoninuseMemoconditioned onviewMode === "json"
Full Changelog: v2.6.0...v2.6.1