# 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 via `manifest.json`) - **CSS**: Modular — 28 files in `src/styles/`, concatenated via `npm run build:css` (or `npm run build:css:min` for 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 use `import`/`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: ```json { "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), or - `data/default-user/extensions/third-party/rpg-companion-sillytavern/` (user install) Key imports in `index.js`: ```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` — jQuery - `toastr` — toast notification library - `eventSource` — SillyTavern's event emitter - `chat` — current chat message array - `chat_metadata` — per-chat metadata store - `characters` — loaded character array - `getContext()` — 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 1. **User sends message** → `MESSAGE_SENT` event fires 2. **Extension hooks** `onMessageSent` in `sillytavern.js` 3. **Together mode**: Tracker prompt is injected into the generation context via `injector.js` 4. **Separate mode**: After main response, `apiClient.js` makes a separate API call 5. **External API mode**: Uses user-configured external API endpoint 6. **AI responds** → `MESSAGE_RECEIVED` event fires 7. **Extension hooks** `onMessageReceived` → `parser.js` extracts tracker data 8. **Rendering**: `renderUserStats()`, `renderInfoBox()`, `renderThoughts()`, etc. update the UI (batched via rAF) 9. **Persistence**: Tracker data is saved to `chat_metadata` per 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 generation - `committedTrackerData` — tracker data committed after a message - `lastActionWasSwipe` — flag to handle swipe-specific logic - `isGenerating` — flag to prevent re-entrant generation - `pendingDiceRoll` — 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_companion` per 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 via `unregisterAllEvents()` - `clearDebugLogs()`: Called on `CHAT_CHANGED` to prevent unbounded debug log growth ### Performance optimizations - **Rendering**: `requestAnimationFrame` batching in `rerenderRpgState()`, `updateIfChanged()` cache comparison in `renderUtils.js`, CSS `content-visibility: auto` on inventory/equipment containers - **Parser**: ~64 regex patterns pre-compiled as module-level constants, format detection caching with `lastDetectedFormat` (3-hit threshold) - **CSS**: Minification via `--minify` flag, extended CSS custom properties, theme refactoring (379 → ~220 lines) --- ## Development commands ```bash # 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 1. Translation files are flat JSON in `src/i18n/{locale}.json` 2. Keys use dot notation: `"template.settingsModal.display.showUserStats"` 3. HTML elements use `data-i18n-key`, `data-i18n-title`, or `data-i18n-aria-label` attributes 4. `src/core/i18n.js` fetches translations via `fetch()` from the extension path 5. `applyTranslations()` walks the DOM and updates `textContent`, `title`, and `aria-label` ### Adding translations 1. Add keys to `src/i18n/en.json` (reference locale) 2. Add corresponding keys to all other locale files 3. Add `data-i18n-key="your.key.here"` to HTML elements in `template.html` or `settings.html` 4. 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`/`.jsx` files for unlocalized text (text in tags without `data-i18n-key`) --- ## Key conventions ### jQuery usage DOM manipulation uses jQuery throughout. Element caches are stored as jQuery objects with `$` prefix: ```js $panelContainer = $('#rpg-companion-panel') $userStatsContainer = $('#rpg-user-stats') ``` ### Event system All SillyTavern event registration goes through `src/core/events.js`: ```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: 1. Collapse toggle button 2. Strip widget container (shown when panel is collapsed) 3. Game container with header 4. Dice roll display 5. Content box with sections: User Stats → Info Box → Thoughts → Inventory → Equipment → Quests → Music Player 6. Feature toggles row (HTML, Dialogue Coloring, Deception, Omniscience, CYOA, Spotify, Weather, Narrator, Auto Avatars) 7. Manual update button 8. 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 1. **No bundler**: All imports use relative paths that depend on the extension's directory structure within SillyTavern. Changing file locations requires updating import paths. 2. **Relative import depth matters**: Files in `src/` use deeper relative paths (`../../../../../../script.js`) than `index.js` (`../../../../script.js`). When moving files between directories, adjust accordingly. 3. **Settings merge**: On load, saved settings are deep-merged with defaults. `config.js` is the single source of truth for default settings — `state.js` imports from it. New settings fields should be added to `config.js`. 4. **Chat metadata**: Per-chat tracker data lives in `chat_metadata.rpg_companion`. Per-swipe data lives on individual chat message objects in the `swipe_store`. 5. **i18n fetch path**: Translations are fetched at `/scripts/extensions/third-party/rpg-companion-sillytavern/src/i18n/{lang}.json`. The hardcoded path in `i18n.js` must match the extension's actual URL path. 6. **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. 7. **Mobile vs desktop**: Viewport breakpoint is 1000px. Below: FAB button + mobile tabs. Above: fixed panel + desktop tabs. 8. **External API key**: Stored in `localStorage` as `rpg_companion_external_api_key`, NOT in extension settings (for security). 9. **Dev dependencies**: `package.json` has devDependencies (Jest, Babel, chokidar, fs-extra, glob, execa) for testing and i18n validation. No runtime dependencies — the extension runs entirely in the browser. 10. **CSS rebuild required**: After modifying any file in `src/styles/`, run `npm run build:css` to regenerate `style.css`. SillyTavern loads `style.css`, not the individual modules. 11. **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. 12. **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. |