[eric] windows 1.1.55: (a) textarea-aware editor read/write/clear helpers so Enter-to-send works on Windows where EditorSurface uses textarea instead of contentEditable (handleSend was reading .textContent which is empty on textarea); (b) gate BrowserCard webview behind !Windows so dashboard hydration no longer segfaults from webview tag commit (iframe fallback is now usable for most sites since we also strip X-Frame-Options and CSP frame-ancestors on sub_frame loads via session.webRequest.onHeadersReceived, scoped to http/https types:sub_frame so OAuth popups and deep links keep their security headers); (c) gate onboarding video autoPlay on Windows so panel mount does not trigger the autoplay+animation crash; Mac path untouched everywhere

This commit is contained in:
Eric
2026-05-25 22:29:17 -07:00
parent 518d0574dd
commit b73f508755
6 changed files with 58 additions and 11 deletions
+20
View File
@@ -1103,6 +1103,26 @@ app.whenReady().then(async () => {
return allowed.includes(permission);
});
// Strip X-Frame-Options and CSP frame-ancestors directives on iframe subframe loads so the Windows BrowserCard iframe fallback (used because <webview> tag commit segfaults on Chromium 144 + this Electron 40 CastLabs build) can render sites that normally refuse to be embedded. Scoped to types:['sub_frame'] so OAuth popups, the main app frame, deep-link redirects, and DRM license fetches keep their security headers intact. urls filter limits to http/https so file:// loads of the bundled frontend are untouched.
session.defaultSession.webRequest.onHeadersReceived(
{ urls: ['http://*/*', 'https://*/*'], types: ['sub_frame'] },
(details, callback) => {
const headers = { ...(details.responseHeaders || {}) };
for (const k of Object.keys(headers)) {
const lk = k.toLowerCase();
if (lk === 'x-frame-options') {
delete headers[k];
} else if (lk === 'content-security-policy' || lk === 'content-security-policy-report-only') {
const cleaned = (headers[k] || [])
.map((v) => v.split(';').filter((d) => !/^\s*frame-ancestors\b/i.test(d)).join(';').trim())
.filter(Boolean);
if (cleaned.length) headers[k] = cleaned; else delete headers[k];
}
}
callback({ responseHeaders: headers });
},
);
// Read-only logging for DRM license requests — no modifying interceptors
// so the network stack can set Content-Type and other headers normally.
session.defaultSession.webRequest.onSendHeaders(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openswarm",
"version": "1.1.54",
"version": "1.1.55",
"description": "OpenSwarm — AI Agent Orchestrator",
"author": "openswarm-ai",
"main": "main.js",
@@ -447,7 +447,7 @@ const StepCardBody: React.FC<StepCardProps> = ({
<Box
component="video"
src={step.videoSrc}
autoPlay
autoPlay={typeof navigator === 'undefined' || !navigator.userAgent.includes('Windows')}
muted
loop
playsInline
@@ -569,7 +569,7 @@ const StepCardBody: React.FC<StepCardProps> = ({
<Box
component="video"
src={step.videoSrc}
autoPlay
autoPlay={typeof navigator === 'undefined' || !navigator.userAgent.includes('Windows')}
muted
loop
playsInline
+10 -4
View File
@@ -105,13 +105,17 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
useImperativeHandle(ref, () => ({
getConfig: () => {
const editor = editorRef.current;
const prompt = editor ? serializeEditorContent(editor, attachedSkillsRef.current).trim() : '';
const prompt = editor
? (editor.tagName === 'TEXTAREA'
? (editor as unknown as HTMLTextAreaElement).value.trim()
: serializeEditorContent(editor, attachedSkillsRef.current).trim())
: '';
return { prompt, contextPaths, forcedTools };
},
setContent: (prompt: string, newContextPaths?: ContextPath[], newForcedTools?: ForcedToolGroup[]) => {
const editor = editorRef.current;
if (editor) {
editor.textContent = prompt;
if (editor.tagName === 'TEXTAREA') (editor as unknown as HTMLTextAreaElement).value = prompt; else editor.textContent = prompt;
setHasContent(!!prompt);
}
if (newContextPaths) setContextPaths(newContextPaths);
@@ -124,7 +128,9 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
if (!editor || disabled) return;
if (summarizingPath) return;
if (oversizeQueue.length > 0) return;
const serialized = serializeEditorContent(editor, attachedSkillsRef.current);
const serialized = editor.tagName === 'TEXTAREA'
? (editor as unknown as HTMLTextAreaElement).value
: serializeEditorContent(editor, attachedSkillsRef.current);
let trimmed = serialized.trim();
if (!trimmed) return;
@@ -144,7 +150,7 @@ const ChatInput = forwardRef<ChatInputHandle, Props>(({ onSend, disabled, mode,
const cmd = trimmed.split(/\s+/)[0].toLowerCase();
const handled = await handleSlashCommand(cmd, sessionId);
if (handled) {
editor.innerHTML = '';
if (editor.tagName === 'TEXTAREA') (editor as unknown as HTMLTextAreaElement).value = ''; else editor.innerHTML = '';
deleteDraft(ownerId);
setHasContent(false);
return;
@@ -18,6 +18,26 @@ import { ForcedToolGroup } from '../types';
type Skill = { id: string; name: string; content: string };
// Editor element type abstraction. On Windows we render a <textarea> instead of a <div contentEditable> to avoid the Chromium 144 + Windows TSF native crash on commit; readers/writers/clearers must route to the right API.
function isTextareaEl(el: HTMLElement | null): el is HTMLTextAreaElement {
return !!el && el.tagName === 'TEXTAREA';
}
function readEditorText(el: HTMLElement | null): string {
if (!el) return '';
if (isTextareaEl(el)) return el.value;
return el.textContent || '';
}
function readEditorHTML(el: HTMLElement | null): string {
if (!el) return '';
if (isTextareaEl(el)) return el.value;
return el.innerHTML;
}
function clearEditor(el: HTMLElement | null): void {
if (!el) return;
if (isTextareaEl(el)) el.value = '';
else el.innerHTML = '';
}
interface Params {
editorRef: RefObject<HTMLDivElement>;
generalFileInputRef: RefObject<HTMLInputElement>;
@@ -104,13 +124,13 @@ export function useEditorHandlers(p: Params) {
if (justPastedRef.current) {
justPastedRef.current = false;
setHasContent(true);
scheduleDraftSave(ownerId, () => editorRef.current?.innerHTML ?? '');
scheduleDraftSave(ownerId, () => readEditorHTML(editorRef.current));
return;
}
updateHasContent();
detectTrigger();
syncAttachedSkills();
scheduleDraftSave(ownerId, () => editorRef.current?.innerHTML ?? '');
scheduleDraftSave(ownerId, () => readEditorHTML(editorRef.current));
}, [updateHasContent, detectTrigger, syncAttachedSkills, ownerId]);
const handleEditorClick = useCallback(() => {
@@ -198,7 +218,7 @@ export function useEditorHandlers(p: Params) {
}
const editor = editorRef.current;
if (editor) {
editor.innerHTML = '';
clearEditor(editor);
updateHasContent();
}
return;
@@ -73,7 +73,8 @@ const HANDLE_DEFS: { dir: ResizeDir; sx: Record<string, any> }[] = [
{ dir: 'se', sx: { bottom: -EDGE_THICKNESS / 2, right: -EDGE_THICKNESS / 2, width: CORNER_SIZE, height: CORNER_SIZE } },
];
const isElectron = navigator.userAgent.includes('Electron');
// On Windows, force iframe fallback path: the <webview> tag mount segfaults the renderer during commit on Chromium 144 + this Electron 40 CastLabs build. iframe renders blank for sites with X-Frame-Options but does not crash. Mac keeps webview (full browser).
const isElectron = navigator.userAgent.includes('Electron') && !navigator.userAgent.includes('Windows');
// eslint-disable-next-line no-console
console.log('[diag][BrowserCard] isElectron=', isElectron, 'ua=', navigator.userAgent, 'hasOpenswarm=', !!(window as any).openswarm);