v3.7.2: Fix status field key generation for parenthetical names & scroll preservation

- Fix: Status fields with parenthetical descriptions (e.g., 'Conditions (up to 5 traits)') now use the base name for the JSON key ('conditions' instead of 'conditions_up_to_5_traits')
- Fix: Status field value templates no longer repeat the field name with numbered suffixes
- Fix: Editing fields in Present Characters no longer scrolls the panel to the top
- Updated jsonPromptHelpers.js, parser.js, and userStats.js to use new toFieldKey() helper
- Added scroll position preservation to renderThoughts() when re-rendering after field edits
This commit is contained in:
Spicy_Marinara
2026-02-13 18:34:44 +01:00
parent 5498c64f5d
commit 105e20e97a
7 changed files with 76 additions and 19 deletions
+2 -5
View File
@@ -7,12 +7,9 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
## 🆕 What's New
### v3.7.1
### v3.7.2
- Improved instructions for the model to generate dynamic weather.
- Small fixes and updates to descriptions.
- Fixed a scroll/viewport bug that moved everything up after you edited fields.
- Changed the display of avatars for present characters.
- Minor bug fixes
**Special thanks to all the other contributors for this project:**
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, Tomt610, and Jakstein!
+1 -1
View File
@@ -6,6 +6,6 @@
"js": "index.js",
"css": "style.css",
"author": "Marinara",
"version": "3.7.1",
"version": "3.7.2",
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
}
+1 -1
View File
@@ -49,7 +49,7 @@
</div>
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
v3.7.1
v3.7.2
</div>
</div>
</div>
+15 -2
View File
@@ -21,6 +21,19 @@ function toSnakeCase(name) {
.replace(/^_+|_+$/g, '');
}
/**
* Extracts the base name (before parentheses) and converts to snake_case for use as JSON key.
* Parenthetical content is treated as a description/hint, not part of the key.
* Example: "Conditions (up to 5 traits)" -> "conditions"
* Example: "Status Effects" -> "status_effects"
* @param {string} name - Field name, possibly with parenthetical description
* @returns {string} snake_case key from the base name only
*/
function toFieldKey(name) {
const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim();
return toSnakeCase(baseName);
}
/**
* Builds User Stats JSON format instruction
* @returns {string} JSON format instruction for user stats
@@ -60,12 +73,12 @@ export function buildUserStatsJSONInstruction() {
if (customFields.length > 0) {
for (let i = 0; i < customFields.length; i++) {
const fieldName = customFields[i].toLowerCase();
const fieldKey = toSnakeCase(fieldName);
const fieldKey = toFieldKey(fieldName);
const comma = (i === customFields.length - 1 && !userStatsConfig.statusSection.showMoodEmoji) ? '' : (userStatsConfig.statusSection.showMoodEmoji || i < customFields.length - 1 ? ',\n' : '\n');
if (i === 0 && userStatsConfig.statusSection.showMoodEmoji) {
instruction += ',\n';
}
instruction += ` "${fieldKey}": "[${fieldName}1, ${fieldName}2]"${comma}`;
instruction += ` "${fieldKey}": "[${fieldName}]"${comma}`;
}
}
if (!userStatsConfig.statusSection.showMoodEmoji && customFields.length > 0) {
+20 -4
View File
@@ -9,6 +9,20 @@ import { saveSettings } from '../../core/persistence.js';
import { extractInventory } from './inventoryParser.js';
import { repairJSON } from '../../utils/jsonRepair.js';
/**
* Extracts the base name (before parentheses) and converts to snake_case for use as JSON key.
* Example: "Conditions (up to 5 traits)" -> "conditions"
* @param {string} name - Field name, possibly with parenthetical description
* @returns {string} snake_case key from the base name only
*/
function toFieldKey(name) {
const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim();
return baseName
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '');
}
/**
* Helper to separate emoji from text in a string
* Handles cases where there's no comma or space after emoji
@@ -559,10 +573,12 @@ export function parseUserStats(statsText) {
const trackerConfig = extensionSettings.trackerConfig;
const customFields = trackerConfig?.userStats?.statusSection?.customFields || [];
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
if (statsData.status[fieldKey]) {
extensionSettings.userStats[fieldKey] = statsData.status[fieldKey];
// console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, statsData.status[fieldKey]);
const fieldKey = toFieldKey(fieldName);
// Try the base key first (e.g., "conditions"), then fall back to full lowercase name
const value = statsData.status[fieldKey] || statsData.status[fieldName.toLowerCase()];
if (value) {
extensionSettings.userStats[fieldKey] = value;
// console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, value);
}
}
}
+20 -3
View File
@@ -153,11 +153,20 @@ function namesMatch(cardName, aiName) {
* Displays character cards with avatars, relationship badges, and traits.
* Includes event listeners for editable character fields.
*/
export function renderThoughts() {
export function renderThoughts({ preserveScroll = false } = {}) {
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
return;
}
// Save scroll position before re-render if requested
let savedContentScroll = 0;
if (preserveScroll) {
const $content = $thoughtsContainer.find('.rpg-thoughts-content');
if ($content.length) {
savedContentScroll = $content[0].scrollTop;
}
}
// Don't render if no data exists (e.g., after cache clear)
const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts;
if (!thoughtsData) {
@@ -714,6 +723,14 @@ export function renderThoughts() {
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
}
// Restore scroll position after re-render
if (preserveScroll) {
const $content = $thoughtsContainer.find('.rpg-thoughts-content');
if ($content.length) {
$content[0].scrollTop = savedContentScroll;
}
}
// Update chat overlay if enabled
if (extensionSettings.showThoughtsInChat) {
updateChatThoughts();
@@ -1147,8 +1164,8 @@ export function updateCharacterField(characterName, field, value) {
// console.log('[RPG Companion] JSON format updated successfully');
// console.log('[RPG Companion] Updated data:', lastGeneratedData.characterThoughts);
// Re-render the thoughts panel to show updated value
renderThoughts();
// Re-render the thoughts panel to show updated value (preserve scroll position)
renderThoughts({ preserveScroll: true });
// Update chat thought overlays if editing thoughts
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
+17 -3
View File
@@ -23,6 +23,20 @@ import { isItemLocked, setItemLock } from '../generation/lockManager.js';
import { updateFabWidgets } from '../ui/mobile.js';
import { getStatBarColors } from '../ui/theme.js';
/**
* Extracts the base name (before parentheses) and converts to snake_case for use as JSON key.
* Example: "Conditions (up to 5 traits)" -> "conditions"
* @param {string} name - Field name, possibly with parenthetical description
* @returns {string} snake_case key from the base name only
*/
function toFieldKey(name) {
const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim();
return baseName
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '');
}
/**
* Builds the user stats text string using custom stat names
* @returns {string} Formatted stats text for tracker
@@ -107,7 +121,7 @@ function updateUserStatsData() {
// Then, add any other numeric stats from extensionSettings that aren't in config
// (these could be custom stats the AI added or disabled stats)
const customFields = config.statusSection?.customFields || [];
const excludeFields = new Set(['mood', ...customFields.map(f => f.toLowerCase()), 'inventory', 'skills', 'level']);
const excludeFields = new Set(['mood', ...customFields.map(f => toFieldKey(f)), 'inventory', 'skills', 'level']);
Object.entries(stats).forEach(([key, value]) => {
if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') {
statsArray.push({
@@ -127,7 +141,7 @@ function updateUserStatsData() {
// Add all custom status fields
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
const fieldKey = toFieldKey(fieldName);
jsonData.status[fieldKey] = stats[fieldKey] || 'None';
}
@@ -334,7 +348,7 @@ export function renderUserStats() {
// Render custom status fields
if (config.statusSection.customFields && config.statusSection.customFields.length > 0) {
for (const fieldName of config.statusSection.customFields) {
const fieldKey = fieldName.toLowerCase();
const fieldKey = toFieldKey(fieldName);
let fieldValue = stats[fieldKey] || 'None';
// Handle array format (from JSON)
if (Array.isArray(fieldValue)) {