diff --git a/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.expandable.test.tsx b/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.expandable.test.tsx index 41a0d4182..51836f7cf 100644 --- a/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.expandable.test.tsx +++ b/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.expandable.test.tsx @@ -71,7 +71,11 @@ describe('FrameworkRequirementsClientPage — Description column', () => { const description = editableCellProps.find((p) => p.columnId === 'description'); expect(description?.expandable).toBe(true); - expect(description?.expandTitle).toBe('Edit Requirement Description'); + // Identifier + name are appended so the editor dialog says which requirement + // is being edited (FRAME-7), e.g. "… - AC-2 - Account Management". + expect(description?.expandTitle).toBe( + 'Edit Requirement Description - AC-2 - Account Management', + ); // The short single-line columns stay as plain inline edits. for (const columnId of ['identifier', 'name']) { diff --git a/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.tsx b/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.tsx index f6e9570a3..da517c096 100644 --- a/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.tsx +++ b/apps/framework-editor/app/(pages)/frameworks/[frameworkId]/FrameworkRequirementsClientPage.tsx @@ -73,6 +73,9 @@ export function FrameworkRequirementsClientPage({ const router = useRouter(); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + // Row whose large description editor is currently open — highlighted so the + // edited row is obvious behind the (semi-transparent) editor dialog. + const [expandedRowId, setExpandedRowId] = useState(null); const initialGridData: RequirementGridRow[] = useMemo( () => @@ -155,16 +158,27 @@ export function FrameworkRequirementsClientPage({ header: 'Description', size: 300, maxSize: 300, - cell: ({ row, getValue }) => ( - - ), + cell: ({ row, getValue }) => { + const { identifier, name } = row.original; + const titleSuffix = [identifier, name].filter(Boolean).join(' - '); + return ( + + setExpandedRowId(open ? row.original.id : null) + } + /> + ); + }, }), columnHelper.accessor('controlTemplates', { header: 'Linked Controls', @@ -369,7 +383,11 @@ export function FrameworkRequirementsClientPage({ {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( { setup({ expandable: true, disabled: true }); expect(screen.queryByRole('button', { name: /large editor/i })).toBeNull(); }); + + it('notifies onExpandedChange when the editor opens and on Save', () => { + const onExpandedChange = vi.fn(); + setup({ expandable: true, onExpandedChange }); + fireEvent.click(screen.getByRole('button', { name: /large editor/i })); + expect(onExpandedChange).toHaveBeenLastCalledWith(true); + fireEvent.change(screen.getByRole('textbox'), { target: { value: 'changed' } }); + fireEvent.click(screen.getByRole('button', { name: 'Save' })); + expect(onExpandedChange).toHaveBeenLastCalledWith(false); + }); + + it('notifies onExpandedChange(false) on Cancel', () => { + const onExpandedChange = vi.fn(); + setup({ expandable: true, onExpandedChange }); + fireEvent.contextMenu(screen.getByText(/assign account managers/i)); + expect(onExpandedChange).toHaveBeenLastCalledWith(true); + fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(onExpandedChange).toHaveBeenLastCalledWith(false); + }); }); diff --git a/apps/framework-editor/app/components/table/EditableCell.tsx b/apps/framework-editor/app/components/table/EditableCell.tsx index 54e129873..26464cdad 100644 --- a/apps/framework-editor/app/components/table/EditableCell.tsx +++ b/apps/framework-editor/app/components/table/EditableCell.tsx @@ -24,6 +24,9 @@ interface EditableCellProps { // values like control descriptions. expandable?: boolean; expandTitle?: string; + // Notified when the large editor opens/closes so the parent can highlight + // the row currently being edited. + onExpandedChange?: (open: boolean) => void; } export function EditableCell({ @@ -35,12 +38,21 @@ export function EditableCell({ placeholder = 'Click to edit', expandable = false, expandTitle = 'Edit', + onExpandedChange, }: EditableCellProps) { const [isEditing, setIsEditing] = useState(false); const [editValue, setEditValue] = useState(value ?? ''); const [isExpanded, setIsExpanded] = useState(false); const [expandValue, setExpandValue] = useState(value ?? ''); + // Keep local open state and the parent notification in lockstep so the row + // highlight tracks the dialog exactly (open icon, right-click, save, cancel, + // Esc, and overlay click all route through here). + const setExpanded = (open: boolean) => { + setIsExpanded(open); + onExpandedChange?.(open); + }; + const handleBlur = () => { setIsEditing(false); if (editValue !== (value ?? '')) { @@ -66,14 +78,14 @@ export function EditableCell({ const handleOpenExpanded = () => { if (disabled) return; setExpandValue(value ?? ''); - setIsExpanded(true); + setExpanded(true); }; const handleExpandSave = () => { if (expandValue !== (value ?? '')) { onUpdate(rowId, columnId, expandValue); } - setIsExpanded(false); + setExpanded(false); }; if (disabled) { @@ -136,7 +148,7 @@ export function EditableCell({ - + {expandTitle} @@ -148,7 +160,7 @@ export function EditableCell({ className="min-h-[260px] font-mono text-sm" /> -