Skip to content

fix: resolve search freeze and improve scroll to results - #4

Merged
rodrigogs merged 22 commits into
devfrom
fix/search-performance-and-scroll
Dec 9, 2025
Merged

fix: resolve search freeze and improve scroll to results#4
rodrigogs merged 22 commits into
devfrom
fix/search-performance-and-scroll

Conversation

@rodrigogs

Copy link
Copy Markdown
Owner

Problem

  • Browser was freezing when searching due to postMessage serialization overhead
  • The serialized MiniSearch index was ~5-10MB for large chats (18K messages)
  • Structured Clone algorithm serializes data ON THE MAIN THREAD before transfer
  • First search result scroll wasn't working reliably for system notifications

Solution

Search Performance (removed MiniSearch)

  • Replaced MiniSearch with simple string.includes() search
  • Worker now receives simplified message data (~200KB) once per chat
  • Subsequent searches only send query string (tiny postMessage overhead)
  • Results use Transferable ArrayBuffer bitmap for O(1) match lookup
  • Added search cancellation and debouncing (150ms) for better UX

Scroll to Search Results

  • Added retry mechanism (20 attempts, 50ms intervals) for scroll-to-result
  • Ensures DOM refs are available before attempting scroll
  • Fixes issue where system notifications weren't scrolled to properly

Other Improvements

  • Pre-serialize messages in index-worker for faster search loading
  • Split searchQuery into inputSearchQuery and activeSearchQuery
  • Added totalSearchMatches counter for accurate result display
  • Added GitHub star CTA with Electron openExternal support
  • UI polish: cursor-pointer on buttons, layout adjustments

Files Changed

  • src/lib/workers/search-worker.ts: Complete rewrite with simple search
  • src/lib/workers/index-worker.ts: Add serializedMessages output
  • src/lib/state.svelte.ts: New search architecture with bitmap lookup
  • src/lib/components/ChatView.svelte: Retry mechanism for scroll
  • src/lib/components/MessageBubble.svelte: Fix highlight conditions
  • src/lib/parser/zip-parser.ts: Add SerializedSearchMessage type
  • src/routes/+page.svelte: Update search result display, add GitHub CTA
  • Removed: src/lib/workers/search-index-worker.ts (orphaned)
  • Removed: minisearch dependency from package.json

Testing

  • ✅ Search no longer freezes browser
  • ✅ Search results scroll correctly (including system notifications)
  • ✅ Highlight appears on matching messages
  • ✅ TypeScript compilation passes

## Problem
- Browser was freezing when searching due to postMessage serialization overhead
- The serialized MiniSearch index was ~5-10MB for large chats (18K messages)
- Structured Clone algorithm serializes data ON THE MAIN THREAD before transfer
- First search result scroll wasn't working reliably for system notifications

## Solution

### Search Performance (removed MiniSearch)
- Replaced MiniSearch with simple string.includes() search
- Worker now receives simplified message data (~200KB) once per chat
- Subsequent searches only send query string (tiny postMessage overhead)
- Results use Transferable ArrayBuffer bitmap for O(1) match lookup
- Added search cancellation and debouncing (150ms) for better UX

### Scroll to Search Results
- Added retry mechanism (20 attempts, 50ms intervals) for scroll-to-result
- Ensures DOM refs are available before attempting scroll
- Fixes issue where system notifications weren't scrolled to properly

### Other Improvements
- Pre-serialize messages in index-worker for faster search loading
- Split searchQuery into inputSearchQuery and activeSearchQuery
- Added totalSearchMatches counter for accurate result display
- Added GitHub star CTA with Electron openExternal support
- UI polish: cursor-pointer on buttons, layout adjustments

