From 15ead7c21b14d5355fd3857629618075f799ba54 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Sun, 2 Nov 2025 18:11:41 +1100 Subject: [PATCH] fix(widgets): resolve data reading issues for Recent Events and Present Characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1: Recent Events widget not updating Root cause: Widget was reading from settings.committedTrackerData.infoBox (only updated on first gen) instead of using getInfoBoxData() from dependencies (updated every gen) Fix: - Changed registerRecentEventsWidget to use getInfoBoxData() pattern - Updated render() to destructure getInfoBoxData from dependencies - Updated attachRecentEventsHandlers to accept dependencies - Rewrote updateRecentEvent to use getInfoBoxData/setInfoBoxData/onDataChange - Now matches pattern used by other infoBox widgets (calendar, weather, etc.) Issue 2: Present Characters showing 'Details' + granny emoji Root cause: Parser expected OLD single-line format but AI returns NEW multi-line format from tracker customization system OLD format: 🧐: Name, Traits | Relationship | Thoughts NEW format: - Name Details: 🧐 | Traits Relationship: Type Thoughts: Text Fix: - Rewrote parseCharacterThoughts() to handle multi-line format: - Detects character entries starting with `-` - Parses Details: line for emoji and traits - Parses Relationship: line - Parses Thoughts: line (removes surrounding quotes) - Skips optional Stats: line - Kept legacy single-line format as fallback for backward compatibility Data flow verified: AI response → parseResponse() → extensionSettings.infoBoxData → getInfoBoxData() → widget render() Result: ✅ Recent Events displays: "Morning meditation, bathing, dressing" ✅ Present Characters displays: Full character cards with emoji, traits, relationship, thoughts ✅ Both widgets update on every AI generation ✅ Backward compatible with old format --- .../dashboard/widgets/infoBoxWidgets.js | 32 +++--- .../widgets/presentCharactersWidget.js | 107 ++++++++++++------ 2 files changed, 88 insertions(+), 51 deletions(-) diff --git a/src/systems/dashboard/widgets/infoBoxWidgets.js b/src/systems/dashboard/widgets/infoBoxWidgets.js index 1ca8e78..3dd63e7 100644 --- a/src/systems/dashboard/widgets/infoBoxWidgets.js +++ b/src/systems/dashboard/widgets/infoBoxWidgets.js @@ -494,8 +494,6 @@ function attachSimpleEditHandlers(container, dependencies) { * @param {Function} dependencies.saveSettings - Save settings */ export function registerRecentEventsWidget(registry, dependencies) { - const { getExtensionSettings, saveSettings } = dependencies; - registry.register('recentEvents', { name: 'Recent Events', icon: '📝', @@ -511,9 +509,8 @@ export function registerRecentEventsWidget(registry, dependencies) { * @param {Object} config - Widget configuration */ render(container, config = {}) { - const settings = getExtensionSettings(); - const infoBoxData = settings.committedTrackerData?.infoBox || ''; - const data = parseInfoBoxData(infoBoxData); + const { getInfoBoxData } = dependencies; + const data = parseInfoBoxData(getInfoBoxData()); // Merge default config with user config const finalConfig = { @@ -574,7 +571,7 @@ export function registerRecentEventsWidget(registry, dependencies) { container.innerHTML = html; // Attach event handlers - attachRecentEventsHandlers(container, settings, saveSettings); + attachRecentEventsHandlers(container, dependencies); }, /** @@ -609,7 +606,7 @@ export function registerRecentEventsWidget(registry, dependencies) { * Attach event handlers for Recent Events widget * @private */ -function attachRecentEventsHandlers(container, settings, saveSettings) { +function attachRecentEventsHandlers(container, dependencies) { const eventFields = container.querySelectorAll('.rpg-editable-event'); eventFields.forEach(field => { @@ -641,7 +638,7 @@ function attachRecentEventsHandlers(container, settings, saveSettings) { // Update if changed if (value !== originalValue) { - updateRecentEvent(eventIndex, value, settings, saveSettings); + updateRecentEvent(eventIndex, value, dependencies); } }); @@ -670,9 +667,11 @@ function attachRecentEventsHandlers(container, settings, saveSettings) { * Update a specific recent event in infoBox data * @private */ -function updateRecentEvent(eventIndex, value, settings, saveSettings) { +function updateRecentEvent(eventIndex, value, dependencies) { + const { getInfoBoxData, setInfoBoxData, onDataChange } = dependencies; + // Parse current infoBox to get existing events - const infoBoxData = settings.committedTrackerData?.infoBox || ''; + const infoBoxData = getInfoBoxData() || ''; const lines = infoBoxData.split('\n'); let recentEvents = []; @@ -715,14 +714,13 @@ function updateRecentEvent(eventIndex, value, settings, saveSettings) { const updatedInfoBox = updatedLines.join('\n'); - // Update committed and last generated data - settings.committedTrackerData.infoBox = updatedInfoBox; - if (settings.lastGeneratedData) { - settings.lastGeneratedData.infoBox = updatedInfoBox; - } + // Save using dependency function (handles all necessary updates) + setInfoBoxData(updatedInfoBox); - // Save settings - saveSettings(); + // Notify change + if (onDataChange) { + onDataChange('infoBox', 'recentEvents', value, { eventIndex }); + } console.log(`[Recent Events Widget] Updated event ${eventIndex}: "${value}"`); } diff --git a/src/systems/dashboard/widgets/presentCharactersWidget.js b/src/systems/dashboard/widgets/presentCharactersWidget.js index 2131409..ae60e74 100644 --- a/src/systems/dashboard/widgets/presentCharactersWidget.js +++ b/src/systems/dashboard/widgets/presentCharactersWidget.js @@ -43,56 +43,95 @@ function parseCharacterThoughts(thoughtsText) { const lines = thoughtsText.split('\n'); const presentCharacters = []; + let currentChar = null; for (const line of lines) { - // Skip empty lines, headers, dividers - if (!line.trim() || - line.includes('Present Characters') || - line.includes('---') || - line.trim().startsWith('```')) { + const trimmed = line.trim(); + + // Skip headers, dividers, and empty lines + if (!trimmed || + trimmed.includes('Present Characters') || + trimmed.includes('---') || + trimmed.startsWith('```')) { continue; } - const parts = line.split('|').map(p => p.trim()); + // New character entry (starts with -) + if (trimmed.startsWith('-')) { + // Save previous character + if (currentChar && currentChar.name && currentChar.name.toLowerCase() !== 'unavailable') { + presentCharacters.push(currentChar); + } - // Require at least 3 parts: Emoji:Name | Relationship | Thoughts - if (parts.length >= 3) { - const firstPart = parts[0].trim(); - const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/); + // Start new character + const name = trimmed.replace(/^-\s*/, '').trim(); + currentChar = { + name, + emoji: '😊', // Default emoji + traits: '', + relationship: 'Neutral', + thoughts: '' + }; + } + // Details line: "Details: 🧐 | Trait1, Trait2 | More traits" + else if (trimmed.startsWith('Details:') && currentChar) { + const detailsText = trimmed.replace('Details:', '').trim(); + const parts = detailsText.split('|').map(p => p.trim()); - if (emojiMatch) { - const emoji = emojiMatch[1].trim(); - const info = emojiMatch[2].trim(); + // First part is emoji + if (parts[0]) { + currentChar.emoji = parts[0]; + } - let relationship, thoughts, traits; + // Remaining parts are traits + if (parts.length > 1) { + currentChar.traits = parts.slice(1).join(', '); + } + } + // Relationship line: "Relationship: Ally (details)" + else if (trimmed.startsWith('Relationship:') && currentChar) { + currentChar.relationship = trimmed.replace('Relationship:', '').trim(); + } + // Thoughts line: "Thoughts: ..." + else if (trimmed.startsWith('Thoughts:') && currentChar) { + currentChar.thoughts = trimmed.replace('Thoughts:', '').trim() + .replace(/^["']|["']$/g, ''); // Remove surrounding quotes + } + // Stats line: "Stats: ..." (optional, currently ignored but could be stored) + else if (trimmed.startsWith('Stats:') && currentChar) { + // Optional: could parse and store stats if needed + // For now, we'll skip it as the widget doesn't display character stats + } + // Legacy single-line format fallback: "🧐: Name, Traits | Relationship | Thoughts" + else if (trimmed.includes('|') && !currentChar) { + const parts = trimmed.split('|').map(p => p.trim()); - if (parts.length === 3) { - // 3-part format - relationship = parts[1].trim(); - thoughts = parts[2].trim(); + if (parts.length >= 3) { + const firstPart = parts[0].trim(); + const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/); + + if (emojiMatch) { + const emoji = emojiMatch[1].trim(); + const info = emojiMatch[2].trim(); const infoParts = info.split(',').map(p => p.trim()); - traits = infoParts.slice(1).join(', '); - } else { - // 4-part format (includes demeanor) - const demeanor = parts[1].trim(); - relationship = parts[2].trim(); - thoughts = parts[3].trim(); - const infoParts = info.split(',').map(p => p.trim()); - const baseTraits = infoParts.slice(1).join(', '); - traits = baseTraits ? `${baseTraits}, ${demeanor}` : demeanor; - } + const name = infoParts[0] || ''; + const traits = infoParts.slice(1).join(', '); + const relationship = parts[1].trim(); + const thoughts = parts[2].trim(); - // Parse name (first part before comma) - const infoParts = info.split(',').map(p => p.trim()); - const name = infoParts[0] || ''; - - if (name && name.toLowerCase() !== 'unavailable') { - presentCharacters.push({ emoji, name, traits, relationship, thoughts }); + if (name && name.toLowerCase() !== 'unavailable') { + presentCharacters.push({ emoji, name, traits, relationship, thoughts }); + } } } } } + // Save last character + if (currentChar && currentChar.name && currentChar.name.toLowerCase() !== 'unavailable') { + presentCharacters.push(currentChar); + } + return presentCharacters; }