fix(settings): resolve overlapping text in Memory Data panel (#1305)

This commit is contained in:
Cyrus Gray
2026-05-06 13:11:32 -07:00
committed by GitHub
parent 5189ca95ef
commit dba87f7686
3 changed files with 132 additions and 6 deletions
@@ -100,7 +100,7 @@ function SourceCard({ status }: SourceCardProps) {
{FRESHNESS_LABEL[status.freshness]}
</span>
</div>
<div className="mt-1 flex items-center gap-3 text-xs text-stone-500">
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-stone-500">
<span data-testid={`memory-sync-chunks-${status.provider}`}>
{lifetime.toLocaleString()} chunks
</span>
@@ -187,7 +187,7 @@ export function MemorySyncConnections({ pollIntervalMs }: MemorySyncConnectionsP
return (
<section className="memory-sync-connections" data-testid="memory-sync-connections">
<h3 className="text-sm font-semibold text-stone-700">Memory sources</h3>
<p className="mt-2 rounded-md bg-coral-50 p-2 text-xs text-coral-800">
<p className="mt-2 rounded-md bg-coral-50 p-2 text-xs text-coral-800 break-words">
Failed to load sync status: {loadError}
</p>
</section>
@@ -137,7 +137,7 @@ const MemoryWindowControl = ({ onError, onSaved }: Props) => {
<div
role="radiogroup"
aria-label="Long-term memory window"
className="grid grid-cols-1 sm:grid-cols-4 gap-2">
className="grid grid-cols-2 gap-2">
{MEMORY_CONTEXT_WINDOWS.map(option => {
const optionMeta = MEMORY_WINDOW_PRESET_META[option];
const isActive = activeForUi === option;
@@ -154,9 +154,9 @@ const MemoryWindowControl = ({ onError, onSaved }: Props) => {
className={`text-left rounded-md border px-3 py-2 transition-colors ${
isActive ? 'border-primary bg-primary/10' : 'border-border hover:bg-accent/40'
} disabled:opacity-60 disabled:cursor-not-allowed`}>
<div className="flex items-center justify-between">
<span className="font-medium">{optionMeta.label}</span>
<span className="text-[10px] uppercase tracking-wide text-muted-foreground">
<div className="flex items-center justify-between gap-1 min-w-0">
<span className="font-medium truncate">{optionMeta.label}</span>
<span className="text-[10px] uppercase tracking-wide text-muted-foreground shrink-0 whitespace-nowrap">
{optionMeta.badge}
</span>
</div>
@@ -0,0 +1,126 @@
/**
* Tests for the Settings → Memory Data panel.
*
* Verifies that all four memory-window preset buttons render, the memory
* sources section is present, and that a sync-connection error does not
* hide or disable the memory-window controls.
*/
import { screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { renderWithProviders } from '../../../../test/test-utils';
import MemoryDataPanel from '../MemoryDataPanel';
// ── Mocks ────────────────────────────────────────────────────────────────────
const hoisted = vi.hoisted(() => ({
mockGetConfig: vi.fn(),
mockUpdateMemorySettings: vi.fn(),
mockIsTauri: vi.fn(() => false),
mockMemoryTreeListChunks: vi.fn(),
mockMemoryTreeListSources: vi.fn(),
mockMemoryTreeTopEntities: vi.fn(),
mockMemoryTreeEntityIndexFor: vi.fn(),
mockMemoryTreeChunkScore: vi.fn(),
mockMemoryTreeChunksForEntity: vi.fn(),
mockStatusList: vi.fn(),
}));
vi.mock('../../../../utils/tauriCommands', () => ({
isTauri: hoisted.mockIsTauri,
openhumanGetConfig: hoisted.mockGetConfig,
openhumanUpdateMemorySettings: hoisted.mockUpdateMemorySettings,
MEMORY_CONTEXT_WINDOWS: ['minimal', 'balanced', 'extended', 'maximum'],
memoryTreeListChunks: hoisted.mockMemoryTreeListChunks,
memoryTreeListSources: hoisted.mockMemoryTreeListSources,
memoryTreeTopEntities: hoisted.mockMemoryTreeTopEntities,
memoryTreeEntityIndexFor: hoisted.mockMemoryTreeEntityIndexFor,
memoryTreeChunkScore: hoisted.mockMemoryTreeChunkScore,
memoryTreeChunksForEntity: hoisted.mockMemoryTreeChunksForEntity,
}));
vi.mock('../../../../services/memorySyncService', async () => {
const actual = await vi.importActual<typeof import('../../../../services/memorySyncService')>(
'../../../../services/memorySyncService'
);
return {
...actual,
memorySyncStatusList: (...args: unknown[]) => hoisted.mockStatusList(...args),
};
});
vi.mock('../../hooks/useSettingsNavigation', () => ({
useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }),
}));
// ── Helpers ───────────────────────────────────────────────────────────────────
const resolveConfigWith = (memory_window = 'balanced') => {
hoisted.mockGetConfig.mockResolvedValue({
result: {
config: { agent: { memory_window } },
workspace_dir: '/tmp/ws',
config_path: '/tmp/cfg.toml',
},
});
};
// ── Tests ─────────────────────────────────────────────────────────────────────
describe('MemoryDataPanel', () => {
beforeEach(() => {
hoisted.mockGetConfig.mockReset();
hoisted.mockUpdateMemorySettings.mockReset();
hoisted.mockIsTauri.mockReturnValue(false);
hoisted.mockStatusList.mockReset();
hoisted.mockMemoryTreeListChunks.mockReset();
hoisted.mockMemoryTreeListSources.mockReset();
hoisted.mockMemoryTreeTopEntities.mockReset();
hoisted.mockMemoryTreeEntityIndexFor.mockReset();
hoisted.mockMemoryTreeChunkScore.mockReset();
hoisted.mockMemoryTreeChunksForEntity.mockReset();
// Default: no sources yet, no errors
hoisted.mockStatusList.mockResolvedValue([]);
hoisted.mockMemoryTreeListChunks.mockResolvedValue({ chunks: [], total: 0, cursor: null });
hoisted.mockMemoryTreeListSources.mockResolvedValue([]);
hoisted.mockMemoryTreeTopEntities.mockResolvedValue([]);
});
it('renders all four preset buttons', async () => {
resolveConfigWith('balanced');
renderWithProviders(<MemoryDataPanel />);
for (const label of ['Minimal', 'Balanced', 'Extended', 'Maximum']) {
expect(screen.getByText(label)).toBeInTheDocument();
}
});
it('renders the memory sources section', async () => {
resolveConfigWith('balanced');
renderWithProviders(<MemoryDataPanel />);
await waitFor(() => {
expect(screen.getByTestId('memory-sync-connections')).toBeInTheDocument();
});
});
it('keeps all preset buttons accessible when sync connections returns an error', async () => {
resolveConfigWith('balanced');
hoisted.mockStatusList.mockRejectedValue(new Error('network timeout'));
renderWithProviders(<MemoryDataPanel />);
// Wait for the error state to appear in the sync connections section
await waitFor(() => {
expect(screen.getByText(/network timeout/i)).toBeInTheDocument();
});
// All four preset buttons must still be in the DOM and not disabled
for (const preset of ['minimal', 'balanced', 'extended', 'maximum']) {
const btn = screen.getByTestId(`memory-window-option-${preset}`);
expect(btn).toBeInTheDocument();
expect(btn).not.toBeDisabled();
}
});
});