Improve v3 grid navigation and graph browsing

This commit is contained in:
thePR0M3TH3AN committed 2026-03-06 07:10:36 -05:00
1 parent 6e7cd5b734
commit 46ffff577b
11 files changed
+656 -32

No files matched your search

+67
View File
@@ -391,6 +391,73 @@ class TestSearchService:
assert results[0]["linked_hits"][0]["relation"] == "references"
assert results[0]["excerpt"].startswith("SeedPass atlas")
def test_linked_neighbors_and_relation_summary(
self, service, mock_manager, tmp_path
):
mock_manager.fingerprint_dir = tmp_path
mock_manager.entry_manager.search_entries.return_value = [
(1, "Project Plan", None, None, False, EntryType.DOCUMENT),
(2, "Ops Vault", "ops", "https://ops", False, EntryType.PASSWORD),
(3, "Runbook", None, None, False, EntryType.DOCUMENT),
]
entries = {
1: {
"id": 1,
"kind": "document",
"label": "Project Plan",
"links": [{"target_id": 2, "relation": "references"}],
},
2: {
"id": 2,
"kind": "password",
"label": "Ops Vault",
"username": "ops",
"url": "https://ops",
},
3: {
"id": 3,
"kind": "document",
"label": "Runbook",
"links": [{"target_id": 1, "relation": "depends_on"}],
},
}
mock_manager.entry_manager.retrieve_entry.side_effect = lambda eid: entries[eid]
neighbors = service.linked_neighbors(1)
summary = service.relation_summary(1)
assert neighbors == [
{
"entry_id": 3,
"label": "Runbook",
"kind": "document",
"scope_path": f"seed/{tmp_path.name}",
"archived": False,
"direction": "incoming",
"relation": "depends_on",
"note": "",
"tags": [],
"meta": "",
},
{
"entry_id": 2,
"label": "Ops Vault",
"kind": "password",
"scope_path": f"seed/{tmp_path.name}",
"archived": False,
"direction": "outgoing",
"relation": "references",
"note": "",
"tags": [],
"meta": "ops",
},
]
assert summary == {
"incoming": {"depends_on": 1},
"outgoing": {"references": 1},
"combined": {"depends_on": 1, "references": 1},
}
@patch("seedpass.core.api.SemanticIndexService.search")
def test_hybrid_search_combines_semantic_and_filters(
self, semantic_search, service, mock_manager, tmp_path
+167 -3
View File
@@ -27,9 +27,24 @@ from seedpass.tui_v3.screens.security import ChangePasswordScreen
class V3EntryService:
def __init__(self) -> None:
self._entries: dict[int, dict] = {
1: {"id": 1, "kind": "password", "label": "Email", "length": 16},
2: {"id": 2, "kind": "managed_account", "label": "Managed Ops"},
3: {"id": 3, "kind": "nostr", "label": "Agent Nostr"},
1: {
"id": 1,
"kind": "password",
"label": "Email",
"length": 16,
"links": [{"target_id": 2, "relation": "references"}],
},
2: {
"id": 2,
"kind": "managed_account",
"label": "Managed Ops",
},
3: {
"id": 3,
"kind": "nostr",
"label": "Agent Nostr",
"links": [{"target_id": 1, "relation": "uses"}],
},
}
self.copied: list[str] = []
@@ -199,6 +214,7 @@ class V3SearchService:
def __init__(self, entry: V3EntryService) -> None:
self.entry = entry
self.calls: list[dict] = []
self.link_calls: list[dict] = []
def search(
self,
@@ -259,6 +275,62 @@ class V3SearchService:
)
return out
def linked_neighbors(
self,
entry_id: int,
*,
relation: str | None = None,
direction: str = "both",
include_archived: bool = True,
limit: int = 50,
) -> list[dict]:
_ = (include_archived, limit)
self.link_calls.append(
{
"entry_id": entry_id,
"relation": relation,
"direction": direction,
}
)
if int(entry_id) == 1:
rows = [
{
"entry_id": 2,
"label": "Managed Ops",
"kind": "managed_account",
"archived": False,
"direction": "outgoing",
"relation": "references",
},
{
"entry_id": 3,
"label": "Agent Nostr",
"kind": "nostr",
"archived": False,
"direction": "incoming",
"relation": "uses",
},
]
if relation:
return [row for row in rows if row["relation"] == relation]
return rows
return []
def relation_summary(
self,
entry_id: int,
*,
include_archived: bool = True,
) -> dict[str, dict[str, int]]:
_ = include_archived
if int(entry_id) != 1:
return {"incoming": {}, "outgoing": {}, "combined": {}}
return {
"incoming": {"uses": 1},
"outgoing": {"references": 1},
"combined": {"references": 1, "uses": 1},
}
def _build_app() -> tuple[object, V3EntryService, V3VaultService, V3SearchService]:
holder: dict[str, object] = {}
@@ -360,6 +432,98 @@ async def test_tui3_grid_uses_search_service_for_query_and_mode() -> None:
assert table.row_count >= 1
@pytest.mark.anyio
async def test_tui3_main_grid_has_default_focus_and_keyboard_navigation() -> None:
app, _entry, _vault, _search = _build_app()
async with app.run_test() as pilot:
await pilot.pause()
table = app.screen.query_one("#entry-data-table", DataTable)
assert app.focused is table
assert app.selected_entry_id is None
await pilot.press("down")
await pilot.pause()
assert app.focused is table
assert app.selected_entry_id == 2
assert table.cursor_coordinate.row == 1
@pytest.mark.anyio
async def test_tui3_palette_close_restores_focus_to_main_grid() -> None:
app, _entry, _vault, _search = _build_app()
async with app.run_test() as pilot:
await pilot.pause()
table = app.screen.query_one("#entry-data-table", DataTable)
palette_input = app.screen.query_one("#palette-input", Input)
app.action_open_palette()
await pilot.pause()
assert app.focused is palette_input
app.action_open_palette()
await pilot.pause()
assert app.focused is table
await pilot.press("down")
await pilot.pause()
assert app.selected_entry_id == 2
@pytest.mark.anyio
async def test_tui3_grid_toolbar_updates_filter_mode_and_sort() -> None:
app, _entry, _vault, search = _build_app()
async with app.run_test() as pilot:
await pilot.pause()
app.screen.query_one("#grid-filter-keys", Button).press()
await pilot.pause()
assert app.filter_kind == "keys"
app.screen.query_one("#grid-mode-hybrid", Button).press()
await pilot.pause()
assert app.search_mode == "hybrid"
app.screen.query_one("#grid-sort-most_linked", Button).press()
await pilot.pause()
assert app.search_sort == "most_linked"
assert search.calls
assert search.calls[-1]["sort"] == "most_linked"
@pytest.mark.anyio
async def test_tui3_search_query_persists_across_refresh() -> None:
app, _entry, _vault, search = _build_app()
async with app.run_test() as pilot:
await pilot.pause()
app.action_search("Email")
await pilot.pause()
assert app.search_query == "Email"
assert search.calls[-1]["query"] == "Email"
app.action_set_kind_filter("all")
await pilot.pause()
assert search.calls[-1]["query"] == "Email"
@pytest.mark.anyio
async def test_tui3_linked_items_panel_shows_neighbors_and_opens_entry() -> None:
app, _entry, _vault, search = _build_app()
async with app.run_test() as pilot:
await pilot.pause()
app.selected_entry_id = 1
await pilot.pause()
summary = app.screen.query_one("#linked-items-summary", Static)
assert "Outgoing references:1" in str(summary.render())
assert "Incoming uses:1" in str(summary.render())
assert search.link_calls[-1]["entry_id"] == 1
open_button = app.screen.query_one("#linked-open-2", Button)
open_button.press()
await pilot.pause()
assert app.selected_entry_id == 2
@pytest.mark.anyio
async def test_tui3_inspector_stays_collapsed_until_selection_and_can_close() -> None:
app, _entry, _vault, _search = _build_app()