From 110bdb3b6470e962ead9b97371d4921057b6b0b1 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Fri, 17 Oct 2025 14:58:07 +1100 Subject: [PATCH] feat(inventory): implement v2 data structure and JSDoc types (Epic 7.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/core/config.js | 11 +++++- src/core/state.js | 18 ++++++++- src/types/inventory.js | 30 +++++++++++++++ src/utils/migration.js | 84 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/types/inventory.js create mode 100644 src/utils/migration.js diff --git a/src/core/config.js b/src/core/config.js index de67a2e..1cca094 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -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, diff --git a/src/core/state.js b/src/core/state.js index 19fffe3..91a7ef1 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -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 diff --git a/src/types/inventory.js b/src/types/inventory.js new file mode 100644 index 0000000..3cfebcf --- /dev/null +++ b/src/types/inventory.js @@ -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.} 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 {}; diff --git a/src/utils/migration.js b/src/utils/migration.js new file mode 100644 index 0000000..b5b0f23 --- /dev/null +++ b/src/utils/migration.js @@ -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' + }; +}