Implements intelligent auto-layout system that efficiently arranges widgets to maximize space usage while respecting panel width constraints.
**Key Features:**
- Smart packing algorithm that sorts by widget area and finds optimal positions
- Respects responsive column count (2-4 columns based on panel width)
- Prefers full-width widgets when possible to eliminate gaps
- Fallback to narrower widths for better vertical packing
- Maintains minimum widget sizes
**Implementation:**
- GridEngine.autoLayout() - Core packing algorithm with collision detection
- DashboardManager.autoLayoutWidgets() - High-level API that re-renders after layout
- Auto-Arrange button in dashboard header (uses fa-table-cells-large icon)
- Event handler wired to call autoLayoutWidgets with preferFullWidth=true
**Algorithm Strategy:**
1. Sort widgets by area (largest first) for efficient packing
2. For each widget, try full-width placement first
3. Find first available position using row-by-row scan
4. If position is too far down, try narrower widths
5. Mark cells as occupied to prevent overlaps
**Testing Notes:**
- Works with current responsive column system (2-4 columns)
- Respects minimum sizes and column constraints
- Re-renders all widgets after repositioning
- Auto-saves layout changes
Part of Epic 2: Dashboard Widget Library
- Add dashboard initialization in initUI() after template load
- Inject all required dependencies for widgets:
- Data accessors (getContext, getExtensionSettings, getUserAvatar, etc.)
- Data setters (setCharacterThoughts)
- Event callbacks (onDataChange, onStatsChange, onDashboardChange)
- Create default layout on first load if no dashboard config exists
- Fallback to legacy rendering (renderUserStats, etc.) on error
- Comprehensive error handling with console logging
- Auto-save on all data changes
Reverted HTML replacement approach and restored the cleaner CSS-based
animation from commit 1855085.
Previous (wrong) approach:
- Replaced button HTML with spinner
- Modified both desktop and mobile buttons in apiClient.js
- Messy and inconsistent
Restored (correct) approach:
- Add/remove .spinning CSS class in click handler
- CSS animates only the icon inside the button
- Button itself stays unchanged
- Much cleaner implementation
Changes:
- Reverted apiClient.js changes from commit 9a49433
- Added .spinning CSS class and @keyframes rpg-spin
- Updated index.js click handler to bind both buttons
- Uses addClass/removeClass for clean animation control
- Includes drag detection to prevent accidental clicks
Now the mobile FAB icon spins smoothly when refreshing!
PROBLEM:
- Existing users with saved off-screen FAB positions can't see buttons
- No way to reset positions without clearing all extension settings
- Salixfire and other users on Xiaomi/other devices need safe positions
SOLUTION:
Added "Reset Button Positions" button in Advanced settings section
IMPLEMENTATION:
1. template.html (lines 241-249):
- Added reset button in Advanced section after Clear Cache button
- Blue-styled button with rotate icon
- Help text explains it resets FAB positions to top-left
2. index.js (lines 361-390):
- Added click handler for reset button
- Resets all 3 FAB positions to safe top-left defaults:
* Mobile toggle: top + 20px, left: 12px
* Refresh: top + 80px, left: 12px
* Debug: top + 140px, left: 12px
- Saves settings immediately
- Applies CSS positions to visible buttons (no page refresh needed)
- Shows success toast notification
3. style.css (lines 2057-2083, 4123-4125):
- Added .rpg-btn-reset-fab styles matching clear cache pattern
- Blue color scheme (vs red for destructive clear cache)
- Same sizing, padding, transitions as other buttons
- Mobile responsive font-size with clamp()
USAGE:
Users experiencing off-screen buttons can now:
1. Open RPG Companion settings (gear icon)
2. Scroll to Advanced section
3. Click "Reset Button Positions"
4. All FAB buttons instantly move to safe top-left positions
This fixes the issue for Salixfire and any other users who:
- Have buttons saved in off-screen positions
- Can't scroll to find buttons
- Need to reset without clearing all settings
Works immediately without page refresh or extension reload.
PROBLEM (reported by Salixfire):
- Present Characters panel showing placeholder instead of actual characters
- Thought bubbles work correctly but main panel doesn't
- Need to toggle settings off/on to get thoughts to appear
- No way to debug on mobile devices
CHANGES:
1. Added comprehensive debug logging to renderThoughts() (src/systems/rendering/thoughts.js):
- Log when function is called and with what data
- Log each line being parsed and how many parts it has
- Log character extraction (emoji, name, traits, relationship, thoughts)
- Log why characters are accepted or rejected
- Log final character count and whether showing placeholder
- All logs visible in mobile-friendly debug panel
2. Fixed toggle to refresh content (index.js:283-291):
- When user toggles "Show Present Characters" on, now calls renderThoughts()
- Previously only showed/hid container without refreshing content
- This ensures panel displays latest data when toggled
3. Normalized parsing logic (src/systems/rendering/thoughts.js:111):
- Changed renderThoughts() to require >= 3 parts (was >= 2)
- Now matches updateChatThoughts() requirement
- Consistent with current prompt format: Emoji:Name | Relationship | Thoughts
- Removed 2-part format fallback code (unreachable now)
- Both functions now use same validation rules
EXPECTED OUTCOME:
- User can enable debug mode and see exactly what data is being parsed
- Toggle will properly refresh the panel content
- We can diagnose from debug logs why placeholder is shown
- More consistent behavior between main panel and thought bubbles
Debug logs will help us identify:
- If characterThoughts data is empty/malformed when renderThoughts() is called
- If parsing is rejecting valid character data
- If there's a timing issue with data availability
- What the actual AI response format looks like
Related to previous commit (37878fc) that added debug mode toggle.
PROBLEM:
- Debug logs only accessible via browser console (impractical on mobile)
- User (Salixfire) reporting parsing issues but can't debug on mobile device
- Need mobile-friendly debug mode for troubleshooting data display issues
SOLUTION:
Implemented debug toggle FAB button following exact pattern of existing mobile FABs:
Files Changed:
- src/core/state.js: Added debugFabPosition and debugMode to extensionSettings
- src/core/config.js: Added debugFabPosition to defaultSettings (reference)
- index.js: Created debug toggle button, imported setupDebugButtonDrag
- style.css: Added debug toggle CSS matching mobile FAB pattern (44px, grab cursor, theme colors)
- src/systems/ui/mobile.js: Added setupDebugButtonDrag() with drag-to-reposition
- src/systems/ui/debug.js: Removed button creation, added just-dragged check, updated visibility control
Implementation Details:
- Button created in index.js (not debug.js) following mobile FAB pattern
- CSS matches mobile toggle/refresh buttons (44px, theme colors, grab cursor, user-select: none)
- Drag support with touch/mouse handlers, 200ms/10px threshold
- Position saved to extensionSettings.debugFabPosition
- Just-dragged flag prevents accidental clicks after drag
- Mobile (≤1000px): slide from right with rpg-mobile-open/closing classes
- Desktop (>1000px): slide from bottom with rpg-debug-open class
- Event delegation for reliable click handling
- Default position: bottom 140px, left 20px (below other FABs)
Bug Fix:
- Initial implementation had debugFabPosition only in config.js
- extensionSettings uses state.js as source, not config.js
- Without debugFabPosition in state.js, button had no position and was invisible
- Now properly initialized in both files
The debug button is hidden by default (debugMode: false) and shown when user enables debug mode in RPG Companion settings. This allows Salixfire to view parser logs on mobile and troubleshoot the data display issues.
The debug toggle was incorrectly added to settings.html (SillyTavern Extensions tab).
It should be in template.html (RPG Companion Settings popup) where all the
other extension settings are.
Changes:
- template.html: Added debug mode checkbox in Display Options section
- index.js: Added event listener and initial state setter
- settings.html: Removed incorrect debug toggle placement
Now users can find the debug toggle by clicking the gear icon in the RPG panel,
under Display Options, right below "Show Plot Progression Buttons".
Add comprehensive debug logging system that's accessible on mobile devices
where browser console is impractical.
**New Features:**
- Debug mode toggle in extension settings (🔍 Debug Mode)
- Mobile-friendly debug panel with slide-up UI
- Red bug FAB button to toggle debug log viewer
- Copy logs to clipboard functionality
- Auto-scrolling log display with timestamps
- Stores last 100 log entries to prevent memory issues
**Parser Enhancements:**
- All parser logs now use debugLog() helper function
- Logs only appear in UI when debug mode is enabled
- Console.log still works for desktop debugging
- Full visibility into parsing pipeline:
- Raw AI response preview
- Code blocks found and matched
- Stats extraction (health, energy, mood, etc.)
- Inventory parsing (v1 and v2)
- Final values saved to settings
**UI Components:**
- src/systems/ui/debug.js: Debug panel creation and management
- style.css: Mobile-first debug panel styles (FAB + slide-up panel)
- Desktop view: Smaller panel in bottom-right corner
**Settings:**
- src/core/config.js: Added debugMode default (false)
- src/core/state.js: Added debug logs storage array
- settings.html: Added debug mode checkbox
- index.js: Wire up debug toggle and initialize UI
**Usage for Mobile Users:**
1. Enable "Debug Mode" in RPG Companion settings
2. Red bug button appears (bottom-left)
3. Tap bug button to view logs
4. Use "Copy" to share logs for troubleshooting
5. Logs show exactly what AI generated and how parser handled it
This addresses the issue where users on mobile can't access browser
console to diagnose parsing problems (vanishing attributes, placeholder
characters, etc.). Now they can view and share logs directly.
- Moved refresh button creation from template.html to index.js (appended to body)
- Created new CSS class .rpg-mobile-refresh (exact copy of .rpg-mobile-toggle pattern)
- Uses opacity for show/hide instead of display (CSS controls visibility based on panel state)
- Show when panel open (body:has(.rpg-panel.rpg-mobile-open))
- Hide when panel closed (opacity: 0, pointer-events: none)
- Updated constrainFabToViewport() to accept optional button parameter
- Automatically detects which button and uses correct settings (mobileFabPosition or mobileRefreshPosition)
- Simplified updateGenerationModeUI() - CSS handles visibility
- Kept full drag functionality with touch and mouse support
- Button positioned via JavaScript with saved position
- z-index: 1001 (above panel, below toggle at 10002)
- Repositioned mobile refresh button to bottom-right (80px from bottom)
- Implemented full drag-to-reposition functionality
* Touch and mouse support with 200ms/10px threshold
* RequestAnimationFrame for smooth dragging
* Position saved to extensionSettings.mobileRefreshPosition
* Viewport constraints with 10px padding
- Fixed sticky tap highlight issue
* Added -webkit-tap-highlight-color: transparent
* Added blur() on click to remove focus
* Set user-select: none and touch-action: none
- Show/hide based on panel state
* Only visible when panel is expanded (rpg-mobile-open)
* Listens to rpg-panel-toggled events
* Auto-hides when panel closes
- Prevent accidental refresh after drag
* just-dragged flag prevents click for 100ms
* Click handler checks flag before executing
- Changed from absolute to fixed positioning for viewport-wide dragging
- Added mobileRefreshPosition to default settings (bottom: 80px, right: 20px)
- z-index: 99 (below FAB toggle at 100)
**Changes:**
1. Move button to float over all tabs (not just Stats)
- Removed from userStats.js HTML
- Added to template.html as floating absolute element
- Now visible on Status, Info, and Inventory tabs
2. Fix sticky black focus state
- Added :focus { outline: none } to CSS
- Call blur() after click to clear focus immediately
3. Add refresh animation
- Button spins during updateRPGData() call
- Smooth 0.8s rotation with @keyframes
- Uses .spinning class added/removed in JS
4. Improve theming and positioning
- Positioned absolute top-right (10px, 10px)
- Increased to 44px for better touch target
- z-index: 100 to float above content
- Already uses theme colors (--rpg-highlight, --rpg-text)
Mobile UX now:
✅ Button visible on all tabs (floating)
✅ Spins smoothly when refreshing
✅ No sticky black state after tap
✅ Properly themed across all themes
- Created getLocationId() helper function to normalize location names to IDs
- Function removes special characters (apostrophes, etc.) before converting to ID
- Both rendering and action handlers now use same ID generation logic
- Fixes issue where locations with apostrophes couldn't be deleted
- Example: "Dottore's Study" now properly generates ID "Dottores-Study"
- Commented out debug logging
- openai_setting_names from /api/settings/get is an array, not an object
- Use .includes() instead of 'in' operator to check for preset
- Prevents duplicate preset imports on every page refresh
- Preset is now only imported once on first extension load
- Check if preset exists using /api/settings/get instead of HEAD request
- Look for preset in openai_setting_names to determine if it's already imported
- Comment out 'already exists' log to reduce console noise
- Only import preset if it's truly missing from the settings
- Use correct /api/presets/save endpoint instead of /api/presets/save-openai
- Add getRequestHeaders() import and use it in the fetch call
- Include apiId: 'openai' in the request body
- Fixes 'Forbidden' error when importing preset
- Fixed extension settings not appearing in Extensions tab by appending to correct container (#extensions_settings2)
- Added Discord and Support Creator buttons directly in JavaScript
- Added persistence for committedTrackerData to maintain state across refreshes and restarts
- Updated saveChatData() to include committedTrackerData in chat metadata
- Updated loadChatData() to restore committedTrackerData from saved chat data
Added comprehensive error handling to prevent extension initialization failures:
- Added settings validation in loadSettings() to detect corrupt data
- Improved error recovery in main initialization with granular try-catch blocks
- Enhanced HTML regex import with structure validation and detailed error logging
- Added detection for conflicting old manual formatting regex scripts
- Added user-friendly toastr notifications for initialization errors and conflicts
- Each init step now has independent error handling to prevent cascade failures
This fixes issues where invalid extension_settings could prevent the extension
from loading entirely. The extension will now gracefully handle corrupt data,
warn about conflicts, and fall back to defaults when necessary.
Related to user report where extension wouldn't load with certain settings.json
configurations containing old manual formatting regexes or malformed data.
Desktop (2 tabs):
- Status tab: User Stats + Info Box + Character Thoughts
- Inventory tab: Inventory system (dedicated space)
Mobile (3 tabs):
- Stats tab: User Stats only
- Info tab: Info Box + Character Thoughts
- Inventory tab: Inventory only
Features:
- Created desktop.js module for desktop tab management
- Updated mobile.js to use 3-tab structure (more breathing room on small screens)
- Added CSS styling for desktop tabs (hover states, active indicators)
- Implemented viewport transition handlers (desktop ↔ mobile)
- Tabs replace dividers (cleaner visual separation)
- Character thoughts can now expand to fill vertical space
This resolves the cramped 4-section panel issue by organizing content into logical tabs on both desktop and mobile.
- Add inventory section to template.html between Thoughts and bottom controls
- Wire up renderInventory() to all event handlers (message received, character changed, swipes)
- Initialize inventory container reference and event listeners in index.js
- Add showInventory toggle checkbox to settings with visibility control
- Update layout.js to handle inventory section and divider visibility
- Add renderInventory parameter to updateRPGData for separate mode support
- Update state.js and config.js with inventory container and showInventory setting
Inventory is now fully integrated as a visible, interactive panel section that persists across all user interactions.
- Create src/systems/integration/sillytavern.js with all event handlers
- Move commitTrackerData() (deferred from Epic 1)
- Move sendPlotProgression() to plotProgression.js
- Move updateGenerationModeUI() to layout.js
- Add registerAllEvents() and unregisterAllEvents() to events.js
- Centralize event registration in index.js initialization
This completes Epic 6: Integration Layer Extraction
~340 lines extracted from index.js
index.js reduced from ~783 lines to 423 lines
Extract ensureHtmlCleaningRegex into src/systems/features/htmlCleaning.js.
This module automatically imports the HTML cleaning regex script that
strips HTML tags from outgoing prompts to prevent formatting issues.
Passes SillyTavern imports (st_extension_settings, saveSettingsDebounced)
as parameters to avoid deep module import path issues.
- Create htmlCleaning.js with regex import logic
- Update index.js to import and use the new module with parameters
- Maintain backward compatibility with existing functionality
Extract setupClassicStatsButtons into src/systems/features/classicStats.js.
This module handles the delegated event listeners for classic RPG stat
+/- buttons (STR, DEX, CON, INT, WIS, CHA).
- Create classicStats.js with event delegation for stat buttons
- Update index.js to import and use the new module
- Maintain backward compatibility with existing functionality
Extract plot progression button setup from index.js into dedicated
feature module at src/systems/features/plotProgression.js.
Due to ES6 module import path limitations in deeply nested modules,
the generation logic (sendPlotProgression) remains in index.js where
it can properly import from SillyTavern's script.js. The UI setup
function accepts the generation function as a callback parameter.
Creates plotProgression.js with 62 lines of UI setup code.
Index.js increases from 800 to 869 lines (+69 for generation logic).
Extracted ~920 lines of UI management code from index.js into 4 specialized modules to improve maintainability and organization.
Modules Created:
- src/systems/ui/theme.js (100 lines) - Theme management and custom colors
- src/systems/ui/modals.js (568 lines) - DiceModal and SettingsModal ES6 classes
- src/systems/ui/layout.js (254 lines) - Panel visibility, positioning, and collapse toggle
- src/systems/ui/mobile.js (694 lines) - Mobile FAB, tabs, keyboard handling, and viewport management
Changes:
- Extracted theme application and custom color management
- Extracted modal classes with proper state management
- Extracted layout management (visibility, sections, positioning)
- Extracted mobile-specific UI (FAB dragging with touch/mouse, tab navigation, keyboard handling)
- Removed unused import (closeMobilePanelWithAnimation only used internally by mobile.js)
- Updated imports in index.js to use new module structure
- Added comprehensive documentation comments
Result:
- index.js reduced from 1606 to 921 lines (-685 lines)
- All UI systems properly modularized with clean dependencies
- Maintains 100% backward compatibility
- All modules pass syntax validation
Dependencies:
- All modules import from src/core/state.js for shared state
- Mobile module imports layout functions for panel animation
- Layout module properly manages DOM element state
After modular refactor, `pendingDiceRoll` is imported as const from
state module and cannot be reassigned. Replace three direct assignments
with `setPendingDiceRoll()` setter function calls:
- DiceModal.close() method (line 572)
- Save roll button handler (line 691)
- rollDice() function (line 773)
Fixes "Assignment to constant variable" errors preventing dice rolls.
Extract rendering logic from index.js into modular system:
- src/utils/avatars.js: Safe thumbnail URL generation with error handling
- src/systems/rendering/userStats.js: User stats panel with progress bars and classic RPG stats
- src/systems/rendering/infoBox.js: Info box dashboard with weather, date, time, and location widgets
- src/systems/rendering/thoughts.js: Character thoughts panel and floating chat bubbles
Reduces index.js from 3,829 to 2,430 lines (-1,399 lines, -36.5%)
All rendering functions now properly modularized with full JSDoc documentation
Event listeners preserved in render functions for interactive fields
Extract AI generation and parsing logic from monolithic index.js into
modular architecture under src/systems/generation/.
**Modules Created:**
- promptBuilder.js (319 lines) - AI prompt generation functions
- parser.js (152 lines) - Response parsing and stats extraction
- apiClient.js (154 lines) - Separate mode API call handler
- injector.js (216 lines) - Prompt injection for both modes
**Changes:**
- All functions preserve exact behavior from original
- Import paths calculated for browser module resolution
- Zero functionality changes, pure code organization
Reduces index.js by ~700 lines when combined with function removal
(to be committed separately).
Extract core system modules from monolithic index.js into modular architecture:
- src/core/state.js: All extension state variables with controlled setters
- src/core/persistence.js: Settings and chat data persistence functions
- src/core/config.js: Extension metadata and default configuration
- src/core/events.js: SillyTavern event system wrapper
Updated index.js to import and use new core modules.
Removed ~220 lines of state/persistence code from index.js.
Part of Epic 1: Foundation & Core Systems (Phase 1.1-1.2)
The previous URL-encoded SVG had unencoded quotes that broke HTML attribute parsing.
The browser would misinterpret xmlns="http://www.w3.org/2000/svg" as separate HTML
attributes, causing broken image rendering.
Changes:
- Add FALLBACK_AVATAR_DATA_URI constant with base64-encoded SVG
- Replace all instances of broken inline transparentPixel variable (3 locations)
- Update comparison check to use the new constant
The base64 encoding ensures the data URI is safely embedded in HTML src attributes
without any quote-escaping issues.
- Add getSafeThumbnailUrl() helper function with comprehensive error handling
- Replace all getThumbnailUrl() calls with safe wrapper that validates results
- Use SVG data URI placeholder instead of 'img/user-default.png' to avoid 400 errors
- Update img onerror handlers to fade opacity instead of trying invalid fallback paths
- Add detailed console logging for debugging avatar loading issues
- Improve updatePersonaAvatar() to only update src when valid URL is available
This fixes persistent 400 errors on some Ubuntu systems where directory names
with spaces (e.g., "User Avatars") caused thumbnail URL construction to fail.
Affected functions:
- getSafeThumbnailUrl() (new)
- updatePersonaAvatar()
- renderUserStats()
- renderCharacterThoughts()
- Fixed persona avatar loading for custom user profile folders with fallback
- Added dynamic persona avatar updates when user switches personas
- Fixed together mode bug where committedTrackerData wasn't persisting after first generation
- Together mode now mirrors separate mode logic for swipes vs new messages
- Converted all CSS font-sizes from rem to vw for proper responsive scaling
- Fixed weather widget scaling issues at high zoom levels
- Added image generation detection to skip tracker injection for quietImage requests
- Removed CHARACTER_MESSAGE_RENDERED listener to fix race condition in together mode
Implemented Visual Viewport API integration to detect and handle mobile
keyboard appearance, preventing contenteditable fields from squashing
the panel layout when the keyboard appears.
**JavaScript Changes (index.js:1886-1948, 522-523):**
- Created setupMobileKeyboardHandling() function:
* Uses Visual Viewport API to monitor viewport height changes
* Detects keyboard appearance (viewport < 75% of window height)
* Adds .rpg-keyboard-visible class when keyboard shows
* Removes class when keyboard dismisses
* Gracefully falls back if API not supported
- Created setupContentEditableScrolling() function:
* Uses event delegation for all contenteditable fields
* Automatically scrolls focused field into view with 300ms delay
* Centers field in viewport using scrollIntoView with 'center' block
* Smooth scrolling animation for better UX
- Added both function calls to initUI() for automatic setup
**CSS Changes (style.css:3232-3252):**
- Added .rpg-keyboard-visible state styling:
* 20px bottom padding to prevent content being pushed too far up
* Compact section padding (8px 12px) for stats/info/thoughts
* Reduced stat bar gap (4px) to maintain layout integrity
- Scoped to mobile viewport only (@media max-width: 1000px)
**Benefits:**
- No layout squashing when keyboard appears on mobile
- Stat bars and charts remain intact and visible
- Focused fields automatically scroll into view
- Smooth animations for professional UX
- Works with all contenteditable fields:
* Stats (health, energy, hygiene, sustenance, arousal)
* Inventory and mood/conditions
* Info box widgets (date, weather, time, location, temperature)
* Character traits and relationship badges
* Character thoughts
- Backward compatible (graceful fallback)
- Desktop users completely unaffected
**How It Works:**
1. Visual Viewport API monitors viewport height changes
2. When height drops below 75% threshold, keyboard detected
3. Panel gets .rpg-keyboard-visible class for compact styling
4. Tapped contenteditable field smoothly scrolls to center
5. When keyboard dismisses, layout returns to normal
**Testing Completed:**
- Inventory field editing works without squashing
- Stat values editable with keyboard visible
- Info box widget editing maintains layout
- Character traits and thoughts remain accessible
- No layout breaks or chart distortion
- Smooth scrolling and transitions verified
Completely redesigned the classic stats (STR, DEX, CON, INT, WIS, CHA)
section for mobile with 1/3 size reduction and vertical stack layout.
**CSS Changes (style.css:3510-3587):**
- Changed from vertical flex list to 2x3 CSS grid layout
- Reduced all dimensions by ~66% for ultra-compact mobile view:
* Labels: 9px (was 12px)
* Values: 14px (was 20px)
* Buttons: 24x24px (was 36x36px)
* Padding: 4px 6px (was 8px 10px)
* Gaps: 2-4px (was 6-8px)
- Vertical stack layout within each stat box:
* Row 1: Label (left) | Value (right)
* Row 2: Minus button (left) | Plus button (right)
- Fixed button visibility:
* Added explicit background, border, and color styles
* Added display: flex !important to ensure rendering
* Added hover and active states
- Fixed right-side cutoff:
* Added 5px padding-right to stats grid
* Adjusted margins for proper spacing
- Centered all elements using justify-self: center
- Removed conflicting @media (max-width: 768px) rule that enlarged buttons
**JavaScript Changes (index.js:518, 985-1011):**
- Created setupClassicStatsButtons() function with delegated event listeners
- Moved event handlers from renderUserStats() to setup function
- Used jQuery .on() delegation to persist across re-renders
- Prevents duplicate handlers and ensures buttons always work
- Called in initUI() during extension initialization
**Benefits:**
- Attributes take up 1/3 the vertical space on mobile
- No vertical overflow in mobile Stats tab
- All 6 attributes fit comfortably in 2x3 grid
- +/- buttons visible and properly aligned
- Buttons fully functional with delegation pattern
- Desktop layout completely unaffected (>1000px)
**Layout:**
```
┌────────────┐ ┌────────────┐
│ STR 13 │ │ DEX 16 │
│ - + │ │ - + │
└────────────┘ └────────────┘
┌────────────┐ ┌────────────┐
│ CON 14 │ │ INT 18 │
│ - + │ │ - + │
└────────────┘ └────────────┘
┌────────────┐ ┌────────────┐
│ WIS 13 │ │ CHA 15 │
│ - + │ │ - + │
└────────────┘ └────────────┘
```
Completely refactored the RPG Companion settings popup with professional
ES6 architecture and mobile-first CSS, matching the dice roller redesign.
**CSS Changes (style.css:2585-2773):**
- Mobile-first responsive design with clamp() and min() functions
- CSS custom properties for fluid scaling across viewports
- min-height: 0 on flex children for proper max-height constraints
- ::before pseudo-element for backdrop (removed overlay div)
- State-based animations with .is-open and .is-closing classes
- 75vh max-height with proper viewport centering
- Touch-friendly 44px minimum tap targets
- Neutral 80% opaque background for visibility
**HTML Changes (template.html:71-214):**
- Added ARIA attributes: role="dialog", aria-modal="true", aria-labelledby
- Semantic <header> element for settings header
- aria-hidden="true" on all decorative icons
- Removed .rpg-settings-popup-overlay div (now CSS ::before)
- Improved accessibility throughout
**JavaScript Changes (index.js:985-1142):**
- Created SettingsModal ES6 class with state management
- open() and close() methods with animation control
- updateTheme() for real-time theme switching
- Private _applyCustomTheme() and _clearCustomTheme() methods
- isAnimating flag prevents double-clicks
- Focus management for accessibility
- Backwards compatible wrapper functions preserve existing API
- Updated event handlers with backdrop click support
- Removed obsolete overlay click handler
**Benefits:**
- Settings modal now fully functional on mobile devices
- Proper scrolling with content overflow
- Smooth open/close animations
- Professional class-based architecture
- Complete accessibility support
- Theme support maintained
- No breaking changes to existing code
BREAKING CHANGES: Dice roller now uses modern ES6 class architecture
Features:
- Mobile-first CSS with fluid responsive units (clamp, min, max)
- ES6 DiceModal class with proper state management (IDLE, ROLLING, SHOWING_RESULT)
- Semantic HTML with ARIA attributes for accessibility
- CSS state classes (.is-open, .is-closing, .is-animating)
- Touch-friendly 44px minimum tap targets
- Desktop enhancement with @media (min-width: 1001px)
Fixes:
- Fixed mobile viewport overflow with min-height: 0 on flex children
- Reduced max-height to 70vh for guaranteed mobile fit
- Removed all jQuery .show()/.hide() inline style injections
- Removed !important CSS hacks from mobile media query
- Fixed transparent modal background (80% opaque neutral gray)
- Darkened backdrop overlay (85% opaque black)
Technical:
- Backdrop uses ::before pseudo-element (no wrapper div needed)
- Flattened HTML structure with proper semantic elements
- Backwards compatible wrapper functions preserved
- Grid layout for inputs (stacked mobile, side-by-side desktop)
- Proper CSS specificity hierarchy (no !important needed)
- Removed .rpg-dice-popup-overlay div dependency
Accessibility:
- role="dialog" with aria-modal="true"
- aria-labelledby for dialog title
- aria-live regions for dynamic content
- aria-busy for loading states
- Proper focus management on open/close
Added matching slide-out animation when closing the mobile panel to
match the smooth slide-in animation on opening:
CSS changes (style.css):
- Add .rpg-mobile-closing class with rpgSlideOutToRight animation
- Create @keyframes rpgSlideOutToRight (0.3s ease-in-out)
- Panel slides smoothly off-screen to right before hiding
JavaScript changes (index.js):
- Add closeMobilePanelWithAnimation() helper function
- Use animationend event to wait for animation before hiding
- Replace all instant removeClass() calls with helper function
- Maintains smooth 0.3s animation timing matching slide-in
Implementation details:
- Helper function adds .rpg-mobile-closing class
- Waits for CSS animation to complete via animationend event
- Then removes closing class and overlay
- All close operations now use this helper for consistency
Result: Panel now smoothly slides in AND out with matching 0.3s
animations. No more jarring instant disappearance on close.
Improved thought icon behavior on mobile with smooth 60fps animations
matching the FAB button drag experience:
CSS changes:
- Add transition properties for top/left position changes
- Use 0.2s ease-out timing for smooth, natural movement
- Add will-change: top, left for browser rendering optimization
- Applied in mobile media query (@media max-width: 1000px)
JavaScript changes:
- Wrap position updates in requestAnimationFrame()
- Cancel pending RAF before scheduling new update (debouncing)
- Sync position updates with display refresh rate
- Same pattern as FAB button smooth drag implementation
Technical details:
- RAF throttling prevents layout thrashing
- CSS transitions handle the actual animation
- Combined approach gives 60fps smooth tracking
- Icon follows avatar smoothly during scroll on mobile
Result: Thought icon smoothly tracks avatar position during scroll
instead of jumping around, with buttery smooth 60fps animation.
Improved the mobile floating action button (FAB) drag experience with
smooth, performant animations:
- Use requestAnimationFrame to throttle position updates during drag
- Add will-change CSS property to optimize rendering performance
- Add dragging class to disable transitions during active drag
- Change cursor to grab/grabbing for better visual feedback
- Remove janky direct CSS updates in favor of RAF-based updates
Technical improvements:
- Position updates now synced to display refresh rate (~60fps)
- Prevents layout thrashing from excessive DOM manipulation
- Smooth transition animations when drag ends
Result: Dragging the FAB button now feels fluid and responsive on
mobile devices instead of laggy and jumpy.
Fixed two mobile UI issues:
1. Relationship badge sizing on mobile:
- Badge was stretching vertically due to .rpg-editable mobile styles
- Override padding, min-height, and line-height for badge on mobile
- Keep badge at compact 18px × 18px to prevent covering avatar
2. Thought panel initial state:
- Panel was showing by default instead of the icon
- Fixed initialization: hide panel, show icon
- Users now click the 💭 icon to open the dialogue as intended
Changes ensure proper mobile experience with appropriately sized UI
elements and correct initial visibility states.
Previously, the thought icon button was positioned to the left of the
character avatar on mobile, causing it to appear partially off-screen
due to lack of left padding around avatars.
Changes:
- Add mobile-specific positioning logic in index.js to detect viewport
width <= 1000px and calculate centered horizontal position
- Add CSS transform in mobile media query to shift icon 50px right
and 45px upward from calculated position
- Center thought panel horizontally on mobile when opened
- Add debug logging to verify mobile detection
Result: On mobile, the thought icon now appears above and to the right
of the avatar, fully visible and accessible.
- Add constrainFabToViewport() function with top-bar awareness
- Only constrains when mobileFabPosition exists (user has dragged button)
- Respects SillyTavern's top bar height via CSS variable
- Prevents button from being hidden behind UI elements
- Applies constraint after drag operations and window resize
- Remove verbose debug logging from drag/touch event handlers
This implements a state-driven approach where the constraint only activates
for user-positioned buttons, allowing CSS defaults to work naturally while
protecting custom positions from viewport changes.
- Fix inverted chevron logic: show left arrow when panel open, right arrow when closed
- Move FAB button from right to left side of screen (12px from left edge)
- Adjust vertical position down by 30px for better placement
- Update console logging to reflect correct icon states
Add full mouse and touch support for mobile toggle button with drag-to-reposition
functionality and persistent position saving.
Changes:
- Add mobile FAB button with draggable positioning (both mouse and touch)
- Implement time-based drag detection (200ms or 10px threshold)
- Add position persistence in extension settings
- Fix click handler to work on both mobile and desktop viewports
- Add comprehensive diagnostic logging for event debugging
- Update mobile panel to slide from right (matching desktop UX)
- Implement dual-button pattern (FAB + internal collapse toggle)
- Add viewport-constrained dragging with 10px padding
- Prevent click events from firing after drag completion
Mobile UX:
- FAB visible when panel closed, hidden when open
- Internal collapse toggle visible only when panel open
- Touch and mouse drag support with real-time positioning
- Click/tap toggles panel, drag repositions button
- Position saved across sessions
Technical:
- Add mousedown/mousemove/mouseup handlers for desktop drag
- Add touchstart/touchmove/touchend handlers for mobile drag
- Remove broken viewport check that prevented mobile clicks
- Add 'just-dragged' flag to prevent click after drag
- Update .gitignore to exclude CLAUDE.md and .env files