fix: dedupe aiui:prefill-composer listener across chat re-navigations (fixes #209)

Register the Code Studio prefill listener as a module-scope singleton via
ensurePrefillListener() and remove it in cleanup(), preventing duplicate
window handlers from stacking on repeated SPA navigation between /chat and
/code-studio.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
MervinPraisonandClaude authored and praisonai-triage-agent[bot] committed 2026-07-30 06:32:06 +00:00
1 parent 90315326f7
commit c4be709f8d
2 files changed
+58 -10

No files matched your search

@@ -22,6 +22,7 @@ let agents = [];
let messageQueue = [];
let focusMode = false;
let sessionSelectHandler = null;
let prefillHandler = null;
let containerRef = null;
let currentDeltaEl = null;
let currentDeltaBodyEl = null;
@@ -739,6 +740,26 @@ function setupResizeHandles(wrapper) {
});
}
// ── Prefill Composer Listener (singleton) ───────────────────────
// Code Studio's "Send to Agent" dispatches a window-level
// 'aiui:prefill-composer' event. This SPA calls render() on every
// visit to /chat, so the listener is installed once at module scope
// and guarded to avoid stacking duplicate handlers across navigations.
function ensurePrefillListener() {
if (prefillHandler) return;
prefillHandler = (e) => {
const text = e.detail && e.detail.text;
if (!text) return;
const el = document.getElementById('chat-input');
if (!el) return;
el.value = text;
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
el.focus();
};
window.addEventListener('aiui:prefill-composer', prefillHandler);
}
// ── Main render (ES module entry point) ─────────────────────────
export async function render(container) {
injectStyles();
@@ -881,16 +902,7 @@ export async function render(container) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
window.addEventListener('aiui:prefill-composer', (e) => {
const text = e.detail && e.detail.text;
if (!text) return;
const el = document.getElementById('chat-input');
if (!el) return;
el.value = text;
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
el.focus();
});
ensurePrefillListener();
input.addEventListener('input', () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 150) + 'px';
@@ -967,6 +979,10 @@ export function cleanup() {
window.removeEventListener('aiui:session-select', sessionSelectHandler);
sessionSelectHandler = null;
}
if (prefillHandler) {
window.removeEventListener('aiui:prefill-composer', prefillHandler);
prefillHandler = null;
}
currentSessionId = null;
currentAgentName = null;
isStreaming = false;
+32
View File
@@ -0,0 +1,32 @@
"""Tests for the chat prefill-composer listener lifecycle (issue #209).
The 'aiui:prefill-composer' listener must be registered as a module-scope
singleton and torn down in cleanup(), so repeated SPA navigations between
/code-studio and /chat do not stack duplicate handlers on window.
"""
from pathlib import Path
VIEWS = Path("src/praisonaiui/templates/frontend/plugins/views")
class TestChatPrefillListenerLifecycle:
def test_module_scope_handler_declared(self):
src = (VIEWS / "chat.js").read_text()
assert "let prefillHandler = null;" in src
def test_ensure_prefill_listener_guard(self):
src = (VIEWS / "chat.js").read_text()
assert "function ensurePrefillListener()" in src
assert "if (prefillHandler) return;" in src
def test_render_uses_singleton_not_inline_addlistener(self):
src = (VIEWS / "chat.js").read_text()
assert "ensurePrefillListener();" in src
# The listener is added exactly once, inside the guard helper.
assert src.count("window.addEventListener('aiui:prefill-composer'") == 1
def test_cleanup_removes_listener(self):
src = (VIEWS / "chat.js").read_text()
assert "window.removeEventListener('aiui:prefill-composer', prefillHandler)" in src
assert src.count("prefillHandler = null;") >= 2