diff --git a/apps/framework-editor/app/components/table/EditableCell.test.tsx b/apps/framework-editor/app/components/table/EditableCell.test.tsx index cb527f94c..e04f032b7 100644 --- a/apps/framework-editor/app/components/table/EditableCell.test.tsx +++ b/apps/framework-editor/app/components/table/EditableCell.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { EditableCell } from './EditableCell'; +import { clearEditorSize, saveEditorSize } from './editor-size-storage'; // The ui package ships untranspiled JSX in dist; stub the bits the cell uses. vi.mock('@trycompai/ui', () => ({ @@ -55,7 +56,10 @@ describe('EditableCell — non-expandable (default)', () => { }); describe('EditableCell — expandable', () => { - beforeEach(() => vi.clearAllMocks()); + beforeEach(() => { + vi.clearAllMocks(); + clearEditorSize(); + }); it('shows an expand affordance', () => { setup({ expandable: true }); @@ -139,4 +143,13 @@ describe('EditableCell — expandable', () => { fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(onExpandedChange).toHaveBeenLastCalledWith(false); }); + + it('reopens the editor at the persisted size (FRAME-3)', () => { + saveEditorSize({ width: 900, height: 500 }); + setup({ expandable: true }); + fireEvent.click(screen.getByRole('button', { name: /large editor/i })); + const textarea = screen.getByRole('textbox') as HTMLTextAreaElement; + expect(textarea.style.width).toBe('900px'); + expect(textarea.style.height).toBe('500px'); + }); }); diff --git a/apps/framework-editor/app/components/table/EditableCell.tsx b/apps/framework-editor/app/components/table/EditableCell.tsx index 26464cdad..02204891f 100644 --- a/apps/framework-editor/app/components/table/EditableCell.tsx +++ b/apps/framework-editor/app/components/table/EditableCell.tsx @@ -10,7 +10,8 @@ import { Textarea, } from '@trycompai/ui'; import { Maximize2 } from 'lucide-react'; -import { useState } from 'react'; +import { useRef, useState } from 'react'; +import { loadEditorSize, saveEditorSize, type EditorSize } from './editor-size-storage'; interface EditableCellProps { value: string | null; @@ -44,6 +45,10 @@ export function EditableCell({ const [editValue, setEditValue] = useState(value ?? ''); const [isExpanded, setIsExpanded] = useState(false); const [expandValue, setExpandValue] = useState(value ?? ''); + // Remembered editor size (FRAME-3): the large editor is resizable in both + // directions and reopens at the size the user last left it. + const [editorSize, setEditorSize] = useState(null); + const textareaRef = useRef(null); // Keep local open state and the parent notification in lockstep so the row // highlight tracks the dialog exactly (open icon, right-click, save, cancel, @@ -78,6 +83,7 @@ export function EditableCell({ const handleOpenExpanded = () => { if (disabled) return; setExpandValue(value ?? ''); + setEditorSize(loadEditorSize()); setExpanded(true); }; @@ -88,6 +94,19 @@ export function EditableCell({ setExpanded(false); }; + // Persist the editor size after a resize-handle drag (fires on pointer + // release). Skipped when unchanged so plain clicks don't thrash storage. + const handleEditorResizeEnd = () => { + const el = textareaRef.current; + if (!el) return; + const next: EditorSize = { width: el.offsetWidth, height: el.offsetHeight }; + if (editorSize && next.width === editorSize.width && next.height === editorSize.height) { + return; + } + setEditorSize(next); + saveEditorSize(next); + }; + if (disabled) { return ( @@ -149,15 +168,22 @@ export function EditableCell({ - + {expandTitle}