Skip to content

Bugfix Vault Assistant Persistence, Display, and AI Re-indexing#332

Merged
failerko merged 26 commits into
AventurasTeam:masterfrom
EventideMiles:bugfix/vault-assistant-persistence-bugs
Jun 10, 2026
Merged

Bugfix Vault Assistant Persistence, Display, and AI Re-indexing#332
failerko merged 26 commits into
AventurasTeam:masterfrom
EventideMiles:bugfix/vault-assistant-persistence-bugs

Conversation

@EventideMiles

@EventideMiles EventideMiles commented May 30, 2026

Copy link
Copy Markdown
Contributor

Overview

Fixes 9 bugs in the vault assistant and lorebook editor, adds AI-driven re-indexing to detect stale entry data after user edits, and adds composed preview for multi-update entities.


Changes

Bug Fixes

1. Approved changes replay on chat reopen (Bug 3)

vaultEditorStore.approve() / reject() / approveAll() mutated change status in memory but never called service.saveConversation() to persist the updated pending-changes array. On reopen, all changes loaded with status: 'pending'. Added saveConversation after every approve/reject/approveAll operation.

Files: InteractiveVaultAssistant.svelte

2. Standalone editor shows stale data after vault assistant edits (Bug 1)

The sync $effect had an if (!isEmbedded) return guard, so standalone mode never synced from the vault store. Removed the guard so both modes read from the canonical source.

Files: VaultLorebookEditorContent.svelte

3. Changes revert on lorebook close/reopen (Bug 2)

handleSaveClick constructed updatedLorebook from the mount-time deep copy, silently destroying any entries added by the vault assistant after mount. Now merges vault-store entries that aren't in the local array before saving.

Files: VaultLorebookEditorContent.svelte

4. Pending operations sidebar shows wrong entry names (Bug 4)

entries was initialized from previewLorebook.entries, which has pending deletes already applied (truncated). Pending changes still referenced original indices. Changed entries initialization to read from the canonical vault store (lorebookVault.getById()?.entries) in embedded mode.

Files: VaultLorebookEditorContent.svelte

5. Close-reopen freeze on vault assistant

Inline onOpenChange guard silently blocked onClose() when isGenerating was true, leaving the component in an invisible mounted-but-stuck state. Replaced with handleOpenChange() that aborts generation and always calls onClose().

Files: InteractiveVaultAssistant.svelte

6. Stale editor data after AI updates in same chat

The sync effect only pulled in entries with new names — updates to existing entries (same name, changed content) were silently dropped when entriesDirty was true. Rewrote the effect to always reconcile both new and updated entries from the vault store, using untrack() to avoid re-firing on keystrokes. Fixed handleSaveClick to not overwrite local changes with vault data (save should persist local state, only merge new entries to prevent accidental deletion).

Files: VaultLorebookEditorContent.svelte

7. Scenario editor doesn't reflect AI-approved updates

VaultScenarioEditor initialized formData once from the scenario prop and never re-synced when the underlying scenario in scenarioVault changed. VaultPanel held a stale snapshot of editingScenario. Fixed by adding a sync $effect (matching the lorebook editor pattern) and deriving editingScenario from the vault store via editingScenarioId.

Files: VaultScenarioEditor.svelte, VaultPanel.svelte

8. Multiple pending updates to the same entity overwrite each other

When two pending updates target the same scenario/character, applyScenarioChange/applyCharacterChange passed the full change.data (a complete snapshot) to scenarioVault.update()/characterVault.update(), so the second update's unchanged fields overwrote the first update's changes. Fixed by computing a delta (_computeDelta()) that only includes fields that differ between data and previous, so sequential approvals compose correctly.

Files: InteractiveVaultService.ts

9. AI sends null/empty values that overwrite existing data

The AI tool calls include null values for fields it omits from an update. The lorebook entry update path already filtered v !== '', but scenario and character update paths only filtered v !== undefined. Extended all three paths to also filter v !== null, preventing accidental data loss.

Files: scenario.ts, character.ts


New Features

10. AI lorebook re-indexing

Detects external lorebook edits between AI turns and injects a system note prompting the AI to refresh entry state.

Version tracking: lorebookVault._entryVersions map — bumped on every update() call that changes entries. Exposed via getEntryVersion().

Tool context: activeLorebookId in LorebookEntryToolContext. Entry tools (list_entries, read_entry, create_entry, update_entry, delete_entry, merge_entries) prefer live getLorebookEntries() lookup over the snapshot entries array when available.

Change detection: InteractiveVaultService.injectLorebookChangeNote() — before each sendMessageStreaming, compares the current entry version against _knownEntryVersions. If stale, pushes a system note. Version map is synced after approvals to avoid redundant notifications.

Persistence: entryVersions field on VaultConversation type, saved/loaded via database.saveVaultConversation / loadVaultConversation. New 035_entry_versions.sql migration.

Files:

  • lorebookVault.svelte.ts — version tracking
  • lorebook.tsactiveLorebookId, live-data preference
  • InteractiveVaultService.tsinjectLorebookChangeNote, approval version sync, save/restore
  • InteractiveVaultAssistant.svelte — wire version check into send flow
  • database.tsentryVersions save/load
  • types/index.tsentryVersions on VaultConversation
  • 035_entry_versions.sql — migration
  • src-tauri/src/lib.rs — register migration

11. Revert Changes button in lorebook editor

Adds a "Revert Changes" button next to "Save All Entries" that appears when entries are dirty. On click, it restores all entries to the original deep-copied snapshot, clears the locallyDeleted set, and resets entriesDirty. The snapshot is captured at mount time from the canonical vault store (embedded mode) or the lorebook prop (standalone mode).

Files: VaultLorebookEditorContent.svelte, vaultEditorStore.svelte.ts

12. Block approve when editor has unsaved changes

handleApprove and handleApproveAll now check vaultEditor.editorDirty before proceeding. If the lorebook editor has unsaved local edits, the approval is blocked with a "Save your local edits before approving changes." error message instead of silently proceeding (which could lose data).

Files: InteractiveVaultAssistant.svelte, vaultEditorStore.svelte.ts

13. Composed preview for multi-update entities

When multiple pending updates target the same scenario or character, the editor and diff cards now show what "Approve All" would produce — all changes merged — instead of only the individual change's data.

