fix(canvas): color-code similarity badge by score tier (closes #783)

fix(canvas): color-code similarity badge by score tier (issue #783)
This commit is contained in:
molecule-ai[bot] 2026-04-17 19:24:44 +00:00 committed by GitHub
commit cd87cd01bb
2 changed files with 50 additions and 2 deletions

View File

@ -427,11 +427,18 @@ function MemoryEntryRow({
{/* Similarity score badge — only rendered when backend provides a score */}
{entry.similarity_score != null && (
<span
className="text-[9px] text-zinc-500 shrink-0 font-mono tabular-nums"
className={[
"text-[9px] shrink-0 font-mono tabular-nums",
entry.similarity_score >= 0.8
? "text-blue-500"
: entry.similarity_score >= 0.5
? "text-zinc-400"
: "text-zinc-400 italic",
].join(" ")}
title={`Similarity: ${(entry.similarity_score * 100).toFixed(1)}%`}
data-testid="similarity-badge"
>
{Math.round(entry.similarity_score * 100)}%
{entry.similarity_score < 0.5 ? "~" : ""}{Math.round(entry.similarity_score * 100)}%
</span>
)}
<span className="text-[9px] text-zinc-600 shrink-0">

View File

@ -475,6 +475,47 @@ describe("MemoryInspectorPanel — semantic search", () => {
).toBeNull();
});
it("colors similarity-badge blue-500 when score >= 0.8", async () => {
mockGet.mockResolvedValue([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ ...ENTRY_A, similarity_score: 0.92 },
] as any);
render(<MemoryInspectorPanel workspaceId="ws-1" />);
await waitFor(() => screen.getByText("task-queue"));
const badge = document.querySelector('[data-testid="similarity-badge"]');
expect(badge?.className).toContain("text-blue-500");
expect(badge?.className).not.toContain("text-zinc-400");
expect(badge?.className).not.toContain("text-zinc-600");
});
it("colors similarity-badge zinc-400 when score is between 0.5 and 0.8", async () => {
mockGet.mockResolvedValue([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ ...ENTRY_A, similarity_score: 0.65 },
] as any);
render(<MemoryInspectorPanel workspaceId="ws-1" />);
await waitFor(() => screen.getByText("task-queue"));
const badge = document.querySelector('[data-testid="similarity-badge"]');
expect(badge?.className).toContain("text-zinc-400");
expect(badge?.className).not.toContain("text-blue-500");
expect(badge?.className).not.toContain("text-zinc-600");
});
it("colors similarity-badge zinc-400 italic with tilde prefix when score is below 0.5", async () => {
mockGet.mockResolvedValue([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ ...ENTRY_A, similarity_score: 0.31 },
] as any);
render(<MemoryInspectorPanel workspaceId="ws-1" />);
await waitFor(() => screen.getByText("task-queue"));
const badge = document.querySelector('[data-testid="similarity-badge"]');
expect(badge?.className).toContain("text-zinc-400");
expect(badge?.className).toContain("italic");
expect(badge?.className).not.toContain("text-blue-500");
expect(badge?.className).not.toContain("text-zinc-600");
expect(badge?.textContent).toBe("~31%");
});
it("clear button resets debouncedQuery immediately and re-fetches without ?q=", async () => {
vi.useFakeTimers();
// eslint-disable-next-line @typescript-eslint/no-explicit-any