## Files Changed
- src/lib/workers/search-worker.ts: Complete rewrite with simple search
- src/lib/workers/index-worker.ts: Add serializedMessages output
- src/lib/state.svelte.ts: New search architecture with bitmap lookup
- src/lib/components/ChatView.svelte: Retry mechanism for scroll
- src/lib/components/MessageBubble.svelte: Fix highlight conditions
- src/lib/parser/zip-parser.ts: Add SerializedSearchMessage type
- src/routes/+page.svelte: Update search result display, add GitHub CTA
- Removed: src/lib/workers/search-index-worker.ts (orphaned)
- Removed: minisearch dependency from package.json

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request addresses critical performance issues with search functionality and improves scroll-to-results reliability. The main change is replacing MiniSearch with a simple string.includes() approach that dramatically reduces postMessage overhead (from 5-10MB to ~200KB).

Key Changes:

  • Removed MiniSearch dependency and replaced with lightweight string search in worker
  • Implemented bitmap-based result matching for O(1) lookup performance
  • Added search debouncing (150ms) and cancellation support
  • Implemented retry mechanism (20 attempts) for scroll-to-search-results
  • Added GitHub star CTA with Electron openExternal support

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/lib/workers/search-worker.ts Complete rewrite using simple string.includes() search with bitmap results and cancellation support
src/lib/state.svelte.ts New search architecture with worker reuse, debouncing, and bitmap-based match lookup
src/lib/workers/index-worker.ts Added serializedMessages output to avoid re-serialization on every search
src/lib/components/ChatView.svelte Implemented retry mechanism for scroll-to-result to handle DOM timing issues
src/lib/components/MessageBubble.svelte Fixed highlight conditions to properly check isSearchMatch
src/routes/+page.svelte Updated search UI to use totalSearchMatches, added GitHub CTA with Electron integration
src/lib/parser/zip-parser.ts Added SerializedSearchMessage type for pre-computed search data
src/app.d.ts Added openExternal method to ElectronAPI interface
electron/preload.cjs Exposed openExternal IPC call
electron/main.cjs Implemented shell.openExternal handler
messages/en.json Added github_star_title and github_star_cta translations
messages/pt.json Added github_star_title and github_star_cta translations
package-lock.json Version bump to 1.2.2 and dependency updates
Comments suppressed due to low confidence (1)

src/lib/state.svelte.ts:448

  • Navigation logic uses searchResultIds.length for modulo wrapping, but the UI displays totalSearchMatches which can be larger (when matches exceed MAX_NAV_RESULTS=1000). This creates a confusing UX where the counter shows "500 of 5000" but navigation only cycles through the first 1000 results.

