feat(inventory): implement v2 data structure and JSDoc types (Epic 7.1)

Create structured inventory system with categorized storage:
- Add JSDoc type definitions (InventoryV2, InventoryV1, MigrationResult)
- Implement migration utility for v1 → v2 conversion
- Update state.js with v2 inventory structure and feature flag
- Update config.js to match new inventory defaults

Changes:
- NEW: src/types/inventory.js (30 lines) - Type definitions
- NEW: src/utils/migration.js (84 lines) - Migration logic
- MODIFIED: src/core/state.js - v2 inventory + FEATURE_FLAGS
- MODIFIED: src/core/config.js - v2 inventory defaults

Inventory v2 structure:
{
  version: 2,
  onPerson: "Sword, 3x Potions",     // Plaintext string
  stored: {
    "Home": "Clothes, Tools",         // Location → items
    "Bank": "Gold, Documents"
  },
  assets: "Motorcycle, House"         // Plaintext string
}

Migration handles all edge cases:
- v1 string → v2.onPerson
- null/undefined → v2 defaults
- "None" → v2 defaults
- Already v2 → no changes

Context overhead: +40 chars (~5% increase) for organized multi-location storage

Part of Epic 7: New Inventory System
Implements: docs/IMPLEMENTATION_PLAN.md Epic 7.1
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-17 14:58:07 +11:00
parent 658b911d12
commit 110bdb3b64
4 changed files with 141 additions and 2 deletions
+10 -1
View File
@@ -3,6 +3,9 @@
* Extension metadata and configuration constants
*/
// Type imports
/** @typedef {import('../types/inventory.js').InventoryV2} InventoryV2 */
export const extensionName = 'third-party/rpg-companion-sillytavern';
/**
@@ -52,7 +55,13 @@ export const defaultSettings = {
arousal: 0,
mood: '😐',
conditions: 'None',
inventory: 'None'
/** @type {InventoryV2} */
inventory: {
version: 2,
onPerson: "None",
stored: {},
assets: "None"
}
},
classicStats: {
str: 10,
+17 -1
View File
@@ -3,6 +3,9 @@
* Centralizes all extension state variables
*/
// Type imports
/** @typedef {import('../types/inventory.js').InventoryV2} InventoryV2 */
/**
* Extension settings - persisted to SillyTavern settings
*/
@@ -40,7 +43,13 @@ export let extensionSettings = {
arousal: 0,
mood: '😐',
conditions: 'None',
inventory: 'None'
/** @type {InventoryV2} */
inventory: {
version: 2,
onPerson: "None",
stored: {},
assets: "None"
}
},
classicStats: {
str: 10,
@@ -94,6 +103,13 @@ export let isPlotProgression = false;
*/
export let pendingDiceRoll = null;
/**
* Feature flags for gradual rollout of new features
*/
export const FEATURE_FLAGS = {
useNewInventory: true // Enable v2 inventory system with categorized storage
};
/**
* Fallback avatar image (base64-encoded SVG with "?" icon)
* Using base64 to avoid quote-encoding issues in HTML attributes
+30
View File
@@ -0,0 +1,30 @@
/**
* Inventory Type Definitions
* JSDoc types for RPG Companion inventory system v2
*/
/**
* Version 2 inventory structure with categorized storage
* @typedef {Object} InventoryV2
* @property {number} version - Schema version (always 2)
* @property {string} onPerson - Items currently carried/worn by the character (plaintext list)
* @property {Object.<string, string>} stored - Items stored at named locations (location name → plaintext list)
* @property {string} assets - Character's vehicles, property, and major possessions (plaintext list)
*/
/**
* Version 1 inventory structure (legacy string format)
* Simple plaintext string like "Sword, Shield, 3x Potions"
* @typedef {string} InventoryV1
*/
/**
* Result of inventory migration operation
* @typedef {Object} MigrationResult
* @property {InventoryV2} inventory - The migrated inventory data in v2 format
* @property {boolean} migrated - Whether migration was performed (true if v1→v2, false if already v2)
* @property {string} source - Source version ('v1', 'v2', 'null', 'default')
*/
// Export types for JSDoc consumption (this file has no runtime exports)
export {};
+84
View File
@@ -0,0 +1,84 @@
/**
* Inventory Migration Module
* Handles conversion from v1 (string) to v2 (structured) inventory format
*/
// Type imports
/** @typedef {import('../types/inventory.js').InventoryV1} InventoryV1 */
/** @typedef {import('../types/inventory.js').InventoryV2} InventoryV2 */
/** @typedef {import('../types/inventory.js').MigrationResult} MigrationResult */
/**
* Default v2 inventory structure for new/empty inventories
* @type {InventoryV2}
*/
const DEFAULT_INVENTORY_V2 = {
version: 2,
onPerson: "None",
stored: {},
assets: "None"
};
/**
* Migrates inventory data from v1 (string) to v2 (structured) format.
* Handles all edge cases: null, undefined, "None", already-migrated data.
*
* @param {InventoryV1 | InventoryV2 | null | undefined} inventory - Inventory data to migrate
* @returns {MigrationResult} Migration result with v2 inventory and metadata
*/
export function migrateInventory(inventory) {
// Case 1: Already v2 format (has version property and is an object)
if (inventory && typeof inventory === 'object' && inventory.version === 2) {
// console.log('[RPG Companion Migration] Inventory already v2, no migration needed');
return {
inventory: inventory,
migrated: false,
source: 'v2'
};
}
// Case 2: null or undefined → use defaults
if (inventory === null || inventory === undefined) {
// console.log('[RPG Companion Migration] Inventory is null/undefined, using defaults');
return {
inventory: { ...DEFAULT_INVENTORY_V2 },
migrated: true,
source: 'null'
};
}
// Case 3: v1 string format → migrate to v2
if (typeof inventory === 'string') {
// Check if it's an empty/default string
const trimmed = inventory.trim();
if (trimmed === '' || trimmed.toLowerCase() === 'none') {
// console.log('[RPG Companion Migration] Inventory is empty/None, using defaults');
return {
inventory: { ...DEFAULT_INVENTORY_V2 },
migrated: true,
source: 'v1'
};
}
// Non-empty v1 string → migrate to v2.onPerson
// console.log('[RPG Companion Migration] Migrating v1 string to v2.onPerson:', inventory);
return {
inventory: {
version: 2,
onPerson: inventory,
stored: {},
assets: "None"
},
migrated: true,
source: 'v1'
};
}
// Case 4: Unknown format (malformed object, number, etc.) → use defaults
console.warn('[RPG Companion Migration] Unknown inventory format, using defaults:', inventory);
return {
inventory: { ...DEFAULT_INVENTORY_V2 },
migrated: true,
source: 'default'
};
}