- README: Add Equipment System feature, Code Quality, Performance, Memory Management, Testing, Development Commands, and Tech Stack sections - AGENTS.md: Update source tree with CSS modules, equipment constants, renderUtils, settings consolidation; add development commands, performance optimizations, memory management notes, validator factory; update file reference table Based on PRs: #1 #3 #6 #8 #10 #12 #14 #17
22 KiB
RPG Companion for SillyTavern — Agent Instructions
What this is
A browser extension for SillyTavern (AI roleplay frontend). It is NOT a standalone Node.js application. All JavaScript runs in the browser context inside SillyTavern. There is no bundler — JS files are loaded directly by SillyTavern's extension loader. CSS modules are concatenated via a build script.
Quick start
- Entry point:
index.js(loaded by SillyTavern viamanifest.json) - CSS: Modular — 28 files in
src/styles/, concatenated vianpm run build:css(ornpm run build:css:minfor production) - HTML templates:
template.html(main panel),settings.html(Extensions tab settings drawer) - Runtime: Browser only. All code executes inside SillyTavern's page context.
- jQuery: Available globally as
$/jQuery. Used extensively for DOM manipulation. - ES modules: All
src/files useimport/export(browser ES module syntax). - Testing: Jest + Babel ESM support — 78 tests (
npm test)
Manifest and loading
manifest.json tells SillyTavern how to load the extension:
{
"display_name": "RPG Companion",
"loading_order": 100,
"js": "index.js",
"css": "style.css"
}
SillyTavern loads index.js and style.css relative to the extension directory. The extension name used internally is third-party/rpg-companion-sillytavern.
Extension path detection
src/core/config.js auto-detects whether the extension is installed globally (public/extensions/) or per-user (data/default-user/extensions/) by inspecting import.meta.url. This determines template loading paths.
SillyTavern integration
The extension imports from SillyTavern's global scripts using relative paths that assume the extension lives at:
scripts/extensions/third-party/rpg-companion-sillytavern/(global install), ordata/default-user/extensions/third-party/rpg-companion-sillytavern/(user install)
Key imports in index.js:
import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings }
from '../../../extensions.js';
import { eventSource, event_types, substituteParams, chat, saveSettingsDebounced, ... }
from '../../../../script.js';
import { selected_group, getGroupMembers } from '../../../group-chats.js';
import { power_user } from '../../../power-user.js';
The deeper relative imports in src/ files (e.g., ../../../../../../script.js) follow the same pattern adjusted for directory depth.
SillyTavern globals available at runtime
self.SillyTavern— global namespace object$/jQuery— jQuerytoastr— toast notification libraryeventSource— SillyTavern's event emitterchat— current chat message arraychat_metadata— per-chat metadata storecharacters— loaded character arraygetContext()— returns current chat context
Architecture
Source tree (src/)
src/
├── core/ # Core infrastructure
│ ├── config.js # Extension name, folder path, default settings (single source of truth)
│ ├── state.js # Centralized state variables + getters/setters (imports defaults from config.js)
│ ├── persistence.js # Save/load settings, chat data, migrations
│ ├── events.js # SillyTavern event system wrapper
│ ├── i18n.js # Translation system (fetches JSON, applies data-i18n-key)
│ └── settingsPanel.js # Extracted settings panel UI (from index.js refactor)
├── i18n/ # Translation JSON files
│ ├── en.json # English (reference, 513 lines)
│ ├── fr.json # French
│ ├── ru.json # Russian
│ ├── zh-cn.json # Simplified Chinese
│ ├── zh-tw.json # Traditional Chinese
│ └── validator.js # Node.js script to validate translation consistency
├── styles/ # CSS modules (28 files, concatenated via build script)
│ ├── _01_variables.css # CSS custom properties
│ ├── _02_panel.css # Main panel layout
│ ├── _03_components.css # Buttons, inputs, common components
│ ├── _04_user-stats.css # User stats panel
│ ├── _05_info-box.css # Info box dashboard
│ ├── _06_thoughts.css # Character thoughts
│ ├── _07_settings.css # Settings modal
│ ├── _08_themes.css # Theme definitions (extended custom properties)
│ ├── _09_dice-modal.css # Dice roller modal
│ ├── _10_tracker-editor.css # Tracker configuration editor
│ ├── _11_settings-modal.css # Settings modal styles
│ ├── _12_thought-bubbles.css # Floating thought bubbles
│ ├── _13_mobile.css # Mobile responsive styles
│ ├── _14_inventory.css # Inventory views
│ ├── _15_inventory-views.css # Inventory detail views
│ ├── _16_desktop-tabs.css # Desktop tab navigation
│ ├── _17_quests.css # Quest tracking
│ ├── _18_equipment.css # Equipment grid
│ ├── _19_encounters.css # Combat encounters
│ ├── _20_plot-buttons.css # Plot progression buttons
│ ├── _21_music-player.css # Spotify music player
│ ├── _22_weather.css # Weather effects
│ ├── _23_holiday-promo.css # Holiday promotions
│ ├── _24_feature-toggles.css # Feature toggle switches
│ ├── _25_import-dialog.css # Import/export dialog
│ ├── _26_fab-widgets.css # Mobile FAB widgets
│ ├── _27_strip-widgets.css # Collapsed panel strip widgets
│ └── _28_equipment-modal.css # Equipment edit modal
├── systems/ # Feature systems (organized by concern)
│ ├── generation/ # AI prompt generation, parsing, API calls
│ │ ├── promptBuilder.js # Builds tracker prompts for AI (refactored with focused helpers)
│ │ ├── parser.js # Parses AI responses (~50 pre-compiled regex patterns)
│ │ ├── apiClient.js # Makes API calls for separate/external generation
│ │ ├── injector.js # Injects tracker prompts into generation context
│ │ ├── lockManager.js # Manages locked tracker fields
│ │ ├── suppression.js # Controls when to skip tracker injection
│ │ ├── encounterPrompts.js # Combat encounter prompt building
│ │ ├── inventoryParser.js # Parses inventory data from AI responses
│ │ └── jsonPromptHelpers.js # JSON formatting helpers for prompts
│ ├── rendering/ # UI rendering functions
│ │ ├── userStats.js # Renders user stats panel
│ │ ├── infoBox.js # Renders info box (date, weather, location, events)
│ │ ├── thoughts.js # Renders character thoughts + chat bubbles
│ │ ├── inventory.js # Renders inventory section
│ │ ├── equipment.js # Renders equipment section
│ │ ├── quests.js # Renders quests section
│ │ ├── musicPlayer.js # Renders Spotify music player
│ │ └── renderUtils.js # Rendering utilities (updateIfChanged cache, rAF batching)
│ ├── ui/ # UI components and helpers
│ │ ├── theme.js # Theme system (default, sci-fi, fantasy, cyberpunk, custom)
│ │ ├── modals.js # Settings modal, dice modal, deprecation modal
│ │ ├── layout.js # Panel positioning, collapse/expand, section visibility
│ │ ├── mobile.js # Mobile FAB button, mobile tabs, keyboard handling
│ │ ├── desktop.js # Desktop strip widgets
│ │ ├── trackerEditor.js # Tracker configuration editor
│ │ ├── promptsEditor.js # Custom prompt editor
│ │ ├── checkpointUI.js # Chapter checkpoint buttons
│ │ ├── encounterUI.js # Combat encounter modal
│ │ ├── snowflakes.js # Snowflake animation effect
│ │ ├── weatherEffects.js # Dynamic weather visual effects
│ │ ├── alternatePresentCharacters.js # Below-chat character panel
│ │ └── settingsListeners.js # Settings event listeners (extracted from index.js)
│ ├── equipment/ # Equipment system
│ │ └── constants.js # Equipment slot definitions, type limits, stat config
│ ├── integration/ # SillyTavern event integration
│ │ ├── sillytavern.js # Event handlers: message sent/received, swipe, etc.
│ │ └── thoughtBasedExpressions.js # Integrates with ST Character Expressions
│ ├── interaction/ # User interaction handlers
│ │ ├── inventoryActions.js # Inventory click/edit handlers
│ │ ├── equipmentActions.js # Equipment click/edit handlers
│ │ └── inventoryEdit.js # Inventory inline editing
│ └── features/ # Feature modules
│ ├── plotProgression.js # Plot progression buttons
│ ├── classicStats.js # D&D-style attribute buttons
│ ├── dice.js # Dice roller
│ ├── htmlCleaning.js # Regex-based HTML tag cleaning
│ ├── jsonCleaning.js # Regex-based JSON cleanup in messages
│ ├── musicPlayer.js # Spotify URL parsing and embedding
│ ├── chapterCheckpoint.js # Save/restore tracker state checkpoints
│ ├── avatarGenerator.js # Auto-generate character avatars
│ └── encounterState.js # Combat encounter state management
├── types/ # JSDoc type definitions
│ └── inventory.js # InventoryV2 type definition
└── utils/ # Utility functions
├── avatars.js # Avatar URL handling
├── imageUrls.js # Image URL utilities
├── itemParser.js # Item string parsing
├── jsonMigration.js # v2 to v3 JSON format migration
├── jsonRepair.js # JSON repair for malformed AI responses (14 pre-compiled regex)
├── migration.js # Settings migration helpers
├── presentCharacters.js # Present character data helpers
├── responseExtractor.js # Extract tracker data from AI responses
├── security.js # Input validation and sanitization (validator factory)
├── sillyTavernExpressions.js # ST expression mapping
├── thoughtBasedExpressionPortraits.js # Expression-to-portrait mapping
└── transformations.js # Data transformation utilities
Data flow
- User sends message →
MESSAGE_SENTevent fires - Extension hooks
onMessageSentinsillytavern.js - Together mode: Tracker prompt is injected into the generation context via
injector.js - Separate mode: After main response,
apiClient.jsmakes a separate API call - External API mode: Uses user-configured external API endpoint
- AI responds →
MESSAGE_RECEIVEDevent fires - Extension hooks
onMessageReceived→parser.jsextracts tracker data - Rendering:
renderUserStats(),renderInfoBox(),renderThoughts(), etc. update the UI (batched via rAF) - Persistence: Tracker data is saved to
chat_metadataper chat, and per-swipe
State management
All extension state lives in src/core/state.js as module-level let variables with getter/setter functions. Key state:
extensionSettings— persisted settings object (merged with defaults on load)lastGeneratedData— tracker data from the last AI generationcommittedTrackerData— tracker data committed after a messagelastActionWasSwipe— flag to handle swipe-specific logicisGenerating— flag to prevent re-entrant generationpendingDiceRoll— dice roll result to inject into next generation- DOM element caches:
$panelContainer,$userStatsContainer, etc.
Settings consolidation: config.js is the single source of truth for default settings. state.js imports defaults from config.js (~300 lines eliminated from duplication).
Persistence
src/core/persistence.js handles:
- Extension settings: saved via SillyTavern's
saveSettingsDebounced() - Chat-specific data: saved in
chat_metadata.rpg_companionper chat - Per-swipe data: stored on each chat message object
- Settings versioning: automatic migration via
CURRENT_SETTINGS_VERSION(currently 7) - Deferred saves: batches chat data saves to avoid excessive writes
Memory management
clearDomCache(): Invalidates jQuery object cache on panel rebuild- Event tracking: All
on()/once()handlers tracked for cleanup viaunregisterAllEvents() clearDebugLogs(): Called onCHAT_CHANGEDto prevent unbounded debug log growth
Performance optimizations
- Rendering:
requestAnimationFramebatching inrerenderRpgState(),updateIfChanged()cache comparison inrenderUtils.js, CSScontent-visibility: autoon inventory/equipment containers - Parser: ~64 regex patterns pre-compiled as module-level constants, format detection caching with
lastDetectedFormat(3-hit threshold) - CSS: Minification via
--minifyflag, extended CSS custom properties, theme refactoring (379 → ~220 lines)
Development commands
# Build CSS from modular sources
npm run build:css # Concatenate CSS modules into style.css
npm run build:css:min # Minified production build (~50% smaller)
# Testing
npm test # Run Jest test suite (78 tests)
# Translation validation
npm run validate_locale # Watch mode: re-validate on file changes
npm run validate_locale_once # Single validation run
i18n system
How it works
- Translation files are flat JSON in
src/i18n/{locale}.json - Keys use dot notation:
"template.settingsModal.display.showUserStats" - HTML elements use
data-i18n-key,data-i18n-title, ordata-i18n-aria-labelattributes src/core/i18n.jsfetches translations viafetch()from the extension pathapplyTranslations()walks the DOM and updatestextContent,title, andaria-label
Adding translations
- Add keys to
src/i18n/en.json(reference locale) - Add corresponding keys to all other locale files
- Add
data-i18n-key="your.key.here"to HTML elements intemplate.htmlorsettings.html - For dynamically generated text, use
i18n.getTranslation('your.key.here')in JS
Validator
src/i18n/validator.js is a Node.js script (uses require(), not ES modules). It:
- Compares all locales against the reference locale for missing/extra keys
- Checks for type mismatches
- Checks for empty strings and null values
- Scans
.html/.js/.jsxfiles for unlocalized text (text in tags withoutdata-i18n-key)
Key conventions
jQuery usage
DOM manipulation uses jQuery throughout. Element caches are stored as jQuery objects with $ prefix:
$panelContainer = $('#rpg-companion-panel')
$userStatsContainer = $('#rpg-user-stats')
Event system
All SillyTavern event registration goes through src/core/events.js:
registerAllEvents({
[event_types.MESSAGE_SENT]: onMessageSent,
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar],
});
CSS naming
All CSS classes use rpg- prefix to avoid conflicts with SillyTavern and other extensions:
.rpg-panel,.rpg-section,.rpg-stat-bar,.rpg-btn-primary, etc.- CSS custom properties:
--rpg-bg,--rpg-accent,--rpg-text,--rpg-highlight,--rpg-border,--rpg-shadow
CSS build pipeline
CSS modules in src/styles/ are concatenated in numeric order by scripts/build-css.js. Modules follow the naming convention _NN_name.css where NN is the load order. After modifying any CSS module, run npm run build:css to regenerate style.css.
Validator factory
Input sanitization uses createStringValidator() from src/utils/security.js — a factory pattern that unifies sanitizeLocationName/sanitizeItemName with configurable rules (max length, allowed characters, etc.).
Panel structure
The main panel (template.html) has these sections in order:
- Collapse toggle button
- Strip widget container (shown when panel is collapsed)
- Game container with header
- Dice roll display
- Content box with sections: User Stats → Info Box → Thoughts → Inventory → Equipment → Quests → Music Player
- Feature toggles row (HTML, Dialogue Coloring, Deception, Omniscience, CYOA, Spotify, Weather, Narrator, Auto Avatars)
- Manual update button
- Settings and Edit Trackers buttons
Generation modes
Three modes control how tracker data is generated:
together: Tracker prompt injected into main generation. Single API call, faster, but tracker JSON mixed in response.separate: Two API calls — one for roleplay, one for tracker data. Cleaner roleplay, slower.external: Uses user-configured external API (OpenAI-compatible endpoint) for tracker generation.
Guided generation compatibility
The skipInjectionsForGuided setting controls tracker injection behavior during guided generations:
'none'— always inject (default)'impersonation'— skip only for impersonation-style guided generations'guided'— skip for any guided/instruct or quiet_prompt generation
Important gotchas
-
No bundler: All imports use relative paths that depend on the extension's directory structure within SillyTavern. Changing file locations requires updating import paths.
-
Relative import depth matters: Files in
src/use deeper relative paths (../../../../../../script.js) thanindex.js(../../../../script.js). When moving files between directories, adjust accordingly. -
Settings merge: On load, saved settings are deep-merged with defaults.
config.jsis the single source of truth for default settings —state.jsimports from it. New settings fields should be added toconfig.js. -
Chat metadata: Per-chat tracker data lives in
chat_metadata.rpg_companion. Per-swipe data lives on individual chat message objects in theswipe_store. -
i18n fetch path: Translations are fetched at
/scripts/extensions/third-party/rpg-companion-sillytavern/src/i18n/{lang}.json. The hardcoded path ini18n.jsmust match the extension's actual URL path. -
Extension enable/disable: The extension can be toggled from SillyTavern's Extensions tab. When disabled, all UI elements are removed from the DOM. When re-enabled,
initUI()rebuilds everything. -
Mobile vs desktop: Viewport breakpoint is 1000px. Below: FAB button + mobile tabs. Above: fixed panel + desktop tabs.
-
External API key: Stored in
localStorageasrpg_companion_external_api_key, NOT in extension settings (for security). -
Dev dependencies:
package.jsonhas devDependencies (Jest, Babel, chokidar, fs-extra, glob, execa) for testing and i18n validation. No runtime dependencies — the extension runs entirely in the browser. -
CSS rebuild required: After modifying any file in
src/styles/, runnpm run build:cssto regeneratestyle.css. SillyTavern loadsstyle.css, not the individual modules. -
Deprecation status: The README states this extension is deprecated in favor of Marinara Engine. The code shows a deprecation modal that displays on first load.
-
ESM testing: Jest uses Babel for ESM transformation (
babel.config.json+jest.config.cjs). All test files use ES module syntax.
File reference
| File | Purpose |
|---|---|
index.js |
Main entry point. Imports, UI init, event registration, startup logic (~456 lines after refactor). |
manifest.json |
SillyTavern extension manifest (name, loading order, JS/CSS files). |
style.css |
Built CSS output (concatenated from src/styles/ modules). Do not edit directly. |
template.html |
Main panel HTML template. Rendered by SillyTavern's renderExtensionTemplateAsync. |
settings.html |
Extensions tab settings drawer. Minimal — most settings are in the panel modal. |
scripts/build-css.js |
CSS build script. Concatenates modules, supports --minify flag. |
src/core/state.js |
Central state module. Imports defaults from config.js. All extension-wide variables. |
src/core/persistence.js |
Settings/chat data save/load, version migrations. |
src/core/config.js |
Extension name, path detection, default settings (single source of truth). |
src/core/events.js |
SillyTavern event system wrapper with bulk register/unregister. |
src/core/i18n.js |
Translation loader and DOM applier. |
src/core/settingsPanel.js |
Extracted settings panel UI (from index.js refactor, PR #6). |
src/systems/integration/sillytavern.js |
All SillyTavern event handlers (message lifecycle, swipes, chat changes). |
src/systems/generation/promptBuilder.js |
Builds AI prompts for tracker generation (refactored with focused helpers). |
src/systems/generation/parser.js |
Parses AI responses to extract tracker JSON (~50 pre-compiled regex patterns). |
src/systems/generation/apiClient.js |
API client for separate/external generation modes. |
src/systems/generation/injector.js |
Injects tracker prompts into SillyTavern's generation context. |
src/systems/rendering/renderUtils.js |
Rendering utilities: updateIfChanged() cache, rAF batching. |
src/systems/equipment/constants.js |
Equipment slot definitions, type limits, stat configuration. |
src/utils/security.js |
Input validation, sanitization, validator factory (createStringValidator()). |
src/utils/jsonRepair.js |
JSON repair for malformed AI responses (14 pre-compiled regex patterns). |
src/i18n/validator.js |
Node.js script for validating translation consistency. |