- Grant/Revoke AI button now notifies target user live via ai_unlock event
- ai_unlock handler updated to support both grant and revoke
- Admin panel auto-refreshes user/ban/mute lists after any action
- VioletHistory model: stores plaintext turns (user/assistant) per user_id
- AI worker loads last 20 turns into Ollama prompt for context
- Saves both user message and AI response after each exchange
- /reset command in Violet PM clears conversation memory
- Fix ai_limit_reached: emit ai_response event instead of raw pm_message
- ai_response handler uses correct PM room and supports plaintext text field
- Remove debug print statements
- Remove cloneNode/replaceWith pattern that orphaned the reference
- Re-bind onclick handlers directly on the existing DOM node
- Context menu now works reliably on every right-click
- User-to-user PMs now use a server-derived shared room key (HMAC-SHA256)
instead of each user's personal PBKDF2 key (which differed per user,
making cross-user decryption impossible)
- Server sends room_key in pm_ready, pm_invite, and pm/history responses
- crypto.js: add importKeyBase64() for importing server-provided keys
- chat.js: use sharedKey for encrypt/decrypt in user-to-user PMs
- Violet AI transit encryption still uses personal key (unchanged)
- PM history decryption now handles errors gracefully per-message
- Encodes otherUser in history URL to prevent injection
- Payment endpoint no longer uses @_require_auth (not client-callable)
- Identifies user from webhook payload user_id instead of client JWT
- Removed hardcoded payment secret from chat.js
- Client now shows placeholder message directing to admin
- Webhook secret + user_id must come from payment provider server