Diff view: "After" column shows the composed result with a blue "all changes" badge when multiple pending updates target the same entity.

Entity editor: Form initializes from the composed preview so you see the final state. Changed-field highlighting diffs composed data against original previous, so all pending changes are highlighted, not just the active change's fields.

Store: vaultEditorStore.composedData derived getter merges all pending changes for the same entity starting from the original previous state, overlaying each change's delta.

Files: vaultEditorStore.svelte.ts, VaultEntityEditPanel.svelte, VaultDiffView.svelte


Verification

  • npm run check — 0 errors, 0 warnings
  • npm run lint — 0 errors, 181 pre-existing boundary-import warnings (unchanged)
  • cargo check — 0 errors, 1 pre-existing unused_mut warning

Summary by CodeRabbit

  • New Features

    • Per-lorebook entry version tracking that notifies when lorebook entries change externally
    • "Revert Changes" in lorebook editor (embedded mode) and richer "After" previews for multi-update edits
    • Editor dirty-state tracking and global save shortcut (Ctrl/Cmd+S)
  • Bug Fixes / Improvements

    • Prevent null/empty-string values from overwriting fields on updates
    • More robust modal open/close flows, unsaved-change warnings, and improved save/approve/reject persistence
    • Improved merge/apply logic for pending edits and tag rendering stability

- Bug 3: Persist approved/rejected status by calling saveConversation
  after every approve, reject, and approveAll in the vault assistant.
- Bug 1: Make standalone lorebook editor reactive to external vault store
  changes (remove isEmbedded guard from sync effect).
- Bug 2: Merge externally-added entries into local state before save
  instead of silently overwriting the vault store.
- Bug 4: Initialize editor entries from the canonical vault store,
  not previewLorebook (which has pending deletes applied, truncating
  the array and misaligning combinedEntries index mapping).
- Close-reopen freeze: Replace inline onOpenChange guard with
  handleOpenChange() that aborts generation and always calls
  onClose(), preventing the component from entering an invisible
  mounted-but-stuck state.
- Entry version tracking in lorebookVault store (_entryVersions map, bumped on entries change)
- activeLorebookId in LorebookEntryToolContext; live-data preference in all tool executables
- injectLorebookChangeNote system-note injection in InteractiveVaultService for stale-entry detection
- version map persistence in saveConversation/loadConversation; new entryVersions field on VaultConversation
- DB migration 035_entry_versions.sql (ALTER TABLE vault_assistant_conversations ADD entry_versions)
- Sync effect now uses untrack() to avoid re-firing on keystrokes while still reconciling vault updates
- handleSaveClick merge reverted to only add new entries (not overwrite existing ones)
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@EventideMiles, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 51 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6a8c406-c328-411b-8d47-72a752dbd715

📥 Commits

Reviewing files that changed from the base of the PR and between 49d4379 and 56ef191.

📒 Files selected for processing (3)
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/components/vault/VaultScenarioEditor.svelte
📝 Walkthrough

<review_stack_artifact_start />
<stack_title>Entry version tracking & editor sync</stack_title>
<stack_summary>Adds per-lorebook entry version persistence/detection, composed pending-change previews and merge utilities, vault-editor sync and editorDirty tracking, tool target resolution, UI modal/save/unsaved-change flows, and DB migration to store version maps.</stack_summary>

