fix(widgets): resolve data reading issues for Recent Events and Present Characters

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
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-11-02 18:11:41 +11:00
parent ded3694d54
commit 15ead7c21b
2 changed files with 88 additions and 51 deletions
+15 -17
View File
@@ -494,8 +494,6 @@ function attachSimpleEditHandlers(container, dependencies) {
* @param {Function} dependencies.saveSettings - Save settings * @param {Function} dependencies.saveSettings - Save settings
*/ */
export function registerRecentEventsWidget(registry, dependencies) { export function registerRecentEventsWidget(registry, dependencies) {
const { getExtensionSettings, saveSettings } = dependencies;
registry.register('recentEvents', { registry.register('recentEvents', {
name: 'Recent Events', name: 'Recent Events',
icon: '📝', icon: '📝',
@@ -511,9 +509,8 @@ export function registerRecentEventsWidget(registry, dependencies) {
* @param {Object} config - Widget configuration * @param {Object} config - Widget configuration
*/ */
render(container, config = {}) { render(container, config = {}) {
const settings = getExtensionSettings(); const { getInfoBoxData } = dependencies;
const infoBoxData = settings.committedTrackerData?.infoBox || ''; const data = parseInfoBoxData(getInfoBoxData());
const data = parseInfoBoxData(infoBoxData);
// Merge default config with user config // Merge default config with user config
const finalConfig = { const finalConfig = {
@@ -574,7 +571,7 @@ export function registerRecentEventsWidget(registry, dependencies) {
container.innerHTML = html; container.innerHTML = html;
// Attach event handlers // 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 * Attach event handlers for Recent Events widget
* @private * @private
*/ */
function attachRecentEventsHandlers(container, settings, saveSettings) { function attachRecentEventsHandlers(container, dependencies) {
const eventFields = container.querySelectorAll('.rpg-editable-event'); const eventFields = container.querySelectorAll('.rpg-editable-event');
eventFields.forEach(field => { eventFields.forEach(field => {
@@ -641,7 +638,7 @@ function attachRecentEventsHandlers(container, settings, saveSettings) {
// Update if changed // Update if changed
if (value !== originalValue) { 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 * Update a specific recent event in infoBox data
* @private * @private
*/ */
function updateRecentEvent(eventIndex, value, settings, saveSettings) { function updateRecentEvent(eventIndex, value, dependencies) {
const { getInfoBoxData, setInfoBoxData, onDataChange } = dependencies;
// Parse current infoBox to get existing events // Parse current infoBox to get existing events
const infoBoxData = settings.committedTrackerData?.infoBox || ''; const infoBoxData = getInfoBoxData() || '';
const lines = infoBoxData.split('\n'); const lines = infoBoxData.split('\n');
let recentEvents = []; let recentEvents = [];
@@ -715,14 +714,13 @@ function updateRecentEvent(eventIndex, value, settings, saveSettings) {
const updatedInfoBox = updatedLines.join('\n'); const updatedInfoBox = updatedLines.join('\n');
// Update committed and last generated data // Save using dependency function (handles all necessary updates)
settings.committedTrackerData.infoBox = updatedInfoBox; setInfoBoxData(updatedInfoBox);
if (settings.lastGeneratedData) {
settings.lastGeneratedData.infoBox = updatedInfoBox;
}
// Save settings // Notify change
saveSettings(); if (onDataChange) {
onDataChange('infoBox', 'recentEvents', value, { eventIndex });
}
console.log(`[Recent Events Widget] Updated event ${eventIndex}: "${value}"`); console.log(`[Recent Events Widget] Updated event ${eventIndex}: "${value}"`);
} }
@@ -43,56 +43,95 @@ function parseCharacterThoughts(thoughtsText) {
const lines = thoughtsText.split('\n'); const lines = thoughtsText.split('\n');
const presentCharacters = []; const presentCharacters = [];
let currentChar = null;
for (const line of lines) { for (const line of lines) {
// Skip empty lines, headers, dividers const trimmed = line.trim();
if (!line.trim() ||
line.includes('Present Characters') || // Skip headers, dividers, and empty lines
line.includes('---') || if (!trimmed ||
line.trim().startsWith('```')) { trimmed.includes('Present Characters') ||
trimmed.includes('---') ||
trimmed.startsWith('```')) {
continue; 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 // Start new character
if (parts.length >= 3) { const name = trimmed.replace(/^-\s*/, '').trim();
const firstPart = parts[0].trim(); currentChar = {
const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/); 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) { // First part is emoji
const emoji = emojiMatch[1].trim(); if (parts[0]) {
const info = emojiMatch[2].trim(); 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) { if (parts.length >= 3) {
// 3-part format const firstPart = parts[0].trim();
relationship = parts[1].trim(); const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/);
thoughts = parts[2].trim();
if (emojiMatch) {
const emoji = emojiMatch[1].trim();
const info = emojiMatch[2].trim();
const infoParts = info.split(',').map(p => p.trim()); const infoParts = info.split(',').map(p => p.trim());
traits = infoParts.slice(1).join(', '); const name = infoParts[0] || '';
} else { const traits = infoParts.slice(1).join(', ');
// 4-part format (includes demeanor) const relationship = parts[1].trim();
const demeanor = parts[1].trim(); const thoughts = parts[2].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;
}
// Parse name (first part before comma) if (name && name.toLowerCase() !== 'unavailable') {
const infoParts = info.split(',').map(p => p.trim()); presentCharacters.push({ emoji, name, traits, relationship, thoughts });
const name = infoParts[0] || ''; }
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; return presentCharacters;
} }