refactor: server-side frame wrapping and helper.js consolidation

Move toggleSelect/send/selectedChoice from frame-template.html inline
script to helper.js so they're auto-injected. Server now detects bare
HTML fragments (no DOCTYPE/html tag) and wraps them in the frame
template automatically. Full documents pass through as before.

Fix dark mode in sendToClaude confirmation (was using hardcoded colors).
Fix test env var bug (BRAINSTORM_SCREEN -> BRAINSTORM_DIR).
Add tests for fragment wrapping, full doc passthrough, and helper.js.
This commit is contained in:
Jesse Vincent
2026-02-06 17:59:43 -08:00
parent 7398af9947
commit 5b00c6eb50
4 changed files with 137 additions and 69 deletions

View File

@@ -7,7 +7,6 @@
ws = new WebSocket(WS_URL);
ws.onopen = () => {
// Send any queued events
eventQueue.forEach(e => ws.send(JSON.stringify(e)));
eventQueue = [];
};
@@ -20,12 +19,11 @@
};
ws.onclose = () => {
// Reconnect after 1 second
setTimeout(connect, 1000);
};
}
function send(event) {
function sendEvent(event) {
event.timestamp = Date.now();
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(event));
@@ -42,9 +40,12 @@
// Don't capture regular link navigation
if (target.tagName === 'A' && !target.dataset.choice) return;
// Don't capture the Send feedback button (handled by send())
if (target.id === 'send-feedback') return;
e.preventDefault();
send({
sendEvent({
type: 'click',
text: target.textContent.trim(),
choice: target.dataset.choice || null,
@@ -61,7 +62,7 @@
const data = {};
formData.forEach((value, key) => { data[key] = value; });
send({
sendEvent({
type: 'submit',
formId: form.id || null,
formName: form.name || null,
@@ -77,37 +78,60 @@
clearTimeout(inputTimeout);
inputTimeout = setTimeout(() => {
send({
sendEvent({
type: 'input',
name: target.name || null,
id: target.id || null,
value: target.value,
inputType: target.type || target.tagName.toLowerCase()
});
}, 500); // 500ms debounce
}, 500);
});
// Send to Claude - triggers server to exit and return all events
// Send to Claude - triggers feedback delivery
function sendToClaude(feedback) {
send({
sendEvent({
type: 'send-to-claude',
feedback: feedback || null
});
// Show confirmation to user
// Show themed confirmation page
document.body.innerHTML = `
<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, sans-serif;">
<div style="text-align: center; color: #666;">
<h2 style="color: #333;">Sent to Claude</h2>
<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary, #f5f5f7);">
<div style="text-align: center; color: var(--text-secondary, #86868b);">
<h2 style="color: var(--text-primary, #1d1d1f); margin-bottom: 0.5rem;">Sent to Claude</h2>
<p>Return to the terminal to see Claude's response.</p>
</div>
</div>
`;
}
// Expose for explicit use if needed
// Frame UI: selection tracking and feedback send
window.selectedChoice = null;
window.toggleSelect = function(el) {
const container = el.closest('.options') || el.closest('.cards');
if (container) {
container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected'));
}
el.classList.add('selected');
window.selectedChoice = el.dataset.choice;
};
window.send = function() {
const feedbackEl = document.getElementById('feedback');
const feedback = feedbackEl ? feedbackEl.value.trim() : '';
const payload = {};
if (window.selectedChoice) payload.choice = window.selectedChoice;
if (feedback) payload.feedback = feedback;
if (Object.keys(payload).length === 0) return;
sendToClaude(payload);
if (feedbackEl) feedbackEl.value = '';
};
// Expose API for explicit use
window.brainstorm = {
send: send,
choice: (value, metadata = {}) => send({ type: 'choice', value, ...metadata }),
send: sendEvent,
choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }),
sendToClaude: sendToClaude
};