End-to-end implementation: DB migration & types, DB wiring, lorebook version accessor, InteractiveVaultService tracking/persistence and tool-context wiring, vault merge utilities, vault-editor composed previews and editorDirty, lorebook editor vault-driven sync, assistant UI save/injection/modal changes, UI composed previews and editor wiring, and small infra/tooling edits. Adds VaultConversation.entryVersions type, migration 035 adding entry_versions column, registers the migration, and extends DatabaseService INSERT/UPDATE/SELECT to persist/restore entryVersions. range_d7460d4bceac range_7d183c17a33d range_ff98b11666d1 range_4a8d0bdf106b range_60f4eed639c8 range_0b8cf2f970fd range_7bcfac8490c7 Adds `getEntryVersion(id)` to LorebookVaultStore to expose a lorebook's entry-change version derived from updatedAt, defaulting to 0 when absent. range_13b10a2b3ea9 InteractiveVaultService gains `_knownEntryVersions`, updates it on approvals/rejections, injects system notes when external lorebook-entry changes are detected, persists/restores entryVersions in save/load, includes activeLorebookId in lorebook-entry tool context, and clears the map on reset. range_0ac362a0d8f0 range_7c176d5cd073 range_3e87427d0db9 range_a2a4f9b0ff18 range_8f6be37df14d range_09cbe868e8f1 range_11b79f38d503 range_53836e73a382 range_c181c06abc8d range_791a2bf17ef7 range_4255ce06dec9 Adds `activeLorebookId` to LorebookEntryToolContext and `resolveTargetEntries` so lorebook-entry tools fall back to the active lorebook, perform live lookup when available, adjust bounds/errors, and record the resolved lorebookId on pending changes. range_ed8b2dfa26fa range_8b0aaf953d34 range_1f627e44d2e2 range_d4d9802f52ec range_049c96f48689 range_71ddff9bda85 range_fa160446c0ce range_af5b205876d4 range_4a8518b76ef3 Adds vaultMerge utilities used to compute field-level deltas and merge array/list intents: `mergeArrayLists`, `mergeIntent`, `computeDelta`, `composePendingOnto`, and record converters for scenarios/characters. range_31a6efde61cc range_30d2afa2ed6a range_7630bdb955be range_e373feb0f219 range_897c4e67be75 range_23440692e201 range_788af0f08db6 Adds reactive `editorDirty`, implements `composedData` and `composePendingChanges()` to build composed previews by applying pending intents over live vault state, adds `getEffectiveChange()`, and updates open/close transitions and reset behavior. range_c64d2da797d0 range_cf401f44c8f2 range_52fbb8e08f02 range_613039be6222 range_7024bdb504ab range_c66a854e1eb3 range_3afcd3cb52e5 VaultLorebookEditorContent initializes entries from the vault in embedded mode, adds a vault-driven effect to sync entries pointwise while skipping locally edited indices, overlays pending edit data into displayed entries, merges externally added entries on save, provides Revert Changes, propagates entriesDirty to vaultEditor.editorDirty, and adds unsaved-change close/save keyboard handling and UI wiring. range_40ca89050d3a range_af5bbef8b15d range_f707f9b4275d range_81519808e63c range_172354345a07 range_20d1c373026d range_32e3a205514a range_6aa862e292c6 range_83081ceb5bbd range_1ca33101b47c range_8920e0c235c8 range_e0d49d3d48cf range_25e88d770a26 range_39d782d288de range_a434f366c273 InteractiveVaultAssistant injects lorebook-change check before streaming, saves conversation after approve/reject/approveAll (and blocks when `vaultEditor.editorDirty`), and centralizes modal open/close handling to abort and clear generation state. range_76e040621287 range_035eff0b108a range_e5b161a6f86e range_6ac617174373 range_88342177eccb range_61292d4473ea range_b3e634230dd6 Diff view and edit panels use composed previews: VaultDiffView computes post-update `afterData`; VaultEntityEditPanel and other editors initialize and present composed/view-mode data, scope updates to the current pending change, reflect changed fields, and editors adopt unsaved-change flows, id-based derived editors, and keyboard shortcuts. range_702ed20b4cd2 range_2dbed1ec6630 range_0e74402db2a0 range_2c0e3e58c15b range_9c7543ce50b6 range_99763b20a99c range_5e743d22aa92 range_96b00a9fb736 range_9cc5982ecf93 range_e482a849fde8 range_345c7e919d62 range_8da38960a21e range_beeb87e20b8f range_ac9a618f84db range_eaa5769cf0fb range_8a5f13c2f9b0 range_ed89b7710f41 range_d66b747ae3a3 range_2857e8f7ed66 range_09437941493d range_612ef5e7fc16 range_2369e08b540e range_c42232834ac6 range_c6e64f3621ed range_319c0c014bfc range_18c91687e885 Character and scenario update tools filter out `undefined`, `null`, and empty-string scalar values when building pending updates to avoid accidental overwrites. range_1cbeaeff2d38 range_5cd33e195e18 Small config and dependency edits: package.json dependency bumps and Vite plugin ordering swap. range_bdc69da127eb range_38b2581a6b15 range_1e205374a008 range_59b3d333d929 Small UI updates: tag list keying change, lorebook list loop variable fix, modal open/close and keyboard binding updates across editors, added imports/icons and toast usage. range_23e72488fead range_eaa5769cf0fb range_c926b760024e range_f89ac13ae73a range_6c58415eaa52 range_40ca89050d3a range_a434f366c273
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Bugfix Vault Assistant Persistence, Display, and AI Re-indexing' accurately summarizes the main changes: persistence fixes across multiple components, display/UI improvements, and the new AI-driven lorebook re-indexing feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces entry version tracking for lorebooks to detect external edits between chat turns and across sessions, updating the database schema, Svelte stores, and AI services accordingly. Feedback on these changes highlights a potential silent fallback bug in the lorebook tools when global access is unavailable, which could lead to operations executing on the wrong lorebook. Additionally, it is recommended to avoid using a Svelte $effect to sync local state back from the store, as the component should remain the source of truth for edits.

Comment thread src/lib/components/vault/VaultLorebookEditorContent.svelte
Comment thread src/lib/services/ai/sdk/tools/lorebook.ts Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/lib/services/database.ts (1)

3353-3363: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize entry_versions with the same shape the rest of the feature expects.

conversation.entryVersions ?? '{}' writes an object even though this field is defined as [lorebookId, version][]. New conversations created through this path will persist an incompatible payload before any later save touches them.

