Feature: TUI PermissionsScreen Scenarios exercising the PermissionsScreen widget and supporting domain models / service for tool permission requests. # ── DiffDisplayMode enum ────────────────────────────────────── Scenario: DiffDisplayMode has three values When I inspect the DiffDisplayMode enum Then DiffDisplayMode should have values "unified", "side_by_side", "context" # ── FileChangeType enum ─────────────────────────────────────── Scenario: FileChangeType has three values When I inspect the FileChangeType enum Then FileChangeType should have values "M", "A", "D" # ── PermissionDecision enum ─────────────────────────────────── Scenario: PermissionDecision has four values When I inspect the PermissionDecision enum Then PermissionDecision should have values "allow_once", "allow_always", "reject_once", "reject_always" # ── PermissionRequest model ─────────────────────────────────── Scenario: PermissionRequest generates a unified diff Given a PermissionRequest for "src/auth/handler.py" with before and after content When I render the diff in "unified" mode Then the rendered diff should contain "@@" And the rendered diff should contain "+" And the rendered diff should contain "-" Scenario: PermissionRequest generates a side-by-side diff Given a PermissionRequest for "src/auth/handler.py" with before and after content When I render the diff in "side_by_side" mode Then the rendered diff should contain "|" Scenario: PermissionRequest generates a context diff Given a PermissionRequest for "src/auth/handler.py" with before and after content When I render the diff in "context" mode Then the rendered diff should contain "***" Scenario: PermissionRequest unified diff with no changes returns empty string Given a PermissionRequest for "src/empty.py" with identical before and after content When I render the diff in "unified" mode Then the rendered diff should be empty Scenario: PermissionRequest side_by_side diff returns equal lines for identical content Given a PermissionRequest for "src/empty.py" with identical before and after content When I call side_by_side_diff on the request Then both sides should have the same number of lines Scenario: PermissionRequest context_diff with no changes returns empty string Given a PermissionRequest for "src/empty.py" with identical before and after content When I render the diff in "context" mode Then the rendered diff should be empty Scenario: PermissionRequest render_diff dispatches to unified Given a PermissionRequest for "src/auth/handler.py" with before and after content When I render the diff in "unified" mode Then the rendered diff should contain "a/src/auth/handler.py" Scenario: PermissionRequest render_diff dispatches to context Given a PermissionRequest for "src/auth/handler.py" with before and after content When I render the diff in "context" mode Then the rendered diff should contain "a/src/auth/handler.py" # ── ToolPermissionRequest model ─────────────────────────────── Scenario: ToolPermissionRequest is pending when no decision is set Given a ToolPermissionRequest with no decision Then the request should be pending And the request should not be allowed And the request should not be rejected Scenario: ToolPermissionRequest is allowed after allow_once decision Given a ToolPermissionRequest with no decision When I apply decision "allow_once" to the request Then the request should be allowed And the request should not be pending Scenario: ToolPermissionRequest is allowed after allow_always decision Given a ToolPermissionRequest with no decision When I apply decision "allow_always" to the request Then the request should be allowed Scenario: ToolPermissionRequest is rejected after reject_once decision Given a ToolPermissionRequest with no decision When I apply decision "reject_once" to the request Then the request should be rejected And the request should not be pending Scenario: ToolPermissionRequest is rejected after reject_always decision Given a ToolPermissionRequest with no decision When I apply decision "reject_always" to the request Then the request should be rejected Scenario: ToolPermissionRequest apply_decision returns a new instance Given a ToolPermissionRequest with no decision When I apply decision "allow_once" to the request Then the original request should still be pending # ── PermissionRequestService ────────────────────────────────── Scenario: PermissionRequestService enqueues a request Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" Then the service should have 1 pending request Scenario: PermissionRequestService records allow_once decision Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_once" for request "req-1" Then the service should have 0 pending requests And the request "req-1" should have decision "allow_once" Scenario: PermissionRequestService records reject_once decision Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "reject_once" for request "req-1" Then the service should have 0 pending requests And the request "req-1" should have decision "reject_once" Scenario: PermissionRequestService auto-resolves with allow_always session decision Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_always" for request "req-1" And I enqueue a request with id "req-2" for tool "local/file-write" Then the service should have 0 pending requests And the request "req-2" should have decision "allow_always" Scenario: PermissionRequestService auto-resolves with reject_always session decision Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "reject_always" for request "req-1" And I enqueue a request with id "req-2" for tool "local/file-write" Then the service should have 0 pending requests And the request "req-2" should have decision "reject_always" Scenario: PermissionRequestService get_session_decision returns None when not set Given a fresh PermissionRequestService Then the session decision for "local/file-write" should be None Scenario: PermissionRequestService get_session_decision returns decision after allow_always Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_always" for request "req-1" Then the session decision for "local/file-write" should be "allow_always" Scenario: PermissionRequestService clear_session_decision removes the decision Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_always" for request "req-1" And I clear the session decision for "local/file-write" Then the session decision for "local/file-write" should be None Scenario: PermissionRequestService clear_session_decision returns False when not set Given a fresh PermissionRequestService When I clear the session decision for "local/file-write" Then the clear result should be False Scenario: PermissionRequestService record_decision returns None for unknown id Given a fresh PermissionRequestService When I record decision "allow_once" for request "nonexistent" Then the decision result should be None Scenario: PermissionRequestService get returns None for unknown id Given a fresh PermissionRequestService Then getting request "nonexistent" should return None Scenario: PermissionRequestService all_requests returns all requests Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I enqueue a request with id "req-2" for tool "local/file-write" Then the service should have 2 total requests Scenario: PermissionRequestService clear_all removes everything Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_always" for request "req-1" And I clear all requests Then the service should have 0 total requests And the session decision for "local/file-write" should be None # ── PermissionsScreen widget ────────────────────────────────── Scenario: PermissionsScreen loads a request and renders content Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the screen text should contain "Permission Request" And the screen text should contain "Files (3 changes)" And the screen text should contain "local/file-write" Scenario: PermissionsScreen shows the first file selected by default Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the selected index should be 0 And the screen text should contain "❯" Scenario: PermissionsScreen navigate_next moves to next file Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I navigate next on the screen Then the selected index should be 1 Scenario: PermissionsScreen navigate_prev wraps around Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I navigate prev on the screen Then the selected index should be 2 Scenario: PermissionsScreen navigate_next wraps around at end Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I navigate next on the screen And I navigate next on the screen And I navigate next on the screen Then the selected index should be 0 Scenario: PermissionsScreen cycle_diff_mode cycles through modes Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the diff mode should be "unified" When I cycle the diff mode Then the diff mode should be "side_by_side" When I cycle the diff mode Then the diff mode should be "context" When I cycle the diff mode Then the diff mode should be "unified" Scenario: PermissionsScreen set_diff_mode sets mode directly Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I set the diff mode to "side_by_side" Then the diff mode should be "side_by_side" Scenario: PermissionsScreen allow_once records decision Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I press allow_once on the screen Then the screen decision should be "allow_once" Scenario: PermissionsScreen allow_always records decision Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I press allow_always on the screen Then the screen decision should be "allow_always" Scenario: PermissionsScreen reject_once records decision Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I press reject_once on the screen Then the screen decision should be "reject_once" Scenario: PermissionsScreen reject_always records decision Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I press reject_always on the screen Then the screen decision should be "reject_always" Scenario: PermissionsScreen clear resets state Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes And I press allow_once on the screen And I clear the screen Then the screen decision should be None And the screen text should be empty Scenario: PermissionsScreen with no request shows placeholder Given a fresh PermissionsScreen Then the screen text should contain "(no permission request)" Scenario: PermissionsScreen navigate_next does nothing with no request Given a fresh PermissionsScreen When I navigate next on the screen Then the selected index should be 0 Scenario: PermissionsScreen navigate_prev does nothing with no request Given a fresh PermissionsScreen When I navigate prev on the screen Then the selected index should be 0 Scenario: PermissionsScreen shows diff mode in status bar Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the screen text should contain "unified" Scenario: PermissionsScreen shows keyboard bindings in status bar Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the screen text should contain "Allow" And the screen text should contain "Reject" Scenario: PermissionsScreen shows diff content for selected file Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the screen text should contain "file0.py" Scenario: PermissionsScreen navigate_next with empty changes list does nothing Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 0 file changes And I navigate next on the screen Then the selected index should be 0 Scenario: PermissionsScreen navigate_prev with empty changes list does nothing Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 0 file changes And I navigate prev on the screen Then the selected index should be 0 # ── _next_diff_mode helper ──────────────────────────────────── Scenario: _next_diff_mode cycles unified to side_by_side When I call _next_diff_mode with "unified" Then the next diff mode result should be "side_by_side" Scenario: _next_diff_mode cycles side_by_side to context When I call _next_diff_mode with "side_by_side" Then the next diff mode result should be "context" Scenario: _next_diff_mode cycles context back to unified When I call _next_diff_mode with "context" Then the next diff mode result should be "unified" # ── Rendering helpers ───────────────────────────────────────── Scenario: _render_file_list marks selected file with arrow When I render a file list with 2 files and selected index 1 Then the file list should contain "❯" on the second entry Scenario: _render_diff_panel returns placeholder for None change When I render a diff panel with no change Then the diff panel should contain "(no file selected)" Scenario: _render_diff_panel returns placeholder for empty diff When I render a diff panel with an identical-content change Then the diff panel should contain "(no changes)" Scenario: _render_status_bar includes tool name and resource name When I render a status bar for tool "local/file-write" and resource "local/api-service" Then the status bar should contain "local/file-write" And the status bar should contain "local/api-service" # ── PermissionsScreen property accessors ───────────────────── Scenario: PermissionsScreen current_request returns None initially Given a fresh PermissionsScreen Then the screen current_request should be None Scenario: PermissionsScreen current_request returns loaded request Given a fresh PermissionsScreen When I load a ToolPermissionRequest with 3 file changes Then the screen current_request should not be None # ── PermissionRequest side_by_side_diff delete/insert branches ─ Scenario: PermissionRequest side_by_side_diff handles delete-only change Given a PermissionRequest with only deleted lines When I call side_by_side_diff on the request Then the left side should have non-empty lines And the right side should have empty lines for deleted content Scenario: PermissionRequest side_by_side_diff handles insert-only change Given a PermissionRequest with only inserted lines When I call side_by_side_diff on the request Then the right side should have non-empty lines And the left side should have empty lines for inserted content # ── PermissionRequestService clear_session_decision return True ─ Scenario: PermissionRequestService clear_session_decision returns True when decision exists Given a fresh PermissionRequestService When I enqueue a request with id "req-1" for tool "local/file-write" And I record decision "allow_always" for request "req-1" And I clear the session decision for "local/file-write" Then the clear result should be True