[eric] windows 1.1.63: drop the textarea + file-input squirrel-era ablations, restore contentEditable @-mention UI and the attach

paperclip on windows, plus a cubic-bezier cursor transition
This commit is contained in:
Eric
2026-05-26 02:42:56 -07:00
parent da61377d8c
commit 529d350cae
5 changed files with 38 additions and 77 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openswarm",
"version": "1.1.62",
"version": "1.1.63",
"description": "OpenSwarm — AI Agent Orchestrator",
"author": "openswarm-ai",
"main": "main.js",
@@ -259,7 +259,14 @@ const AgenticCursor = forwardRef<AgenticCursorHandle>((_props, ref) => {
zIndex: 10500,
pointerEvents: 'none',
transformOrigin: 'top left',
...(IS_WIN ? { transform: `translate(${storePos.x}px, ${storePos.y}px)` } : null),
...(IS_WIN
? {
transform: `translate(${storePos.x}px, ${storePos.y}px)`,
// Closest CSS approximation of the Mac spring (stiffness 260, damping 26): a softly easing ~420ms cubic-bezier. Without this the cursor teleports because the shim strips Framer's spring runtime.
transition: 'transform 420ms cubic-bezier(0.22, 1, 0.36, 1)',
willChange: 'transform',
}
: null),
}}
>
{visible && (
@@ -71,7 +71,7 @@ export function useEditorHandlers(p: Params) {
const updateHasContent = useCallback(() => {
const editor = editorRef.current;
if (!editor) return;
const text = (editor.textContent || '').replace(/\u200B/g, '');
const text = readEditorText(editor).replace(/\u200B/g, '');
const hasPills = editor.querySelector(`[${SKILL_PILL_ATTR}]`) !== null;
setHasContent(text.trim().length > 0 || hasPills);
}, []);
@@ -104,7 +104,7 @@ export function useEditorHandlers(p: Params) {
const { [skillId]: _, ...rest } = prev;
return rest;
});
const text = (editor.textContent || '').replace(/\u200B/g, '');
const text = readEditorText(editor).replace(/\u200B/g, '');
const hasPills = editor.querySelector(`[${SKILL_PILL_ATTR}]`) !== null;
setHasContent(text.trim().length > 0 || hasPills);
editor.focus();
@@ -2,9 +2,6 @@ import React, { RefObject } from 'react';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
// Windows ablation re-instated in v1.1.58: the file input AND the contentEditable were BOTH crashers. v1.1.57's restore of <input type="file"> brought back the AgentChat-children commit segfault (crash fires right after AgentChat:before-jsx without any child render logs). Drag-and-drop attach still works via AttachmentChips drop zone on Windows. Future: replace this button with an Electron native showOpenDialog IPC so the button is functional on Windows again without touching <input type="file">.
const IS_WIN = typeof navigator !== 'undefined' && navigator.userAgent.includes('Windows');
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import StopIcon from '@mui/icons-material/Stop';
@@ -85,39 +82,34 @@ export const ToolbarActions: React.FC<Props> = ({
);
})()}
{/* Windows-only ablation: hidden <input type="file"> + attach button skipped on Windows to test whether IFileDialog COM init is the trigger for the Chromium 144 commit-phase segfault. Mac still renders both (drag/paste/click attach all work). On Windows, drag-and-drop attach still works via AttachmentChips drop zone. */}
{!IS_WIN && (
<input
ref={generalFileInputRef}
type="file"
multiple
hidden
onChange={(e) => {
if (!e.target.files) return;
const all = Array.from(e.target.files);
const imgs = all.filter((f) => f.type.startsWith('image/'));
const rest = all.filter((f) => !f.type.startsWith('image/'));
if (imgs.length > 0) addImageFiles(imgs);
if (rest.length > 0) uploadAndAttachFiles(rest);
e.target.value = '';
<input
ref={generalFileInputRef}
type="file"
multiple
hidden
onChange={(e) => {
if (!e.target.files) return;
const all = Array.from(e.target.files);
const imgs = all.filter((f) => f.type.startsWith('image/'));
const rest = all.filter((f) => !f.type.startsWith('image/'));
if (imgs.length > 0) addImageFiles(imgs);
if (rest.length > 0) uploadAndAttachFiles(rest);
e.target.value = '';
}}
/>
<Tooltip title="Attach file">
<IconButton
size="small"
onClick={() => generalFileInputRef.current?.click()}
sx={{
color: c.text.tertiary,
p: 0.5,
'&:hover': { color: c.text.secondary, bgcolor: 'rgba(0,0,0,0.04)' },
}}
/>
)}
{!IS_WIN && (
<Tooltip title="Attach file">
<IconButton
size="small"
onClick={() => generalFileInputRef.current?.click()}
sx={{
color: c.text.tertiary,
p: 0.5,
'&:hover': { color: c.text.secondary, bgcolor: 'rgba(0,0,0,0.04)' },
}}
>
<AttachFileIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
)}
>
<AttachFileIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
{!autoRunMode && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{hasContent && (
@@ -18,9 +18,6 @@ interface Props {
onPaste: (e: React.ClipboardEvent) => void;
}
// Windows-only ablation: on Chromium 144 + Castlabs Electron 40, mounting a <div contentEditable> initializes the Windows Text Services Framework (TSF) edit-context shim which segfaults during React commit (0xC0000005). Plain <textarea> goes through a different native path and doesn't engage the same TSF shim. Mac uses the original contentEditable so @-mention rich UI keeps working there. ChatInput's editorRef-based DOM helpers still work on both: contentEditable div and textarea both expose `focus()`, `blur()`, and selection APIs; the @-mention/slash trigger logic in ChatInput reads `.textContent` / `.innerText` for div and falls through to `.value` for textarea via duck-typed access.
const IS_WIN = typeof navigator !== 'undefined' && navigator.userAgent.includes('Windows');
export const EditorSurface: React.FC<Props> = ({
c, editorRef, disabled, hasContent, hasAttachments, autoRunMode, isRunning, queueLength,
placeholderLabel, onInput, onClick, onKeyDown, onPaste,
@@ -33,41 +30,6 @@ export const EditorSurface: React.FC<Props> = ({
? (queueLength > 0 ? `${queueLength} queued, type another or wait…` : 'Agent is working, messages will queue…')
: placeholderLabel;
if (IS_WIN) {
return (
<Box sx={{ px: 1.5, pt: hasAttachments ? 0.5 : 1.25, pb: 0.25, position: 'relative' }}>
<textarea
ref={editorRef as unknown as React.RefObject<HTMLTextAreaElement>}
data-onboarding="chat-input"
disabled={!!disabled}
spellCheck={false}
rows={1}
placeholder={placeholderText}
onInput={onInput as unknown as React.FormEventHandler<HTMLTextAreaElement>}
onClick={onClick as unknown as React.MouseEventHandler<HTMLTextAreaElement>}
onKeyDown={onKeyDown as unknown as React.KeyboardEventHandler<HTMLTextAreaElement>}
onPaste={onPaste as unknown as React.ClipboardEventHandler<HTMLTextAreaElement>}
style={{
width: '100%',
minHeight: '1.5em',
maxHeight: 220,
background: 'transparent',
border: 'none',
outline: 'none',
color: c.text.primary,
fontSize: '0.95rem',
lineHeight: '1.55',
fontFamily: 'inherit',
wordBreak: 'break-word',
whiteSpace: 'pre-wrap',
resize: 'none',
padding: 0,
}}
/>
</Box>
);
}
return (
<Box sx={{ px: 1.5, pt: hasAttachments ? 0.5 : 1.25, pb: 0.25, position: 'relative' }}>
<div