Recommendation: Either display searchResultIds.length in the UI counter and indicate when results are capped (e.g., "500 of 1000+"), or use totalSearchMatches for navigation (though this would require populating all matching IDs, not just the first 1000).

		// Navigate to next search result
		nextSearchResult() {
			if (searchResultIds.length > 0) {
				currentSearchIndex = (currentSearchIndex + 1) % searchResultIds.length;
			}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/state.svelte.ts Outdated
Comment thread src/lib/state.svelte.ts
Comment thread src/lib/components/ChatView.svelte
@rodrigogs

Copy link
Copy Markdown
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Also, make sure lint and checks are passing before finishing

Copilot AI commented Dec 9, 2025

Copy link
Copy Markdown
Contributor

@rodrigogs I've opened a new pull request, #5, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits December 9, 2025 14:16
Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
fix: prevent worker timeout leaks and scroll race conditions

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/components/ChatView.svelte Outdated
Comment thread src/lib/state.svelte.ts Outdated
@rodrigogs

Copy link
Copy Markdown
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Make sure lint and check are passing before ending

Copilot AI commented Dec 9, 2025

Copy link
Copy Markdown
Contributor

@rodrigogs I've opened a new pull request, #6, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 11 commits December 9, 2025 15:33
…ureSearchWorker

Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
- Merge IndexWorkerMessage and SerializedSearchMessage into single SerializedMessage type
- Remove redundant .map() operation (messages already in correct format)
- Add documentation explaining why type is duplicated from zip-parser.ts
- Rename SerializedFlatItem to FlatItem for consistency with zip-parser.ts
- Add documentation explaining type duplication rationale
- Remove MiniSearch references from worker file headers
- Simplify comments in state.svelte.ts
- Document ChatMessage type in stats-worker.ts
- Streamline documentation to focus on current architecture
- Rename WorkerInput to SearchWorkerInput/StatsWorkerInput to avoid conflicts
- Fix postMessage signature to use { transfer: [...] } options object
…and fix postMessage transfer syntax

Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
- Merge IndexWorkerMessage and SerializedSearchMessage into single SerializedMessage type
- Remove redundant .map() operation (messages already in correct format)
- Add documentation explaining why type is duplicated from zip-parser.ts
- Rename SerializedFlatItem to FlatItem for consistency with zip-parser.ts
- Add documentation explaining type duplication rationale
- Remove MiniSearch references from worker file headers
- Simplify comments in state.svelte.ts
- Document ChatMessage type in stats-worker.ts
- Streamline documentation to focus on current architecture
fix: add error handling for search worker initialization and fix transcription highlighting

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 15 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/workers/search-worker.ts Outdated
const chunkEnd = Math.min(i + CHUNK_SIZE, messages.length);
// Process in chunks to allow cancellation and progress updates
const CHUNK_SIZE = 2000;
let processed = 0;

Copilot AI Dec 9, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable processed is declared but never meaningfully used. It's assigned on line 125 but that value is only used once on line 126 in the progress calculation, where end would work just as well. Consider removing this variable and using end directly in the progress calculation.

Copilot uses AI. Check for mistakes.
Comment thread electron/main.cjs
@rodrigogs

Copy link
Copy Markdown
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Make sure lint and types are working before ending

Copilot AI commented Dec 9, 2025

Copy link
Copy Markdown
Contributor

@rodrigogs I've opened a new pull request, #7, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits December 9, 2025 16:16
Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com>
[WIP] Fix search freeze and improve scroll to results
- Remove floating hamburger button from bottom-left corner
- Add discrete menu icon in chat header (before avatar)
- Add toggle button to 'no chat selected' view header
- Remove hamburger CSS animations (~65 lines)
- Icon changes between menu (3 lines) and double-arrow when sidebar is open
@rodrigogs
rodrigogs merged commit 904ea97 into dev Dec 9, 2025
4 checks passed
@rodrigogs
rodrigogs deleted the fix/search-performance-and-scroll branch December 9, 2025 17:49
rodrigogs added a commit that referenced this pull request Apr 1, 2026
Correctness:
- #1: handleRemoveChat now removes from UI first (sync), cleans up
  IndexedDB in background to avoid stale index issues
- #2: Remove redundant double-store of FileSystemFileHandle
  (savePersistedChat already handles it)
- #3: Restored chats now populate chatFileReferences with persistedId
  to prevent orphaned duplicates on toggle

Security:
- #4: Use file descriptor with O_NOFOLLOW to prevent TOCTOU race
  in Electron file read IPC

DRY:
- #5: Extract LoadingChat interface to shared state.svelte.ts
- #6: getBookmarksForChatAsExport delegates to getBookmarksForChat
- #7: Extract sanitizeFilename helper to format.ts
- #8: formatDate kept local (locale-aware, not extractable)
- #9: Add addRemembered/removeRemembered helpers (5 call sites)

i18n/Accessibility:
- #10: Add aria-label to ReselectFileModal drop zone
- #11: i18n Modal close label across 10 locales

Clean Code:
- #12: Split handleToggleRemember into rememberChat/forgetChat
- #13: Toast uses Icon component instead of inline SVGs
rodrigogs added a commit that referenced this pull request Apr 1, 2026
Correctness:
- #1: handleRemoveChat now removes from UI first (sync), cleans up
  IndexedDB in background to avoid stale index issues
- #2: Remove redundant double-store of FileSystemFileHandle
  (savePersistedChat already handles it)
- #3: Restored chats now populate chatFileReferences with persistedId
  to prevent orphaned duplicates on toggle

Security:
- #4: Use file descriptor with O_NOFOLLOW to prevent TOCTOU race
  in Electron file read IPC

DRY:
- #5: Extract LoadingChat interface to shared state.svelte.ts
- #6: getBookmarksForChatAsExport delegates to getBookmarksForChat
- #7: Extract sanitizeFilename helper to format.ts
- #8: formatDate kept local (locale-aware, not extractable)
- #9: Add addRemembered/removeRemembered helpers (5 call sites)

i18n/Accessibility:
- #10: Add aria-label to ReselectFileModal drop zone
- #11: i18n Modal close label across 10 locales

Clean Code:
- #12: Split handleToggleRemember into rememberChat/forgetChat
- #13: Toast uses Icon component instead of inline SVGs
rodrigogs added a commit that referenced this pull request Apr 1, 2026
…storation (#66)

* feat: add persistent conversation feature with cross-platform file restoration

Implement "Remember Conversation" toggle that persists chat sessions
across app restarts using IndexedDB (idb-keyval). Three platform
strategies: Electron file paths, Chromium FileSystemFileHandle, and
fallback reselect for Firefox/Safari.

New components: RestoreSessionModal, ReselectFileModal, Toast, Modal.
Stores metadata, bookmarks, transcriptions, settings (~1MB).
Never stores the ZIP file itself.

* fix: resolve all 15 type errors in persistent conversations feature

- Add File System Access API type declarations (FileSystemHandlePermissionDescriptor,
  OpenFilePickerOptions, FileSystemFileHandle augmentation, Window.showOpenFilePicker)
  to the global scope in src/app.d.ts
- Register 7 missing icon names (alert-circle, folder, clock, check-all, x,
  check-circle, message-circle) in the Icon component with SVG path definitions
- Add missing isElectronPathReference import in +page.svelte, which also resolves
  the union type narrowing error for filePath access

* fix(electron): harden file:readFromPath IPC handler

- Restrict to .zip files only to prevent path traversal attacks
- Use async fs.promises.readFile() instead of blocking readFileSync()
- Properly slice Buffer to ArrayBuffer using byteOffset/byteLength

* fix(i18n): replace hardcoded English strings with Paraglide messages

Replace hardcoded strings in RestoreSessionModal ("Select All",
"Deselect All") and ReselectFileModal ("Drop WhatsApp ZIP file here",
"or click below to browse", "Browse Files") with proper Paraglide i18n
message calls. Added translations for all 10 supported languages.

* fix(persistence): clean up IndexedDB entries when removing a remembered chat

When a user removed a chat via handleRemoveChat(), persisted data in
IndexedDB was not cleaned up, leaving orphaned entries that caused the
restore modal to show stale chats. Now handleRemoveChat checks if the
chat is in rememberedChats and, if so, finds and removes the persisted
entry from IndexedDB before removing it from UI state.

* fix(persistence): add error logging to silent catch blocks

Add console.error/warn logging to five catch blocks in
persistence.svelte.ts that were silently swallowing errors, making
IndexedDB issues impossible to debug.

* fix(ReselectFileModal): eliminate drag-drop flicker with counter pattern

Replace boolean isDragging state with dragCounter pattern to fix visual
flicker that occurred when dragging files over nested elements. The counter
increments on dragenter and decrements on dragleave, preventing the rapid
toggling that caused the flicker.

* chore: remove AI handoff document

* fix: clean up dead code, timer leaks, and unused imports

* fix: resolve critical bugs and extract duplicated parse/index logic

- Fix C2: restore loop now awaits user reselect via Promise pattern
  instead of continuing to next chat immediately
- Fix C1: add one-time guard to $effect for persistence check
- Fix C3: harden Electron file read with path normalization and
  symlink protection (lstat check)
- DRY: extract startIndexWorker() and makeProgressCallback() from
  ~150 lines of duplicated code between handleFilesSelected and
  loadChatFromBuffer

* fix: address final review items

- Replace remaining hardcoded English strings with i18n messages
- Filter transcriptions to only include current chat's message IDs
  when persisting (prevents cross-chat data leakage)
- Add translations for new keys to all 9 non-English locales
- Document chatFileReferences intentional non-reactive mutation

* fix: translate all persistence keys and remove dead isRestoring state

* refactor: deduplicate getBookmarksForChatAsExport

* fix(electron): use file descriptor to prevent TOCTOU race in file read

* refactor(Toast): replace inline SVGs with Icon component

Replace inline SVG elements with the reusable Icon component for better code maintainability and consistency. The status icon now uses check-circle for success and alert-circle for error/info types, while the close button uses the x icon.

* fix(a11y): add aria-label to drop zone and i18n Modal close label

- Add aria-label to drop zone in ReselectFileModal for screen reader accessibility
- Replace hardcoded 'Close modal' string with i18n key close_modal in Modal component
- Add close_modal translations to all 10 locale files (en, pt, es, fr, de, it, nl, ja, zh, ru)

* refactor: address all 13 review items

Correctness:
- #1: handleRemoveChat now removes from UI first (sync), cleans up
  IndexedDB in background to avoid stale index issues
- #2: Remove redundant double-store of FileSystemFileHandle
  (savePersistedChat already handles it)
- #3: Restored chats now populate chatFileReferences with persistedId
  to prevent orphaned duplicates on toggle

Security:
- #4: Use file descriptor with O_NOFOLLOW to prevent TOCTOU race
  in Electron file read IPC

DRY:
- #5: Extract LoadingChat interface to shared state.svelte.ts
- #6: getBookmarksForChatAsExport delegates to getBookmarksForChat
- #7: Extract sanitizeFilename helper to format.ts
- #8: formatDate kept local (locale-aware, not extractable)
- #9: Add addRemembered/removeRemembered helpers (5 call sites)

i18n/Accessibility:
- #10: Add aria-label to ReselectFileModal drop zone
- #11: i18n Modal close label across 10 locales

Clean Code:
- #12: Split handleToggleRemember into rememberChat/forgetChat
- #13: Toast uses Icon component instead of inline SVGs

* fix: address all 9 Copilot review comments

1. Capture Electron file.path from drag-drop for persistence
2. Block metadata restoration when file validation fails
3. Deduplicate savePersistedChat (removes existing entry first)
4. Filter transcriptions by chat message IDs (prevents cross-chat leak)
5. Case-insensitive .zip check in ReselectFileModal
6. Clamp dragCounter to 0 minimum
7. Use isAbsolute + reject .. segments for Electron path validation
8. Guard error.message with instanceof Error check
9. Add keyboard/click handlers to drop zone for accessibility

* fix: address 3 new Copilot review comments

- Seed rememberedChats from IndexedDB on load so toggle state is
  correct even when user skips restore modal or clicks Start Fresh
- Fix verifyHandlePermission docstring to reflect shouldRequest param
- Use chatId+messageId composite key for bookmark import dedup to
  prevent cross-chat bookmark collisions

* fix: capture FileSystemFileHandle during drag-drop for seamless persistence

- Use DataTransferItem.getAsFileSystemHandle() during drop event to
  capture handles without showing a second file picker (Chrome 86+)
- Remove promptForFileHandle() call from rememberChat — handles are
  now captured at drag-drop time, eliminating the confusing double-
  file-picker UX
- Update reselect flow to capture Electron file.path and update the
  persisted entry so future restores work automatically
- Add DataTransferItem.getAsFileSystemHandle type declaration

* fix: capture file handles synchronously during drop and fix closure bug

- Start all getAsFileSystemHandle() Promises synchronously via
  Promise.all before any await, preventing DataTransferItem
  invalidation after first async tick
- Capture handleIndex and file path BEFORE the async IIFE to avoid
  closure-over-loop-variable bug where stale index was read after
  multiple awaits

* fix: pass Electron file path from dialog to persistence layer

The Electron file picker (openFile dialog) returns the absolute path
but it was discarded when creating the File object from the buffer.
This caused all Electron-picked files to be stored as
'reselect-required' instead of 'electron-path', forcing manual
reselection on every restore.

Now FileDropZone passes the path from Electron's dialog result via
a new 'paths' callback parameter. handleFilesSelected prefers this
explicit path over file.path (drag-drop fallback).

* debug: add persistence flow logging to trace Electron path issue

* fix: address 4 Copilot review comments

- Toast: reset visibility and restart timer when message prop changes
- FileDropZone: use showOpenFilePicker() on Chrome for file input to
  capture FileSystemFileHandle (drag-drop already captures handles;
  this fixes the regular "click to browse" flow)
- Remove unused isFileSystemAccessSupported import from +page.svelte
- Electron dialog path already fixed (result.path passed via paths
  callback parameter)

* fix: address 3 Copilot review comments

- Electron IPC: add lstat pre-check for cross-platform symlink
  rejection (O_NOFOLLOW not supported on Windows), with graceful
  fallback to O_RDONLY when O_NOFOLLOW unavailable
- FileDropZone: enable multi-select in showOpenFilePicker to match
  the existing <input multiple> behavior
- CSS: use :where(button) for low-specificity cursor rule so Tailwind
  utilities can override it

* fix: ReselectFileModal uses Electron dialog to capture file path

The reselect modal was using the web file input which doesn't
capture the absolute file path in Electron. Now uses the Electron
native dialog (electronAPI.openFile) which returns the path, and
passes it through to updatePersistedChat so the entry is upgraded
from 'reselect-required' to 'electron-path'. After one reselect,
future restores work automatically.

* fix: move Remember Conversation above Transcription Language in context menu

* fix: ReselectFileModal captures FileSystemFileHandle on Chrome and upgrades persisted entries

The reselect modal was using plain <input type=file> on Chrome,
which doesn't capture a FileSystemFileHandle. This meant even after
reselecting, the entry stayed as 'reselect-required' and the user
would be prompted again on every restart.

Now:
- Chrome/Edge: uses showOpenFilePicker() to capture a handle, then
  upgrades the persisted entry from 'reselect-required' to
  'file-handle' via storeFileHandle + updatePersistedChat
- Electron: uses native dialog to capture path, upgrades to
  'electron-path'
- Drag-drop in reselect modal also captures handle via
  getAsFileSystemHandle()

After one reselect, future restores work automatically on all
platforms.

* fix: removing chat from list no longer deletes persisted data

Removing a chat only clears it from the current session. Persisted
data in IndexedDB is preserved so the chat reappears in the restore
modal on next launch. To permanently forget a chat, user must toggle
Remember Conversation off.

* chore: remove debug logging from persistence flow

* fix: sidebar import uses Electron dialog and captures file path

The sidebar "Import chat" button was using a plain <input type=file>
which doesn't reliably provide file.path in Electron. Now uses the
same platform-aware import as the main drop zone:
- Electron: native dialog via electronAPI.openFile() with path capture
- Chrome/Edge: showOpenFilePicker() with handle capture
- Fallback: regular file input

Also fixes Node.js Buffer pool issue in dialog:openFile handler
(was sending entire shared ArrayBuffer instead of just the file's
portion).

* refactor: address all review items - dead code, DRY, a11y, error feedback

- Replace dynamic import of storeFileHandle with static import
- Remove 6 unused i18n keys from all 10 locale files
- Remove dead exports: promptForFileHandle, getPersistedChat, getBookmarksForChatAsExport
- Add error toast on restore failure in handleRestoreChats
- Extract shared file-picker helpers (openZipFilePicker, openElectronFile, getElectronFilePath)
- Add aria-label to FileDropZone drop zone
- Rename persistence_close_notification to close_notification in Toast

* refactor: centralize modals, fix cross-chat ID collisions, address review feedback

- Extract ChatAvatar component replacing 3 inline avatar implementations
- Add shared formatRelativeDate helper (label/compact modes)
- Add Button size="lg" variant with disabled styles
- Vertically center Modal on all breakpoints, not just desktop
- RestoreSessionModal: remove description banner, only chat list scrolls,
  fixed footer with action buttons
- Fix message ID collisions across chats by including filename in hash
- Fix Toast timer not restarting when message changes while visible
- Gate reselect flow: only upgrade persisted entry when validation passes
- Add persistence_restore_failed i18n key across all 10 locales
- Remove unused isFileSystemAccessSupported import and function

---------

Co-authored-by: Rodrigo Gomes da Silva <rodrigo.smscom@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants