Add Vault & Inventory integration: DB schema, IPC, renderer UI, and tests#61
Add Vault & Inventory integration: DB schema, IPC, renderer UI, and tests#61hyperremix wants to merge 34 commits intomainfrom
Conversation
Greptile OverviewGreptile SummaryThis PR adds a first-class Vault + unified Inventory search feature across the stack:
Key integration point: Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| electron/database/schema.ts | Extends SQL schema with vault tables/indexes/triggers; integrates into createSchema. |
| electron/database/vault-items.ts | Implements vault item CRUD/search/reconcile logic; main risk area but no definitive runtime bug found in review. |
| electron/ipc-handlers/vaultHandlers.ts | Adds vault and inventory IPC handlers with input sanitization; includes a deterministic bug where inventory characterId filtering can never match because snapshots don’t set item.characterId. |
| electron/services/saveFileMonitor.ts | Adds inventory snapshot generation, fingerprinting, and vault reconciliation; overall coherent but ensure downstream consumers don’t expect characterId on items (currently never set). |
| src/components/inventory/CharacterInventoryBrowser.tsx | Implements inventory browser UI consuming preload inventory APIs; no deterministic issues found in review. |
| src/components/vault/ItemVault.tsx | Implements vault UI consuming vault IPC APIs; no deterministic issues found in review. |
Sequence Diagram
sequenceDiagram
participant R as Renderer
participant P as Preload
participant I as IPC(Main)
participant S as SaveFileMonitor
participant D as GrailDatabase
participant V as Vault DB
Note over S,D: On save scan (parseFiles)
S->>S: parseSaveDirectory/parseAllSaveDirectories
S->>S: createParsedInventoryItem + createFingerprint
S->>D: upsertVaultItemByFingerprint(fingerprint, fields)
D->>V: INSERT .. ON CONFLICT(fingerprint) DO UPDATE
S->>D: reconcileVaultItemsForScan(presentFingerprints)
D->>V: UPDATE is_present_in_latest_scan/last_seen_at
Note over R,V: User searches Inventory + Vault
R->>P: window.electron.inventory.searchAll(filter)
P->>I: ipcRenderer.invoke('inventory:searchAll', filter)
I->>S: getInventorySearchResult()
I->>D: searchVaultItems(filter)
D->>V: SELECT/COUNT vault_items (+ category EXISTS)
I-->>P: {inventory, vault}
P-->>R: {inventory, vault}
|
@greptile |
Greptile OverviewGreptile SummaryThis PR implements a comprehensive vault and inventory system allowing users to track items from save files without modifying game saves. The implementation adds database schemas for vault storage, save-file reconciliation logic with deterministic fingerprints, IPC handlers with input validation, and polished UI components with drag-and-drop support. Key Changes:
Previous Issue Addressed: Testing: Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| electron/database/schema.ts | Added 3 new vault tables (vault_categories, vault_items, vault_item_categories) with comprehensive indexes and triggers for timestamp management |
| electron/database/vault-items.ts | Implemented comprehensive vault item CRUD operations with fingerprint-based upsert, reconciliation logic, and search with pagination |
| electron/services/saveFileMonitor.ts | Extended to produce typed inventory snapshots with deterministic fingerprints, reconciliation pipeline, and characterId propagation (fixes previous issue) |
| electron/ipc-handlers/vaultHandlers.ts | Implemented defensive IPC handlers with input validation, sanitization, and combined inventory+vault search functionality |
| electron/preload.ts | Exposed vault and inventory APIs on preload surface with proper type safety |
| src/components/inventory/CharacterInventoryBrowser.tsx | New inventory browser with filtering, drag-and-drop vault integration, optimistic UI updates, and character-based navigation |
| src/components/vault/ItemVault.tsx | New vault management UI with search, filtering, category management, and drag-and-drop support |
| electron/database/vault-items.test.ts | Comprehensive tests for vault item operations including upsert deduplication, reconciliation, and category filtering |
| electron/ipc-handlers/vaultHandlers.test.ts | Tests for IPC handler validation, error handling, and combined search functionality |
| electron/types/grail.ts | Added comprehensive type definitions for vault items, categories, inventory snapshots, and search filters |
Sequence Diagram
sequenceDiagram
participant UI as Renderer UI
participant IPC as IPC Handlers
participant DB as GrailDatabase
participant Monitor as SaveFileMonitor
participant FS as File System
Note over Monitor,FS: Save File Scan & Reconciliation
FS->>Monitor: File change detected
Monitor->>Monitor: parseSave() - Parse D2S/SSS files
Monitor->>Monitor: createParsedInventoryItem() - Create fingerprints
Monitor->>Monitor: Create CharacterInventorySnapshot
Monitor->>DB: upsertVaultItemByFingerprint() - Upsert each item
Monitor->>DB: reconcileVaultItemsForScan() - Mark present/missing
Note over UI,DB: User Searches Inventory
UI->>IPC: inventory:searchAll(filter)
IPC->>Monitor: getInventorySearchResult()
Monitor-->>IPC: Return snapshots with ParsedInventoryItems
IPC->>DB: searchVaultItems(filter)
DB-->>IPC: Return VaultItemSearchResult
IPC-->>UI: Combined inventory + vault results
Note over UI,DB: User Vaults an Item
UI->>IPC: vault:addItem(VaultItemUpsertInput)
IPC->>IPC: validateVaultItemInput()
IPC->>DB: addVaultItem()
DB->>DB: Insert into vault_items table
DB->>DB: setVaultItemCategories() if categoryIds provided
DB-->>IPC: Return VaultItem
IPC-->>UI: Return VaultItem
Note over UI,DB: User Manages Categories
UI->>IPC: vault:createCategory(input)
IPC->>DB: addVaultCategory()
DB-->>IPC: Return VaultCategory
UI->>IPC: vault:updateItemTags(itemId, categoryIds)
IPC->>DB: setVaultItemCategories()
DB->>DB: Update vault_item_categories mapping
DB-->>IPC: Success response
Note over UI,DB: User Searches Vault
UI->>IPC: vault:search(filter)
IPC->>IPC: sanitizeFilter()
IPC->>DB: searchVaultItems()
DB->>DB: SQL query with filters & pagination
DB->>DB: attachCategoryIds()
DB-->>IPC: VaultItemSearchResult
IPC-->>UI: Return paginated results
d3c76b3 to
0f58ce2
Compare
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@greptile |
Greptile OverviewGreptile SummaryThis PR adds a comprehensive vault and inventory integration feature to the D2R Arcane Tracker. The implementation includes database schema additions for vault items/categories with proper indexing, a reconciliation pipeline that automatically tracks item presence across save file scans using deterministic fingerprints, defensive IPC handlers with input validation, and full-featured UI components for browsing inventory and managing vaulted items. Key Changes:
Architecture: Test Coverage: Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| electron/database/drizzle/schema/vaultItems.ts | Added vault_items schema with fingerprint-based uniqueness, comprehensive indexing for queries, and foreign key relationships to characters and items |
| electron/database/vault-items.ts | Implemented vault item DB operations: upsert by fingerprint, reconciliation logic for scan presence tracking, search with pagination and filtering |
| electron/services/saveFileMonitor.ts | Extended save file parsing to create typed inventory snapshots with deterministic fingerprints, integrated vault reconciliation pipeline |
| electron/ipc-handlers/vaultHandlers.ts | Added defensive IPC handlers for vault/inventory operations with input validation, sanitization, and typed error handling |
| src/components/inventory/CharacterInventoryBrowser.tsx | New inventory browser component with filtering, vault status tracking, drag-and-drop to vault functionality |
| src/components/vault/ItemVault.tsx | New vault UI with category management, tag assignments, search/filter, and drag-and-drop to unvault items |
| electron/database/vault-items.test.ts | Comprehensive tests for vault DB operations: upsert deduplication, reconciliation, search filtering, category mappings |
| electron/types/grail.ts | Added comprehensive TypeScript types for vault items, categories, inventory snapshots, filters, and reconciliation inputs |
Sequence Diagram
sequenceDiagram
participant User as Renderer UI
participant IPC as IPC Handlers
participant Monitor as SaveFileMonitor
participant DB as GrailDatabase
participant FS as File System
User->>IPC: inventory:searchAll(filter)
IPC->>Monitor: getInventorySearchResult()
Monitor-->>IPC: CharacterInventorySnapshot[]
IPC->>DB: searchVaultItems(filter)
DB-->>IPC: VaultItemSearchResult
IPC-->>User: {inventory, vault}
FS->>Monitor: Save file modified
Monitor->>Monitor: parseSave() → ParsedInventoryItem[]
Monitor->>Monitor: createFingerprint(item)
Monitor->>DB: upsertVaultItemByFingerprint()
DB->>DB: INSERT ON CONFLICT UPDATE
Monitor->>DB: reconcileVaultItemsForScan()
DB->>DB: Mark present/missing items
User->>IPC: vault:addItem(item)
IPC->>DB: addVaultItem(item)
DB-->>IPC: VaultItem
IPC-->>User: VaultItem
User->>IPC: vault:updateItemTags(id, categoryIds)
IPC->>DB: setVaultItemCategories()
DB->>DB: DELETE + INSERT mappings
DB-->>IPC: success
IPC-->>User: {success: true}
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ule rebuilds Force-parse save files when inventory snapshots are empty on startup, use characterName as fallback key in character dropdown and filter, and auto-rebuild better-sqlite3 for the correct runtime in dev/test scripts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend vault items with grid position, dimensions, equipped slot, and icon metadata columns. Add spatial layout classification for inventory, stash, and equipped item rendering with overflow board support. Introduce sprite icon resolution and board primitive components for the inventory browser. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d4935f4 to
75efea0
Compare
There was a problem hiding this comment.
💡 Codex Review
d2r-arcane-tracker/electron/services/saveFileMonitor.ts
Lines 1331 to 1335 in 75efea0
This parser only treats .d2i as a shared-stash file and falls back to D2S header offsets for all other extensions, but the monitor also ingests .sss and .d2x; for those files this produces invalid character metadata (often unknown class) that is later written through character upserts and can violate the characters.character_class check, causing repeated batch-write failures whenever legacy stash files are present. The stash branch should include .sss/.d2x and return shared-stash metadata instead of D2S character metadata.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await removeItemFromSaveFile( | ||
| normalizedItem.sourceFilePath, | ||
| normalizedItem.sourceFileType, | ||
| parsedItem.id, | ||
| ); |
There was a problem hiding this comment.
Persist vault record before removing source save item
The vault:addItem flow deletes the item from the save file before inserting/upserting it into vault_items, so any failure in grailDatabase.addVaultItem(...) (for example a transient SQLite write failure or a constraint error) leaves the item removed from the save and absent from the vault. That is a user-data-loss path during a normal vault action and should be avoided by reordering the operations (or adding rollback logic).
Useful? React with 👍 / 👎.
- Add modernStashParser.ts: parses D2R v105 .d2i stash files, classifying items into shared/gems/materials/runes tabs with correct stack counts - Extend constants105 magical_properties through index 381 so the d2s bit-reader no longer throws "Invalid Stat Id: 381" for stacked resource items (gems, runes, materials with stack count > 1) - Add attribute-381 fallback in resolveStackCount for runes and other items whose stack count is encoded exclusively via magic attribute 381 - Add stashFormat.ts: low-level .d2i sector/metadata reader - Integrate parseModernStash into saveFileMonitor for .d2i v105+ files - Expose stashTabKind and stackCount through IPC / CharacterInventoryBrowser - Fix vite.config.ts: pass d2sSourceAliases explicitly to the electron main vite config; vite-plugin-electron does not inherit root resolve.alias, so the @dschu012/d2s/lib/ → src/ redirect was absent from the main-process build, causing a CommonJS resolver failure at startup - Add d2s-compat.d.ts ambient declarations for untyped d2s internals - Add fixtures, unit tests for stashFormat, modernStashParser, updated saveFileMonitor/saveFileEditor/CharacterInventoryBrowser tests - Bump vitest config with d2s source aliases matching vite.config.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resource-stash items (runes, gems, materials) were always showing stackCount = 1 because the v105 conditional field in d2s/items.ts was skipping the 8-bit quantity value with SkipBits(8) instead of storing it. Diagnostic confirmed simple_item = 1 for runes/gems and simple_item = 0 with empty magic_attrs for materials — both rely on this field. - Point @dschu012/d2s at file:../d2s (local clone with the fix applied) - Delete lib/ from clone so Vite aliases to TS source, avoiding Rollup __exportStar static-analysis error on symlinked CJS modules - Raise resolveStackCount upper bound from 99 to 511 (full 9-bit range) - Add type declarations for IMagicAttribute and _readMagicProperties - Fix tests: remove stackCount === 1 assertions that were testing the bug, update bound to <= 511, add simple-item quantity path coverage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ls tab sections
- Fix socketed rune items showing raw item codes (r18, r26) instead of human-readable
names (Ko Rune, Vex Rune) by looking up runeDisplayNameByCode[raw.type] first in
getSocketedItemName(), since d2s encodes the item code in `type`, not `name`
- Fix Lo Rune (and all runes) failing to show their icon in socket tooltip entries by
adding runeImageFilenameByCode[raw.type] as the first icon candidate, resolving in
one IPC cache hit instead of ~11 sequential misses before reaching {name}_rune.png
- Add RunesTabSection, GemsTabSection, and MaterialsTabSection components with
dedicated grouped layouts for rune/gem/material stash tabs
- Wire new tab sections into CharacterInventoryBrowser based on fallbackTabKind
- Add pointer-events-none to tooltip content to prevent hover capture issues
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…patch Replace file:../d2s with viniciusrezende/d2s#feat/support-reign-of-warlock and apply the one-line stack-count fix (SkipBits → ReadUInt8) as a managed bun patch in patches/@dschu012+d2s.patch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The waitFor sentinel was screen.getByText('Selected Item'), which is an
unconditional card title always present before data loads. On slower CI
machines (especially with coverage overhead) the async searchAll mock had
not yet resolved when getByTestId ran immediately after waitFor.
Replace the two-step pattern with a single
await waitFor(() => screen.getByTestId('mercenary-board-merc-unknown'))
so the assertion only proceeds once the board is actually in the DOM.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe8ffe1897
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (parsedItem.id !== undefined) { | ||
| await removeItemFromSaveFile( | ||
| normalizedItem.sourceFilePath, | ||
| normalizedItem.sourceFileType, | ||
| parsedItem.id, |
There was a problem hiding this comment.
Handle id-less modern stash items in
vault:addItem
When vault:addItem is used on a rune/gem/material from a v105 .d2i, rawItemJson often has no numeric id (the move-input validation in this file already documents that simple .d2i items are located by position instead). In that case this branch skips removeItemFromSaveFile but still inserts the vault row, so the item stays in the stash and also appears in the vault. That duplicates stackables for modern-stash users unless this falls back to the position-based removal path.
Useful? React with 👍 / 👎.
| ); | ||
| } | ||
| } | ||
|
|
||
| return grailDatabase.addVaultItem(normalizedItem); |
There was a problem hiding this comment.
Persist the vault record before deleting the source item
This removes the item from the save file before calling grailDatabase.addVaultItem. If the DB write fails afterwards (for example, SQLite I/O/locking errors), the handler exits with the item already deleted from the character or stash and no vault record created. Because the file mutation is irreversible here, vaulting becomes destructive under ordinary persistence failures.
Useful? React with 👍 / 👎.
| const readOnly = false; | ||
|
|
||
| if (extension === '.d2i') { | ||
| try { | ||
| const metadata = readD2iMetadata(buffer); |
There was a problem hiding this comment.
Mark modern v105 stash snapshots as read-only
This hard-codes every parsed snapshot as writable, but the renderer only disables dragging when snapshot.readOnly is true (src/components/inventory/CharacterInventoryBrowser.tsx:3617). For modern .d2i resource tabs, the move backend only searches shared-page sectors (electron/services/saveFileEditor.ts:2018-2024), so dragging a rune/gem/material from tabs 5-7 exposes a path that cannot find the source item and fails at runtime. Setting readOnly for modern stash snapshots, or at least for their resource tabs, would prevent that broken interaction.
Useful? React with 👍 / 👎.
…verwrite - Add full targetOptions validation to vault:unvaultItem; guard against silent data loss when targetOptions is provided but vault item is missing - Wrap rawItemJson parse in try/catch (vault:unvaultItem) - Validate categoryIds elements in vault:updateItemTags - Add quality assertion to validateVaultItemInput - Add id assertion to validateCategoryInput (create path) - Add categoryIds, characterId, and page upper-bound checks in sanitizeFilter - Wrap tickReader parse block in try/finally so readingFiles is always reset - Only update sourceCharacterName in setVaultItemsPresentInLatestScan when defined Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Motivation
Description
vault_items,vault_categories, andvault_item_categories, plus converters and Drizzle re-exports to support vault models and indexes/triggers inelectron/database/*.electron/database/vault-items.ts,vault-categories.ts) and surfaced methods onGrailDatabase(add/update/remove/upsert/search/reconcile/mark-missing/set-present, category CRUD & mapping).SaveFileMonitorto produce typed inventory snapshots, deterministic fingerprint creation, and a reconciliation pipeline that upserts fingerprints and calls per-scan reconciliation to mark present/missing items.electron/ipc-handlers/vaultHandlers.ts, exposedvaultandinventoryAPIs on the preload surface (electron/preload.ts), and wiredinitializeVaultHandlersintoelectron/main.tswith access to the save-file monitor.src/components/inventory/CharacterInventoryBrowser.tsx,src/components/vault/ItemVault.tsx, updatedItemDetailsDialogto show vault status/tags and Vault/Unvault actions, updatedTitleBarandrouterto include Inventory/Vault routes, and updated translations usage.electron/database/vault-items.test.ts,electron/ipc-handlers/vaultHandlers.test.ts), save-file monitor reconciliation tests, and frontend component tests forCharacterInventoryBrowser,ItemVault, and updatedItemDetailsDialog.test.tsx; added translation keys insrc/i18n/locales/en/common.jsonand consumed them viasrc/i18n/translations.ts.Testing
bun run typecheckand it completed successfully.bun run format), lint (bun run lint) and combined checks (bun run check) and they all passed.bun run test:run(Vitest); all added and existing tests passed: Test Files 37 passed, Tests 651 passed.net::ERR_EMPTY_RESPONSE) and Electron could not run in this environment due to a missing native library (libatk-1.0.so.0), so visual/electron runtime verification was not possible here.Codex Task