Address PR review feedback (#2003)

- parseInsightsContent: append continuation lines to the previous bullet so
  multiline insights render in full instead of truncating after the first line
- .memory-insight-item: white-space: pre-wrap so continuation breaks display
- regression tests for per-bullet parsing, counts, and multiline continuations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-10 23:46:34 -07:00
parent 1d2d73ba5c
commit d44da531ff
3 changed files with 61 additions and 3 deletions

View File

@@ -213,6 +213,8 @@ bare border-left text rows, matching the InsightsView card language.
font-size: 13px;
color: var(--text);
line-height: 1.5;
/* Multiline insights keep their continuation-line breaks (PR #2003 review). */
white-space: pre-wrap;
}
/*

View File

@@ -81,12 +81,24 @@ function parseInsightsContent(content: string | null): ParsedInsightCategory[] {
.replace(/<!--[\s\S]*?-->/g, "")
.trim();
// Extract bullet points: filter bullet lines first, then strip the prefix
/*
FNXC:MemoryView 2026-07-11-01:00:
PR #2003 review: an insight can span multiple lines (one bullet followed by
continuation text). Bullet lines start a new item; non-empty non-bullet lines
append to the previous item instead of being dropped, so multiline insights
render in full rather than silently truncating after the first line.
*/
const items = body
.split("\n")
.map((line) => line.trim())
.filter((line) => /^[-*]\s+/.test(line))
.map((line) => line.replace(/^[-*]\s+/, ""));
.reduce<string[]>((acc, line) => {
if (/^[-*]\s+/.test(line)) {
acc.push(line.replace(/^[-*]\s+/, ""));
} else if (line.length > 0 && acc.length > 0) {
acc[acc.length - 1] = `${acc[acc.length - 1]}\n${line}`;
}
return acc;
}, []);
if (items.length > 0 || body.length > 0) {
categories.push({

View File

@@ -158,6 +158,50 @@ describe("MemoryView", () => {
);
});
/*
FNXC:MemoryView 2026-07-11-01:00:
PR #2003 regression tests for insight parsing: bullets must parse into individual items
(the pre-fix parser stripped "- " before filtering for it, collapsing categories into one
blob), and a bullet's continuation lines must stay attached to that bullet instead of
being dropped.
*/
it("parses each markdown bullet into its own insight item with the correct count", async () => {
mockUseMemoryData.mockReturnValue(
createMemoryData({
insightsExists: true,
insightsContent: "## Patterns\n<!-- extraction marker -->\n- First insight\n- Second insight\n\n## Pitfalls\n- Third insight",
}),
);
render(<MemoryView addToast={vi.fn()} />);
await userEvent.click(screen.getByRole("tab", { name: "Insights" }));
expect(screen.getByText("First insight")).toBeInTheDocument();
expect(screen.getByText("Second insight")).toBeInTheDocument();
expect(screen.getByText("Third insight")).toBeInTheDocument();
// Total Insights stat counts individual bullets, and extraction-marker comments are excluded.
expect(screen.getByText("3")).toBeInTheDocument();
expect(screen.queryByText(/extraction marker/)).not.toBeInTheDocument();
});
it("keeps continuation lines attached to their bullet for multiline insights", async () => {
mockUseMemoryData.mockReturnValue(
createMemoryData({
insightsExists: true,
insightsContent: "## Patterns\n- Multiline insight head\n continuation detail line\n- Plain insight",
}),
);
render(<MemoryView addToast={vi.fn()} />);
await userEvent.click(screen.getByRole("tab", { name: "Insights" }));
expect(screen.getByText(/Multiline insight head/)).toBeInTheDocument();
expect(screen.getByText(/continuation detail line/)).toBeInTheDocument();
expect(screen.getByText("Plain insight")).toBeInTheDocument();
// The continuation belongs to the first bullet: still exactly 2 insights in the Total stat.
expect(document.querySelector(".memory-stat-value")?.textContent).toBe("2");
});
it("shows read-only warning after backend resolves as non-writable", () => {
mockUseMemoryData.mockReturnValue(
createMemoryData({