mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
Fix memory action alignment (#3175)
This commit is contained in:
parent
693176457e
commit
a7e7d5db18
3 changed files with 159 additions and 51 deletions
|
|
@ -1421,33 +1421,35 @@ export function MemorySection({
|
|||
{entry.description || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="library-card-expand"
|
||||
onClick={() => openPreview(entry.id)}
|
||||
title={t('settings.memoryPreview')}
|
||||
>
|
||||
<Icon
|
||||
name={previewId === entry.id ? 'chevron-down' : 'chevron-right'}
|
||||
size={14}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => startEdit(entry.id)}
|
||||
title={t('settings.memoryEdit')}
|
||||
>
|
||||
<Icon name="edit" size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => onDelete(entry.id)}
|
||||
title={t('settings.memoryDelete')}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
<div className="memory-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="library-card-expand"
|
||||
onClick={() => openPreview(entry.id)}
|
||||
title={t('settings.memoryPreview')}
|
||||
>
|
||||
<Icon
|
||||
name={previewId === entry.id ? 'chevron-down' : 'chevron-right'}
|
||||
size={14}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => startEdit(entry.id)}
|
||||
title={t('settings.memoryEdit')}
|
||||
>
|
||||
<Icon name="edit" size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => onDelete(entry.id)}
|
||||
title={t('settings.memoryDelete')}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{previewId === entry.id && (
|
||||
<div className="library-preview" style={{ width: '100%' }}>
|
||||
{previewBody === null ? (
|
||||
|
|
@ -1530,15 +1532,17 @@ export function MemorySection({
|
|||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => void onDeleteExtraction(record.id)}
|
||||
title={t('settings.memoryExtractionDelete')}
|
||||
aria-label={t('settings.memoryExtractionDelete')}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
<div className="memory-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => void onDeleteExtraction(record.id)}
|
||||
title={t('settings.memoryExtractionDelete')}
|
||||
aria-label={t('settings.memoryExtractionDelete')}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -2280,12 +2284,7 @@ export function MemorySection({
|
|||
{children.map((child) => (
|
||||
<li
|
||||
key={child.id}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr) auto',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}
|
||||
className="memory-tree-child-row"
|
||||
>
|
||||
<span style={{ minWidth: 0 }}>
|
||||
<span className="library-card-name">{child.name}</span>{' '}
|
||||
|
|
@ -2299,14 +2298,16 @@ export function MemorySection({
|
|||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => startEdit(child.id)}
|
||||
title={t('settings.memoryEdit')}
|
||||
>
|
||||
<Icon name="edit" size={14} />
|
||||
</button>
|
||||
<div className="memory-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="ghost library-card-action"
|
||||
onClick={() => startEdit(child.id)}
|
||||
title={t('settings.memoryEdit')}
|
||||
>
|
||||
<Icon name="edit" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1345,7 +1345,7 @@
|
|||
|
||||
.memory-records-section .library-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) repeat(3, 30px);
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 58px;
|
||||
|
|
@ -1394,6 +1394,21 @@
|
|||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.memory-card-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.memory-tree-child-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.memory-records-section .library-card-expand,
|
||||
.memory-records-section .library-card .library-card-action {
|
||||
width: 30px;
|
||||
|
|
@ -2212,6 +2227,15 @@
|
|||
grid-column: 2;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.memory-records-section .library-card,
|
||||
.memory-tree-child-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.memory-card-actions {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Memory section: monospace path showing where the memory files live on
|
||||
|
|
|
|||
|
|
@ -281,6 +281,89 @@ describe('MemorySection', () => {
|
|||
expect(screen.getByDisplayValue('- Keep design-system extraction in the loop')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('anchors memory tree entry edit controls in the right-side action zone', async () => {
|
||||
globalThis.EventSource = StubEventSource as unknown as typeof EventSource;
|
||||
const entry = {
|
||||
id: 'project_design_agent_goal',
|
||||
name: 'Design agent goal',
|
||||
description: 'Open Design should evolve from accepted work',
|
||||
type: 'project',
|
||||
body: '- Keep design-system extraction in the loop',
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = input.toString();
|
||||
if (url === '/api/memory') {
|
||||
return new Response(JSON.stringify({
|
||||
enabled: true,
|
||||
rootDir: '/tmp/memory',
|
||||
index: '# Memory\n',
|
||||
entries: [entry],
|
||||
extraction: null,
|
||||
}), { status: 200, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
if (url === '/api/memory/tree') {
|
||||
return new Response(JSON.stringify({
|
||||
enabled: true,
|
||||
rootDir: '/tmp/memory',
|
||||
tree: [
|
||||
{
|
||||
id: 'folder:project',
|
||||
parentId: null,
|
||||
path: '/project',
|
||||
name: 'Project',
|
||||
kind: 'folder',
|
||||
type: 'project',
|
||||
scope: 'project',
|
||||
sourcePacketIds: [],
|
||||
proposalIds: [],
|
||||
createdAt: new Date(entry.updatedAt).toISOString(),
|
||||
updatedAt: new Date(entry.updatedAt).toISOString(),
|
||||
childrenCount: 1,
|
||||
},
|
||||
{
|
||||
id: entry.id,
|
||||
parentId: 'folder:project',
|
||||
path: `/project/${entry.id}`,
|
||||
name: entry.name,
|
||||
description: entry.description,
|
||||
kind: 'entry',
|
||||
type: 'project',
|
||||
scope: 'project',
|
||||
sourcePacketIds: [],
|
||||
proposalIds: [],
|
||||
createdAt: new Date(entry.updatedAt).toISOString(),
|
||||
updatedAt: new Date(entry.updatedAt).toISOString(),
|
||||
childrenCount: 0,
|
||||
},
|
||||
],
|
||||
}), { status: 200, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
if (url === '/api/memory/extractions') {
|
||||
return new Response(JSON.stringify({ extractions: [] }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({}), { status: 404 });
|
||||
}) as typeof fetch;
|
||||
|
||||
renderMemorySection();
|
||||
|
||||
const treeDetails = (await screen.findByText('Memory tree')).closest('details')!;
|
||||
const childRow = within(treeDetails)
|
||||
.getByText('Design agent goal')
|
||||
.closest('.memory-tree-child-row') as HTMLElement;
|
||||
const editButton = within(childRow).getByTitle('Edit');
|
||||
const actionZone = editButton.closest('.memory-card-actions');
|
||||
const childContent = childRow.firstElementChild as HTMLElement;
|
||||
|
||||
expect(actionZone).toBeTruthy();
|
||||
expect(actionZone?.parentElement).toBe(childRow);
|
||||
expect(childContent.contains(editButton)).toBe(false);
|
||||
});
|
||||
|
||||
it('shows unsaved index state and saves the updated index', async () => {
|
||||
globalThis.EventSource = StubEventSource as unknown as typeof EventSource;
|
||||
let savedIndex = '# Memory\n\n- Existing bullet\n';
|
||||
|
|
|
|||
Loading…
Reference in a new issue