{"version":3,"file":"memory-editor.test.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/memory-editor.test.ts"],"names":[],"mappings":"","sourcesContent":["import { visibleWidth } from \"@apholdings/jensen-tui\";\nimport { beforeAll, describe, expect, it, vi } from \"vitest\";\nimport type { MemoryItem } from \"../../../core/memory.js\";\nimport { initTheme } from \"../theme/theme.js\";\nimport { type MemoryEditorCallbacks, MemoryEditorComponent } from \"./memory-editor.js\";\n\n// Initialize theme before all tests (required by theme.ts)\nbeforeAll(() => {\n\tinitTheme(\"dark\");\n});\n\nconst WIDTH = 80;\n\nfunction makeCallbacks(overrides?: Partial<MemoryEditorCallbacks>): MemoryEditorCallbacks {\n\treturn {\n\t\tgetMemoryItems: () => [],\n\t\tgetMemoryHistory: () => [],\n\t\tsetMemoryItem: vi.fn().mockReturnValue([]),\n\t\tdeleteMemoryItem: vi.fn().mockReturnValue([]),\n\t\tclearMemory: vi.fn().mockReturnValue([]),\n\t\trequestRender: vi.fn(),\n\t\t...overrides,\n\t};\n}\n\nfunction makeItem(key: string, value: string, daysAgo = 0): MemoryItem {\n\tconst timestamp = new Date(Date.now() - daysAgo * 86_400_000).toISOString();\n\treturn { key, value, timestamp };\n}\n\nfunction makeSnapshot(\n\tid: string,\n\titems: Array<{ key: string; value: string }>,\n\trecordedAt = \"2024-01-01T00:00:00.000Z\",\n\tisCurrent = false,\n): import(\"../../../core/memory.js\").MemoryHistorySnapshot {\n\treturn {\n\t\tentryId: id,\n\t\tparentId: null,\n\t\trecordedAt,\n\t\titems: items.map((i) => ({ ...i, timestamp: recordedAt })),\n\t\tisCurrent,\n\t};\n}\n\ndescribe(\"MemoryEditorComponent\", () => {\n\tdescribe(\"render — list mode\", () => {\n\t\tit(\"shows title and subtitle for empty state\", () => {\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => [] });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines[0]).toContain(\"Session Memory\");\n\t\t\t// Subtitle line should contain \"(no memory items\"\n\t\t\texpect(lines.find((l) => l.includes(\"(no memory items\"))).toBeDefined();\n\t\t});\n\n\t\tit(\"shows stale indicator for old items\", () => {\n\t\t\tconst items = [makeItem(\"test\", \"hello\", 10)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should contain the stale marker\n\t\t\tconst staleLine = lines.find((l) => l.includes(\"test\") && l.includes(\"⚠\"));\n\t\t\texpect(staleLine).toBeDefined();\n\t\t});\n\n\t\tit(\"shows fresh indicator for recent items\", () => {\n\t\t\tconst items = [makeItem(\"test\", \"hello\", 1)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should contain the fresh marker\n\t\t\tconst freshLine = lines.find((l) => l.includes(\"test\") && l.includes(\"✓\"));\n\t\t\texpect(freshLine).toBeDefined();\n\t\t});\n\n\t\tit(\"shows selection cursor on selected item\", () => {\n\t\t\tconst items = [makeItem(\"first\", \"v1\"), makeItem(\"second\", \"v2\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\t// Default selectedIndex is 0\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// First item should have cursor\n\t\t\tconst selectedLine = lines.find((l) => l.includes(\"first\") && l.includes(\"›\"));\n\t\t\texpect(selectedLine).toBeDefined();\n\t\t});\n\n\t\tit(\"updates cursor when selectedIndex changes\", () => {\n\t\t\tconst items = [makeItem(\"first\", \"v1\"), makeItem(\"second\", \"v2\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"j\"); // Move down\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\tconst selectedLine = lines.find((l) => l.includes(\"second\") && l.includes(\"›\"));\n\t\t\texpect(selectedLine).toBeDefined();\n\t\t});\n\n\t\tit(\"truncates long value preview to terminal width\", () => {\n\t\t\tconst longValue = \"a\".repeat(100);\n\t\t\tconst items = [makeItem(\"key\", longValue)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\tconst itemLine = lines.find((l) => l.includes(\"key\"));\n\t\t\texpect(itemLine).toBeDefined();\n\t\t\t// Visible width must not exceed terminal width (ANSI codes add chars to .length)\n\t\t\texpect(visibleWidth(itemLine!)).toBeLessThanOrEqual(WIDTH);\n\t\t});\n\t});\n\n\tdescribe(\"render — input mode\", () => {\n\t\tit(\"shows key prompt when adding\", () => {\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => [] });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"a\"); // start add\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines[0]).toContain(\"Add Key\");\n\t\t});\n\n\t\tit(\"shows value prompt after key entered\", () => {\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => [] });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"a\"); // start add\n\t\t\tcomp.handleInput(\"k\"); // type 'k'\n\t\t\tcomp.handleInput(\"k\"); // type 'k' (key: \"kk\")\n\t\t\tcomp.handleInput(\"\\r\"); // submit key\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines[0]).toContain(\"Add Value\");\n\t\t});\n\n\t\tit(\"cancel reverts to list mode\", () => {\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => [] });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"a\"); // start add\n\t\t\tcomp.handleInput(\"\\x1b\"); // cancel\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines[0]).toContain(\"Session Memory\");\n\t\t\texpect(lines[0]).not.toContain(\"Add\");\n\t\t});\n\t});\n\n\tdescribe(\"render — confirm mode\", () => {\n\t\tit(\"shows confirm delete prompt with key\", () => {\n\t\t\tconst items = [makeItem(\"mykey\", \"val\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\t// Trigger delete mode directly\n\t\t\t(comp as any).mode = \"confirm_delete\";\n\t\t\t(comp as any).editingItemKey = \"mykey\";\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines.find((l) => l.includes(\"Confirm Delete\"))).toBeDefined();\n\t\t\texpect(lines.find((l) => l.includes(\"mykey\"))).toBeDefined();\n\t\t});\n\n\t\tit(\"confirm delete calls backend\", () => {\n\t\t\tconst deleteMock = vi.fn().mockReturnValue([]);\n\t\t\tconst items = [makeItem(\"todelete\", \"val\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items, deleteMemoryItem: deleteMock });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\t(comp as any).startDelete(\"todelete\");\n\t\t\tcomp.handleInput(\"\\r\"); // confirm\n\t\t\texpect(deleteMock).toHaveBeenCalledWith(\"todelete\");\n\t\t});\n\n\t\tit(\"confirm clear calls backend\", () => {\n\t\t\tconst clearMock = vi.fn().mockReturnValue([]);\n\t\t\tconst items = [makeItem(\"a\", \"b\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items, clearMemory: clearMock });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\t(comp as any).startClearAll();\n\t\t\tcomp.handleInput(\"\\r\"); // confirm\n\t\t\texpect(clearMock).toHaveBeenCalled();\n\t\t});\n\t});\n\n\tdescribe(\"render — review mode\", () => {\n\t\tit(\"shows review title\", () => {\n\t\t\tconst items = [makeItem(\"a\", \"b\", 10)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"r\"); // enter review mode\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines[0]).toContain(\"Review\");\n\t\t});\n\n\t\tit(\"shows stale section by default when items are stale\", () => {\n\t\t\tconst items = [makeItem(\"old\", \"v\", 20)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"r\");\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should have stale tab selected\n\t\t\texpect(lines.find((l) => l.includes(\"◉ Stale\"))).toBeDefined();\n\t\t});\n\n\t\tit(\"switches section on left/right arrow\", () => {\n\t\t\tconst items = [makeItem(\"old\", \"v\", 20), makeItem(\"new\", \"v\", 1)];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"r\"); // enter review\n\t\t\tcomp.handleInput(\"\\u001b[C]\"); // right arrow\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines.find((l) => l.includes(\"◉ Fresh\"))).toBeDefined();\n\t\t});\n\t});\n\n\tdescribe(\"refresh()\", () => {\n\t\tit(\"clamps selectedIndex to valid range\", () => {\n\t\t\tconst items = [makeItem(\"a\", \"b\")];\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => items });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\t// Select second item (index 1) when only one item exists\n\t\t\t(comp as any).selectedIndex = 5;\n\t\t\tcomp.refresh();\n\t\t\texpect((comp as any).selectedIndex).toBe(0);\n\t\t});\n\t});\n\n\tdescribe(\"add flow\", () => {\n\t\tit(\"saves new item through backend\", () => {\n\t\t\tconst setMock = vi.fn().mockReturnValue([makeItem(\"newkey\", \"newval\")]);\n\t\t\tconst cb = makeCallbacks({ getMemoryItems: () => [], setMemoryItem: setMock });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\n\t\t\tcomp.handleInput(\"a\"); // start add\n\t\t\t// Type key character by character\n\t\t\tcomp.handleInput(\"n\");\n\t\t\tcomp.handleInput(\"e\");\n\t\t\tcomp.handleInput(\"w\");\n\t\t\tcomp.handleInput(\"k\");\n\t\t\tcomp.handleInput(\"e\");\n\t\t\tcomp.handleInput(\"y\");\n\t\t\tcomp.handleInput(\"\\r\"); // submit key\n\n\t\t\t// Type value\n\t\t\tcomp.handleInput(\"n\");\n\t\t\tcomp.handleInput(\"e\");\n\t\t\tcomp.handleInput(\"w\");\n\t\t\tcomp.handleInput(\"v\");\n\t\t\tcomp.handleInput(\"a\");\n\t\t\tcomp.handleInput(\"l\");\n\t\t\tcomp.handleInput(\"\\r\"); // submit value\n\n\t\t\texpect(setMock).toHaveBeenCalledWith(\"newkey\", \"newval\");\n\t\t});\n\t});\n\n\tdescribe(\"baseline picker — history list\", () => {\n\t\tit(\"b key arms current snapshot as baseline\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"old\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"mid\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s2\", [{ key: \"new\", value: \"v\" }], \"2024-01-03T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\"); // enter history (selectedIndex=0, newest)\n\t\t\tcomp.handleInput(\"b\"); // arm as baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(2); // chronological index of s2\n\t\t});\n\n\t\tit(\"baseline marker visible in history list render output\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\"); // enter history\n\t\t\tcomp.handleInput(\"b\"); // arm baseline (s1, chronological index 1)\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should contain the baseline marker\n\t\t\texpect(lines.some((l) => l.includes(\"[baseline]\"))).toBe(true);\n\t\t});\n\n\t\tit(\"re-arming baseline changes selected baseline\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s2\", [{ key: \"c\", value: \"v\" }], \"2024-01-03T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\"); // enter history (selectedIndex=0, s2 newest)\n\t\t\tcomp.handleInput(\"b\"); // arm s2 (chronological index 2)\n\t\t\tcomp.handleInput(\"j\"); // move down to s1 (selectedIndex=1, reversed)\n\t\t\tcomp.handleInput(\"b\"); // re-arm to s1 (chronological index 1)\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1);\n\t\t});\n\t});\n\n\tdescribe(\"baseline picker — history detail\", () => {\n\t\tit(\"baseline marker shown in history detail when viewing armed snapshot\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\"); // enter history\n\t\t\tcomp.handleInput(\"b\"); // arm baseline (s1)\n\t\t\tcomp.handleInput(\"\\r\"); // enter detail\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines.some((l) => l.includes(\"[baseline]\"))).toBe(true);\n\t\t});\n\n\t\tit(\"b key arms current snapshot as baseline in detail mode\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\"); // enter history\n\t\t\tcomp.handleInput(\"\\r\"); // enter detail (on s1)\n\t\t\tcomp.handleInput(\"b\"); // arm s1 as baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1);\n\t\t});\n\n\t\tit(\"c key clears armed baseline in detail mode\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm baseline\n\t\t\tcomp.handleInput(\"\\r\");\n\t\t\tcomp.handleInput(\"c\"); // clear baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBeNull();\n\t\t});\n\t});\n\n\tdescribe(\"baseline picker — diff semantics\", () => {\n\t\tit(\"d with explicit baseline enters target-picking, Enter opens diff\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"a\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s2\", [{ key: \"a\", value: \"newer\" }], \"2024-01-03T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s2 (index 2) as baseline\n\t\t\tcomp.handleInput(\"\\u001b[D]\"); // go to s1 in history (prev snapshot, chronological index 1)\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\texpect((comp as any).mode).toBe(\"history\"); // still in history mode\n\t\t\tcomp.handleInput(\"\\r\"); // confirm target → open diff\n\t\t\texpect((comp as any).diffBaseIndex).toBe(2); // explicit baseline\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t});\n\n\t\tit(\"d without baseline still opens adjacent diff immediately\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"a\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s2\", [{ key: \"a\", value: \"newer\" }], \"2024-01-03T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\t// Enter detail on s2 (selectedIndex=2)\n\t\t\tcomp.handleInput(\"\\r\");\n\t\t\t// Arrow left to s1 (selectedIndex=1)\n\t\t\tcomp.handleInput(\"\\u001b[D]\");\n\t\t\t// d without baseline: adjacent diff immediately\n\t\t\tcomp.handleInput(\"d\");\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t\texpect((comp as any).diffBaseIndex).toBe(0); // adjacent previous\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t});\n\n\t\tit(\"compare same snapshot to itself yields no changes\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"a\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1 as baseline\n\t\t\tcomp.handleInput(\"j\"); // move to s0 (selectedIndex=1 reversed = chron 0)\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\tcomp.handleInput(\"\\r\"); // confirm target → open diff (s1 baseline → s0 target)\n\t\t\texpect((comp as any).diffBaseIndex).toBe(1); // s1 baseline\n\t\t\texpect((comp as any).selectedIndex).toBe(0); // s0 chronologically\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should show \"No changes between snapshots\"\n\t\t\texpect(lines.some((l) => l.includes(\"No changes\"))).toBe(true);\n\t\t});\n\n\t\tit(\"c key clears baseline in diff mode\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"a\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s2\", [{ key: \"a\", value: \"newer\" }], \"2024-01-03T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s2 as baseline\n\t\t\tcomp.handleInput(\"\\u001b[D]\"); // go to s1\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tcomp.handleInput(\"\\r\"); // confirm → open diff\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(2);\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t\tcomp.handleInput(\"c\"); // clear baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBeNull();\n\t\t\t// Should still be in diff mode\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t});\n\n\t\tit(\"c key clears baseline in history list mode\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1);\n\t\t\tcomp.handleInput(\"c\"); // clear baseline\n\t\t\texpect((comp as any).armedBaselineIndex).toBeNull();\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines.some((l) => l.includes(\"[baseline]\"))).toBe(false);\n\t\t});\n\t});\n\n\tdescribe(\"two-step compare chooser\", () => {\n\t\tit(\"d enters target-picking in history list when baseline armed\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm baseline (s1, index 1)\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\texpect((comp as any).mode).toBe(\"history\"); // stays in history mode\n\t\t});\n\n\t\tit(\"target-picking renders [selecting target] subtitle with baseline annotation\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm baseline\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Should show baseline and selecting target in subtitle\n\t\t\texpect(lines.some((l) => l.includes(\"[baseline:\"))).toBe(true);\n\t\t\texpect(lines.some((l) => l.includes(\"[selecting target:\"))).toBe(true);\n\t\t});\n\n\t\tit(\"target-picking shows [target] marker on selected line\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1 (selectedIndex=0 in reversed = index 1 chron)\n\t\t\tcomp.handleInput(\"j\"); // move to s0 (selectedIndex=1 in reversed = index 0 chron)\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\t// Selected line (s0) should show [target] marker\n\t\t\texpect(lines.some((l) => l.includes(\"[target]\"))).toBe(true);\n\t\t});\n\n\t\tit(\"Enter confirms target and opens diff\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1 as baseline (chron index 1)\n\t\t\tcomp.handleInput(\"j\"); // move to s0 (selectedIndex=1 reversed)\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tcomp.handleInput(\"\\r\"); // confirm target\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t\texpect((comp as any).diffBaseIndex).toBe(1); // baseline s1\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t});\n\n\t\tit(\"d also confirms target and opens diff from target-picking\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1\n\t\t\tcomp.handleInput(\"j\"); // move to s0\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tcomp.handleInput(\"d\"); // d also confirms target\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t\texpect((comp as any).diffBaseIndex).toBe(1);\n\t\t});\n\n\t\tit(\"Escape exits target-picking and keeps baseline armed\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1);\n\t\t\tcomp.handleInput(\"\\x1b\"); // Escape — exit target-picking, keep baseline\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1); // baseline preserved\n\t\t});\n\n\t\tit(\"c clears baseline and exits target-picking\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\tcomp.handleInput(\"c\"); // clear baseline + exit target-picking\n\t\t\texpect((comp as any).armedBaselineIndex).toBeNull();\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t});\n\n\t\tit(\"same snapshot as baseline and target shows (same snapshot) in subtitle\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1 (selectedIndex=0 in reversed)\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking, s1 is already selected as target\n\t\t\tconst lines = comp.render(WIDTH);\n\t\t\texpect(lines.some((l) => l.includes(\"(same snapshot)\"))).toBe(true);\n\t\t});\n\n\t\tit(\"baseline arming still works after target-picking is cancelled\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tcomp.handleInput(\"\\x1b\"); // cancel — baseline kept\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(1);\n\t\t\t// Can re-arm to a different snapshot\n\t\t\tcomp.handleInput(\"j\"); // move to s0\n\t\t\tcomp.handleInput(\"b\"); // re-arm to s0\n\t\t\texpect((comp as any).armedBaselineIndex).toBe(0);\n\t\t});\n\n\t\tit(\"d enters target-picking in history detail when baseline armed\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"\\r\"); // enter detail on s1\n\t\t\tcomp.handleInput(\"b\"); // arm baseline\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\texpect((comp as any).mode).toBe(\"history_detail\"); // stays in detail mode\n\t\t});\n\n\t\tit(\"Enter confirms target from history detail and opens diff\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"old\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"new\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm s1 as baseline\n\t\t\tcomp.handleInput(\"\\r\"); // enter detail\n\t\t\tcomp.handleInput(\"\\u001b[D]\"); // go to s0\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\tcomp.handleInput(\"\\r\"); // confirm target\n\t\t\texpect((comp as any).mode).toBe(\"history_diff\");\n\t\t\texpect((comp as any).diffBaseIndex).toBe(1); // baseline s1\n\t\t});\n\n\t\tit(\"refresh exits target-picking when no snapshots remain\", () => {\n\t\t\tconst snapshots = [\n\t\t\t\tmakeSnapshot(\"s0\", [{ key: \"a\", value: \"v\" }], \"2024-01-01T00:00:00.000Z\", false),\n\t\t\t\tmakeSnapshot(\"s1\", [{ key: \"b\", value: \"v\" }], \"2024-01-02T00:00:00.000Z\", true),\n\t\t\t];\n\t\t\tconst cb = makeCallbacks({ getMemoryHistory: () => snapshots });\n\t\t\tconst comp = new MemoryEditorComponent(cb, vi.fn());\n\t\t\tcomp.handleInput(\"h\");\n\t\t\tcomp.handleInput(\"b\"); // arm baseline\n\t\t\tcomp.handleInput(\"d\"); // enter target-picking\n\t\t\texpect((comp as any).compareStep).toBe(\"target_picking\");\n\t\t\t// Simulate snapshots becoming empty\n\t\t\tcb.getMemoryHistory = () => [];\n\t\t\tcomp.refresh();\n\t\t\texpect((comp as any).compareStep).toBe(\"none\");\n\t\t});\n\t});\n});\n"]}