Suggested fix
       [
         conversation.id,
         conversation.title,
         conversation.createdAt,
         conversation.updatedAt,
         conversation.messages,
         conversation.chatMessages,
         conversation.pendingChanges,
-        conversation.entryVersions ?? '{}',
+        conversation.entryVersions ?? '[]',
       ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/services/database.ts` around lines 3353 - 3363, The INSERT is
persisting conversation.entryVersions as an object string '{}' which doesn't
match the expected [lorebookId, version][] shape; update the value passed for
entry_versions in the INSERT (the parameter currently using
conversation.entryVersions ?? '{}') to serialize an array shape instead, e.g.
JSON.stringify(conversation.entryVersions ?? []), so new rows store an empty
array or the proper [id,version] pairs; locate the INSERT into
vault_assistant_conversations and change the entryVersions binding accordingly
(reference: conversation.entryVersions).
src/lib/services/ai/sdk/tools/lorebook.ts (2)

137-140: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Report the live bounds in read_entry errors.

This branch validates against targetEntries, but the error still prints the original entries length, so cross-lorebook reads can return the wrong range.

Suggested fix
         if (index < 0 || index >= targetEntries.length) {
           return {
             found: false,
-            error: `Entry index ${index} out of range (0-${entries.length - 1})`,
+            error: `Entry index ${index} out of range (0-${targetEntries.length - 1})`,
           }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/services/ai/sdk/tools/lorebook.ts` around lines 137 - 140, In
read_entry, the out-of-range error message validates index against targetEntries
but prints entries.length; change the error to report the live bounds using
targetEntries.length (e.g., "Entry index ${index} out of range
(0-${targetEntries.length - 1})") so cross-lorebook reads show the correct
range; update the error string where the function checks "if (index < 0 || index
>= targetEntries.length)" and ensure any other messages in read_entry that
reference the entries length also use targetEntries.

201-229: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the resolved targetId in the emitted pending changes.

These executors resolve targetId = lorebookId ?? activeLorebookId for lookup, but the pending change still stores the raw lorebookId. That leaves lorebookId undefined whenever the tool is used in bound-context mode, which breaks consumers of createLorebookEntryTools() outside InteractiveVaultService.

Suggested fix
-        const pendingChange: LorebookEntryPendingChangeSchema = {
+        const pendingChange: LorebookEntryPendingChangeSchema = {
           id: changeId,
           type: 'create',
           toolCallId: changeId,
-          lorebookId,
+          lorebookId: targetId,
           entry: newEntry,
           status: 'pending',
         }

Apply the same lorebookId: targetId change in update_entry, delete_entry, and merge_entries.

Also applies to: 307-316, 375-383, 455-463

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/services/ai/sdk/tools/lorebook.ts` around lines 201 - 229, The
pending change objects are using the raw lorebookId instead of the resolved
targetId, so when lorebookId is omitted (bound-context mode) the pending change
stores undefined; update the pendingChange construction in the create (near
generateId and LorebookEntryPendingChangeSchema), and similarly in update_entry,
delete_entry, and merge_entries to set lorebookId: targetId (use the same
resolved targetId variable used for the lookup) so all emitted pending changes
reference the actual resolved lorebook ID.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/migrations/035_entry_versions.sql`:
- Around line 5-6: The migration adds column entry_versions to
vault_assistant_conversations but sets a JSON-object default ('{}') while the
column is documented to store an array of tuples ([lorebookId, version][]);
update the SQL ALTER TABLE to use a JSON array default ('[]') instead of '{}'
and also update any JS insert fallback that seeds or inserts entry_versions to
use an empty array JSON ('[]') so new rows and runtime inserts share the correct
tuple-array shape for parsing/iteration.

In `@src/lib/components/vault/VaultLorebookEditorContent.svelte`:
- Around line 114-138: The merge uses entry.name as identity which causes
duplicates on rename and resurrects deletions; change reconciliation to match on
a stable unique id (e.g., entry.id or add one if missing) instead of name: build
a map keyed by id from vaultLorebook.entries, and a local id set from entries,
then when iterating use id to find vaultEntry and replace/update the localEntry
(copying fields like name) rather than appending a new record; ensure
locallyDeleted checks use the id (locallyDeleted should track ids or tombstones)
so missing vault entries that were deleted externally are not re-added. Apply
the same id-based logic to the other merge at the 448-457 location.
- Around line 486-488: After a successful save (after await
saveHandler(updatedLorebook) in VaultLorebookEditorContent.svelte) clear the
locallyDeleted tombstone set so names removed and persisted during this session
do not remain suppressed; specifically reset the locallyDeleted collection
(e.g., reassign an empty Set/array) immediately after the await and before
setting saving = false / entriesDirty = false so subsequent syncs or new entries
with the same name are not erroneously ignored.

In `@src/lib/services/ai/vault/InteractiveVaultService.ts`:
- Around line 134-135: The _knownEntryVersions map in InteractiveVaultService
holds per-lorebook versions but isn't cleared by reset(), causing stale state to
persist; update the reset() method of InteractiveVaultService to clear
_knownEntryVersions (e.g., call this._knownEntryVersions.clear()) along with any
other state resets so the map does not carry over between conversations and
trigger false "modified externally" notes.

In `@src/lib/stores/lorebookVault.svelte.ts`:
- Around line 17-18: _entryVersions is an in-memory Map so versions reset on
reload; persist or derive these per-lorebook versions and restore them in load()
so cross-session comparisons work. Modify the code paths that mutate
_entryVersions (the increment logic where entries are added/removed/updated) to
also write the Map (or a per-lorebook monotonic value like lastModified or a
persisted counter) into the same persistent store you use for lorebook data,
serializing the Map to JSON, and update load() to deserialize and repopulate the
private _entryVersions: Map<string, number> (or reconstruct versions
deterministically from persisted metadata such as timestamps or an incrementing
counter) so versions survive reloads and increments are persisted atomically
with entry changes.
- Around line 57-62: The update method currently increments this._entryVersions
before calling database.updateVaultLorebook, so a failed write still bumps the
version; change update(id: string, updates: Partial<VaultLorebook>) to await
database.updateVaultLorebook(id, updates) first and only then, if
updates.entries is present, calculate and set the new version on
this._entryVersions (i.e., move the version bump to after the successful await
and make it conditional on updates.entries) so failures don't advance the stored
version.

---

Outside diff comments:
In `@src/lib/services/ai/sdk/tools/lorebook.ts`:
- Around line 137-140: In read_entry, the out-of-range error message validates
index against targetEntries but prints entries.length; change the error to
report the live bounds using targetEntries.length (e.g., "Entry index ${index}
out of range (0-${targetEntries.length - 1})") so cross-lorebook reads show the
correct range; update the error string where the function checks "if (index < 0
|| index >= targetEntries.length)" and ensure any other messages in read_entry
that reference the entries length also use targetEntries.
- Around line 201-229: The pending change objects are using the raw lorebookId
instead of the resolved targetId, so when lorebookId is omitted (bound-context
mode) the pending change stores undefined; update the pendingChange construction
in the create (near generateId and LorebookEntryPendingChangeSchema), and
similarly in update_entry, delete_entry, and merge_entries to set lorebookId:
targetId (use the same resolved targetId variable used for the lookup) so all
emitted pending changes reference the actual resolved lorebook ID.

In `@src/lib/services/database.ts`:
- Around line 3353-3363: The INSERT is persisting conversation.entryVersions as
an object string '{}' which doesn't match the expected [lorebookId, version][]
shape; update the value passed for entry_versions in the INSERT (the parameter
currently using conversation.entryVersions ?? '{}') to serialize an array shape
instead, e.g. JSON.stringify(conversation.entryVersions ?? []), so new rows
store an empty array or the proper [id,version] pairs; locate the INSERT into
vault_assistant_conversations and change the entryVersions binding accordingly
(reference: conversation.entryVersions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b21f011f-337c-48bf-9128-0bc8bf5a4d1c

📥 Commits

Reviewing files that changed from the base of the PR and between d1929ff and 5fcfe8e.

📒 Files selected for processing (9)
  • src-tauri/migrations/035_entry_versions.sql
  • src-tauri/src/lib.rs
  • src/lib/components/vault/InteractiveVaultAssistant.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/services/ai/sdk/tools/lorebook.ts
  • src/lib/services/ai/vault/InteractiveVaultService.ts
  • src/lib/services/database.ts
  • src/lib/stores/lorebookVault.svelte.ts
  • src/lib/types/index.ts

Comment thread src-tauri/migrations/035_entry_versions.sql Outdated
Comment thread src/lib/components/vault/VaultLorebookEditorContent.svelte Outdated
Comment thread src/lib/components/vault/VaultLorebookEditorContent.svelte
Comment thread src/lib/services/ai/vault/InteractiveVaultService.ts
Comment thread src/lib/stores/lorebookVault.svelte.ts Outdated
Comment thread src/lib/stores/lorebookVault.svelte.ts
Extract the repeated lorebookId + getLorebookEntries + activeLorebookId
resolution pattern into a resolveTargetEntries helper shared by all
5 entry tools (list/read/update/delete/merge).

Fixes: when a specific lorebookId is requested but global lorebook access
(getLorebookEntries) is not available, the tool now returns a clear
error instead of silently operating on the wrong lorebook.
The entry_versions column is parsed as [string, number][] (array of tuples)
for Map construction. Using '{}' as default produces an object at runtime
which doesn't match the expected iterable shape. Changed to '[]' in both
the migration SQL and the JS insert fallback.
Name-based matching in the sync effect couldn't detect renames: the vault
had the new name but local had the old one, so both appeared as unrelated
entries. Added index-based fallback with duplicate tracking: when a local
entry's name isn't found in the vault, the vault entry at the same index
is applied if it wasn't already matched by name from another position.
- Replace in-memory _entryVersions map with updatedAt-derived version
  (persists across reloads, no false negatives, no migration needed)
- Replace name-based sync effect with position-based pointwise comparison
  (fixes duplicate entries after delete approval, handles duplicate names)
- Overlay change.data onto entry in combinedEntries for edit actions
  (fixes rename showing old name in editor sidebar)

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (1)
src/lib/components/vault/VaultLorebookEditorContent.svelte (1)

494-496: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear locallyDeleted after a successful save.

Once a delete is persisted, those names remain tombstoned for the rest of the session. If the vault later receives a new entry with the same name (from a different source), the save-time merge at lines 459-461 will incorrectly suppress it.

🐛 Proposed fix
       await saveHandler(updatedLorebook)
       saving = false
       entriesDirty = false
+      locallyDeleted = new Set()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/vault/VaultLorebookEditorContent.svelte` around lines 494
- 496, The session-level tombstones in locallyDeleted are never cleared after a
successful persist, so subsequent incoming entries with the same name get
suppressed; after awaiting saveHandler(updatedLorebook) in the save flow (the
block that currently sets saving = false and entriesDirty = false) clear/reset
the locallyDeleted collection (e.g. reinitialize locallyDeleted) so those names
are no longer treated as deleted for the remainder of the session.
🧹 Nitpick comments (1)
src/lib/components/vault/VaultLorebookEditorContent.svelte (1)

109-145: 💤 Low value

Index-based sync may race with unsaved local deletions.

If the user deletes an entry locally and the vault is updated externally (e.g., assistant adds a new entry) before the local save completes, the sync effect will overwrite local state with vault state—potentially resurrecting the deleted entry. In embedded mode with immediate saves this window is narrow, but not zero.

Consider guarding the sync by checking entriesDirty or skipping reconciliation entirely while a save is in flight.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/vault/VaultLorebookEditorContent.svelte` around lines 109
- 145, The sync effect ($effect block using lorebookVault.getById and updating
entries) can overwrite local user deletions; modify the effect to early-return
when a local save is in progress or the local buffer is dirty (check
entriesDirty or an in-flight save flag like entriesSaving), so reconciliation is
skipped while entriesDirty === true or a save is pending; ensure you reference
the same symbols (entries, entriesDirty, lorebookVault.getById, and the $effect)
and only perform the pointwise/append/trim merge when no local save/dirtiness is
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/lib/components/vault/VaultLorebookEditorContent.svelte`:
- Around line 494-496: The session-level tombstones in locallyDeleted are never
cleared after a successful persist, so subsequent incoming entries with the same
name get suppressed; after awaiting saveHandler(updatedLorebook) in the save
flow (the block that currently sets saving = false and entriesDirty = false)
clear/reset the locallyDeleted collection (e.g. reinitialize locallyDeleted) so
those names are no longer treated as deleted for the remainder of the session.

---

Nitpick comments:
In `@src/lib/components/vault/VaultLorebookEditorContent.svelte`:
- Around line 109-145: The sync effect ($effect block using
lorebookVault.getById and updating entries) can overwrite local user deletions;
modify the effect to early-return when a local save is in progress or the local
buffer is dirty (check entriesDirty or an in-flight save flag like
entriesSaving), so reconciliation is skipped while entriesDirty === true or a
save is pending; ensure you reference the same symbols (entries, entriesDirty,
lorebookVault.getById, and the $effect) and only perform the
pointwise/append/trim merge when no local save/dirtiness is present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 146d86e1-49b3-46c6-9481-ca1f3878fff2

📥 Commits

Reviewing files that changed from the base of the PR and between 5668e1c and b06ccd4.

📒 Files selected for processing (2)
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/stores/lorebookVault.svelte.ts

Persisted deletions should not remain tombstoned — otherwise reusing
a deleted entry's name in a new entry would be suppressed by the save
path merge. Reset the set after saveHandler resolves.
Defensive cleanup — prevents stale version data from persisting
between conversations if any code path reads the map between
reset() and the next conversation load.
- read_entry used closure entries.length instead of resolved
  targetEntries.length in out-of-range error (cross-lorebook reads
  would show wrong bounds)
- All lorebook entry tools (create, update, delete, merge) stored the
  raw optional lorebookId parameter in pending changes instead of the
  resolved targetId, causing undefined IDs in bound-context mode
- resolveTargetEntries now returns targetId alongside targetEntries
@EventideMiles

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread src-tauri/migrations/035_entry_versions.sql
Comment thread src/lib/components/vault/InteractiveVaultAssistant.svelte
@EventideMiles

Copy link
Copy Markdown
Contributor Author

So, I'm actually about to get some sleep for a few hours and I'll be picking up work on this branch tonight. Specifically I discovered another issue regarding scenario edits I'd like to address since it seems to follow the pattern I used for vault lorebook entries. Will be addressing comments and then working on the changes to fix the issues I'm experiencing.

VaultScenarioEditor initialized formData once from the scenario prop and
never re-synced when scenarioVault changed after AI approval. VaultPanel
held a stale editingScenario snapshot instead of deriving from the store.

- Add $effect in VaultScenarioEditor to sync formData from scenarioVault
  when the store version diverges (matches VaultLorebookEditorContent pattern)
- Replace editingScenario with editingScenarioId + $derived lookup in
  VaultPanel so the editor always receives the latest store data
… preview

- Compute delta (changed fields only) when approving scenario/character
  updates so sequential approvals compose instead of overwriting
- Filter null and empty-string from AI tool cleanUpdates to prevent
  accidental data loss (matching lorebook entry pattern)
- Add composedData to vaultEditorStore: merges all pending changes for
  the same entity so editor shows what Approve All would produce
- VaultEntityEditPanel uses composed preview for form state and
  changed-field highlighting
- VaultDiffView shows composed After with all-changes badge when
  multiple updates target the same entity
@EventideMiles

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/vault/VaultDiffView.svelte`:
- Around line 258-289: The derived composedData currently builds the preview
from raw vaultEditor.pendingChanges so edits made via updateChangeData() (which
only updates the edited-change overlay) aren’t reflected; change this to resolve
pending changes through the store helper that materializes edited overlays (use
the existing function that returns resolved/merged pending changes or add one in
the vaultEditor store), then iterate that resolvedPendingChanges instead of
vaultEditor.pendingChanges inside composedData (and apply the same replacement
where the other similar derived block is used around the other instance
mentioned).

In `@src/lib/components/vault/VaultEntityEditPanel.svelte`:
- Around line 39-50: The current $effect uses vaultEditor.composedData to seed
charData/scenarioData which causes emitUpdate()/updateChangeData() to persist
the merged multi-change snapshot; instead initialize local editable state from
this change's own data (i.e. use 'data' in change ? change.data : null) for
charData, scenarioData and entryData and only collapse or compute a composed
preview for display (not for persistence); ensure
emitUpdate()/updateChangeData() is passed the delta scoped to the active change
(compute the patch against change.previous or use the original change.data as
base) so saving a change does not include sibling pending updates (review
symbols: composedData, vaultEditor.composedData, change, charData, scenarioData,
entryData, emitUpdate, updateChangeData, $effect).

In `@src/lib/components/vault/VaultPanel.svelte`:
- Around line 95-98: The template currently gates the modal by editingScenarioId
but editingScenario = $derived(...) can be null if
scenarioVault.getById(editingScenarioId) returns null, causing
VaultScenarioEditor to receive a null scenario and crash; update the logic so
the modal (and rendering of <VaultScenarioEditor ...>) is conditional on
editingScenario being truthy, or when scenarioVault.getById(editingScenarioId)
returns null immediately clear editingScenarioId; refer to editingScenarioId,
editingScenario, scenarioVault.getById and VaultScenarioEditor to locate and fix
the checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fc25259-472f-41af-a0dc-1fc01a59b45a

📥 Commits

Reviewing files that changed from the base of the PR and between bb2c908 and f80d3c0.

📒 Files selected for processing (8)
  • src/lib/components/vault/VaultDiffView.svelte
  • src/lib/components/vault/VaultEntityEditPanel.svelte
  • src/lib/components/vault/VaultPanel.svelte
  • src/lib/components/vault/VaultScenarioEditor.svelte
  • src/lib/services/ai/sdk/tools/character.ts
  • src/lib/services/ai/sdk/tools/scenario.ts
  • src/lib/services/ai/vault/InteractiveVaultService.ts
  • src/lib/stores/vaultEditorStore.svelte.ts

Comment thread src/lib/components/vault/VaultDiffView.svelte Outdated
Comment thread src/lib/components/vault/VaultEntityEditPanel.svelte
Comment thread src/lib/components/vault/VaultPanel.svelte
- composedData now reads from vault state instead of change.previous,
  so already-approved changes (e.g. NPCs) remain visible while other
  pending changes are still awaiting approval
- _autoCloseAfterAction transitions to view mode for same-entity
  approvals instead of closing the editor
- VaultEntityEditPanel uses  display values in view mode,
  eliminating  →  timing gaps that caused stale data
- VaultDiffView shows per-change preview (afterData) instead of
  composed all-changes preview
- Extract shared merge/entity-to-record utilities into vaultMerge.ts,
  removing triplicated _mergeArrayLists, _scenarioToRecord, and
  _characterToRecord across vaultEditorStore, InteractiveVaultService,
  and VaultEntityEditPanel
- Remove dead _computeDelta method from InteractiveVaultService
- Gate scenario editor modal on editingScenario truthiness
… replacements

- Use multiset counting for removals so duplicate values are handled correctly
- Detect genuine replacements vs. positional shifts caused by insertions/deletions
- Insert unused replacements at original indices in descending order
- Fix mergeIntent: use full data copy (not computeDelta) when no live base
- Delegate composePendingOnto to mergeIntent for consistent logic
- Remove dedup guard on append (caller is responsible)
The method already receives the lorebook from the switch statement above,
making the second vault store fetch redundant.
Deduplicates the per-change afterData computation by using the shared
computeDelta from vaultMerge.
…de edit protection

- scopeToOwnChanges scalar fields: use form value only when user explicitly
  edited it (form !== originalData), otherwise keep originalData to prevent
  sibling scalar values from leaking via composedData
- Add formDirty guard to skip syncing view mode data when user is editing,
  preventing AI responses from overwriting in-progress edits
- handleViewModeSave stays open with toast feedback instead of closing
… dirty guard

- Save button stays open after save with toast feedback (creation still closes)
- Add dedicated Close button with X icon beside Save
- Disable save button when no unsaved changes (hasChanges)
- Toast cooldown close protection: first close with dirty state shows warning,
  second attempt within 3s closes
- Ctrl+S keyboard shortcut for saving
- Sync form from vault store after external updates
…dirty guard

- Save button stays open after save with toast feedback (creation still closes)
- Add dedicated Close button with X icon beside Cancel
- Disable save button when no unsaved changes (hasChanges)
- Toast cooldown close protection for button, Escape, and outside click
- Ctrl+S keyboard shortcut for saving
…dirty guard

- Remove onSaveAndClose prop; Save button preserves editor open with toast
- Add dedicated Close button with X icon
- Disable save button when no unsaved changes
- Toast cooldown close protection for button, Escape, and outside click
- Ctrl+S keyboard shortcut for saving
- Track dirtyEntryIndices so sync effect doesn't overwrite user edits
- Reset vaultEditor.editorDirty on component destroy to unblock approve buttons
Use index as key instead of tag value so duplicate tags don't throw
a fatal Svelte runtime error.
Wrap the one-time snapshot capture in untrack() to tell Svelte the
reads of isEmbedded and lorebook are intentionally non-reactive.
@EventideMiles

Copy link
Copy Markdown
Contributor Author

Update: Improved Merge Logic, Editor UX, and Bug Fixes

Overview

Adds ~850 lines across 8 commits on top of the existing PR: rewrites the merge array logic to handle duplicates and out-of-order approvals, reworks all three standalone editors (character, scenario, lorebook) to save without closing, adds toast-based close protection, and fixes several data-leakage bugs in scopeToOwnChanges and approve-button blocking.


Changes

Bug Fixes

1. Sibling scalar leakage in scopeToOwnChanges

When two pending updates modified the same entity (e.g. Change A sets description: "X", Change B sets description: "Y"), approving Change A would leak Change B's value into the approval because scopeToOwnChanges used form[key] — the composed preview — for all scalar fields. Now uses form[key] only when the user explicitly edited it; otherwise preserves originalData[key] (the AI's intent for this specific change).

Files: VaultEntityEditPanel.svelte

2. Svelte each_key_duplicate crash on duplicate tags

TagInput keyed its {#each} block on tag values. A character with duplicate tags (e.g. test-tag-3 at indexes 2 and 5) caused a fatal Svelte runtime error that prevented the character editor from rendering at all. Switched to index-based keys.

Files: TagInput.svelte

3. Approve buttons permanently blocked after closing lorebook editor

vaultEditor.editorDirty was set to entriesDirty in a $effect but never reset when the component unmounted, so all approve buttons stayed blocked until page reload. Added a cleanup function to the effect that resets editorDirty to false on destroy.

Files: VaultLorebookEditorContent.svelte

4. Dead double lorebook fetch in applyLorebookEntryChange

The method received the lorebook from the switch statement above but then re-fetched it from the vault store unnecessarily. Removed the redundant fetch.

Files: InteractiveVaultService.ts


Refactors

5. Rewrite mergeArrayLists with count-based multiset

The previous logic treated any positional difference as a replacement, so when a deletion shifted remaining items left, the shifted item was both kept in place and re-inserted at its new index — causing duplication. Now uses a multiset count to detect true removals (items in prev minus items in data) and only treats a positional difference as a genuine replacement when the new value doesn't exist elsewhere in prev.

Also fixes duplicate handling: if an array has two identical NPCs named "John" and one is removed, only one copy is removed instead of both.

Files: vaultMerge.ts

6. composePendingOnto delegates to mergeIntent

composePendingOnto duplicated the delta-computation logic from mergeIntent inline. Now delegates to mergeIntent for consistent handling of scalars and arrays.

Files: vaultMerge.ts

7. VaultDiffView uses computeDelta utility

Replaced inline key-by-key diff logic with the shared computeDelta utility from vaultMerge.ts.

Files: VaultDiffView.svelte


Features

8. Save-without-close + close button + dirty guard for all editors

All three standalone editors (character, scenario, lorebook) now:

  • Save without closing — the Save button persists and stays open with a toast confirmation. Only the creation path still closes.
  • Dedicated Close button — an X icon button next to Save, replacing the old pattern of "Cancel" or relying on Escape/click-outside.
  • Dirty-state save button — disabled when no unsaved changes (hasChanges), preventing pointless saves.
  • Toast cooldown close protection — first close attempt with unsaved changes shows a yellow toast warning; a second attempt within 3 seconds closes. Applies to the close button, Escape key, and outside-click.
  • Ctrl+S keyboard shortcut(meta|ctrl)+S triggers save in all editors.

Lorebook-specific: Removed the onSaveAndClose prop entirely. The content tracks dirtyEntryIndices so the sync effect doesn't overwrite the user's in-progress edits.

Files: VaultCharacterForm.svelte, VaultScenarioEditor.svelte, VaultLorebookEditor.svelte, VaultLorebookEditorContent.svelte

9. View-mode edit protection

When the inline editor is in view mode (for already-approved changes), the sync $effect would overwrite user edits whenever a new AI response arrived. Added a formDirty guard: once the user starts editing, the sync effect skips updates until the user saves, preventing disruption.

Files: VaultEntityEditPanel.svelte


Verification

  • npm run check — 0 errors, 4 pre-existing warnings
  • npm run lint — 0 errors, 181 pre-existing boundary-import warnings (unchanged)

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

@EventideMiles Thanks for the thorough writeup! The changes look well-scoped — the multiset rewrite for mergeArrayLists, the editorDirty cleanup-on-destroy fix, and the scalar leakage fix in scopeToOwnChanges all address sharp edges that are easy to miss. Kicking off a review now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
src/lib/components/vault/VaultScenarioEditor.svelte (1)

64-67: 💤 Low value

Timer cleanup missing on component unmount.

Same as VaultCharacterForm — the closeCooldownTimer should be cleared when the component unmounts to avoid stale callbacks.

🧹 Suggested cleanup
+  import { onDestroy } from 'svelte'
+
+  onDestroy(() => {
+    clearTimeout(closeCooldownTimer)
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/vault/VaultScenarioEditor.svelte` around lines 64 - 67,
The component declares a closeCooldownTimer (let closeCooldownTimer) but never
clears it on unmount; add lifecycle cleanup in VaultScenarioEditor.svelte by
importing Svelte's onDestroy and in the onDestroy callback call
clearTimeout(closeCooldownTimer) and set closeCooldownTimer to undefined so any
pending timeout is cancelled when the component is destroyed. Ensure you
reference the existing CLOSE_COOLDOWN_MS, closeCooldownActive and
closeCooldownTimer variables so the cleanup mirrors how the timer is set.
src/lib/components/vault/VaultCharacterForm.svelte (1)

38-41: 💤 Low value

Timer cleanup missing on component unmount.

The closeCooldownTimer is not cleared when the component unmounts. If the user triggers the cooldown and then the component is unmounted (e.g., parent navigates away), the timer callback will fire on a stale component.

🧹 Suggested cleanup
+  import { onDestroy } from 'svelte'
+
+  onDestroy(() => {
+    clearTimeout(closeCooldownTimer)
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/vault/VaultCharacterForm.svelte` around lines 38 - 41, Add
a Svelte onDestroy cleanup that clears the pending cooldown timer and resets
related state: import onDestroy (if not already), and in onDestroy call
clearTimeout(closeCooldownTimer), set closeCooldownTimer = undefined, and set
closeCooldownActive = false so the timer callback cannot run against an
unmounted component; refer to the CLOSE_COOLDOWN_MS, closeCooldownTimer and
closeCooldownActive variables and add this cleanup inside the VaultCharacterForm
component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/components/vault/VaultCharacterForm.svelte`:
- Around line 38-41: Add a Svelte onDestroy cleanup that clears the pending
cooldown timer and resets related state: import onDestroy (if not already), and
in onDestroy call clearTimeout(closeCooldownTimer), set closeCooldownTimer =
undefined, and set closeCooldownActive = false so the timer callback cannot run
against an unmounted component; refer to the CLOSE_COOLDOWN_MS,
closeCooldownTimer and closeCooldownActive variables and add this cleanup inside
the VaultCharacterForm component.

In `@src/lib/components/vault/VaultScenarioEditor.svelte`:
- Around line 64-67: The component declares a closeCooldownTimer (let
closeCooldownTimer) but never clears it on unmount; add lifecycle cleanup in
VaultScenarioEditor.svelte by importing Svelte's onDestroy and in the onDestroy
callback call clearTimeout(closeCooldownTimer) and set closeCooldownTimer to
undefined so any pending timeout is cancelled when the component is destroyed.
Ensure you reference the existing CLOSE_COOLDOWN_MS, closeCooldownActive and
closeCooldownTimer variables so the cleanup mirrors how the timer is set.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1169b1f6-585d-44bd-a7da-e7f8bd18f79f

📥 Commits

Reviewing files that changed from the base of the PR and between f80d3c0 and 494607e.

📒 Files selected for processing (11)
  • src/lib/components/tags/TagInput.svelte
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultDiffView.svelte
  • src/lib/components/vault/VaultEntityEditPanel.svelte
  • src/lib/components/vault/VaultLorebookEditor.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/components/vault/VaultPanel.svelte
  • src/lib/components/vault/VaultScenarioEditor.svelte
  • src/lib/services/ai/vault/InteractiveVaultService.ts
  • src/lib/stores/vaultEditorStore.svelte.ts
  • src/lib/utils/vaultMerge.ts

@EventideMiles

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume and @coderabbitai review

I've addressed the nitpicks and a few other issues found by a local model. Please review the latest changes.

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

@EventideMiles Sure! Resuming automatic reviews and kicking off a review of the latest changes now.

✅ Actions performed

Reviews resumed.


Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vite.config.js`:
- Line 9: The plugins array in vite.config.js currently calls sveltekit() before
tailwindcss(), which can change directive/CSS processing; swap the order to
[tailwindcss(), sveltekit()] in the exported plugins array ( locate the plugins
definition where sveltekit() and tailwindcss() are invoked ), revert the change
unless there is a documented reason for the swap, and then run both dev and
build to confirm no regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1046097a-241f-4e44-87ee-5bfae8bdf746

📥 Commits

Reviewing files that changed from the base of the PR and between 494607e and 49d4379.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json
  • src/lib/components/lorebook/LorebookList.svelte
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultLorebookEditor.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/components/vault/VaultScenarioEditor.svelte
  • src/lib/stores/vaultEditorStore.svelte.ts
  • vite.config.js
💤 Files with no reviewable changes (1)
  • src/lib/stores/vaultEditorStore.svelte.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/components/lorebook/LorebookList.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/components/vault/VaultLorebookEditor.svelte
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte

Comment thread vite.config.js Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
vite.config.js (1)

9-9: ⚡ Quick win

Keep Tailwind ahead of sveltekit() unless you've confirmed the new order is required.

Line 9 now flips the plugin order, but Tailwind’s current SvelteKit setup still documents plugins: [tailwindcss(), sveltekit()]. I’d re-verify this change before merging, or switch back to the documented sequence. (tailwindcss.com)

Suggested diff
-  plugins: [sveltekit(), tailwindcss()],
+  plugins: [tailwindcss(), sveltekit()],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite.config.js` at line 9, The plugins array order was changed to plugins:
[sveltekit(), tailwindcss()] which may break the documented SvelteKit+Tailwind
setup; revert or confirm order by placing tailwindcss() before sveltekit()
(i.e., plugins: [tailwindcss(), sveltekit()]) unless you've explicitly tested
and validated the new sequence—update the plugins array where it is defined to
restore tailwindcss() ahead of sveltekit() or add a comment noting the
intentional change after verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/vault/VaultScenarioEditor.svelte`:
- Line 228: handleSave() must guard against being invoked when saving is true or
hasChanges is false (the same conditions that disable the Save button), because
the global keydown handler (svelte:window onkeydown invoking handleKeydown which
calls handleSave) can trigger repeated saves. Update the handleSave function to
immediately return if saving === true or hasChanges === false, and ensure it
does not proceed to call scenarioVault.update(...) or show duplicate toasts when
those flags prevent a save; reference the existing saving and hasChanges state
variables and the scenarioVault.update(...) call inside handleSave to apply the
early-return guard.

---

Nitpick comments:
In `@vite.config.js`:
- Line 9: The plugins array order was changed to plugins: [sveltekit(),
tailwindcss()] which may break the documented SvelteKit+Tailwind setup; revert
or confirm order by placing tailwindcss() before sveltekit() (i.e., plugins:
[tailwindcss(), sveltekit()]) unless you've explicitly tested and validated the
new sequence—update the plugins array where it is defined to restore
tailwindcss() ahead of sveltekit() or add a comment noting the intentional
change after verification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1046097a-241f-4e44-87ee-5bfae8bdf746

📥 Commits

Reviewing files that changed from the base of the PR and between 494607e and 49d4379.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json
  • src/lib/components/lorebook/LorebookList.svelte
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultLorebookEditor.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte
  • src/lib/components/vault/VaultScenarioEditor.svelte
  • src/lib/stores/vaultEditorStore.svelte.ts
  • vite.config.js
💤 Files with no reviewable changes (1)
  • src/lib/stores/vaultEditorStore.svelte.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/components/lorebook/LorebookList.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/components/vault/VaultLorebookEditor.svelte
  • src/lib/components/vault/VaultCharacterForm.svelte
  • src/lib/components/vault/VaultLorebookEditorContent.svelte

Comment thread src/lib/components/vault/VaultScenarioEditor.svelte
@failerko failerko merged commit 2a41f21 into AventurasTeam:master Jun 10, 2026
2 checks passed
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