feat(tui-v2): polish action strip copy and rebalance dense layout

This commit is contained in:
thePR0M3TH3AN committed 2026-03-02 20:24:58 -05:00
1 parent 64971851ae
commit 0ec4b86076
4 files changed
+306 -34

No files matched your search

+93
View File
@@ -1073,3 +1073,96 @@ Still needed to match mockup behavior intent:
- denser row/column presentation,
- reduced decorative padding.
3. Next implementation slices should target this density path to effectively fit more content per viewport.
## 29) Progress Update (2026-03-03, Slice: Password/Stored Password/Note Card Geometry)
Completed:
1. Password and Stored Password inspector boards now use stronger card sections:
- `Credentials`
- `Quick Actions`
2. Note/Document board now uses the same card section framing:
- `Content`
- `Quick Actions`
3. Increased note preview payload from 120 to 180 chars for better content-card parity in standard density.
Code references:
1. `src/seedpass/tui_v2/app.py` (`_board_card`, password/stored_password board, note/document board)
Regression coverage:
1. Updated note board metadata test to assert card framing.
2. Added password board card-section regression test.
Validation:
1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
2. Result: `57 passed`.
Remaining strict-closeout slice order unchanged:
1. Final UI-board density pass (table rows + lower inspector balance)
2. Final action-strip verb/microcopy polish
## 30) Progress Update (2026-03-03, Slice: UI-Board Density Rebalance)
Completed:
1. Dense/high-res layout now rebalances vertical space by context:
- Idle (no selected entry, no editor, no 2FA board): `top-work=9fr`, `right=3fr`
- Active inspect/edit/2FA: `top-work=8fr`, `right=4fr`
2. Added explicit layout rebalance hook so selection and pane-mode transitions re-apply split without requiring window resize.
3. Rebalance path wired through:
- page render transitions (empty/non-empty)
- entry selection/open flow
- right-pane mode switches (view/edit/2FA)
Code references:
1. `src/seedpass/tui_v2/app.py` (`_update_responsive_layout`, `_refresh_layout_balance`, `_set_right_pane_mode`, `_render_current_page`, `_show_entry`)
Regression coverage:
1. Added dense-mode split behavior test:
- `test_tui2_textual_hires_density_rebalances_idle_vs_selected`
Validation:
1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
2. Result: `58 passed`.
Remaining strict-closeout slice:
1. Final action-strip verb/microcopy polish.
## 31) Progress Update (2026-03-03, Slice: Action-Strip Verb/Microcopy Polish)
Completed:
1. Context row wording standardized to verb-first labels with key hints:
- `Reveal (v)`
- `QR (g)`
- `Edit (e)` / `Edit Doc (e)`
- `Archive (a)`
- `Save (Ctrl+S)`
- `2FA Board (6)`
2. Managed account row now retains explicit session controls while matching the same style:
- `managed-load`
- `managed-exit`
- plus reveal/qr/edit/archive with key hints.
3. Click routing remains compatible after copy update:
- row-2 parser now recognizes both symbol-led and verb-led tokens (`v`/`Reveal`, `g`/`QR`, etc.).
Code references:
1. `src/seedpass/tui_v2/app.py` (`_update_action_strip`, `_action_strip_context_action`)
Regression coverage:
1. Updated action-strip click + context-update tests to assert verb-led labels.
Validation:
1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
2. Result: `58 passed`.
+65
View File
@@ -110,3 +110,68 @@
- Focused and gate suite green:
- `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
- Result: `56 passed`.
## Session Update (2026-03-03, Slice 2)
### UI-board density and hierarchy polish
- Dense high-resolution mode now keeps `#grid-heading` at 4 rows instead of 3 to avoid header/divider clipping in large viewports.
- Refined dense top-left header hierarchy to reduce line noise:
- retained explicit `Fingerprint` and `Path`
- consolidated session/profile/managed state into one line
- kept scope line focused on active filters only
### Regression updates
- Updated `test_tui2_textual_hires_density_compacts_vertical_chrome` to assert the new dense grid-heading height (`4`).
### Verification
- Focused TUI v2 gate remains green:
- `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
- Result: `56 passed`.
## Session Update (2026-03-03, Slice 3)
### Board geometry parity pass
- Added reusable ASCII card renderer for inspector sections (`_board_card`).
- Password and stored-password boards now render framed `Credentials` and `Quick Actions` blocks.
- Note/document boards now render framed `Content` and `Quick Actions` blocks.
- Increased note/document preview length (120 -> 180 chars) for better mockup-like content emphasis.
### Regression coverage
- Extended note metadata board test to assert card framing.
- Added password board card-section test.
### Verification
- Focused TUI v2 gate:
- `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
- Result: `57 passed`.
## Session Update (2026-03-03, Slice 4)
### UI-board density rebalancing
- Added dynamic dense/high-res vertical split:
- idle: `top-work=9fr`, `right=3fr`
- active inspect/edit/2FA: `top-work=8fr`, `right=4fr`
- Added `_refresh_layout_balance()` and invoked it during key state transitions so layout rebalance is state-driven, not resize-driven.
### Regression coverage
- Added `test_tui2_textual_hires_density_rebalances_idle_vs_selected`.
### Verification
- Focused TUI v2 gate:
- `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
- Result: `58 passed`.
## Session Update (2026-03-03, Slice 5)
### Action-strip microcopy polish
- Standardized context action wording to verb-first with key hints across entry kinds.
- Preserved managed account session actions while aligning adjacent wording style.
- Expanded row-2 click parser to recognize verb-led labels so copy changes do not break mouse interactions.
### Regression coverage
- Updated action-strip context tests to match polished copy.
### Verification
- Focused TUI v2 gate:
- `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py`
- Result: `58 passed`.
+82 -29
View File
@@ -884,21 +884,23 @@ def launch_tui2(
kind_l = self._entry_kind(self._selected_entry)
kind = kind_l
if kind_l in {"password", "stored_password"}:
context = "Entry ▣ v Reveal ▣ g QR ▣ e Edit ▣ a Archive"
context = "Entry ▣ Reveal (v) ▣ QR (g) ▣ Edit (e) ▣ Archive (a)"
elif kind_l == "managed_account":
context = "Entry ▣ managed-load ▣ managed-exit ▣ v Reveal (confirm) ▣ g QR ▣ e Edit ▣ a Archive"
context = "Entry ▣ managed-load ▣ managed-exit ▣ Reveal (v confirm) ▣ QR (g) ▣ Edit (e) ▣ Archive (a)"
elif kind_l == "seed":
context = "Entry ▣ v Reveal (confirm) ▣ g QR ▣ e Edit ▣ a Archive"
context = "Entry ▣ Reveal (v confirm) ▣ QR (g) ▣ Edit (e) ▣ Archive (a)"
elif kind_l == "totp":
context = "Entry ▣ 6 2FA Board ▣ v Reveal ▣ g QR ▣ a Archive"
context = "Entry ▣ 2FA Board (6) ▣ Reveal (v) ▣ QR (g) ▣ Archive (a)"
elif kind_l in {"ssh", "pgp"}:
context = "Entry ▣ v Reveal (confirm) ▣ a Archive ▣ copy/export-field"
context = "Entry ▣ Reveal (v confirm) ▣ Archive (a) ▣ copy/export-field"
elif kind_l == "nostr":
context = "Entry ▣ v Reveal ▣ g QR (public/private) ▣ a Archive"
context = "Entry ▣ Reveal (v) ▣ QR (g public/private) ▣ Archive (a)"
elif kind_l in {"document", "note"}:
context = "Entry ▣ e Edit Doc ▣ Ctrl+S Save ▣ a Archive ▣ doc-export"
context = (
"Entry ▣ Edit Doc (e) ▣ Save (Ctrl+S) ▣ Archive (a) ▣ doc-export"
)
elif kind_l == "key_value":
context = "Entry ▣ set-field/clear-field ▣ a Archive ▣ notes/tags"
context = "Entry ▣ set-field/clear-field ▣ Archive (a) ▣ notes/tags"
else:
context = "Select an entry to view context actions."
if kind:
@@ -985,15 +987,15 @@ def launch_tui2(
if not token:
return None
lead = token.split()[0].strip().lower()
if lead == "v":
if lead in {"v", "reveal", "rev"}:
return "reveal"
if lead == "g":
if lead in {"g", "qr"}:
return "qr"
if lead == "e":
if lead in {"e", "edit"}:
return "edit"
if lead == "a":
if lead in {"a", "archive", "arch"}:
return "archive"
if lead == "6":
if lead in {"6", "2fa"}:
return "totp_board"
if lead in {"managed-load", "managed_load", "ml"}:
return "managed_load"
@@ -1243,13 +1245,30 @@ def launch_tui2(
right.styles.height = "5fr"
activity.add_class("hidden")
elif height >= 52:
top_work.styles.height = "8fr"
right.styles.height = "4fr"
if dense_hires and (
self._selected_entry_id is None
and not self.editing_document
and not self.totp_board_open
):
# In dense high-res idle state, bias vertical space to the grid.
top_work.styles.height = "9fr"
right.styles.height = "3fr"
else:
top_work.styles.height = "8fr"
right.styles.height = "4fr"
activity.remove_class("hidden")
activity.styles.height = 4
else:
top_work.styles.height = "7fr"
right.styles.height = "4fr"
if dense_hires and (
self._selected_entry_id is None
and not self.editing_document
and not self.totp_board_open
):
top_work.styles.height = "8fr"
right.styles.height = "3fr"
else:
top_work.styles.height = "7fr"
right.styles.height = "4fr"
activity.remove_class("hidden")
activity.styles.height = 3
@@ -1260,7 +1279,7 @@ def launch_tui2(
ribbon.styles.height = 2
status.styles.height = 2
action_strip.styles.height = 2
grid_heading.styles.height = 3
grid_heading.styles.height = 4
inspector_heading.styles.height = 2
else:
brand.styles.height = 3
@@ -1280,6 +1299,11 @@ def launch_tui2(
except Exception:
pass
def _refresh_layout_balance(self) -> None:
self._update_responsive_layout(
width=self._viewport_width, height=self._viewport_height
)
def _refresh_doc_edit_help(self) -> None:
marker = "*" if self._doc_dirty else "clean"
self.query_one("#doc-edit-help", Static).update(
@@ -1535,8 +1559,15 @@ def launch_tui2(
"SeedPass ◈ TUI v2",
f"Fingerprint: {current_fp}",
f"Path: {self._session_breadcrumb_text()}",
f"FP: {current_fp} | Profiles: {profiles_loaded} | Session: {session_state}",
f"Scope: filter={self.filter_kind} arch={self.archive_scope} links={self.link_relation_filter} managed={managed_state}",
(
"Session: "
f"{session_state} | Profiles: {profiles_loaded} | Managed: {managed_state}"
),
(
"Scope: "
f"filter={self.filter_kind} arch={self.archive_scope} "
f"links={self.link_relation_filter}"
),
self._selected_summary(),
(
f"Results: {len(self._all_results)} | "
@@ -1742,6 +1773,7 @@ def launch_tui2(
else:
self._set_status("No entries match current filter/search")
self._update_filters_panel()
self._refresh_layout_balance()
return
self._result_page, start, end, _total_pages = pagination_window(
@@ -1791,6 +1823,7 @@ def launch_tui2(
pass
self._set_status("Entries loaded. Select an entry to inspect.")
self._update_filters_panel()
self._refresh_layout_balance()
def _entry_detail_text(self, entry: dict[str, Any]) -> str:
payload = truncate_entry_for_display(
@@ -1865,6 +1898,14 @@ def launch_tui2(
f"Notes: {notes_preview}",
]
@staticmethod
def _board_card(title: str, rows: list[str]) -> list[str]:
width = max([len(title), *[len(row) for row in rows], 12])
top = f"+- {title} " + "-" * max(1, width - len(title) - 2) + "+"
body = [f"| {row.ljust(width)} |" for row in rows]
bottom = "+" + "-" * (width + 2) + "+"
return [top, *body, bottom]
def _entry_board_header(self, entry: dict[str, Any]) -> list[str]:
entry_id = (
self._selected_entry_id
@@ -1919,6 +1960,13 @@ def launch_tui2(
archived = "Yes" if bool(entry.get("archived", False)) else "No"
index_num = str(entry.get("index", "(auto)"))
label = str(entry.get("label") or "(untitled)")
credential_rows = [
"Password*: hidden (v reveal, g qr)",
f"Username*: {username}",
f"URL : {url}",
f"Length : {length} chars",
]
operation_rows = [action_row]
lines = [
f"Entry #{entry_id} {label}",
"-" * max(24, len(label) + 10),
@@ -1926,12 +1974,9 @@ def launch_tui2(
f"Kind: {kind} | Modified: {modified} | Archived: {archived}",
f"Index Num*: {index_num}",
"Login Fields",
"Password*: hidden (v reveal, g qr)",
f"Username*: {username}",
f"URL : {url}",
f"Length : {length} chars",
*self._board_card("Credentials", credential_rows),
"Operations",
action_row,
*self._board_card("Quick Actions", operation_rows),
*self._notes_tags_panel_hint(
tags_text=tags_text, notes_text=notes_text
),
@@ -1948,7 +1993,7 @@ def launch_tui2(
content = str(entry.get("content") or "")
preview = content.strip()
if preview:
preview = preview.replace("\n", " ")[:120]
preview = preview.replace("\n", " ")[:180]
else:
preview = "(empty)"
entry_id = (
@@ -1964,6 +2009,10 @@ def launch_tui2(
)
archived = "Yes" if bool(entry.get("archived", False)) else "No"
index_num = str(entry.get("index", "(auto)"))
document_rows = [
f"Content Length: {len(content)} chars",
f"Preview : {preview}",
]
lines = [
f"Entry #{entry_id} {str(entry.get('label') or '(untitled)')}",
"-" * 28,
@@ -1971,10 +2020,9 @@ def launch_tui2(
f"Kind: {kind} | Modified: {modified} | Archived: {archived}",
f"Index Num*: {index_num} | File Type: {file_type}",
"Document Fields",
f"Content Length: {len(content)} chars",
f"Preview : {preview}",
*self._board_card("Content", document_rows),
"Operations",
"▣ Edit ▣ Export",
*self._board_card("Quick Actions", ["▣ Edit ▣ Export"]),
*self._notes_tags_panel_hint(
tags_text=tags_text, notes_text=notes_text
),
@@ -2397,6 +2445,7 @@ def launch_tui2(
self._update_links_panel()
self._update_filters_panel()
self._set_status(f"Selected entry {entry_index}")
self._refresh_layout_balance()
except Exception as exc:
self.query_one("#entry-detail", Static).update(
f"Failed to load entry {entry_index}: {exc}"
@@ -2415,6 +2464,7 @@ def launch_tui2(
retry=lambda: self._show_entry(entry_index),
hint="Press 'x' to retry.",
)
self._refresh_layout_balance()
def _update_links_panel(self) -> None:
if self._service is None or self._selected_entry_id is None:
@@ -2926,6 +2976,7 @@ def launch_tui2(
self.totp_board_open = False
self.editing_document = True
self._update_inspector_heading()
self._refresh_layout_balance()
return
if mode_token == "totp":
editor.add_class("hidden")
@@ -2937,6 +2988,7 @@ def launch_tui2(
self.editing_document = False
self._refresh_totp_board(force_reload=True)
self._update_inspector_heading()
self._refresh_layout_balance()
return
# Default "view": entry board + side panels, no editor/2FA board.
if mode_token != "view":
@@ -2950,6 +3002,7 @@ def launch_tui2(
self.totp_board_open = False
self.editing_document = False
self._update_inspector_heading()
self._refresh_layout_balance()
def _set_palette_visible(self, visible: bool) -> None:
palette = self.query_one("#command-palette", Input)
+66 -5
View File
@@ -1317,7 +1317,7 @@ async def test_tui2_textual_hires_density_compacts_vertical_chrome() -> None:
assert str(ribbon.styles.height) == "2"
assert str(status.styles.height) == "2"
assert str(action_strip.styles.height) == "2"
assert str(grid_heading.styles.height) == "3"
assert str(grid_heading.styles.height) == "4"
assert "Dense" in _widget_text(app, "#action-strip")
assert "S Set" in _widget_text(app, "#action-strip")
assert "Keys:" in _widget_text(app, "#filters")
@@ -1338,6 +1338,37 @@ async def test_tui2_textual_hires_density_compacts_vertical_chrome() -> None:
assert "Dense" not in _widget_text(app, "#action-strip")
@pytest.mark.anyio
async def test_tui2_textual_hires_density_rebalances_idle_vs_selected() -> None:
service = FakeEntryService(
[
{"id": 1, "kind": "password", "label": "Login 1", "length": 16},
{"id": 2, "kind": "document", "label": "Doc 2", "content": "hello"},
]
)
app = _build_app(service)
async with app.run_test() as pilot:
await pilot.pause()
top_work = app.query_one("#top-work")
right = app.query_one("#right")
app._update_responsive_layout(width=220, height=55)
await pilot.pause()
assert str(top_work.styles.height) == "9fr"
assert str(right.styles.height) == "3fr"
app._run_palette_command("open 1")
await pilot.pause()
assert str(top_work.styles.height) == "8fr"
assert str(right.styles.height) == "4fr"
app._set_totp_board_visible(True)
await pilot.pause()
assert str(top_work.styles.height) == "8fr"
assert str(right.styles.height) == "4fr"
@pytest.mark.anyio
async def test_tui2_textual_dense_mode_reduces_inspector_helper_noise() -> None:
service = FakeEntryService(
@@ -1501,6 +1532,8 @@ async def test_tui2_textual_note_and_totp_boards_include_common_metadata() -> No
assert "Kind: document | Modified:" in board
assert "Index Num*:" in board
assert "Document Fields" in board
assert "+- Content " in board
assert "+- Quick Actions " in board
app._run_palette_command("open 2")
await pilot.pause()
@@ -1511,6 +1544,34 @@ async def test_tui2_textual_note_and_totp_boards_include_common_metadata() -> No
assert "2FA Fields" in board
@pytest.mark.anyio
async def test_tui2_textual_password_board_uses_card_sections() -> None:
service = FakeEntryService(
[
{
"id": 1,
"kind": "password",
"label": "Login 1",
"username": "alice",
"url": "https://example.com",
"length": 18,
}
]
)
app = _build_app(service)
async with app.run_test() as pilot:
await pilot.pause()
app._run_palette_command("open 1")
await pilot.pause()
board = _widget_text(app, "#entry-detail")
assert "Password Board" in board
assert "Login Fields" in board
assert "+- Credentials " in board
assert "Password*: hidden (v reveal, g qr)" in board
assert "+- Quick Actions " in board
@pytest.mark.anyio
async def test_tui2_textual_filters_panel_tracks_selected_entry() -> None:
service = FakeEntryService(
@@ -1682,9 +1743,9 @@ async def test_tui2_textual_action_strip_click_routes_to_shortcuts() -> None:
app._run_palette_command("open 1")
await pilot.pause()
action_line = _widget_text(app, "#action-strip").splitlines()[1]
reveal_col = action_line.find("v Reveal")
reveal_col = action_line.find("Reveal (v")
if reveal_col < 0:
reveal_col = action_line.find("v Rev")
reveal_col = action_line.find("Rev (v")
assert reveal_col >= 0
handled = app._handle_action_strip_click(reveal_col + 1, 1)
await pilot.pause()
@@ -1707,13 +1768,13 @@ async def test_tui2_textual_action_strip_context_updates_for_selected_kind() ->
app._run_palette_command("open 1")
await pilot.pause()
action_line = _widget_text(app, "#action-strip").splitlines()[1]
assert ("v Reveal" in action_line) or ("v Rev" in action_line)
assert ("Reveal (v)" in action_line) or ("Rev (v)" in action_line)
assert "(password)" in action_line
app._run_palette_command("open 2")
await pilot.pause()
action_line = _widget_text(app, "#action-strip").splitlines()[1]
assert "e Edit Doc" in action_line
assert "Edit Doc (e)" in action_line
assert "(document)" in action_line