Codex System Handbook

Bottom-pane composer, popups, and mention input

stage-10.1.242 files

This stage is the control center for the terminal app’s bottom pane, where the user types messages, chooses suggestions, answers prompts, and changes small settings. It is part of the main work loop. The bottom pane decides whether each key press belongs to the chat composer, a popup, a modal form, or an interrupt action.

At its core are the shared bottom-pane view contract, the pane controller, the reusable composer widget, and the text area. These keep typing, cursor movement, wrapping, paste detection, Vim-style edits, attachments, draft text, footer hints, and popup state working together. History files add Up/Down recall and Ctrl+R search. Slash-command files parse commands like “/status”, decide which ones are allowed, show suggestions, and run the chosen command.

Mention and search pieces power “@” style insertion. They build searchable candidates from files, skills, and plugins, filter them, and draw the popup and footer. Reusable picker views provide lists, multi-select menus, and search panels. The remaining popup views handle settings, skills, memories, hooks, custom prompts, feedback, app-link requests, server questions, and user-input overlays, all using the same bottom-pane machinery.

Files in this stage42

Bottom-pane host and view contract

These files define the bottom-pane host, its shared view interface, and the public wrapper that exposes composer behavior outside the chat UI.

tui/src/bottom_pane/bottom_pane_view.rssource ↗
data_modelcross-cutting UI interaction

The bottom pane is the part of the terminal interface that temporarily asks the user for something or shows a small interactive view. This file defines the shared rules for those views. Think of it like a standard plug shape: any bottom-pane view can be plugged into the same holder as long as it follows this shape.

The main piece is the BottomPaneView trait. A trait is Rust's way of saying “any type that promises to provide these abilities.” Every bottom-pane view must also be Renderable, meaning it knows how to draw itself. The trait then gives optional hooks for common UI events: key presses, pasted text, Ctrl-C or Esc cancellation, time-based redraws, and outside requests that arrive while the view is open.

Most methods have safe default behavior: do nothing, return “not complete,” or say “I did not consume this request.” Individual views override only the parts they need. For example, a confirmation dialog may override approval handling and completion status, while a text-entry form may override paste handling.

This file matters because it keeps the bottom pane from needing special-case code for every kind of view. Without it, the UI would need many separate paths for drawing, dismissing, refreshing, and routing input to each modal screen.

Function details20
BottomPaneView::handle_key_event22–22 ↗
fn handle_key_event(&mut self, _key_event: KeyEvent)

Purpose: Receives a keyboard event while this bottom-pane view is active. The default version ignores the key, so only views that care about typing or shortcuts need to override it.

Data flow: A key event comes in from the terminal. By default, the method does not read or change any view state and returns nothing. A real view can override it to turn keys into changes, such as moving a selection or accepting a form.

Call relations: The bottom pane calls this when the active view should receive a key press. Implementing views can use it as their main input point, while views that do not need keyboard handling inherit the no-op behavior.

BottomPaneView::is_complete25–27 ↗
fn is_complete(&self) -> bool

Purpose: Tells the bottom pane whether this view has finished and should be removed. The default answer is no, meaning the view stays open unless it overrides this.

Data flow: The bottom pane asks for completion status. The default method reads no state and returns false. Views that track whether the user accepted, cancelled, or finished something can override it to return true at the right time.

Call relations: The bottom pane checks this after input or timed updates. It works together with completion so the surrounding UI can know both that the view is done and why it ended.

BottomPaneView::completion30–32 ↗
fn completion(&self) -> Option<ViewCompletion>

Purpose: Reports why a finished view ended, such as accepted or cancelled. The default returns no reason because the default view never claims to be complete.

Data flow: The caller asks for a completion reason. The default method reads nothing and returns None. A view that can finish should override this to return a ViewCompletion value once it is done.

Call relations: The bottom pane uses this after is_complete indicates the view is finished. Parent views or the bottom-pane controller can then decide whether to continue, dismiss, or clean up based on the reason.

BottomPaneView::dismiss_after_child_accept35–37 ↗
fn dismiss_after_child_accept(&self) -> bool

Purpose: Says whether this view should also close after a child view is accepted. The default is no, so parent views normally remain open.

Data flow: The bottom pane asks whether accepting a nested child view should remove this parent view. The default reads no state and returns false. Views that start a child flow can override this when accepting the child should also dismiss the parent.

Call relations: This is used in multi-step bottom-pane flows, where one view may open another. After the child completes successfully, the bottom pane can ask the parent whether it should disappear too.

BottomPaneView::clear_dismiss_after_child_accept40–40 ↗
fn clear_dismiss_after_child_accept(&mut self)

Purpose: Clears any temporary marker saying the parent should close after a child is accepted. The default does nothing because most views do not use that marker.

Data flow: The bottom pane calls this after a child view is cancelled. The default changes nothing and returns nothing. A parent view that tracks child-flow cleanup can override it to reset its internal state.

Call relations: This pairs with dismiss_after_child_accept. If a child view is cancelled instead of accepted, the bottom pane gives the parent a chance to undo any pending dismissal plan.

BottomPaneView::view_id43–45 ↗
fn view_id(&self) -> Option<&'static str>

Purpose: Provides a stable name for a view that may need to be refreshed from outside while it is open. The default has no identifier.

Data flow: The caller asks for a view identifier. The default reads no state and returns None. Views that need external refreshes can override it with a fixed string ID.

Call relations: The bottom pane or other UI coordination code can use this to recognize that a refreshed view is the same kind of screen as the one already open. Views that do not need that recognition skip it.

BottomPaneView::selected_index49–51 ↗
fn selected_index(&self) -> Option<usize>

Purpose: Reports the currently selected item in list-style views. This helps preserve the user's place when a list is refreshed.

Data flow: The caller asks for a selected item number. The default returns None, meaning there is no list selection to preserve. A list view can override it to return the current item index.

Call relations: This supports external refreshes for views that show lists. When the view is rebuilt or updated, surrounding code can use the old selected index to keep the cursor from jumping unexpectedly.

BottomPaneView::active_tab_id55–57 ↗
fn active_tab_id(&self) -> Option<&str>

Purpose: Reports which tab is active in a tabbed bottom-pane view. The default returns no tab because most views are not tabbed.

Data flow: The caller asks for the active tab identifier. The default reads nothing and returns None. A tabbed view can override it to return the current tab's stable ID.

Call relations: This is another preservation hook for refreshed views. If a tabbed list is updated from outside, the bottom pane can keep the user on the same tab instead of resetting the screen.

BottomPaneView::on_ctrl_c60–62 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Gives the active view a chance to respond to Ctrl-C, which usually means cancellation. The default says the view did not handle it.

Data flow: A Ctrl-C event reaches the active view. The default does not change state and returns CancellationEvent::NotHandled. A view can override it to cancel itself, reject a prompt, or otherwise mark the event as handled.

Call relations: The bottom pane calls this when Ctrl-C is pressed. If the view does not handle it, the broader application can continue with its normal cancellation behavior.

BottomPaneView::prefer_esc_to_handle_key_event66–68 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

Purpose: Says whether the Esc key should be treated like an ordinary key for this view instead of following the Ctrl-C-style cancellation path. The default says no.

Data flow: The bottom pane asks how Esc should be routed. The default returns false, so Esc can follow the normal cancellation route. A view can override this when Esc has a special local meaning, such as closing a menu inside the view.

Call relations: This guides the bottom pane before it dispatches an Esc key. It decides whether Esc goes to handle_key_event or through cancellation handling.

BottomPaneView::handle_paste72–74 ↗
fn handle_paste(&mut self, _pasted: String) -> bool

Purpose: Lets a view process pasted text. The default ignores pasted text and says no redraw is needed.

Data flow: A pasted string comes in. The default discards it, changes nothing, and returns false. Text-entry views can override it to insert the pasted content and return true when the screen should be redrawn.

Call relations: The bottom pane calls this when paste input arrives while a modal view is active. It allows views that reuse text composers to behave like the main chat composer.

BottomPaneView::flush_paste_burst_if_due80–82 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

Purpose: Allows a view to finish processing a burst of pasted text after a short timing window. The default has no paste burst to flush.

Data flow: The bottom pane asks whether pending paste-burst state should be flushed. The default changes nothing and returns false. A view with paste buffering can override it to finalize the paste and request a redraw if its state changed.

Call relations: This supports the same paste behavior used by the primary composer. The bottom pane can periodically call it so modal text-entry views clean up paste state at the right time.

BottomPaneView::is_in_paste_burst88–90 ↗
fn is_in_paste_burst(&self) -> bool

Purpose: Tells whether the view is temporarily holding pasted text in a burst window. The default says no.

Data flow: The bottom pane asks whether paste-burst state is active. The default reads nothing and returns false. A view that buffers paste input can override it to return true while waiting to flush.

Call relations: The bottom pane uses this to decide whether to schedule a short delayed redraw. It works with flush_paste_burst_if_due to make pasted content appear smoothly.

BottomPaneView::pre_draw_tick96–98 ↗
fn pre_draw_tick(&mut self, _now: Instant) -> bool

Purpose: Lets a view update time-based state immediately before it is drawn. The default does nothing.

Data flow: The current time is passed in before rendering. The default ignores it, changes no state, and returns false. Views with timers, animations, or delayed completion can override it and return true if something changed.

Call relations: The bottom pane calls this just before drawing the active view. If it reports a change, the pane can redraw or notice that the view has completed.

BottomPaneView::try_consume_approval_request102–107 ↗
fn try_consume_approval_request(
        &mut self,
        request: ApprovalRequest,
    ) -> Option<ApprovalRequest>

Purpose: Gives the active view first chance to take over an approval request. The default does not consume it and hands the same request back.

Data flow: An approval request enters the method. The default wraps and returns the original request unchanged, meaning it was not used. A suitable approval view can override this to absorb the request into its own state and return nothing.

Call relations: When an approval request arrives while a bottom-pane view is open, the bottom pane can ask the current view if it can deal with it. If the request is returned, other code can route it elsewhere.

BottomPaneView::try_consume_user_input_request111–116 ↗
fn try_consume_user_input_request(
        &mut self,
        request: ToolRequestUserInputParams,
    ) -> Option<ToolRequestUserInputParams>

Purpose: Gives the active view a chance to handle a tool's request for user input. The default declines by returning the original request.

Data flow: A user-input request comes in. The default returns that same request unchanged inside Some, signaling that it was not consumed. A form-like view can override it to store or display the request and return None.

Call relations: This is used when external tool interaction asks the user for information while a view is active. The bottom pane checks whether the current view can absorb the request before creating or routing another UI flow.

BottomPaneView::try_consume_mcp_server_elicitation_request120–125 ↗
fn try_consume_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    ) -> Option<McpServerElicitationFormRequest>

Purpose: Gives the active view a chance to handle an MCP server elicitation form request. MCP here means Model Context Protocol, a way for external servers to ask the app for structured interaction.

Data flow: A server form request enters the method. The default returns the original request unchanged, meaning this view did not use it. A view built for that form can override the method to consume the request and update its displayed state.

Call relations: The bottom pane calls this when an MCP server asks for information and a view is already open. If the current view cannot use the request, the returned value can be handled by another part of the UI.

BottomPaneView::dismiss_app_server_request130–132 ↗
fn dismiss_app_server_request(&mut self, _request: &ResolvedAppServerRequest) -> bool

Purpose: Lets a view close or update itself when a request it was showing has already been resolved somewhere else. The default says nothing changed.

Data flow: A resolved app-server request is passed in. The default ignores it, changes no state, and returns false. A view showing that request can override this to dismiss itself or remove the resolved item, then return true.

Call relations: This matters when more than one client or UI surface can answer the same request. The bottom pane can tell the active view that the request is no longer pending so the screen does not keep asking for an already-finished action.

BottomPaneView::terminal_title_requires_action139–141 ↗
fn terminal_title_requires_action(&self) -> bool

Purpose: Reports whether this view is blocking progress until the user responds. The default says no action is required.

Data flow: The UI asks whether the terminal title should show an action-required state. The default returns false. Views that are waiting for approval or input can override it to return true.

Call relations: The terminal title code can use this while the view is active. If a view requires action, the user can see from the terminal tab title that Codex is waiting on them instead of simply working.

BottomPaneView::next_frame_delay144–146 ↗
fn next_frame_delay(&self) -> Option<std::time::Duration>

Purpose: Tells the bottom pane when this view next needs a time-based redraw. The default asks for no scheduled frame.

Data flow: The bottom pane asks for the next redraw delay. The default returns None, meaning there is no timer to set. Animated or time-sensitive views can override it with a duration, such as “redraw in 100 milliseconds.”

Call relations: This works with pre_draw_tick. A view can request a future frame, and when that time arrives the bottom pane can tick the view and redraw if needed.

tui/src/bottom_pane/mod.rssource ↗
orchestrationmain loop and request handling

The bottom pane is the part of the terminal interface where the user types and where short, urgent UI elements appear. Think of it like the dashboard at the bottom of a car: it has the steering wheel for input, warning lights for status, and temporary pop-up prompts when the driver must decide something. Without this file, the app would not know how to route typing, paste events, Ctrl-C, approval prompts, image attachments, or task status updates in the lower part of the screen.

The main object, BottomPane, owns a ChatComposer for normal typing and a stack of temporary views for modals and selection lists. When a modal is present, it gets first chance at keys and paste text. When no modal is present, input goes to the composer. The file also keeps status widgets in sync with running tasks, delays approval prompts while the user is actively typing, and asks the terminal to redraw whenever visible state changes.

Rendering is built from smaller pieces: status indicator, execution footer, pending approvals, queued input preview, and the composer. If a modal is active, it replaces that normal layout. The file also includes tests and small test-only fake views that check important behavior, such as Ctrl-C not accidentally clearing drafts and approval prompts waiting until typing has paused. The bottom pane is like the control desk at the bottom of the terminal. It has to let the user type a message, but it also has to temporarily show other things: approval questions, selection lists, command popups, skill popups, running-task status, queued messages, and attached images. This is easy to get wrong because several things can be active at once. For example, pressing Escape might mean “close this popup,” “cancel this list,” or “interrupt the running task,” depending on what is currently visible.

The tests in this part of the file check those edge cases. They build a BottomPane with a fake event channel, simulate user actions such as typing, pasting, pressing Enter, pressing Escape, or waiting for a typing-idle delay, and then inspect the pane’s state or rendered text. They also check that the pane sends the right app events, such as approving a command or interrupting a task, only when it should.

A major theme is protecting the user’s intent. Popups should close without accidentally killing work. Denied approvals should return the user to a useful composer/status view. Remote images should appear before submission and disappear after submission. Completed modal flows should clean up correctly without leaving hidden stale views behind.

Function details196
BottomPane::new255–299 ↗
fn new(params: BottomPaneParams) -> Self

Purpose: Creates a fresh bottom pane with its composer, key bindings, status placeholders, and modal stack ready to use.

Data flow: It receives setup values such as event sender, redraw scheduler, focus state, paste settings, placeholder text, animation setting, and skill mentions. It builds a ChatComposer, connects it to redraw requests and default key bindings, stores the rest of the UI state, and returns a ready BottomPane.

Call relations: Higher-level UI setup and tests call this when they need a bottom pane. It creates the composer and supporting widgets that nearly all later bottom-pane actions use.

Call graph: calls 5 internal fn (new, new, new, new, defaults); called by 24 (composer_shown_after_denied_while_task_running, ctrl_c_cancels_history_search_without_clearing_draft_or_showing_quit_hint, ctrl_c_on_modal_consumes_without_showing_quit_hint, drain_pending_submission_state_clears_remote_image_urls, esc_interrupts_running_task_when_no_popup, esc_release_after_dismissing_agent_picker_does_not_interrupt_task, esc_routes_to_handle_key_event_when_requested, esc_with_agent_command_without_popup_does_not_interrupt_task, esc_with_skill_popup_does_not_interrupt_task, esc_with_slash_command_popup_does_not_interrupt_task (+14 more)); 2 external calls (new, new).

BottomPane::set_skills301–304 ↗
fn set_skills(&mut self, skills: Option<Vec<SkillMetadata>>)

Purpose: Updates the list of skills that can be mentioned from the composer.

Data flow: It receives an optional list of skill metadata, passes it into the composer, then asks the screen to redraw so the mention UI can reflect the change.

Call relations: This is a small forwarding step from application state into the composer, followed by a visual refresh.

Call graph: calls 2 internal fn (request_redraw, set_skill_mentions).

BottomPane::set_image_paste_enabled309–312 ↗
fn set_image_paste_enabled(&mut self, enabled: bool)

Purpose: Turns image pasting support in the composer on or off.

Data flow: It receives a boolean, gives it to the composer, and requests a redraw because visible paste affordances may change.

Call relations: The pane keeps the composer as the source of truth for input behavior while handling redraw scheduling itself.

Call graph: calls 2 internal fn (request_redraw, set_image_paste_enabled).

BottomPane::set_connectors_snapshot314–317 ↗
fn set_connectors_snapshot(&mut self, snapshot: Option<ConnectorsSnapshot>)

Purpose: Updates connector mention data used by the composer.

Data flow: It receives an optional connector snapshot, stores it through the composer, and redraws the bottom pane.

Call relations: Application code can refresh connector data here; the composer then uses it when offering mention choices.

Call graph: calls 2 internal fn (request_redraw, set_connector_mentions).

BottomPane::set_plugin_mentions319–322 ↗
fn set_plugin_mentions(&mut self, plugins: Option<Vec<PluginCapabilitySummary>>)

Purpose: Updates the plugins that can be mentioned while typing.

Data flow: It receives an optional plugin list, passes it to the composer, and schedules a redraw.

Call relations: Plugin-loading flows call this after plugin mention data is available, so the composer can show up-to-date mention suggestions.

Call graph: calls 2 internal fn (request_redraw, set_plugin_mentions); called by 2 (on_plugin_mentions_loaded, refresh_plugin_mentions).

BottomPane::set_plugins_command_enabled324–327 ↗
fn set_plugins_command_enabled(&mut self, enabled: bool)

Purpose: Enables or disables the slash command related to plugins.

Data flow: It receives a boolean, updates the composer command availability, and redraws.

Call relations: This lets feature flags or runtime state control which composer commands the user can invoke.

Call graph: calls 2 internal fn (request_redraw, set_plugins_command_enabled).

BottomPane::set_token_activity_command_enabled329–332 ↗
fn set_token_activity_command_enabled(&mut self, enabled: bool)

Purpose: Enables or disables the command for showing token activity.

Data flow: It passes the enabled flag into the composer and requests a redraw.

Call relations: The bottom pane exposes this as a simple switch while the composer owns command behavior.

Call graph: calls 2 internal fn (request_redraw, set_token_activity_command_enabled).

BottomPane::set_mentions_v2_enabled334–337 ↗
fn set_mentions_v2_enabled(&mut self, enabled: bool)

Purpose: Switches the newer mention system on or off for the composer.

Data flow: It receives a boolean, forwards it to the composer, and redraws.

Call relations: Feature configuration can call this to change how mentions behave without touching composer internals directly.

Call graph: calls 2 internal fn (request_redraw, set_mentions_v2_enabled).

BottomPane::take_mention_bindings339–341 ↗
fn take_mention_bindings(&mut self) -> Vec<MentionBinding>

Purpose: Removes and returns mention bindings currently held by the composer.

Data flow: It asks the composer for its stored mention bindings; the composer clears them and returns the list.

Call relations: Submission cleanup calls this when it needs to drain state that belongs to the message being sent.

Call graph: calls 1 internal fn (take_mention_bindings); called by 1 (drain_pending_submission_state).

BottomPane::take_recent_submission_mention_bindings343–345 ↗
fn take_recent_submission_mention_bindings(&mut self) -> Vec<MentionBinding>

Purpose: Returns mention bindings tied to the most recent submitted message and clears that pending record.

Data flow: It delegates to the composer, which gives back the recent bindings and forgets them.

Call relations: Used during pending submission cleanup so old mention data is not accidentally reused.

Call graph: calls 1 internal fn (take_recent_submission_mention_bindings); called by 1 (drain_pending_submission_state).

BottomPane::record_pending_slash_command_history351–353 ↗
fn record_pending_slash_command_history(&mut self)

Purpose: Tells the composer to remember a pending slash command in history.

Data flow: It has no external input. It asks the composer to record its current pending command history state.

Call relations: This is a narrow bridge from surrounding app flow into composer history behavior.

Call graph: calls 1 internal fn (record_pending_slash_command_history).

BottomPane::set_keymap_bindings362–372 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

Purpose: Replaces the active keyboard shortcuts used by the bottom pane and its composer.

Data flow: It receives a runtime keymap, stores a copy, updates the composer, updates interrupt shortcut hints in preview and status widgets, then redraws.

Call relations: When key bindings change, this keeps every visible bottom-pane hint aligned with the new shortcut rules.

Call graph: calls 4 internal fn (request_redraw, set_keymap_bindings, set_interrupt_binding, primary_binding); 1 external calls (clone).

BottomPane::drain_pending_submission_state375–380 ↗
fn drain_pending_submission_state(&mut self)

Purpose: Clears leftover attachments, remote image URLs, and mention bindings from a pending submission.

Data flow: It calls several take-style methods that return and clear data, then discards the returned values.

Call relations: Submission flows call this as a cleanup step so stale data from an abandoned or completed submission cannot leak into the next one.

Call graph: calls 4 internal fn (take_mention_bindings, take_recent_submission_images_with_placeholders, take_recent_submission_mention_bindings, take_remote_image_urls).

BottomPane::set_collaboration_modes_enabled382–385 ↗
fn set_collaboration_modes_enabled(&mut self, enabled: bool)

Purpose: Turns collaboration-mode command support on or off in the composer.

Data flow: It forwards the boolean to the composer and redraws.

Call relations: This exposes collaboration feature availability to the typing UI.

Call graph: calls 2 internal fn (request_redraw, set_collaboration_modes_enabled).

BottomPane::set_connectors_enabled387–389 ↗
fn set_connectors_enabled(&mut self, enabled: bool)

Purpose: Enables or disables connector-related composer behavior.

Data flow: It receives a boolean and forwards it to the composer. It does not request a redraw here.

Call relations: The pane acts as the public access point, while the composer decides what the flag means for input.

Call graph: calls 1 internal fn (set_connectors_enabled).

BottomPane::set_windows_degraded_sandbox_active392–395 ↗
fn set_windows_degraded_sandbox_active(&mut self, enabled: bool)

Purpose: Shows or hides composer UI related to a degraded Windows sandbox mode.

Data flow: It passes the flag to the composer and redraws so the user can see the changed state.

Call relations: Runtime environment checks can call this to reflect sandbox limitations in the input area.

Call graph: calls 2 internal fn (request_redraw, set_windows_degraded_sandbox_active).

BottomPane::set_collaboration_mode_indicator397–403 ↗
fn set_collaboration_mode_indicator(
        &mut self,
        indicator: Option<CollaborationModeIndicator>,
    )

Purpose: Sets the small visual indicator for the current collaboration mode.

Data flow: It receives an optional indicator, gives it to the composer, and redraws.

Call relations: This keeps the composer display in sync with collaboration state selected elsewhere.

Call graph: calls 2 internal fn (request_redraw, set_collaboration_mode_indicator).

BottomPane::set_goal_status_indicator405–408 ↗
fn set_goal_status_indicator(&mut self, indicator: Option<GoalStatusIndicator>)

Purpose: Sets the visual indicator for goal status in the composer.

Data flow: It receives an optional goal indicator, forwards it, and redraws.

Call relations: Goal-related application state uses this to surface progress or mode information in the bottom pane.

Call graph: calls 2 internal fn (request_redraw, set_goal_status_indicator).

BottomPane::set_ide_context_active410–413 ↗
fn set_ide_context_active(&mut self, active: bool)

Purpose: Marks whether IDE context is currently active in the composer UI.

Data flow: It receives a boolean, updates the composer, and requests a redraw.

Call relations: IDE integration state can call this so the user sees whether editor context is being used.

Call graph: calls 2 internal fn (request_redraw, set_ide_context_active).

BottomPane::set_personality_command_enabled415–418 ↗
fn set_personality_command_enabled(&mut self, enabled: bool)

Purpose: Enables or disables the personality slash command.

Data flow: It sends the enabled flag to the composer and redraws.

Call relations: Feature or account state can use this to change available commands.

Call graph: calls 2 internal fn (request_redraw, set_personality_command_enabled).

BottomPane::set_service_tier_commands_enabled420–423 ↗
fn set_service_tier_commands_enabled(&mut self, enabled: bool)

Purpose: Enables or disables service-tier commands in the composer.

Data flow: It forwards the flag to the composer and redraws.

Call relations: This lets app state control whether service-tier options are visible or usable.

Call graph: calls 2 internal fn (request_redraw, set_service_tier_commands_enabled).

BottomPane::set_service_tier_commands425–428 ↗
fn set_service_tier_commands(&mut self, commands: Vec<ServiceTierCommand>)

Purpose: Provides the composer with the current service-tier command choices.

Data flow: It receives a list of commands, passes them to the composer, and redraws.

Call relations: When available service tiers change, this updates the command menu shown while typing.

Call graph: calls 2 internal fn (request_redraw, set_service_tier_commands).

BottomPane::set_goal_command_enabled430–433 ↗
fn set_goal_command_enabled(&mut self, enabled: bool)

Purpose: Enables or disables the goal-related slash command.

Data flow: It forwards the boolean to the composer and redraws.

Call relations: Goal feature setup can call this to expose or hide the command.

Call graph: calls 2 internal fn (request_redraw, set_goal_command_enabled).

BottomPane::set_side_conversation_active435–438 ↗
fn set_side_conversation_active(&mut self, active: bool)

Purpose: Marks whether the composer is currently in a side conversation.

Data flow: It passes the active flag to the composer and redraws.

Call relations: Conversation-mode orchestration calls this so the input area shows the right context.

Call graph: calls 2 internal fn (request_redraw, set_side_conversation_active).

BottomPane::set_placeholder_text440–443 ↗
fn set_placeholder_text(&mut self, placeholder: String)

Purpose: Changes the placeholder text shown when the composer is empty.

Data flow: It receives the new placeholder string, stores it in the composer, and redraws.

Call relations: Other flows use this when the input box needs different guidance for the user.

Call graph: calls 2 internal fn (request_redraw, set_placeholder_text).

BottomPane::set_queued_message_edit_binding447–450 ↗
fn set_queued_message_edit_binding(&mut self, binding: Option<KeyBinding>)

Purpose: Changes the shortcut shown for editing queued messages.

Data flow: It receives an optional key binding, stores it in the pending input preview, and redraws.

Call relations: Queue-related UI uses this so shortcut hints match the current keymap.

Call graph: calls 2 internal fn (request_redraw, set_edit_binding).

BottomPane::set_vim_enabled452–455 ↗
fn set_vim_enabled(&mut self, enabled: bool)

Purpose: Turns Vim-style editing mode on or off in the composer.

Data flow: It forwards the enabled flag to the composer and redraws.

Call relations: Settings or commands call this to update editor behavior and visible mode state.

Call graph: calls 2 internal fn (request_redraw, set_vim_enabled).

BottomPane::toggle_vim_enabled457–461 ↗
fn toggle_vim_enabled(&mut self) -> bool

Purpose: Flips Vim-style editing mode and reports the new state.

Data flow: It asks the composer to toggle the setting, requests a redraw, and returns whether Vim mode is now enabled.

Call relations: The command that toggles Vim mode calls this, then can notify the user of the resulting state.

Call graph: calls 2 internal fn (request_redraw, toggle_vim_enabled); called by 1 (toggle_vim_mode_and_notify).

BottomPane::status_widget463–465 ↗
fn status_widget(&self) -> Option<&StatusIndicatorWidget>

Purpose: Returns the current status indicator widget, if one exists.

Data flow: It reads the optional status field and returns a shared reference without changing anything.

Call relations: Rendering or inspection code can use this to look at task status UI.

BottomPane::skills467–469 ↗
fn skills(&self) -> Option<&Vec<SkillMetadata>>

Purpose: Returns the skills currently known by the composer.

Data flow: It asks the composer for its optional skills list and returns that reference.

Call relations: Other code can inspect available skill mentions through the bottom pane.

Call graph: calls 1 internal fn (skills).

BottomPane::plugins471–473 ↗
fn plugins(&self) -> Option<&Vec<PluginCapabilitySummary>>

Purpose: Returns the plugin mention data currently known by the composer.

Data flow: It asks the composer for its optional plugin list and returns it.

Call relations: Plugin mention flows use this to check or reuse the current plugin data.

Call graph: calls 1 internal fn (plugins); called by 2 (on_plugin_mentions_loaded, plugins_for_mentions).

BottomPane::context_window_percent476–478 ↗
fn context_window_percent(&self) -> Option<i64>

Purpose: Reports the stored context-window usage percentage, if known.

Data flow: It reads the cached percentage and returns it.

Call relations: Token information displays can query this simple value from the pane.

BottomPane::context_window_used_tokens481–483 ↗
fn context_window_used_tokens(&self) -> Option<i64>

Purpose: Reports the stored number of used context-window tokens, if known.

Data flow: It reads the cached token count and returns it.

Call relations: Token information displays can query this alongside the percentage.

BottomPane::active_view485–487 ↗
fn active_view(&self) -> Option<&dyn BottomPaneView>

Purpose: Finds the currently active modal or popup view, if any.

Data flow: It looks at the last item in the view stack and returns it as a generic bottom-pane view reference.

Call relations: Rendering, frame scheduling, and title-state checks use this to treat the topmost modal as the active UI.

Call graph: called by 3 (as_renderable_with_composer_right_reserve, schedule_active_view_frame, terminal_title_requires_action).

BottomPane::push_view489–493 ↗
fn push_view(&mut self, view: Box<dyn BottomPaneView>)

Purpose: Shows a new modal or popup by putting it on top of the view stack.

Data flow: It receives a boxed view, pushes it onto the stack, schedules any animation frame it needs, and redraws.

Call relations: All flows that open approval prompts, selection lists, and other overlays route through this helper.

Call graph: calls 2 internal fn (request_redraw, schedule_active_view_frame); called by 8 (maybe_show_delayed_approval_requests_at, push_approval_request, push_mcp_server_elicitation_request, push_user_input_request, replace_active_views_with_selection_view, replace_selection_view_if_active, show_selection_view, show_view).

BottomPane::pop_active_view_with_completion495–516 ↗
fn pop_active_view_with_completion(&mut self, completion: Option<ViewCompletion>)

Purpose: Closes the top modal and reacts to whether it was accepted or cancelled.

Data flow: It removes the active view. If a child view was accepted, it may also remove parent views that asked to disappear after child acceptance; if cancelled, it clears that parent-dismiss flag. It then runs the stack-depth cleanup path.

Call relations: Key handling and Ctrl-C call this when a modal finishes, so follow-up cleanup is consistent.

Call graph: calls 1 internal fn (on_view_stack_depth_decreased); called by 2 (handle_key_event, on_ctrl_c).

BottomPane::on_view_stack_depth_decreased518–522 ↗
fn on_view_stack_depth_decreased(&mut self)

Purpose: Runs cleanup when one or more modal views have been removed.

Data flow: It checks whether the view stack is now empty. If so, it calls the active-view-complete cleanup.

Call relations: Modal dismissal paths use this to resume normal composer input only after all modals are gone.

Call graph: calls 1 internal fn (on_active_view_complete); called by 2 (dismiss_app_server_request, pop_active_view_with_completion).

BottomPane::approval_prompt_delay_remaining524–531 ↗
fn approval_prompt_delay_remaining(&self, now: Instant) -> Option<Duration>

Purpose: Calculates how much longer an approval prompt should wait because the user was recently typing.

Data flow: It reads the last composer activity time and the current time. If the idle delay has not passed, it returns the remaining duration; otherwise it returns nothing.

Call relations: Approval prompt scheduling uses this to avoid popping a modal in front of someone mid-typing.

Call graph: called by 3 (maybe_show_delayed_approval_requests_at, push_approval_request, record_composer_activity_at).

BottomPane::record_composer_activity_at533–540 ↗
fn record_composer_activity_at(&mut self, now: Instant)

Purpose: Records that the user just typed or edited in the composer.

Data flow: It stores the activity time. If approval requests are waiting, it schedules a redraw for the moment they may become eligible to show.

Call relations: Key and paste handling call this so delayed approval prompts follow the latest typing activity.

Call graph: calls 2 internal fn (approval_prompt_delay_remaining, request_redraw_in); called by 2 (handle_key_event, handle_paste); 1 external calls (is_empty).

BottomPane::maybe_show_delayed_approval_requests_at542–569 ↗
fn maybe_show_delayed_approval_requests_at(&mut self, now: Instant)

Purpose: Shows delayed approval prompts once the user has been idle long enough.

Data flow: It checks for queued approval requests and active modals. If still within the delay, it schedules a future redraw. Otherwise it builds an approval overlay from the queued requests, pauses the status timer, and pushes the modal.

Call relations: Pre-draw ticks and approval insertion call this to promote waiting requests at the right time.

Call graph: calls 5 internal fn (approval_prompt_delay_remaining, pause_status_timer_for_modal, push_view, request_redraw_in, new); called by 2 (pre_draw_tick_at, push_approval_request); 5 external calls (new, is_empty, pop_back, pop_front, clone).

BottomPane::handle_key_event572–664 ↗
fn handle_key_event(&mut self, key_event: KeyEvent) -> InputResult

Purpose: Routes a keyboard event to the right place: active modal, task interrupt, or normal composer.

Data flow: It receives a key event. If a modal is open, it lets that view process it and closes the view if complete. Otherwise it may interrupt a running task if the configured shortcut was pressed, or pass the key to the composer. It records typing activity, schedules paste-burst redraws, and returns the composer input result when applicable.

Call relations: This is one of the central main-loop entry points for the bottom pane. It coordinates modal behavior, task status, keymap rules, and composer editing.

Call graph: calls 11 internal fn (composer_should_handle_vim_insert_escape, composer_text, pop_active_view_with_completion, record_composer_activity_at, request_redraw, request_redraw_in, handle_key_event, is_in_paste_burst, popup_active, recommended_paste_flush_delay (+1 more)); 2 external calls (now, matches!).

BottomPane::on_ctrl_c675–700 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Implements the pane's response to Ctrl-C.

Data flow: If a modal is open, it gives the modal a chance to cancel. If the composer is searching history, it cancels that search. If the composer is empty, it says Ctrl-C was not handled. Otherwise it clears the draft, records it to history when possible, shows the quit shortcut hint, redraws, and reports that Ctrl-C was handled.

Call relations: Global cancellation handling calls this to decide whether Ctrl-C affected the bottom pane or should be handled by the wider app.

Call graph: calls 6 internal fn (clear_composer_for_ctrl_c, composer_is_empty, pop_active_view_with_completion, request_redraw, show_quit_shortcut_hint, ctrl); 3 external calls (Char, matches!, cancel_history_search).

BottomPane::handle_paste702–723 ↗
fn handle_paste(&mut self, pasted: String)

Purpose: Routes pasted text to the active modal or to the composer.

Data flow: It receives pasted text. If a modal is open, the modal processes it and may complete. Otherwise the composer processes it, typing activity is recorded for non-empty paste text, and redraws happen when needed.

Call relations: Terminal paste handling enters here so paste behavior stays consistent with whether a modal is currently active.

Call graph: calls 4 internal fn (on_active_view_complete, record_composer_activity_at, request_redraw, handle_paste); 1 external calls (now).

BottomPane::insert_str725–728 ↗
fn insert_str(&mut self, text: &str)

Purpose: Programmatically inserts text into the composer.

Data flow: It receives a string slice, inserts it through the composer, and redraws.

Call relations: Higher-level commands use this when they need to add text as if it had been typed.

Call graph: calls 2 internal fn (request_redraw, insert_str); called by 1 (insert_str).

BottomPane::pre_draw_tick730–732 ↗
fn pre_draw_tick(&mut self)

Purpose: Runs time-based updates just before drawing.

Data flow: It gets the current time and passes it to the time-aware tick helper.

Call relations: The UI loop calls this before rendering so delayed prompts, animations, and popups are current.

Call graph: calls 1 internal fn (pre_draw_tick_at); called by 1 (pre_draw_tick); 1 external calls (now).

BottomPane::pre_draw_tick_at734–739 ↗
fn pre_draw_tick_at(&mut self, now: Instant)

Purpose: Runs all bottom-pane updates that depend on time.

Data flow: It receives a timestamp, syncs composer popups, tries to show delayed approvals, ticks the active view, and schedules the next active-view frame if needed.

Call relations: The normal pre-draw tick and tests use this to drive timed behavior deterministically.

Call graph: calls 4 internal fn (maybe_show_delayed_approval_requests_at, schedule_active_view_frame, tick_active_view, sync_popups); called by 1 (pre_draw_tick).

BottomPane::tick_active_view741–754 ↗
fn tick_active_view(&mut self, now: Instant)

Purpose: Lets the active modal update itself before drawing.

Data flow: It gives the current time to the top view. If the view needs redraw or completes, it redraws; if complete, it clears the stack and runs modal-completion cleanup.

Call relations: Pre-draw ticking calls this so modals can animate, time out, or finish themselves.

Call graph: calls 2 internal fn (on_active_view_complete, request_redraw); called by 1 (pre_draw_tick_at).

BottomPane::schedule_active_view_frame756–763 ↗
fn schedule_active_view_frame(&self)

Purpose: Schedules the next redraw needed by the active view.

Data flow: It asks the active view for its next frame delay. If there is one, it schedules a future redraw after that delay.

Call relations: Opening, replacing, dismissing, and ticking views use this to keep modal animations or timers moving.

Call graph: calls 2 internal fn (active_view, request_redraw_in); called by 4 (dismiss_view_by_id, pre_draw_tick_at, push_view, replace_selection_view_if_present).

BottomPane::set_composer_text770–780 ↗
fn set_composer_text(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
    )

Purpose: Replaces the composer contents with given text, structured text elements, and local images.

Data flow: It sends the new content to the composer, moves the cursor to the end, and redraws.

Call relations: External editing or state restoration paths use this to reset the draft shown in the input box.

Call graph: calls 3 internal fn (request_redraw, move_cursor_to_end, set_text_content); called by 1 (set_composer_text).

BottomPane::set_composer_text_with_mention_bindings787–802 ↗
fn set_composer_text_with_mention_bindings(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        mention_bindings: Vec<Men

Purpose: Replaces composer content while preserving explicit mention binding information.

Data flow: It receives text, text elements, image paths, and mention bindings, installs them in the composer, moves the cursor to the end, and redraws.

Call relations: This is used when restoring or editing text that already has resolved mentions attached to it.

Call graph: calls 3 internal fn (request_redraw, move_cursor_to_end, set_text_content_with_mention_bindings).

BottomPane::set_composer_input_enabled805–812 ↗
fn set_composer_input_enabled(
        &mut self,
        enabled: bool,
        placeholder: Option<String>,
    )

Purpose: Enables or disables typing in the composer, optionally changing the placeholder text.

Data flow: It passes the enabled flag and optional placeholder to the composer, then redraws.

Call relations: Modal flows disable normal typing while the user must answer a prompt, and modal completion re-enables it.

Call graph: calls 2 internal fn (request_redraw, set_input_enabled); called by 3 (on_active_view_complete, push_mcp_server_elicitation_request, push_user_input_request).

BottomPane::show_shutdown_in_progress814–818 ↗
fn show_shutdown_in_progress(&mut self)

Purpose: Switches the pane into a shutdown message state.

Data flow: It clears active views, tells the composer to show shutdown-in-progress UI, and redraws.

Call relations: Application shutdown flow uses this so the user sees that input is no longer normal.

Call graph: calls 2 internal fn (request_redraw, show_shutdown_in_progress); called by 1 (show_shutdown_in_progress).

BottomPane::clear_composer_for_ctrl_c820–832 ↗
fn clear_composer_for_ctrl_c(&mut self)

Purpose: Clears the current draft because Ctrl-C cancelled it, while trying to save that draft to history.

Data flow: It asks the composer to clear and return the old text. If a thread id is known, it sends an app event to append that text to message history; otherwise it logs a warning. It redraws afterward.

Call relations: Ctrl-C handling calls this when the draft is non-empty and should be cancelled rather than submitted.

Call graph: calls 3 internal fn (send, request_redraw, clear_for_ctrl_c); called by 1 (on_ctrl_c); 1 external calls (warn!).

BottomPane::composer_text835–837 ↗
fn composer_text(&self) -> String

Purpose: Returns the current plain text in the composer.

Data flow: It asks the composer for its current text and returns the string.

Call relations: Key routing uses this to detect special slash-command context, and other code can inspect the draft.

Call graph: calls 1 internal fn (current_text); called by 1 (handle_key_event).

BottomPane::composer_cursor840–842 ↗
fn composer_cursor(&self) -> usize

Purpose: Returns the current cursor position in the composer text.

Data flow: It reads the cursor index from the composer and returns it.

Call relations: Cursor-aware UI or tests can query this through the pane.

Call graph: calls 1 internal fn (cursor).

BottomPane::composer_draft_snapshot844–846 ↗
fn composer_draft_snapshot(&self) -> chat_composer::ComposerDraftSnapshot

Purpose: Captures the current composer draft as a snapshot.

Data flow: It asks the composer to package its draft state and returns that snapshot.

Call relations: State-saving flows can use this without reaching into composer internals.

Call graph: calls 1 internal fn (draft_snapshot).

BottomPane::composer_text_elements849–851 ↗
fn composer_text_elements(&self) -> Vec<TextElement>

Purpose: Returns structured text elements from the current composer draft.

Data flow: It asks the composer for its text elements and returns them as a vector.

Call relations: Submission and editing flows use these elements when plain text alone is not enough.

Call graph: calls 1 internal fn (text_elements).

BottomPane::composer_local_images853–855 ↗
fn composer_local_images(&self) -> Vec<LocalImageAttachment>

Purpose: Returns local image attachments currently associated with the composer.

Data flow: It asks the composer for local image attachment records and returns them.

Call relations: Message submission can collect attached images through this method.

Call graph: calls 1 internal fn (local_images).

BottomPane::composer_local_image_paths858–860 ↗
fn composer_local_image_paths(&self) -> Vec<PathBuf>

Purpose: Returns the file paths of local images attached in the composer.

Data flow: It asks the composer for image paths and returns them.

Call relations: Code that only needs paths, not full attachment records, can use this shortcut.

Call graph: calls 1 internal fn (local_image_paths).

BottomPane::composer_text_with_pending862–864 ↗
fn composer_text_with_pending(&self) -> String

Purpose: Returns composer text including any pending pasted content.

Data flow: It asks the composer for its text plus pending paste state and returns that string.

Call relations: Submission or preview code can see what the user effectively has queued to send.

Call graph: calls 1 internal fn (current_text_with_pending).

BottomPane::composer_input_enabled867–869 ↗
fn composer_input_enabled(&self) -> bool

Purpose: Reports whether normal composer typing is currently enabled.

Data flow: It reads the composer input-enabled flag and returns it.

Call relations: Modal and app-state code can check whether the user should currently be able to type.

Call graph: calls 1 internal fn (input_enabled).

BottomPane::composer_pending_pastes871–873 ↗
fn composer_pending_pastes(&self) -> Vec<(String, String)>

Purpose: Returns paste chunks that the composer is still holding as pending.

Data flow: It asks the composer for its pending paste pairs and returns them.

Call relations: Paste-burst handling and tests can inspect this buffered paste state.

Call graph: calls 1 internal fn (pending_pastes).

BottomPane::apply_external_edit875–878 ↗
fn apply_external_edit(&mut self, text: String)

Purpose: Applies text returned from an external editor to the composer.

Data flow: It receives edited text, gives it to the composer, and redraws.

Call relations: External-editor integration uses this when the user finishes editing outside the terminal UI.

Call graph: calls 2 internal fn (request_redraw, apply_external_edit).

BottomPane::set_plan_mode_nudge_visible886–890 ↗
fn set_plan_mode_nudge_visible(&mut self, visible: bool)

Purpose: Shows or hides a plan-mode nudge in the composer.

Data flow: It passes the requested visibility to the composer. If the composer says something changed, it redraws.

Call relations: Planning-mode guidance can be toggled without causing unnecessary redraws when nothing changed.

Call graph: calls 2 internal fn (request_redraw, set_plan_mode_nudge_visible).

BottomPane::plan_mode_nudge_visible893–895 ↗
fn plan_mode_nudge_visible(&self) -> bool

Purpose: Reports whether the plan-mode nudge is currently visible.

Data flow: It reads this state from the composer and returns it.

Call relations: Other UI logic can check the current nudge state through the bottom pane.

Call graph: calls 1 internal fn (plan_mode_nudge_visible).

BottomPane::set_remote_image_urls897–900 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>)

Purpose: Stores remote image URLs attached to the current composer draft.

Data flow: It receives URL strings, gives them to the composer, and redraws.

Call relations: Image attachment flows call this when images are represented by remote URLs instead of local files.

Call graph: calls 2 internal fn (request_redraw, set_remote_image_urls); called by 1 (set_remote_image_urls).

BottomPane::remote_image_urls902–904 ↗
fn remote_image_urls(&self) -> Vec<String>

Purpose: Returns remote image URLs currently attached in the composer.

Data flow: It asks the composer for its URL list and returns it.

Call relations: Submission or inspection code can collect remote image attachments here.

Call graph: calls 1 internal fn (remote_image_urls); called by 1 (remote_image_urls).

BottomPane::take_remote_image_urls906–910 ↗
fn take_remote_image_urls(&mut self) -> Vec<String>

Purpose: Removes and returns remote image URLs from the composer.

Data flow: It asks the composer to take the URL list, redraws, and returns the removed URLs.

Call relations: Submission cleanup uses this so image URLs are consumed once and not reused.

Call graph: calls 2 internal fn (request_redraw, take_remote_image_urls); called by 2 (drain_pending_submission_state, take_remote_image_urls).

BottomPane::set_composer_pending_pastes912–915 ↗
fn set_composer_pending_pastes(&mut self, pending_pastes: Vec<(String, String)>)

Purpose: Replaces the composer's pending paste buffer.

Data flow: It receives pending paste pairs, passes them to the composer, and redraws.

Call relations: State restoration or tests can set paste-burst state directly through this method.

Call graph: calls 2 internal fn (request_redraw, set_pending_pastes).

BottomPane::update_status920–932 ↗
fn update_status(
        &mut self,
        header: String,
        details: Option<String>,
        details_capitalization: StatusDetailsCapitalization,
        details_max_lines: usize,
    )

Purpose: Updates the visible status indicator text.

Data flow: It receives a header, optional details, capitalization setting, and maximum detail lines. If a status widget exists, it updates those fields and redraws.

Call relations: Running-task updates use this to refresh the short status message shown above the composer.

Call graph: calls 1 internal fn (request_redraw).

BottomPane::show_quit_shortcut_hint940–962 ↗
fn show_quit_shortcut_hint(&mut self, key: KeyBinding)

Purpose: Shows a temporary hint telling the user the quit shortcut.

Data flow: It receives the key binding to display. If the feature is enabled, it asks the composer to show the hint, schedules a future frame after the timeout using Tokio or a fallback thread, and redraws.

Call relations: Ctrl-C handling calls this after consuming cancellation, so the user learns how to quit if they press again.

Call graph: calls 2 internal fn (request_redraw, show_quit_shortcut_hint); called by 1 (on_ctrl_c); 4 external calls (spawn, try_current, sleep, clone).

BottomPane::clear_quit_shortcut_hint965–968 ↗
fn clear_quit_shortcut_hint(&mut self)

Purpose: Hides the quit shortcut hint.

Data flow: It tells the composer to clear the hint using the current focus state and redraws.

Call relations: Other input or state changes can clear the temporary quit hint.

Call graph: calls 2 internal fn (request_redraw, clear_quit_shortcut_hint).

BottomPane::quit_shortcut_hint_visible971–973 ↗
fn quit_shortcut_hint_visible(&self) -> bool

Purpose: Reports whether the quit shortcut hint is currently visible.

Data flow: It reads the visibility state from the composer and returns it.

Call relations: Tests and UI logic use this to confirm hint behavior.

Call graph: calls 1 internal fn (quit_shortcut_hint_visible).

BottomPane::status_indicator_visible976–978 ↗
fn status_indicator_visible(&self) -> bool

Purpose: Reports whether a status indicator widget currently exists.

Data flow: It checks whether the optional status field is present and returns a boolean.

Call relations: Other UI code can use this to decide whether status is being shown.

BottomPane::status_line_text981–983 ↗
fn status_line_text(&self) -> Option<String>

Purpose: Returns the composer's current status-line text, if any.

Data flow: It asks the composer for status-line text and returns it.

Call relations: Outer UI code can surface or test the status line through the pane.

Call graph: calls 1 internal fn (status_line_text); called by 1 (status_line_text).

BottomPane::show_esc_backtrack_hint985–989 ↗
fn show_esc_backtrack_hint(&mut self)

Purpose: Shows a hint that Escape can be used for backtracking.

Data flow: It marks the hint as active in the pane, tells the composer to show it, and redraws.

Call relations: Navigation guidance calls this when the app wants to teach or remind the user about Escape behavior.

Call graph: calls 2 internal fn (request_redraw, set_esc_backtrack_hint); called by 1 (show_esc_backtrack_hint).

BottomPane::clear_esc_backtrack_hint991–997 ↗
fn clear_esc_backtrack_hint(&mut self)

Purpose: Hides the Escape backtrack hint if it is currently shown.

Data flow: It checks the pane's flag. If active, it clears the flag, tells the composer to hide the hint, and redraws.

Call relations: Navigation state changes call this when the hint is no longer relevant.

Call graph: calls 2 internal fn (request_redraw, set_esc_backtrack_hint); called by 1 (clear_esc_backtrack_hint).

BottomPane::set_task_running1001–1026 ↗
fn set_task_running(&mut self, running: bool)

Purpose: Marks whether an assistant task is currently running and updates the visible task status UI.

Data flow: It receives a running flag, stores it, and tells the composer. When a task starts, it creates a status indicator if needed, shows the interrupt hint, syncs inline execution summary text, and redraws. When a task ends, it hides the status indicator.

Call relations: Task submission and review-mode flows call this so the bottom pane reflects work in progress and offers the interrupt shortcut.

Call graph: calls 6 internal fn (hide_status_indicator, request_redraw, sync_status_inline_message, set_task_running, primary_binding, new); called by 2 (enter_review_mode_with_hint, submit_op); 2 external calls (clone, clone).

BottomPane::set_queue_submissions1028–1030 ↗
fn set_queue_submissions(&mut self, queue_submissions: bool)

Purpose: Tells the composer whether new submissions should be queued.

Data flow: It receives a boolean and forwards it to the composer.

Call relations: Queueing policy is controlled outside the composer but applied to composer behavior here.

Call graph: calls 1 internal fn (set_queue_submissions).

BottomPane::hide_status_indicator1033–1037 ↗
fn hide_status_indicator(&mut self)

Purpose: Removes the status indicator if it is currently visible.

Data flow: It takes the status widget out of the pane. If one was removed, it redraws.

Call relations: Task completion uses this to clear running-task UI while leaving any modal views alone.

Call graph: calls 1 internal fn (request_redraw); called by 1 (set_task_running).

BottomPane::ensure_status_indicator1039–1052 ↗
fn ensure_status_indicator(&mut self)

Purpose: Creates a status indicator if one is not already present.

Data flow: It checks the status field. If missing, it builds a new widget, applies the current interrupt shortcut, syncs inline execution text, and redraws.

Call relations: Code that needs status UI can call this without worrying whether it already exists.

Call graph: calls 4 internal fn (request_redraw, sync_status_inline_message, primary_binding, new); 2 external calls (clone, clone).

BottomPane::set_interrupt_hint_visible1054–1059 ↗
fn set_interrupt_hint_visible(&mut self, visible: bool)

Purpose: Shows or hides the interrupt shortcut hint inside the status indicator.

Data flow: It receives a visibility flag, updates the status widget if present, and redraws.

Call relations: Task UI can adjust whether interrupt guidance should be displayed.

Call graph: calls 1 internal fn (request_redraw).

BottomPane::set_context_window1061–1072 ↗
fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>)

Purpose: Updates context-window usage information shown in the composer.

Data flow: It receives optional percent and used-token values. If they differ from cached values, it stores them, updates the composer, and redraws.

Call relations: Token information flows call this to keep the bottom pane's usage indicator current without redrawing for unchanged values.

Call graph: calls 2 internal fn (request_redraw, set_context_window); called by 3 (apply_token_info, restore_pre_review_token_info, set_token_info).

BottomPane::show_selection_view1075–1086 ↗
fn show_selection_view(
        &mut self,
        mut params: list_selection_view::SelectionViewParams,
    )

Purpose: Opens a list-style selection popup.

Data flow: It receives selection-view parameters, applies the standard cancel/help hint, builds a list view with app events and list keymap, and pushes it onto the modal stack.

Call relations: Feature prompts such as feedback consent or enablement prompts call this to ask the user to choose from a list.

Call graph: calls 3 internal fn (apply_standard_popup_hint, push_view, new); called by 3 (open_feedback_consent, open_memories_enable_prompt, open_multi_agent_enable_prompt); 2 external calls (new, clone).

BottomPane::apply_standard_popup_hint1088–1102 ↗
fn apply_standard_popup_hint(&self, params: &mut list_selection_view::SelectionViewParams)

Purpose: Fills in the standard popup footer hint when a selection view allows cancellation.

Data flow: It receives mutable selection parameters. If cancellation is not allowed, it removes the standard hint. If cancellation is allowed and no custom hint is present, it inserts the keymap-aware standard hint.

Call relations: Selection-view creation and replacement call this so popup help text is consistent with the current keymap.

Call graph: calls 2 internal fn (standard_popup_hint_line, standard_popup_hint_line); called by 4 (replace_active_views_with_selection_view, replace_selection_view_if_active, replace_selection_view_if_present, show_selection_view).

BottomPane::replace_selection_view_if_active1105–1127 ↗
fn replace_selection_view_if_active(
        &mut self,
        view_id: &'static str,
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

Purpose: Replaces the currently active selection view if it has a matching id.

Data flow: It checks the top view id. If it matches, it removes that view, applies standard hints to the new parameters, builds a new list view, pushes it, and returns true; otherwise it returns false.

Call relations: Flows that refresh an open selection popup use this when only the active popup should be replaced.

Call graph: calls 3 internal fn (apply_standard_popup_hint, push_view, new); 2 external calls (new, clone).

BottomPane::replace_selection_view_if_present1130–1155 ↗
fn replace_selection_view_if_present(
        &mut self,
        view_id: &'static str,
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

Purpose: Replaces a matching selection view anywhere in the view stack.

Data flow: It searches the stack from the top for the given id. If found, it builds a replacement list view and swaps it in; if the replaced view was active, it schedules its frame. It redraws and returns true.

Call relations: This is used when a popup may be underneath another view but still needs its contents updated.

Call graph: calls 4 internal fn (apply_standard_popup_hint, request_redraw, schedule_active_view_frame, new); 2 external calls (new, clone).

BottomPane::standard_popup_hint_line1157–1159 ↗
fn standard_popup_hint_line(&self) -> Line<'static>

Purpose: Builds the standard popup help line using the current list keymap.

Data flow: It reads the pane's list keymap and asks the popup constants helper for the matching hint line.

Call relations: Popup hint application calls this so help text reflects customized shortcuts.

Call graph: calls 1 internal fn (standard_popup_hint_line_for_keymap); called by 1 (apply_standard_popup_hint).

BottomPane::list_keymap1161–1163 ↗
fn list_keymap(&self) -> crate::keymap::ListKeymap

Purpose: Returns a copy of the current list-navigation keymap.

Data flow: It clones the list part of the runtime keymap and returns it.

Call relations: Views opened elsewhere use this to apply the same list controls as the bottom pane.

Call graph: called by 2 (open_app_link_view, open_memories_popup).

BottomPane::replace_active_views_with_selection_view1167–1197 ↗
fn replace_active_views_with_selection_view(
        &mut self,
        view_ids: &[&'static str],
        mut params: list_selection_view::SelectionViewParams,
    ) -> bool

Purpose: Replaces one or more active stacked views with a new selection view when their ids match a given set.

Data flow: It checks whether the top view id is in the given id list. If so, it pops all consecutive matching views from the top, applies standard hints, builds the replacement list view, pushes it, and returns true.

Call relations: Multi-step popup flows use this to collapse old related views into a new selection screen.

Call graph: calls 3 internal fn (apply_standard_popup_hint, push_view, new); 2 external calls (new, clone).

BottomPane::selected_index_for_active_view1199–1204 ↗
fn selected_index_for_active_view(&self, view_id: &'static str) -> Option<usize>

Purpose: Returns the selected row index for the active view if its id matches.

Data flow: It checks the top view id, then asks that view for its selected index.

Call relations: Outer logic can remember or inspect selection state for a specific active popup.

BottomPane::active_tab_id_for_active_view1206–1211 ↗
fn active_tab_id_for_active_view(&self, view_id: &'static str) -> Option<&str>

Purpose: Returns the active tab id for the active view if its id matches.

Data flow: It checks the top view id, then asks that view for its active tab id.

Call relations: Tabbed popups can expose their current tab through this helper.

BottomPane::dismiss_active_view_if_id1213–1225 ↗
fn dismiss_active_view_if_id(&mut self, view_id: &'static str) -> bool

Purpose: Closes the active view only if it has a specific id.

Data flow: It compares the top view id to the requested id. If it matches, it pops the view, redraws, and returns true; otherwise it returns false.

Call relations: Targeted dismissal flows use this to avoid closing the wrong modal.

Call graph: calls 1 internal fn (request_redraw).

BottomPane::dismiss_view_by_id1228–1244 ↗
fn dismiss_view_by_id(&mut self, view_id: &'static str) -> bool

Purpose: Closes the most recent view with a given id, even if it is not active.

Data flow: It searches the stack from the top. If found, it removes that view, schedules a frame if the active view changed, redraws, and returns true.

Call relations: This supports cleanup of nested or background popup views by identity.

Call graph: calls 2 internal fn (request_redraw, schedule_active_view_frame).

BottomPane::set_pending_input_preview1247–1257 ↗
fn set_pending_input_preview(
        &mut self,
        queued: Vec<String>,
        pending_steers: Vec<String>,
        rejected_steers: Vec<String>,
    )

Purpose: Updates the preview of queued messages and steering instructions.

Data flow: It receives queued messages, pending steers, and rejected steers, stores them in the preview widget, and redraws.

Call relations: Queue and steering flows call this so the user can see what input is waiting or rejected.

Call graph: calls 1 internal fn (request_redraw).

BottomPane::set_pending_thread_approvals1260–1264 ↗
fn set_pending_thread_approvals(&mut self, threads: Vec<String>)

Purpose: Updates the list of threads waiting for approval.

Data flow: It gives the thread list to the pending approvals widget. If that widget reports a change, it redraws.

Call relations: Thread approval state changes flow through this to update the bottom-pane preview.

Call graph: calls 2 internal fn (request_redraw, set_threads); called by 1 (set_pending_thread_approvals).

BottomPane::pending_thread_approvals1267–1269 ↗
fn pending_thread_approvals(&self) -> &[String]

Purpose: Returns the current thread names or ids waiting for approval.

Data flow: It asks the pending approvals widget for its thread slice and returns it.

Call relations: Outer code and tests can inspect pending approval state through this method.

Call graph: calls 1 internal fn (threads); called by 1 (pending_thread_approvals).

BottomPane::set_unified_exec_processes1275–1280 ↗
fn set_unified_exec_processes(&mut self, processes: Vec<String>)

Purpose: Updates the list of running execution processes shown in the footer or status line.

Data flow: It passes process names to the unified execution footer. If the list changed, it syncs the status inline message and redraws.

Call relations: Execution tracking code uses this to keep the bottom pane's running-process summary current.

Call graph: calls 3 internal fn (request_redraw, sync_status_inline_message, set_processes).

BottomPane::sync_status_inline_message1286–1290 ↗
fn sync_status_inline_message(&mut self)

Purpose: Copies the execution footer summary into the status indicator.

Data flow: It reads the footer summary text and, if a status widget exists, updates that widget's inline message.

Call relations: Status creation, task-start logic, and process updates call this so the status line and execution footer do not drift apart.

Call graph: calls 1 internal fn (summary_text); called by 3 (ensure_status_indicator, set_task_running, set_unified_exec_processes).

BottomPane::composer_is_empty1292–1294 ↗
fn composer_is_empty(&self) -> bool

Purpose: Reports whether the composer draft is empty.

Data flow: It asks the composer whether it has any content and returns that boolean.

Call relations: Ctrl-C handling uses this to decide whether to clear a draft or let cancellation bubble up.

Call graph: calls 1 internal fn (is_empty); called by 2 (on_ctrl_c, composer_is_empty).

BottomPane::composer_is_vim_enabled1297–1299 ↗
fn composer_is_vim_enabled(&self) -> bool

Purpose: Reports whether Vim-style editing is enabled in the composer.

Data flow: It asks the composer for its Vim-mode flag and returns it.

Call relations: Settings and tests can query the current editor mode through the pane.

Call graph: calls 1 internal fn (is_vim_enabled).

BottomPane::composer_should_handle_vim_insert_escape1301–1303 ↗
fn composer_should_handle_vim_insert_escape(&self, key_event: KeyEvent) -> bool

Purpose: Decides whether Escape should be handled by the composer for Vim insert-mode behavior.

Data flow: It receives a key event, asks the composer whether that key should switch Vim state, and returns the answer.

Call relations: Key routing uses this to avoid treating Escape as a task interrupt when the composer needs it for Vim editing.

Call graph: calls 1 internal fn (should_handle_vim_insert_escape); called by 2 (handle_key_event, should_handle_vim_insert_escape).

BottomPane::is_task_running1305–1307 ↗
fn is_task_running(&self) -> bool

Purpose: Reports whether the pane currently believes a task is running.

Data flow: It returns the stored running-task boolean.

Call relations: Submission and review-mode code use this to make decisions about current task state.

Call graph: called by 3 (enter_review_mode_with_hint, is_task_running_for_test, submit_op).

BottomPane::terminal_title_requires_action1309–1312 ↗
fn terminal_title_requires_action(&self) -> bool

Purpose: Reports whether the active modal needs user attention in the terminal title.

Data flow: It checks the active view and asks whether it requires action; if no view is active, it returns false.

Call relations: Terminal title updating uses this to flag pending user decisions.

Call graph: calls 1 internal fn (active_view).

BottomPane::has_active_view1314–1316 ↗
fn has_active_view(&self) -> bool

Purpose: Reports whether any modal or popup view is active.

Data flow: It checks whether the view stack is non-empty and returns that boolean.

Call relations: Other UI code uses this as a simple guard for modal state.

Call graph: called by 1 (has_active_view).

BottomPane::active_view_id1319–1321 ↗
fn active_view_id(&self) -> Option<&'static str>

Purpose: Returns the id of the active view, if it has one.

Data flow: It looks at the top view and asks for its optional id.

Call relations: Targeted popup logic can identify which view is currently on top.

BottomPane::is_normal_backtrack_mode1326–1328 ↗
fn is_normal_backtrack_mode(&self) -> bool

Purpose: Reports whether the pane is in a normal state where Escape backtracking can apply.

Data flow: It returns true only when no task is running, no modal is open, and the composer has no popup active.

Call relations: Navigation logic uses this to decide whether Escape should mean normal backtracking.

Call graph: calls 1 internal fn (popup_active); called by 1 (is_normal_backtrack_mode).

BottomPane::can_launch_external_editor1331–1333 ↗
fn can_launch_external_editor(&self) -> bool

Purpose: Reports whether it is safe to open an external editor.

Data flow: It returns true only when no modal view and no composer popup are active.

Call relations: External-editor commands use this to avoid launching while another UI interaction is in progress.

Call graph: calls 1 internal fn (popup_active); called by 1 (no_modal_or_popup_active).

BottomPane::no_modal_or_popup_active1340–1342 ↗
fn no_modal_or_popup_active(&self) -> bool

Purpose: Reports whether there is no modal and no composer popup active.

Data flow: It reuses the external-editor safety check and returns the same boolean.

Call relations: This gives other call sites a clearer name for the same condition.

Call graph: calls 1 internal fn (can_launch_external_editor).

BottomPane::show_view1344–1346 ↗
fn show_view(&mut self, view: Box<dyn BottomPaneView>)

Purpose: Shows an already-built bottom-pane view.

Data flow: It receives a boxed view and pushes it onto the view stack.

Call relations: Other modules that construct specialized views call this to display them without duplicating stack logic.

Call graph: calls 1 internal fn (push_view); called by 3 (open_app_link_view, open_memories_popup, show_feedback_note).

BottomPane::push_approval_request1349–1384 ↗
fn push_approval_request(&mut self, request: ApprovalRequest, features: &Features)

Purpose: Displays or queues an approval prompt for an action that needs user permission.

Data flow: It receives an approval request and feature settings. If the active view can consume the request, it lets that happen. Otherwise it either delays the request because the user recently typed, or immediately builds and pushes an approval overlay. It pauses the status timer when showing the modal.

Call relations: Execution and permission flows call this when the assistant needs approval before continuing.

Call graph: calls 6 internal fn (approval_prompt_delay_remaining, maybe_show_delayed_approval_requests_at, pause_status_timer_for_modal, push_view, request_redraw, new); 6 external calls (new, now, is_empty, push_back, clone, clone).

BottomPane::push_user_input_request1387–1414 ↗
fn push_user_input_request(&mut self, request: ToolRequestUserInputParams)

Purpose: Shows a modal asking the user to provide requested tool input.

Data flow: It lets an active view consume the request if possible. Otherwise it builds a user-input overlay, pauses the status timer, disables the normal composer with explanatory placeholder text, and pushes the modal.

Call relations: Tool-request flows use this when progress depends on answers from the user.

Call graph: calls 5 internal fn (pause_status_timer_for_modal, push_view, request_redraw, set_composer_input_enabled, new_with_keymap); 3 external calls (new, clone, clone).

BottomPane::push_mcp_server_elicitation_request1416–1501 ↗
fn push_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    )

Purpose: Shows a modal for an MCP server request, or a special app-link suggestion if the request asks the user to install or enable a tool.

Data flow: It first lets an active view consume the request. If the request contains a tool suggestion with an install URL, it builds an app-link view with instructions and target metadata. Otherwise it builds a normal MCP elicitation overlay. In both cases it pauses status timing, disables composer input with helpful placeholder text, and pushes the view.

Call relations: MCP server interaction flows call this when an external server needs the user to respond before work can continue.

Call graph: calls 10 internal fn (pause_status_timer_for_modal, push_view, request_redraw, set_composer_input_enabled, new_with_keymap, request_id, server_name, thread_id, tool_suggestion, new_with_keymap); 4 external calls (new, matches!, clone, unreachable!).

BottomPane::dismiss_app_server_request1503–1540 ↗
fn dismiss_app_server_request(
        &mut self,
        request: &ResolvedAppServerRequest,
    ) -> bool

Purpose: Dismisses approval or server-request UI that matches a resolved app-server request.

Data flow: It removes matching delayed approval requests, then walks the view stack and asks each view whether it should dismiss the request. Completed matching views are removed, cleanup runs if the stack shrank to empty, and the pane redraws if anything changed.

Call relations: Request-resolution flows call this to remove prompts that are no longer needed.

Call graph: calls 2 internal fn (on_view_stack_depth_decreased, request_redraw); called by 1 (dismiss_app_server_request); 3 external calls (new, len, retain).

BottomPane::on_active_view_complete1542–1545 ↗
fn on_active_view_complete(&mut self)

Purpose: Restores normal pane behavior after all modal views are gone.

Data flow: It resumes the status timer and re-enables composer input with no special placeholder.

Call relations: View-pop, paste, and tick completion paths call this after modal interaction finishes.

Call graph: calls 2 internal fn (resume_status_timer_after_modal, set_composer_input_enabled); called by 3 (handle_paste, on_view_stack_depth_decreased, tick_active_view).

BottomPane::pause_status_timer_for_modal1547–1551 ↗
fn pause_status_timer_for_modal(&mut self)

Purpose: Pauses the status indicator timer while a modal is open.

Data flow: It checks for a status widget and asks it to pause its timer.

Call relations: Modal-opening flows call this so status timing does not keep changing behind a prompt.

Call graph: called by 4 (maybe_show_delayed_approval_requests_at, push_approval_request, push_mcp_server_elicitation_request, push_user_input_request).

BottomPane::resume_status_timer_after_modal1553–1557 ↗
fn resume_status_timer_after_modal(&mut self)

Purpose: Resumes the status indicator timer after modal interaction ends.

Data flow: It checks for a status widget and asks it to resume its timer.

Call relations: Active-view completion calls this when returning to normal composer interaction.

Call graph: called by 1 (on_active_view_complete).

BottomPane::request_redraw1560–1562 ↗
fn request_redraw(&self)

Purpose: Asks the UI to draw a new frame soon.

Data flow: It calls the frame requester to schedule a frame and returns nothing.

Call relations: Nearly every state-changing method uses this so visible changes appear on screen.

Call graph: calls 1 internal fn (schedule_frame); called by 67 (apply_external_edit, attach_image, clear_composer_for_ctrl_c, clear_esc_backtrack_hint, clear_quit_shortcut_hint, dismiss_active_view_if_id, dismiss_app_server_request, dismiss_view_by_id, ensure_status_indicator, handle_key_event (+15 more)).

BottomPane::request_redraw_in1564–1566 ↗
fn request_redraw_in(&self, dur: Duration)

Purpose: Asks the UI to draw a new frame after a delay.

Data flow: It receives a duration and passes it to the frame requester.

Call relations: Paste-burst flushing, delayed approvals, and animated views use this to wake the UI later.

Call graph: calls 1 internal fn (schedule_frame_in); called by 4 (handle_key_event, maybe_show_delayed_approval_requests_at, record_composer_activity_at, schedule_active_view_frame).

BottomPane::set_history_metadata1570–1579 ↗
fn set_history_metadata(
        &mut self,
        thread_id: ThreadId,
        log_id: u64,
        entry_count: usize,
    )

Purpose: Stores message-history metadata for the current thread and passes it to the composer.

Data flow: It receives a thread id, log id, and entry count. It saves the thread id locally and tells the composer about the history location.

Call relations: History navigation and Ctrl-C draft preservation rely on this metadata.

Call graph: calls 1 internal fn (set_history_metadata).

BottomPane::flush_paste_burst_if_due1581–1590 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

Purpose: Flushes buffered paste input when the paste-burst delay has expired.

Data flow: It first asks the active view to flush paste state, if one exists. If that does not flush anything, it asks the composer. It returns whether anything was flushed.

Call relations: The UI loop can call this to finish processing large pastes consistently for modals and normal composer input.

Call graph: calls 1 internal fn (flush_paste_burst_if_due).

BottomPane::is_in_paste_burst1592–1599 ↗
fn is_in_paste_burst(&self) -> bool

Purpose: Reports whether the active view or composer is currently buffering a paste burst.

Data flow: It checks the active view first, then the composer, and returns true if either is in paste-burst mode.

Call relations: Paste scheduling code uses this to know whether another delayed flush may be needed.

Call graph: calls 1 internal fn (is_in_paste_burst).

BottomPane::on_history_entry_response1601–1615 ↗
fn on_history_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
    )

Purpose: Applies an asynchronous history lookup response to the composer.

Data flow: It receives the log id, offset, and optional history entry. The composer decides whether this updates its state. If it does, the pane syncs popups and redraws.

Call relations: History response handling calls this after a requested history entry is loaded.

Call graph: calls 3 internal fn (request_redraw, on_history_entry_response, sync_popups); called by 1 (handle_history_entry_response).

BottomPane::record_replayed_user_message_history1617–1619 ↗
fn record_replayed_user_message_history(&mut self, entry: HistoryEntry)

Purpose: Records a replayed user message in composer history.

Data flow: It receives a history entry and forwards it to the composer.

Call relations: Committed replayed-message handling uses this to keep composer history accurate.

Call graph: calls 1 internal fn (record_replayed_user_message_history); called by 1 (on_committed_user_message).

BottomPane::on_file_search_result1621–1624 ↗
fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>)

Purpose: Delivers file-search results to the composer.

Data flow: It receives the query and matches, passes them to the composer, and redraws.

Call relations: File mention or search flows call this when search results arrive.

Call graph: calls 2 internal fn (request_redraw, on_file_search_result); called by 1 (apply_file_search_result).

BottomPane::attach_image1626–1631 ↗
fn attach_image(&mut self, path: PathBuf)

Purpose: Attaches a local image to the composer when no modal is active.

Data flow: It receives an image path. If the view stack is empty, it adds the image through the composer and redraws; otherwise it ignores the request.

Call relations: Image attachment commands use this guard so attachments are not added while a modal is taking input.

Call graph: calls 2 internal fn (request_redraw, attach_image).

BottomPane::take_recent_submission_images1634–1636 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

Purpose: Removes and returns local image paths associated with the recent submission.

Data flow: It asks the composer to take its recent submission images and returns them.

Call relations: Submission processing uses this to collect images exactly once.

Call graph: calls 1 internal fn (take_recent_submission_images).

BottomPane::take_recent_submission_images_with_placeholders1638–1643 ↗
fn take_recent_submission_images_with_placeholders(
        &mut self,
    ) -> Vec<LocalImageAttachment>

Purpose: Removes and returns recent image attachments together with placeholder information.

Data flow: It delegates to the composer, which clears and returns those attachment records.

Call relations: Pending submission cleanup uses this to drain richer image attachment state.

Call graph: calls 1 internal fn (take_recent_submission_images_with_placeholders); called by 1 (drain_pending_submission_state).

BottomPane::prepare_inline_args_submission1645–1650 ↗
fn prepare_inline_args_submission(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

Purpose: Prepares a submission created from inline command arguments.

Data flow: It receives whether history should be recorded, asks the composer to prepare the submission, and returns optional text plus structured elements.

Call relations: Inline command submission paths use this to turn composer content into a sendable message.

Call graph: calls 1 internal fn (prepare_inline_args_submission).

BottomPane::as_renderable1652–1654 ↗
fn as_renderable(&'_ self) -> RenderableItem<'_>

Purpose: Builds the renderable representation of the pane with no extra composer right-side reserve.

Data flow: It calls the more general renderable builder with a reserve value of zero and returns the result.

Call relations: Rendering, height, cursor position, and cursor style methods use this common representation.

Call graph: calls 1 internal fn (as_renderable_with_composer_right_reserve); called by 4 (cursor_pos, cursor_style, desired_height, render).

BottomPane::as_renderable_with_composer_right_reserve1656–1712 ↗
fn as_renderable_with_composer_right_reserve(
        &'_ self,
        composer_right_reserve: u16,
    ) -> RenderableItem<'_>

Purpose: Builds the visual layout object for the bottom pane.

Data flow: If a modal is active, it returns that view as the renderable item. Otherwise it assembles a flexible layout containing status, execution footer, pending approvals, pending input preview, spacing, and the composer. If requested, it wraps the composer so part of the right side is reserved.

Call relations: All rendering and layout measurement paths go through this so the bottom pane is drawn consistently.

Call graph: calls 4 internal fn (active_view, is_empty, is_empty, new); called by 5 (as_renderable, cursor_pos_with_composer_right_reserve, cursor_style_with_composer_right_reserve, desired_height_with_composer_right_reserve, render_with_composer_right_reserve); 3 external calls (new, Borrowed, Owned).

BottomPane::render_with_composer_right_reserve1714–1722 ↗
fn render_with_composer_right_reserve(
        &self,
        area: Rect,
        buf: &mut Buffer,
        composer_right_reserve: u16,
    )

Purpose: Draws the pane while leaving extra space on the composer's right side.

Data flow: It receives a screen area, buffer, and reserve width, builds the matching renderable layout, and renders it into the buffer.

Call relations: Outer render code uses this variant when another UI element needs space beside the composer.

Call graph: calls 1 internal fn (as_renderable_with_composer_right_reserve); called by 1 (render).

BottomPane::desired_height_with_composer_right_reserve1724–1731 ↗
fn desired_height_with_composer_right_reserve(
        &self,
        width: u16,
        composer_right_reserve: u16,
    ) -> u16

Purpose: Calculates how tall the pane wants to be with a right-side composer reserve.

Data flow: It receives available width and reserve width, builds the renderable layout, and asks it for desired height.

Call relations: Layout code uses this before drawing to allocate enough terminal rows.

Call graph: calls 1 internal fn (as_renderable_with_composer_right_reserve); called by 1 (desired_height).

BottomPane::cursor_pos_with_composer_right_reserve1733–1740 ↗
fn cursor_pos_with_composer_right_reserve(
        &self,
        area: Rect,
        composer_right_reserve: u16,
    ) -> Option<(u16, u16)>

Purpose: Finds the cursor position when rendering with a right-side composer reserve.

Data flow: It receives the screen area and reserve width, builds the renderable layout, and asks it for the cursor coordinates.

Call relations: Terminal cursor placement uses this to keep the visible cursor aligned with the composer.

Call graph: calls 1 internal fn (as_renderable_with_composer_right_reserve); called by 1 (cursor_pos).

BottomPane::cursor_style_with_composer_right_reserve1742–1749 ↗
fn cursor_style_with_composer_right_reserve(
        &self,
        area: Rect,
        composer_right_reserve: u16,
    ) -> crossterm::cursor::SetCursorStyle

Purpose: Finds the cursor style when rendering with a right-side composer reserve.

Data flow: It receives the screen area and reserve width, builds the renderable layout, and asks it for the cursor style.

Call relations: Terminal drawing uses this so insert, overwrite, or Vim-style cursor appearance matches composer state.

Call graph: calls 1 internal fn (as_renderable_with_composer_right_reserve); called by 1 (cursor_style).

BottomPane::set_status_line1751–1755 ↗
fn set_status_line(&mut self, status_line: Option<Line<'static>>)

Purpose: Sets or clears an extra status line in the composer.

Data flow: It sends the optional line to the composer. If the composer changed, it redraws.

Call relations: Status-message flows use this to show lightweight information directly in the composer area.

Call graph: calls 2 internal fn (request_redraw, set_status_line).

BottomPane::set_status_line_enabled1763–1767 ↗
fn set_status_line_enabled(&mut self, enabled: bool)

Purpose: Turns the composer status line on or off.

Data flow: It passes the enabled flag to the composer and redraws if the setting changed.

Call relations: UI state can suppress or restore the status line through this method.

Call graph: calls 2 internal fn (request_redraw, set_status_line_enabled).

BottomPane::set_active_agent_label1773–1777 ↗
fn set_active_agent_label(&mut self, active_agent_label: Option<String>)

Purpose: Sets the label for the currently active agent shown in the composer.

Data flow: It passes the optional label to the composer and redraws if the visible state changed.

Call relations: Agent-selection flows use this to keep the input area labeled with the active agent.

Call graph: calls 2 internal fn (request_redraw, set_active_agent_label).

BottomPane::set_side_conversation_context_label1779–1783 ↗
fn set_side_conversation_context_label(&mut self, label: Option<String>)

Purpose: Sets the label describing the side-conversation context.

Data flow: It forwards the optional label to the composer and redraws if it changed.

Call relations: Side-conversation UI uses this so the user can see what context they are typing into.

Call graph: calls 2 internal fn (request_redraw, set_side_conversation_context_label).

ChatComposerRightReserveRenderable::render1792–1799 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws a composer while reserving blank space on the right side of its text area.

Data flow: It receives an area and buffer, then asks the composer to render with no mask character and the configured right reserve.

Call relations: The bottom pane uses this wrapper when its layout must leave room beside the composer.

Call graph: calls 1 internal fn (render_with_mask_and_textarea_right_reserve).

ChatComposerRightReserveRenderable::desired_height1801–1804 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates composer height while accounting for reserved right-side space.

Data flow: It receives a width and asks the composer how tall it wants to be with that reserve.

Call relations: Layout measurement uses this wrapper exactly like any other renderable item.

Call graph: calls 1 internal fn (desired_height_with_textarea_right_reserve).

ChatComposerRightReserveRenderable::cursor_pos1806–1809 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Finds the composer's cursor position while accounting for reserved right-side space.

Data flow: It receives the drawing area and asks the composer for cursor coordinates with the reserve applied.

Call relations: Cursor placement uses this so the cursor position matches the reserved layout.

Call graph: calls 1 internal fn (cursor_pos_with_textarea_right_reserve).

ChatComposerRightReserveRenderable::cursor_style1811–1813 ↗
fn cursor_style(&self, area: Rect) -> crossterm::cursor::SetCursorStyle

Purpose: Returns the composer's cursor style.

Data flow: It receives the area but simply asks the composer for its cursor style.

Call relations: This completes the renderable wrapper interface for reserved-space composer rendering.

Call graph: calls 1 internal fn (cursor_style).

BottomPane::render1817–1819 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the bottom pane into the terminal buffer.

Data flow: It receives a screen area and buffer, builds the standard renderable layout, and renders it.

Call relations: The terminal rendering system calls this when painting the bottom pane.

Call graph: calls 1 internal fn (as_renderable); called by 1 (render_snapshot).

BottomPane::desired_height1820–1822 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Reports how many terminal rows the bottom pane would like.

Data flow: It receives a width, builds the standard renderable layout, and asks for desired height.

Call relations: Layout code calls this before rendering to divide screen space.

Call graph: calls 1 internal fn (as_renderable).

BottomPane::cursor_pos1823–1825 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Reports where the terminal cursor should appear for the bottom pane.

Data flow: It receives the pane area, builds the standard renderable layout, and returns optional cursor coordinates.

Call relations: The terminal UI uses this after drawing to place the hardware cursor.

Call graph: calls 1 internal fn (as_renderable).

BottomPane::cursor_style1827–1829 ↗
fn cursor_style(&self, area: Rect) -> crossterm::cursor::SetCursorStyle

Purpose: Reports what cursor style should be used for the bottom pane.

Data flow: It receives the pane area, builds the standard renderable layout, and returns the cursor style.

Call relations: The terminal UI uses this so the cursor shape reflects the active input mode.

Call graph: calls 1 internal fn (as_renderable).

tests::snapshot_buffer1856–1866 ↗
fn snapshot_buffer(buf: &Buffer) -> String

Purpose: Turns a rendered terminal buffer into plain text for snapshot-style assertions.

Data flow: It reads every cell in the buffer area, collects the first character from each cell, joins rows with newlines, and returns the resulting string.

Call relations: Rendering tests use this helper to compare what appeared on screen.

Call graph: 3 external calls (area, new, new).

tests::render_snapshot1868–1872 ↗
fn render_snapshot(pane: &BottomPane, area: Rect) -> String

Purpose: Renders a bottom pane into a fresh buffer and returns it as text.

Data flow: It receives a pane and area, creates an empty buffer, renders the pane, converts the buffer to text, and returns that text.

Call relations: Tests use this to inspect rendered output without a real terminal.

Call graph: calls 1 internal fn (render); 2 external calls (empty, snapshot_buffer).

tests::test_pane1874–1876 ↗
fn test_pane(app_event_tx: AppEventSender) -> BottomPane

Purpose: Builds a standard test BottomPane.

Data flow: It receives an app event sender and calls the more configurable test builder with paste-burst handling enabled.

Call relations: Many tests use this shortcut to avoid repeating setup values.

Call graph: 1 external calls (test_pane_with_disable_paste_burst).

tests::test_pane_with_disable_paste_burst1878–1892 ↗
fn test_pane_with_disable_paste_burst(
        app_event_tx: AppEventSender,
        disable_paste_burst: bool,
    ) -> BottomPane

Purpose: Builds a test BottomPane with a chosen paste-burst setting.

Data flow: It receives an event sender and paste-burst flag, fills in test defaults such as dummy frame requester and placeholder text, and returns a new BottomPane.

Call relations: Tests call this when they need to control paste behavior.

Call graph: calls 2 internal fn (new, test_dummy); 1 external calls (new).

tests::exec_request1894–1908 ↗
fn exec_request() -> ApprovalRequest

Purpose: Creates a sample command-execution approval request for tests.

Data flow: It builds an approval request for an echo command with accept and cancel decisions and returns it.

Call relations: Approval-modal tests use this consistent fake request.

Call graph: calls 1 internal fn (new); 1 external calls (vec!).

tests::DismissibleView::render1918–1918 ↗
fn render(&self, _area: Rect, _buf: &mut Buffer)

Purpose: Provides a no-op render method for a fake dismissible test view.

Data flow: It receives an area and buffer but intentionally draws nothing.

Call relations: This lets the fake view satisfy the view interface while tests focus on dismissal behavior.

tests::DismissibleView::desired_height1920–1922 ↗
fn desired_height(&self, _width: u16) -> u16

Purpose: Reports zero height for the fake dismissible test view.

Data flow: It ignores the width and returns 0.

Call relations: The test view only exists for control-flow tests, not visual layout.

tests::DismissibleView::is_complete1926–1928 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the fake dismissible view has completed.

Data flow: It returns the view's internal complete flag.

Call relations: Dismissal tests use this through the normal bottom-pane view-completion machinery.

tests::DismissibleView::view_id1930–1932 ↗
fn view_id(&self) -> Option<&'static str>

Purpose: Returns the fake view's optional id.

Data flow: It reads and returns the stored id.

Call relations: View-stack tests can identify this fake view the same way production views are identified.

tests::DismissibleView::dismiss_app_server_request1934–1944 ↗
fn dismiss_app_server_request(&mut self, request: &ResolvedAppServerRequest) -> bool

Purpose: Marks the fake view complete when a matching execution approval request is dismissed.

Data flow: It receives a resolved app-server request. If it is an execution approval with the expected id, it sets complete to true and returns true; otherwise it returns false.

Call relations: Tests use this to verify BottomPane removes completed views after resolved requests.

tests::CompletingView::render1954–1954 ↗
fn render(&self, _area: Rect, _buf: &mut Buffer)

Purpose: Provides a no-op render method for a fake completing test view.

Data flow: It receives drawing inputs and ignores them.

Call relations: The fake view is used for key-completion behavior, not rendering.

tests::CompletingView::desired_height1956–1958 ↗
fn desired_height(&self, _width: u16) -> u16

Purpose: Reports zero height for the fake completing test view.

Data flow: It ignores the width and returns 0.

Call relations: This satisfies the view interface for tests.

tests::CompletingView::handle_key_event1962–1966 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Completes the fake view when Enter is pressed.

Data flow: It receives a key event. If the key code is Enter, it sets its complete flag.

Call relations: Key-routing tests use this to check that BottomPane pops views when they complete.

tests::CompletingView::is_complete1968–1970 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the fake completing view has completed.

Data flow: It returns the internal complete flag.

Call relations: BottomPane's modal-completion logic reads this during tests.

tests::CompletingView::view_id1972–1974 ↗
fn view_id(&self) -> Option<&'static str>

Purpose: Returns the fake completing view's optional id.

Data flow: It reads and returns the stored id.

Call relations: Tests use this for targeted view-stack operations.

tests::ctrl_c_on_modal_consumes_without_showing_quit_hint1978–1996 ↗
fn ctrl_c_on_modal_consumes_without_showing_quit_hint()

Purpose: Checks that Ctrl-C closes an approval modal without showing the quit hint.

Data flow: It builds a pane, opens an approval request, sends Ctrl-C, and asserts the cancellation was handled, no quit hint is visible, and a second Ctrl-C is not handled.

Call relations: This protects the intended priority: modal cancellation should not look like app quit guidance.

Call graph: calls 4 internal fn (with_defaults, new, new, test_dummy); 4 external calls (new, assert!, assert_eq!, exec_request).

tests::ctrl_c_cancels_history_search_without_clearing_draft_or_showing_quit_hint1999–2021 ↗
fn ctrl_c_cancels_history_search_without_clearing_draft_or_showing_quit_hint()

Purpose: Checks that Ctrl-C cancels composer history search without deleting the draft.

Data flow: It creates a pane, inserts draft text, starts history search with Ctrl-R, sends Ctrl-C, and verifies the draft remains, the popup closes, and no quit hint appears.

Call relations: This guards the composer behavior used by BottomPane::on_ctrl_c.

Call graph: calls 3 internal fn (new, new, test_dummy); 5 external calls (Char, new, new, assert!, assert_eq!).

tests::overlay_not_shown_above_approval_modal2026–2057 ↗
fn overlay_not_shown_above_approval_modal()

Purpose: Checks that normal status overlays do not appear above an approval modal.

Data flow: It opens an approval modal, renders the pane, reads the top row, and asserts it does not contain the working overlay text.

Call relations: This protects the rendering rule that an active modal replaces the normal bottom-pane layout.

Call graph: calls 4 internal fn (with_defaults, new, new, test_dummy); 6 external calls (empty, new, new, new, assert!, exec_request).

tests::approval_request_shows_immediately_without_recent_typing2060–2070 ↗
fn approval_request_shows_immediately_without_recent_typing()

Purpose: Checks that approval prompts appear immediately when the user has not recently typed.

Data flow: It creates a pane, pushes an approval request, and asserts one view is active and no delayed approvals remain.

Call relations: This tests the immediate branch of approval prompt scheduling.

Call graph: calls 2 internal fn (with_defaults, new); 4 external calls (assert!, assert_eq!, exec_request, test_pane).

tests::approval_request_is_delayed_after_recent_typing2073–2095 ↗
fn approval_request_is_delayed_after_recent_typing()

Purpose: Checks that approval prompts wait until the typing idle delay has passed.

Data flow: It marks recent composer activity, pushes an approval request, verifies it is delayed, ticks just before the delay and then at the delay, and confirms the modal appears only after the deadline.

Call relations: This tests the delayed approval flow used to avoid interrupting active typing.

Call graph: calls 2 internal fn (with_defaults, new); 6 external calls (from_millis, now, assert!, assert_eq!, exec_request, test_pane).

tests::continued_typing_resets_delayed_approval_idle_deadline2098–2117 ↗
fn continued_typing_resets_delayed_approval_idle_deadline()

Purpose: Checks that more typing pushes back the delayed approval deadline.

Data flow: It creates a delayed approval, records later composer activity, ticks at the old deadline and then the new one, and verifies the prompt appears only after the later deadline.

Call relations: This protects the relationship between record_composer_activity_at and delayed approval display.

Call graph: calls 2 internal fn (with_defaults, new); 6 external calls (from_millis, now, assert!, assert_eq!, exec_request, test_pane).

tests::typed_approval_shortcuts_during_delay_stay_in_composer2120–2140 ↗
fn typed_approval_shortcuts_during_delay_stay_in_composer()

Purpose: Checks that approval shortcut letters typed before the approval modal appears are treated as normal composer text.

Data flow: It delays an approval prompt, types letters that might otherwise be approval shortcuts, verifies they appear in the composer, and checks that no approval submission event was sent.

Call relations: This ensures delayed approval requests do not secretly capture user typing before their modal is visible.

Call graph: calls 2 internal fn (with_defaults, new); 7 external calls (now, Char, new, assert!, assert_eq!, exec_request, test_pane_with_disable_paste_burst).

tests::delayed_approval_shortcut_works_after_idle_deadline2143–2169 ↗
fn delayed_approval_shortcut_works_after_idle_deadline()

Purpose: Checks that a delayed approval prompt still accepts the keyboard shortcut after the user has been idle long enough. This protects the case where approval prompts wait briefly so they do not steal keys while the user is still typing.

Data flow: The test creates a fake app-event channel, a pane, and a command approval request. It marks recent composer activity, queues the approval, advances time to the idle deadline, presses y, then reads the outgoing events. The expected result is an approval event saying the command was accepted.

Call relations: The test runner calls this test. Inside it, the pane is built with default features and a test helper, then push_approval_request, pre_draw_tick_at, and handle_key_event are exercised together to prove the delayed prompt becomes active and sends the correct decision.

Call graph: calls 2 internal fn (with_defaults, new); 6 external calls (now, Char, new, assert_eq!, exec_request, test_pane).

tests::dismiss_app_server_request_prunes_delayed_approval2172–2190 ↗
fn dismiss_app_server_request_prunes_delayed_approval()

Purpose: Checks that dismissing an app-server approval request also removes any delayed approval prompt waiting to appear. Without this, an approval modal could pop up after the request was already gone.

Data flow: The test creates a pane, records recent typing activity, and adds an approval request that is delayed. It then dismisses the matching request by id and checks that the delayed request list is empty. After advancing time, the view stack is still empty, meaning no stale prompt appeared.

Call relations: The test runner invokes this scenario to connect push_approval_request, dismiss_app_server_request, and the later pre-draw tick. It verifies that dismissal cleans both visible and not-yet-visible approval state.

Call graph: calls 2 internal fn (with_defaults, new); 4 external calls (now, assert!, exec_request, test_pane).

tests::dismiss_app_server_request_removes_matching_buried_view2193–2219 ↗
fn dismiss_app_server_request_removes_matching_buried_view()

Purpose: Checks that a dismissed approval request can remove a matching view even when that view is underneath another modal. This matters because modal views are stacked like cards, and a lower card may still belong to a request that has been canceled.

Data flow: The test pushes two views onto the pane: a lower view tied to request request-1 and a top view with no matching request. It dismisses request-1. The lower view is removed, while the top view remains.

Call relations: The test runner calls this to exercise dismiss_app_server_request against the pane’s view stack. It proves the dismissal logic searches through the stack, not just the top visible view.

Call graph: calls 1 internal fn (new); 4 external calls (new, assert!, assert_eq!, test_pane).

tests::dismiss_app_server_request_returns_false_when_no_view_matches2222–2248 ↗
fn dismiss_app_server_request_returns_false_when_no_view_matches()

Purpose: Checks that dismissing a request reports failure and changes nothing when no view belongs to that request. This prevents unrelated modals from disappearing by accident.

Data flow: The test pushes two views, neither matching request request-1. It calls the dismissal method and expects false. The view stack length and top view stay the same.

Call relations: The test runner uses this as the negative case for dismiss_app_server_request. Together with the matching-view test, it defines both sides of the expected cleanup behavior.

Call graph: calls 1 internal fn (new); 4 external calls (new, assert!, assert_eq!, test_pane).

tests::completing_top_view_preserves_underlying_view2251–2273 ↗
fn completing_top_view_preserves_underlying_view()

Purpose: Checks that when the top modal view finishes, only that top view is removed. Any view underneath should remain, because it may still be part of another active interaction.

Data flow: The test pushes an underlying dismissible view and a top view that completes when Enter is pressed. It sends an Enter key event. The top view disappears, and the underlying view remains on the stack.

Call relations: The test runner calls this to exercise handle_key_event and the pane’s completed-view cleanup. It confirms that finishing one modal does not wipe out the whole stack.

Call graph: calls 1 internal fn (new); 4 external calls (new, new, assert_eq!, test_pane).

tests::composer_shown_after_denied_while_task_running2276–2341 ↗
fn composer_shown_after_denied_while_task_running()

Purpose: Checks that denying an approval while a task is still running returns the interface to a useful state: the running status plus the composer. This prevents the user from being left with a blank or hidden input area after saying no.

Data flow: The test creates a real BottomPane, starts a running task, shows an approval prompt, and simulates pressing n. It checks that the modal is gone, renders the pane, and looks for both a Working status row and the composer placeholder below it.

Call relations: The test runner invokes this to connect approval handling, task-running state, and rendering. It uses the pane’s event and render paths together to prove the visible layout recovers correctly after denial.

Call graph: calls 4 internal fn (with_defaults, new, new, test_dummy); 10 external calls (empty, from_millis, Char, new, new, new, new, assert!, sleep, exec_request).

tests::status_indicator_visible_during_command_execution2344–2368 ↗
fn status_indicator_visible_during_command_execution()

Purpose: Checks that the bottom pane shows a working status while a command or task is running. This gives the user feedback that the app is busy rather than frozen.

Data flow: The test creates a pane, marks a task as running, renders it into a small terminal buffer, and turns that buffer into text. The expected output contains • Working.

Call relations: The test runner calls this as a direct rendering check for set_task_running followed by render. It confirms that running-task state reaches the visible terminal output.

Call graph: calls 3 internal fn (new, new, test_dummy); 5 external calls (empty, new, new, assert!, snapshot_buffer).

tests::status_and_composer_fill_height_without_bottom_padding2371–2399 ↗
fn status_and_composer_fill_height_without_bottom_padding()

Purpose: Checks that when status and composer are both visible, the pane uses exactly its desired height and does not leave an extra blank row at the bottom. This keeps the terminal layout compact and predictable.

Data flow: The test creates a running pane, asks it for its desired height at a given width, renders exactly that area, and compares the result with a saved snapshot. The snapshot captures the expected rows with no trailing padding.

Call relations: The test runner uses this snapshot test to tie together desired_height and rendering. It guards against layout regressions where height calculation and actual drawing drift apart.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert!, assert_snapshot!).

tests::status_only_snapshot2402–2422 ↗
fn status_only_snapshot()

Purpose: Captures the expected appearance of the pane when only the running status layout is active. Snapshot tests like this catch unintended visual changes.

Data flow: The test creates a pane, marks a task as running, asks for the needed height, renders that area, and compares the rendered text to a stored snapshot.

Call relations: The test runner calls this to verify the combined behavior of task state, height calculation, and drawing. It hands the pane to the snapshot helper, which records the visible result.

Call graph: calls 3 internal fn (new, new, test_dummy); 3 external calls (new, new, assert_snapshot!).

tests::unified_exec_summary_does_not_increase_height_when_status_visible2425–2451 ↗
fn unified_exec_summary_does_not_increase_height_when_status_visible()

Purpose: Checks that adding a summary for background terminal processes does not make the bottom pane taller when the status indicator is already visible. The summary should fit into the existing status area.

Data flow: The test starts a running task, records the pane’s desired height, adds one background process summary, and checks that the height stays the same. It then renders and confirms the summary text appears.

Call relations: The test runner uses this to connect set_task_running, set_unified_exec_processes, desired_height, and rendering. It ensures extra status information is displayed without disrupting layout.

Call graph: calls 3 internal fn (new, new, test_dummy); 6 external calls (new, new, assert!, assert_eq!, render_snapshot, vec!).

tests::status_with_details_and_queued_messages_snapshot2454–2488 ↗
fn status_with_details_and_queued_messages_snapshot()

Purpose: Checks the visual layout when the status has detail lines and there is also queued user input waiting. This protects a busy state where the app needs to show both what it is doing and what the user has queued next.

Data flow: The test starts a task, updates the status with two detail lines, adds a queued follow-up question preview, renders the pane, and compares it to a saved snapshot.

Call relations: The test runner calls this to exercise status updates, queued-input preview state, height calculation, and rendering as one flow. The snapshot records how those pieces should share space.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert_snapshot!, vec!).

tests::queued_messages_visible_when_status_hidden_snapshot2491–2520 ↗
fn queued_messages_visible_when_status_hidden_snapshot()

Purpose: Checks that queued messages still appear when the running status indicator is hidden. This ensures the user can still see pending input even if the status row is not currently displayed.

Data flow: The test starts a task, adds a queued follow-up question, hides the status indicator, renders the pane, and compares the output with a snapshot.

Call relations: The test runner uses this to prove queued-message rendering does not depend on the status indicator being visible. It links set_pending_input_preview, hide_status_indicator, and rendering.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert_snapshot!, vec!).

tests::status_and_queued_messages_snapshot2523–2551 ↗
fn status_and_queued_messages_snapshot()

Purpose: Checks the normal visual layout when both a running status and queued messages are visible. It makes sure the user can see current work and pending follow-up input together.

Data flow: The test creates a pane, marks a task as running, adds a queued question preview, renders the needed area, and compares the result to a snapshot.

Call relations: The test runner invokes this as a baseline snapshot for status plus queued input. It works alongside the hidden-status test to define both display modes.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert_snapshot!, vec!).

tests::remote_images_render_above_composer_text2554–2580 ↗
fn remote_images_render_above_composer_text()

Purpose: Checks that remote image attachments are shown above the composer text. This lets the user see attached images before sending a message.

Data flow: The test creates a pane, sets two remote image URLs, confirms the composer text is still empty, renders the pane, and checks that placeholders for Image #1 and Image #2 appear.

Call relations: The test runner calls this to exercise set_remote_image_urls, desired_height, and rendering. It verifies image attachment state becomes visible without being mixed into the typed text.

Call graph: calls 3 internal fn (new, new, test_dummy); 6 external calls (new, new, assert!, assert_eq!, render_snapshot, vec!).

tests::drain_pending_submission_state_clears_remote_image_urls2583–2603 ↗
fn drain_pending_submission_state_clears_remote_image_urls()

Purpose: Checks that remote image URLs are cleared when pending submission state is drained. This prevents old image attachments from accidentally carrying over to the next message.

Data flow: The test adds one remote image URL and confirms it exists. It then drains the pending submission state and checks that the image URL list is empty.

Call relations: The test runner uses this to verify cleanup after submission. It connects attachment state with drain_pending_submission_state, which prepares the pane for the next user input.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, assert!, assert_eq!, vec!).

tests::esc_with_skill_popup_does_not_interrupt_task2606–2651 ↗
fn esc_with_skill_popup_does_not_interrupt_task()

Purpose: Checks that pressing Escape while a skill popup is open closes the popup instead of interrupting the running task. This avoids turning a local UI cancel action into a task-killing action.

Data flow: The test creates a pane with one skill, starts a task, types $ to open the skill popup, then presses Escape. It reads outgoing events and confirms no interrupt event was sent, and it checks that the popup closed.

Call relations: The test runner invokes this to exercise composer popup behavior, Escape handling, and task interruption rules together. The pane should give the popup first chance to consume Escape.

Call graph: calls 3 internal fn (new, new, test_dummy); 3 external calls (new, assert!, vec!).

tests::esc_with_slash_command_popup_does_not_interrupt_task2654–2686 ↗
fn esc_with_slash_command_popup_does_not_interrupt_task()

Purpose: Checks that pressing Escape while the slash-command popup is active does not interrupt a running task. Escape should be treated as part of editing or dismissing the command UI first.

Data flow: The test starts a running task, types / to open the command popup, presses Escape, then checks the event channel for absence of an interrupt. The composer text remains /.

Call relations: The test runner calls this to verify the priority order in handle_key_event: active composer popups come before global task interruption.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert!, assert_eq!).

tests::esc_with_agent_command_without_popup_does_not_interrupt_task2689–2722 ↗
fn esc_with_agent_command_without_popup_does_not_interrupt_task()

Purpose: Checks that typing an agent command draft, even after its popup has closed, keeps Escape local and does not interrupt a running task. This protects the user while editing /agent ... text.

Data flow: The test starts a task, inserts /agent , verifies no popup is active, presses Escape, and checks that no interrupt event was sent. The composer text remains unchanged.

Call relations: The test runner uses this to cover a subtle state: command text is present but the popup is gone. It confirms interruption logic also considers special draft text, not only visible popups.

Call graph: calls 3 internal fn (new, new, test_dummy); 4 external calls (new, new, assert!, assert_eq!).

tests::esc_release_after_dismissing_agent_picker_does_not_interrupt_task2725–2770 ↗
fn esc_release_after_dismissing_agent_picker_does_not_interrupt_task()

Purpose: Checks that the key-release event after pressing Escape to close a selection view does not accidentally interrupt a running task. Some terminals send both press and release events, so both must be handled safely.

Data flow: The test starts a task, shows a selection view, sends an Escape press followed by an Escape release, then inspects outgoing events. No interrupt is sent, and the selection view is gone.

Call relations: The test runner invokes this to exercise modal dismissal and key event kinds. It ensures the pane ignores or safely routes the release event after the press already closed the modal.

Call graph: calls 3 internal fn (new, new, test_dummy); 5 external calls (default, new_with_kind, new, assert!, vec!).

tests::esc_interrupts_running_task_when_no_popup2773–2795 ↗
fn esc_interrupts_running_task_when_no_popup()

Purpose: Checks the intended global behavior: if a task is running and no popup or modal is active, Escape interrupts the task. This gives the user a quick way to stop ongoing work.

Data flow: The test starts a task in a clean pane, presses Escape, and reads the event channel. The expected output is a Codex interrupt operation.

Call relations: The test runner calls this as the positive counterpart to the popup cases. It proves Escape is still an interrupt shortcut when nothing local needs to consume it first.

Call graph: calls 3 internal fn (new, new, test_dummy); 3 external calls (new, new, assert!).

tests::remapped_interrupt_turn_uses_configured_key_including_agent_drafts2798–2819 ↗
fn remapped_interrupt_turn_uses_configured_key_including_agent_drafts()

Purpose: Checks that if the interrupt shortcut is remapped, the pane respects the new key even while editing an agent command draft. This lets users customize shortcuts without losing consistent behavior.

Data flow: The test changes the interrupt key from Escape to F12, starts a task, and inserts /agent . Pressing Escape sends no event. Pressing F12 sends the interrupt event.

Call relations: The test runner uses this to connect runtime keymap settings with handle_key_event. It proves interruption is driven by configured bindings, not hard-coded only to Escape.

Call graph: calls 2 internal fn (new, defaults); 5 external calls (F, new, assert!, test_pane, vec!).

tests::selection_view_esc_respects_remapped_list_cancel2822–2850 ↗
fn selection_view_esc_respects_remapped_list_cancel()

Purpose: Checks that a selection list uses the configured cancel key instead of always treating Escape as cancel. This matters for users who remap list navigation keys.

Data flow: The test changes the list cancel key to q, shows a selection view with an on_cancel callback, then presses Escape. The view remains and no callback event is sent. Pressing q closes the view and sends the callback event.

Call relations: The test runner invokes this to verify selection-view cancellation follows the runtime keymap. It ties together modal state, configured bindings, and cancel callbacks.

Call graph: calls 2 internal fn (new, defaults); 7 external calls (new, default, Char, new, assert!, test_pane, vec!).

tests::esc_routes_to_handle_key_event_when_requested2853–2909 ↗
fn esc_routes_to_handle_key_event_when_requested()

Purpose: Checks that a modal view can ask to receive Escape through its normal key handler instead of through the pane’s cancellation path. This gives special views control over what Escape means.

Data flow: The test defines a small fake view that counts calls to handle_key_event and on_ctrl_c, and says it prefers Escape as a normal key event. After pushing the view and pressing Escape, the normal handler count is one and the cancellation count is zero.

Call relations: The test runner calls this to exercise the prefer_esc_to_handle_key_event choice on active views. It proves the pane consults the view before deciding how to route Escape.

Call graph: calls 3 internal fn (new, new, test_dummy); 7 external calls (new, new, new, clone, new, new, assert_eq!).

tests::release_events_are_ignored_for_active_view2912–2963 ↗
fn release_events_are_ignored_for_active_view()

Purpose: Checks that key-release events are not forwarded to the active view as ordinary input. This avoids duplicate actions when terminals report both key press and key release.

Data flow: The test pushes a fake view that counts key-handler calls. It sends a Down-arrow press and then a Down-arrow release. Only the press reaches the view, so the count is one.

Call relations: The test runner invokes this to verify event filtering before dispatching to modal views. It protects all active views from receiving duplicate release events.

Call graph: calls 3 internal fn (new, new, test_dummy); 7 external calls (new, new, new_with_kind, clone, new, new, assert_eq!).

tests::paste_completion_clears_stacked_views_and_restores_composer_input2966–3044 ↗
fn paste_completion_clears_stacked_views_and_restores_composer_input()

Purpose: Checks that if a paste completes the active modal flow, the pane clears the stacked views and restores normal composer input. This prevents a finished paste-driven modal from leaving hidden blockers behind.

Data flow: The test disables composer input, pushes a lower blocking view and a top view that marks itself complete when it receives pasted text, then pastes hello. The stack becomes empty. A later key event does not reach the old lower view, and the composer has a cursor again.

Call relations: The test runner uses this to exercise handle_paste, completed-view cleanup, and composer restoration together. It proves a completed top view can tear down the modal flow cleanly instead of exposing stale underlying views.

Call graph: calls 3 internal fn (new, new, test_dummy); 10 external calls (new, new, new, default, clone, new, new, new, assert!, assert_eq!).

tui/src/public_widgets/composer_input.rssource ↗
orchestrationmain loop input handling and rendering

The internal ChatComposer is a full-featured text box used by the terminal interface. This file wraps it in a smaller public type called ComposerInput, like putting a complex machine behind a few clear buttons. Other crates can create one, feed it key presses or pasted text, ask how tall it wants to be, draw it on screen, and learn when the user has submitted text.

The wrapper also hides some internal plumbing. ChatComposer expects an app event sender, so ComposerInput::new creates a private message channel for those events. Because outside users of this widget do not need those internal events, the wrapper drains and discards them after input, paste, or paste-flush work. This keeps the inner composer happy while presenting a clean interface.

The main result type is ComposerAction. When a key press submits text, callers receive Submitted(String). Otherwise they receive None, meaning the input changed or did nothing but no final text was sent yet.

A notable behavior is paste-burst support. Large pasted text may arrive as many rapid key-like events, so the composer can treat that burst specially and flush it after a short delay. This avoids awkward redraws or mistaken interpretation of pasted newlines.

Function details15
ComposerInput::new36–48 ↗
fn new() -> Self

Purpose: Creates a ready-to-use composer input with a default placeholder. It also sets up the private event channel needed by the inner composer.

Data flow: It starts with no outside input. It creates a sender and receiver channel, wraps the sender in an AppEventSender, builds a focused ChatComposer with enhanced key support enabled, then returns a ComposerInput containing the composer and the private receiver.

Call relations: This is the setup step for the widget. It is used when code directly constructs a composer input, and it is also the behavior behind the default constructor. Tests create it to check that typed characters render correctly.

Call graph: calls 2 internal fn (new, new); called by 2 (new, composer_input_renders_typed_characters); 1 external calls (unbounded_channel).

ComposerInput::is_empty51–53 ↗
fn is_empty(&self) -> bool

Purpose: Tells callers whether the text box currently has any text. This is useful for deciding whether there is anything to submit, clear, or warn about.

Data flow: It reads the current state of the inner composer and returns a simple true-or-false answer. It does not change the widget.

Call relations: This is a small query method. It delegates the real check to the internal ChatComposer so outside callers do not need to inspect the composer directly.

Call graph: calls 1 internal fn (is_empty).

ComposerInput::clear56–59 ↗
fn clear(&mut self)

Purpose: Erases all text from the input. A caller would use this after a submission or when resetting the form.

Data flow: It takes the current text state, replaces it with an empty string, and clears the related internal text metadata by passing empty lists. It returns nothing, but the composer is left blank.

Call relations: This is part of the public control surface of the wrapper. It relies on the inner composer’s text-setting method rather than editing fields directly.

Call graph: calls 1 internal fn (set_text_content); 2 external calls (new, new).

ComposerInput::input62–69 ↗
fn input(&mut self, key: KeyEvent) -> ComposerAction

Purpose: Feeds one keyboard event into the composer and reports whether that event submitted text. This is the main method a terminal event loop uses when the user presses a key.

Data flow: It receives a KeyEvent, gives it to the inner composer, and looks at the result. If the composer says text was submitted, it returns ComposerAction::Submitted with that text; otherwise it returns ComposerAction::None. Before finishing, it drains any private app events produced by the inner composer.

Call relations: This sits between the outer terminal event loop and the internal composer. It hands key presses inward, translates the composer’s detailed result into the simpler public ComposerAction, then calls drain_app_events to hide internal event traffic.

Call graph: calls 2 internal fn (handle_key_event, drain_app_events); 1 external calls (Submitted).

ComposerInput::handle_paste71–75 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Gives pasted text to the composer using its paste-aware behavior. This lets the widget treat pasted blocks differently from normal typing when needed.

Data flow: It receives a pasted string, passes it to the inner composer, and gets back whether the paste was accepted or processed. It then drains private app events and returns that true-or-false result to the caller.

Call relations: This is used by code that receives paste events separately from normal key presses. Like input, it delegates the real work to ChatComposer and then calls drain_app_events so internal notifications do not leak out.

Call graph: calls 2 internal fn (handle_paste, drain_app_events).

ComposerInput::set_hint_items79–85 ↗
fn set_hint_items(&mut self, items: Vec<(impl Into<String>, impl Into<String>)>)

Purpose: Replaces the footer hints shown under the input with caller-provided hints. These hints are the small labels that tell the user what keys do, such as a key name followed by an action.

Data flow: It receives a list of key-label pairs that can be turned into strings. It converts each pair into owned strings and passes the list to the inner composer as a footer override. It returns nothing, but future rendering uses the custom hints.

Call relations: This is a customization hook for screens that reuse the composer but want different instructions. It hands the finished hint list to the inner composer’s footer override setting.

Call graph: calls 1 internal fn (set_footer_hint_override).

ComposerInput::clear_hint_items88–90 ↗
fn clear_hint_items(&mut self)

Purpose: Removes any custom footer hints and restores the composer’s normal hints. Callers use this when a temporary UI mode ends.

Data flow: It sends None as the footer hint override to the inner composer. Nothing is returned, but the next render falls back to the default hints.

Call relations: This is the counterpart to set_hint_items. It does not rebuild the widget; it simply tells the inner composer to stop using an override.

Call graph: calls 1 internal fn (set_footer_hint_override).

ComposerInput::desired_height93–95 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Asks how many terminal rows the input would like for a given width. This helps the surrounding layout reserve enough space for multi-line text.

Data flow: It receives a width in terminal columns, asks the inner composer to calculate its needed height at that width, and returns the row count. It does not change the widget.

Call relations: A layout system can call this before rendering. The wrapper forwards the question to ChatComposer, which knows how its text wraps and how much footer space it needs.

Call graph: calls 1 internal fn (desired_height).

ComposerInput::cursor_pos98–100 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Computes where the text cursor should appear on screen. This lets the terminal UI place the visible cursor in the right spot after drawing.

Data flow: It receives a rectangular screen area, asks the inner composer where the cursor belongs inside that area, and returns either coordinates or nothing if no cursor should be shown.

Call relations: This is used after layout has chosen the widget’s area. The wrapper relies on the inner composer’s cursor math, because that code understands wrapping, scrolling, and the current text position.

Call graph: calls 1 internal fn (cursor_pos).

ComposerInput::render_ref103–105 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the composer input into a terminal buffer. The buffer is the off-screen drawing surface that later becomes visible in the terminal.

Data flow: It receives a screen rectangle and a mutable buffer. It asks the inner composer to render itself into that rectangle, which changes the buffer contents but returns no separate value.

Call relations: This is the drawing entry point for code that embeds the widget. It passes rendering straight through to ChatComposer, which knows how to draw the input text, borders or styling, and footer hints.

Call graph: calls 1 internal fn (render).

ComposerInput::is_in_paste_burst108–110 ↗
fn is_in_paste_burst(&self) -> bool

Purpose: Reports whether the composer is currently treating incoming text as part of a rapid paste burst. This helps callers decide whether to schedule a short follow-up flush.

Data flow: It reads the paste-burst state from the inner composer and returns true or false. It does not modify the input.

Call relations: This is a status check used around paste handling. The wrapper exposes the inner composer’s paste-detection state without exposing the whole internal composer.

Call graph: calls 1 internal fn (is_in_paste_burst).

ComposerInput::flush_paste_burst_if_due114–118 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

Purpose: Finishes processing a pending paste burst once enough time has passed. It tells the caller whether the text changed and the screen should be redrawn.

Data flow: It asks the inner composer to flush any paste text whose timeout has expired. It then drains private app events and returns whether a flush actually changed the text.

Call relations: This is called by an outer loop that is watching paste-burst timing. It delegates the timing decision and text update to ChatComposer, then uses drain_app_events to discard internal notifications.

Call graph: calls 2 internal fn (flush_paste_burst_if_due, drain_app_events).

ComposerInput::drain_app_events126–128 ↗
fn drain_app_events(&mut self)

Purpose: Empties the private internal event queue used only to satisfy the inner composer. This prevents unused internal events from piling up.

Data flow: It repeatedly tries to receive messages from the private receiver. Each message is discarded. When no message is immediately available, it stops and returns nothing.

Call relations: This is an internal cleanup helper. It is called after key input, paste handling, and paste flushing, because those operations may cause ChatComposer to send app events that this public wrapper intentionally hides.

Call graph: called by 3 (flush_paste_burst_if_due, handle_paste, input); 1 external calls (try_recv).

ComposerInput::default132–134 ↗
fn default() -> Self

Purpose: Provides the standard default way to create a ComposerInput. It makes the type convenient to use in code that expects a default constructor.

Data flow: It receives no input and returns a newly created composer input by using the same setup as ComposerInput::new.

Call relations: This connects Rust’s Default convention to the widget’s normal constructor. Any code asking for a default ComposerInput gets the same initialized widget that new creates.

Call graph: 1 external calls (new).

Composer text engine and input state

This group builds the reusable text-editing foundation and the low-level state pieces that the chat composer uses to manage drafts, paste behavior, attachments, and footer/popup presentation.

tui/src/bottom_pane/paste_burst.rssource ↗
domain_logicrequest handling

Some terminals, especially on Windows, do not send a single clear “paste happened” signal. Instead, pasted text can arrive as many ordinary key presses very quickly. That creates problems: a pasted question mark could open help, a pasted Enter could submit the message, and the screen may flicker as text is first shown as typing and then reclassified as paste.

This file defines PasteBurst, a small state machine. A state machine is like a traffic light for input: it remembers recent events and decides what should happen next. The composer feeds it plain character keys and timestamps. If characters arrive slowly, they are treated as normal typing. If several arrive very close together, they are treated as one paste. For the first fast ASCII character, it may briefly hold the character back so the UI does not show it and then immediately move it into a paste.

The file also keeps a short “Enter suppression” window. During and just after a suspected paste, Enter means “insert a newline” rather than “send the message,” so multiline pasted text stays intact. Importantly, this code never edits the text box directly. It only returns decisions, and the caller applies those decisions to the UI.

Function details25
PasteBurst::on_plain_char218–248 ↗
fn on_plain_char(&mut self, ch: char, now: Instant) -> CharDecision

Purpose: This is the main decision point for ordinary ASCII character input. It decides whether the character should be held briefly, added to an active paste buffer, or used to begin a new paste burst.

Data flow: It receives a character and the current time. It updates the record of recent character timing, checks whether a burst is already active or whether characters are arriving very quickly, and returns a CharDecision telling the caller what to do next. It may also store a first character temporarily or start a paste buffer.

Call relations: The composer calls this for plain ASCII characters. Internally it calls note_plain_char to update the timing counter, then hands a decision back to the caller, which is responsible for inserting, buffering, or withholding the character.

Call graph: calls 1 internal fn (note_plain_char); 1 external calls (duration_since).

PasteBurst::on_plain_char_no_hold256–271 ↗
fn on_plain_char_no_hold(&mut self, now: Instant) -> Option<CharDecision>

Purpose: This is a version of on_plain_char for non-ASCII input or input method editor text, where hiding the first character would feel like the app dropped it. It still detects fast paste-like streams but never asks the caller to hold the first character.

Data flow: It receives the current time. It updates the recent-character timing, checks whether buffering is already active or whether enough fast characters have arrived, and returns either a paste-buffering decision or None for normal insertion.

Call relations: handle_key_event_at uses this for character input paths that should not delay display. It calls note_plain_char and then returns a simpler decision set to the caller.

Call graph: calls 1 internal fn (note_plain_char); called by 1 (handle_key_event_at).

PasteBurst::note_plain_char273–282 ↗
fn note_plain_char(&mut self, now: Instant)

Purpose: This updates the detector’s memory of how quickly plain characters are arriving. It is the timing counter that lets the rest of the file recognize paste-like bursts.

Data flow: It receives the current time. It compares that time with the previous plain character time; if the gap is short, it increases the consecutive-fast-character count, otherwise it starts the count over at one. It then records the new time.

Call relations: Both character entry points, on_plain_char and on_plain_char_no_hold, call this before making their paste-or-typing decision.

Call graph: called by 2 (on_plain_char, on_plain_char_no_hold); 1 external calls (duration_since).

PasteBurst::flush_if_due293–317 ↗
fn flush_if_due(&mut self, now: Instant) -> FlushResult

Purpose: This checks whether enough time has passed to stop waiting and release buffered input. It either emits a full paste, releases one held typed character, or says nothing is ready yet.

Data flow: It receives the current time. It compares that time with the last plain character time using the right timeout for the current state. If an active buffer has gone idle, it empties the buffer and returns it as Paste; if only one first character was being held, it returns that as Typed; otherwise it returns None.

Call relations: The UI calls this on ticks. It uses is_active_internal to decide which timeout applies, then hands the caller either pasted text or a normal typed character to apply to the text box.

Call graph: calls 1 internal fn (is_active_internal); 3 external calls (take, Paste, Typed).

PasteBurst::append_newline_if_active324–332 ↗
fn append_newline_if_active(&mut self, now: Instant) -> bool

Purpose: This adds a newline to the paste buffer when Enter happens during a paste burst. It stops pasted multiline text from being mistaken for a request to send the message.

Data flow: It receives the current time. If the detector is in a paste-related active state, it appends \n to the buffer, extends the Enter-suppression window, and returns true; otherwise it returns false and changes nothing.

Call relations: Enter-handling code can call this before submitting. It asks is_active whether a paste burst is in progress and, if so, keeps the newline inside the buffered paste.

Call graph: calls 1 internal fn (is_active).

PasteBurst::newline_should_insert_instead_of_submit335–338 ↗
fn newline_should_insert_instead_of_submit(&self, now: Instant) -> bool

Purpose: This answers the question, “Should Enter make a newline rather than send the message?” It is used to protect multiline paste input during and shortly after a burst.

Data flow: It receives the current time. It checks whether the detector is active or whether the current time is still inside the short Enter-suppression window, and returns a boolean answer.

Call relations: direct_insert_newline_should_insert builds on this. Other Enter logic can use it to decide whether Enter belongs to pasted content instead of the composer’s submit action.

Call graph: calls 1 internal fn (is_active); called by 1 (direct_insert_newline_should_insert).

PasteBurst::direct_insert_newline_should_insert341–346 ↗
fn direct_insert_newline_should_insert(&self, now: Instant) -> bool

Purpose: This is an Enter decision helper for code paths that insert characters immediately rather than fully buffering them. It is slightly more cautious and treats Enter as a newline if it arrives right after a fast character.

Data flow: It receives the current time. It first checks newline_should_insert_instead_of_submit, then also checks whether the last plain character was recent enough to look like a burst. It returns true for newline insertion or false for normal submit behavior.

Call relations: handle_key_event_at calls this in its Enter handler when it needs a quick yes-or-no decision without going through the full buffering flow.

Call graph: calls 1 internal fn (newline_should_insert_instead_of_submit); called by 1 (handle_key_event_at).

PasteBurst::extend_window349–351 ↗
fn extend_window(&mut self, now: Instant)

Purpose: This keeps the short paste-related Enter-suppression window alive. It is used when the caller sees input that should continue to count as part of a burst-like sequence.

Data flow: It receives the current time and sets the suppression deadline to a short time after it. It returns nothing.

Call relations: handle_key_event_at calls this when Enter or burst-like direct input should keep Enter behaving as “insert newline” for a little longer.

Call graph: called by 1 (handle_key_event_at).

PasteBurst::begin_with_retro_grabbed354–360 ↗
fn begin_with_retro_grabbed(&mut self, grabbed: String, now: Instant)

Purpose: This starts a paste buffer using text that was already inserted into the UI but is now being reclassified as part of a paste. It supports “retro capture,” meaning the app can pull recent text back into the paste buffer.

Data flow: It receives the already-grabbed string and the current time. If the string is not empty, it appends it to the internal buffer, marks buffering as active, and extends the Enter-suppression window. It returns nothing.

Call relations: decide_begin_buffer calls this after deciding that recently typed-looking text actually looks like the start of a paste.

Call graph: called by 1 (decide_begin_buffer).

PasteBurst::append_char_to_buffer363–366 ↗
fn append_char_to_buffer(&mut self, ch: char, now: Instant)

Purpose: This adds one character to the current paste buffer. Callers use it after the detector has already decided that the character belongs to a paste burst.

Data flow: It receives a character and the current time. It appends the character to the internal buffer and refreshes the Enter-suppression window. It returns nothing.

Call relations: Caller code uses this after decisions such as BufferAppend or BeginBufferFromPending. try_append_char_if_active also calls it when it can safely capture a character into an existing burst.

Call graph: called by 1 (try_append_char_if_active).

PasteBurst::try_append_char_if_active371–378 ↗
fn try_append_char_if_active(&mut self, ch: char, now: Instant) -> bool

Purpose: This is a safe convenience helper: it appends a character only if a paste buffer is already in progress. It lets callers try to join a character to an existing burst without accidentally starting one.

Data flow: It receives a character and the current time. If buffering is active or the buffer already has text, it appends the character through append_char_to_buffer and returns true; otherwise it leaves state unchanged and returns false.

Call relations: Code that is unsure whether a character should join a paste can call this. When the answer is yes, it delegates the actual append work to append_char_to_buffer.

Call graph: calls 1 internal fn (append_char_to_buffer).

PasteBurst::decide_begin_buffer391–411 ↗
fn decide_begin_buffer(
        &mut self,
        now: Instant,
        before: &str,
        retro_chars: usize,
    ) -> Option<RetroGrab>

Purpose: This decides whether recently inserted text should be pulled back out of the UI and treated as the beginning of a paste. It avoids doing that for short ordinary words, but does it for text that looks paste-like, such as text with whitespace or a long run of characters.

Data flow: It receives the current time, the text before the cursor, and how many recent characters might belong to the burst. It finds the byte position where those characters start, copies that slice, checks whether it looks like paste, and if so starts buffering with it and returns a RetroGrab describing what to remove from the UI. If not, it returns None.

Call relations: Callers use this after a BeginBuffer decision. It calls retro_start_index to convert a character count into a safe string position, then calls begin_with_retro_grabbed if retro capture should happen.

Call graph: calls 2 internal fn (begin_with_retro_grabbed, retro_start_index).

PasteBurst::flush_before_modified_input414–424 ↗
fn flush_before_modified_input(&mut self) -> Option<String>

Purpose: This immediately releases any paste-related buffered text before the app handles an unrelated key, such as an arrow key or a modified shortcut. It prevents hidden buffered text from being stranded or incorrectly grouped with later typing.

Data flow: It takes no input. If no paste-related state is active, it returns None. Otherwise it marks buffering inactive, takes the buffered text, appends any held first character, and returns the combined string for the caller to apply.

Call relations: Input-handling code should call this before non-character or modified input. It uses is_active to decide whether anything needs flushing, then hands the text back to the caller.

Call graph: calls 1 internal fn (is_active); 1 external calls (take).

PasteBurst::clear_window_after_non_char430–436 ↗
fn clear_window_after_non_char(&mut self)

Purpose: This clears the paste-detection timing window after an unrelated key input. It stops the next typed character from being mistakenly grouped into an old burst.

Data flow: It takes no input. It resets the consecutive-character count, last character time, suppression window, active flag, and any held first character. It does not return anything.

Call relations: Callers use this after they have already flushed anything important with another method. It is a cleanup step for classification state, not a way to emit buffered text.

PasteBurst::is_active441–443 ↗
fn is_active(&self) -> bool

Purpose: This reports whether the detector is in any temporary paste-related state. That includes active buffering, a non-empty buffer, or a first character being held briefly.

Data flow: It takes no input. It checks the internal active/buffer state and whether a pending first character exists, then returns true or false.

Call relations: append_newline_if_active, flush_before_modified_input, and newline_should_insert_instead_of_submit use this to decide whether paste-aware behavior should apply.

Call graph: calls 1 internal fn (is_active_internal); called by 3 (append_newline_if_active, flush_before_modified_input, newline_should_insert_instead_of_submit).

PasteBurst::is_active_internal445–447 ↗
fn is_active_internal(&self) -> bool

Purpose: This is the narrower active-state check used inside the detector. It ignores a merely held first character and only asks whether a paste buffer is active or non-empty.

Data flow: It takes no input. It returns whether the active flag is set or the internal buffer contains text. It changes nothing.

Call relations: flush_if_due uses this to choose the correct timeout and decide whether to emit a paste. is_active builds on it by also considering the pending first character.

Call graph: called by 2 (flush_if_due, is_active).

PasteBurst::clear_after_explicit_paste449–456 ↗
fn clear_after_explicit_paste(&mut self)

Purpose: This fully resets paste-burst detection after the app receives or applies a clear, explicit paste event. It makes sure old timing and buffered data do not affect later typing.

Data flow: It takes no input. It clears timing, counters, the Enter-suppression window, the active flag, the buffer, and any pending first character. It returns nothing.

Call relations: handle_key_event_at and handle_paste call this when paste handling is already known and complete, so the burst detector can start fresh.

Call graph: called by 2 (handle_key_event_at, handle_paste).

retro_start_index459–469 ↗
fn retro_start_index(before: &str, retro_chars: usize) -> usize

Purpose: This finds where to start slicing a UTF-8 string when retroactively grabbing a certain number of characters before the cursor. It matters because characters can be more than one byte, so byte positions must be chosen carefully.

Data flow: It receives a string slice and a number of characters to step backward. If the count is zero, it returns the end of the string. Otherwise it walks character boundaries from the end and returns the byte index where the requested number of characters begins, or zero if the string is shorter than requested.

Call relations: decide_begin_buffer calls this before removing already-inserted text from the UI. It supplies the safe starting byte for the RetroGrab result.

Call graph: called by 1 (decide_begin_buffer).

tests::ascii_first_char_is_held_then_flushes_as_typed479–490 ↗
fn ascii_first_char_is_held_then_flushes_as_typed()

Purpose: This test proves that one fast ASCII character is only held briefly and then becomes normal typed input if no paste follows. It protects against a bug where a single typed key might disappear or be treated as paste.

Data flow: It creates a fresh detector, sends one character at a starting time, waits beyond the recommended flush delay, and checks that flushing returns Typed('a'). It also checks that the detector is no longer active.

Call relations: The test calls PasteBurst::recommended_flush_delay to cross the timeout boundary and exercises the public character and flush flow used by the UI.

Call graph: calls 1 internal fn (recommended_flush_delay); 4 external calls (from_millis, now, assert!, default).

tests::ascii_two_fast_chars_start_buffer_from_pending_and_flush_as_paste495–515 ↗
fn ascii_two_fast_chars_start_buffer_from_pending_and_flush_as_paste()

Purpose: This test proves that two ASCII characters arriving very quickly are grouped as a paste without first rendering the initial character. It protects the flicker-suppression behavior.

Data flow: It creates a detector, sends a, then sends b one millisecond later. It checks that buffering starts from the held first character, appends b, waits past the active flush delay, and checks that the output paste is ab.

Call relations: The test uses PasteBurst::recommended_active_flush_delay and follows the same caller contract the composer uses: interpret the decision, append to the buffer, then flush.

Call graph: calls 1 internal fn (recommended_active_flush_delay); 4 external calls (from_millis, now, assert!, default).

tests::flush_before_modified_input_includes_pending_first_char520–530 ↗
fn flush_before_modified_input_includes_pending_first_char()

Purpose: This test proves that a held first character is not lost when an unrelated key is about to be handled. It protects the cleanup path used before modified or non-character input.

Data flow: It creates a detector, sends one ASCII character so it is held, then calls flush_before_modified_input. It expects to receive the string a and verifies that the detector is inactive afterward.

Call relations: The test directly exercises the pre-non-character flush path that input handling should use before moving on to unrelated key behavior.

Call graph: 4 external calls (now, assert!, assert_eq!, default).

tests::decide_begin_buffer_only_triggers_for_pastey_prefixes535–552 ↗
fn decide_begin_buffer_only_triggers_for_pastey_prefixes()

Purpose: This test proves that retro capture is selective. Short ordinary text should not be pulled back into a paste buffer, but text with whitespace should be considered paste-like.

Data flow: It creates a detector and asks it to retro-capture from ab, expecting no capture. Then it asks using a b, expecting capture of the trailing b, the correct start byte, and an active detector.

Call relations: The test exercises decide_begin_buffer, including its use of safe character-based slicing and its call into the buffer-starting path.

Call graph: 4 external calls (now, assert!, assert_eq!, default).

tests::newline_suppression_window_outlives_buffer_flush557–579 ↗
fn newline_suppression_window_outlives_buffer_flush()

Purpose: This test proves that Enter remains protected briefly even after a paste buffer has flushed. It protects multiline paste behavior where Enter may arrive slightly later than the text.

Data flow: It simulates a fast two-character paste, flushes it as ab, then checks that Enter would still insert a newline at the flush time. After the suppression window expires, it checks that Enter would no longer be treated that way.

Call relations: The test follows the same burst creation and flushing flow as the composer, then checks newline_should_insert_instead_of_submit to verify the post-paste Enter window.

Call graph: calls 1 internal fn (recommended_active_flush_delay); 4 external calls (from_millis, now, assert!, default).

tui/src/bottom_pane/textarea/vim.rssource ↗
domain_logicediting keypress handling

This file teaches the editor how to understand a small but important part of Vim behavior: text objects. In Vim, a command is often built like a sentence. For example, “daw” means “delete around word,” and “ci)” means “change inside parentheses.” To make that work, the editor must turn key presses into ideas like “inner” versus “around,” “word” versus “quotes,” and then find the matching byte range in the text.

The file defines the small set of Vim concepts it needs: modes, operators, motions, text object scopes, and text object kinds. The main work happens inside TextArea methods. First, key events are matched against configured key bindings. Then text_object_range dispatches to the right range-finding logic: words, bracket-like pairs, or quote pairs.

The word logic separates “big words” from smaller word pieces. A big word is any run of non-whitespace text, while a small word can split punctuation and word parts more carefully. “Around” word selection expands to include nearby whitespace, similar to how Vim avoids leaving awkward double spaces after deletion.

For parentheses, brackets, and braces, the code scans the text with a stack, like matching opening and closing doors. For quotes, it searches only the current line and ignores escaped quotes, so " does not count as the end of a string. It also skips special embedded elements in the text area, so hidden or structured spans do not confuse text selection.

Function details17
TextArea::vim_text_object_scope_for_event64–83 ↗
fn vim_text_object_scope_for_event(
        &self,
        event: KeyEvent,
    ) -> Option<VimTextObjectScope>

Purpose: This turns a key press into the Vim idea of “inner” or “around.” A caller uses it after an operator key, such as delete or change, to know whether the command should affect only the contents or also surrounding characters like spaces or brackets.

Data flow: It receives a keyboard event and reads the text area’s Vim operator key bindings. If the event matches the configured “inner text object” key, it returns Inner; if it matches “around text object,” it returns Around; otherwise it returns nothing.

Call relations: This is part of the first step in reading a Vim-style command. Higher-level input code asks it what kind of text object scope the user requested before later asking for the actual text object and range.

TextArea::vim_text_object_for_event85–111 ↗
fn vim_text_object_for_event(&self, event: KeyEvent) -> Option<VimTextObject>

Purpose: This turns a key press into the kind of Vim text object the user named, such as a word, parentheses, brackets, braces, or quotes. It is the bridge between raw keyboard input and a meaningful editing target.

Data flow: It receives a keyboard event and checks it against the configured text-object key bindings. The first matching binding becomes a VimTextObject value; if no binding matches, it returns nothing.

Call relations: This is used when the editor is waiting for the final part of a Vim command, such as the w in “delete around word.” Once this identifies the object, other code can call TextArea::text_object_range to find the actual text span.

TextArea::text_object_range113–128 ↗
fn text_object_range(
        &self,
        object: VimTextObject,
        scope: VimTextObjectScope,
    ) -> Option<Range<usize>>

Purpose: This chooses the correct range-finding rule for a requested Vim text object. It answers the practical question: “Given the cursor and the command, which exact part of the text should be selected, deleted, yanked, or changed?”

Data flow: It receives a text object kind and a scope, then looks at the current text and cursor through helper methods. Word objects are sent to TextArea::word_text_object_range, bracket-like objects to TextArea::paired_text_object_range, and quote objects to TextArea::quoted_text_object_range. It returns a byte range if a matching target is found.

Call relations: This is the central dispatcher after key interpretation has already decided what the user asked for. It hands off to specialized helpers because words, paired delimiters, and quotes all need different rules.

Call graph: calls 3 internal fn (paired_text_object_range, quoted_text_object_range, word_text_object_range).

TextArea::word_text_object_range130–144 ↗
fn word_text_object_range(
        &self,
        scope: VimTextObjectScope,
        big_word: bool,
    ) -> Option<Range<usize>>

Purpose: This finds the word under or just before the cursor, using either Vim’s small-word or big-word behavior. It also applies the difference between “inner word” and “around word.”

Data flow: It receives the requested scope and a flag saying whether to use big-word rules. It finds the inner word range through TextArea::big_word_range_at_cursor or TextArea::small_word_range_at_cursor. If the scope is Inner, it returns that range as-is; if the scope is Around, it expands the range with TextArea::expand_word_around to include nearby whitespace.

Call relations: It is called by TextArea::text_object_range when the requested object is a word or big word. It then delegates the details of locating and expanding that word to smaller helpers.

Call graph: calls 3 internal fn (big_word_range_at_cursor, expand_word_around, small_word_range_at_cursor); called by 1 (text_object_range).

TextArea::big_word_range_at_cursor146–150 ↗
fn big_word_range_at_cursor(&self) -> Option<Range<usize>>

Purpose: This finds a Vim “big word” at the cursor. In this file, a big word means one continuous stretch of non-whitespace characters, so punctuation stays attached instead of being split apart.

Data flow: It reads the full text and cursor position. First it asks TextArea::non_ws_runs for all stretches that are not whitespace. Then it returns the first stretch that contains the cursor or ends exactly at the cursor.

Call relations: It is called by TextArea::word_text_object_range when the user asks for a big-word text object. It relies on TextArea::non_ws_runs to do the basic scanning.

Call graph: calls 1 internal fn (non_ws_runs); called by 1 (word_text_object_range).

TextArea::small_word_range_at_cursor152–171 ↗
fn small_word_range_at_cursor(&self) -> Option<Range<usize>>

Purpose: This finds a smaller Vim-style word piece at the cursor. Unlike a big word, it can split a non-whitespace run into more precise pieces, such as separating punctuation from letters.

Data flow: It reads the full text and cursor position. It gets non-whitespace runs from TextArea::non_ws_runs, skips runs the cursor is not touching, and then uses split_word_pieces to break a run into smaller parts. It returns the piece under the cursor, the last piece if the cursor is exactly at the run’s end, or the whole run as a fallback.

Call relations: It is called by TextArea::word_text_object_range for normal word text objects. It uses TextArea::cursor_overlaps_range and TextArea::cursor_is_at_range_end to decide whether the cursor should count as being on a candidate range.

Call graph: calls 3 internal fn (cursor_is_at_range_end, cursor_overlaps_range, non_ws_runs); called by 1 (word_text_object_range); 1 external calls (split_word_pieces).

TextArea::non_ws_runs173–189 ↗
fn non_ws_runs(&self) -> Vec<Range<usize>>

Purpose: This breaks the text into stretches of non-whitespace characters. It provides the raw material for both small-word and big-word selection.

Data flow: It reads the text from start to end, remembering where a non-whitespace stretch begins. Whenever whitespace is reached, it closes the current stretch and stores its range. At the end, it returns all collected ranges.

Call relations: It is used by TextArea::big_word_range_at_cursor and TextArea::small_word_range_at_cursor. Those functions build more Vim-specific word behavior on top of these simple non-space runs.

Call graph: called by 2 (big_word_range_at_cursor, small_word_range_at_cursor); 1 external calls (new).

TextArea::cursor_overlaps_range191–193 ↗
fn cursor_overlaps_range(&self, range: &Range<usize>) -> bool

Purpose: This answers whether the cursor is inside a given text range. It is a small helper that makes the word-finding code easier to read and less error-prone.

Data flow: It receives a range and reads the current cursor position. It returns true when the cursor is at or after the range start and before the range end.

Call relations: It is called by TextArea::small_word_range_at_cursor while deciding whether a run or word piece is the one the user is pointing at.

Call graph: called by 1 (small_word_range_at_cursor).

TextArea::cursor_is_at_range_end195–197 ↗
fn cursor_is_at_range_end(&self, range: &Range<usize>) -> bool

Purpose: This answers whether the cursor sits exactly at the end of a non-empty range. That matters because Vim often treats the cursor at the end of a word as still related to that word.

Data flow: It receives a range and reads the current cursor position. It returns true only if the range has at least one character and the cursor position equals the range’s end.

Call relations: It is called by TextArea::small_word_range_at_cursor to support the edge case where the cursor is just after a word rather than inside it.

Call graph: called by 1 (small_word_range_at_cursor).

TextArea::expand_word_around199–205 ↗
fn expand_word_around(&self, inner: Range<usize>) -> Range<usize>

Purpose: This turns an “inner word” range into an “around word” range by including nearby whitespace. This matches Vim’s useful behavior of deleting a word plus one side of its spacing, rather than leaving messy gaps.

Data flow: It receives the word’s inner range. It first checks for whitespace after the word with TextArea::following_whitespace_end; if any exists, it extends the range forward. If not, it looks backward with TextArea::preceding_whitespace_start and includes whitespace before the word instead.

Call relations: It is called by TextArea::word_text_object_range when the user chooses the Around scope. It hands the forward and backward whitespace checks to dedicated helpers.

Call graph: calls 2 internal fn (following_whitespace_end, preceding_whitespace_start); called by 1 (word_text_object_range).

TextArea::following_whitespace_end207–216 ↗
fn following_whitespace_end(&self, start: usize) -> usize

Purpose: This finds how far whitespace continues after a given position. It helps decide how much trailing space belongs to an “around word” selection.

Data flow: It receives a starting byte position and scans forward through the text. It stops at the first non-whitespace character and returns the byte position just after the last whitespace character it saw.

Call relations: It is called by TextArea::expand_word_around before checking preceding whitespace, because trailing whitespace is preferred when expanding an around-word selection.

Call graph: called by 1 (expand_word_around).

TextArea::preceding_whitespace_start218–227 ↗
fn preceding_whitespace_start(&self, end: usize) -> usize

Purpose: This finds where the whitespace immediately before a given position begins. It is used when there is no trailing whitespace to include after a word.

Data flow: It receives an ending byte position and scans backward through the text before that point. It stops when it reaches a non-whitespace character and returns the start position of the whitespace run.

Call relations: It is called by TextArea::expand_word_around as the fallback when there is no whitespace after the selected word.

Call graph: called by 1 (expand_word_around).

TextArea::paired_text_object_range229–264 ↗
fn paired_text_object_range(
        &self,
        scope: VimTextObjectScope,
        open: char,
        close: char,
    ) -> Option<Range<usize>>

Purpose: This finds the smallest matching pair of delimiters around the cursor, such as parentheses, brackets, or braces. It supports commands like “change inside parentheses” or “delete around braces.”

Data flow: It receives a scope plus an opening and closing character. It scans the text, skipping positions that are inside special text-area elements. Opening characters are pushed onto a stack, and closing characters pop from that stack to form pairs. If a pair surrounds the cursor, it builds either the inside range or the full around range, then keeps the smallest matching candidate.

Call relations: It is called by TextArea::text_object_range for parentheses, brackets, and braces. It calls TextArea::is_inside_element so structured embedded elements do not get mistaken for real delimiter characters.

Call graph: calls 1 internal fn (is_inside_element); called by 1 (text_object_range); 1 external calls (new).

TextArea::quoted_text_object_range266–298 ↗
fn quoted_text_object_range(
        &self,
        scope: VimTextObjectScope,
        quote: char,
    ) -> Option<Range<usize>>

Purpose: This finds a matching quoted string around the cursor on the current line. It supports text objects such as inside double quotes, around single quotes, or around backticks.

Data flow: It receives a scope and the quote character to look for. It scans only the current line, skips quote characters inside special elements, and ignores escaped quotes such as \". It pairs quote marks in order, checks whether the cursor lies between them, and returns either the inside range or the full quoted range.

Call relations: It is called by TextArea::text_object_range for quote-based text objects. It uses TextArea::is_inside_element to ignore embedded elements, TextArea::is_escaped to avoid treating escaped quotes as real boundaries, and idx_range to build the around-quote range.

Call graph: calls 3 internal fn (is_escaped, is_inside_element, idx_range); called by 1 (text_object_range).

TextArea::is_inside_element300–304 ↗
fn is_inside_element(&self, pos: usize) -> bool

Purpose: This checks whether a text position falls inside one of the text area’s special embedded elements. Those elements should not be treated like ordinary typed text when searching for brackets or quotes.

Data flow: It receives a byte position and reads the text area’s element ranges. It returns true if the position is inside any element range, and false otherwise.

Call relations: It is called by TextArea::paired_text_object_range and TextArea::quoted_text_object_range while scanning text. This keeps delimiter matching focused on normal editable text instead of structured spans.

Call graph: called by 2 (paired_text_object_range, quoted_text_object_range).

TextArea::is_escaped306–315 ↗
fn is_escaped(&self, pos: usize) -> bool

Purpose: This decides whether the character at a position is escaped with a backslash. For quotes, this prevents an escaped quote from being mistaken for the end of a quoted section.

Data flow: It receives a byte position and looks backward from that point, counting consecutive backslashes. If the count is odd, it returns true, meaning the character is escaped; if the count is even, it returns false.

Call relations: It is called by TextArea::quoted_text_object_range whenever a quote character is found. This helps the quote scanner tell the difference between a real closing quote and a literal quote character inside the text.

Call graph: called by 1 (quoted_text_object_range).

idx_range318–320 ↗
fn idx_range(open_idx: usize, close_idx: usize, quote: char) -> Range<usize>

Purpose: This builds the full range for a quoted text object when the selection should include both quote marks. It is a tiny helper for the “around quotes” case.

Data flow: It receives the byte positions of the opening and closing quote and the quote character itself. It returns a range that starts at the opening quote and ends just after the closing quote.

Call relations: It is called by TextArea::quoted_text_object_range when the requested scope is Around. That caller has already found the matching quote pair; this helper just packages the correct byte range.

Call graph: called by 1 (quoted_text_object_range).

tui/src/bottom_pane/textarea.rssource ↗
domain_logicrequest handling

The text area is the small editor behind the bottom input pane. It solves a deceptively hard problem: terminal input is not just a string. Users move by characters, words, and lines; paste text; delete and yank text; use optional Vim controls; and may include special embedded elements such as image placeholders that must behave like one solid object. Without this file, the input box would easily put the cursor inside a multi-byte character, split an embedded element in half, lose the visible cursor after wrapping, or treat key releases as typed letters.

The main TextArea stores the raw text and a cursor position measured in bytes, but it constantly clamps that position to safe character boundaries. It also tracks ranges for embedded elements and makes sure edits either avoid them or include them whole. A small kill buffer acts like a clipboard for delete/yank/paste operations. Keyboard events are translated through configurable keymaps, with a separate path for normal editing and Vim-style insert/normal/operator flows. For display, the file wraps text to the available width, caches those wrapped line ranges, keeps the cursor visible by adjusting scroll, and renders visible lines into the terminal buffer. From this chunk, the file is both a text editor widget and its safety net of tests. The widget draws visible lines into a terminal buffer, a grid of screen cells. It can draw normal text, special embedded elements in a different color, temporary highlight ranges such as search results, and masked text for hidden input. Think of it like painting a sign: first it paints the background and plain letters, then paints special labels, then paints temporary marker pen highlights on top so they stay visible.

The tests show the wider job of the file. TextArea must keep the cursor in a valid place even when the text contains emoji, CJK characters, combining marks, or multi-byte characters. It supports deleting by character, word, line, and Vim motions, while remembering deleted text in a kill buffer so it can be pasted back. It also supports wrapping long text into visual lines and scrolling a small viewport so the cursor remains visible. Without this file, the bottom input pane would feel unreliable: keys could delete the wrong text, cursor movement could break on Unicode, Vim mode could behave surprisingly, and rendering highlights could corrupt or hide content. This part of the file is about confidence. A text area looks simple to a user, but it has many tricky edge cases: emoji and other multi-byte characters, wrapped lines, cursor movement, deletion, scrolling, and special inserted “elements” that should behave like solid blocks rather than ordinary editable text. The fuzz test here acts like a very impatient user who randomly types, deletes, moves around, resizes the visible box, inserts protected chunks of text, and tries to edit inside them.

The test repeats this many times using a daily seed, so failures can be reproduced during the same day while still exploring new cases over time. After each random action, it checks important promises: the cursor never points past the end of the text, protected elements are not partly corrupted, the cursor is not left inside one of those elements, and rendering into a terminal buffer does not panic. It also checks scrolling behavior when all wrapped lines fit on screen.

An everyday analogy is stress-testing a notebook by scribbling, erasing, folding pages, and moving a bookmark around at random, then checking that no page tears and the bookmark never lands halfway inside a sticker.

Function details193
is_word_separator48–50 ↗
fn is_word_separator(ch: char) -> bool

Purpose: Checks whether a character counts as punctuation or a symbol that should be treated as its own word piece. This helps word movement behave more like a real editor.

Data flow: It receives one character, looks for it in the configured separator set, and returns true or false.

Call relations: Word splitting calls this small test for each character so later word-navigation functions can decide where words begin and end.

Call graph: called by 1 (split_word_pieces).

split_word_pieces52–76 ↗
fn split_word_pieces(run: &str) -> Vec<(usize, &str)>

Purpose: Breaks a run of non-whitespace text into smaller word-like pieces. It separates normal letters from separator characters so cursor movement can stop at useful places.

Data flow: It receives a text slice, walks through Unicode word boundaries, then further splits when characters switch between separator and non-separator. It returns byte offsets paired with each piece.

Call relations: Previous-word and next-word searches call this when they need editor-like word boundaries rather than just whitespace boundaries.

Call graph: calls 1 internal fn (is_word_separator); called by 2 (beginning_of_previous_word, end_of_next_word_from); 1 external calls (new).

TextArea::new139–158 ↗
fn new() -> Self

Purpose: Creates an empty text area ready for use. It starts with no text, the cursor at the beginning, no embedded elements, and default keyboard bindings.

Data flow: It reads the default runtime keymap, fills all stored fields with initial values, and returns a new TextArea.

Call relations: Construction code and tests call this before any editing, rendering, or token detection can happen.

Call graph: calls 1 internal fn (defaults); called by 20 (new, test_current_at_token_allows_file_queries_with_second_at, test_current_at_token_basic_cases, test_current_at_token_cursor_positions, test_current_at_token_ignores_mid_word_at, test_current_at_token_tracks_tokens_with_second_at, test_current_at_token_whitespace_boundaries, new, new, delete_forward_deletes_element_at_left_edge (+10 more)); 3 external calls (new, new, new).

TextArea::set_keymap_bindings166–171 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

Purpose: Replaces the text area's keyboard shortcuts with a provided runtime keymap. This lets the editor follow user or application key binding settings.

Data flow: It receives a keymap, clones the editor and Vim binding sections, and stores them in the text area.

Call relations: External setup code calls this when key bindings change; later input handling reads these stored bindings to interpret key events.

TextArea::set_text_clearing_elements179–181 ↗
fn set_text_clearing_elements(&mut self, text: &str)

Purpose: Replaces the visible text and removes any embedded element ranges. This is useful when the text is being reset as plain text.

Data flow: It receives new text, passes it to the shared text-setting routine with no element list, and leaves the text area with a clean element table.

Call relations: It delegates to TextArea::set_text_inner, which performs the safe cursor clamping and cache reset.

Call graph: calls 1 internal fn (set_text_inner).

TextArea::set_text_with_elements188–190 ↗
fn set_text_with_elements(&mut self, text: &str, elements: &[UserTextElement])

Purpose: Replaces the visible text while also installing known embedded element ranges. This is used when restoring or setting text that contains special placeholders.

Data flow: It receives text plus element descriptions, then passes both to the shared text-setting routine.

Call relations: It delegates to TextArea::set_text_inner, which rebuilds internal element records safely against the new text.

Call graph: calls 1 internal fn (set_text_inner).

TextArea::set_text_inner192–221 ↗
fn set_text_inner(&mut self, text: &str, elements: Option<&[UserTextElement]>)

Purpose: Does the real work of replacing the whole buffer. It also rebuilds embedded element ranges and clears cached display information that no longer matches.

Data flow: It receives new text and optionally element ranges. It stores the text, clamps the cursor into the new buffer, creates fresh internal element records with safe character boundaries, clears wrapping state, and resets vertical-movement memory.

Call relations: set_text_clearing_elements and set_text_with_elements both call this so full-buffer replacement follows one safe path.

Call graph: calls 3 internal fn (clamp_pos_to_char_boundary, clamp_pos_to_nearest_boundary, next_element_id); called by 2 (set_text_clearing_elements, set_text_with_elements).

TextArea::set_vim_enabled229–237 ↗
fn set_vim_enabled(&mut self, enabled: bool)

Purpose: Turns Vim-style editing on or off. When enabled, the editor starts in normal mode; when disabled, it behaves like a regular insert-only editor.

Data flow: It receives a boolean, stores it, clears any pending Vim command, and sets the current Vim mode accordingly.

Call relations: Setup or preference code calls this; later input handling checks these fields to choose between normal editing and Vim editing.

TextArea::is_vim_enabled240–242 ↗
fn is_vim_enabled(&self) -> bool

Purpose: Reports whether Vim-style editing is currently turned on.

Data flow: It reads the stored Vim-enabled flag and returns it.

Call relations: Other parts of the UI can call this to decide what behavior or status to show.

TextArea::is_vim_normal_mode249–251 ↗
fn is_vim_normal_mode(&self) -> bool

Purpose: Reports whether the text area is currently in Vim normal mode. Normal mode means keys are commands rather than typed text.

Data flow: It reads the Vim-enabled flag and current mode, returning true only when both match normal-mode editing.

Call relations: UI and input code can use this to decide cursor style, status labels, or command behavior.

TextArea::vim_normal_end_cursor254–260 ↗
fn vim_normal_end_cursor(&self) -> usize

Purpose: Finds the last safe cursor position for Vim normal mode. Vim normal mode usually places the cursor on a character, not after the final character.

Data flow: It checks whether the text is empty. If not, it moves one atomic step back from the end, respecting graphemes and embedded elements.

Call relations: It relies on prev_atomic_boundary to avoid landing inside a complex character or element.

Call graph: calls 1 internal fn (prev_atomic_boundary).

TextArea::is_vim_operator_pending266–268 ↗
fn is_vim_operator_pending(&self) -> bool

Purpose: Reports whether Vim is waiting for the rest of a command, such as after pressing delete before choosing a motion.

Data flow: It reads the pending Vim state and returns true unless that state is empty.

Call relations: UI code can use this while displaying command state; Vim input code updates the pending state as keys arrive.

Call graph: 1 external calls (matches!).

TextArea::enter_vim_insert_mode275–280 ↗
fn enter_vim_insert_mode(&mut self)

Purpose: Switches to Vim insert mode, where typed characters are inserted into the text. It only does this if Vim mode is enabled.

Data flow: It checks the Vim-enabled flag, then sets the mode to insert and clears any half-finished Vim command.

Call relations: Vim command handling can call this after commands that begin text entry.

TextArea::enter_vim_normal_mode288–294 ↗
fn enter_vim_normal_mode(&mut self)

Purpose: Switches to Vim normal mode, where keys act as editor commands. It also clears vertical cursor-column memory so later movement starts fresh.

Data flow: It checks the Vim-enabled flag, sets mode to normal, clears pending commands, and resets the preferred column.

Call relations: handle_vim_insert calls this when Escape is pressed.

Call graph: called by 1 (handle_vim_insert).

TextArea::allows_paste_burst301–303 ↗
fn allows_paste_burst(&self) -> bool

Purpose: Tells the caller whether a burst of pasted text should be accepted directly. Paste bursts are allowed outside Vim or while Vim is in insert mode.

Data flow: It reads Vim state and returns true unless Vim normal mode is active.

Call relations: Key-event handling calls this before treating a paste-like stream as text input.

Call graph: called by 1 (handle_key_event_at).

TextArea::uses_vim_insert_cursor306–308 ↗
fn uses_vim_insert_cursor(&self) -> bool

Purpose: Reports whether the cursor should look like Vim insert mode. This helps the UI draw the right cursor style.

Data flow: It reads whether Vim is enabled and whether the current mode is insert, then returns that combined answer.

Call relations: Rendering or surrounding UI code can call this when deciding how the cursor should appear.

TextArea::should_handle_vim_insert_escape315–321 ↗
fn should_handle_vim_insert_escape(&self, event: KeyEvent) -> bool

Purpose: Recognizes the plain Escape key while in Vim insert mode. This lets the application intercept Escape as a mode switch instead of treating it as some other command.

Data flow: It receives a key event, checks Vim state, key code, modifiers, and event kind, and returns true only for an unmodified Escape press or repeat.

Call relations: Input orchestration can call this before normal key handling so Escape exits insert mode cleanly.

Call graph: 1 external calls (matches!).

TextArea::vim_mode_label328–336 ↗
fn vim_mode_label(&self) -> Option<&'static str>

Purpose: Returns a short label for the current Vim mode, or nothing if Vim editing is off. This is meant for status display.

Data flow: It reads the Vim-enabled flag and mode, then returns Normal, Insert, or no label.

Call relations: UI code can call this while drawing mode indicators.

TextArea::text338–340 ↗
fn text(&self) -> &str

Purpose: Gives read-only access to the current text. Callers use this when they need to submit, inspect, or render the buffer.

Data flow: It reads the stored string and returns it as a borrowed text slice without changing anything.

Call relations: Submission, rendering, key handling, and token-detection code call this to see the current buffer content.

Call graph: called by 5 (current_prefixed_token_range, handle_key_event_at, render, render, submit).

TextArea::insert_str342–344 ↗
fn insert_str(&mut self, text: &str)

Purpose: Inserts text at the current cursor position. It is the simple public entry point for normal typing and paste insertion.

Data flow: It receives text and passes it with the current cursor position to insert_str_at, which performs the safe edit.

Call relations: Keyboard input, paste handling, yanking, and Vim paste operations call this when text should appear where the cursor is.

Call graph: calls 1 internal fn (insert_str_at); called by 6 (handle_key_event_at, handle_paste, handle_paste, input_with_keymap, paste_after_cursor, yank).

TextArea::insert_str_at346–355 ↗
fn insert_str_at(&mut self, pos: usize, text: &str)

Purpose: Inserts text at a requested byte position while keeping the cursor and embedded elements consistent. It avoids inserting into the middle of a protected element.

Data flow: It receives a position and text, clamps the position to a safe insertion point, inserts the text, invalidates wrapping, shifts later element ranges, updates the cursor if needed, and clears preferred column memory.

Call relations: Lower-level editing paths call this for normal insertion, element insertion, Vim line opening, and linewise paste.

Call graph: calls 2 internal fn (clamp_pos_for_insertion, shift_elements); called by 4 (handle_vim_normal, insert_element, insert_str, paste_line_after_current_line).

TextArea::replace_range357–360 ↗
fn replace_range(&mut self, range: std::ops::Range<usize>, text: &str)

Purpose: Replaces a range of text, but first expands the range so embedded elements are not split apart. This is the safe public replacement path.

Data flow: It receives a byte range and replacement text, expands the range around any intersecting element, then hands it to the raw replacement routine.

Call relations: Backward and forward deletion use this so simple deletes still respect embedded elements.

Call graph: calls 2 internal fn (expand_range_to_element_boundaries, replace_range_raw); called by 2 (delete_backward, delete_forward).

TextArea::replace_range_raw362–393 ↗
fn replace_range_raw(&mut self, range: std::ops::Range<usize>, text: &str)

Purpose: Performs the actual text replacement once the caller has chosen the range. It updates the cursor, element ranges, and wrapping cache afterward.

Data flow: It receives a range and replacement text, clamps the range into the buffer, replaces the bytes, updates element positions, moves the cursor according to where the edit happened, and clamps the cursor to a safe boundary.

Call relations: replace_range and kill operations call this after deciding what text should be removed or replaced.

Call graph: calls 2 internal fn (clamp_pos_to_nearest_boundary, update_elements_after_replace); called by 2 (kill_range_with_kind, replace_range); 1 external calls (assert!).

TextArea::cursor395–397 ↗
fn cursor(&self) -> usize

Purpose: Returns the current cursor byte position. This is useful for code that needs to relate outside actions to the text buffer.

Data flow: It reads and returns the stored cursor position without changing the text area.

Call relations: Token detection and remote image selection code call this to know where the user is editing.

Call graph: called by 2 (current_prefixed_token_range, handle_remote_image_selection_key).

TextArea::set_cursor399–403 ↗
fn set_cursor(&mut self, pos: usize)

Purpose: Moves the cursor to a requested position while keeping it valid. It will not leave the cursor inside a multi-byte character or embedded element.

Data flow: It receives a byte position, clamps it into the text length, snaps it to the nearest safe boundary, and clears preferred column memory.

Call relations: Many movement, insertion, paste, and Vim helper paths call this whenever they need an explicit cursor jump.

Call graph: calls 1 internal fn (clamp_pos_to_nearest_boundary); called by 8 (handle_vim_normal, input_with_keymap, insert_element, move_cursor_to_beginning_of_line, move_cursor_to_end_of_line, paste_after_cursor, paste_line_after_current_line, target_for_motion).

TextArea::desired_height405–407 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows the current text would need at a given width. This helps the UI size the input area.

Data flow: It receives a width, gets the wrapped line ranges for that width, and returns their count as a terminal row count.

Call relations: Input layout code calls this when deciding how tall the bottom pane should be.

Call graph: calls 1 internal fn (wrapped_lines); called by 2 (input_height, input_height).

TextArea::cursor_pos410–412 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Calculates the cursor's screen coordinates using default scroll state. This maps the internal byte cursor to a visible terminal position.

Data flow: It receives a rectangular drawing area, creates default state, and delegates to the state-aware cursor position function.

Call relations: Rendering code calls this when it does not need to preserve or supply scroll state explicitly.

Call graph: calls 1 internal fn (cursor_pos_with_state); 1 external calls (default).

TextArea::cursor_pos_with_state415–426 ↗
fn cursor_pos_with_state(&self, area: Rect, state: TextAreaState) -> Option<(u16, u16)>

Purpose: Calculates where the cursor appears on screen after wrapping and scrolling. This turns the text cursor into terminal coordinates.

Data flow: It receives a drawing area and scroll state, wraps the text, adjusts scroll so the cursor is visible, finds the wrapped line containing the cursor, measures the display width before the cursor, and returns x/y coordinates.

Call relations: cursor_pos and other cursor display code call this when drawing or positioning the terminal cursor.

Call graph: calls 2 internal fn (effective_scroll, wrapped_lines); called by 3 (cursor_pos, cursor_pos, cursor_pos); 1 external calls (wrapped_line_index_by_start).

TextArea::is_empty428–430 ↗
fn is_empty(&self) -> bool

Purpose: Reports whether the text area currently contains no text.

Data flow: It reads the stored string and returns whether it is empty.

Call relations: Outside code can use this for decisions such as whether submission is meaningful.

TextArea::current_display_col432–435 ↗
fn current_display_col(&self) -> usize

Purpose: Finds the cursor's visual column within the current logical line. Visual width matters because some Unicode characters occupy more than one terminal column.

Data flow: It finds the start of the current line, measures the display width from that point to the cursor, and returns the width.

Call relations: Vertical cursor movement uses this to keep the cursor in the same apparent column when moving up or down.

Call graph: calls 1 internal fn (beginning_of_current_line); called by 2 (move_cursor_down, move_cursor_up).

TextArea::wrapped_line_index_by_start437–442 ↗
fn wrapped_line_index_by_start(lines: &[Range<usize>], pos: usize) -> Option<usize>

Purpose: Finds which wrapped line contains a given byte position. Wrapped lines are the visual rows after long text is broken to fit the terminal width.

Data flow: It receives wrapped line ranges and a position, searches by range starts, and returns the matching line index if one exists.

Call relations: Cursor positioning and vertical movement use this to connect a byte position to a visual line.

Call graph: 1 external calls (partition_point).

TextArea::move_to_display_col_on_line444–462 ↗
fn move_to_display_col_on_line(
        &mut self,
        line_start: usize,
        line_end: usize,
        target_col: usize,
    )

Purpose: Moves the cursor within one line to the closest position for a target visual column. It respects Unicode display width and embedded element boundaries.

Data flow: It receives a line start, line end, and target column. It walks grapheme clusters, adds their screen widths, and sets the cursor where the target column is reached or at the line end.

Call relations: Up and down movement call this after deciding which line the cursor should move to.

Call graph: calls 1 internal fn (clamp_pos_to_nearest_boundary); called by 2 (move_cursor_down, move_cursor_up).

TextArea::beginning_of_line464–466 ↗
fn beginning_of_line(&self, pos: usize) -> usize

Purpose: Finds the byte position where the line containing a given position starts.

Data flow: It receives a position, searches backward for a newline, and returns the byte after it or zero if there is no earlier newline.

Call relations: Line movement, current-line calculations, and vertical Vim ranges use this as their basic line-start helper.

Call graph: called by 3 (beginning_of_current_line, linewise_range_for_vertical_motion, move_cursor_to_beginning_of_line).

TextArea::beginning_of_current_line467–469 ↗
fn beginning_of_current_line(&self) -> usize

Purpose: Finds the start of the line where the cursor currently sits.

Data flow: It reads the current cursor position and delegates to beginning_of_line.

Call relations: Many editing and Vim commands call this before moving, deleting, or opening lines.

Call graph: calls 1 internal fn (beginning_of_line); called by 9 (current_display_col, current_line_range_with_newline, first_non_blank_of_current_line, handle_vim_insert, handle_vim_normal, kill_to_beginning_of_line, move_cursor_to_beginning_of_line, target_for_motion, vim_line_end_cursor).

TextArea::first_non_blank_of_current_line471–478 ↗
fn first_non_blank_of_current_line(&self) -> usize

Purpose: Finds the first non-whitespace character on the current line. This supports Vim-style commands that jump to indentation.

Data flow: It finds the current line boundaries, scans from the start for a non-whitespace character, and returns that position or the line end if the line is blank.

Call relations: Vim normal-mode insert-at-line-start uses this to place the cursor after indentation.

Call graph: calls 2 internal fn (beginning_of_current_line, end_of_current_line); called by 1 (handle_vim_normal).

TextArea::end_of_line480–485 ↗
fn end_of_line(&self, pos: usize) -> usize

Purpose: Finds the byte position where the line containing a given position ends, not including the newline.

Data flow: It receives a position, searches forward for a newline, and returns that newline position or the end of the text.

Call relations: Line movement, current-line calculations, and linewise Vim ranges use this as their basic line-end helper.

Call graph: called by 3 (end_of_current_line, linewise_range_for_vertical_motion, move_cursor_to_end_of_line).

TextArea::end_of_current_line486–488 ↗
fn end_of_current_line(&self) -> usize

Purpose: Finds the end of the line where the cursor currently sits.

Data flow: It reads the cursor position and delegates to end_of_line.

Call relations: Deletion, paste, Vim movement, and line opening all use this to know where the current line stops.

Call graph: calls 1 internal fn (end_of_line); called by 9 (current_line_range_with_newline, first_non_blank_of_current_line, handle_vim_normal, kill_to_end_of_line, move_cursor_to_end_of_line, paste_line_after_current_line, target_for_motion, vim_kill_to_end_of_line, vim_line_end_cursor).

TextArea::input490–502 ↗
fn input(&mut self, event: KeyEvent)

Purpose: Processes one keyboard event for the text area. It ignores key releases and sends key presses to either normal editing or Vim editing.

Data flow: It receives a key event, returns immediately unless it is a press or repeat, then chooses handle_vim_input or input_with_keymap based on Vim state.

Call relations: Top-level key-event handling calls this when the text area should react to user input.

Call graph: calls 2 internal fn (handle_vim_input, input_with_keymap); called by 2 (handle_key_event_at, handle_key_event); 2 external calls (matches!, clone).

TextArea::input_with_keymap504–620 ↗
fn input_with_keymap(&mut self, event: KeyEvent, keymap: &EditorKeymap)

Purpose: Interprets a key event using the regular editor keymap. It turns shortcuts into editing actions and plain printable characters into inserted text.

Data flow: It receives a key event and keymap, checks shortcuts in priority order, performs matching actions such as delete, move, kill, yank, or insert newline, and inserts plain characters when appropriate.

Call relations: input uses this when Vim is off, and handle_vim_insert uses it while Vim is in insert mode.

Call graph: calls 19 internal fn (beginning_of_previous_word, delete_backward, delete_backward_word, delete_forward, delete_forward_word, end_of_next_word, insert_str, kill_current_line, kill_to_beginning_of_line, kill_to_end_of_line (+9 more)); called by 2 (handle_vim_insert, input); 2 external calls (matches!, debug!).

TextArea::handle_vim_input622–627 ↗
fn handle_vim_input(&mut self, event: KeyEvent)

Purpose: Dispatches a Vim-mode key event to the correct mode-specific handler. Vim insert mode and normal mode interpret the same keys very differently.

Data flow: It receives a key event, reads the current Vim mode, and calls either handle_vim_insert or handle_vim_normal.

Call relations: input calls this whenever Vim editing is enabled.

Call graph: calls 2 internal fn (handle_vim_insert, handle_vim_normal); called by 1 (input).

TextArea::handle_vim_insert629–640 ↗
fn handle_vim_insert(&mut self, event: KeyEvent)

Purpose: Handles keys while Vim is in insert mode. Most keys behave like normal editor input, but Escape returns to normal mode.

Data flow: It receives a key event. If it is Escape, it moves the cursor back one safe step when appropriate and enters normal mode; otherwise it passes the event to regular keymap input handling.

Call relations: handle_vim_input calls this for insert-mode Vim events.

Call graph: calls 4 internal fn (beginning_of_current_line, enter_vim_normal_mode, input_with_keymap, prev_atomic_boundary); called by 1 (handle_vim_input); 2 external calls (matches!, clone).

TextArea::handle_vim_normal642–784 ↗
fn handle_vim_normal(&mut self, event: KeyEvent)

Purpose: Handles keys while Vim is in normal mode. In this mode keys move the cursor, start commands, delete or yank text, paste, or switch back to insert mode.

Data flow: It receives a key event, first completes any pending operator or text-object command, then checks normal-mode bindings and updates text, cursor, mode, or pending command state.

Call relations: handle_vim_input calls this for normal-mode events; it hands multi-key commands to operator and text-object helpers.

Call graph: calls 20 internal fn (beginning_of_current_line, beginning_of_next_word, beginning_of_previous_word, delete_forward_kill, end_of_current_line, first_non_blank_of_current_line, handle_vim_operator, handle_vim_text_object, insert_str_at, move_cursor_down (+10 more)); called by 1 (handle_vim_input); 2 external calls (replace, Operator).

TextArea::handle_vim_operator786–813 ↗
fn handle_vim_operator(&mut self, op: VimOperator, event: KeyEvent) -> bool

Purpose: Handles the second key after a Vim operator such as delete or yank. For example, it decides whether d followed by a motion deletes that motion's range.

Data flow: It receives an operator and key event, checks special line commands, cancellation, text-object starts, and motion keys. It may update pending state or apply the operator to a motion range, returning whether it consumed the key.

Call relations: handle_vim_normal calls this when an operator was pending from the previous key.

Call graph: calls 4 internal fn (apply_vim_operator, kill_current_line, vim_motion_for_event, yank_current_line); called by 1 (handle_vim_normal).

TextArea::handle_vim_text_object815–831 ↗
fn handle_vim_text_object(
        &mut self,
        op: VimOperator,
        scope: VimTextObjectScope,
        event: KeyEvent,
    ) -> bool

Purpose: Completes a Vim text-object command, such as operating on a quoted or bracketed region. It applies delete, yank, or change to the chosen object range.

Data flow: It receives an operator, object scope, and key event. It handles cancellation, identifies the requested text object, finds its range, and applies the operator if a range exists.

Call relations: handle_vim_normal calls this when the previous keys started a text-object operation.

Call graph: calls 1 internal fn (apply_vim_operator_to_range); called by 1 (handle_vim_normal).

TextArea::vim_motion_for_event833–870 ↗
fn vim_motion_for_event(&self, event: KeyEvent) -> Option<VimMotion>

Purpose: Converts a Vim operator-mode key event into a motion name, such as left, word-forward, or line-end.

Data flow: It receives a key event, checks it against Vim operator motion bindings, and returns the matching motion or nothing.

Call relations: handle_vim_operator calls this before applying delete or yank over a movement range.

Call graph: called by 1 (handle_vim_operator).

TextArea::apply_vim_operator872–881 ↗
fn apply_vim_operator(&mut self, op: VimOperator, motion: VimMotion)

Purpose: Applies a Vim operator to the range described by a motion. This is how commands like delete-word or yank-to-line-end become concrete edits.

Data flow: It receives an operator and motion, asks for the motion's range, and then deletes or yanks that range. Change is intentionally not handled on plain motions here.

Call relations: handle_vim_operator calls this after recognizing a motion key.

Call graph: calls 3 internal fn (kill_range, range_for_motion, yank_range); called by 1 (handle_vim_operator).

TextArea::apply_vim_operator_to_range883–892 ↗
fn apply_vim_operator_to_range(&mut self, op: VimOperator, range: Range<usize>)

Purpose: Applies a Vim operator directly to an already-known range, usually from a text object. Change deletes the range and switches to insert mode.

Data flow: It receives an operator and byte range, then deletes, yanks, or deletes-and-enters-insert depending on the operator.

Call relations: handle_vim_text_object calls this after resolving the selected text object.

Call graph: calls 2 internal fn (kill_range, yank_range); called by 1 (handle_vim_text_object).

TextArea::range_for_motion894–909 ↗
fn range_for_motion(&mut self, motion: VimMotion) -> Option<Range<usize>>

Purpose: Turns a Vim motion into the byte range that an operator should affect. It handles vertical movement specially as linewise ranges.

Data flow: It receives a motion, uses linewise logic for up/down, otherwise calculates a target position, compares it with the original cursor, and returns the span between them if non-empty.

Call relations: apply_vim_operator calls this before deleting or yanking motion-based text.

Call graph: calls 2 internal fn (linewise_range_for_vertical_motion, target_for_motion); called by 1 (apply_vim_operator); 1 external calls (matches!).

TextArea::linewise_range_for_vertical_motion911–944 ↗
fn linewise_range_for_vertical_motion(&self, motion: VimMotion) -> Option<Range<usize>>

Purpose: Builds a whole-line range for Vim operators combined with up or down movement. Vertical operator motions affect lines rather than just columns.

Data flow: It reads the current line range, expands upward to include the previous line or downward to include the next line, and returns the non-empty range.

Call relations: range_for_motion uses this when the motion is up or down.

Call graph: calls 3 internal fn (beginning_of_line, current_line_range_with_newline, end_of_line); called by 1 (range_for_motion).

TextArea::target_for_motion946–964 ↗
fn target_for_motion(&mut self, motion: VimMotion) -> usize

Purpose: Finds where a motion would move the cursor without actually leaving the cursor there. This lets operators measure a range safely.

Data flow: It saves the current cursor and preferred column, performs the requested movement, records the resulting cursor, then restores the original cursor state and returns the target.

Call relations: range_for_motion calls this for non-linewise Vim motions.

Call graph: calls 10 internal fn (beginning_of_current_line, beginning_of_next_word, beginning_of_previous_word, end_of_current_line, move_cursor_down, move_cursor_left, move_cursor_right, move_cursor_up, set_cursor, vim_word_end_exclusive); called by 1 (range_for_motion).

TextArea::delete_backward967–979 ↗
fn delete_backward(&mut self, n: usize)

Purpose: Deletes a given number of atomic units before the cursor. An atomic unit may be a whole grapheme or a whole embedded element.

Data flow: It receives a count, repeatedly steps backward to find the deletion start, then replaces that range with nothing.

Call relations: Regular keymap input calls this for backspace-style deletion.

Call graph: calls 2 internal fn (prev_atomic_boundary, replace_range); called by 1 (input_with_keymap).

TextArea::delete_forward981–993 ↗
fn delete_forward(&mut self, n: usize)

Purpose: Deletes a given number of atomic units after the cursor. It respects complex characters and embedded elements.

Data flow: It receives a count, repeatedly steps forward to find the deletion end, then replaces the range from the cursor to that point with nothing.

Call relations: Regular keymap input calls this for delete-key behavior.

Call graph: calls 2 internal fn (next_atomic_boundary, replace_range); called by 1 (input_with_keymap).

TextArea::delete_forward_kill995–1007 ↗
fn delete_forward_kill(&mut self, n: usize)

Purpose: Deletes text after the cursor and saves it in the kill buffer. This is used for Vim delete-character behavior.

Data flow: It receives a count, finds the forward atomic boundary after that many units, and kills the range from the cursor to that boundary.

Call relations: Vim normal mode calls this for commands that delete characters under or after the cursor.

Call graph: calls 2 internal fn (kill_range, next_atomic_boundary); called by 1 (handle_vim_normal).

TextArea::delete_backward_word1009–1012 ↗
fn delete_backward_word(&mut self)

Purpose: Deletes the word before the cursor and saves it in the kill buffer. This matches common editor shortcut behavior.

Data flow: It finds the beginning of the previous word, then kills the range from that point to the cursor.

Call relations: Regular keymap input calls this for backward-word deletion.

Call graph: calls 2 internal fn (beginning_of_previous_word, kill_range); called by 1 (input_with_keymap).

TextArea::delete_forward_word1019–1024 ↗
fn delete_forward_word(&mut self)

Purpose: Deletes the next word after the cursor and saves it in the kill buffer.

Data flow: It finds the end of the next word and, if that is beyond the cursor, kills the range between the cursor and that end.

Call relations: Regular keymap input calls this for forward-word deletion.

Call graph: calls 2 internal fn (end_of_next_word, kill_range); called by 1 (input_with_keymap).

TextArea::kill_to_end_of_line1032–1047 ↗
fn kill_to_end_of_line(&mut self)

Purpose: Deletes from the cursor to the end of the current line, saving what was removed. If already at line end, it removes the newline when possible.

Data flow: It finds the current line end, chooses the correct range, and kills it if there is something to remove.

Call relations: Regular keymap input calls this for kill-line-end shortcuts.

Call graph: calls 2 internal fn (end_of_current_line, kill_range); called by 1 (input_with_keymap).

TextArea::vim_kill_to_end_of_line1049–1054 ↗
fn vim_kill_to_end_of_line(&mut self)

Purpose: Deletes from the cursor to the end of the current line for Vim commands. Unlike the regular version, it does not remove the newline when already at line end.

Data flow: It finds the current line end and kills text only if the cursor is before that end.

Call relations: Vim normal mode calls this for delete-to-line-end and change-to-line-end commands.

Call graph: calls 2 internal fn (end_of_current_line, kill_range); called by 1 (handle_vim_normal).

TextArea::kill_to_beginning_of_line1056–1067 ↗
fn kill_to_beginning_of_line(&mut self)

Purpose: Deletes from the cursor back to the start of the current line, saving what was removed. If already at the start, it removes the previous newline when possible.

Data flow: It finds the current line start, chooses a backward range, and kills it if one exists.

Call relations: Regular keymap input calls this for kill-line-start shortcuts.

Call graph: calls 2 internal fn (beginning_of_current_line, kill_range); called by 1 (input_with_keymap).

TextArea::yank1074–1080 ↗
fn yank(&mut self)

Purpose: Pastes the current kill buffer at the cursor. This is similar to paste after a previous cut or yank.

Data flow: It checks whether the kill buffer has text, clones it, and inserts it at the cursor.

Call relations: Regular keymap input calls this for yank/paste shortcuts.

Call graph: calls 1 internal fn (insert_str); called by 1 (input_with_keymap).

TextArea::kill_range1082–1084 ↗
fn kill_range(&mut self, range: Range<usize>)

Purpose: Deletes a characterwise range and stores the removed text in the kill buffer. Characterwise means the paste later behaves like inline text.

Data flow: It receives a range and delegates to the kind-aware kill routine with the characterwise kind.

Call relations: Vim operators and word/line-edge deletion helpers call this for normal cut-style operations.

Call graph: calls 1 internal fn (kill_range_with_kind); called by 8 (apply_vim_operator, apply_vim_operator_to_range, delete_backward_word, delete_forward_kill, delete_forward_word, kill_to_beginning_of_line, kill_to_end_of_line, vim_kill_to_end_of_line).

TextArea::kill_line_range1086–1088 ↗
fn kill_line_range(&mut self, range: Range<usize>)

Purpose: Deletes a whole-line range and marks the kill buffer as linewise. Linewise paste later inserts it as a line, not inline text.

Data flow: It receives a range and delegates to the kind-aware kill routine with the linewise kind.

Call relations: kill_current_line calls this when a complete line is removed.

Call graph: calls 1 internal fn (kill_range_with_kind); called by 1 (kill_current_line).

TextArea::kill_range_with_kind1090–1103 ↗
fn kill_range_with_kind(&mut self, range: Range<usize>, kind: KillBufferKind)

Purpose: Cuts text out of the buffer and records how it should paste later. It also protects embedded elements by expanding the cut range around them.

Data flow: It receives a range and kill-buffer kind, expands the range to include whole elements, copies the removed text into the kill buffer, then removes it from the buffer.

Call relations: kill_range and kill_line_range use this shared implementation.

Call graph: calls 3 internal fn (expand_range_to_element_boundaries, replace_range_raw, store_kill_buffer); called by 2 (kill_line_range, kill_range); 1 external calls (clone).

TextArea::yank_range1105–1107 ↗
fn yank_range(&mut self, range: Range<usize>)

Purpose: Copies a characterwise range into the kill buffer without deleting it.

Data flow: It receives a range and delegates to the kind-aware yank routine with the characterwise kind.

Call relations: Vim operator application calls this for yank motions and text objects.

Call graph: calls 1 internal fn (yank_range_with_kind); called by 2 (apply_vim_operator, apply_vim_operator_to_range).

TextArea::yank_line_range1109–1111 ↗
fn yank_line_range(&mut self, range: Range<usize>)

Purpose: Copies a whole-line range into the kill buffer and marks it as linewise.

Data flow: It receives a range and delegates to the kind-aware yank routine with the linewise kind.

Call relations: yank_current_line calls this for Vim line-yank commands.

Call graph: calls 1 internal fn (yank_range_with_kind); called by 1 (yank_current_line).

TextArea::yank_range_with_kind1113–1123 ↗
fn yank_range_with_kind(&mut self, range: Range<usize>, kind: KillBufferKind)

Purpose: Copies text into the kill buffer without changing the visible buffer. It expands around embedded elements so copied text does not contain half an element.

Data flow: It receives a range and kind, expands the range to whole elements, copies the text if non-empty, and stores it in the kill buffer with the kind.

Call relations: yank_range and yank_line_range share this implementation.

Call graph: calls 2 internal fn (expand_range_to_element_boundaries, store_kill_buffer); called by 2 (yank_line_range, yank_range).

TextArea::store_kill_buffer1125–1128 ↗
fn store_kill_buffer(&mut self, text: String, kind: KillBufferKind)

Purpose: Stores text in the internal kill buffer and remembers whether it is inline or linewise.

Data flow: It receives text and a kind, then replaces the previous kill buffer contents and kind.

Call relations: Cut and yank helpers call this after they have decided what text should be saved.

Call graph: called by 2 (kill_range_with_kind, yank_range_with_kind).

TextArea::paste_after_cursor1130–1142 ↗
fn paste_after_cursor(&mut self)

Purpose: Implements Vim-style paste after the cursor. Linewise saved text is pasted after the current line; inline saved text is inserted after the current atomic unit.

Data flow: It checks the kill buffer, chooses linewise or characterwise paste, moves the cursor to the insertion point for inline paste, and inserts the saved text.

Call relations: Vim normal mode calls this for paste-after commands.

Call graph: calls 4 internal fn (insert_str, next_atomic_boundary, paste_line_after_current_line, set_cursor); called by 1 (handle_vim_normal).

TextArea::paste_line_after_current_line1144–1163 ↗
fn paste_line_after_current_line(&mut self)

Purpose: Pastes linewise kill-buffer text after the current line. It adjusts newlines so the result looks like a line paste rather than a raw string splice.

Data flow: It finds the current line end, chooses an insertion point and final cursor position, prepares text with needed newline handling, inserts it, and moves the cursor.

Call relations: paste_after_cursor calls this when the kill buffer is marked linewise.

Call graph: calls 3 internal fn (end_of_current_line, insert_str_at, set_cursor); called by 1 (paste_after_cursor); 1 external calls (format!).

TextArea::yank_current_line1165–1168 ↗
fn yank_current_line(&mut self)

Purpose: Copies the current line, including its newline when present, into the kill buffer as linewise text.

Data flow: It finds the current line range including the newline and yanks that range with linewise kind.

Call relations: Vim normal mode and Vim operator handling call this for line-yank commands.

Call graph: calls 2 internal fn (current_line_range_with_newline, yank_line_range); called by 2 (handle_vim_normal, handle_vim_operator).

TextArea::kill_current_line1170–1173 ↗
fn kill_current_line(&mut self)

Purpose: Deletes the current line, including its newline when present, and stores it as linewise kill-buffer text.

Data flow: It finds the current line range including the newline and kills that range with linewise kind.

Call relations: Regular keymap input and Vim operator handling call this for whole-line deletion.

Call graph: calls 2 internal fn (current_line_range_with_newline, kill_line_range); called by 2 (handle_vim_operator, input_with_keymap).

TextArea::current_line_range_with_newline1175–1180 ↗
fn current_line_range_with_newline(&self) -> Range<usize>

Purpose: Returns the range for the current line, including the trailing newline if there is one. This is the unit used for whole-line operations.

Data flow: It finds the current line start and end, extends the end past the newline when possible, and returns the resulting byte range.

Call relations: Whole-line kill, yank, and vertical Vim operator ranges call this.

Call graph: calls 2 internal fn (beginning_of_current_line, end_of_current_line); called by 3 (kill_current_line, linewise_range_for_vertical_motion, yank_current_line).

TextArea::move_cursor_left1183–1186 ↗
fn move_cursor_left(&mut self)

Purpose: Moves the cursor one atomic step left. It will jump over an embedded element as a whole instead of entering it.

Data flow: It asks for the previous atomic boundary, stores it as the cursor position, and clears preferred column memory.

Call relations: Regular input, Vim normal mode, and Vim range measurement call this for left movement.

Call graph: calls 1 internal fn (prev_atomic_boundary); called by 3 (handle_vim_normal, input_with_keymap, target_for_motion).

TextArea::move_cursor_right1189–1192 ↗
fn move_cursor_right(&mut self)

Purpose: Moves the cursor one atomic step right. It respects graphemes and embedded elements.

Data flow: It asks for the next atomic boundary, stores it as the cursor position, and clears preferred column memory.

Call relations: Regular input, Vim normal mode, and Vim range measurement call this for right movement.

Call graph: calls 1 internal fn (next_atomic_boundary); called by 3 (handle_vim_normal, input_with_keymap, target_for_motion).

TextArea::move_cursor_up1194–1255 ↗
fn move_cursor_up(&mut self)

Purpose: Moves the cursor up one visible row while trying to keep the same screen column. If wrapped-line information exists, it moves by visual wrapped lines; otherwise it falls back to newline-based lines.

Data flow: It reads cached wrapping when available, chooses the previous visual line and target column, moves within that line, or moves to the start if already at the top. Without wrapping, it uses newline searches.

Call relations: Regular input, Vim normal mode, and Vim range measurement call this for upward movement.

Call graph: calls 2 internal fn (current_display_col, move_to_display_col_on_line); called by 3 (handle_vim_normal, input_with_keymap, target_for_motion); 1 external calls (wrapped_line_index_by_start).

TextArea::move_cursor_down1257–1323 ↗
fn move_cursor_down(&mut self)

Purpose: Moves the cursor down one visible row while preserving the preferred screen column. It understands both wrapped visual lines and plain newline-separated lines.

Data flow: It uses cached wrapping when available to choose the next visual line, otherwise searches for the next logical line. It updates the cursor or moves to the end if already at the bottom.

Call relations: Regular input, Vim normal mode, and Vim range measurement call this for downward movement.

Call graph: calls 2 internal fn (current_display_col, move_to_display_col_on_line); called by 3 (handle_vim_normal, input_with_keymap, target_for_motion); 1 external calls (wrapped_line_index_by_start).

TextArea::move_cursor_to_beginning_of_line1325–1333 ↗
fn move_cursor_to_beginning_of_line(&mut self, move_up_at_bol: bool)

Purpose: Moves the cursor to the start of the current line, with an optional shortcut behavior that moves to the previous line if already at the start.

Data flow: It receives a flag, finds the current line start, optionally finds the previous line start, sets the cursor, and clears preferred column memory.

Call relations: Regular keymap input calls this for line-start movement shortcuts.

Call graph: calls 3 internal fn (beginning_of_current_line, beginning_of_line, set_cursor); called by 1 (input_with_keymap).

TextArea::move_cursor_to_end_of_line1335–1343 ↗
fn move_cursor_to_end_of_line(&mut self, move_down_at_eol: bool)

Purpose: Moves the cursor to the end of the current line, with an optional shortcut behavior that moves to the next line's end if already at the end.

Data flow: It receives a flag, finds the current line end, optionally finds the next line end, and sets the cursor there.

Call relations: Regular keymap input calls this for line-end movement shortcuts.

Call graph: calls 3 internal fn (end_of_current_line, end_of_line, set_cursor); called by 1 (input_with_keymap).

TextArea::element_payloads1347–1352 ↗
fn element_payloads(&self) -> Vec<String>

Purpose: Returns the current text content of all embedded elements. This gives callers just the visible placeholder strings for those elements.

Data flow: It walks internal element ranges, extracts each matching text slice, converts it to an owned string, and returns the list.

Call relations: External feature code can call this when it needs the text represented by embedded elements.

TextArea::text_elements1354–1368 ↗
fn text_elements(&self) -> Vec<UserTextElement>

Purpose: Returns public descriptions of all embedded elements, including their byte ranges and placeholder text. This lets outside code save or inspect element positions.

Data flow: It walks internal elements, builds UserTextElement values with their ranges and current text, and returns them.

Call relations: Code outside the text area can use this to preserve element metadata alongside the raw text.

TextArea::text_element_snapshots1370–1383 ↗
fn text_element_snapshots(&self) -> Vec<TextElementSnapshot>

Purpose: Returns internal snapshots of embedded elements, including stable ids, ranges, and current text. This is useful when later code needs to compare or update elements.

Data flow: It walks element records, extracts text for each valid range, and returns snapshot objects.

Call relations: Internal UI or attachment code can call this to track embedded element state.

TextArea::element_id_for_exact_range1385–1390 ↗
fn element_id_for_exact_range(&self, range: Range<usize>) -> Option<u64>

Purpose: Finds the id of an embedded element that exactly matches a given byte range.

Data flow: It receives a range, searches stored elements for the same range, and returns the id if found.

Call relations: Internal code can use this when it already knows a range and needs the stable element identity.

TextArea::replace_element_payload1396–1459 ↗
fn replace_element_payload(&mut self, old: &str, new: &str) -> bool

Purpose: Replaces the text of one embedded element while preserving element tracking. This is useful when a placeholder needs to be relabeled.

Data flow: It receives old and new text, finds the element whose current payload matches the old text, replaces that text, updates that element's range, shifts later elements, updates the cursor, clears wrapping, and returns whether a replacement happened.

Call relations: Image relabeling code calls this when local image placeholders need new visible text.

Call graph: calls 1 internal fn (clamp_pos_to_nearest_boundary); called by 1 (relabel_local_images).

TextArea::insert_element1461–1469 ↗
fn insert_element(&mut self, text: &str) -> u64

Purpose: Inserts a new embedded element at the cursor and records its range as protected. The cursor ends up after the inserted element.

Data flow: It receives the element's text, clamps the insertion point, inserts the text, adds an element range around it, moves the cursor to the end, and returns the new element id.

Call relations: Attachment code calls this when inserting things such as image placeholders into the input.

Call graph: calls 4 internal fn (add_element, clamp_pos_for_insertion, insert_str_at, set_cursor); called by 1 (attach_image).

TextArea::add_element1471–1476 ↗
fn add_element(&mut self, range: Range<usize>) -> u64

Purpose: Adds an internal embedded-element record for an existing range and assigns it a fresh id.

Data flow: It receives a range, allocates the next id, stores the element, sorts elements by position, and returns the id.

Call relations: insert_element and add_element_range use this after they have chosen a valid range.

Call graph: calls 1 internal fn (next_element_id); called by 2 (add_element_range, insert_element).

TextArea::add_element_range1482–1504 ↗
fn add_element_range(&mut self, range: Range<usize>) -> Option<u64>

Purpose: Marks an existing range of text as an embedded element. It rejects empty, duplicate, or overlapping ranges.

Data flow: It receives a range, clamps both ends to safe character boundaries, checks for validity and overlap, then adds the element and returns its id if successful.

Call relations: External code can call this when text already contains a placeholder that should become protected.

Call graph: calls 2 internal fn (add_element, clamp_pos_to_char_boundary).

TextArea::remove_element_range1506–1516 ↗
fn remove_element_range(&mut self, range: Range<usize>) -> bool

Purpose: Removes embedded-element tracking for an exact text range without deleting the text itself.

Data flow: It receives a range, clamps it safely, removes any stored element with exactly that range, and returns whether anything was removed.

Call relations: External code can call this when a placeholder should stop being treated as protected.

Call graph: calls 1 internal fn (clamp_pos_to_char_boundary).

TextArea::next_element_id1518–1522 ↗
fn next_element_id(&mut self) -> u64

Purpose: Generates a new stable id for an embedded element. The id counter saturates instead of overflowing.

Data flow: It reads the current next id, increments the stored counter, and returns the old value as the assigned id.

Call relations: Element creation paths call this when rebuilding text with elements or adding a new element.

Call graph: called by 2 (add_element, set_text_inner).

TextArea::find_element_containing1523–1527 ↗
fn find_element_containing(&self, pos: usize) -> Option<usize>

Purpose: Finds whether a byte position is inside an embedded element, not including its edges. This helps keep the cursor out of protected ranges.

Data flow: It receives a position, scans element ranges, and returns the index of the first element whose interior contains that position.

Call relations: Boundary clamping and atomic cursor movement call this whenever they need to avoid splitting an element.

Call graph: called by 5 (adjust_pos_out_of_elements, clamp_pos_for_insertion, clamp_pos_to_nearest_boundary, next_atomic_boundary, prev_atomic_boundary).

TextArea::clamp_pos_to_char_boundary1529–1547 ↗
fn clamp_pos_to_char_boundary(&self, pos: usize) -> usize

Purpose: Moves a byte position to the nearest valid character boundary. This prevents slicing a UTF-8 character in half.

Data flow: It receives a byte position, limits it to the text length, checks whether it is already valid, otherwise searches backward and forward for valid boundaries and returns the closest one.

Call relations: Text setting, element range changes, cursor clamping, and insertion safety all rely on this.

Call graph: called by 5 (add_element_range, clamp_pos_for_insertion, clamp_pos_to_nearest_boundary, remove_element_range, set_text_inner).

TextArea::clamp_pos_to_nearest_boundary1549–1563 ↗
fn clamp_pos_to_nearest_boundary(&self, pos: usize) -> usize

Purpose: Moves a byte position to a safe cursor location. Safe means both a valid character boundary and not inside an embedded element.

Data flow: It first clamps to a character boundary, then if the position is inside an element, chooses the nearest element edge and returns that boundary.

Call relations: Cursor setting, text replacement, element replacement, and visual-line movement use this after computing a new position.

Call graph: calls 2 internal fn (clamp_pos_to_char_boundary, find_element_containing); called by 5 (move_to_display_col_on_line, replace_element_payload, replace_range_raw, set_cursor, set_text_inner).

TextArea::clamp_pos_for_insertion1565–1581 ↗
fn clamp_pos_for_insertion(&self, pos: usize) -> usize

Purpose: Chooses a safe insertion point for new text. It prevents new text from being inserted into the middle of an embedded element.

Data flow: It receives a position, clamps it to a character boundary, and if it falls inside an element, returns the closest edge of that element.

Call relations: Text insertion and element insertion call this before modifying the string.

Call graph: calls 2 internal fn (clamp_pos_to_char_boundary, find_element_containing); called by 2 (insert_element, insert_str_at).

TextArea::expand_range_to_element_boundaries1583–1603 ↗
fn expand_range_to_element_boundaries(&self, mut range: Range<usize>) -> Range<usize>

Purpose: Expands an edit range until any touched embedded elements are included whole. This avoids cutting or copying half of a protected placeholder.

Data flow: It receives a range, repeatedly scans elements for overlaps, grows the range to include full overlapping elements, and returns the final range.

Call relations: Replacement, kill, and yank routines call this before operating on user-visible text.

Call graph: called by 3 (kill_range_with_kind, replace_range, yank_range_with_kind).

TextArea::shift_elements1605–1628 ↗
fn shift_elements(&mut self, at: usize, removed: usize, inserted: usize)

Purpose: Updates embedded-element ranges after text has been inserted, deleted, or replaced. It keeps element positions lined up with the changed string.

Data flow: It receives an edit start, removed length, and inserted length. It removes elements fully deleted by the edit, shifts elements after the edit by the length difference, and repairs partial overlaps defensively.

Call relations: Insertion and replacement-update code call this whenever text length changes.

Call graph: called by 2 (insert_str_at, update_elements_after_replace).

TextArea::update_elements_after_replace1630–1632 ↗
fn update_elements_after_replace(&mut self, start: usize, end: usize, inserted_len: usize)

Purpose: Updates element positions after a replacement operation. It is a small adapter around the generic element-shifting logic.

Data flow: It receives replacement start, end, and inserted length, computes the removed length, and calls shift_elements.

Call relations: replace_range_raw calls this after changing the text.

Call graph: calls 1 internal fn (shift_elements); called by 1 (replace_range_raw).

TextArea::prev_atomic_boundary1634–1658 ↗
fn prev_atomic_boundary(&self, pos: usize) -> usize

Purpose: Finds the previous safe cursor boundary before a position. It treats an embedded element as one indivisible chunk and respects Unicode grapheme clusters.

Data flow: It receives a position, returns zero at the start, jumps to an element start when inside or at an element end, otherwise asks a Unicode grapheme cursor for the previous boundary and adjusts away from element interiors.

Call relations: Backward deletion, left movement, Vim cursor placement, and word-end logic use this.

Call graph: calls 1 internal fn (find_element_containing); called by 7 (delete_backward, handle_vim_insert, move_cursor_left, vim_line_end_cursor, vim_normal_end_cursor, vim_word_end_cursor, vim_word_end_exclusive); 1 external calls (new).

TextArea::next_atomic_boundary1660–1684 ↗
fn next_atomic_boundary(&self, pos: usize) -> usize

Purpose: Finds the next safe cursor boundary after a position. It treats embedded elements and complex Unicode characters as indivisible units.

Data flow: It receives a position, returns the text length at the end, jumps to an element end when inside or at its start, otherwise asks a Unicode grapheme cursor for the next boundary and adjusts away from element interiors.

Call relations: Forward deletion, right movement, Vim append, and paste-after logic use this.

Call graph: calls 1 internal fn (find_element_containing); called by 5 (delete_forward, delete_forward_kill, handle_vim_normal, move_cursor_right, paste_after_cursor); 1 external calls (new).

TextArea::beginning_of_previous_word1686–1719 ↗
fn beginning_of_previous_word(&self) -> usize

Purpose: Finds where the previous word-like piece starts. It skips trailing whitespace and treats runs of punctuation separately from normal word text.

Data flow: It looks backward from the cursor, finds the previous non-whitespace run, splits it into pieces, chooses the correct piece start, and adjusts out of embedded elements.

Call relations: Word movement and backward-word deletion call this in regular and Vim editing.

Call graph: calls 2 internal fn (adjust_pos_out_of_elements, split_word_pieces); called by 4 (delete_backward_word, handle_vim_normal, input_with_keymap, target_for_motion).

TextArea::end_of_next_word1721–1723 ↗
fn end_of_next_word(&self) -> usize

Purpose: Finds the end position of the next word-like piece starting from the current cursor.

Data flow: It passes the current cursor position to the more general end_of_next_word_from helper and returns that result.

Call relations: Word deletion, word movement, and Vim word-end helpers call this.

Call graph: calls 1 internal fn (end_of_next_word_from); called by 4 (beginning_of_next_word, delete_forward_word, input_with_keymap, vim_word_end_exclusive).

TextArea::end_of_next_word_from1725–1749 ↗
fn end_of_next_word_from(&self, cursor_pos: usize) -> usize

Purpose: Finds the end of the next word-like piece starting from a supplied position. It can be used from places other than the current cursor.

Data flow: It receives a starting position, skips whitespace, takes the next non-whitespace run, splits it into separator-aware pieces, chooses the correct end, and adjusts out of embedded elements.

Call relations: end_of_next_word and Vim word-end logic call this when they need word boundaries.

Call graph: calls 2 internal fn (adjust_pos_out_of_elements, split_word_pieces); called by 2 (end_of_next_word, vim_word_end_exclusive).

TextArea::vim_word_end_exclusive1751–1763 ↗
fn vim_word_end_exclusive(&self) -> usize

Purpose: Computes the exclusive end boundary used for Vim word-end operations. Exclusive means the returned position is just after the affected text.

Data flow: It finds the next word end, checks whether the cursor would fail to move in Vim terms, and may advance to the following word end.

Call relations: Vim motion targeting and Vim word-end cursor placement call this.

Call graph: calls 3 internal fn (end_of_next_word, end_of_next_word_from, prev_atomic_boundary); called by 2 (target_for_motion, vim_word_end_cursor).

TextArea::vim_word_end_cursor1765–1772 ↗
fn vim_word_end_cursor(&self) -> usize

Purpose: Finds the cursor position for Vim's word-end movement. Vim displays the cursor on the last character of the word, so this moves back from the exclusive end.

Data flow: It gets the exclusive word end and, if it is beyond the cursor, returns the previous atomic boundary; otherwise it returns the end itself.

Call relations: Vim normal mode calls this for word-end movement.

Call graph: calls 2 internal fn (prev_atomic_boundary, vim_word_end_exclusive); called by 1 (handle_vim_normal).

TextArea::vim_line_end_cursor1774–1782 ↗
fn vim_line_end_cursor(&self) -> usize

Purpose: Finds the cursor position for Vim's line-end movement. Vim normally lands on the last character of the line rather than after it.

Data flow: It finds the current line start and end, moves one atomic step back from the line end when the line is non-empty, and never moves before the line start.

Call relations: Vim normal mode calls this for line-end movement.

Call graph: calls 3 internal fn (beginning_of_current_line, end_of_current_line, prev_atomic_boundary); called by 1 (handle_vim_normal).

TextArea::beginning_of_next_word1784–1801 ↗
fn beginning_of_next_word(&self) -> usize

Purpose: Finds the start of the next word-like piece. If the cursor is in whitespace, it returns the next non-whitespace position; otherwise it skips the current word first.

Data flow: It searches forward from the cursor, either returns the next non-whitespace start or finds the current word end and then the next word start, adjusting away from embedded elements.

Call relations: Vim normal movement and Vim motion targeting call this for word-forward commands.

Call graph: calls 2 internal fn (adjust_pos_out_of_elements, end_of_next_word); called by 2 (handle_vim_normal, target_for_motion).

TextArea::adjust_pos_out_of_elements1803–1814 ↗
fn adjust_pos_out_of_elements(&self, pos: usize, prefer_start: bool) -> usize

Purpose: Moves a position out of an embedded element if it lands inside one. The caller chooses whether to prefer the start or end edge.

Data flow: It receives a position and preference flag, checks whether the position is inside an element, and returns that element's start or end if needed.

Call relations: Word-boundary functions call this so word movement does not stop inside protected placeholders.

Call graph: calls 1 internal fn (find_element_containing); called by 3 (beginning_of_next_word, beginning_of_previous_word, end_of_next_word_from).

TextArea::wrapped_lines1817–1836 ↗
fn wrapped_lines(&self, width: u16) -> Ref<'_, Vec<Range<usize>>>

Purpose: Returns the current text split into visual line ranges for a given terminal width. It caches the result so repeated rendering does not rewrap unchanged text.

Data flow: It receives a width, checks whether the cached wrap result matches that width, recalculates ranges if needed, stores them, and returns a borrowed view of the ranges.

Call relations: Height calculation, cursor positioning, and rendering paths call this before working with visual rows.

Call graph: calls 1 internal fn (wrap_ranges); called by 5 (cursor_pos_with_state, desired_height, render_ref, render_ref_masked, render_ref_styled_with_highlights); 2 external calls (new, map).

TextArea::effective_scroll1843–1869 ↗
fn effective_scroll(
        &self,
        area_height: u16,
        lines: &[Range<usize>],
        current_scroll: u16,
    ) -> u16

Purpose: Chooses a scroll offset that keeps the cursor visible inside the drawing area. It also clamps scroll so it never goes past the available wrapped lines.

Data flow: It receives area height, wrapped lines, and current scroll, computes total lines and cursor line, adjusts scroll upward or downward if the cursor is outside the visible window, and returns the new scroll.

Call relations: Cursor positioning and all render variants call this before drawing visible lines.

Call graph: called by 4 (cursor_pos_with_state, render_ref, render_ref_masked, render_ref_styled_with_highlights); 2 external calls (wrapped_line_index_by_start, len).

TextArea::render_ref1882–1890 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State)

Purpose: Draws the text area normally into the terminal buffer. It wraps the text, updates scroll, and renders only visible lines.

Data flow: It receives a drawing rectangle, buffer, and state, gets wrapped lines, computes effective scroll, stores it in state, and asks the line renderer to draw the visible slice with default styling.

Call relations: The terminal UI rendering system calls this as the standard render path.

Call graph: calls 3 internal fn (effective_scroll, render_lines, wrapped_lines); 1 external calls (default).

TextArea::render_ref_masked1894–1908 ↗
fn render_ref_masked(
        &self,
        area: Rect,
        buf: &mut Buffer,
        state: &mut TextAreaState,
        mask_char: char,
    )

Purpose: Draws the text area with every visible character replaced by a mask character. This is useful for password-like input.

Data flow: It receives a drawing area, buffer, state, and mask character, computes wrapped lines and scroll, then calls the masked line renderer for the visible range.

Call relations: UI code calls this variant when the input content should be hidden.

Call graph: calls 3 internal fn (effective_scroll, render_lines_masked, wrapped_lines).

TextArea::render_ref_styled_with_highlights1914–1929 ↗
fn render_ref_styled_with_highlights(
        &self,
        area: Rect,
        buf: &mut Buffer,
        state: &mut TextAreaState,
        base_style: Style,
        highlights: &[(Range<usize>, St

Purpose: Draws the text area with a base style and extra highlighted ranges. This supports visual emphasis such as matches or tokens.

Data flow: It receives drawing information, a base style, and highlight ranges, computes wrapped lines and scroll, then renders the visible lines with those styles applied.

Call relations: UI code calls this styled render path when plain text drawing is not enough.

Call graph: calls 3 internal fn (effective_scroll, render_lines, wrapped_lines).

TextArea::render_lines1931–1975 ↗
fn render_lines(
        &self,
        area: Rect,
        buf: &mut Buffer,
        lines: &[Range<usize>],
        range: std::ops::Range<usize>,
        base_style: Style,
        highlights: &[(R

Purpose: Draws a set of visible text lines into the terminal buffer with a base style, special element coloring, and optional temporary highlights. It is used when the text area is visible and may need styled regions such as attachments or search matches.

Data flow: It reads the text, the precomputed line byte ranges, the screen rectangle, the base style, and highlight ranges. For each visible row, it paints the whole row with the base style, writes the plain text, overlays embedded elements in cyan where they overlap the line, then overlays highlight styles last. The output is a changed terminal buffer; the stored text is not changed.

Call relations: The normal render paths, including render_ref and render_ref_styled_with_highlights, call this when they need to paint unmasked text. It hands the actual screen writing to the buffer methods that set styles and strings.

Call graph: called by 2 (render_ref, render_ref_styled_with_highlights); 5 external calls (set_string, set_style, new, fg, enumerate).

TextArea::render_lines_masked1977–1995 ↗
fn render_lines_masked(
        &self,
        area: Rect,
        buf: &mut Buffer,
        lines: &[Range<usize>],
        range: std::ops::Range<usize>,
        mask_char: char,
    )

Purpose: Draws visible text as repeated mask characters, like password dots or stars. It lets the text area show that content exists without revealing the actual characters.

Data flow: It reads the same line ranges and screen rectangle as normal rendering, plus a mask character. For each visible line, it replaces every character in that slice with the mask character and writes that masked string to the terminal buffer. The original text remains unchanged.

Call relations: The masked render path, render_ref_masked, calls this when hidden input should be displayed. It delegates final drawing to the terminal buffer.

Call graph: called by 1 (render_ref_masked); 3 external calls (set_string, default, enumerate).

tests::rand_grapheme2006–2045 ↗
fn rand_grapheme(rng: &mut rand::rngs::StdRng) -> String

Purpose: Creates a random visible text unit for tests, including plain letters, spaces, newlines, emoji, CJK characters, combining marks, and joined emoji sequences. This helps tests cover the kinds of text that often break cursor movement.

Data flow: It takes a seeded random number generator, chooses a category, then returns one string representing a single grapheme, meaning what a user sees as one character even if it is stored as several code points. It changes only the random generator’s state.

Call relations: This is a test helper. No caller is shown in this chunk, but it is designed to feed randomized TextArea tests with realistic Unicode cases.

Call graph: 2 external calls (random_range, format!).

tests::ta_with2047–2051 ↗
fn ta_with(text: &str) -> TextArea

Purpose: Builds a TextArea already filled with the given text. Tests use it to avoid repeating setup code.

Data flow: It receives a string slice, creates a new TextArea, inserts the string, and returns the populated TextArea. The caller gets a ready-to-edit test object.

Call relations: Many tests in this module call it before exercising cursor movement, deletion, rendering, or Vim behavior. It relies on TextArea::new and insert_str.

Call graph: calls 1 internal fn (new).

tests::insert_and_replace_update_cursor_and_text2054–2094 ↗
fn insert_and_replace_update_cursor_and_text()

Purpose: Checks that inserting and replacing text changes both the text and cursor position in predictable ways. It protects against edits leaving the cursor in the wrong place.

Data flow: It builds small text areas, performs inserts and replacements before, at, and after the cursor, then compares the resulting text and cursor values with expected results. It produces no value; failures are reported by assertions.

Call relations: The test runner calls this test. It uses ta_with and public editing methods to verify the core insertion and replacement rules.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::insert_str_at_clamps_to_char_boundary2097–2104 ↗
fn insert_str_at_clamps_to_char_boundary()

Purpose: Checks that insertion at an invalid byte position inside a multi-byte character is moved to a safe character boundary. This prevents corrupt Unicode text.

Data flow: It creates a TextArea containing a Chinese character, asks to insert at byte position 1, and confirms the insertion happens before the character. The text and cursor are the observed outputs.

Call relations: The test runner calls it. It directly exercises TextArea::new, insert_str_at, and cursor handling for Unicode safety.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tests::set_text_clamps_cursor_to_char_boundary2107–2115 ↗
fn set_text_clamps_cursor_to_char_boundary()

Purpose: Checks that replacing the whole text moves the cursor to a valid boundary when the old cursor position would land inside a new multi-byte character.

Data flow: It starts with ASCII text and a cursor at byte 1, replaces the text with one Chinese character, then inserts another character. The expected final text proves the cursor was clamped safely.

Call relations: The test runner calls it. It verifies set_text_clearing_elements and later insertion work together without Unicode corruption.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tests::delete_backward_and_forward_edges2118–2141 ↗
fn delete_backward_and_forward_edges()

Purpose: Checks basic backspace and delete behavior at normal positions and at the start or end of text. It ensures edge positions are harmless no-ops instead of crashes or wrong edits.

Data flow: It edits a short string, deletes backward and forward, moves the cursor to boundaries, and checks text and cursor after each step. The only output is assertion success or failure.

Call relations: The test runner calls it. It uses ta_with and deletion methods to validate simple character deletion rules.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::delete_forward_deletes_element_at_left_edge2144–2156 ↗
fn delete_forward_deletes_element_at_left_edge()

Purpose: Checks that forward delete removes a special embedded element when the cursor is at the element’s start. Embedded elements are treated as one atomic item.

Data flow: It inserts normal text, an element placeholder, and more text, moves the cursor to the element start, deletes forward, and confirms the element text is gone while surrounding text remains. Cursor position is also checked.

Call relations: The test runner calls it. It uses TextArea::new and insert_element to exercise element-aware deletion.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tests::vim_insert_and_escape2159–2170 ↗
fn vim_insert_and_escape()

Purpose: Checks the basic Vim flow of entering insert mode, typing a character, then returning to normal mode with Escape. Vim mode here means an editing style where keys can mean commands instead of literal input.

Data flow: It enables Vim behavior, sends key events for i, h, and Escape, then checks that h was inserted, mode is Normal, and cursor moved to the expected spot. It changes only the test TextArea.

Call relations: The test runner calls it. It drives TextArea::input with key events to verify the Vim state machine.

Call graph: calls 1 internal fn (new); 3 external calls (Char, new, assert_eq!).

tests::vim_insert_key_enters_insert_mode2173–2182 ↗
fn vim_insert_key_enters_insert_mode()

Purpose: Checks that the Insert key can enter Vim insert mode and allow typing. This supports keyboards or users that use the physical Insert key.

Data flow: It enables Vim mode, sends Insert and then h, and verifies the text contains h and the mode label says Insert. Assertions are the output.

Call relations: The test runner calls it. It exercises TextArea::input and the Vim mode label.

Call graph: calls 1 internal fn (new); 3 external calls (Char, new, assert_eq!).

tests::vim_normal_arrow_keys_move_cursor2185–2201 ↗
fn vim_normal_arrow_keys_move_cursor()

Purpose: Checks that arrow keys still move the cursor while Vim is in normal mode. This keeps standard terminal navigation usable even with Vim behavior enabled.

Data flow: It starts with two lines of text, enables Vim mode, sends right, down, left, and up key events, and checks the cursor after each move. The TextArea cursor is the changing state.

Call relations: The test runner calls it. It verifies the normal-mode input path dispatches arrow keys to cursor movement.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::vim_escape_from_insert_at_start_does_not_underflow2204–2213 ↗
fn vim_escape_from_insert_at_start_does_not_underflow()

Purpose: Checks that pressing Escape at the start of text does not move the cursor before zero. This prevents an underflow, where subtracting from zero wraps to a huge number.

Data flow: It enables Vim mode, types one character, presses Escape, and checks mode and cursor. The expected cursor remains safely at 0.

Call relations: The test runner calls it. It targets a boundary case in the Escape-from-insert logic.

Call graph: calls 1 internal fn (new); 3 external calls (Char, new, assert_eq!).

tests::vim_escape_from_insert_at_line_start_stays_on_line2216–2226 ↗
fn vim_escape_from_insert_at_line_start_stays_on_line()

Purpose: Checks that leaving Vim insert mode at the beginning of a line does not jump to the previous line. This matches user expectations around line starts.

Data flow: It places the cursor at the start of the second line, enters insert mode, presses Escape, and checks that cursor stays at that line start. The text is unchanged.

Call relations: The test runner calls it. It exercises TextArea::input with Vim enabled and a newline boundary.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_escape_moves_by_grapheme_boundary2229–2239 ↗
fn vim_escape_moves_by_grapheme_boundary()

Purpose: Checks that Escape in Vim mode moves left by one visible character, not by one byte. This is important for emoji and other multi-byte text.

Data flow: It starts with two thumbs-up emoji, puts the cursor at the end, enters insert mode, exits it, and checks the cursor lands before the second emoji. The byte index matches a full emoji boundary.

Call relations: The test runner calls it. It verifies Vim cursor adjustment uses grapheme-aware movement.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_escape_respects_atomic_element_boundary2242–2253 ↗
fn vim_escape_respects_atomic_element_boundary()

Purpose: Checks that leaving insert mode near an embedded element does not put the cursor inside that element. Atomic elements should be skipped like indivisible objects.

Data flow: It inserts text and an element, enables Vim mode, enters and exits insert mode, then checks the cursor sits before the element. The element remains intact.

Call relations: The test runner calls it. It combines insert_element with Vim Escape behavior.

Call graph: calls 1 internal fn (new); 3 external calls (Char, new, assert_eq!).

tests::vim_shift_i_enters_insert_at_first_non_blank_with_shift_only_binding2256–2266 ↗
fn vim_shift_i_enters_insert_at_first_non_blank_with_shift_only_binding()

Purpose: Checks that a configured Shift-I Vim command moves to the first non-space character on the line and enters insert mode. This tests custom key binding behavior.

Data flow: It sets a keymap entry for Shift-I, places the cursor within an indented line, sends uppercase I, and checks mode and cursor. The cursor moves to after the indentation.

Call relations: The test runner calls it. It edits the TextArea Vim keymap and then drives input through that custom binding.

Call graph: 5 external calls (Char, new, assert_eq!, ta_with, vec!).

tests::vim_shift_a_enters_insert_at_line_end_with_shift_only_binding2269–2279 ↗
fn vim_shift_a_enters_insert_at_line_end_with_shift_only_binding()

Purpose: Checks that a configured Shift-A Vim command moves to the end of the current line and enters insert mode. It protects the append-at-line-end command.

Data flow: It sets the append-line-end binding, places the cursor in a line, sends uppercase A, and verifies the cursor is at that line’s end and mode is Insert. The text is unchanged.

Call relations: The test runner calls it. It verifies custom Vim normal-mode key binding dispatch.

Call graph: 5 external calls (Char, new, assert_eq!, ta_with, vec!).

tests::vim_shift_c_changes_to_line_end_and_enters_insert_mode2282–2293 ↗
fn vim_shift_c_changes_to_line_end_and_enters_insert_mode()

Purpose: Checks that Shift-C deletes from the cursor to the end of the line, saves the removed text, and enters insert mode. This is the Vim change-to-end-of-line command.

Data flow: It starts in the middle of a line, sends Shift-C, and checks that the tail of the line was removed, the cursor stayed in place, mode became Insert, and the kill buffer holds the deleted text.

Call relations: The test runner calls it. It drives TextArea::input to test Vim command editing and kill-buffer storage.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_uppercase_c_changes_to_line_end2296–2306 ↗
fn vim_uppercase_c_changes_to_line_end()

Purpose: Checks that an uppercase C key event without an explicit Shift modifier also performs Vim change-to-end-of-line. This covers how terminals may report uppercase letters.

Data flow: It sends C, then checks the text was cut at the cursor and Vim mode changed to Insert. The expected cursor remains at the cut point.

Call relations: The test runner calls it. It verifies equivalent input forms reach the same Vim command path.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_s_substitutes_current_character_and_enters_insert_mode2309–2325 ↗
fn vim_s_substitutes_current_character_and_enters_insert_mode()

Purpose: Checks that Vim s deletes the current character and enters insert mode, so the next typed character replaces it. This is the substitute command.

Data flow: It sends s on the middle character of abc, confirms b is removed, then types X and confirms the text becomes aXc. Cursor and mode are checked after each phase.

Call relations: The test runner calls it. It exercises Vim normal command handling followed by insert-mode typing.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_s_on_empty_line_enters_insert_without_deleting_newline2328–2338 ↗
fn vim_s_on_empty_line_enters_insert_without_deleting_newline()

Purpose: Checks that Vim s on an empty line does not delete the newline. It should simply enter insert mode at that empty line.

Data flow: It positions the cursor on a blank line, sends s, and verifies text is unchanged, cursor stays there, and mode becomes Insert. The newline remains present.

Call relations: The test runner calls it. It targets an empty-line edge case in Vim substitute behavior.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_d_at_line_end_does_not_remove_newline2341–2351 ↗
fn vim_d_at_line_end_does_not_remove_newline()

Purpose: Checks that Vim D at the end of a line does not remove the newline or store anything. There is no text after the cursor on that line to delete.

Data flow: It places the cursor right before a newline, sends D, and checks text, mode, and kill buffer. Everything remains unchanged except normal-mode processing completes.

Call relations: The test runner calls it. It verifies delete-to-line-end has a safe no-op case.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_c_at_line_end_enters_insert_without_removing_newline2354–2365 ↗
fn vim_c_at_line_end_enters_insert_without_removing_newline()

Purpose: Checks that Vim C at a line end enters insert mode without deleting the newline. This allows changing an empty tail of a line.

Data flow: It places the cursor before a newline, sends C, and verifies text and kill buffer stay unchanged while mode becomes Insert. Cursor remains at the line end.

Call relations: The test runner calls it. It tests the change-to-line-end edge case.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_shift_o_opens_line_above_with_shift_only_binding2368–2379 ↗
fn vim_shift_o_opens_line_above_with_shift_only_binding()

Purpose: Checks that a configured Shift-O Vim command inserts a new blank line above the current line and enters insert mode. This matches Vim’s open-line-above command.

Data flow: It sets the open-line-above binding, positions the cursor in the second line, sends uppercase O, and checks a blank line was inserted before that line. Cursor lands on the new line.

Call relations: The test runner calls it. It verifies custom key binding plus line insertion behavior.

Call graph: 5 external calls (Char, new, assert_eq!, ta_with, vec!).

tests::vim_o_opens_line_below_on_inserted_line2382–2392 ↗
fn vim_o_opens_line_below_on_inserted_line()

Purpose: Checks that Vim o opens a blank line below the current line, even when the cursor is in the middle of that line. It should enter insert mode on the new line.

Data flow: It starts in one line of a two-line string, sends o, and verifies a blank line appears after the current line. Cursor moves to the start of the inserted line.

Call relations: The test runner calls it. It exercises the Vim open-line-below command.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_o_opens_line_below_final_line_and_moves_to_new_line2395–2405 ↗
fn vim_o_opens_line_below_final_line_and_moves_to_new_line()

Purpose: Checks that Vim o works on the final line by appending a newline and moving the cursor there. This covers the end-of-text case.

Data flow: It sends o while editing a one-line string and confirms the text ends with a newline, mode is Insert, and cursor is at the new line. The TextArea is modified in place.

Call relations: The test runner calls it. It verifies the final-line branch of open-line-below.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_delete_word2408–2418 ↗
fn vim_delete_word()

Purpose: Checks Vim dw, delete word, from the start of a word. It should remove the word plus following space and save that text.

Data flow: It sends d then w at the start of hello world. The text becomes world and the kill buffer becomes hello plus a space.

Call relations: The test runner calls it. It tests Vim operator-pending flow, where d waits for a motion like w.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_change_inner_word_deletes_word_and_enters_insert2421–2434 ↗
fn vim_change_inner_word_deletes_word_and_enters_insert()

Purpose: Checks Vim ciw, change inner word, deletes the current word and enters insert mode. This supports common Vim text-object editing.

Data flow: It places the cursor on world, sends c, i, w, then verifies world was removed, saved in the kill buffer, and the cursor stayed at the deletion point in Insert mode.

Call relations: The test runner calls it. It exercises a Vim operator followed by a text object.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_word_text_objects_cover_delete_yank_and_big_word2437–2460 ↗
fn vim_word_text_objects_cover_delete_yank_and_big_word()

Purpose: Checks word text objects for yanking, deleting, and big-word behavior. A big word treats punctuation as part of the word until whitespace.

Data flow: First it yanks a word plus following space without changing text. Then it deletes a punctuation-heavy big word. It checks text, kill buffer, and mode.

Call relations: The test runner calls it. It verifies several Vim text-object paths through TextArea::input.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_word_text_objects_accept_cursor_at_word_end2463–2487 ↗
fn vim_word_text_objects_accept_cursor_at_word_end()

Purpose: Checks that word text objects still work when the cursor is at the end of a word or at the end of text. This avoids surprising no-ops at boundaries.

Data flow: It performs delete-around-word at the end of hello, then change-inner-big-word at the end of foo bar. It confirms the right text is removed and stored.

Call relations: The test runner calls it. It targets boundary handling in Vim word text-object selection.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_delimiter_text_objects_select_innermost_pair_and_aliases2490–2513 ↗
fn vim_delimiter_text_objects_select_innermost_pair_and_aliases()

Purpose: Checks Vim text objects for paired delimiters such as parentheses and brackets. It should choose the innermost matching pair and support aliases like b for parentheses.

Data flow: It changes inside nested parentheses, then deletes around brackets. After each command sequence it checks the remaining text, kill buffer, and mode.

Call relations: The test runner calls it. It exercises delimiter-finding logic through Vim input commands.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_empty_inner_text_objects_are_valid_targets2516–2542 ↗
fn vim_empty_inner_text_objects_are_valid_targets()

Purpose: Checks that empty text objects, such as inside empty parentheses or quotes, are still valid change targets. They should enter insert mode without deleting anything.

Data flow: It runs change-inside-parentheses on call() and change-inside-quotes on an empty quoted string. In both cases text stays the same, kill buffer is empty, and mode becomes Insert.

Call relations: The test runner calls it. It verifies Vim text-object commands can target zero-length ranges safely.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_quote_text_objects_are_line_local_and_handle_escapes2545–2568 ↗
fn vim_quote_text_objects_are_line_local_and_handle_escapes()

Purpose: Checks quote-based Vim text objects with escaped quotes and with quotes split across lines. It ensures escaped quotes do not end the selection and quote matching stays on one line.

Data flow: First it changes inside a quoted string containing escaped quotes and verifies the inner text is removed. Then it tries deleting inside quotes across a newline and verifies nothing changes.

Call relations: The test runner calls it. It drives quote text-object selection through TextArea::input.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_text_object_cancellation_and_unsupported_change_motions_do_not_edit2571–2592 ↗
fn vim_text_object_cancellation_and_unsupported_change_motions_do_not_edit()

Purpose: Checks that unsupported Vim change motions and cancelled text-object commands do not edit text. This prevents partial command sequences from damaging input.

Data flow: It sends c then an unsupported motion and checks text and kill buffer are unchanged. Then it starts a d i sequence, presses Escape, and checks the pending operator is cleared.

Call relations: The test runner calls it. It verifies the Vim command state machine cancels safely.

Call graph: 5 external calls (Char, new, assert!, assert_eq!, ta_with).

tests::vim_operator_invalid_motion_is_consumed2595–2609 ↗
fn vim_operator_invalid_motion_is_consumed()

Purpose: Checks that an invalid motion after a Vim operator is consumed and clears the pending command. The invalid key should not be inserted or leave the editor stuck.

Data flow: It sends d to start delete, confirms an operator is pending, then sends z and checks text, cursor, mode, and pending state are unchanged or reset. No deletion happens.

Call relations: The test runner calls it. It tests Vim operator-pending error handling.

Call graph: 5 external calls (Char, new, assert!, assert_eq!, ta_with).

tests::vim_e_lands_on_word_end_character2612–2625 ↗
fn vim_e_lands_on_word_end_character()

Purpose: Checks that Vim e moves to the last character of the current word, not after it. This matters because later commands act on the character under the cursor.

Data flow: It sends e from the start of abc, confirms cursor lands on c, then sends x and confirms c is deleted and stored. The final text is ab.

Call relations: The test runner calls it. It connects motion behavior with a following Vim delete-character command.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_e_advances_from_each_word_end2628–2640 ↗
fn vim_e_advances_from_each_word_end()

Purpose: Checks repeated Vim e movement across several word endings. It captures the cursor positions as a snapshot for easier regression detection.

Data flow: It starts near the end of alpha, sends e three times, and records the text plus a caret line showing cursor position after each move. The snapshot assertion is the expected output.

Call relations: The test runner calls it. It uses TextArea::input and insta snapshot testing to verify motion over multiple words.

Call graph: 6 external calls (Char, new, new, format!, assert_snapshot!, ta_with).

tests::vim_delete_to_word_end_advances_from_existing_word_end2643–2653 ↗
fn vim_delete_to_word_end_advances_from_existing_word_end()

Purpose: Checks that d e from an existing word end deletes through the next word end rather than doing nothing. This matches Vim’s forward motion behavior.

Data flow: It starts near the end of alpha, sends d then e, and verifies the removed text spans a beta segment while the remaining text joins correctly. The kill buffer records the deleted range.

Call relations: The test runner calls it. It tests the combination of Vim delete operator and e motion.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_e_from_word_end_can_land_on_trailing_space2656–2664 ↗
fn vim_e_from_word_end_can_land_on_trailing_space()

Purpose: Checks that Vim e can move into trailing spaces when there is no later word. This covers the end-of-line whitespace case.

Data flow: It starts near the end of alpha followed by spaces, sends e, and checks the cursor lands in the trailing space area. Text is unchanged.

Call relations: The test runner calls it. It exercises a whitespace edge case in Vim word-end motion.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_e_advances_across_atomic_element_word_ends2667–2681 ↗
fn vim_e_advances_across_atomic_element_word_ends()

Purpose: Checks that Vim e treats embedded elements as atomic word-like units when moving across text. It should land at the element boundary, then continue to the next word.

Data flow: It builds text with alpha, an element, and gamma, starts near alpha’s end, sends e twice, and checks cursor positions. The first stop is the element start, the second is near the end of gamma.

Call relations: The test runner calls it. It combines insert_element with Vim word-end motion.

Call graph: calls 1 internal fn (new); 3 external calls (Char, new, assert_eq!).

tests::vim_dollar_lands_on_line_end_character2684–2697 ↗
fn vim_dollar_lands_on_line_end_character()

Purpose: Checks that Vim $ lands on the final character of the current line. A following x should delete that character, not the newline.

Data flow: It sends $ from inside abc, checks cursor is on c, then sends x and confirms c is removed while the newline and next line remain. The kill buffer stores c.

Call relations: The test runner calls it. It verifies line-end motion and delete-character behavior.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::vim_linewise_yank_pastes_below_current_line2700–2713 ↗
fn vim_linewise_yank_pastes_below_current_line()

Purpose: Checks that yy yanks a full line and p pastes it as a line below the current one. Linewise means the stored text represents whole lines, including the newline when appropriate.

Data flow: It yanks the first line of a three-line string, pastes, and checks the copied line appears below the original. It also checks cursor position, kill buffer content, and kill buffer kind.

Call relations: The test runner calls it. It exercises Vim linewise yank and paste through input events.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::delete_backward_word_and_kill_line_variants2716–2764 ↗
fn delete_backward_word_and_kill_line_variants()

Purpose: Checks several word-backward deletion and line-kill cases. It protects common shell-style editing actions from off-by-one errors around words and newlines.

Data flow: It runs multiple small scenarios, deleting previous words, killing to line end, and killing to line beginning. Each scenario checks the resulting text and cursor.

Call relations: The test runner calls it. It directly exercises TextArea deletion and kill-line helper methods.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::kill_current_line_removes_current_line_linewise2767–2777 ↗
fn kill_current_line_removes_current_line_linewise()

Purpose: Checks that killing a middle line removes that full line and stores it as linewise text. This supports commands like cut current line.

Data flow: It places the cursor on def in a three-line string, calls kill_current_line, and checks the line is gone, cursor moved to the start of the next line, and kill buffer contains def plus newline.

Call relations: The test runner calls it. It directly verifies kill_current_line and kill buffer metadata.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::kill_current_line_keeps_previous_newline_for_final_line2780–2790 ↗
fn kill_current_line_keeps_previous_newline_for_final_line()

Purpose: Checks the special case of killing the final line. The previous newline should remain so the earlier line still ends cleanly.

Data flow: It kills the final line def from abc\ndef and checks the text becomes abc plus newline. The killed text is stored without a trailing newline.

Call relations: The test runner calls it. It targets final-line behavior in kill_current_line.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::kill_whole_line_keymap_dispatch_uses_linewise_kill2793–2809 ↗
fn kill_whole_line_keymap_dispatch_uses_linewise_kill()

Purpose: Checks that a configured key binding for killing a whole line uses the linewise kill behavior. This ensures keymap dispatch reaches the right editing command.

Data flow: It changes the runtime editor keymap so Ctrl-U means kill whole line, sends that key event, and verifies the current line is removed and stored linewise. Text, cursor, buffer, and buffer kind are checked.

Call relations: The test runner calls it. It uses RuntimeKeymap::defaults, input_with_keymap, and kill-current-line behavior together.

Call graph: calls 1 internal fn (defaults); 5 external calls (Char, new, assert_eq!, ta_with, vec!).

tests::delete_forward_word_variants2812–2848 ↗
fn delete_forward_word_variants()

Purpose: Checks forward word deletion from the start, middle, end, before newlines, and past the end. It ensures word deletion behaves safely in common editing situations.

Data flow: It creates several TextAreas, sets the cursor in different positions, calls delete_forward_word, and checks text and cursor each time. Out-of-range cursors are clamped.

Call relations: The test runner calls it. It directly exercises delete_forward_word across edge cases.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::delete_forward_word_handles_atomic_elements2851–2882 ↗
fn delete_forward_word_handles_atomic_elements()

Purpose: Checks that forward word deletion can remove embedded elements as single units. This avoids deleting only part of an element placeholder.

Data flow: It builds three cases with an element at the start, after spaces, and under the cursor. After delete_forward_word, it verifies the element is removed and the cursor lands safely.

Call relations: The test runner calls it. It combines TextArea::new, insert_element, and delete_forward_word.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tests::delete_backward_word_respects_word_separators2885–2907 ↗
fn delete_backward_word_respects_word_separators()

Purpose: Checks that backward word deletion treats separators like slash as their own deletion units. This is useful for paths and punctuation-heavy input.

Data flow: It deletes backward through path-like strings and slash-space combinations, checking that words and separators are removed in expected chunks. Cursor follows the new end of text.

Call relations: The test runner calls it. It verifies word-boundary rules used by delete_backward_word.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::delete_forward_word_respects_word_separators2910–2932 ↗
fn delete_forward_word_respects_word_separators()

Purpose: Checks that forward word deletion also treats separators like slash as separate units. This keeps forward and backward word deletion consistent.

Data flow: It deletes forward through path-like strings and slash-space combinations, checking the remaining text after each deletion. Cursor stays at the starting position.

Call relations: The test runner calls it. It verifies separator-aware behavior in delete_forward_word.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::yank_restores_last_kill2935–2965 ↗
fn yank_restores_last_kill()

Purpose: Checks that yank, meaning paste from the kill buffer, restores text removed by several kill commands. This makes cut-and-paste editing reliable.

Data flow: It kills text from line end, deletes a previous word, and kills to line beginning in separate cases, then calls yank each time. It verifies the original text returns and the cursor moves after the pasted text.

Call relations: The test runner calls it. It directly exercises kill commands followed by yank.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::kill_buffer_persists_across_set_text2968–2980 ↗
fn kill_buffer_persists_across_set_text()

Purpose: Checks that replacing the TextArea text does not erase the kill buffer. This lets users paste previously killed text even after the visible input is reset.

Data flow: It kills text, sets the TextArea text to another value and then empty, calls yank, and verifies the old killed text returns. The kill buffer survives the set_text_clearing_elements calls.

Call relations: The test runner calls it. It verifies interaction between text reset and paste history.

Call graph: 3 external calls (assert!, assert_eq!, ta_with).

tests::cursor_left_and_right_handle_graphemes2983–3003 ↗
fn cursor_left_and_right_handle_graphemes()

Purpose: Checks that left and right cursor movement steps over whole visible characters, including emoji. It prevents landing inside a multi-byte grapheme.

Data flow: It moves left across a string containing an emoji, records that positions decrease safely, then moves right back to the end. Assertions check ordering and final position.

Call relations: The test runner calls it. It directly exercises move_cursor_left and move_cursor_right.

Call graph: 3 external calls (assert!, assert_eq!, ta_with).

tests::control_b_and_f_move_cursor3006–3015 ↗
fn control_b_and_f_move_cursor()

Purpose: Checks Emacs-style Ctrl-F and Ctrl-B movement, where Ctrl-F moves forward and Ctrl-B moves backward. This supports familiar terminal editing shortcuts.

Data flow: It starts with the cursor in abcd, sends Ctrl-F and Ctrl-B key events, and checks the cursor moves right then left by one visible character. Text is unchanged.

Call relations: The test runner calls it. It drives TextArea::input with control-modified key events.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::control_b_f_fallback_control_chars_move_cursor3018–3030 ↗
fn control_b_f_fallback_control_chars_move_cursor()

Purpose: Checks fallback handling for terminals that send raw control characters instead of modifier-marked Ctrl-B or Ctrl-F. This makes movement work across terminal differences.

Data flow: It sends Unicode control codes U+0002 and U+0006 as plain character events and checks the cursor moves left then right. The text remains unchanged.

Call relations: The test runner calls it. It verifies TextArea::input recognizes C0 control characters.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::c0_line_feed_inserts_newline_through_insert_newline_keymap3033–3041 ↗
fn c0_line_feed_inserts_newline_through_insert_newline_keymap()

Purpose: Checks that a raw line-feed control character inserts a newline through the normal newline key binding path. This supports terminals that report Enter as U+000A.

Data flow: It sends the line-feed character while the cursor is between a and b, then verifies the text becomes a newline b and cursor moves after the newline.

Call relations: The test runner calls it. It exercises C0 control character handling and keymap dispatch.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::c0_control_chars_respect_unbound_editor_movement3044–3056 ↗
fn c0_control_chars_respect_unbound_editor_movement()

Purpose: Checks that raw control-character movement does not happen when the corresponding editor key binding has been removed. User keymap choices should be respected.

Data flow: It clears the move-up binding, sends the raw Ctrl-P character, and verifies the cursor does not move. The keymap is the important input.

Call relations: The test runner calls it. It uses RuntimeKeymap::defaults and input_with_keymap to test remapping behavior.

Call graph: calls 1 internal fn (defaults); 4 external calls (Char, new, assert_eq!, ta_with).

tests::c0_control_chars_respect_remapped_editor_movement3059–3072 ↗
fn c0_control_chars_respect_remapped_editor_movement()

Purpose: Checks that raw control characters follow remapped editor movement bindings. This ensures fallback control-code handling still honors custom keymaps.

Data flow: It remaps move-down to Ctrl-P, sends raw Ctrl-P, and verifies the cursor moves down to the next line. Text is unchanged.

Call relations: The test runner calls it. It combines RuntimeKeymap customization with TextArea::input_with_keymap.

Call graph: calls 1 internal fn (defaults); 5 external calls (Char, new, assert_eq!, ta_with, vec!).

tests::delete_backward_word_alt_keys3075–3092 ↗
fn delete_backward_word_alt_keys()

Purpose: Checks two shortcut forms for deleting the previous word: Alt+Ctrl+H and Alt+Backspace. Both should remove the word before the cursor.

Data flow: It runs the same text through each key combination at the end of hello world. Each time, world is removed and the cursor ends after hello plus a space.

Call relations: The test runner calls it. It verifies input key dispatch for alternate backward-word deletion shortcuts.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::shift_backspace_and_shift_delete_keep_grapheme_delete_behavior3095–3109 ↗
fn shift_backspace_and_shift_delete_keep_grapheme_delete_behavior()

Purpose: Checks that adding Shift to Backspace or Delete does not change ordinary character deletion behavior. Shift should not accidentally trigger word deletion or another command.

Data flow: It deletes backward with Shift+Backspace and forward with Shift+Delete in separate short strings. Both leave ac and put the cursor in the expected place.

Call relations: The test runner calls it. It drives TextArea::input with shifted deletion keys.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::control_backspace_variants_delete_backward_word3112–3124 ↗
fn control_backspace_variants_delete_backward_word()

Purpose: Checks that Ctrl+Backspace and Ctrl+Shift+Backspace both delete the previous word. This supports common keyboard variants.

Data flow: For each modifier combination, it starts at the end of hello world, sends Backspace, and verifies world is removed. Cursor lands after the remaining space.

Call relations: The test runner calls it. It tests key dispatch for backward word deletion.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::control_delete_variants_delete_forward_word3127–3139 ↗
fn control_delete_variants_delete_forward_word()

Purpose: Checks that Ctrl+Delete and Ctrl+Shift+Delete both delete the next word. This mirrors common editor shortcuts.

Data flow: For each modifier combination, it starts at the beginning of hello world, sends Delete, and checks hello is removed while the leading space before world remains. Cursor stays at zero.

Call relations: The test runner calls it. It tests key dispatch for forward word deletion.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::delete_backward_word_handles_narrow_no_break_space3142–3148 ↗
fn delete_backward_word_handles_narrow_no_break_space()

Purpose: Checks that backward word deletion handles a narrow no-break space, a Unicode space often used in formatted times. It should treat it as a separator without breaking Unicode handling.

Data flow: It starts with 32 followed by a narrow no-break space and AM, sends Alt+Backspace, and verifies only AM is removed. Cursor ends at the new text length.

Call relations: The test runner calls it. It exercises Unicode separator handling through the input shortcut path.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::delete_forward_word_with_without_alt_modifier3151–3163 ↗
fn delete_forward_word_with_without_alt_modifier()

Purpose: Checks that Alt+Delete deletes a whole forward word, while plain Delete removes just one character. This keeps shortcut meanings distinct.

Data flow: It first sends Alt+Delete at the start of hello world and expects hello removed. Then it sends plain Delete at the start of hello and expects only h removed.

Call relations: The test runner calls it. It verifies TextArea::input distinguishes modified and unmodified Delete.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::delete_forward_word_alt_d3166–3172 ↗
fn delete_forward_word_alt_d()

Purpose: Checks that Alt+D deletes the word after the cursor. This is a common terminal editing shortcut.

Data flow: It places the cursor before world, sends Alt+D, and verifies world is removed while hello and the space remain. Cursor stays at the deletion point.

Call relations: The test runner calls it. It tests the Alt+D key binding through TextArea::input.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::control_h_backspace3175–3194 ↗
fn control_h_backspace()

Purpose: Checks that Ctrl+H behaves like Backspace at the middle, start, and end of text. Some terminals send Ctrl+H for Backspace.

Data flow: It sends Ctrl+H after the third character, at the beginning, and at the end, checking text and cursor each time. At the beginning it should do nothing.

Call relations: The test runner calls it. It verifies a terminal compatibility path in TextArea::input.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::altgr_ctrl_alt_char_inserts_literal3198–3206 ↗
fn altgr_ctrl_alt_char_inserts_literal()

Purpose: Checks that a Ctrl+Alt character can be inserted literally when it represents AltGr-style typing rather than an editor command. AltGr is a keyboard modifier used to type extra characters on many layouts.

Data flow: It sends Ctrl+Alt+c into an empty TextArea and verifies c is inserted and the cursor moves to 1. The key is treated as text input.

Call relations: The test runner calls it. It protects international keyboard behavior in the input parser.

Call graph: 4 external calls (Char, new, assert_eq!, ta_with).

tests::cursor_vertical_movement_across_lines_and_bounds3209–3238 ↗
fn cursor_vertical_movement_across_lines_and_bounds()

Purpose: Checks up and down cursor movement across lines of different lengths and at text boundaries. It ensures vertical movement preserves a preferred column when possible and clamps when needed.

Data flow: It starts on a long second line, moves up and down through shorter lines, and checks cursor positions are within expected line ranges. Moving down at the last line reaches the end.

Call relations: The test runner calls it. It directly exercises move_cursor_up and move_cursor_down.

Call graph: 3 external calls (assert!, assert_eq!, ta_with).

tests::home_end_and_emacs_style_home_end3241–3263 ↗
fn home_end_and_emacs_style_home_end()

Purpose: Checks beginning-of-line and end-of-line movement, including Emacs-style behavior that moves to the previous or next line when already at a line edge. This supports Ctrl-A and Ctrl-E style shortcuts.

Data flow: It positions the cursor on the second line, moves to line start, then previous line start, then line end, then next line end. Each resulting cursor index is asserted.

Call relations: The test runner calls it. It directly exercises move_cursor_to_beginning_of_line and move_cursor_to_end_of_line.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::end_of_line_or_down_at_end_of_text3266–3279 ↗
fn end_of_line_or_down_at_end_of_text()

Purpose: Checks that moving to line end with the option to move down is safe at the absolute end of text and works at a non-final line end. This avoids panics and off-by-one errors.

Data flow: It first places the cursor at text end and confirms it stays there, then places it at the first newline and confirms it moves to the end of the next line. Text is unchanged.

Call relations: The test runner calls it. It targets boundary behavior in move_cursor_to_end_of_line.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::word_navigation_helpers3282–3298 ↗
fn word_navigation_helpers()

Purpose: Checks helper methods that find the beginning of the previous word and the end of the next word. These helpers underpin word deletion and Vim word motions.

Data flow: It sets the cursor around alpha and beta in a spaced string, calls the helper methods, and compares returned indexes with expected word boundaries. No text is edited.

Call relations: The test runner calls it. It directly verifies word navigation helper methods.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::word_navigation_cjk_each_char_is_boundary3301–3316 ↗
fn word_navigation_cjk_each_char_is_boundary()

Purpose: Checks that each CJK character is treated as its own word boundary when moving backward. CJK here means Chinese, Japanese, and Korean characters, which often do not use spaces between words.

Data flow: It moves the cursor backward through a four-character Chinese string and checks the returned byte positions step by one full character each time. Text is unchanged.

Call relations: The test runner calls it. It verifies Unicode-aware backward word navigation.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::word_navigation_cjk_forward3319–3334 ↗
fn word_navigation_cjk_forward()

Purpose: Checks that forward word navigation steps through CJK characters one at a time. This avoids treating the whole CJK string as one large word.

Data flow: It sets the cursor before each Chinese character and checks end_of_next_word returns the next character boundary. The returned byte indexes are the outputs.

Call relations: The test runner calls it. It verifies Unicode-aware forward word navigation.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::word_navigation_mixed_ascii_cjk3337–3355 ↗
fn word_navigation_mixed_ascii_cjk()

Purpose: Checks word navigation in text that mixes ASCII letters and CJK characters. It should treat hello as one word and each CJK character as separate boundaries.

Data flow: It moves forward from the start, then backward from the end and middle, checking the returned boundary indexes. The TextArea content is not modified.

Call relations: The test runner calls it. It tests navigation helper behavior across script changes.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::word_navigation_preserves_separator_breaks_within_unicode_segments3358–3372 ↗
fn word_navigation_preserves_separator_breaks_within_unicode_segments()

Purpose: Checks that punctuation separators inside text still create word boundaries. Examples include apostrophes, decimal points, and dots in names.

Data flow: It sets the cursor around strings like can't, 32.3, and foo.bar, then asks for the previous word beginning. The expected indexes show separators are respected.

Call relations: The test runner calls it. It protects separator logic used by word movement and deletion.

Call graph: 2 external calls (assert_eq!, ta_with).

tests::wrapping_and_cursor_positions3375–3400 ↗
fn wrapping_and_cursor_positions()

Purpose: Checks that long text wraps into visual lines and that cursor position and scrolling account for that wrapping. Visual lines are screen rows after wrapping, not necessarily real newline-separated lines.

Data flow: It wraps hello world here in a narrow area, checks desired height, maps a cursor in world to the second visual row, then renders into a one-row viewport and confirms scrolling keeps the cursor visible. The buffer and state are changed by rendering.

Call relations: The test runner calls it. It uses render_ref, cursor_pos, cursor_pos_with_state, and TextAreaState together.

Call graph: 7 external calls (empty, new, assert!, assert_eq!, render_ref, default, ta_with).

tests::render_highlights_apply_style_without_mutating_text3403–3437 ↗
fn render_highlights_apply_style_without_mutating_text()

Purpose: Checks that render-only highlights change screen styling but do not alter the stored text. This is important for temporary search highlights.

Data flow: It renders hello world with a reversed style over world, then verifies the TextArea text is unchanged and the buffer cells for the highlighted range have the style. Non-highlighted cells remain unstyled.

Call relations: The test runner calls it. It exercises render_ref_styled_with_highlights, which eventually relies on render_lines.

Call graph: 7 external calls (empty, new, default, assert!, assert_eq!, default, ta_with).

tests::cursor_pos_with_state_basic_and_scroll_behaviors3440–3478 ↗
fn cursor_pos_with_state_basic_and_scroll_behaviors()

Purpose: Checks cursor coordinate calculation with scrolling in simple, below-window, and above-window cases. This keeps the on-screen cursor visible even when the saved scroll value is stale.

Data flow: It creates several wrapped or unwrapped TextAreas, passes different viewport rectangles and scroll states, and verifies the returned screen coordinates. Text is not edited.

Call relations: The test runner calls it. It directly verifies cursor_pos and cursor_pos_with_state behavior.

Call graph: 3 external calls (new, assert_eq!, ta_with).

tests::wrapped_navigation_across_visual_lines3481–3516 ↗
fn wrapped_navigation_across_visual_lines()

Purpose: Checks cursor movement across wrapped visual lines in text without real newlines. It ensures up and down use screen rows created by wrapping.

Data flow: It wraps abcdefghij at width 4, moves down and up, checks cursor indexes, and checks screen coordinates in full and one-row viewports. The cursor changes; text does not.

Call relations: The test runner calls it. It combines desired_height, movement methods, and cursor position mapping.

Call graph: 4 external calls (new, assert_eq!, default, ta_with).

tests::cursor_pos_with_state_after_movements3519–3560 ↗
fn cursor_pos_with_state_after_movements()

Purpose: Checks that rendering updates scroll state as the cursor moves through wrapped lines. The cursor should stay visible in a two-row viewport.

Data flow: It renders a wrapped string, moves the cursor down and up, renders after each move, and checks cursor screen coordinates with the current state. The render call updates the scroll state.

Call relations: The test runner calls it. It uses ratatui StatefulWidgetRef rendering plus cursor_pos_with_state.

Call graph: 6 external calls (empty, new, assert_eq!, render_ref, default, ta_with).

tests::wrapped_navigation_with_newlines_and_spaces3563–3585 ↗
fn wrapped_navigation_with_newlines_and_spaces()

Purpose: Checks wrapped vertical movement when text contains spaces and an explicit newline. It confirms visual-line movement crosses both wrapping boundaries and real line breaks sensibly.

Data flow: It wraps a string with word spacing and a newline, places the cursor on a wrapped line, moves up and down, and checks the resulting cursor positions. The final move lands somewhere in the next real line.

Call relations: The test runner calls it. It directly exercises move_cursor_up and move_cursor_down after wrapping calculations.

Call graph: 3 external calls (assert!, assert_eq!, ta_with).

tests::wrapped_navigation_with_wide_graphemes3588–3605 ↗
fn wrapped_navigation_with_wide_graphemes()

Purpose: Checks wrapped movement with wide graphemes such as emoji, which take more than one screen column. Movement should not split or misplace those visible characters.

Data flow: It wraps four emoji in a narrow width, places the cursor after the second emoji, moves down, verifies the cursor moved forward safely, then moves up and checks it returns to the original boundary.

Call relations: The test runner calls it. It verifies wrapping and vertical movement are grapheme-aware.

Call graph: 3 external calls (assert!, assert_eq!, ta_with).

tests::fuzz_textarea_randomized3608–3836 ↗
fn fuzz_textarea_randomized()

Purpose: This is a randomized stress test for the terminal text area. It tries many combinations of edits, cursor moves, protected inline elements, resizing, wrapping, scrolling, and rendering to catch bugs that ordinary hand-written tests might miss.

Data flow: The test starts by choosing a deterministic random seed based on the current Pacific-day date, then creates many fresh text areas. For each one, it builds random starting text, places the cursor at a valid character boundary, and repeatedly performs random operations such as inserting text, deleting text, replacing ranges, moving the cursor, inserting protected element text, and changing the visible width or height. After each operation, it reads the text, cursor, element payloads, render size, and scroll state, then checks that the text area is still internally consistent. Nothing is returned; the output is success if all assertions hold, or a test failure if any invariant is broken.

Call relations: The Rust test runner calls this function when the test suite runs. Inside the test, it creates new TextArea and TextAreaState values, drives the public editing methods as if a user were interacting with the widget, and asks Ratatui, the terminal UI rendering library, to draw the widget into buffers. The assertions are the final gate: if editing or rendering leaves the widget in an invalid state, the test stops and reports the problem.

Call graph: calls 1 internal fn (new); 15 external calls (empty, new, new, new, assert!, assert_eq!, hours, now, format!, seed_from_u64 (+5 more)).

tui/src/bottom_pane/chat_composer/draft_state.rssource ↗
data_modelchat composing

The chat composer is the place where a user types a message before sending it. This file is like the composer’s desk drawer: it stores everything the composer needs to remember while the user is editing, without mixing that storage with the higher-level control flow.

The main type, DraftState, holds the text area itself, the text area’s editing state, whether the composer is in bash-style command mode, and any pasted text that still needs to be processed. It also records whether typing is enabled, and what placeholder message to show if input is disabled.

Pasting gets special attention. The paste_burst field helps detect a burst of pasted input, so the interface can treat a paste differently from normal typing. There is also a switch to disable that behavior when needed.

The file also tracks mention bindings. A mention binding connects something the user typed, such as a referenced file or item, to the underlying path or stored mention value. ComposerMentionBinding is the small record used for these links. Recently submitted mention bindings are kept separately so they can still be used after a message is sent.

Without this file, the composer would have no single, clear place to keep its draft text and nearby editing details.

Function details1
DraftState::new25–38 ↗
fn new() -> Self

Purpose: Creates a fresh, empty draft for the chat composer. It is used when the composer needs a clean starting point with typing enabled, no pending pastes, no mentions, and default text editing state.

Data flow: It takes no input. It creates a new empty text area, a default text editing state, empty lists and maps for paste and mention tracking, and default paste-burst tracking. It returns a ready-to-use DraftState with input enabled and bash mode turned off.

Call relations: When the composer is built through new_with_config, that setup code calls DraftState::new to get the base editable state. Inside this constructor, it delegates to the text area and paste tracking types to create their own empty or default starting values, then gathers them into one draft object.

Call graph: calls 1 internal fn (new); called by 1 (new_with_config); 5 external calls (new, new, new, default, default).

tui/src/bottom_pane/chat_composer/popup_state.rssource ↗
data_modelchat composer input handling

The chat composer can show several helper popups while a user types, such as a command picker, file search, skill picker, or mention suggestions. This file is the small shared record that says which one is currently visible. Think of it like a single “now showing” sign outside a room: the room may contain the command popup, the file popup, the skill popup, the mention popup, or nothing at all.

The central type is PopupState. It stores the active popup in active, and also remembers a few dismissal details. For example, if a user dismisses a file-search suggestion for a certain token, the composer can remember that token and avoid reopening the same suggestion right away. It also tracks the current file query, so the file popup can stay synchronized with what the user is typing.

ActivePopup is an enum, meaning a value that can be exactly one of several named choices. Here that matters because the interface should not show overlapping helper popups. The default state is None, so a new composer starts with no popup open. The one behavior in this file, PopupState::active, answers the simple question: “is any popup currently visible?”

Function details1
PopupState::active18–20 ↗
fn active(&self) -> bool

Purpose: This function answers whether the chat composer currently has a popup open. It is used when other parts of the interface need a simple yes-or-no answer instead of caring which specific popup is visible.

Data flow: It reads the active field inside PopupState. If that field is ActivePopup::None, it returns false; for command, file search, skill, or mention popups, it returns true. It does not change any state.

Call relations: When code such as popup_active or set_input_enabled needs to decide how the composer should behave, it asks this function whether a popup is present. Internally, the function uses Rust’s pattern-checking helper to distinguish the empty state from every real popup state.

Call graph: called by 2 (popup_active, set_input_enabled); 1 external calls (matches!).

tui/src/bottom_pane/chat_composer/attachment_state.rssource ↗
domain_logicchat composer editing and submission

The chat composer lets a user send text along with images. Some images are local files, which appear inside the typed message as placeholder labels such as an image tag. Other images are remote URLs, shown as separate rows above or near the composer. This file is the small bookkeeping system that keeps those two kinds of image attachments consistent.

The important job is numbering. Remote images come first, and local images are numbered after them. If a remote image is added, removed, or taken away for submission, the local image placeholders may need to be renamed so the labels still match the real attachment order. Think of it like numbered coat-check tickets: if ticket number 1 disappears, the later tickets may need new numbers so the list has no confusing gaps.

The file also tracks keyboard selection for remote-image rows. The user can move up into the remote-image list, move down through it, and delete the selected remote image with Backspace or Delete. When that happens, this state updates the selected row and then relabels local image placeholders inside the text area.

Before a message is submitted, the file can remove local images whose placeholders were deleted from the text. It can also hand off attachments to the sending code and clear its own state afterward.

Function details18
AttachmentState::is_empty34–36 ↗
fn is_empty(&self) -> bool

Purpose: Checks whether the composer currently has no image attachments at all. This is useful when the UI or submit logic needs to know whether there is anything image-related to show or send.

Data flow: It reads the stored local image list and remote image URL list. If both lists are empty, it returns true; otherwise it returns false. It does not change anything.

Call relations: Other parts of the chat composer can call this as a quick yes-or-no check before deciding whether to draw attachment UI or include attachments in a message.

AttachmentState::local_image_paths38–43 ↗
fn local_image_paths(&self) -> Vec<PathBuf>

Purpose: Returns just the file paths for the local images currently attached. This is for callers that only need to know where the image files are on disk, not their placeholder labels.

Data flow: It reads each saved local image entry, copies its path, and returns a new list of paths. The attachment state itself stays unchanged.

Call relations: This acts as a read-only view into the local attachment list for code that needs local file locations without taking ownership of the attachments.

AttachmentState::local_images45–53 ↗
fn local_images(&self) -> Vec<LocalImageAttachment>

Purpose: Returns local image attachments with both their placeholder label and file path. This is used when the caller needs to preserve the connection between text placeholders and actual image files.

Data flow: It reads the internal local image records, copies each placeholder and path into a public attachment shape, and returns that new list. Nothing is removed or modified.

Call relations: This gives other composer or submission code a safe snapshot of the local image attachments while leaving the state ready for continued editing.

AttachmentState::set_remote_image_urls55–59 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>, textarea: &mut TextArea)

Purpose: Replaces the current remote image URL list with a new one. Because remote images are numbered before local images, it also updates local placeholder labels afterward.

Data flow: It receives a new list of URLs and the text area. It stores the new URLs, clears any selected remote-image row, then asks AttachmentState::relabel_local_images to rename local placeholders in the text area if their numbers changed. It returns nothing.

Call relations: When some outside flow refreshes the remote images, this function is the entry point for putting that list into attachment state. It immediately hands off to AttachmentState::relabel_local_images so the visible text stays consistent.

Call graph: calls 1 internal fn (relabel_local_images).

AttachmentState::remote_image_urls61–63 ↗
fn remote_image_urls(&self) -> Vec<String>

Purpose: Returns a copy of the current remote image URLs. This is for callers that need to inspect or send the remote images without changing the composer state.

Data flow: It reads the stored URL list, clones it into a new list, and returns that copy. The original list and selected row are unchanged.

Call relations: Other parts of the UI or submission path can call this as a read-only snapshot of remote image attachments.

AttachmentState::take_remote_image_urls65–70 ↗
fn take_remote_image_urls(&mut self, textarea: &mut TextArea) -> Vec<String>

Purpose: Removes all remote image URLs from the attachment state and gives them to the caller. This is useful when a message is being submitted and the attachments should move out of the composer.

Data flow: It receives the text area, empties the stored URL list while keeping the old URLs to return, clears the remote selection, and relabels local image placeholders because remote images no longer take the first numbers. The output is the old list of URLs.

Call relations: During submission or transfer, this function hands remote images out of the composer. It uses AttachmentState::relabel_local_images afterward so any remaining local image labels in the text area are renumbered correctly.

Call graph: calls 1 internal fn (relabel_local_images); 1 external calls (take).

AttachmentState::clear_remote_image_urls72–75 ↗
fn clear_remote_image_urls(&mut self)

Purpose: Deletes all remote image URLs from the state and clears any selected remote row. This is a simple reset for remote attachments.

Data flow: It empties the remote URL list and sets the selected remote image index to none. It does not return anything and does not touch local image placeholders.

Call relations: This is used when remote images should be discarded without needing to return them to another part of the program.

AttachmentState::reset_local_images77–94 ↗
fn reset_local_images(
        &mut self,
        local_image_paths: Vec<PathBuf>,
        textarea: &mut TextArea,
    )

Purpose: Replaces the local image list with a new set of file paths. It assigns each image a placeholder label based on how many remote images already exist.

Data flow: It receives new local image paths and the text area. It clears the old local images, creates a new entry for each path with the correct numbered placeholder, clears any remote selection, then calls AttachmentState::relabel_local_images to make the text area match the state. It returns nothing.

Call relations: When local attachments are restored or replaced as a group, this function rebuilds the local side of the attachment state. It finishes by using AttachmentState::relabel_local_images, which keeps the text box placeholders aligned with the rebuilt list.

Call graph: calls 1 internal fn (relabel_local_images).

AttachmentState::attach_image96–101 ↗
fn attach_image(&mut self, textarea: &mut TextArea, path: PathBuf)

Purpose: Adds one local image file to the current message. It also inserts that image’s placeholder label into the text area at the current editing position.

Data flow: It receives the text area and an image path. It calculates the next image number after existing remote and local images, creates the matching placeholder text, inserts that placeholder into the text area, and stores the placeholder plus file path in the local image list. It returns nothing.

Call relations: This is called when the user attaches a new local image while composing. It relies on local_image_label_text to create the visible label and on the text area’s insert_element behavior to place that label into the message.

Call graph: calls 2 internal fn (local_image_label_text, insert_element).

AttachmentState::prune_local_images_for_submission103–118 ↗
fn prune_local_images_for_submission(
        &mut self,
        text: &str,
        text_elements: &[TextElement],
    )

Purpose: Removes local images whose placeholder labels are no longer present in the message being submitted. This prevents sending an image that the user deleted from the text.

Data flow: It receives the final text and its parsed text elements. It gathers the placeholders still present in those text elements, then keeps only local images whose saved placeholder appears in that set. It changes the local image list in place and returns nothing.

Call relations: This runs just before or during submission, after the text has been broken into elements. It uses those parsed elements as the source of truth for which local image placeholders survived in the message.

Call graph: 1 external calls (iter).

AttachmentState::take_recent_submission_images121–126 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

Purpose: In tests, removes all local images from the state and returns only their file paths. This gives test code a simple way to check what images would have been submitted.

Data flow: It empties the local image list, converts each removed image record into its path, and returns the list of paths. Afterward, the state no longer contains those local images.

Call relations: This function is only compiled for tests. Test code can call it after a simulated submission to inspect and clear the local attachments in one step.

Call graph: 1 external calls (take).

AttachmentState::take_recent_submission_images_with_placeholders128–138 ↗
fn take_recent_submission_images_with_placeholders(
        &mut self,
    ) -> Vec<LocalImageAttachment>

Purpose: Removes all local images from the state and returns them with both placeholder labels and file paths. This is used when the submission path needs the exact link between the typed placeholder and the image file.

Data flow: It empties the local image list, converts each removed internal record into a LocalImageAttachment, and returns that list. The local image state is cleared as a side effect.

Call relations: This is part of the handoff from editing to sending. Instead of merely copying local attachments, it transfers them out of the composer so they are not submitted again later.

Call graph: 1 external calls (take).

AttachmentState::remote_image_lines140–153 ↗
fn remote_image_lines(&self) -> Vec<Line<'static>>

Purpose: Builds the display rows for remote image attachments. It marks the selected row differently so the user can see which remote image keyboard actions will affect.

Data flow: It reads the remote URL list and selected index. For each remote image, it creates a numbered label line; selected rows are styled with a highlighted reversed look, while unselected rows are colored normally. It returns the list of lines for the terminal UI to draw.

Call relations: The drawing code can call this when rendering the composer. It turns internal state into ready-to-display terminal text, without changing the attachment state.

AttachmentState::clear_remote_image_selection155–157 ↗
fn clear_remote_image_selection(&mut self)

Purpose: Clears the current keyboard selection among remote image rows. After this, no remote image is selected for deletion or navigation.

Data flow: It sets the selected remote image index to none. It does not read or change the URL list and returns nothing.

Call relations: AttachmentState::handle_remote_image_selection_key calls this when moving down past the last selected remote row. AttachmentState::remove_selected_remote_image also calls it when a stale or invalid selection must be dropped.

Call graph: called by 2 (handle_remote_image_selection_key, remove_selected_remote_image).

AttachmentState::handle_remote_image_selection_key159–205 ↗
fn handle_remote_image_selection_key(
        &mut self,
        key_event: &KeyEvent,
        textarea: &mut TextArea,
    ) -> Option<(InputResult, bool)>

Purpose: Interprets keyboard presses for moving through or deleting remote image rows. It lets the user use Up, Down, Backspace, and Delete in a predictable way while composing.

Data flow: It receives a key event and the text area. If there are no remote images, if modifier keys are held, or if the event is not a real key press, it does nothing and returns none. For Up, it either moves the selection upward or, when the text cursor is at the start, enters the remote-image list from the composer. For Down, it moves the selection downward or clears it after the last row. For Delete or Backspace, it removes the selected remote image. When it handles a key, it returns an input result paired with a flag saying the key was consumed.

Call relations: This function sits in the keyboard-input path for the chat composer. It uses the text area’s cursor position to decide when Up should move from text into the remote-image rows, calls AttachmentState::clear_remote_image_selection when leaving the list, and calls AttachmentState::remove_selected_remote_image when the user deletes a selected remote image.

Call graph: calls 3 internal fn (clear_remote_image_selection, remove_selected_remote_image, cursor).

AttachmentState::remove_deleted_local_placeholders207–223 ↗
fn remove_deleted_local_placeholders(
        &mut self,
        removed_payloads: &[String],
        textarea: &mut TextArea,
    ) -> bool

Purpose: Removes local image attachments whose placeholder elements were deleted from the text area. This keeps the hidden attachment list from keeping images the user visibly removed.

Data flow: It receives a list of removed placeholder payloads and the text area. It filters the local image list to drop any image whose placeholder appears in that removed list. If anything was removed, it relabels the remaining local images in the text area. It returns true if at least one attachment was removed, otherwise false.

Call relations: This is called after the text area reports that some embedded elements were deleted. If that deletion affected image placeholders, it hands off to AttachmentState::relabel_local_images so the remaining image labels stay correctly numbered.

Call graph: calls 1 internal fn (relabel_local_images).

AttachmentState::relabel_local_images225–235 ↗
fn relabel_local_images(&mut self, textarea: &mut TextArea)

Purpose: Renames local image placeholders so their numbers match the current attachment order. This is the central cleanup step used whenever remote or local attachment counts change.

Data flow: It receives the text area and walks through the local image list. For each image, it calculates the placeholder it should have after counting all remote images first. If the saved placeholder is outdated, it replaces the saved value and asks the text area to replace the old placeholder payload with the new one. It returns nothing.

Call relations: Several state-changing functions call this after they alter remote images or local images: AttachmentState::set_remote_image_urls, AttachmentState::take_remote_image_urls, AttachmentState::reset_local_images, AttachmentState::remove_deleted_local_placeholders, and AttachmentState::remove_selected_remote_image. It is the shared step that keeps the internal records and visible text synchronized.

Call graph: calls 2 internal fn (local_image_label_text, replace_element_payload); called by 5 (remove_deleted_local_placeholders, remove_selected_remote_image, reset_local_images, set_remote_image_urls, take_remote_image_urls); 1 external calls (replace).

AttachmentState::remove_selected_remote_image237–250 ↗
fn remove_selected_remote_image(&mut self, selected_index: usize, textarea: &mut TextArea)

Purpose: Deletes one remote image by its selected index and updates the selection afterward. It also relabels local image placeholders because removing a remote image changes the numbering of later images.

Data flow: It receives the selected index and the text area. If the index is outside the URL list, it clears the selection and stops. Otherwise, it removes that URL, moves the selection to a valid remaining row or clears it if no remote images remain, then calls AttachmentState::relabel_local_images. It returns nothing.

Call relations: AttachmentState::handle_remote_image_selection_key calls this when the user presses Delete or Backspace on a selected remote image. It may call AttachmentState::clear_remote_image_selection for invalid or empty selections, and it uses AttachmentState::relabel_local_images to keep local placeholders correct after the remote row is gone.

Call graph: calls 2 internal fn (clear_remote_image_selection, relabel_local_images); called by 1 (handle_remote_image_selection_key).

tui/src/bottom_pane/chat_composer_history.rssource ↗
domain_logicchat input handling

This module is the chat composer’s memory. Without it, the user would have to retype earlier prompts, and recalled prompts could lose important draft details such as image placeholders, pasted content markers, or tool/app mentions. It treats history like one long list: older saved entries come first, then entries typed during the current UI session. Current-session entries are already in memory and can be restored fully. Saved entries are fetched only when needed, so the UI can ask for one old entry and keep going when the answer arrives.

The main type, ChatComposerHistory, is a small state machine. For normal Up/Down browsing, it keeps a cursor into the combined history list. If the requested entry is local or already cached, it returns it right away. If it lives in persistent history and has not been loaded yet, it sends a lookup event and waits for on_entry_response to finish the move.

Ctrl+R search has separate state because search behaves differently from simple browsing. It remembers the current query, avoids showing the same prompt text twice, waits safely for asynchronous saved-history lookups, and reports when the user has reached the oldest or newest matching entry. This separation keeps ordinary multiline cursor movement from being confused with history recall.

Function details47
HistoryEntry::new49–51 ↗
fn new(text: String) -> Self

Purpose: Creates a simple history entry from stored text. It is mainly for persistent history, where only text is available and attachment details are not saved.

Data flow: It receives raw text, assumes at-mention restoration is allowed, and passes that text into the fuller constructor. It returns a HistoryEntry whose text and mention bindings have been decoded, with attachment and paste fields empty.

Call relations: Other history code and tests use this as the quick way to make a text-only entry. It delegates the real work to HistoryEntry::new_with_at_mentions so mention decoding is consistent.

Call graph: 1 external calls (new_with_at_mentions).

HistoryEntry::new_with_at_mentions53–71 ↗
fn new_with_at_mentions(text: String, at_mentions_enabled: bool) -> Self

Purpose: Builds a text-only history entry while deciding whether old saved mention syntax should restore @ mentions. This matters because persistent history may contain encoded references to tools or apps.

Data flow: It takes stored text and a true/false setting for @ mentions, decodes any saved mention links, then fills a HistoryEntry with decoded text and mention bindings. It leaves image, remote URL, text element, and paste fields empty because persistent text does not carry those payloads.

Call relations: HistoryEntry::new calls this with mention restoration enabled. ChatComposerHistory::on_entry_response calls it when an older saved entry arrives from storage, using the current mention-restoration setting.

Call graph: calls 1 internal fn (decode_history_mentions_with_at_mentions); 1 external calls (new).

HistoryEntry::with_pending74–88 ↗
fn with_pending(
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        pending_pastes: Vec<(String, String)>,
    ) -> Self

Purpose: Creates a test-only history entry that includes local draft metadata such as text elements, image paths, and pending pasted content. It helps tests check that rich draft state can survive history recall.

Data flow: It receives text plus draft metadata and returns a HistoryEntry containing those values. Remote image URLs and mention bindings are left empty.

Call relations: This helper is compiled only for tests. It is separate from HistoryEntry::new because real local submissions should preserve more than plain text.

Call graph: 1 external calls (new).

HistoryEntry::with_pending_and_remote91–106 ↗
fn with_pending_and_remote(
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        pending_pastes: Vec<(String, String)>,
        remote_image_

Purpose: Creates a test-only history entry with both local metadata and remote image URLs. It lets tests build entries that look like richer composer drafts.

Data flow: It receives text, text elements, local image paths, pending pasted content, and remote image URLs. It returns a HistoryEntry carrying those values, with mention bindings empty.

Call relations: This helper is available only in tests. It supports test cases that need to verify full draft restoration rather than simple text recall.

Call graph: 1 external calls (new).

ChatComposerHistory::new229–243 ↗
fn new() -> Self

Purpose: Creates a fresh, empty history state for a chat composer. At this point it knows nothing about saved history, but it can still record prompts typed during the current session.

Data flow: It takes no input and returns a ChatComposerHistory with no thread metadata, no saved-entry count, empty caches, no active cursor, and no active search.

Call relations: Composer setup code and many tests call this first. Later, set_metadata can attach it to a persistent history log, and record_local_submission can add new in-session entries.

Call graph: called by 12 (new_with_config, duplicate_submissions_are_not_recorded, navigation_with_async_fetch, persistent_restore_gates_at_mentions, repeated_boundary_search_does_not_refetch_persistent_history, reset_navigation_resets_cursor, search_fetches_persistent_history_until_match, search_is_case_insensitive_and_empty_query_finds_latest, search_matches_local_history_and_stops_at_boundaries, search_skips_duplicate_local_matches (+2 more)); 2 external calls (new, new).

ChatComposerHistory::set_at_mention_restore_enabled245–254 ↗
fn set_at_mention_restore_enabled(&mut self, enabled: bool)

Purpose: Turns restoration of saved @ mentions on or off. Changing this setting clears cached saved entries because the same stored text may decode differently under the new setting.

Data flow: It receives a boolean setting. If the setting changed, it stores the new value and clears fetched entries, browsing position, last recalled text, and active search.

Call relations: The composer configuration path calls this through set_mentions_v2_enabled. It affects later persistent-history responses handled by on_entry_response.

Call graph: called by 1 (set_mentions_v2_enabled).

ChatComposerHistory::set_metadata261–272 ↗
fn set_metadata(&mut self, thread_id: ThreadId, log_id: u64, entry_count: usize)

Purpose: Connects this history state to a particular saved history log for a chat thread. This gives the module enough information to request older entries safely.

Data flow: It receives a thread id, a log id, and the number of persistent entries known at session start. It stores those values and clears local history, replay-seeded history, fetched cache, cursors, and search state.

Call relations: Higher-level session code calls this through set_history_metadata. It resets offsets so old asynchronous lookup replies cannot be mistaken for entries from a new log.

Call graph: called by 1 (set_history_metadata).

ChatComposerHistory::record_local_submission278–280 ↗
fn record_local_submission(&mut self, entry: HistoryEntry)

Purpose: Adds a prompt submitted during the current UI session to recall history. This preserves full draft information, unlike older saved history that may only have text.

Data flow: It receives a HistoryEntry and passes it to the shared recording helper. If accepted, the entry is appended to local history and navigation/search state is reset.

Call relations: Submission paths call this after the user sends text or records slash-command history. It relies on record_local_submission_inner to reject empty or duplicate entries.

Call graph: calls 1 internal fn (record_local_submission_inner); called by 3 (clear_for_ctrl_c, prepare_submission_text_with_options, record_pending_slash_command_history).

ChatComposerHistory::record_replayed_submission282–286 ↗
fn record_replayed_submission(&mut self, entry: HistoryEntry)

Purpose: Records a user message replayed from a resumed transcript, while also remembering that it came from replay. This helps avoid showing the same prompt twice when saved history also contains it.

Data flow: It receives a HistoryEntry, tries to add it as local history, and if that succeeds also stores it in replay_seeded_history. The input entry is cloned so both lists can keep their own copy.

Call relations: Transcript replay code calls this through record_replayed_user_message_history. Later, persistent_entry_duplicates_local uses the replay list to skip duplicate saved entries.

Call graph: calls 1 internal fn (record_local_submission_inner); called by 1 (record_replayed_user_message_history); 1 external calls (clone).

ChatComposerHistory::record_local_submission_inner288–310 ↗
fn record_local_submission_inner(&mut self, entry: HistoryEntry) -> bool

Purpose: Performs the actual rules for adding a local history entry. It ignores completely empty drafts and collapses identical consecutive submissions.

Data flow: It receives a HistoryEntry. If every part of the entry is empty, it returns false; otherwise it clears browsing/search state, checks the previous local entry for equality, and appends only if it is not a duplicate. It returns true when something was added.

Call relations: Both record_local_submission and record_replayed_submission use this helper so normal and replayed submissions follow the same rules.

Call graph: called by 2 (record_local_submission, record_replayed_submission).

ChatComposerHistory::reset_navigation317–322 ↗
fn reset_navigation(&mut self)

Purpose: Stops normal Up/Down history browsing and returns the composer to a neutral history state. The next Up press will start again from the newest entry.

Data flow: It takes no new data. It clears the history cursor, any pending navigation direction, the last recalled text, and active search state.

Call relations: Code that clears or cancels composer input calls this, such as clear_for_ctrl_c. It deliberately clears search too, because browsing and Ctrl+R search use different cursor rules.

Call graph: called by 1 (clear_for_ctrl_c).

ChatComposerHistory::should_handle_navigation343–361 ↗
fn should_handle_navigation(&self, text: &str, cursor: usize) -> bool

Purpose: Decides whether an Up or Down key should recall history or behave like normal cursor movement. This protects multiline editing, where Up/Down often means moving within the text.

Data flow: It reads the current composer text, cursor position, available history, and last recalled text. It returns true for empty input when history exists, or for text that exactly matches the last recalled entry with the cursor at the beginning or end.

Call relations: Keyboard and popup synchronization code ask this before calling navigate_up or navigate_down. It is the gate that prevents history recall from stealing normal editor movement.

Call graph: called by 2 (handle_key_event_without_popup, sync_popups); 1 external calls (matches!).

ChatComposerHistory::navigate_up368–387 ↗
fn navigate_up(&mut self, app_event_tx: &AppEventSender) -> Option<HistoryEntry>

Purpose: Moves one step toward older history, like pressing Up in a shell. It may return an entry immediately or request a saved entry asynchronously.

Data flow: It reads the current cursor and total history size, chooses the next older offset, stores that cursor, and asks populate_history_at_index to fetch or return the entry. It returns Some(entry) when the composer can be updated now, or None when there is nothing to show yet.

Call relations: Keyboard handling calls this after should_handle_navigation allows an Up press. It exits Ctrl+R search mode and hands the actual lookup details to populate_history_at_index.

Call graph: calls 1 internal fn (populate_history_at_index); called by 1 (handle_key_event_without_popup).

ChatComposerHistory::navigate_down394–424 ↗
fn navigate_down(&mut self, app_event_tx: &AppEventSender) -> Option<HistoryEntry>

Purpose: Moves one step toward newer history, or clears the composer when the user moves past the newest entry. This mirrors shell-style Down behavior.

Data flow: It reads the current cursor and total history size. If there is a newer entry, it moves there and asks populate_history_at_index for it; if the cursor moves past the newest entry, it clears browsing state and returns an empty HistoryEntry.

Call relations: Keyboard handling calls this for Down presses. Like navigate_up, it leaves Ctrl+R search mode and uses populate_history_at_index for cached or asynchronous entries.

Call graph: calls 1 internal fn (populate_history_at_index); called by 1 (handle_key_event_without_popup); 2 external calls (new, new).

ChatComposerHistory::on_entry_response433–502 ↗
fn on_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
        app_event_tx: &AppEventSender,
    ) -> HistoryEntryResponse

Purpose: Receives the answer to an asynchronous persistent-history lookup. It decides whether that answer completes normal browsing, continues a Ctrl+R search, or should be ignored.

Data flow: It receives a log id, an offset, optional stored text, and an event sender. It rejects stale log ids, decodes returned text into a HistoryEntry, caches it, then checks whether a search or normal navigation was waiting for that offset. It returns a HistoryEntryResponse describing the result.

Call relations: The app-level history response handler calls this through on_history_entry_response. It may call search helpers to continue scanning, or populate_history_at_index to skip duplicate replayed entries during normal navigation.

Call graph: calls 7 internal fn (advance_search_after, next_history_offset, persistent_entry_duplicates_local, populate_history_at_index, search_match, search_matches, search_result_is_unique); called by 1 (on_history_entry_response); 2 external calls (Found, Search).

ChatComposerHistory::total_entries586–588 ↗
fn total_entries(&self) -> usize

Purpose: Counts how many entries are in the combined history list. This is the persistent entry count plus current-session entries.

Data flow: It reads persistent_entry_count and local_history length, adds them, and returns the total.

Call relations: Search helpers call this when deciding valid offsets. It keeps offset math in one obvious place.

Call graph: called by 2 (advance_search_from, search).

ChatComposerHistory::search_start_offset590–618 ↗
fn search_start_offset(
        &self,
        total_entries: usize,
        direction: HistorySearchDirection,
        restart: bool,
    ) -> Option<usize>

Purpose: Chooses where a search scan should begin. The answer depends on whether the search is restarting and whether the user wants older or newer entries.

Data flow: It receives the total entry count, direction, and restart flag. It reads the currently selected search offset, then returns the newest, oldest, previous, or next valid offset as appropriate, or None if there is no place to scan.

Call relations: ChatComposerHistory::search uses this just before scanning. It turns user intent into a concrete history offset.

Call graph: called by 1 (search).

ChatComposerHistory::advance_search_after620–643 ↗
fn advance_search_after(
        &mut self,
        offset: usize,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
        app_event_tx: &AppEventSender,
    ) -> Histo

Purpose: Continues a pending search after a saved-history response has been processed and did not itself produce a match. It resumes scanning at the next offset.

Data flow: It receives the offset just handled, the direction, boundary behavior, and an event sender. It computes the next offset, calls advance_search_from, and converts a plain miss into the correct boundary result when needed.

Call relations: on_entry_response calls this when an asynchronous persistent entry was not a usable search match. It keeps Ctrl+R search moving across gaps in the cache.

Call graph: calls 2 internal fn (advance_search_from, exhausted_search_result); called by 1 (on_entry_response); 1 external calls (matches!).

ChatComposerHistory::advance_search_from645–689 ↗
fn advance_search_from(
        &mut self,
        mut offset: usize,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
        app_event_tx: &AppEventSender,
    ) -> Hi

Purpose: Scans history entries from a given offset until it finds a matching unique prompt, needs to fetch a saved entry, or runs out. This is the core search loop.

Data flow: It receives a starting offset, direction, boundary behavior, and event sender. For each offset it tries cached/local data; if an entry matches the query and has not already been shown, it records and returns it. If it reaches an unfetched persistent entry, it sends a lookup event and returns Pending.

Call relations: ChatComposerHistory::search uses this for fresh scans, and advance_search_after uses it to resume after asynchronous replies. It depends on entry_at_cached_offset, search_matches, search_result_is_unique, and search_match.

Call graph: calls 6 internal fn (send, entry_at_cached_offset, search_match, search_matches, search_result_is_unique, total_entries); called by 2 (advance_search_after, search).

ChatComposerHistory::entry_at_cached_offset691–699 ↗
fn entry_at_cached_offset(&self, offset: usize) -> Option<HistoryEntry>

Purpose: Looks up a combined-history offset only if the entry is already available in memory. It does not start any disk or background fetch by itself.

Data flow: It receives an offset. If the offset points into local history, it clones and returns that local entry; if it points into persistent history, it returns the cached entry for that offset if present. Otherwise it returns None.

Call relations: Search and navigation helpers call this before deciding whether they must send a persistent-history lookup event.

Call graph: called by 2 (advance_search_from, populate_history_at_index).

ChatComposerHistory::search_matches701–706 ↗
fn search_matches(&self, entry: &HistoryEntry) -> bool

Purpose: Checks whether one history entry matches the current Ctrl+R query. The match is case-insensitive, and an empty query matches everything.

Data flow: It reads the active search state and receives a HistoryEntry. If no search is active it returns false; otherwise it lowercases the entry text and checks whether it contains the lowercased query.

Call relations: advance_search_from and on_entry_response use this before accepting an entry as a search result.

Call graph: called by 2 (advance_search_from, on_entry_response).

ChatComposerHistory::search_result_is_unique708–712 ↗
fn search_result_is_unique(&self, entry: &HistoryEntry) -> bool

Purpose: Prevents Ctrl+R search from showing the same prompt text repeatedly in one search session. It compares exact prompt text, not storage identity.

Data flow: It receives a HistoryEntry and reads the active search state’s seen_texts set. It returns true if there is no active search or if this exact text has not already been shown.

Call relations: advance_search_from and on_entry_response call this after a text match is found. If it returns true, search_match can record and display the entry.

Call graph: called by 2 (advance_search_from, on_entry_response).

ChatComposerHistory::search_match714–725 ↗
fn search_match(&mut self, offset: usize, entry: HistoryEntry) -> HistorySearchResult

Purpose: Records a found search result as the current selected history item and returns it to the caller. This keeps the visible composer text and search state in sync.

Data flow: It receives an offset and HistoryEntry. It updates the normal history cursor, remembers the entry text as the last recalled text, records the match in the active search state, clears any pending search fetch, and returns Found(entry).

Call relations: advance_search_from calls this for cached/local hits, and on_entry_response calls it when an asynchronous persistent response is a hit.

Call graph: called by 2 (advance_search_from, on_entry_response); 1 external calls (Found).

ChatComposerHistory::select_cached_unique_match727–750 ↗
fn select_cached_unique_match(
        &mut self,
        direction: HistorySearchDirection,
    ) -> Option<HistorySearchResult>

Purpose: Moves among search matches that have already been discovered, without rescanning history. This makes repeated older/newer search keys fast and stable.

Data flow: It receives a direction, reads the current selected match index, and chooses the next cached unique match in that direction. It updates the history cursor, last recalled text, and selected search index, then returns the cached entry as Found.

Call relations: ChatComposerHistory::search tries this before scanning for more entries. If there is no cached match in that direction, search continues into advance_search_from.

Call graph: called by 1 (search); 1 external calls (Found).

ChatComposerHistory::exhausted_search_result752–769 ↗
fn exhausted_search_result(
        &mut self,
        direction: HistorySearchDirection,
        boundary_if_exhausted: bool,
    ) -> HistorySearchResult

Purpose: Finishes a search scan when no more matching entries exist in the requested direction. It distinguishes a first miss from hitting a boundary after a valid selection.

Data flow: It receives a direction and a boundary flag. It clears any pending search fetch, optionally marks that direction as exhausted, and returns AtBoundary when the current selection should stay valid or NotFound when there was no match.

Call relations: ChatComposerHistory::search and advance_search_after use this whenever scanning reaches the end. It prevents repeated boundary key presses from refetching the same persistent history.

Call graph: called by 2 (advance_search_after, search).

ChatComposerHistory::populate_history_at_index771–810 ↗
fn populate_history_at_index(
        &mut self,
        global_idx: usize,
        direction: HistorySearchDirection,
        app_event_tx: &AppEventSender,
    ) -> Option<HistoryEntry>

Purpose: Turns a history offset into an entry for normal Up/Down browsing. If the entry is missing from persistent history, it sends a lookup request and tells the caller to wait.

Data flow: It receives a combined-history index, direction, and event sender. It loops through cached entries, skipping persistent entries that duplicate replayed local messages; if it finds a usable entry it records it and returns it. If the target saved entry is not cached, it sends a LookupMessageHistoryEntry event and returns None.

Call relations: navigate_up, navigate_down, and on_entry_response call this during normal browsing. It uses next_history_offset and persistent_entry_duplicates_local to avoid replay duplicates.

Call graph: calls 4 internal fn (send, entry_at_cached_offset, next_history_offset, persistent_entry_duplicates_local); called by 3 (navigate_down, navigate_up, on_entry_response).

ChatComposerHistory::next_history_offset812–823 ↗
fn next_history_offset(
        &self,
        offset: usize,
        direction: HistorySearchDirection,
    ) -> Option<usize>

Purpose: Computes the neighboring history offset in the requested direction. It safely returns nothing when the move would go outside the list.

Data flow: It receives the current offset and direction. For Older it subtracts one if possible; for Newer it adds one only if the result is still inside total history.

Call relations: populate_history_at_index uses this while skipping duplicates, and on_entry_response uses it when a fetched persistent entry duplicates replayed local history.

Call graph: called by 2 (on_entry_response, populate_history_at_index).

ChatComposerHistory::persistent_entry_duplicates_local825–829 ↗
fn persistent_entry_duplicates_local(&self, entry: &HistoryEntry) -> bool

Purpose: Checks whether a saved-history entry is the same as one replayed into local history when resuming a transcript. This avoids showing the same recalled prompt twice.

Data flow: It receives a HistoryEntry and compares its text and mention bindings with replay-seeded local entries. It returns true if a matching replayed entry exists.

Call relations: Normal navigation paths call this from populate_history_at_index and on_entry_response. It is not used to deduplicate Ctrl+R search; search has its own exact-text deduplication.

Call graph: called by 2 (on_entry_response, populate_history_at_index).

HistorySearchState::new833–845 ↗
fn new(query: &str) -> Self

Purpose: Creates fresh state for one Ctrl+R search query. It starts with no selected result, no cached matches, and no pending persistent lookup.

Data flow: It receives the query string, stores both the original query and a lowercase copy, and initializes empty match tracking and boundary flags.

Call relations: ChatComposerHistory::search calls this when search starts, restarts, or the query changes. The lowercase query is later used by search_matches.

Call graph: called by 1 (search); 2 external calls (new, new).

HistorySearchState::is_exhausted847–852 ↗
fn is_exhausted(&self, direction: HistorySearchDirection) -> bool

Purpose: Reports whether the current search has already reached the end in a given direction. This helps avoid repeating useless scans.

Data flow: It receives a direction and returns the matching exhausted flag: older or newer.

Call relations: ChatComposerHistory::search uses this before scanning again after a boundary has already been reached.

HistorySearchState::mark_exhausted854–859 ↗
fn mark_exhausted(&mut self, direction: HistorySearchDirection)

Purpose: Marks that a search has no further results in one direction. This preserves boundary behavior for repeated key presses.

Data flow: It receives a direction and sets either exhausted_older or exhausted_newer to true.

Call relations: exhausted_search_result calls this when the user hit a boundary after already having a selected match.

HistorySearchState::record_match861–883 ↗
fn record_match(&mut self, offset: usize, entry: &HistoryEntry)

Purpose: Adds a newly found unique search match to the search state, or reselects it if it was already known. The cached matches are kept in newest-to-oldest order.

Data flow: It receives an offset and entry. If that offset is already in the match list, it selects the existing item; otherwise it records the entry text as seen, inserts the match in order, and selects it.

Call relations: ChatComposerHistory::search_match calls this whenever a search hit is accepted. It calls select_match to update the selected offset and index.

Call graph: calls 1 internal fn (select_match); 1 external calls (clone).

HistorySearchState::select_match885–894 ↗
fn select_match(&mut self, index: usize)

Purpose: Makes one cached search match the active selection. This is the small state update used both for new matches and for revisiting cached matches.

Data flow: It receives an index into unique_matches. If that index exists, it stores the match’s offset as selected, stores the index, clears any pending lookup, and resets exhausted flags.

Call relations: HistorySearchState::record_match calls this after inserting or finding a match. ChatComposerHistory::select_cached_unique_match updates the selected index directly through this method.

Call graph: called by 1 (record_match).

tests::test_thread_id904–907 ↗
fn test_thread_id() -> ThreadId

Purpose: Creates a stable thread id for tests. It avoids repeating the same parsing code in every test case.

Data flow: It parses a fixed string into a ThreadId and returns it. If parsing failed, the test would fail immediately.

Call relations: Several tests call this when they need persistent-history metadata with a realistic thread id.

Call graph: calls 1 internal fn (from_string).

tests::duplicate_submissions_are_not_recorded910–936 ↗
fn duplicate_submissions_are_not_recorded()

Purpose: Verifies that local history ignores empty entries and consecutive duplicates. This protects users from cluttered recall history.

Data flow: It creates a new history state, records empty, repeated, and different entries, and checks the resulting local history length and contents.

Call relations: This test exercises ChatComposerHistory::record_local_submission and the shared recording rules inside record_local_submission_inner.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert_eq!, new).

tests::persistent_restore_gates_at_mentions939–1005 ↗
fn persistent_restore_gates_at_mentions()

Purpose: Checks that saved mention text is decoded differently depending on whether @ mention restoration is enabled. This confirms the feature flag changes restored history safely.

Data flow: It creates history metadata, requests a persistent entry, injects a response with encoded mentions, and compares the returned HistoryEntry before and after enabling @ restoration.

Call relations: This test covers navigate_up, on_entry_response, set_at_mention_restore_enabled, and HistoryEntry::new_with_at_mentions behavior.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, test_thread_id).

tests::navigation_with_async_fetch1008–1079 ↗
fn navigation_with_async_fetch()

Purpose: Verifies normal Up navigation when some history is local and some must be fetched asynchronously. It checks that lookup events are sent and responses complete navigation.

Data flow: It sets up persistent metadata and one local entry, navigates upward, reads emitted lookup events, feeds back persistent responses, and checks the returned entries.

Call relations: This test exercises should_handle_navigation, navigate_up, event sending through populate_history_at_index, and response handling in on_entry_response.

Call graph: calls 2 internal fn (new, new); 5 external calls (assert!, assert_eq!, new, panic!, test_thread_id).

tests::search_matches_local_history_and_stops_at_boundaries1082–1145 ↗
fn search_matches_local_history_and_stops_at_boundaries()

Purpose: Checks that Ctrl+R search finds local matching prompts in the right order and reports boundaries without moving past them. This protects the user’s current selected match.

Data flow: It records several local entries, searches for “git” toward older entries, verifies each found result, then verifies AtBoundary at the ends and movement back toward newer results.

Call relations: This test mainly exercises ChatComposerHistory::search and the local-history path inside advance_search_from.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, new).

tests::search_skips_duplicate_local_matches1148–1203 ↗
fn search_skips_duplicate_local_matches()

Purpose: Verifies that Ctrl+R search does not show duplicate local prompts with the same text during one search session. This keeps search results useful instead of repetitive.

Data flow: It records local entries including repeated “git status”, searches through matches, and checks that only unique prompt texts are returned.

Call relations: This test covers search_result_is_unique, search_match, and cached unique-match traversal through ChatComposerHistory::search.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, new).

tests::repeated_boundary_search_does_not_refetch_persistent_history1206–1276 ↗
fn repeated_boundary_search_does_not_refetch_persistent_history()

Purpose: Checks that once a persistent search has reached a boundary, pressing search again does not request the same saved entries repeatedly. This prevents wasted background lookups.

Data flow: It sets up persistent metadata, searches for a query, feeds one matching response and two non-matching responses, then confirms repeated boundary search returns AtBoundary without sending more events.

Call relations: This test exercises pending search handling in on_entry_response, exhausted_search_result, and the exhausted flags in HistorySearchState.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, test_thread_id).

tests::search_fetches_persistent_history_until_match1279–1340 ↗
fn search_fetches_persistent_history_until_match()

Purpose: Verifies that Ctrl+R search walks through persistent history one fetched entry at a time until it finds a match. This confirms asynchronous search continuation works.

Data flow: It starts a search, checks the first lookup event, returns a non-match, checks that the next lookup is sent, then returns a matching entry and verifies it is found.

Call relations: This test covers advance_search_from, on_entry_response, advance_search_after, and event-based persistent lookup.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, test_thread_id).

tests::search_skips_duplicate_persistent_matches1343–1431 ↗
fn search_skips_duplicate_persistent_matches()

Purpose: Checks that duplicate prompt text is skipped even when matches come from persistent history. It also verifies that cached unique results can be revisited in the newer direction.

Data flow: It searches persistent history, returns duplicate and non-matching entries, confirms the older unique match is found, then searches newer and checks the earlier unique result is returned.

Call relations: This test exercises persistent search deduplication, pending lookup continuation, boundary behavior, and select_cached_unique_match.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, test_thread_id).

tests::search_is_case_insensitive_and_empty_query_finds_latest1434–1459 ↗
fn search_is_case_insensitive_and_empty_query_finds_latest()

Purpose: Verifies two user-friendly search rules: letter case does not matter, and an empty query recalls the newest entry. These match common shell search behavior.

Data flow: It records one mixed-case local entry, searches with lowercase text, then searches with an empty query, and checks both return the same entry.

Call relations: This test covers search_matches and the restart behavior in ChatComposerHistory::search.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, new).

tests::reset_navigation_resets_cursor1462–1492 ↗
fn reset_navigation_resets_cursor()

Purpose: Checks that resetting navigation clears the browsing cursor so the next Up starts again from the newest entry. This matters after canceling or clearing composer input.

Data flow: It preloads cached persistent entries, navigates upward twice, calls reset_navigation, checks cursor state is cleared, then confirms another Up returns the newest entry again.

Call relations: This test exercises navigate_up, reset_navigation, and cached persistent entry lookup.

Call graph: calls 2 internal fn (new, new); 4 external calls (assert!, assert_eq!, new, test_thread_id).

tests::should_handle_navigation_when_cursor_is_at_line_boundaries1495–1504 ↗
fn should_handle_navigation_when_cursor_is_at_line_boundaries()

Purpose: Verifies that history navigation is allowed only when recalled text is selected at a safe cursor position. This protects normal movement inside multiline drafts.

Data flow: It creates a history state, records a local entry, sets the last recalled text, and checks navigation decisions for cursor positions at the start, end, middle, and with different text.

Call relations: This test directly covers ChatComposerHistory::should_handle_navigation, the gate used before keyboard handling calls navigate_up or navigate_down.

Call graph: calls 1 internal fn (new); 2 external calls (assert!, new).

tui/src/bottom_pane/chat_composer/history_search.rssource ↗
domain_logickey handling and rendering while the chat composer is active

This file exists so searching command history feels safe and predictable. When a user presses Ctrl+R, the composer must not immediately overwrite what they were typing. Instead, it takes a snapshot of the current draft, opens a small search prompt in the footer, and waits for the user to type a query. As matches are found, the matching history entry is shown in the main text area as a preview, while the query itself stays in the footer. This is like trying on an old message without committing to wearing it.

The file owns the user-interface side of the search session: starting it, reading keys while it is active, showing footer hints, placing the cursor, and highlighting matching text. The separate history store decides which entries match and how to move through them. That split matters because the composer knows about drafts, popups, pasted text, cursor placement, and rendering, while the history store knows about history order and search rules.

Important behavior: Esc and Ctrl+C cancel the search and restore the exact draft from before search began. Enter only accepts a real match. Empty searches do not preview the latest history item. If no match is found, the original draft comes back but the search prompt stays open so the user can keep editing the query.

Function details27
ChatComposer::history_search_active79–81 ↗
fn history_search_active(&self) -> bool

Purpose: This test-only helper says whether the composer is currently in Ctrl+R history search mode. Tests use it to check that search mode starts and stops at the right time.

Data flow: It reads the composer's optional search session. If a session exists, it returns true; if not, it returns false. It does not change anything.

Call relations: This is used by the test code as a simple yes-or-no window into private composer state. Normal application code does not need it.

ChatComposer::is_history_search_key89–91 ↗
fn is_history_search_key(key_event: &KeyEvent, bindings: &[KeyBinding]) -> bool

Purpose: This checks whether a key press means “start reverse history search” or “move to an older match.” It uses the configured key bindings instead of hard-coding one keyboard shape.

Data flow: It receives a key event and a list of allowed key bindings. It asks the binding list whether that event matches, then returns true or false without changing state.

Call relations: Search-mode key handling calls this before ordinary text input can treat the same key as typed text. It delegates the actual comparison to the key-binding helper is_pressed.

Call graph: calls 1 internal fn (is_pressed).

ChatComposer::is_history_search_forward_key93–95 ↗
fn is_history_search_forward_key(key_event: &KeyEvent, bindings: &[KeyBinding]) -> bool

Purpose: This checks whether a key press means “move forward toward newer history matches.” It is the forward-search companion to the reverse-search key check.

Data flow: It receives a key event and the configured forward-search bindings. It returns whether the key matches one of those bindings and changes nothing.

Call relations: The active search key handler calls this when deciding whether to move to a newer match. The low-level binding comparison is done by is_pressed.

Call graph: calls 1 internal fn (is_pressed).

ChatComposer::handle_history_search_key134–231 ↗
fn handle_history_search_key(&mut self, key_event: KeyEvent) -> (InputResult, bool)

Purpose: This is the main keyboard controller while the footer is being used as the history search box. It decides whether each key edits the query, moves through matches, accepts a match, cancels search, or is simply swallowed.

Data flow: It receives one key event. Release events are ignored. Ctrl+R or Up searches older matches; the forward binding or Down searches newer matches; Esc and Ctrl+C cancel; Enter accepts only when a match is being previewed; Backspace and Ctrl+H remove a query character; Ctrl+U clears the query; normal printable characters are appended to the query. It returns an input result plus a flag saying whether the key was consumed.

Call relations: This function sits between raw terminal key events and the rest of composer editing. It calls history_search_in_direction for navigation, update_history_search_query for text edits, and cancel_history_search for cancellation, so ordinary composer editing does not accidentally modify the wrong text while search mode is active.

Call graph: calls 5 internal fn (cancel_history_search, history_search_in_direction, update_history_search_query, reset_mode_after_activity, has_ctrl_or_alt); 4 external calls (is_history_search_forward_key, is_history_search_key, new, matches!).

ChatComposer::history_search_in_direction233–257 ↗
fn history_search_in_direction(&mut self, direction: HistorySearchDirection) -> InputResult

Purpose: This moves the active search to the next matching history entry in one direction: older or newer. It is used for repeated Ctrl+R, Up, Ctrl+S, or Down behavior.

Data flow: It reads the current search query and the saved original draft. If there is no active search, it does nothing. If the query is empty, it resets search state and restores the original draft. Otherwise it asks the history store for the next match in the requested direction, then applies that result to the composer preview.

Call relations: handle_history_search_key calls this when the user presses a navigation key. It hands the result to apply_history_search_result, which turns history-store answers into visible composer state.

Call graph: calls 1 internal fn (apply_history_search_result); called by 1 (handle_history_search_key).

ChatComposer::update_history_search_query259–286 ↗
fn update_history_search_query(&mut self, query: String)

Purpose: This replaces the footer search text after the user types, deletes, or clears characters. Every query change restarts the search from the newest matching history entry.

Data flow: It receives the new query string. It saves that query into the active session, marks the session as searching, restores the original draft so stale previews disappear, and then either resets search if the query is empty or asks the history store for the newest older match. The result is then applied to the text area and status.

Call relations: handle_history_search_key calls this for Backspace, Ctrl+U, and normal character input. It calls apply_history_search_result after the history store answers.

Call graph: calls 1 internal fn (apply_history_search_result); called by 1 (handle_history_search_key).

ChatComposer::apply_history_search_result311–342 ↗
fn apply_history_search_result(&mut self, result: HistorySearchResult)

Purpose: This converts a history search answer into what the user sees. It decides whether to preview a match, show a waiting state, keep the current match, or restore the original draft.

Data flow: It receives a search result from the history store. A found entry changes the status to match and puts that entry into the text area. A pending result marks the footer as searching. A boundary result keeps the current preview as a match. A not-found result marks no match and restores the saved draft.

Call relations: Both history_search_in_direction and update_history_search_query call this after asking the history store to search. This keeps all result-to-UI translation in one place.

Call graph: called by 2 (history_search_in_direction, update_history_search_query).

ChatComposer::history_search_action_key_span372–374 ↗
fn history_search_action_key_span(key: KeyCode) -> Span<'static>

Purpose: This formats a key name for the history-search footer hints. It makes action keys visually distinct so users can quickly see what to press.

Data flow: It receives a key code such as Enter or Esc. It turns that key into plain text and wraps it in a styled span with emphasis, then returns that span.

Call relations: history_search_footer_line calls this when it needs to display accept and cancel shortcuts. The actual key-name text comes from the shared key-hint helper.

Call graph: calls 1 internal fn (plain); 1 external calls (from).

ChatComposer::history_search_highlight_ranges381–389 ↗
fn history_search_highlight_ranges(&self) -> Vec<Range<usize>>

Purpose: This tells the renderer which parts of the previewed history entry should be highlighted as matches for the query. It only highlights while search is still active and a match is being previewed.

Data flow: It reads the active search status, query, and current text-area text. If there is no active match or the query is empty, it returns an empty list. Otherwise it returns byte ranges for each case-insensitive occurrence of the query in the preview text.

Call relations: The composer rendering path uses these ranges to draw matching text differently. It relies on case_insensitive_match_ranges for the actual matching calculation.

Call graph: 3 external calls (case_insensitive_match_ranges, new, matches!).

ChatComposer::case_insensitive_match_ranges391–438 ↗
fn case_insensitive_match_ranges(text: &str, query: &str) -> Vec<Range<usize>>

Purpose: This finds every case-insensitive occurrence of a query inside some text and reports where those matches are in the original text. It is careful with Unicode characters whose lowercase form may not be the same byte length as the original.

Data flow: It receives the original text and query. It lowercases both in a way that tracks how each lowercase character maps back to the original byte range, searches through the lowercased text, and returns original-text byte ranges for each match. Empty queries return no ranges.

Call relations: history_search_highlight_ranges uses this helper before rendering highlights. The tests call it directly to protect behavior for ordinary ASCII text and trickier Unicode text.

Call graph: 2 external calls (new, new).

ChatComposer::history_search_cursor_pos445–482 ↗
fn history_search_cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: This calculates where the terminal cursor should appear while the user is typing the footer search query. The cursor follows the footer query, not the previewed text in the main editor.

Data flow: It receives the available screen rectangle. It reads the active search query, computes the footer hint area inside the composer layout, measures the prompt and query width, clamps the x-position so it stays on screen, and returns the cursor coordinates. If no search is active or the footer area is unavailable, it returns nothing.

Call relations: The terminal rendering layer calls this when deciding where to place the cursor. It uses the same footer spacing and layout helpers as normal footer rendering so the cursor lines up with the displayed search prompt.

Call graph: 4 external calls (Length, vertical, from, footer_spacing).

tests::history_search_opens_without_previewing_latest_entry505–525 ↗
fn history_search_opens_without_previewing_latest_entry()

Purpose: This test checks the safety rule that pressing Ctrl+R opens search mode without immediately inserting the newest history entry into the composer.

Data flow: It creates a composer, records one history entry, leaves the draft empty, sends Ctrl+R, and then checks that search is active, the text area is still empty, and the footer is in history-search mode.

Call relations: The test exercises the public key-event path that eventually starts history search. It protects the behavior implemented by begin_history_search.

Call graph: calls 2 internal fn (new, new); 7 external calls (Char, new, new, new, assert!, assert_eq!, new).

tests::history_search_match_ranges_are_case_insensitive528–538 ↗
fn history_search_match_ranges_are_case_insensitive()

Purpose: This test proves that search highlighting ignores letter case and handles a Unicode lowercase edge case. It also confirms that an empty query highlights nothing.

Data flow: It calls the match-range helper with sample text and queries, then compares the returned byte ranges with expected ranges. No composer state is created or changed.

Call relations: The test calls ChatComposer::case_insensitive_match_ranges directly because the matching math is small but easy to get subtly wrong.

Call graph: 2 external calls (assert!, assert_eq!).

tests::history_search_accepts_matching_entry541–576 ↗
fn history_search_accepts_matching_entry()

Purpose: This test checks the happy path: search for a past prompt, preview it, press Enter, and keep it as the editable draft.

Data flow: It creates a composer with two history entries and an existing draft. After Ctrl+R, it types git, confirms the preview changed to git status, presses Enter, and checks that search ended while the chosen text remained and the cursor moved to the end.

Call relations: The test goes through the same key handling a user would use. It covers query updates, applying a found result, and accepting through handle_history_search_key.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, new).

tests::vim_normal_history_search_preview_places_cursor_on_last_char579–602 ↗
fn vim_normal_history_search_preview_places_cursor_on_last_char()

Purpose: This test checks cursor behavior when Vim-style editing is enabled. In Vim normal mode, a preview should place the cursor on the last character rather than after the text.

Data flow: It creates a composer, records git status, enables Vim behavior, starts search, types git, and checks the preview text plus cursor position.

Call relations: The test exercises the normal key path into history search and relies on the composer’s existing draft-application behavior for Vim mode.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert_eq!, new).

tests::history_search_stays_on_single_match_at_boundaries605–657 ↗
fn history_search_stays_on_single_match_at_boundaries()

Purpose: This test makes sure repeated navigation does not erase or mark a single match as failed when the search reaches the end of available results.

Data flow: It creates one matching history entry, searches for it, repeatedly asks for older matches, then repeatedly asks for newer matches. After each boundary condition, it confirms the same preview remains and the status still counts as a match.

Call relations: The test protects the AtBoundary branch in apply_history_search_result, which must keep the current match instead of behaving like no match.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, new).

tests::history_search_highlights_matches_until_accepted721–774 ↗
fn history_search_highlights_matches_until_accepted()

Purpose: This test checks that matching text is highlighted while it is only a preview, then stops being highlighted after the user accepts it.

Data flow: It creates a composer with history, searches for git, renders the UI into a test buffer, and checks that the matching letters are drawn with highlight styling. Then it presses Enter, renders again, and checks that the accepted text no longer has preview highlighting.

Call relations: The test exercises history_search_highlight_ranges through real rendering rather than calling it alone, so it protects the link between search state and visual output.

Call graph: calls 2 internal fn (new, new); 7 external calls (empty, Char, new, new, assert!, assert_eq!, new).

tests::history_search_esc_restores_original_draft777–802 ↗
fn history_search_esc_restores_original_draft()

Purpose: This test checks that Esc cancels search and restores both the original draft text and cursor position.

Data flow: It creates a composer with a draft and history, places the cursor inside the draft, starts search, previews a history entry, presses Esc, and then checks that the previous draft and cursor position are back.

Call relations: The test drives the user-facing path into cancel_history_search through handle_history_search_key.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, new).

tests::history_search_ctrl_c_restores_original_draft805–842 ↗
fn history_search_ctrl_c_restores_original_draft()

Purpose: This test checks that both common terminal forms of Ctrl+C cancel history search safely. Some terminals report Ctrl+C as a modified c; others report the raw control character.

Data flow: It builds a composer already previewing a history entry, sends each Ctrl+C variant, and checks that search ended and the saved draft text and cursor position were restored.

Call relations: The test protects the Ctrl+C branches inside handle_history_search_key, which both route to cancel_history_search.

Call graph: 4 external calls (Char, new, assert!, assert_eq!).

tests::history_search_flushes_pending_first_char_before_snapshot845–870 ↗
fn history_search_flushes_pending_first_char_before_snapshot()

Purpose: This test makes sure a single character waiting in the paste-burst buffer becomes part of the saved original draft before history search starts.

Data flow: It types h, confirms it is still buffered rather than inserted, presses Ctrl+R, and checks that search is active, the paste buffer is cleared, and the draft now contains h. After Esc, it confirms h is still there.

Call relations: The test protects the paste-flushing step in begin_history_search, ensuring cancellation restores what the user had really typed.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::history_search_flushes_buffered_paste_before_snapshot873–908 ↗
fn history_search_flushes_buffered_paste_before_snapshot()

Purpose: This test checks the same safety rule for a multi-character paste burst. Search should snapshot the draft after pending pasted text has been applied, not before.

Data flow: It simulates rapidly typed or pasted characters spelling paste, confirms they are buffered, starts history search, and checks that the draft contains paste. After canceling search, it verifies that paste remains.

Call relations: The test exercises the paste-burst path that begin_history_search flushes before saving the original draft.

Call graph: calls 2 internal fn (new, new); 6 external calls (from_millis, now, Char, new, assert!, assert_eq!).

tests::history_search_esc_resets_normal_history_navigation911–941 ↗
fn history_search_esc_resets_normal_history_navigation()

Purpose: This test checks that canceling search also resets normal history navigation. After canceling a search preview, pressing Up should begin normal history navigation from the newest entry.

Data flow: It records two history entries, searches for a matching older one, cancels with Esc, verifies the draft is empty again, then presses Up and checks that the newest history entry appears.

Call relations: The test protects the reset-navigation work in cancel_history_search, not just the visible draft restoration.

Call graph: calls 2 internal fn (new, new); 7 external calls (Char, new, new, new, assert!, assert_eq!, new).

tests::history_search_no_match_restores_preview_but_keeps_search_open944–967 ↗
fn history_search_no_match_restores_preview_but_keeps_search_open()

Purpose: This test checks the no-match behavior. A failed query should restore the original draft but leave search mode open so the user can keep editing the query.

Data flow: It creates a composer with a draft and one unrelated history entry, starts search, types a query that cannot match, and then checks that search is still active, the draft text is restored, and the footer remains in history-search mode.

Call relations: The test protects the NotFound branch in apply_history_search_result and the query-edit flow through update_history_search_query.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, new).

Slash commands and composer flow

These files cover slash-command vocabulary and parsing, popup support for command selection, and the main chat composer state machine that ties message entry together.

tui/src/bottom_pane/prompt_args.rssource ↗
domain_logicprompt submission and validation

This file solves a small but important parsing problem for the terminal user interface: when a user types a command beginning with /, the program needs to know which command they meant and what extra text they gave it. For example, /search cats should become the command name search and the argument text cats.

The parser is deliberately narrow. It only looks at the first line it is given. The line must start with /, and there must be at least one non-space character after that slash to count as a command name. The command name runs until the first whitespace character, which means spaces, tabs, and similar separators all work. Anything after the command name is treated as the command's remaining text, but leading whitespace before that remaining text is skipped.

One detail matters for the rest of the interface: the function returns a byte offset into the original line where the remaining text starts. This is like marking the exact column in a sentence where the arguments begin. Other parts of the prompt system can use that location for validation, highlighting, extracting prepared arguments, or submitting queued slash prompts without guessing where the command ended.

Function details1
parse_slash_name7–25 ↗
fn parse_slash_name(line: &str) -> Option<(&str, &str, usize)>

Purpose: This function checks whether a line starts with a slash command and, if so, separates the command name from the rest of the line. Someone would use it whenever they need to understand user input like /command extra text without changing the original text.

Data flow: It receives one text line. First it verifies that the line begins with /; if not, it returns nothing. Then it scans the characters after the slash until it reaches whitespace, treating that slice as the command name. If the name is empty, it returns nothing. Otherwise it trims only the leading whitespace before the remaining text, calculates the byte position where that remaining text begins in the original line, and returns the command name, the trimmed remaining text, and that position.

Call relations: This function is the shared first step for several prompt features. Submission code calls it when deciding what to do with a typed command, validation code calls it to check whether the input is acceptable, and UI-related code calls it to find the command or argument range for highlighting or editing. After it returns the parsed pieces, those callers continue with their own jobs, such as preparing arguments, recognizing bare or inline commands, or submitting a queued slash prompt.

Call graph: called by 7 (handle_submission_with_time, bare_command, command_element_range, inline_command, validate_submission, prepared_args, submit_queued_slash_prompt).

tui/src/slash_command.rssource ↗
domain_logicrequest handling

This file is the command directory for the terminal interface. When a user types a message starting with /, the app needs to know whether that word is a real command, what to show in the autocomplete popup, whether the command can take extra text after it, and whether it is safe to run right now. Without this file, slash commands would be scattered and inconsistent, like a restaurant where every waiter has a different menu.

The main piece is the SlashCommand enum. An enum is a fixed list of named choices; here, each choice is one built-in command. The strum helper macros generate common behavior from that list, such as turning a command into its text name, parsing text back into a command, and iterating over all commands for autocomplete.

The methods on SlashCommand add policy. description gives the short help sentence shown to users. supports_inline_args says which commands may be followed by extra words, such as /review something. available_during_task protects the running session by blocking commands that would interrupt or reconfigure important state while work is already happening. available_in_side_conversation limits what can be used in a temporary side chat. is_visible hides commands that do not apply on the current operating system or are only meant for debug builds.

Finally, built_in_slash_commands produces the filtered command list used by the UI.

Function details12
SlashCommand::description83–144 ↗
fn description(self) -> &'static str

Purpose: Returns the short explanation shown to the user for a specific slash command. This is what makes the command popup understandable instead of showing only names like /compact or /mcp.

Data flow: It starts with one SlashCommand value. It checks which command it is and returns a fixed text sentence describing what that command does. It does not change anything; it only translates an internal command choice into user-facing help text.

Call relations: This method is part of the command catalog that the terminal UI can consult when presenting commands to the user. It sits beside the command name and availability rules so the popup can explain not just what commands exist, but why someone might choose one.

SlashCommand::command148–150 ↗
fn command(self) -> &'static str

Purpose: Returns the command's text name without the leading slash, such as status for /status. It exists so other code can ask for the canonical spelling of a command in a simple, consistent way.

Data flow: It receives one SlashCommand value. The generated conversion code turns that value into its static command string, including special names and aliases defined on the enum. The result is a borrowed text string; nothing else changes.

Call relations: Command-dispatch code calls this when it needs to compare, report, or pass around the command name. It is used while preparing commands with arguments, checking side-conversation behavior, and requesting empty side conversations, so those flows all speak the same command language.

Call graph: called by 3 (dispatch_prepared_command_with_args, ensure_side_command_allowed_outside_review, request_empty_side_conversation).

SlashCommand::supports_inline_args153–171 ↗
fn supports_inline_args(self) -> bool

Purpose: Says whether a command is allowed to have extra text after it on the same line. For example, this allows forms like /review ... while preventing accidental unused text after commands that do not accept details.

Data flow: It receives one command. It checks whether that command is in the allowed group for inline arguments and returns true or false. It does not parse the arguments themselves; it only answers whether arguments are permitted.

Call relations: When command-dispatch code receives a slash command with extra text, it calls this method before continuing. The method hands back a yes-or-no decision that helps dispatch_command_with_args accept commands that can use the extra words and reject or treat differently those that cannot.

Call graph: called by 1 (dispatch_command_with_args); 1 external calls (matches!).

SlashCommand::available_in_side_conversation174–185 ↗
fn available_in_side_conversation(self) -> bool

Purpose: Says whether a command may be used inside an active side conversation. A side conversation is a temporary fork, so only a small set of safe commands are allowed there.

Data flow: It receives one command. It checks whether that command is one of the side-conversation-safe commands, such as copying output, viewing a diff, checking status, or mentioning a file. It returns true if allowed and false otherwise.

Call relations: The side-conversation guard calls this before letting a slash command run in that special context. The result helps ensure_slash_command_allowed_in_side_conversation keep temporary conversations from performing actions that belong to the main session.

Call graph: called by 1 (ensure_slash_command_allowed_in_side_conversation); 1 external calls (matches!).

SlashCommand::available_during_task188–244 ↗
fn available_during_task(self) -> bool

Purpose: Says whether a command can be run while Codex is already working on a task. This protects the session from disruptive actions, such as changing the model or clearing the chat, while still allowing harmless checks like status or copying output.

Data flow: It receives one command. It sorts that command into allowed or blocked groups for the "task in progress" state and returns a boolean answer. It does not stop the task itself; it provides the rule that other code enforces.

Call relations: Both command dispatch paths call this when the user tries to run a slash command during active work. Its decision tells dispatch_command and dispatch_command_with_args whether to proceed or prevent the command until the task is no longer running.

Call graph: called by 2 (dispatch_command, dispatch_command_with_args).

SlashCommand::is_visible246–254 ↗
fn is_visible(self) -> bool

Purpose: Decides whether a command should appear in the built-in command list on the current platform and build type. This hides commands that would not work or should not be exposed, such as debug-only commands in normal builds.

Data flow: It receives one command. It checks special cases against compile-time settings, such as the operating system or whether this is a debug build. It returns true for commands that should be shown and false for commands that should be hidden.

Call relations: The command-list builder calls this while preparing commands for the UI. It acts like a filter at the door, keeping unsuitable commands out before the autocomplete list is shown.

Call graph: 1 external calls (cfg!).

built_in_slash_commands258–263 ↗
fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)>

Purpose: Builds the list of slash commands that the UI should offer to the user. Each item contains both the command text and the matching SlashCommand value used internally.

Data flow: It starts by iterating over every value in the SlashCommand enum. It removes commands that are not visible on this platform or build, converts the remaining commands to their text names, and returns a vector of (command string, command value) pairs.

Call relations: The autocomplete-building code calls this through builtins_for_input when it needs the available built-in slash commands. This function gathers the catalog, applies visibility rules through SlashCommand::is_visible, and hands the cleaned list to the UI layer.

Call graph: called by 1 (builtins_for_input); 1 external calls (iter).

tests::stop_command_is_canonical_name273–275 ↗
fn stop_command_is_canonical_name()

Purpose: Checks that the Stop command's main displayed name is stop. This protects a user-facing command spelling from accidentally changing.

Data flow: The test asks SlashCommand::Stop for its command string. It compares the result with the expected text stop. The test passes if they match and fails if the canonical name changes.

Call relations: This test supports the alias behavior around Stop. It verifies the main name while a separate test checks that the older or alternate spelling still parses correctly.

Call graph: 1 external calls (assert_eq!).

tests::clean_alias_parses_to_stop_command278–280 ↗
fn clean_alias_parses_to_stop_command()

Purpose: Checks that typing clean is accepted as an alias for the Stop command. This keeps compatibility for users or code that still use the alternate name.

Data flow: The test parses the text clean as a slash command. It expects the parser to produce SlashCommand::Stop. The only output is the test result: pass if the alias maps correctly, fail otherwise.

Call relations: This test relies on the generated parsing behavior from the enum annotations. Together with the canonical-name test, it confirms that stop is the displayed name while clean remains accepted as input.

Call graph: 1 external calls (assert_eq!).

tests::pet_alias_parses_to_pets_command283–286 ↗
fn pet_alias_parses_to_pets_command()

Purpose: Checks both the main name and alias for the pets command. It ensures the command is displayed as pets but also accepts pet when typed.

Data flow: The test first asks SlashCommand::Pets for its command string and expects pets. Then it parses the text pet and expects the same Pets command. It changes no application state; it only verifies naming behavior.

Call relations: This test protects the special naming rule attached to the Pets enum variant. It confirms that command display and command parsing stay aligned with the intended user experience.

Call graph: 1 external calls (assert_eq!).

tests::certain_commands_are_available_during_task289–298 ↗
fn certain_commands_are_available_during_task()

Purpose: Checks that selected safe commands remain usable while a task is running. It also verifies that /raw is allowed in side conversations and can take inline arguments.

Data flow: The test calls availability methods on several command values and expects each answer to be true. If any of those commands becomes blocked by mistake, the test fails. It does not run the commands themselves.

Call relations: This test guards important policy choices in available_during_task, available_in_side_conversation, and supports_inline_args. It helps keep command-dispatch behavior stable for commands that users may need while Codex is busy.

Call graph: 1 external calls (assert!).

tests::auto_review_command_is_approve301–307 ↗
fn auto_review_command_is_approve()

Purpose: Checks that the auto-review retry command is exposed to users as approve. This preserves the intended command spelling for approving one retry after a denial.

Data flow: The test asks SlashCommand::AutoReview for its command string and expects approve. It also parses the text approve and expects the AutoReview command. The result is only a pass or fail for this naming contract.

Call relations: This test verifies the custom enum annotation for AutoReview. It ensures both directions work: internal command to displayed text, and typed text back to the internal command used by dispatch.

Call graph: 1 external calls (assert_eq!).

tui/src/bottom_pane/slash_commands.rssource ↗
domain_logicuser input and command popup filtering

Slash commands are short commands a user types after a slash, such as changing model settings or checking status. This file is the shared rulebook for those commands in the bottom pane of the terminal interface. Without it, the command popup and the command submission path could disagree: a command might appear in the menu but fail when typed, or be accepted even though a feature is turned off.

The file defines two kinds of command items. A command can be a built-in command known by the app, or a service-tier command supplied by model configuration, such as a faster or cheaper model tier. It also defines a set of flags that describe the current situation: which features are enabled, whether sandbox elevation is allowed, and whether the user is in a side conversation.

The main flow is simple. First, builtins_for_input starts from the full built-in command list and filters out commands that should not be visible right now. Then commands_for_input turns those into displayable command items and, when the /model command is reached, inserts service-tier commands after it. Lookup functions then answer two different questions: “does this exact command exist?” and “does the current partial text look like the start of any command?” A subtle detail is that exact lookup is sometimes more permissive than the popup, so the app can give a clear “not available here” message instead of acting as if the command does not exist.

Function details23
SlashCommandItem::command27–32 ↗
fn command(&self) -> &str

Purpose: Returns the text name that should be matched or shown for a command item. It hides the difference between a built-in command and a service-tier command so callers can treat both the same way.

Data flow: It receives one command item. If the item wraps a built-in command, it asks that built-in command for its command text. If it wraps a service-tier command, it returns the tier's name. The output is a borrowed string such as a command name to compare against user input.

Call relations: Other command-filtering code can call this when it needs a single command label no matter where the command came from. In this file, it is especially useful for prefix matching, where built-in and service-tier commands must be checked in one combined list.

SlashCommandItem::supports_inline_args34–39 ↗
fn supports_inline_args(&self) -> bool

Purpose: Says whether this command can accept extra text on the same line. Built-in commands decide this for themselves, while service-tier commands do not accept inline arguments.

Data flow: It receives one command item. For a built-in command, it forwards the question to that command's own rules. For a service-tier command, it returns false. Nothing else is changed.

Call relations: This method gives callers a uniform way to ask about inline arguments without knowing which kind of command item they have. It mirrors the built-in command behavior but keeps service-tier commands simple.

SlashCommandItem::available_in_side_conversation41–46 ↗
fn available_in_side_conversation(&self) -> bool

Purpose: Says whether this command is allowed while the user is in a side conversation. Built-in commands carry their own answer; service-tier commands are treated as unavailable there.

Data flow: It receives one command item. If it is built-in, it asks the built-in command whether side conversations are allowed. If it is a service-tier command, it returns false. The result is a yes-or-no value used for filtering.

Call relations: The command-list builder uses this kind of rule to hide commands that should not be offered in a side conversation. It keeps the side-conversation restrictions consistent for both built-in and service-tier commands.

SlashCommandItem::available_during_task48–53 ↗
fn available_during_task(&self) -> bool

Purpose: Says whether a command can be used while a task is already running. This protects the app from accepting commands that only make sense when the system is idle.

Data flow: It receives one command item. Built-in commands answer according to their own rules. Service-tier commands return false, because changing tiers is not allowed during an active task through this path. The output is a yes-or-no decision.

Call relations: When a slash command is about to be used, reject_slash_command_if_unavailable calls this to decide whether to block it. This is the last safety check after a command has already been recognized.

Call graph: called by 1 (reject_slash_command_if_unavailable).

builtins_for_input70–82 ↗
fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static str, SlashCommand)>

Purpose: Builds the list of built-in slash commands that should be visible and usable for the current input situation. It applies feature switches and context rules, like hiding sandbox elevation when it is not allowed.

Data flow: It receives a BuiltinCommandFlags value, which is a bundle of yes-or-no settings about enabled features and current context. It starts with the full built-in command list from built_in_slash_commands, removes commands blocked by those settings, and returns the remaining built-ins with their command names.

Call relations: commands_for_input uses this as the first step before adding service-tier commands. find_builtin_command also uses it to check whether a typed built-in command is allowed by the relevant feature gates. Tests call it to verify that side conversations and disabled features hide the right commands.

Call graph: calls 1 internal fn (built_in_slash_commands); called by 3 (commands_for_input, find_builtin_command, side_conversation_hides_commands_without_side_flag).

commands_for_input84–105 ↗
fn commands_for_input(
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> Vec<SlashCommandItem>

Purpose: Creates the full command list for the input box or command popup, combining built-in commands with optional service-tier commands. Service-tier commands are inserted right after the built-in model command so they appear in the most relevant place.

Data flow: It receives the current flags and a slice of service-tier command records. It asks builtins_for_input for the visible built-ins, wraps each one as a SlashCommandItem, and, if service tiers are enabled, adds the tier commands after the model command. It then applies the side-conversation availability check and returns the final ordered list.

Call relations: The popup creation path calls this to know what to show. has_slash_command_prefix calls it when checking whether partially typed text resembles a command. A test also calls it to make sure service tiers are exposed after the model command.

Call graph: calls 1 internal fn (builtins_for_input); called by 3 (new, has_slash_command_prefix, all_service_tiers_are_exposed_as_commands_after_model); 3 external calls (new, iter, Builtin).

find_builtin_command112–126 ↗
fn find_builtin_command(name: &str, flags: BuiltinCommandFlags) -> Option<SlashCommand>

Purpose: Looks up one built-in slash command by an exact command name or accepted alias, after applying most feature gates. It is used when the user has typed a command and the app needs to know what it means.

Data flow: It receives the typed command name and the current flags. It first tries to parse the name into a built-in command. It also accepts playful versions of goal with extra o letters, such as goooal. Then it checks the filtered built-in list and returns the command if it is allowed, or no result if it is unknown or disabled.

Call relations: find_slash_command calls this before checking service-tier commands. Tests call it to confirm exact lookup works for commands, aliases, disabled feature behavior, and commands hidden from the popup but still useful for dispatch-time error messages.

Call graph: calls 1 internal fn (builtins_for_input); called by 2 (find_slash_command, debug_command_still_resolves_for_dispatch); 1 external calls (from_str).

find_slash_command128–147 ↗
fn find_slash_command(
    name: &str,
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> Option<SlashCommandItem>

Purpose: Finds either a built-in command or a service-tier command by exact name. This is the main lookup used after the user submits a slash command.

Data flow: It receives the typed name, current flags, and available service-tier commands. It first asks find_builtin_command whether the name is a built-in. If not, and service-tier commands are enabled, it searches the service-tier list for a matching name and returns it as a command item. If neither path matches, it returns no result.

Call relations: The command submission flow calls this when it needs to turn typed text into a concrete command to run. It also feeds command parsing inside the composer path. It delegates built-in recognition to find_builtin_command and only searches service tiers afterward.

Call graph: calls 1 internal fn (find_builtin_command); called by 2 (command, submit_queued_slash_prompt); 1 external calls (Builtin).

has_slash_command_prefix149–157 ↗
fn has_slash_command_prefix(
    name: &str,
    flags: BuiltinCommandFlags,
    service_tier_commands: &[ServiceTierCommand],
) -> bool

Purpose: Checks whether a partially typed name looks like the beginning or fuzzy match of any available slash command. This helps the editor decide whether the user is currently typing a command name.

Data flow: It receives the partial name, current flags, and service-tier commands. It builds the current command list with commands_for_input, compares each command name using fuzzy matching, and returns true as soon as one command matches. If none match, it returns false.

Call relations: is_editing_command_name calls this while the user is typing. It relies on commands_for_input so the prefix check follows the same visibility rules as the command popup.

Call graph: calls 1 internal fn (commands_for_input); called by 1 (is_editing_command_name).

tests::all_enabled_flags165–177 ↗
fn all_enabled_flags() -> BuiltinCommandFlags

Purpose: Creates a standard test setup where every feature gate is turned on and there is no side conversation. Tests use it as a clean starting point before changing one setting at a time.

Data flow: It takes no input. It returns a BuiltinCommandFlags value with all feature-related flags enabled, sandbox elevation allowed, and side conversation set to false. It does not change any shared state.

Call relations: Many tests call this helper so they can focus on the one behavior being checked. It keeps the test setup short and consistent.

tests::debug_command_still_resolves_for_dispatch180–183 ↗
fn debug_command_still_resolves_for_dispatch()

Purpose: Checks that the debug configuration command can still be found by exact lookup. This protects dispatch behavior for a command that may not be part of the normal visible command list.

Data flow: It starts with all features enabled, looks up debug-config, and compares the result with the expected debug command. The output is the test pass or failure.

Call relations: The test calls find_builtin_command with the all-enabled test flags and verifies the answer with an assertion. It exists to catch regressions in built-in command lookup.

Call graph: calls 1 internal fn (find_builtin_command); 2 external calls (assert_eq!, all_enabled_flags).

tests::clear_command_resolves_for_dispatch186–191 ↗
fn clear_command_resolves_for_dispatch()

Purpose: Checks that typing clear resolves to the built-in clear command. This confirms a common command remains available to dispatch.

Data flow: It provides the command name clear under the all-enabled setup and expects the lookup result to be the clear command. The test passes if the actual and expected values match.

Call relations: The test is run by the Rust test harness with the other slash-command tests. It guards the exact-name lookup path used when commands are submitted.

Call graph: 1 external calls (assert_eq!).

tests::goal_command_allows_extra_os_for_dispatch194–199 ↗
fn goal_command_allows_extra_os_for_dispatch()

Purpose: Checks the special shortcut that lets users type goal with extra o letters. This preserves a small user-friendly behavior in command lookup.

Data flow: It submits a long gooooo...al spelling under the all-enabled setup and expects the result to be the goal command. If that special parsing stops working, the assertion fails.

Call relations: This test exercises the custom parsing branch inside find_builtin_command, even though the relation is visible through the assertion-driven test flow. It protects behavior that normal command-name parsing would not cover.

Call graph: 1 external calls (assert_eq!).

tests::stop_command_resolves_for_dispatch202–207 ↗
fn stop_command_resolves_for_dispatch()

Purpose: Checks that typing stop resolves to the built-in stop command. This ensures the command dispatcher can recognize the normal stop command name.

Data flow: It uses the all-enabled setup, looks up stop, and expects the stop command as the result. The test changes no state outside its local values.

Call relations: The test belongs to the dispatch lookup test group. It verifies one straightforward command name so changes to parsing do not break stopping behavior.

Call graph: 1 external calls (assert_eq!).

tests::clean_command_alias_resolves_for_dispatch210–215 ↗
fn clean_command_alias_resolves_for_dispatch()

Purpose: Checks that clean works as an alias for the stop command. This matters because users may type the alternate name and still expect the same action.

Data flow: It supplies clean as the typed name and expects the lookup to return the stop command. The only output is whether the assertion succeeds.

Call relations: The test protects alias behavior in the same lookup path used by submitted slash commands. If alias parsing changes, this test points to the break.

Call graph: 1 external calls (assert_eq!).

tests::service_tier_commands_are_hidden_when_disabled218–228 ↗
fn service_tier_commands_are_hidden_when_disabled()

Purpose: Checks that service-tier commands are not accepted when the service-tier feature flag is off. This prevents hidden or disabled model-tier options from being used by typing them directly.

Data flow: It starts from all-enabled flags, turns off service-tier commands, creates one fake tier named fast, and tries to find it. The expected result is no command.

Call relations: The test uses the shared flag helper and an assertion to verify find_slash_command behavior. It makes sure service-tier lookup obeys the same feature gate as display.

Call graph: 3 external calls (assert_eq!, all_enabled_flags, vec!).

tests::all_service_tiers_are_exposed_as_commands_after_model231–261 ↗
fn all_service_tiers_are_exposed_as_commands_after_model()

Purpose: Checks that all configured service tiers appear in the command list immediately after the model command. This keeps the popup order predictable and easy for users to understand.

Data flow: It creates two fake service-tier commands, builds the full command list with all features enabled, finds where the model command appears, and compares the following items with the expected tier commands. The test passes if the order and contents match.

Call relations: This test calls commands_for_input, which is the same function used by command-list creation. It specifically protects the insertion point where service-tier commands are added.

Call graph: calls 1 internal fn (commands_for_input); 3 external calls (assert_eq!, all_enabled_flags, vec!).

tests::goal_command_is_hidden_when_disabled264–268 ↗
fn goal_command_is_hidden_when_disabled()

Purpose: Checks that the goal command cannot be found when its feature flag is turned off. This confirms feature gates actually prevent command use.

Data flow: It starts with all features enabled, disables only the goal command flag, and attempts to look up goal. The expected output is no command.

Call relations: The test uses the standard flag helper and an assertion around exact lookup. It guards the goal-specific feature gate in the built-in filtering rules.

Call graph: 2 external calls (assert_eq!, all_enabled_flags).

tests::usage_command_is_hidden_from_input_when_account_token_activity_is_disabled271–280 ↗
fn usage_command_is_hidden_from_input_when_account_token_activity_is_disabled()

Purpose: Checks that the usage command is hidden from the input command list when account token activity is disabled. This keeps the popup from offering a command that should not be advertised.

Data flow: It starts with all features enabled, turns off token activity, builds the visible built-in list, and searches for the usage command. The expected result is that usage is absent.

Call relations: This test calls the same built-in filtering function used by the input UI. It protects the visible-command behavior for the usage command.

Call graph: 2 external calls (assert_eq!, all_enabled_flags).

tests::usage_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled283–290 ↗
fn usage_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled()

Purpose: Checks that exact lookup still recognizes the usage command even when token activity is disabled for display. This allows the dispatcher to give a specific unavailable message instead of saying the command is unknown.

Data flow: It disables token activity in the flags, looks up usage, and expects the usage command to be returned. The test output is whether that expectation holds.

Call relations: This test covers the deliberate difference between popup filtering and dispatch lookup. It ensures find_builtin_command keeps token-activity lookup permissive enough for clearer error handling.

Call graph: 2 external calls (assert_eq!, all_enabled_flags).

tests::side_conversation_hides_commands_without_side_flag293–314 ↗
fn side_conversation_hides_commands_without_side_flag()

Purpose: Checks that a side conversation only shows commands that are marked safe for that context. This prevents the side conversation command popup from offering commands that belong to the main conversation.

Data flow: It creates flags with side conversation active, asks for visible built-in commands, extracts just the commands, and compares them with the expected short list. The test passes if only side-conversation-safe commands remain.

Call relations: The test directly calls builtins_for_input, the shared visibility filter. It protects the side-conversation branch of the command filtering rules.

Call graph: calls 1 internal fn (builtins_for_input); 2 external calls (assert_eq!, all_enabled_flags).

tests::side_conversation_exact_lookup_still_resolves_hidden_commands_for_dispatch_error317–328 ↗
fn side_conversation_exact_lookup_still_resolves_hidden_commands_for_dispatch_error()

Purpose: Checks that a command hidden from the side-conversation popup can still be recognized by exact lookup. This lets the app respond with a useful “not available here” message.

Data flow: It sets side conversation active, looks up review, and expects the review command to be recognized. The test does not mean the command is allowed there; it means the dispatcher can identify it.

Call relations: This test protects the intentional gap between what the popup shows and what exact lookup can identify. It supports better dispatch-time error messages.

Call graph: 1 external calls (assert_eq!).

tests::side_conversation_exact_lookup_still_resolves_service_tier_commands_for_dispatch_error331–346 ↗
fn side_conversation_exact_lookup_still_resolves_service_tier_commands_for_dispatch_error()

Purpose: Checks that service-tier commands are still found by exact lookup during a side conversation. As with hidden built-ins, this helps the dispatcher explain that the command is unavailable in that context.

Data flow: It creates one service-tier command named fast, sets side conversation active, and looks up fast. The expected result is that the service-tier command item is returned.

Call relations: The test uses the all-enabled helper and assertion-based lookup check. It protects find_slash_command so service-tier recognition remains separate from side-conversation display filtering.

Call graph: 2 external calls (assert_eq!, all_enabled_flags).

tui/src/bottom_pane/command_popup.rssource ↗
domain_logicmain loop, while editing the command composer

When a user starts typing a slash command, such as /model or /clear, the app needs to show a helpful menu instead of making the user remember every command. This file is that menu. It gathers the commands that are currently allowed, filters them based on what the user has typed, formats them as display rows, and renders them in the bottom pane.

The central type is CommandPopup. Think of it like a small autocomplete box: it stores the current typed filter, the list of possible commands, and a scroll position so keyboard navigation feels natural. Commands can come from two places: built-in app commands, or service-tier commands supplied from a catalog. The file also has CommandPopupFlags, which are feature switches that decide whether commands like collaboration, service tier, or personality options should be offered.

Filtering is intentionally simple and predictable. With an empty filter, the popup shows the normal command list but hides duplicate aliases such as /quit and /btw. Once the user types a prefix, aliases can appear if they match. Exact matches are listed before prefix matches. The popup also highlights the matching part of the command name and measures row height so long descriptions wrap without overflowing. Without this file, typing / would not produce the interactive command picker users rely on.

Function details29
BuiltinCommandFlags::from56–68 ↗
fn from(value: CommandPopupFlags) -> Self

Purpose: Converts popup-specific feature switches into the command system's built-in command switches. This keeps the popup's idea of which commands are available aligned with the rest of the slash-command code.

Data flow: It receives a CommandPopupFlags value containing booleans such as whether collaboration modes or connectors are enabled. It copies those choices into a BuiltinCommandFlags value, with one renamed meaning: degraded Windows sandbox mode becomes permission to show the sandbox-elevation command. The result is passed to command-list building code.

Call relations: This conversion is used during CommandPopup::new. The popup first translates its flags, then asks commands_for_input for the commands that should be visible under those conditions.

CommandPopup::new72–91 ↗
fn new(
        flags: CommandPopupFlags,
        service_tier_commands: Vec<ServiceTierCommand>,
    ) -> Self

Purpose: Creates a fresh command popup with the right set of commands for the current app state. It filters out commands that should not appear in the visible suggestion menu, such as debug commands and the apps command.

Data flow: It takes feature flags and a list of service-tier commands. It converts the flags, asks the slash-command catalog for available commands, turns each result into a CommandItem, skips hidden built-ins, and stores the finished list with an empty text filter and a new scroll state. The output is a ready-to-use CommandPopup.

Call relations: This is the starting point for nearly every popup use and for the tests. After construction, later calls such as on_composer_text_change, move_down, selected_item, and render_ref operate on the state created here.

Call graph: calls 2 internal fn (new, commands_for_input); called by 17 (command_popup, app_command_popup_snapshot, btw_hidden_in_empty_filter_but_shown_for_prefix, changing_filter_resets_selection_after_scrolling, debug_commands_are_hidden_from_popup, default_command_popup_items_snapshot, filter_includes_init_when_typing_prefix, filtered_commands_keep_presentation_order_for_prefix, model_is_first_suggestion_for_mo, personality_command_hidden_when_disabled (+7 more)); 2 external calls (new, into).

CommandPopup::on_composer_text_change97–127 ↗
fn on_composer_text_change(&mut self, text: String)

Purpose: Updates the popup when the text input changes. It extracts the command prefix after the leading slash and uses it to narrow the suggestion list.

Data flow: It receives the current composer text. It looks only at the first line, removes a leading / if present, takes the first non-space token as the filter, and compares it with the previous filter. If the filter changed, it resets scrolling and selection; then it checks the filtered list length, clamps the selected row to a valid position, and makes sure the selection is visible. The popup's internal filter and scroll state are changed.

Call relations: The composer calls this as the user types. It relies on filtered_items to know how many matches remain, then delegates selection safety to the scroll state through reset, clamp_selection, and ensure_visible.

Call graph: calls 4 internal fn (filtered_items, clamp_selection, ensure_visible, reset).

CommandPopup::calculate_required_height131–141 ↗
fn calculate_required_height(&self, width: u16) -> u16

Purpose: Figures out how tall the popup should be for a given terminal width. This matters because command descriptions can wrap onto multiple lines.

Data flow: It takes a width in terminal cells. It builds the current filtered rows, then asks the shared row-measuring helper to calculate how many lines are needed, respecting the maximum number of popup rows. It returns the preferred height as a number.

Call relations: Layout code can call this before rendering. It uses filtered and rows_from_matches to turn the current command list into display rows, then hands those rows to measure_rows_height_with_col_width_mode.

Call graph: calls 3 internal fn (filtered, rows_from_matches, measure_rows_height_with_col_width_mode).

CommandPopup::filtered146–194 ↗
fn filtered(&self) -> Vec<(CommandItem, Option<Vec<usize>>)>

Purpose: Builds the current list of matching commands, including information about which characters should be highlighted. It is the popup's main search routine.

Data flow: It reads the stored command filter and command list. If the filter is empty, it returns all normal commands while hiding alias commands like /quit and /btw. If the filter has text, it compares the filter case-insensitively against each command name, separates exact matches from prefix matches, and records the character positions that matched. It returns command items paired with optional highlight positions.

Call relations: This function feeds the rest of the popup. filtered_items uses it when only the commands are needed, while calculate_required_height and render_ref use it before turning matches into visible rows.

Call graph: called by 3 (calculate_required_height, filtered_items, render_ref); 2 external calls (new, matches!).

CommandPopup::filtered_items196–198 ↗
fn filtered_items(&self) -> Vec<CommandItem>

Purpose: Returns just the matching commands without the display highlighting details. It is a convenience helper for selection and tests.

Data flow: It calls filtered, discards the optional match-index data, and collects only the CommandItem values. The popup state is not changed.

Call relations: Navigation and selection code use this to know what can currently be chosen. It is called by move_up, move_down, on_composer_text_change, and selected_item.

Call graph: calls 1 internal fn (filtered); called by 4 (move_down, move_up, on_composer_text_change, selected_item).

CommandPopup::rows_from_matches200–222 ↗
fn rows_from_matches(
        &self,
        matches: Vec<(CommandItem, Option<Vec<usize>>)>,
    ) -> Vec<GenericDisplayRow>

Purpose: Turns matching command items into generic display rows that the shared popup renderer knows how to draw.

Data flow: It receives command matches and optional highlight indexes. For each match, it adds the leading slash to the command name, copies the command description, shifts highlight positions to account for the added slash, and fills a GenericDisplayRow. It returns the list of rows ready for measuring or drawing.

Call relations: Both sizing and rendering depend on this conversion. calculate_required_height uses the rows for measurement, and render_ref uses the same row shape for drawing.

Call graph: called by 2 (calculate_required_height, render_ref).

CommandPopup::move_up225–229 ↗
fn move_up(&mut self)

Purpose: Moves the highlighted selection one row upward, wrapping around if needed. This supports keyboard navigation in the popup.

Data flow: It checks how many filtered commands are currently available. It asks the scroll state to move the selection up with wraparound, then adjusts the scroll window so the selected row remains visible. It changes only the popup's selection and scroll state.

Call relations: This is called when the user presses the key for moving up. It uses filtered_items to understand the current list size and then relies on the scroll-state helper for the actual movement.

Call graph: calls 3 internal fn (filtered_items, ensure_visible, move_up_wrap).

CommandPopup::move_down232–237 ↗
fn move_down(&mut self)

Purpose: Moves the highlighted selection one row downward, wrapping around if needed. This is the matching keyboard navigation action for moving down through suggestions.

Data flow: It counts the currently filtered commands, tells the scroll state to move the selection down with wraparound, and then makes sure that row is inside the visible popup area. The popup's selection and scroll offset may change.

Call relations: This is called when the user presses the key for moving down. Like move_up, it uses filtered_items for the list length and delegates movement details to the scroll state.

Call graph: calls 3 internal fn (filtered_items, ensure_visible, move_down_wrap).

CommandPopup::selected_item240–245 ↗
fn selected_item(&self) -> Option<CommandItem>

Purpose: Returns the command that is currently highlighted, if there is one. The rest of the app can use this when the user confirms a suggestion.

Data flow: It builds the current filtered command list, reads the selected index from the scroll state, and looks up that command. If the index is missing or no longer valid, it returns nothing; otherwise it returns a cloned CommandItem.

Call relations: This is the handoff point from the popup to command execution. After typing and navigation have updated the popup, callers ask this function what command should be accepted.

Call graph: calls 1 internal fn (filtered_items).

CommandItem::command249–254 ↗
fn command(&self) -> &str

Purpose: Gets the command name shown and matched by the popup. It hides whether the command came from the built-in list or the service-tier catalog.

Data flow: It receives a CommandItem. For a built-in command, it returns the built-in command's name; for a service-tier command, it returns the catalog-provided name. It does not change anything.

Call relations: Filtering, row building, and tests use this whenever they need the plain command text. It provides a common interface over the two command sources.

CommandItem::description256–261 ↗
fn description(&self) -> &str

Purpose: Gets the help text that explains what a command does. This text is shown next to the command in the popup.

Data flow: It receives a CommandItem. For a built-in command, it returns that command's built-in description; for a service-tier command, it returns the description from the service-tier catalog. It does not change anything.

Call relations: The row-building code uses this to fill the description column. Tests also check it to make sure catalog commands display their supplied description.

CommandPopup::render_ref265–278 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the command popup into the terminal screen buffer. This is where the filtered command list becomes visible text.

Data flow: It receives a rectangular screen area and a mutable terminal buffer. It filters the commands, converts them into display rows, slightly indents the drawing area, and asks the shared row renderer to paint the rows, selection, wrapping, and the no matches message. The terminal buffer is modified with the popup contents.

Call relations: The terminal UI framework calls this through the WidgetRef interface when the popup should be shown. It hands the hard drawing work to render_rows_with_col_width_mode after preparing the rows.

Call graph: calls 4 internal fn (filtered, rows_from_matches, render_rows_with_col_width_mode, tlbr); 1 external calls (inset).

tests::filter_includes_init_when_typing_prefix287–304 ↗
fn filter_includes_init_when_typing_prefix()

Purpose: Checks that typing a prefix such as /in includes the /init command in the suggestions. This protects basic prefix search behavior.

Data flow: The test creates a default popup, feeds it composer text /in, reads the filtered items, and looks for the built-in command named init. It passes only if that command is present.

Call relations: It exercises CommandPopup::new, on_composer_text_change, and the filtered item path indirectly, confirming that the public popup flow exposes the expected command.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tests::selecting_init_by_exact_match307–321 ↗
fn selecting_init_by_exact_match()

Purpose: Checks that an exact typed command is selected by default. If the user types /init, the popup should point at init immediately.

Data flow: The test creates a default popup, updates it with /init, asks for the selected item, and verifies that it is the built-in init command. Any other command or no selection causes the test to fail.

Call relations: It covers the connection between filtering and selection: on_composer_text_change updates the filter and selection state, then selected_item reports the chosen command.

Call graph: calls 1 internal fn (new); 4 external calls (new, assert_eq!, panic!, default).

tests::model_is_first_suggestion_for_mo324–335 ↗
fn model_is_first_suggestion_for_mo()

Purpose: Checks that /model is the first suggestion when the user types /mo. This preserves the intended ordering for common command prefixes.

Data flow: The test creates a default popup, types /mo, reads the filtered items, and inspects the first result. It expects the first command name to be model.

Call relations: It protects the ordering produced by the popup's filtering code and the source command list created through CommandPopup::new.

Call graph: calls 1 internal fn (new); 4 external calls (new, assert_eq!, panic!, default).

tests::service_tier_command_uses_catalog_name_and_description338–368 ↗
fn service_tier_command_uses_catalog_name_and_description()

Purpose: Checks that service-tier commands appear using their catalog-provided name and help text. This matters because these commands are not hard-coded built-ins.

Data flow: The test creates a popup with service-tier commands enabled and supplies a catalog command named fast. After typing /fa, it checks that the selected item is that service-tier command and that the display row uses the supplied description. The popup state is only used inside the test.

Call relations: It exercises construction from service-tier input, filtering, selection, and row creation. It ensures CommandItem::command and CommandItem::description work for catalog commands.

Call graph: calls 1 internal fn (new); 4 external calls (assert_eq!, panic!, default, vec!).

tests::filtered_commands_keep_presentation_order_for_prefix371–392 ↗
fn filtered_commands_keep_presentation_order_for_prefix()

Purpose: Checks that prefix matches keep the intended display order instead of being rearranged alphabetically or unpredictably. This keeps the suggestion list familiar to users.

Data flow: The test creates a default popup, types /m, collects the matching command names, and compares them with the expected order: model, memories, mention, and mcp. The output is a pass or failure.

Call relations: It verifies the ordering behavior of filtered as seen through filtered_items, using the normal popup setup path.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert_eq!, default).

tests::app_command_popup_snapshot396–411 ↗
fn app_command_popup_snapshot()

Purpose: Records the rendered look of the popup for /app on platforms where the app command is relevant. Snapshot tests catch accidental visual changes.

Data flow: The test creates a popup, types /app, calculates the needed height, renders into a temporary buffer, and compares the buffer text with a stored snapshot. It does not interact with the real terminal.

Call relations: It ties together sizing and rendering: calculate_required_height decides the rectangle height, and render_ref draws the final output that the snapshot checks.

Call graph: calls 1 internal fn (new); 5 external calls (empty, new, new, assert_snapshot!, default).

tests::default_command_popup_items_snapshot415–431 ↗
fn default_command_popup_items_snapshot()

Purpose: Records the default list of visible commands when the user has typed only /. This protects the curated command menu from accidental changes.

Data flow: The test creates a default popup, updates it with /, turns each filtered command into a /name - description line, and compares the full text with a stored snapshot. It produces no app state changes outside the test.

Call relations: It checks CommandPopup::new, empty-filter behavior, alias hiding, and command descriptions as presented by the popup.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert_snapshot!, default).

tests::prefix_filter_limits_matches_for_ac434–450 ↗
fn prefix_filter_limits_matches_for_ac()

Purpose: Checks that filtering is prefix-based, not fuzzy substring search. For example, /ac should not match compact just because it contains those letters later.

Data flow: The test creates a popup, types /ac, collects matching names, and asserts that compact is absent. The result is a simple pass or failure.

Call relations: It protects a specific rule inside filtered: matches must be exact or start with the typed text.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tests::changing_filter_resets_selection_after_scrolling453–482 ↗
fn changing_filter_resets_selection_after_scrolling()

Purpose: Checks that changing the typed filter resets a previously scrolled popup to a sensible position. This prevents the user from being left on an invisible or stale selection.

Data flow: The test opens the full list, moves down enough times to scroll, confirms scrolling happened, then changes the input to /st. It expects /status to be selected, the scroll position to return to the top, and the rendered buffer to match a snapshot.

Call relations: It exercises the interaction between move_down, on_composer_text_change, selected_item, calculate_required_height, and render_ref. It specifically protects the reset behavior when the filter changes.

Call graph: calls 1 internal fn (new); 7 external calls (empty, new, new, assert!, assert_eq!, assert_snapshot!, default).

tests::quit_hidden_in_empty_filter_but_shown_for_prefix485–494 ↗
fn quit_hidden_in_empty_filter_but_shown_for_prefix()

Purpose: Checks that the /quit alias is hidden in the default list but still searchable when the user starts typing it. This avoids duplicate menu entries without making aliases unusable.

Data flow: The test first types / and confirms quit is not in the filtered items. Then it types /qu and confirms quit appears. The popup state changes only through those input updates.

Call relations: It verifies the alias rule in filtered: aliases are skipped only for an empty filter, not for a matching typed prefix.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tests::btw_hidden_in_empty_filter_but_shown_for_prefix497–506 ↗
fn btw_hidden_in_empty_filter_but_shown_for_prefix()

Purpose: Checks the same alias behavior for /btw. The default menu should stay uncluttered, but users who know the alias can still find it.

Data flow: The test types /, checks that btw is absent, then types /bt and checks that btw is present. It reads the popup's filtered items after each change.

Call relations: It protects the alias handling controlled by the ALIAS_COMMANDS list and implemented in filtered.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tests::plan_command_hidden_when_collaboration_modes_disabled509–525 ↗
fn plan_command_hidden_when_collaboration_modes_disabled()

Purpose: Checks that /plan is not offered when collaboration modes are turned off. This prevents users from seeing a command that is not available in their current app mode.

Data flow: The test creates a popup with default flags, types /, collects all visible command names, and asserts that plan is not among them. The result confirms the feature flag affects the command list.

Call relations: It tests the path from CommandPopupFlags through BuiltinCommandFlags::from and commands_for_input into the popup's stored command list.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tests::plan_command_visible_when_collaboration_modes_enabled528–552 ↗
fn plan_command_visible_when_collaboration_modes_enabled()

Purpose: Checks that /plan becomes available when collaboration modes are enabled. This is the positive case for the feature flag.

Data flow: The test creates a popup with collaboration enabled, types /plan, asks for the selected item, and expects the built-in command named plan. Other results fail the test.

Call relations: It confirms that popup construction honors enabled flags and that exact-match filtering selects the newly available command.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert_eq!, panic!).

tests::personality_command_hidden_when_disabled555–584 ↗
fn personality_command_hidden_when_disabled()

Purpose: Checks that /personality is hidden when its feature flag is off. This keeps unavailable experimental or optional commands out of the user's suggestions.

Data flow: The test creates a popup with collaboration enabled but personality disabled, types /pers, collects matching command names, and asserts that personality is missing. It only reads the popup's filtered output.

Call relations: It verifies that the flags passed into CommandPopup::new affect commands_for_input and therefore the filter results.

Call graph: calls 1 internal fn (new); 2 external calls (new, assert!).

tests::personality_command_visible_when_enabled587–611 ↗
fn personality_command_visible_when_enabled()

Purpose: Checks that /personality appears and is selected when its feature flag is on. This is the positive case paired with the hidden-when-disabled test.

Data flow: The test creates a popup with personality enabled, types /personality, and asks for the selected item. It expects the built-in command named personality; anything else fails.

Call relations: It tests the full flow from feature flag setup to command availability, exact filtering, and selected-command reporting.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert_eq!, panic!).

tests::debug_commands_are_hidden_from_popup614–629 ↗
fn debug_commands_are_hidden_from_popup()

Purpose: Checks that commands whose names start with debug do not appear in the popup. This keeps internal or developer-only commands out of the normal user menu.

Data flow: The test creates a default popup, reads all filtered command names, and asserts that none starts with debug. It does not type a filter because it is checking the initial command list built by the popup.

Call relations: It protects the filtering done in CommandPopup::new, where debug built-ins are removed before the popup stores its command list.

Call graph: calls 1 internal fn (new); 3 external calls (new, assert!, default).

tui/src/bottom_pane/chat_composer/slash_input.rssource ↗
domain_logicrequest handling

The chat composer is not just a plain text box. When a message starts with /, the app may treat it as a command instead of normal chat. This file is the layer that decides which is which. It checks whether slash commands are enabled, whether the composer is in shell mode, whether the typed command name is known, and whether the command should run immediately or accept extra words as arguments.

It also keeps the suggestion popup in sync with the text under the cursor. For example, if the user types /re, this file can open a list of matching commands, move the highlighted item with arrow keys, complete it with Tab, or run it with Enter. A useful detail is that some commands support inline arguments. For those, completing /re in front of existing text like view the diff can turn the draft into /review view the diff instead of deleting the draft.

The file also marks completed slash command text inside the editor as a special text element. Think of this like highlighting a ticket number in a document: the text is still there, but the editor knows it has special meaning. The tests at the bottom protect the most delicate completion behavior.

Function details26
SlashInput::new54–66 ↗
fn new(
        enabled: bool,
        is_bash_mode: bool,
        command_flags: BuiltinCommandFlags,
        service_tier_commands: &'a [ServiceTierCommand],
    ) -> Self

Purpose: Creates a small helper object that knows the current slash-command settings. Other composer code uses this object whenever it needs to parse or complete slash commands.

Data flow: It receives whether commands are enabled, whether shell mode is active, feature flags for built-in commands, and the available service-tier commands. It stores those pieces together and returns a ready-to-use SlashInput.

Call relations: The composer-side helper that builds slash input calls this when it needs a fresh view of the current command settings. The returned object is then used by the parsing, validation, popup, and highlighting paths in this file.

Call graph: called by 1 (slash_input).

SlashInput::validate_submission68–87 ↗
fn validate_submission(
        &self,
        text: &str,
        input_starts_with_space: bool,
    ) -> SubmissionValidation

Purpose: Checks a message right before submission to catch a likely mistyped slash command. This prevents /revieew from being sent as normal chat when the user probably meant a command.

Data flow: It reads the submitted text and whether the original input began with a space. If slash commands are disabled, the text does not begin like a command, the user intentionally started with a space, or the name contains another slash, it treats the message as ordinary valid input. Otherwise it looks up the command name and returns either valid or an unknown-command result with the typed name.

Call relations: Submission code asks this before accepting command-looking input. Internally it uses parse_slash_name to split the leading command name from the rest, then asks SlashInput::command whether that name is known.

Call graph: calls 2 internal fn (command, parse_slash_name); 1 external calls (UnknownCommand).

SlashInput::bare_command89–105 ↗
fn bare_command(&self, text: &str) -> Option<SlashCommandItem>

Purpose: Detects when the whole input is just a slash command with no arguments. This is used for commands that should run as commands rather than be sent as chat text.

Data flow: It reads the current text, looks only at the first line, and extracts a leading slash command name. If slash commands are disabled, shell mode is active, there is extra text on the first line, or the command is unknown, it returns nothing. If the text is a clean command by itself, it returns the matching command item.

Call relations: Higher-level submission logic can call this to decide whether the draft is simply a command. It depends on parse_slash_name to read the prefix and SlashInput::command to find the command definition.

Call graph: calls 2 internal fn (command, parse_slash_name).

SlashInput::inline_command107–123 ↗
fn inline_command(&self, text: &'text str) -> Option<InlineCommand<'text>>

Purpose: Detects a slash command followed by inline arguments, such as /review view the diff. It only accepts commands that explicitly support this style.

Data flow: It reads the full text. If commands are disabled, shell mode is active, the text starts with a space, the command name is malformed, or there is no argument text, it returns nothing. If the command exists and supports inline arguments, it returns the command plus the remaining argument text and where that argument text begins.

Call relations: Submission preparation can use this when it wants to turn a typed command-with-arguments into a command action. It parses the name with parse_slash_name and verifies the command through SlashInput::command.

Call graph: calls 2 internal fn (command, parse_slash_name).

SlashInput::should_parse_on_dequeue125–127 ↗
fn should_parse_on_dequeue(&self, text: &str) -> bool

Purpose: Decides whether queued text should be re-checked for a slash command later. This matters when input is held while another task is running.

Data flow: It reads the prepared text and the enabled flag. If slash commands are enabled, the text does not intentionally start with a space, and the trimmed text starts with /, it returns true. Otherwise it returns false.

Call relations: Queue handling uses this idea when deciding whether delayed input should later become a slash command instead of plain text.

SlashInput::command_element_range129–155 ↗
fn command_element_range(
        &self,
        first_line: &str,
        cursor: usize,
    ) -> Option<Range<usize>>

Purpose: Finds the byte range of a completed slash command at the start of the first line so the editor can mark it as a special element. It avoids marking a command while the user is still editing its name.

Data flow: It receives the first line and the cursor position. It parses the slash name, rejects shell mode and names containing extra slashes, checks that a space follows the command name, and confirms the command exists. If everything fits, it returns the range covering the slash and command name; otherwise it returns nothing.

Call relations: The composer calls this from slash-command element synchronization. It uses parse_slash_name to understand the first line and SlashInput::command to confirm that the marked prefix is really a command.

Call graph: calls 2 internal fn (command, parse_slash_name).

SlashInput::is_editing_command_name157–169 ↗
fn is_editing_command_name(&self, first_line: &str, cursor: usize) -> bool

Purpose: Answers whether the cursor is currently inside a slash command name that should show command suggestions. This keeps the popup focused on command-name editing rather than normal message text.

Data flow: It reads the first line and cursor position. It asks command_under_cursor for the command fragment around the cursor. If commands are disabled it returns false. For an empty name after /, it only returns true when there is no extra text; otherwise it checks whether any known command begins with the typed fragment.

Call relations: Popup synchronization can use this to decide whether to show or keep a slash-command popup. It relies on command_under_cursor for cursor-aware slicing and has_slash_command_prefix for matching available commands.

Call graph: calls 2 internal fn (command_under_cursor, has_slash_command_prefix).

SlashInput::command_popup171–188 ↗
fn command_popup(&self, filter_text: &str) -> CommandPopup

Purpose: Builds the suggestion popup for slash commands and applies the current filter text. This is what turns typed text like /mo into a narrowed command list.

Data flow: It reads command feature flags, service-tier command definitions, and the filter text. It creates a CommandPopup with the correct enabled or disabled command categories, feeds the filter text into it, and returns the popup.

Call relations: Composer popup code calls this when a slash-command popup is needed. It hands command availability into CommandPopup and lets that popup do its own filtering and selection work.

Call graph: calls 1 internal fn (new); 1 external calls (to_vec).

SlashInput::command190–192 ↗
fn command(&self, name: &str) -> Option<SlashCommandItem>

Purpose: Looks up one slash command name in the currently available command set. It is the shared shortcut used by the rest of this file whenever it needs to know whether a command exists.

Data flow: It receives a command name, combines that with stored feature flags and service-tier commands, and returns the matching command item if one is available.

Call relations: Validation, bare-command detection, inline-command detection, and command-element marking all call this. It delegates the actual search to find_slash_command so the command list logic stays in one place.

Call graph: calls 1 internal fn (find_slash_command); called by 4 (bare_command, command_element_range, inline_command, validate_submission).

queued_input_action195–206 ↗
fn queued_input_action(
    prepared_text: &str,
    defer_slash_validation: bool,
) -> QueuedInputAction

Purpose: Classifies prepared input that is about to be queued as a slash command, shell command, or plain message. This lets delayed input keep its intended meaning.

Data flow: It reads the prepared text and whether slash validation should be deferred. Text starting with / becomes a delayed slash parse only when deferral is requested. Text starting with ! becomes a shell action. Everything else becomes plain input.

Call relations: The timed submission path calls this while deciding how to store input that cannot run immediately. Its result tells later code whether to parse slash syntax, run a shell command, or submit normal text.

Call graph: called by 1 (handle_submission_with_time).

ChatComposer::handle_key_event_with_slash_popup210–380 ↗
fn handle_key_event_with_slash_popup(
        &mut self,
        key_event: KeyEvent,
    ) -> (InputResult, bool)

Purpose: Processes keyboard input while the slash-command suggestion popup is open. It lets the user move through suggestions, complete a command, dismiss the popup, or run the selected command.

Data flow: It receives a key event and reads the current draft, cursor, popup selection, footer mode, and task-running state. Arrow keys and Control-P/Control-N move the selection. Escape dismisses or changes the footer hint. Tab or / completes the selected command when possible. Enter runs the selected command or falls back to normal input behavior. It returns an InputResult plus a flag saying the key was consumed.

Call relations: The main composer key handler calls this only when the active popup is a command popup. During completion it asks command_popup_filter_text for the latest filter, uses selected_command_completion or the inline-argument preserving helper to rewrite the draft, and may return command results for the rest of the app to execute.

Call graph: calls 6 internal fn (complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args, command_popup_filter_text, selected_command_completion, selected_command_dispatches_immediately_on_tab, esc_hint_mode, reset_mode_after_activity); 3 external calls (Command, ServiceTierCommand, unreachable!).

ChatComposer::complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args382–445 ↗
fn complete_selected_slash_command_preserving_existing_draft_tail_as_inline_args(
        &mut self,
        selected_cmd: &CommandItem,
    ) -> bool

Purpose: Completes a selected built-in slash command without throwing away useful text already after the cursor. This is the careful behavior that turns a partial command plus draft text into a command with arguments.

Data flow: It receives the selected command and reads the full draft, first-line boundary, cursor, and marked text elements. If the selected item is not a built-in command with inline-argument support, or the cursor is not in a valid command prefix, it returns false. Otherwise it replaces the partial command prefix with the full command, keeps the tail text as arguments, removes any text-element marking that would now cross the replacement boundary, moves the cursor to the end, exits shell mode, and returns true.

Call relations: The slash-popup key handler calls this before using simpler completion or dispatch behavior. When it succeeds, the caller can either leave the completed text in the composer or immediately try to dispatch the command with its arguments.

Call graph: called by 1 (handle_key_event_with_slash_popup); 1 external calls (format!).

ChatComposer::sync_slash_command_elements448–485 ↗
fn sync_slash_command_elements(&mut self)

Purpose: Keeps the editor’s special slash-command marking correct as the user edits the first line. It removes stale markings and adds the one marking that matches the current command, if any.

Data flow: It reads the draft text, first line, cursor, and existing text elements. It asks SlashInput for the desired command range, scans existing slash-looking elements, removes any that no longer match, and adds the desired range if it is missing.

Call relations: Composer synchronization code calls this as text changes. It connects SlashInput::command_element_range with the textarea’s element system so completed command prefixes stay visually and semantically aligned with the actual text.

Call graph: 1 external calls (new).

selected_command_dispatches_immediately_on_tab488–490 ↗
fn selected_command_dispatches_immediately_on_tab(command: &CommandItem) -> bool

Purpose: Identifies commands that should run as soon as the user presses Tab on them, instead of merely completing text. At present this special case is for the Skills command.

Data flow: It receives a selected command item and checks whether it is the built-in Skills command. It returns true for that case and false for all others.

Call relations: The slash-popup key handler asks this during Tab handling. If it returns true, the handler clears the draft and returns a command result immediately.

Call graph: called by 1 (handle_key_event_with_slash_popup); 1 external calls (matches!).

selected_command_completion492–499 ↗
fn selected_command_completion(
    first_line: &str,
    command: &CommandItem,
) -> Option<String>

Purpose: Builds the text that should appear in the composer when a selected command is completed normally. It avoids rewriting text that already starts with that exact command.

Data flow: It receives the current first line and selected command. It forms the full slash command text, such as /model, and checks whether the trimmed first line already begins with it. If not, it returns the completed command followed by a space; otherwise it returns nothing.

Call relations: The slash-popup key handler uses this after the more careful inline-argument completion path fails or does not apply. It provides the simple replacement text for ordinary command completion.

Call graph: called by 1 (handle_key_event_with_slash_popup); 1 external calls (format!).

prepared_args501–504 ↗
fn prepared_args(prepared_text: &str) -> Option<(&str, usize)>

Purpose: Extracts the argument part from already prepared slash-command text. This gives later code both the argument string and where it began in the original text.

Data flow: It receives prepared text, parses the slash command name and rest, and returns the rest text plus its byte offset. If the text does not start like a slash command, it returns nothing.

Call relations: Inline-argument submission preparation calls this before converting a command draft into an executable command-with-arguments. It uses parse_slash_name to do the shared command splitting.

Call graph: calls 1 internal fn (parse_slash_name); called by 1 (prepare_inline_args_submission).

args_elements509–532 ↗
fn args_elements(
    rest: &str,
    rest_offset: usize,
    text_elements: &[TextElement],
) -> Vec<TextElement>

Purpose: Moves special text-element ranges from the full input into the argument-only view. This preserves attachments or marked spans when only the command arguments are passed onward.

Data flow: It receives the argument text, the byte offset where those arguments began in the full input, and the full list of text elements. It ignores elements before the arguments, shifts overlapping ranges so they start at zero relative to the argument text, clips them to the argument length, and returns the adjusted elements.

Call relations: Both inline-argument preparation and command dispatch with arguments call this. It bridges the composer’s full-text representation with the command system’s argument-only representation.

Call graph: called by 2 (prepare_inline_args_submission, try_dispatch_slash_command_with_args); 3 external calls (new, is_empty, iter).

command_popup_filter_text534–537 ↗
fn command_popup_filter_text(first_line: &str, cursor: usize) -> Option<String>

Purpose: Creates the filter string for the command popup based on what command name fragment the cursor is inside. This lets suggestions follow the cursor rather than blindly using the whole line.

Data flow: It receives the first line and cursor position. If command_under_cursor finds a slash command fragment, it adds the leading slash back and returns it as filter text. If the cursor is not in a command name, it returns nothing.

Call relations: The slash-popup key handler calls this before applying completion so the popup selection reflects the latest typed fragment. It relies on command_under_cursor for the cursor-sensitive parsing.

Call graph: calls 1 internal fn (command_under_cursor); called by 1 (handle_key_event_with_slash_popup); 1 external calls (format!).

command_under_cursor541–568 ↗
fn command_under_cursor(first_line: &str, cursor: usize) -> Option<(&str, &str)>

Purpose: Finds the slash command name fragment that the cursor is currently editing on the first line. It is a small parsing helper for popup filtering and edit detection.

Data flow: It receives the first line and cursor position. It first requires the line to start with /, the cursor to be within the string, and the cursor to sit on a valid character boundary. It then finds the command-name end at the first whitespace, treats a cursor directly after / as if it were at the name end, and returns the name before the cursor plus the rest after it. If the cursor is outside the command name, it returns nothing.

Call relations: SlashInput::is_editing_command_name and command_popup_filter_text both call this when they need to understand what the cursor is touching. It keeps the cursor parsing rules consistent between popup display and popup filtering.

Call graph: called by 2 (is_editing_command_name, command_popup_filter_text).

tests::test_composer578–587 ↗
fn test_composer() -> ChatComposer

Purpose: Creates a basic ChatComposer for tests. This avoids repeating setup code in each test case.

Data flow: It creates an internal event channel, builds an AppEventSender from it, and constructs a ChatComposer with ordinary test settings and placeholder text. The returned composer is ready for simulated typing.

Call relations: The test helper that places text at a cursor calls this first. It gives the completion tests a real composer object without needing the full application.

Call graph: calls 2 internal fn (new, new).

tests::press589–593 ↗
fn press(composer: &mut ChatComposer, code: KeyCode) -> InputResult

Purpose: Simulates pressing one keyboard key in a test and returns the composer’s result. This makes the tests read like user actions.

Data flow: It receives a mutable composer and a key code. It wraps the code in a KeyEvent with no modifiers, sends it through the composer’s normal key handler, and returns the InputResult part of the response.

Call relations: The slash-completion tests call this to press Tab or Enter. It routes the test through the same key handling path used during real interaction.

Call graph: calls 1 internal fn (handle_key_event); 1 external calls (new).

tests::composer_with_text_at_cursor595–601 ↗
fn composer_with_text_at_cursor(text: &str, cursor: usize) -> ChatComposer

Purpose: Builds a test composer containing specific text with the cursor at a specific byte position. This lets tests start from precise editing situations.

Data flow: It receives text and a cursor position. It creates a test composer, replaces the textarea content, sets the cursor, syncs popups, and returns the prepared composer.

Call relations: Several tests use this directly, and another helper builds on it for prefix-plus-tail drafts. It sets up the exact state needed before simulated key presses.

Call graph: 1 external calls (test_composer).

tests::composer_with_draft_tail603–605 ↗
fn composer_with_draft_tail(prefix: &str, draft: &str) -> ChatComposer

Purpose: Builds a test composer where the cursor sits between a partial slash command and existing draft text. This models the important case where completion should preserve the tail as arguments.

Data flow: It receives a prefix and draft tail, joins them into one text string, and sets the cursor at the end of the prefix. It returns a composer prepared with that state.

Call relations: The completion tests call this when they need to check what happens to text after a partial command. It delegates the actual composer setup to composer_with_text_at_cursor.

Call graph: 2 external calls (format!, composer_with_text_at_cursor).

tests::slash_completion_preserves_existing_draft_tail_for_inline_arg_commands608–623 ↗
fn slash_completion_preserves_existing_draft_tail_for_inline_arg_commands()

Purpose: Verifies that commands supporting inline arguments keep existing draft text when completed. This protects the user-friendly /re plus draft-tail behavior.

Data flow: It creates composers with /re before view the diff, presses Tab in one case and Enter in another, and checks the resulting text, cursor position, and command result. The expected outcome is /review view the diff, with Enter producing a command-with-arguments result.

Call relations: This test drives the real key handling through the press helper. It specifically exercises the inline-argument preserving completion path inside the slash-popup handler.

Call graph: 2 external calls (assert_eq!, composer_with_draft_tail).

tests::slash_completion_does_not_preserve_existing_draft_tail_for_other_commands626–635 ↗
fn slash_completion_does_not_preserve_existing_draft_tail_for_other_commands()

Purpose: Verifies that ordinary commands do not accidentally treat following draft text as arguments. This keeps inline-argument preservation limited to commands that opted into it.

Data flow: It creates a composer with /mo before extra draft text, presses Tab, and checks that the composer becomes /model with the cursor at the end. The original tail text is not preserved.

Call relations: This test uses the same simulated key path as real completion. It confirms that the preserving helper declines non-inline-argument commands and the normal completion path takes over.

Call graph: 2 external calls (assert_eq!, composer_with_draft_tail).

tests::slash_completion_does_not_turn_command_suffix_into_args638–649 ↗
fn slash_completion_does_not_turn_command_suffix_into_args()

Purpose: Verifies that completing inside a partially typed full command does not mistake the remaining letters for arguments. This prevents /review with the cursor after /re from becoming /review view or similar nonsense.

Data flow: It creates composers containing /review with the cursor after /re. Pressing Tab should complete to /review , while pressing Enter should dispatch the Review command without arguments and clear the input. The test checks both outcomes.

Call relations: This test targets a subtle edge case in the inline-argument preserving logic. It uses composer_with_text_at_cursor and press to run through the real popup completion and dispatch behavior.

Call graph: 3 external calls (assert!, assert_eq!, composer_with_text_at_cursor).

tui/src/bottom_pane/file_search_popup.rssource ↗
domain_logicinteractive UI input and render loop

This popup is the file-search chooser for the terminal UI. When a user types a file search query, results may arrive a moment later, so the popup keeps two versions of the query: the latest text the user typed, and the query that produced the results currently shown. That prevents old search results from replacing newer ones if they arrive late, like ignoring an outdated delivery after you already changed your order.

The main state is stored in FileSearchPopup: the pending query, whether it is still waiting, the visible file matches, and a shared scroll/selection state. When the query changes, the popup marks itself as waiting. When results arrive, it only accepts them if they match the newest query. It also keeps only the first page of results, because the popup is designed to stay small.

The file also turns search results into display rows and hands them to shared popup rendering code. If there are no rows, it shows either “loading...” or “no matches”, depending on whether a search is still in progress. Navigation wraps around the list and keeps the selected row visible.

Function details11
FileSearchPopup::new32–40 ↗
fn new() -> Self

Purpose: Creates a fresh file-search popup with no query, no results, and a new scroll/selection state. It starts in a waiting state so the UI can show that results are expected once searching begins.

Data flow: It takes no input. It builds empty strings for the shown and pending queries, an empty match list, and a new ScrollState, which is the object that remembers the selected row and scroll position. It returns a ready-to-use FileSearchPopup.

Call relations: The wider UI creates this through sync_file_search_popup when it needs a popup state to match the current file-search interaction. The test also creates one before checking how result lists are trimmed.

Call graph: calls 1 internal fn (new); called by 2 (sync_file_search_popup, set_matches_keeps_only_the_first_page_of_results); 2 external calls (new, new).

FileSearchPopup::set_query43–52 ↗
fn set_query(&mut self, query: &str)

Purpose: Records that the user has typed a new search query. It marks the popup as waiting because the results for that new text have not arrived yet.

Data flow: It receives a query string. If the text is the same as the current pending query, it leaves everything alone. Otherwise it replaces the pending query with the new text and sets waiting to true, while keeping the old visible results until new ones arrive.

Call relations: This is meant to be called when the search text changes. It does not call other project helpers; it simply prepares the popup so that a later FileSearchPopup::set_matches call can decide whether incoming results are still current.

FileSearchPopup::set_empty_prompt56–63 ↗
fn set_empty_prompt(&mut self)

Purpose: Puts the popup into the special empty-search state used when the user has only opened file search but has not typed a real query yet. Instead of showing stale results, it clears the list and resets the selection.

Data flow: It takes no input. It clears both query strings, turns off the waiting flag, removes all matches, and calls reset on the scroll state so no old highlight or scroll position remains. The popup is left ready to show an empty prompt or message.

Call relations: This function is used when the UI wants the popup to stop behaving like an active search result list. It hands the selection cleanup to ScrollState::reset, because that shared helper owns the cursor and scroll bookkeeping.

Call graph: calls 1 internal fn (reset).

FileSearchPopup::set_matches67–78 ↗
fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>)

Purpose: Accepts a batch of file-search results and makes them visible, but only if they belong to the latest query. This protects the user from seeing outdated results after typing ahead quickly.

Data flow: It receives the query text that produced the results and a list of FileMatch values. If that query is not the current pending query, it discards the results as stale. If it matches, it stores the query as the displayed query, keeps only up to MAX_POPUP_ROWS matches, turns off the waiting flag, clamps the selected row so it is still valid, and scrolls enough to keep that row visible.

Call relations: This is the receiving point for completed searches. After storing the accepted results, it asks ScrollState::clamp_selection and ScrollState::ensure_visible to repair the cursor and scrolling. The test tests::set_matches_keeps_only_the_first_page_of_results verifies the trimming behavior.

Call graph: calls 2 internal fn (clamp_selection, ensure_visible).

FileSearchPopup::move_up81–85 ↗
fn move_up(&mut self)

Purpose: Moves the highlighted file choice one row upward. If the movement changes what should be visible, it adjusts the scroll position too.

Data flow: It reads the number of current matches. It asks the scroll state to move the selection upward with wraparound, meaning moving above the first item can jump to the bottom. Then it asks the scroll state to keep the selected item inside the visible page. It returns nothing, but changes the popup’s selection and possibly its scroll offset.

Call relations: This is meant to be called from keyboard navigation. It delegates the actual cursor rules to ScrollState::move_up_wrap and the viewport correction to ScrollState::ensure_visible, so this popup does not duplicate shared list-navigation code.

Call graph: calls 2 internal fn (ensure_visible, move_up_wrap).

FileSearchPopup::move_down88–92 ↗
fn move_down(&mut self)

Purpose: Moves the highlighted file choice one row downward. It supports wraparound navigation and keeps the selected item visible.

Data flow: It reads the current match count. It asks the scroll state to move the selection down with wraparound, then ensures the selected row is within the visible popup area. It returns nothing, but updates selection and scrolling inside the popup state.

Call relations: This is the downward counterpart to FileSearchPopup::move_up, intended for keyboard navigation. It relies on ScrollState::move_down_wrap and ScrollState::ensure_visible for the shared list behavior.

Call graph: calls 2 internal fn (ensure_visible, move_down_wrap).

FileSearchPopup::selected_match94–99 ↗
fn selected_match(&self) -> Option<&PathBuf>

Purpose: Returns the path for the currently highlighted file, if there is one. Callers use this when the user confirms a choice.

Data flow: It reads the selected index from the scroll state. If an index exists and there is a matching result at that position, it returns a reference to that file’s path. If nothing is selected or the index is not valid, it returns None, meaning there is no usable choice.

Call relations: This function is the bridge from UI selection to action: after navigation has set the highlighted row, another part of the interface can ask this popup what file should be inserted or opened. It does not call other helpers; it only reads existing popup state.

FileSearchPopup::calculate_required_height101–109 ↗
fn calculate_required_height(&self) -> u16

Purpose: Calculates how tall the popup should be. It keeps the popup visible even when there are no results, and caps the height so the file list does not grow too large.

Data flow: It reads how many matches are currently stored. It clamps that count between 1 and MAX_POPUP_ROWS, then returns it as a terminal row count. With zero matches, the answer is still one row so the UI can show “loading...” or “no matches”.

Call relations: Layout code can use this before rendering to reserve the right amount of screen space. It works from the same match list that FileSearchPopup::set_matches fills and that FileSearchPopup::render_ref later draws.

FileSearchPopup::render_ref113–153 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the popup into the terminal screen buffer. It converts file-search results into the shared row format used by selection popups, then asks common rendering code to paint them.

Data flow: It receives a screen rectangle and a mutable buffer, which is the terminal drawing surface. It turns each FileMatch into a GenericDisplayRow, including the file path text and optional highlighted match positions. If there are no rows, it chooses “loading...” while waiting or “no matches” when idle. It indents the drawing area slightly and calls render_rows, which writes the visible popup contents into the buffer.

Call relations: This function is called by the terminal UI rendering system through the WidgetRef trait, which is a standard way for ratatui widgets to draw themselves. It hands final painting to the shared render_rows helper, using Insets::tlbr and rectangle inset logic to position the list neatly.

Call graph: calls 2 internal fn (render_rows, tlbr); 2 external calls (inset, new).

tests::file_match162–170 ↗
fn file_match(index: usize) -> FileMatch

Purpose: Builds a predictable fake file-search result for tests. It keeps test setup short and makes expected results easy to compare.

Data flow: It receives a number. It uses that number as the score and embeds it in a path like src/file_00.rs, then fills in the other FileMatch fields with fixed test values. It returns the constructed FileMatch.

Call relations: The test tests::set_matches_keeps_only_the_first_page_of_results calls this helper to create more results than the popup should keep. It uses standard path and string formatting helpers to make realistic-looking file paths.

Call graph: 2 external calls (from, format!).

tests::set_matches_keeps_only_the_first_page_of_results173–183 ↗
fn set_matches_keeps_only_the_first_page_of_results()

Purpose: Checks that the popup stores only the first page of file-search results. This protects the UI rule that the popup should stay bounded by MAX_POPUP_ROWS.

Data flow: It creates a new popup, sets the query to file, and gives it more fake matches than the maximum visible row count. Then it compares the stored matches with exactly the first MAX_POPUP_ROWS fake results, and also checks that the calculated height equals the maximum popup height.

Call relations: This test exercises FileSearchPopup::new, FileSearchPopup::set_query, FileSearchPopup::set_matches, and FileSearchPopup::calculate_required_height. It uses tests::file_match to generate the input data and assert_eq! to verify the popup’s stored state.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tui/src/bottom_pane/skill_popup.rssource ↗
domain_logicwhile editing input and rendering the mention popup

This file is the user interface logic for a searchable mention picker. Think of it like an autocomplete menu: the app has a list of possible skills or plugins, the user types part of a name, and the popup shows the best matches. Without this file, the bottom pane could not show a useful skill picker, keep the highlighted row in bounds, or tell the rest of the app which item the user chose.

The main data item is MentionItem. It stores what the user sees, such as a display name and description, and what the app should insert if that item is selected. SkillPopup keeps the current search text, the full mention list, and a ScrollState, which remembers which row is selected and which part of a long list is visible.

Filtering uses fuzzy matching, which means the typed text can match a name even if it is only a partial or loose match. Matches in the visible display name are preferred over matches in hidden search terms, because those are easier for users to understand. Results are then sorted by match quality, rank, and name.

Rendering turns matches into simple display rows, truncates long names, draws only the visible window of rows, and adds a hint line such as “Press Enter to insert or Esc to close.” The tests protect the important behavior: long result lists are not lost, scrolling changes the visible window, and search sorting stays intuitive.

Function details22
SkillPopup::new40–46 ↗
fn new(mentions: Vec<MentionItem>) -> Self

Purpose: Creates a fresh popup from a list of possible mention items. It starts with an empty search query and a new scroll state so the popup is ready to show from the top.

Data flow: It receives a list of MentionItem values. It stores that list, creates an empty query string, creates a new ScrollState, and returns a SkillPopup ready for filtering, selection, and drawing.

Call relations: The app’s mention-popup setup code calls this when it needs a new picker. The tests also call it to build popups with controlled data before checking filtering, sorting, height, and scrolling behavior.

Call graph: calls 1 internal fn (new); called by 5 (sync_mention_popup, display_name_match_sorting_beats_worse_secondary_search_term_matches, filtered_mentions_preserve_results_beyond_popup_height, query_match_score_sorts_before_plugin_rank_bias, scrolling_mentions_shifts_rendered_window_snapshot); 1 external calls (new).

SkillPopup::set_mentions48–51 ↗
fn set_mentions(&mut self, mentions: Vec<MentionItem>)

Purpose: Replaces the popup’s available mention list. This is useful when the app discovers or refreshes the skills and plugins that can be inserted.

Data flow: It receives a new list of MentionItem values, stores it in the popup, then checks whether the current selection still makes sense. If the old selected row is now outside the filtered list, it moves the selection back into a valid place.

Call relations: After changing the list, it immediately relies on SkillPopup::clamp_selection so the scroll and highlighted row do not point at an item that no longer exists.

Call graph: calls 1 internal fn (clamp_selection).

SkillPopup::set_query53–56 ↗
fn set_query(&mut self, query: &str)

Purpose: Updates the search text used to filter the popup. It keeps the selected row valid after the results change.

Data flow: It receives the new query as text, copies it into the popup, recomputes what the valid result range looks like indirectly, and adjusts selection and scrolling if needed.

Call relations: This is called when the user’s typed filter changes. It hands control to SkillPopup::clamp_selection because a new query can shrink or reorder the result list.

Call graph: calls 1 internal fn (clamp_selection).

SkillPopup::calculate_required_height58–62 ↗
fn calculate_required_height(&self, _width: u16) -> u16

Purpose: Figures out how tall the popup should be. It asks for enough space to show the matching rows, but never more than the configured maximum, plus room for the hint area.

Data flow: It reads the current query and mention list, builds the filtered rows, counts how many should be visible, clamps that count between one row and the maximum popup height, then returns that height plus two extra lines.

Call relations: The test rendering helper calls this before drawing so it can create a correctly sized terminal buffer. Internally it uses SkillPopup::filtered to find matches and SkillPopup::rows_from_matches to convert those matches into display rows.

Call graph: calls 2 internal fn (filtered, rows_from_matches); called by 1 (render_popup).

SkillPopup::move_up64–68 ↗
fn move_up(&mut self)

Purpose: Moves the highlighted selection one row upward, wrapping around if needed. It also scrolls the visible window so the new selection can be seen.

Data flow: It reads the current filtered item count, asks the ScrollState to move the selection upward within that count, then asks the ScrollState to keep the selected row inside the visible popup window.

Call relations: This is used when the user presses the key for moving up in the popup. It depends on SkillPopup::filtered_items to know how many selectable results exist, then delegates the actual selection math to ScrollState.

Call graph: calls 3 internal fn (ensure_visible, move_up_wrap, filtered_items).

SkillPopup::move_down70–74 ↗
fn move_down(&mut self)

Purpose: Moves the highlighted selection one row downward, wrapping around if needed. It keeps the visible list scrolled to the selected item.

Data flow: It reads the filtered result count, tells ScrollState to advance the selection, and then tells ScrollState to adjust the scroll offset so the chosen row remains visible.

Call relations: This is used when the user presses the key for moving down. The scrolling snapshot test drives this repeatedly to confirm that the popup window shifts correctly once the selection moves past the visible area.

Call graph: calls 3 internal fn (ensure_visible, move_down_wrap, filtered_items).

SkillPopup::selected_mention76–81 ↗
fn selected_mention(&self) -> Option<&MentionItem>

Purpose: Returns the actual mention item currently highlighted by the user. The app can use this when the user presses Enter to insert the selected skill or plugin.

Data flow: It builds the current filtered item index list, reads the selected row number from ScrollState, maps that row back to the original mention list, and returns the matching MentionItem if everything is valid. If there is no valid selection, it returns nothing.

Call relations: This sits at the handoff point between the popup and the rest of the app: after navigation and filtering, other code can ask it what the user chose. It uses SkillPopup::filtered_items so the selected row is interpreted in the current filtered order.

Call graph: calls 1 internal fn (filtered_items).

SkillPopup::clamp_selection83–87 ↗
fn clamp_selection(&mut self)

Purpose: Keeps the selected row and scroll position safe after the result list changes. It prevents the popup from pointing beyond the end of the current matches.

Data flow: It counts the current filtered items, tells ScrollState to clamp the selected index into that range, and then tells ScrollState to ensure the selection is visible within the popup’s maximum row count.

Call relations: SkillPopup::set_mentions and SkillPopup::set_query call this after they change the data that affects results. It acts like a guardrail between changing inputs and later rendering or selection.

Call graph: calls 3 internal fn (clamp_selection, ensure_visible, filtered_items); called by 2 (set_mentions, set_query).

SkillPopup::filtered_items89–91 ↗
fn filtered_items(&self) -> Vec<usize>

Purpose: Returns only the original mention indexes for the current filtered results. This is a compact form used when code needs to navigate or choose items, not draw them.

Data flow: It runs the full filtering logic, takes each matched result, keeps the original index into the mention list, and returns those indexes in the same sorted order.

Call relations: Movement, selection, and selection clamping all call this because they only need to know which mention rows exist and in what order. It is a simpler wrapper around SkillPopup::filtered.

Call graph: calls 1 internal fn (filtered); called by 4 (clamp_selection, move_down, move_up, selected_mention).

SkillPopup::rows_from_matches93–128 ↗
fn rows_from_matches(
        &self,
        matches: Vec<(usize, Option<Vec<usize>>, i32)>,
    ) -> Vec<GenericDisplayRow>

Purpose: Turns filtered mention matches into rows that the shared popup renderer can draw. It decides what name, description, and match highlighting information should be shown.

Data flow: It receives match records that point back to the mention list. For each one, it looks up the mention, shortens long display names, combines the category tag and description when present, carries over any display-name match positions for highlighting, and returns a list of GenericDisplayRow values.

Call relations: SkillPopup::calculate_required_height uses this to count display rows, and SkillPopup::render_ref uses it to prepare the rows that are actually drawn. It bridges the popup’s mention-specific data with the generic row-rendering helper.

Call graph: called by 2 (calculate_required_height, render_ref).

SkillPopup::filtered130–181 ↗
fn filtered(&self) -> Vec<(usize, Option<Vec<usize>>, i32)>

Purpose: Finds and sorts the mentions that match the current query. It is the main search-brain of the popup.

Data flow: It trims the query. If the query is empty, every mention is included and sorted by rank and name. If the query has text, it first tries fuzzy matching against the visible display name, keeping match positions for highlighting. If that fails, it tries the mention’s extra search terms. Matching results are sorted so visible-name matches come before hidden-term matches, then better scores, rank, and name decide the order.

Call relations: Rendering, height calculation, and selection helpers all depend on this so they share one consistent idea of what the current result list is. It calls the fuzzy matching utility to score loose text matches.

Call graph: called by 3 (calculate_required_height, filtered_items, render_ref); 2 external calls (new, fuzzy_match).

SkillPopup::render_ref185–217 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the popup into a terminal UI buffer. It shows the visible result rows and, when there is enough height, a one-line keyboard hint.

Data flow: It receives a screen rectangle and a mutable buffer to draw into. It splits the rectangle into a list area and optional hint area, filters and converts the mentions into display rows, draws the rows with the shared single-line row renderer, and then draws the Enter/Esc hint if space allows.

Call relations: The terminal UI framework calls this through the WidgetRef interface whenever the popup needs to be painted. The test helper also calls it to capture rendered snapshots. It uses SkillPopup::filtered, SkillPopup::rows_from_matches, render_rows_single_line, and skill_popup_hint_line as its drawing pipeline.

Call graph: calls 5 internal fn (render_rows_single_line, filtered, rows_from_matches, skill_popup_hint_line, tlbr); called by 1 (render_popup); 2 external calls (Length, vertical).

skill_popup_hint_line220–228 ↗
fn skill_popup_hint_line() -> Line<'static>

Purpose: Builds the short instruction line shown at the bottom of the popup. It tells the user that Enter inserts the selected item and Esc closes the popup.

Data flow: It creates a formatted text line made of plain words and key-name spans. The result is a Line value that the renderer can place into the terminal buffer.

Call relations: SkillPopup::render_ref calls this only when the popup has enough vertical space to include the hint below the list.

Call graph: called by 1 (render_ref); 2 external calls (from, vec!).

tests::mention_item237–247 ↗
fn mention_item(index: usize) -> MentionItem

Purpose: Creates a predictable fake MentionItem for tests. It gives each item a numbered name, description, insert text, path, category tag, and rank.

Data flow: It receives a number, formats that number into several fields, and returns a complete MentionItem. The generated data is regular and easy for tests to compare.

Call relations: The tests use this helper to quickly build long lists of mentions, especially for checking popup height and scrolling behavior.

Call graph: 2 external calls (format!, vec!).

tests::ranked_mention_item249–267 ↗
fn ranked_mention_item(
        display_name: &str,
        search_terms: &[&str],
        category_tag: &str,
        sort_rank: u8,
    ) -> MentionItem

Purpose: Creates a fake MentionItem with a chosen name, search terms, category tag, and sort rank. It is used by sorting tests where the exact rank and category matter.

Data flow: It receives display text, search terms, a category label, and a rank. It copies those into a MentionItem, generates insert text from the display name, leaves optional path and description empty, and returns the item.

Call relations: This is the base helper for more specific test builders. It lets the sorting tests set up skills and plugins with controlled rank differences.

Call graph: 1 external calls (format!).

tests::named_mention_item269–271 ↗
fn named_mention_item(display_name: &str, search_terms: &[&str]) -> MentionItem

Purpose: Creates a test mention that represents a normal skill. It uses the standard skill category tag and the usual skill rank.

Data flow: It receives a display name and search terms, then passes them along with the skill tag and rank to the ranked test-item builder. The output is a MentionItem ready for sorting tests.

Call relations: The search-order tests call this through their setup data so most test items behave like ordinary skills without repeating the tag and rank each time.

Call graph: 1 external calls (ranked_mention_item).

tests::plugin_mention_item273–275 ↗
fn plugin_mention_item(display_name: &str, search_terms: &[&str]) -> MentionItem

Purpose: Creates a test mention that represents a plugin. It gives the item a plugin category tag and a different rank so tests can check how rank interacts with match quality.

Data flow: It receives a display name and search terms, then passes them with the plugin tag and plugin rank to the ranked test-item builder. The result is a plugin-like MentionItem.

Call relations: The rank-bias sorting test uses this to confirm that a weaker plugin match does not jump ahead of better visible-name skill matches just because of rank.

Call graph: 1 external calls (ranked_mention_item).

tests::filtered_mentions_preserve_results_beyond_popup_height278–297 ↗
fn filtered_mentions_preserve_results_beyond_popup_height()

Purpose: Checks that filtering does not throw away matches just because the popup can only show a limited number of rows. The popup may display a window, but the full result list must still exist for scrolling and selection.

Data flow: It creates more fake mentions than the maximum visible popup rows, asks the popup for its filtered item order, turns those indexes back into names, and compares them with the full expected list. It also checks that the calculated height stops at the maximum visible row count plus the extra hint space.

Call relations: The test runner calls this as a unit test. It uses SkillPopup::new and then directly exercises the popup’s filtering and height behavior.

Call graph: calls 1 internal fn (new); 1 external calls (assert_eq!).

tests::render_popup299–304 ↗
fn render_popup(popup: &SkillPopup, width: u16) -> String

Purpose: Renders a popup into an in-memory terminal buffer and returns a printable snapshot string. It is a test helper for checking what the user would see.

Data flow: It receives a popup and a width, asks the popup how tall it should be, creates an empty buffer for that rectangle, renders the popup into the buffer, and returns the buffer’s debug text.

Call relations: The scrolling snapshot test calls this after moving the selection. It connects SkillPopup::calculate_required_height and SkillPopup::render_ref so tests cover the same size-then-draw flow used by the UI.

Call graph: calls 2 internal fn (calculate_required_height, render_ref); 3 external calls (empty, new, format!).

tests::scrolling_mentions_shifts_rendered_window_snapshot307–315 ↗
fn scrolling_mentions_shifts_rendered_window_snapshot()

Purpose: Checks that moving down through a long result list scrolls the visible window. It protects the behavior users rely on when there are more matches than fit on screen.

Data flow: It builds a popup with more items than can be shown, moves the selection downward enough times to force scrolling, renders the popup into a snapshot string, and compares that output with the saved expected snapshot.

Call relations: The test runner calls this. It uses SkillPopup::new, SkillPopup::move_down, and the render helper to verify the full navigation-and-rendering path.

Call graph: calls 1 internal fn (new); 1 external calls (assert_snapshot!).

tests::display_name_match_sorting_beats_worse_secondary_search_term_matches318–347 ↗
fn display_name_match_sorting_beats_worse_secondary_search_term_matches()

Purpose: Checks that matches in the visible display name are preferred over worse matches found only through extra search terms. This keeps search results understandable to the user.

Data flow: It builds several named skill mentions, sets the query to “pr”, asks for the filtered order, converts the results to display names, and compares that order with the expected list.

Call relations: The test runner calls this. It exercises SkillPopup::new, SkillPopup::set_query, and the filtering order so future changes do not accidentally make hidden search terms outrank clearer visible matches.

Call graph: calls 1 internal fn (new); 2 external calls (assert_eq!, vec!).

tests::query_match_score_sorts_before_plugin_rank_bias350–389 ↗
fn query_match_score_sorts_before_plugin_rank_bias()

Purpose: Checks that match quality matters before plugin-versus-skill rank bias when a query is present. In plain terms, a better text match should appear before a lower-ranked category advantage.

Data flow: It creates one plugin-like item and several skill-like items, sets the query to “pr”, collects the filtered results as name and category pairs, and compares them with the expected order.

Call relations: The test runner calls this. It uses the skill and plugin test-item builders plus SkillPopup::set_query to protect the detailed sorting rules inside SkillPopup::filtered.

Call graph: calls 1 internal fn (new); 2 external calls (assert_eq!, vec!).

tui/src/bottom_pane/chat_composer.rssource ↗
domain_logicmain loop / user input handling

The chat composer is the part of the app where a user writes a message before sending it. It is more than a plain text box. It understands slash commands, file and skill mentions, pasted images, large pasted text, command history, Vim-style editing, and small footer messages that teach the user which keys are available. Think of it like a smart writing desk: the text area is the paper, attachments are items clipped to it, popups are suggestion cards, and the footer is a small instruction strip. This chunk shows how the composer is created, configured, laid out on screen, updated from keyboard and paste input, restored from history, and converted into clean text for submission. A few details matter: very large pasted content can be stored behind a placeholder so the screen stays usable; image paths can turn into real image attachments; bash mode hides the leading ! inside the editor while still reporting it as part of the message; and mention or file popups are kept in sync whenever the text or available suggestions change. This part of the chat composer is the control desk for what happens while a user types. It watches the text around the cursor, decides when to show helpful popups, inserts selected files or mentions, and prepares the final text when the user submits. Without it, the input box would be much more like a plain text field: slash commands would not reliably dispatch, file and skill suggestions would not appear at the right time, pasted blocks could be mishandled, and hidden attachment or mention information could be lost.

A key idea here is that some visible text is also an “element”: an atomic piece of text with extra meaning, like an inserted mention or a placeholder for a large paste. The composer keeps these elements lined up with the plain text, much like keeping labels attached to items on a conveyor belt. When the user edits, submits, recalls history, or deletes text, this code keeps those labels accurate.

The file also protects the user from mistakes. It refuses unknown slash commands, blocks commands that cannot run during an active task, restores the draft if submission is rejected, and trims or expands text carefully before sending. It also decides which popup deserves focus, so command suggestions, file search, and mention suggestions do not fight each other. The chat composer is like the control panel at the bottom of the app. It must accept text, show whether input is available, display shortcuts, show context usage, mark special mentions, and adapt when the terminal is narrow. Without this file, the user could still have backend chat logic, but the terminal would not know how tall the input box should be, where the cursor belongs, what footer hints to show, or how to visually distinguish states like shell mode, shutdown, history search, or plugin mentions.

This part of the file has three main jobs. First, it exposes small setters that let the rest of the app update visible composer state, such as “a task is running,” “show this status line,” or “disable input because shutdown started.” Second, it contains helper logic for rendering: calculating height, choosing footer layouts, drawing popups, drawing remote image attachment previews, drawing the prompt, showing placeholder text, masking input when needed, and applying highlight styles. Third, it includes tests that protect user-visible details, such as footer spacing, flash messages overriding normal hints, shell-mode styling, plugin mention colors, hyperlinks, and Vim-mode edge cases. The chat composer is the front desk of the terminal app. It turns raw key presses, pasted text, image attachments, and special tokens like /plan, @file, or $plugin into meaningful actions for the rest of the program. Without this file, the app would not know when the user meant “send this prompt,” “open a help overlay,” “refer to this file,” “restore my draft,” or “run a shell command.” This chunk focuses on tests. They check that the composer behaves predictably in edge cases: Vim mode should switch between Insert and Normal at the right times; Ctrl-C should clear a draft but still save it in history; large pastes should be represented by safe placeholders until submit time; image placeholders should survive history restore; question mark should open shortcuts only when it is meant as a shortcut, not when typed or pasted; and mention suggestions for skills, plugins, apps, and files should open only when appropriate. Think of these tests as guardrails around the input box. They make sure a small change to keyboard handling does not accidentally break a user’s muscle memory or lose hidden data attached to visible placeholder text. The chat composer is a small but important control room for user input. It decides whether a key press means “type this character,” “submit the prompt,” “queue this for later,” “complete a slash command,” or “edit an attachment row.” Without this logic, ordinary typing could accidentally submit pasted text, large pastes could flood the screen, and slash commands could be sent as plain messages.

This chunk is a set of regression tests. They build a fresh composer, simulate key presses and pasted content, then check the result. Many tests focus on “paste burst” behavior: when many characters arrive very quickly, the composer treats them like a paste and buffers them briefly, much like waiting to see whether a few raindrops turn into a downpour. Other tests check Unicode text, large paste placeholders, image rows, command popups, custom key bindings, queued submissions while a task is running, and Emacs-style kill/yank editing shortcuts.

Together, these tests make sure the composer feels predictable for real users: Enter submits only when it should, Tab completes or queues correctly, non-English text is preserved, and the displayed terminal UI matches expected snapshots. The chat composer is the front desk of the terminal app. It turns keystrokes, pasted text, file completions, history navigation, Vim-style movement, and image attachments into clean submissions or commands. These tests focus on edge cases that are easy to get wrong: deciding whether /diff is a command or plain text, making sure !ls stays a shell-style message, replacing huge pasted blocks with short placeholders while editing, and then expanding them again when the user submits. They also check that image placeholders such as [Image #1] stay linked to the real file path, even after history navigation or deletion. Several tests cover keyboard navigation, including regular arrow keys and Vim keys like j and k, because the composer must not accidentally recall history when the user meant to move inside text. Without these tests, small changes to editing behavior could silently break real user workflows: pasted content might be lost, images might detach from their placeholders, commands might submit as ordinary text, or history recall could feel unpredictable. The chat composer is the user's front door into the terminal app. It has to do more than accept plain text: it recognizes slash commands like /diff, supports a bash-style ! prefix, inserts local image placeholders such as [Image #1], remembers pasted content, and temporarily buffers very fast typing so a large paste does not flood the editor one character at a time. Think of it like a smart notepad with extra labels stuck into the text: if one label is removed, the remaining labels must still point to the right attachments.

This chunk is the safety net for those rules. The tests create a ChatComposer, simulate keystrokes, pastes, external edits, image additions, and shutdown state, then check the visible text and internal attachment lists. They focus on edge cases that are easy to break: reordered image placeholders, remote images that change numbering, slash-like file paths that should not become commands, and large paste placeholders that must expand back to the original text before submission. These tests matter because small mistakes here would directly confuse users: images could be submitted in the wrong order, commands could run accidentally, pasted text could be lost, or typing could appear to freeze.

Function details367
user_input_too_large_message266–270 ↗
fn user_input_too_large_message(actual_chars: usize) -> String

Purpose: Builds the friendly error text shown when a user tries to submit a message that is longer than the allowed limit.

Data flow: It receives the actual character count, combines it with the fixed maximum, and returns one complete message string. It does not change any composer state.

Call relations: When submission text is being prepared, the size check calls this helper so the user gets a clear explanation instead of a vague failure.

Call graph: called by 1 (prepare_submission_text_with_options); 1 external calls (format!).

ChatComposerConfig::default329–335 ↗
fn default() -> Self

Purpose: Creates the normal composer configuration, with popups, slash commands, and image pasting all turned on.

Data flow: It takes no input and returns a configuration object filled with the standard feature settings.

Call relations: The main constructor uses this so a regular chat composer starts with the expected interactive features enabled.

Call graph: called by 1 (new).

ChatComposerConfig::plain_text343–349 ↗
fn plain_text() -> Self

Purpose: Creates a stripped-down configuration for a simple text-only composer.

Data flow: It takes no input and returns a configuration where suggestion popups, slash commands, and image paste support are disabled.

Call relations: Alternate constructors use this when they need a plain editor without the richer chat features.

Call graph: called by 2 (new_with_keymap, new_with_keymap).

plan_mode_nudge_line418–428 ↗
fn plan_mode_nudge_line() -> Line<'static>

Purpose: Builds the short footer line that suggests switching to Plan mode.

Data flow: It takes no input, creates styled text fragments for the prompt and key hints, and returns a single display line.

Call relations: Rendering code asks for this line when the composer should nudge the user toward Plan mode.

Call graph: called by 1 (render_with_mask_and_textarea_right_reserve); 2 external calls (from, vec!).

ChatComposer::slash_input431–438 ↗
fn slash_input(&self) -> SlashInput<'_>

Purpose: Collects the current slash-command settings into a helper object that can understand slash command text.

Data flow: It reads whether slash commands and bash mode are enabled, gathers command feature flags and service-tier commands, and returns a SlashInput view of that state.

Call relations: Submission, popup syncing, and slash-command dispatch all call this so they interpret commands using the same current rules.

Call graph: calls 3 internal fn (builtin_command_flags, slash_commands_enabled, new); called by 5 (handle_submission_with_time, prepare_submission_text_with_options, sync_command_popup, try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args).

ChatComposer::builtin_command_flags440–452 ↗
fn builtin_command_flags(&self) -> BuiltinCommandFlags

Purpose: Packages the many on/off switches that decide which built-in slash commands are available.

Data flow: It reads feature booleans from the composer and returns one flags object describing the allowed command set.

Call relations: The slash input builder calls this before command parsing or popup display, so command availability stays consistent.

Call graph: called by 1 (slash_input).

ChatComposer::new454–469 ↗
fn new(
        has_input_focus: bool,
        app_event_tx: AppEventSender,
        enhanced_keys_supported: bool,
        placeholder_text: String,
        disable_paste_burst: bool,
    ) -> Self

Purpose: Creates a standard chat composer for normal use.

Data flow: It receives focus state, event sending, keyboard capability, placeholder text, and paste-burst settings, then delegates to the configurable constructor with default feature settings.

Call relations: Tests and app setup use this when they want the ordinary composer rather than a customized plain-text one.

Call graph: calls 1 internal fn (default); called by 175 (new, history_search_accepts_matching_entry, history_search_esc_resets_normal_history_navigation, history_search_esc_restores_original_draft, history_search_flushes_buffered_paste_before_snapshot, history_search_flushes_pending_first_char_before_snapshot, history_search_footer_action_hints_are_emphasized, history_search_highlights_matches_until_accepted, history_search_no_match_restores_preview_but_keeps_search_open, history_search_opens_without_previewing_latest_entry (+15 more)); 1 external calls (new_with_config).

ChatComposer::new_with_config475–563 ↗
fn new_with_config(
        has_input_focus: bool,
        app_event_tx: AppEventSender,
        enhanced_keys_supported: bool,
        placeholder_text: String,
        disable_paste_burst: bool,

Purpose: Builds a composer from all of its initial parts: text draft, history, footer hints, popups, attachments, feature flags, and key bindings.

Data flow: It receives startup settings plus a configuration object, creates the internal state, fills in default keyboard bindings, applies paste-burst behavior, and returns the ready composer.

Call relations: All constructors flow through this function so setup details and side effects, such as paste-burst disabling, happen in one place.

Call graph: calls 7 internal fn (new, footer_insert_newline_key, new, ctrl, plain, defaults, primary_binding); called by 2 (new_with_keymap, new_with_keymap); 5 external calls (Char, new, default, default, vec!).

ChatComposer::set_frame_requester565–567 ↗
fn set_frame_requester(&mut self, frame_requester: FrameRequester)

Purpose: Stores the object used to ask the interface for another screen redraw.

Data flow: It receives a frame requester and saves it inside the composer. Nothing is returned.

Call relations: Other parts of the app can install this hook so later composer activity can trigger fresh rendering.

ChatComposer::set_skill_mentions569–572 ↗
fn set_skill_mentions(&mut self, skills: Option<Vec<SkillMetadata>>)

Purpose: Updates the list of skills that can appear in mention suggestions.

Data flow: It receives an optional skill list, stores it, then refreshes popups so suggestions match the new list.

Call relations: The higher-level skill setter calls this when available skills change.

Call graph: calls 1 internal fn (sync_popups); called by 1 (set_skills).

ChatComposer::set_plugin_mentions574–577 ↗
fn set_plugin_mentions(&mut self, plugins: Option<Vec<PluginCapabilitySummary>>)

Purpose: Updates the plugins that can be suggested in mention-style completions.

Data flow: It receives an optional plugin list, stores it, and refreshes popups to reflect the new options.

Call relations: Plugin state changes pass through this function so the visible suggestions do not become stale.

Call graph: calls 1 internal fn (sync_popups); called by 1 (set_plugin_mentions).

ChatComposer::set_plugins_command_enabled579–581 ↗
fn set_plugins_command_enabled(&mut self, enabled: bool)

Purpose: Turns the plugins slash command on or off.

Data flow: It receives a boolean and stores it in the composer feature flags.

Call relations: Slash-command availability later reads this flag through the command flag builder.

Call graph: called by 1 (set_plugins_command_enabled).

ChatComposer::set_token_activity_command_enabled583–585 ↗
fn set_token_activity_command_enabled(&mut self, enabled: bool)

Purpose: Turns the token-activity slash command on or off.

Data flow: It receives a boolean and records whether that command should be offered.

Call relations: The slash command system consults this setting when building its list of commands.

Call graph: called by 1 (set_token_activity_command_enabled).

ChatComposer::set_mentions_v2_enabled587–591 ↗
fn set_mentions_v2_enabled(&mut self, enabled: bool)

Purpose: Switches the newer mention system on or off.

Data flow: It stores the setting, tells history whether it should restore at-mentions, and refreshes popups.

Call relations: When the app toggles the newer mention feature, this keeps editing, history, and suggestions in agreement.

Call graph: calls 2 internal fn (sync_popups, set_at_mention_restore_enabled); called by 1 (set_mentions_v2_enabled).

ChatComposer::set_image_paste_enabled597–599 ↗
fn set_image_paste_enabled(&mut self, enabled: bool)

Purpose: Controls whether pasted image paths should become image attachments.

Data flow: It receives a boolean and writes it into the composer configuration.

Call relations: Paste handling reads this setting before trying to interpret pasted text as an image path.

Call graph: called by 1 (set_image_paste_enabled).

ChatComposer::set_connector_mentions601–604 ↗
fn set_connector_mentions(&mut self, connectors_snapshot: Option<ConnectorsSnapshot>)

Purpose: Updates connector information used by mention suggestions.

Data flow: It receives an optional connector snapshot, stores it, and refreshes the active popup state.

Call relations: Connector updates call this so mention suggestions can include or remove connector-related choices.

Call graph: calls 1 internal fn (sync_popups); called by 1 (set_connectors_snapshot).

ChatComposer::take_mention_bindings606–623 ↗
fn take_mention_bindings(&mut self) -> Vec<MentionBinding>

Purpose: Returns the mention bindings that still match the current text, then clears the stored binding map.

Data flow: It reads the current mention elements, checks each stored binding against the visible mention text and symbol, collects valid bindings in order, clears the rest, and returns the valid list.

Call relations: Callers use this when they need to consume mention metadata for a submission without keeping old bindings around.

Call graph: calls 1 internal fn (current_mention_elements); called by 1 (take_mention_bindings); 1 external calls (new).

ChatComposer::set_collaboration_modes_enabled625–627 ↗
fn set_collaboration_modes_enabled(&mut self, enabled: bool)

Purpose: Turns collaboration-mode slash commands or indicators on or off.

Data flow: It receives a boolean and stores it in the composer.

Call relations: The slash command flag builder later reads this setting when deciding what commands are available.

Call graph: called by 1 (set_collaboration_modes_enabled).

ChatComposer::set_connectors_enabled629–631 ↗
fn set_connectors_enabled(&mut self, enabled: bool)

Purpose: Turns connector-related command support on or off.

Data flow: It receives a boolean and saves it as a feature flag.

Call relations: Slash command setup uses this value when it creates the current command input rules.

Call graph: called by 1 (set_connectors_enabled).

ChatComposer::set_service_tier_commands_enabled633–635 ↗
fn set_service_tier_commands_enabled(&mut self, enabled: bool)

Purpose: Controls whether service-tier slash commands are available.

Data flow: It receives a boolean and stores it.

Call relations: The slash command system reads this flag through the command flags helper.

Call graph: called by 1 (set_service_tier_commands_enabled).

ChatComposer::set_service_tier_commands637–640 ↗
fn set_service_tier_commands(&mut self, commands: Vec<ServiceTierCommand>)

Purpose: Replaces the current list of service-tier commands and updates suggestions.

Data flow: It receives a command list, stores it, and refreshes popups so command completions can change immediately.

Call relations: Service-tier updates call this before slash input or popups need the new command list.

Call graph: calls 1 internal fn (sync_popups); called by 1 (set_service_tier_commands).

ChatComposer::set_goal_command_enabled642–644 ↗
fn set_goal_command_enabled(&mut self, enabled: bool)

Purpose: Turns goal-related slash command support on or off.

Data flow: It receives a boolean and saves it in the composer.

Call relations: Command parsing and command popups later use this through the built-in command flags.

Call graph: called by 1 (set_goal_command_enabled).

ChatComposer::set_keymap_bindings652–672 ↗
fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap)

Purpose: Applies a runtime keyboard shortcut map to the composer and its footer hints.

Data flow: It receives a keymap, copies relevant submit, queue, search, editor, Vim, and footer bindings into the composer, and updates the underlying text area too.

Call relations: When user-configured shortcuts change, this function keeps actual behavior and displayed hints aligned.

Call graph: calls 2 internal fn (footer_insert_newline_key, primary_binding); called by 1 (set_keymap_bindings).

ChatComposer::set_collaboration_mode_indicator674–679 ↗
fn set_collaboration_mode_indicator(
        &mut self,
        indicator: Option<CollaborationModeIndicator>,
    )

Purpose: Sets the collaboration status shown in the footer.

Data flow: It receives an optional indicator and stores it in footer state.

Call relations: Footer rendering later reads this value when building the right-side status line.

Call graph: called by 1 (set_collaboration_mode_indicator).

ChatComposer::set_goal_status_indicator681–683 ↗
fn set_goal_status_indicator(&mut self, indicator: Option<GoalStatusIndicator>)

Purpose: Sets the goal status shown in the footer.

Data flow: It receives an optional goal indicator and stores it.

Call relations: Rendering combines this with other status indicators when drawing the composer.

Call graph: called by 1 (set_goal_status_indicator).

ChatComposer::set_ide_context_active685–687 ↗
fn set_ide_context_active(&mut self, active: bool)

Purpose: Records whether editor or IDE context is currently active.

Data flow: It receives a boolean and saves it in footer state.

Call relations: The footer status line reads this value to show context-related status.

Call graph: called by 1 (set_ide_context_active).

ChatComposer::set_personality_command_enabled689–691 ↗
fn set_personality_command_enabled(&mut self, enabled: bool)

Purpose: Turns personality slash command support on or off.

Data flow: It receives a boolean and saves it as a feature flag.

Call relations: Slash command setup includes this flag when deciding available built-in commands.

Call graph: called by 1 (set_personality_command_enabled).

ChatComposer::set_side_conversation_active693–695 ↗
fn set_side_conversation_active(&mut self, active: bool)

Purpose: Records whether the composer is working inside a side conversation.

Data flow: It receives a boolean and stores it in composer state.

Call relations: Slash command flags read this so commands can change while a side conversation is active.

Call graph: called by 1 (set_side_conversation_active).

ChatComposer::set_steer_enabled699–699 ↗
fn set_steer_enabled(&mut self, _enabled: bool)

Purpose: Accepts a steer-mode setting but currently does nothing.

Data flow: It receives a boolean and ignores it, leaving composer state unchanged.

Call relations: This exists as a compatibility hook for callers that expect to set steer support.

ChatComposer::popups_enabled701–703 ↗
fn popups_enabled(&self) -> bool

Purpose: Reports whether suggestion popups are allowed.

Data flow: It reads the configuration and returns the popup-enabled flag.

Call relations: Popup syncing calls this before showing or updating popups.

Call graph: called by 1 (sync_popups).

ChatComposer::slash_commands_enabled705–707 ↗
fn slash_commands_enabled(&self) -> bool

Purpose: Reports whether slash commands are allowed.

Data flow: It reads the configuration and returns the slash-command flag.

Call relations: Submission, slash input creation, and popup syncing use this to decide whether slash command behavior should run.

Call graph: called by 3 (handle_submission_with_time, slash_input, sync_popups).

ChatComposer::image_paste_enabled709–711 ↗
fn image_paste_enabled(&self) -> bool

Purpose: Reports whether pasted image paths may become attachments.

Data flow: It reads the configuration and returns the image-paste flag.

Call relations: Paste handling checks this before trying image-path detection.

Call graph: called by 1 (handle_paste).

ChatComposer::set_windows_degraded_sandbox_active713–715 ↗
fn set_windows_degraded_sandbox_active(&mut self, enabled: bool)

Purpose: Records whether the Windows degraded sandbox state is active.

Data flow: It receives a boolean and saves it.

Call relations: Built-in command flags use this value to decide whether sandbox elevation may be offered.

Call graph: called by 1 (set_windows_degraded_sandbox_active).

ChatComposer::layout_areas716–718 ↗
fn layout_areas(&self, area: Rect) -> [Rect; 4]

Purpose: Calculates the main screen rectangles for the composer with no extra right-side reserve.

Data flow: It receives a terminal rectangle and delegates to the more general layout function with a zero reserve.

Call relations: Rendering and cursor code can use this simpler form when nothing needs space beside the text area.

Call graph: calls 1 internal fn (layout_areas_with_textarea_right_reserve).

ChatComposer::layout_areas_with_textarea_right_reserve720–770 ↗
fn layout_areas_with_textarea_right_reserve(
        &self,
        area: Rect,
        textarea_right_reserve: u16,
    ) -> [Rect; 4]

Purpose: Divides the available screen space into composer, remote-image, text-area, and popup regions.

Data flow: It reads footer height, active popup needs, attachment display lines, and the requested right-side reserve, then returns four rectangles describing where each part should be drawn.

Call relations: Rendering and cursor-position calculations call this so text, images, footer, and popups line up correctly.

Call graph: calls 3 internal fn (custom_footer_height, footer_props, tlbr); called by 3 (cursor_pos_with_textarea_right_reserve, layout_areas, render_with_mask_and_textarea_right_reserve); 6 external calls (Max, Min, vertical, footer_spacing, remote_image_lines, from).

ChatComposer::cursor_pos_with_textarea_right_reserve784–803 ↗
fn cursor_pos_with_textarea_right_reserve(
        &self,
        area: Rect,
        textarea_right_reserve: u16,
    ) -> Option<(u16, u16)>

Purpose: Finds where the text cursor should appear on screen, allowing for reserved space on the right.

Data flow: It receives the screen area and reserve width, checks whether input is currently eligible for a cursor, handles history-search cursor placement first, then asks the text area for its cursor coordinates.

Call relations: The app calls this when positioning the terminal cursor during rendering.

Call graph: calls 1 internal fn (layout_areas_with_textarea_right_reserve); called by 2 (cursor_pos, cursor_pos).

ChatComposer::is_empty805–807 ↗
fn is_empty(&self) -> bool

Purpose: Checks whether the composer has no user content at all.

Data flow: It reads the text area, bash-mode flag, and attachments, then returns true only when all are empty or inactive.

Call relations: Clearing, footer mode, shortcut handling, and outside queries use this to decide whether there is anything to act on.

Call graph: called by 6 (composer_is_empty, clear_for_ctrl_c, footer_mode, handle_key_event_without_popup, handle_shortcut_overlay_key, is_empty); 1 external calls (is_empty).

ChatComposer::set_history_metadata811–818 ↗
fn set_history_metadata(
        &mut self,
        thread_id: ThreadId,
        log_id: u64,
        entry_count: usize,
    )

Purpose: Gives the history system the identifiers it needs to fetch or track older entries.

Data flow: It receives a thread id, log id, and entry count, then passes them to the history component.

Call relations: Higher-level history setup calls this before navigation or search needs persistent history.

Call graph: calls 1 internal fn (set_metadata); called by 1 (set_history_metadata).

ChatComposer::on_history_entry_response826–848 ↗
fn on_history_entry_response(
        &mut self,
        log_id: u64,
        offset: usize,
        entry: Option<String>,
    ) -> bool

Purpose: Applies a history lookup result if it belongs to the current request.

Data flow: It receives a log id, offset, and optional entry text, asks the history component how to interpret it, then either restores an entry, applies a search result, or ignores it, returning whether anything changed.

Call relations: The outer app calls this when an asynchronous history response arrives.

Call graph: calls 2 internal fn (apply_history_entry, on_entry_response); called by 1 (on_history_entry_response).

ChatComposer::record_replayed_user_message_history850–852 ↗
fn record_replayed_user_message_history(&mut self, entry: HistoryEntry)

Purpose: Adds a replayed user message into composer history.

Data flow: It receives a history entry and passes it to the history component as a replayed submission.

Call relations: Replay flows call this so navigation can include messages that were brought back into the session.

Call graph: calls 1 internal fn (record_replayed_submission); called by 1 (record_replayed_user_message_history).

ChatComposer::handle_paste872–890 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Processes pasted text, including large paste placeholders and pasted image paths.

Data flow: It normalizes line endings, counts characters, stores very large pastes behind a placeholder, tries image attachment detection for small multi-character pastes, otherwise inserts the text, then clears paste-burst state and refreshes popups.

Call relations: Keyboard, paste-burst, and explicit paste paths all call this as the common paste entry point.

Call graph: calls 5 internal fn (handle_paste_image_path, image_paste_enabled, insert_str, next_large_paste_placeholder, sync_popups); called by 10 (handle_paste, handle_input_basic_with_time, handle_key_event_without_popup, handle_non_ascii_char, handle_paste_burst_flush, handle_submission_with_time, set_disable_paste_burst, handle_paste, handle_paste, handle_paste).

ChatComposer::handle_paste_image_path892–913 ↗
fn handle_paste_image_path(&mut self, pasted: String) -> bool

Purpose: Tries to turn pasted text into an image attachment if it looks like a valid image path.

Data flow: It normalizes the pasted path, checks whether image dimensions can be read, attaches the image on success, and returns true or false.

Call relations: Paste handling calls this only when image paste support is enabled.

Call graph: calls 3 internal fn (attach_image, normalize_pasted_path, pasted_image_format); called by 1 (handle_paste); 4 external calls (image_dimensions, debug!, info!, trace!).

ChatComposer::set_disable_paste_burst933–942 ↗
fn set_disable_paste_burst(&mut self, disabled: bool)

Purpose: Turns automatic paste-burst detection off or on, flushing any buffered paste if needed.

Data flow: It receives the disabled setting, stores it, and if disabling just began, turns any pending burst buffer into a normal paste and clears burst state.

Call relations: Composer construction and runtime settings call this so paste buffering does not leave hidden text behind.

Call graph: calls 1 internal fn (handle_paste).

ChatComposer::apply_external_edit949–1024 ↗
fn apply_external_edit(&mut self, text: String)

Purpose: Replaces the composer content with text returned from an external editor while preserving matching image placeholders.

Data flow: It clears pending pastes, imports text, counts which existing image placeholders still appear, keeps only matching attachments, rebuilds the text area with placeholder elements, relabels images, moves the cursor to the end, and refreshes popups.

Call relations: External-editor integration calls this when the user finishes editing outside the terminal.

Call graph: calls 2 internal fn (imported_text_for_textarea, sync_popups); called by 1 (apply_external_edit); 3 external calls (new, new, relabel_local_images).

ChatComposer::set_vim_enabled1032–1036 ↗
fn set_vim_enabled(&mut self, enabled: bool)

Purpose: Turns Vim-style editing on or off for the text area.

Data flow: It updates the text area mode, clears paste-burst state, and resets transient footer mode after the activity.

Call relations: Direct setters and the toggle function use this to keep Vim mode changes tidy.

Call graph: calls 1 internal fn (reset_mode_after_activity); called by 2 (set_vim_enabled, toggle_vim_enabled).

ChatComposer::toggle_vim_enabled1043–1047 ↗
fn toggle_vim_enabled(&mut self) -> bool

Purpose: Flips Vim-style editing between enabled and disabled.

Data flow: It reads the current Vim setting, sets the opposite value, and returns the new enabled state.

Call relations: The app calls this when the user activates the Vim toggle command.

Call graph: calls 1 internal fn (set_vim_enabled); called by 1 (toggle_vim_enabled).

ChatComposer::is_vim_enabled1051–1053 ↗
fn is_vim_enabled(&self) -> bool

Purpose: Reports whether Vim-style editing is currently enabled.

Data flow: It reads the text area's Vim setting and returns it.

Call relations: Outside composer queries use this to show or react to Vim state.

Call graph: called by 1 (composer_is_vim_enabled).

ChatComposer::should_handle_vim_insert_escape1060–1064 ↗
fn should_handle_vim_insert_escape(&self, key_event: KeyEvent) -> bool

Purpose: Checks whether an Escape key event should be treated as leaving Vim insert mode.

Data flow: It receives a key event, asks the text area, and returns the answer.

Call relations: Keyboard handling and outside composer checks use this before deciding what Escape should do.

Call graph: called by 2 (composer_should_handle_vim_insert_escape, handle_key_event_without_popup).

ChatComposer::vim_mode_indicator_span1066–1075 ↗
fn vim_mode_indicator_span(&self) -> Option<Span<'static>>

Purpose: Creates the colored footer label for the current Vim mode.

Data flow: It reads the text area's Vim mode label and returns a styled span for Normal or Insert mode, or nothing when Vim mode is not active.

Call relations: Footer-line builders call this when they need to include Vim status.

Call graph: called by 2 (mode_indicator_line, right_footer_line_with_context).

ChatComposer::mode_indicator_line1077–1098 ↗
fn mode_indicator_line(&self, show_cycle_hint: bool) -> Option<Line<'static>>

Purpose: Builds the footer line that shows editing and status indicators.

Data flow: It combines the Vim indicator, collaboration or goal indicators, IDE context, and optional cycle hint into one line, or returns nothing if there is nothing to show.

Call relations: Rendering calls this to draw the right-side mode/status area.

Call graph: calls 2 internal fn (vim_mode_indicator_span, status_line_right_indicator_line); called by 1 (render_with_mask_and_textarea_right_reserve); 2 external calls (from, new).

ChatComposer::current_text_with_pending1112–1124 ↗
fn current_text_with_pending(&self) -> String

Purpose: Returns the current message text with large paste placeholders expanded back into their full pasted content.

Data flow: It starts with current visible text; if pending pastes exist, it replaces matching placeholder elements with the stored paste payloads and returns the expanded string.

Call relations: Submission-related and draft-related callers use this when they need the real message, not the compact display version.

Call graph: calls 2 internal fn (current_text, current_text_elements); called by 5 (composer_text_with_pending, on_ctrl_c, handle_key_event, notes_has_content, on_ctrl_c); 1 external calls (expand_pending_pastes).

ChatComposer::input_enabled1127–1129 ↗
fn input_enabled(&self) -> bool

Purpose: Reports whether the composer currently accepts typing.

Data flow: It reads the draft input-enabled flag and returns it.

Call relations: External composer queries use this to know whether input should be routed to the composer.

Call graph: called by 1 (composer_input_enabled).

ChatComposer::pending_pastes1131–1133 ↗
fn pending_pastes(&self) -> Vec<(String, String)>

Purpose: Returns the stored large paste placeholders and their full hidden contents.

Data flow: It clones the pending paste list and returns it without changing composer state.

Call relations: Draft capture and outside queries use this so large pasted content can be saved and restored.

Call graph: called by 4 (composer_pending_pastes, draft_snapshot, capture_composer_draft, capture_composer_draft).

ChatComposer::set_pending_pastes1135–1141 ↗
fn set_pending_pastes(&mut self, pending_pastes: Vec<(String, String)>)

Purpose: Restores pending large pastes, but only if their placeholders still appear in the text.

Data flow: It receives placeholder-content pairs, reads the current text, filters out pairs whose placeholders are gone, and stores the survivors.

Call relations: History restore and draft restore call this after text content has been restored.

Call graph: calls 1 internal fn (current_text); called by 6 (set_composer_pending_pastes, apply_history_entry, restore_draft, restore_current_draft, apply_submission_draft, restore_current_draft).

ChatComposer::set_plan_mode_nudge_visible1153–1159 ↗
fn set_plan_mode_nudge_visible(&mut self, visible: bool) -> bool

Purpose: Shows or hides the Plan mode nudge and reports whether the visibility changed.

Data flow: It receives the desired visibility, compares it to the current value, updates it if different, and returns true only on change.

Call relations: The app uses this to avoid unnecessary redraws when the nudge state is unchanged.

Call graph: called by 1 (set_plan_mode_nudge_visible).

ChatComposer::plan_mode_nudge_visible1162–1164 ↗
fn plan_mode_nudge_visible(&self) -> bool

Purpose: Reports whether the Plan mode nudge is currently visible.

Data flow: It reads the footer flag and returns it.

Call relations: Outside checks call this when deciding whether to display or clear the nudge.

Call graph: called by 1 (plan_mode_nudge_visible).

ChatComposer::set_remote_image_urls1166–1170 ↗
fn set_remote_image_urls(&mut self, urls: Vec<String>)

Purpose: Replaces the remote image URLs attached to the composer.

Data flow: It receives URLs, gives them to the attachment state so the text area can be updated, then refreshes popups.

Call relations: History and draft restore call this to rebuild remote-image attachments.

Call graph: calls 1 internal fn (sync_popups); called by 3 (set_remote_image_urls, apply_history_entry, restore_draft); 1 external calls (set_remote_image_urls).

ChatComposer::remote_image_urls1172–1174 ↗
fn remote_image_urls(&self) -> Vec<String>

Purpose: Returns the remote image URLs currently attached to the draft.

Data flow: It asks the attachment state for its remote URLs and returns them.

Call relations: Draft snapshots and outside queries call this to save or inspect remote images.

Call graph: called by 2 (remote_image_urls, draft_snapshot); 1 external calls (remote_image_urls).

ChatComposer::take_remote_image_urls1176–1182 ↗
fn take_remote_image_urls(&mut self) -> Vec<String>

Purpose: Removes and returns the remote image URLs from the composer.

Data flow: It asks attachment state to take the URLs while updating the text area, refreshes popups, and returns the removed URLs.

Call relations: Submission or transfer flows call this when remote images should move out of the draft.

Call graph: calls 1 internal fn (sync_popups); called by 1 (take_remote_image_urls); 1 external calls (take_remote_image_urls).

ChatComposer::set_text_content1195–1207 ↗
fn set_text_content(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
    )

Purpose: Replaces the composer text and local image attachments without mention bindings.

Data flow: It receives text, text elements, and image paths, then delegates to the fuller setter with an empty mention-binding list.

Call relations: Clear, restore, and submission-draft paths use this simpler method when no mention metadata is needed.

Call graph: calls 1 internal fn (set_text_content_with_mention_bindings); called by 14 (set_composer_text, clear_for_ctrl_c, apply_submission_to_draft, clear_current_draft, reset_for_request, restore_current_draft, apply_submission_draft, apply_submission_to_draft, clear_notes_and_focus_options, clear_notes_draft (+4 more)); 1 external calls (new).

ChatComposer::set_text_content_with_mention_bindings1221–1244 ↗
fn set_text_content_with_mention_bindings(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
        local_image_paths: Vec<PathBuf>,
        mention_bindings: Vec<Ment

Purpose: Fully replaces the draft text, rich text elements, local images, and mention bindings.

Data flow: It clears old content and metadata, imports text into textarea form, sets text elements, resets local images, binds mentions, moves the cursor to the start, and refreshes popups.

Call relations: History restore, submission preparation, and draft restore use this as the authoritative way to load draft content.

Call graph: calls 3 internal fn (bind_mentions_from_snapshot, imported_text_for_textarea, sync_popups); called by 6 (set_composer_text_with_mention_bindings, apply_history_entry, handle_submission_with_time, prepare_submission_text_with_options, restore_draft, set_text_content); 1 external calls (reset_local_images).

ChatComposer::current_cursor1246–1248 ↗
fn current_cursor(&self) -> usize

Purpose: Returns the logical cursor position in the full message text.

Data flow: It reads the text-area cursor and adds one extra position when bash mode hides a leading !.

Call relations: Snapshots and cursor queries use this so saved cursor positions match the text returned by current_text.

Call graph: called by 3 (cursor, history_navigation_cursor, snapshot_draft).

ChatComposer::cursor1251–1253 ↗
fn cursor(&self) -> usize

Purpose: Exposes the current logical cursor position to outside callers.

Data flow: It delegates to the internal current-cursor calculation and returns the result.

Call relations: External composer APIs call this when they need the draft cursor.

Call graph: calls 1 internal fn (current_cursor); called by 1 (composer_cursor).

ChatComposer::history_navigation_cursor1255–1266 ↗
fn history_navigation_cursor(&self) -> usize

Purpose: Calculates the cursor position used when navigating history.

Data flow: It accounts for bash mode and Vim normal-mode end cursor behavior, then returns the position history navigation should use.

Call relations: Keyboard handling and popup syncing use this to decide whether history navigation should happen at the start or end of text.

Call graph: calls 2 internal fn (current_cursor, current_text); called by 2 (handle_key_event_without_popup, sync_popups).

ChatComposer::set_current_cursor1268–1277 ↗
fn set_current_cursor(&mut self, cursor: usize)

Purpose: Restores a logical cursor position into the visible text area.

Data flow: It receives a saved cursor, subtracts the hidden bash prefix if needed, clamps it to the visible text length, and sets the text-area cursor.

Call relations: Draft restoration calls this after restoring text content.

Call graph: called by 1 (restore_draft).

ChatComposer::current_text_elements1279–1287 ↗
fn current_text_elements(&self) -> Vec<TextElement>

Purpose: Returns rich text elements adjusted to match the full message text.

Data flow: It reads text-area elements and shifts their ranges when bash mode adds a hidden leading !, dropping any invalid ranges.

Call relations: Submission, snapshots, and clear operations call this so element positions line up with current_text.

Call graph: called by 6 (clear_for_ctrl_c, current_text_with_pending, handle_submission_with_time, prepare_submission_text_with_options, snapshot_draft, text_elements).

ChatComposer::shift_text_element1289–1297 ↗
fn shift_text_element(element: TextElement, shift: isize) -> Option<TextElement>

Purpose: Moves a text element's byte range forward or backward by a fixed amount.

Data flow: It receives an element and a shift, safely adjusts start and end positions, rejects invalid ranges, and returns the adjusted element if possible.

Call relations: Import and export paths use this when bash mode adds or removes the visible leading marker.

Call graph: 1 external calls (map_range).

ChatComposer::snapshot_draft1299–1309 ↗
fn snapshot_draft(&self) -> ComposerDraft

Purpose: Captures the full internal draft so it can be restored later.

Data flow: It reads text, elements, local and remote images, mention bindings, pending pastes, and cursor position, then returns a draft object.

Call relations: Draft save and history flows use this to preserve the composer as a bundle.

Call graph: calls 4 internal fn (current_cursor, current_text, current_text_elements, snapshot_mention_bindings); 2 external calls (local_image_paths, remote_image_urls).

ChatComposer::restore_draft1311–1331 ↗
fn restore_draft(&mut self, draft: ComposerDraft)

Purpose: Restores a previously captured internal draft.

Data flow: It unpacks saved text, attachments, mentions, pending pastes, and cursor, restores each part in the right order, and refreshes popups.

Call relations: Draft restoration calls this when switching back to saved composer content.

Call graph: calls 5 internal fn (set_current_cursor, set_pending_pastes, set_remote_image_urls, set_text_content_with_mention_bindings, sync_popups).

ChatComposer::set_placeholder_text1334–1336 ↗
fn set_placeholder_text(&mut self, placeholder: String)

Purpose: Changes the placeholder text shown when the composer is empty.

Data flow: It receives a string and stores it as the new placeholder.

Call relations: Placeholder syncing and draft restore paths call this when the prompt text should change.

Call graph: called by 4 (set_placeholder_text, restore_current_draft, restore_current_draft, sync_composer_placeholder).

ChatComposer::move_cursor_to_end1339–1344 ↗
fn move_cursor_to_end(&mut self)

Purpose: Moves the cursor to the end of the visible text.

Data flow: It sets the text-area cursor to the current text length and refreshes popups.

Call relations: Callers use this after loading or changing content when editing should continue at the end.

Call graph: calls 1 internal fn (sync_popups); called by 11 (set_composer_text, set_composer_text_with_mention_bindings, apply_submission_to_draft, clear_current_draft, restore_current_draft, apply_submission_draft, apply_submission_to_draft, clear_notes_and_focus_options, clear_notes_draft, clear_selection (+1 more)).

ChatComposer::move_cursor_to_history_entry_end1346–1354 ↗
fn move_cursor_to_history_entry_end(&mut self)

Purpose: Moves the cursor to the appropriate end position after loading a history entry.

Data flow: It chooses the Vim-normal end cursor when needed, otherwise the text length, sets the cursor, and refreshes popups.

Call relations: History entry application calls this so recalled text is ready to edit naturally.

Call graph: calls 1 internal fn (sync_popups); called by 1 (apply_history_entry).

ChatComposer::imported_text_for_textarea1360–1378 ↗
fn imported_text_for_textarea(
        &mut self,
        text: String,
        text_elements: Vec<TextElement>,
    ) -> (String, Vec<TextElement>)

Purpose: Converts incoming full message text into the form stored inside the text area.

Data flow: It checks whether the text starts with !; if so, it enables bash mode, strips the marker, and shifts text elements back by one byte, otherwise it disables bash mode and keeps the text as-is.

Call relations: External edits and text restore paths call this before writing into the text area.

Call graph: called by 2 (apply_external_edit, set_text_content_with_mention_bindings).

ChatComposer::clear_for_ctrl_c1380–1402 ↗
fn clear_for_ctrl_c(&mut self) -> Option<String>

Purpose: Clears the composer after Ctrl-C while saving the previous draft into local history.

Data flow: If the composer is empty it returns nothing; otherwise it captures current text, elements, attachments, mentions, and pending pastes, clears the draft and remote images, resets history navigation, records the old content, and returns the previous text.

Call relations: The Ctrl-C handling path calls this so a cleared message can still be recovered from history.

Call graph: calls 7 internal fn (current_text, current_text_elements, is_empty, set_text_content, snapshot_mention_bindings, record_local_submission, reset_navigation); called by 1 (clear_composer_for_ctrl_c); 6 external calls (new, new, clear_remote_image_urls, local_image_paths, remote_image_urls, take).

ChatComposer::current_text1405–1411 ↗
fn current_text(&self) -> String

Purpose: Returns the current message text as the user would submit it.

Data flow: It reads the text area and prepends ! when bash mode is active, otherwise returns the text directly.

Call relations: Submission, snapshots, history, clearing, and outside text queries use this as the basic source of draft text.

Call graph: called by 16 (composer_text, clear_for_ctrl_c, current_text_with_pending, draft_snapshot, handle_key_event_without_popup, handle_submission_with_time, history_navigation_cursor, is_bang_shell_command, prepare_submission_text_with_options, set_pending_pastes (+6 more)); 1 external calls (format!).

ChatComposer::apply_history_entry1420–1438 ↗
fn apply_history_entry(&mut self, entry: HistoryEntry)

Purpose: Loads a saved history entry into the composer.

Data flow: It unpacks text, attachments, mentions, and pending pastes, restores remote images and text content, restores pending pastes, and moves the cursor to the entry end.

Call relations: History navigation and history responses call this when the user selects an older message.

Call graph: calls 4 internal fn (move_cursor_to_history_entry_end, set_pending_pastes, set_remote_image_urls, set_text_content_with_mention_bindings); called by 2 (handle_key_event_without_popup, on_history_entry_response).

ChatComposer::text_elements1440–1442 ↗
fn text_elements(&self) -> Vec<TextElement>

Purpose: Returns the current rich text elements for the draft.

Data flow: It delegates to the adjusted current text element calculation and returns the list.

Call relations: Draft snapshots and outside queries use this to preserve non-plain-text parts such as placeholders.

Call graph: calls 1 internal fn (current_text_elements); called by 4 (composer_text_elements, draft_snapshot, capture_composer_draft, capture_composer_draft).

ChatComposer::draft_snapshot1444–1453 ↗
fn draft_snapshot(&self) -> ComposerDraftSnapshot

Purpose: Builds a public snapshot of the composer draft.

Data flow: It gathers current text, elements, local images, remote URLs, mention bindings, and pending pastes into a snapshot object.

Call relations: Outside composer APIs call this when they need to inspect or save draft state.

Call graph: calls 6 internal fn (current_text, local_images, mention_bindings, pending_pastes, remote_image_urls, text_elements); called by 1 (composer_draft_snapshot).

ChatComposer::local_image_paths1456–1458 ↗
fn local_image_paths(&self) -> Vec<PathBuf>

Purpose: Returns file paths for local images attached to the draft.

Data flow: It asks attachment state for local image paths and returns them.

Call relations: Outside composer queries use this when they need image paths without the fuller attachment objects.

Call graph: called by 1 (composer_local_image_paths); 1 external calls (local_image_paths).

ChatComposer::status_line_text1461–1463 ↗
fn status_line_text(&self) -> Option<String>

Purpose: Returns the plain text of the current footer status line, if one exists.

Data flow: It asks footer state for its status text and returns the optional string.

Call relations: Status-line queries call this to read what the composer is showing.

Call graph: calls 1 internal fn (status_line_text); called by 1 (status_line_text).

ChatComposer::local_images1465–1467 ↗
fn local_images(&self) -> Vec<LocalImageAttachment>

Purpose: Returns full local image attachment records.

Data flow: It asks attachment state for local images and returns the list.

Call relations: Snapshots and submission-draft flows use this when placeholders and paths are both needed.

Call graph: called by 6 (composer_local_images, draft_snapshot, apply_submission_to_draft, capture_composer_draft, apply_submission_to_draft, capture_composer_draft); 1 external calls (local_images).

ChatComposer::mention_bindings1469–1471 ↗
fn mention_bindings(&self) -> Vec<MentionBinding>

Purpose: Returns the current mention metadata attached to visible mentions.

Data flow: It snapshots the mention bindings and returns them.

Call relations: Draft snapshots call this so mention choices can be restored or submitted accurately.

Call graph: calls 1 internal fn (snapshot_mention_bindings); called by 1 (draft_snapshot).

ChatComposer::take_recent_submission_mention_bindings1473–1475 ↗
fn take_recent_submission_mention_bindings(&mut self) -> Vec<MentionBinding>

Purpose: Removes and returns mention bindings from the most recent submission.

Data flow: It takes the stored recent submission binding list, leaving an empty list behind, and returns the old list.

Call relations: Submission follow-up code calls this when it needs to hand off mention metadata exactly once.

Call graph: called by 1 (take_recent_submission_mention_bindings); 1 external calls (take).

ChatComposer::record_pending_slash_command_history1481–1485 ↗
fn record_pending_slash_command_history(&mut self)

Purpose: Stores a slash-command draft in history if one was waiting to be recorded.

Data flow: It takes the pending history entry, if present, records it as a local submission, and clears the pending slot.

Call relations: Slash-command dispatch paths call this so commands entered by the user can still appear in history.

Call graph: calls 1 internal fn (record_local_submission); called by 3 (record_pending_slash_command_history, try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args).

ChatComposer::attach_image1488–1491 ↗
fn attach_image(&mut self, path: PathBuf)

Purpose: Adds a local image file to the draft.

Data flow: It receives a path and passes it with the text area to attachment state, which inserts the proper image placeholder.

Call relations: Paste handling, file selection, and direct attachment APIs call this when an image should become part of the message.

Call graph: called by 4 (attach_image, handle_key_event_with_file_popup, handle_paste_image_path, insert_selected_file_path); 1 external calls (attach_image).

ChatComposer::take_recent_submission_images1494–1496 ↗
fn take_recent_submission_images(&mut self) -> Vec<PathBuf>

Purpose: Removes and returns image paths from the most recent submission.

Data flow: It asks attachment state to take its recent-submission image paths and returns them.

Call relations: Submission follow-up code calls this to transfer submitted image attachments once.

Call graph: called by 1 (take_recent_submission_images); 1 external calls (take_recent_submission_images).

ChatComposer::take_recent_submission_images_with_placeholders1498–1501 ↗
fn take_recent_submission_images_with_placeholders(&mut self) -> Vec<LocalImageAttachment>

Purpose: Removes and returns recent submitted images together with their placeholders.

Data flow: It asks attachment state for full local image attachment records from the recent submission and returns them.

Call relations: Callers use this when they need to match submitted image files back to their placeholder text.

Call graph: called by 1 (take_recent_submission_images_with_placeholders); 1 external calls (take_recent_submission_images_with_placeholders).

ChatComposer::flush_paste_burst_if_due1513–1515 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

Purpose: Flushes buffered paste-burst text if enough time has passed.

Data flow: It gets the current time, passes it to the paste-burst flush logic, and returns whether anything changed.

Call relations: Timers, tests, and typing helpers call this to turn buffered fast input into a paste at the right time.

Call graph: calls 1 internal fn (handle_paste_burst_flush); called by 6 (flush_paste_burst_if_due, flush_after_paste_burst, type_chars_humanlike, flush_paste_burst_if_due, flush_paste_burst_if_due, flush_paste_burst_if_due); 1 external calls (now).

ChatComposer::is_in_paste_burst1521–1523 ↗
fn is_in_paste_burst(&self) -> bool

Purpose: Reports whether the composer is currently buffering a fast paste-like burst of characters.

Data flow: It reads the paste-burst state and returns whether it is active.

Call relations: Input handling, rendering, and overlay logic use this to adjust behavior while a paste burst is in progress.

Call graph: called by 7 (handle_key_event, is_in_paste_burst, handle_shortcut_overlay_key, render_with_mask_and_textarea_right_reserve, is_in_paste_burst, is_in_paste_burst, is_in_paste_burst).

ChatComposer::on_file_search_result1533–1557 ↗
fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>)

Purpose: Applies asynchronous file search results to the active file or mention popup, but only if the user is still typing the same token.

Data flow: It receives the original query and matches, checks the current token under the cursor, ignores stale results, and updates the active popup with matches when appropriate.

Call relations: The app calls this when file search results arrive after the user typed an at-mention or file token.

Call graph: calls 1 internal fn (current_mentions_v2_token); called by 1 (on_file_search_result); 1 external calls (current_at_token).

ChatComposer::show_quit_shortcut_hint1564–1571 ↗
fn show_quit_shortcut_hint(&mut self, key: KeyBinding, has_focus: bool)

Purpose: Shows a temporary footer reminder for the quit shortcut.

Data flow: It receives the shortcut and focus state, sets an expiration time, stores the key, switches footer mode to the reminder, and updates focus.

Call relations: Quit-shortcut handling calls this after the first quit key press to teach the user what to press next.

Call graph: calls 1 internal fn (set_has_focus); called by 1 (show_quit_shortcut_hint); 1 external calls (now).

ChatComposer::clear_quit_shortcut_hint1574–1578 ↗
fn clear_quit_shortcut_hint(&mut self, has_focus: bool)

Purpose: Removes the temporary quit shortcut reminder.

Data flow: It clears the expiration, resets footer mode after activity, and updates focus state.

Call relations: The app calls this when the reminder should disappear or focus changes.

Call graph: calls 2 internal fn (set_has_focus, reset_mode_after_activity); called by 1 (clear_quit_shortcut_hint).

ChatComposer::quit_shortcut_hint_visible1585–1589 ↗
fn quit_shortcut_hint_visible(&self) -> bool

Purpose: Checks whether the quit shortcut reminder is still within its display timeout.

Data flow: It reads the stored expiration time and compares it to the current time, returning true only if it has not expired.

Call relations: Footer mode and shortcut handling use this to decide whether the reminder is active.

Call graph: called by 3 (quit_shortcut_hint_visible, footer_mode, handle_shortcut_overlay_key).

ChatComposer::next_large_paste_placeholder1591–1613 ↗
fn next_large_paste_placeholder(&self, char_count: usize) -> String

Purpose: Creates a unique placeholder label for a large paste.

Data flow: It receives the paste character count, builds a base label, checks existing pending paste placeholders, and adds a numbered suffix if needed.

Call relations: Paste handling calls this before inserting a placeholder for large pasted content.

Call graph: called by 1 (handle_paste); 1 external calls (format!).

ChatComposer::insert_str1615–1619 ↗
fn insert_str(&mut self, text: &str)

Purpose: Inserts plain text into the draft and updates related composer state.

Data flow: It writes the text into the text area, synchronizes bash mode from the new text, and refreshes popups.

Call relations: Paste and paste-burst flows use this as the common way to insert ordinary text.

Call graph: calls 2 internal fn (sync_bash_mode_from_text, sync_popups); called by 3 (insert_str, handle_paste, handle_paste_burst_flush).

ChatComposer::handle_key_event1622–1650 ↗
fn handle_key_event(&mut self, key_event: KeyEvent) -> (InputResult, bool)

Purpose: Routes one keyboard event to the correct composer behavior.

Data flow: It ignores input when disabled or on key release, diverts history-search keys, chooses the active popup handler or the normal handler, resets Vim mode after successful dispatch, refreshes popups, and returns the input result plus whether the key was consumed.

Call relations: The main input loop calls this for each key press.

Call graph: calls 6 internal fn (handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup, reset_vim_mode_after_successful_dispatch, sync_popups); called by 6 (handle_key_event, press, type_chars_humanlike, handle_key_event, handle_key_event, input); 2 external calls (is_history_search_key, matches!).

ChatComposer::popup_active1653–1655 ↗
fn popup_active(&self) -> bool

Purpose: Reports whether any popup-like mode is currently active.

Data flow: It checks history search and the popup state, then returns true if either is active.

Call relations: External editor launching, key handling, and backtrack logic use this to avoid conflicting actions.

Call graph: calls 1 internal fn (active); called by 3 (can_launch_external_editor, handle_key_event, is_normal_backtrack_mode).

ChatComposer::clamp_to_char_boundary1658–1669 ↗
fn clamp_to_char_boundary(text: &str, pos: usize) -> usize

Purpose: Moves a byte position backward to a safe character boundary in a UTF-8 string.

Data flow: It receives text and a byte position, clamps it to the string length, and if it lands inside a multi-byte character, moves it to the start of that character.

Call relations: Non-ASCII input and file-path insertion use this before slicing strings so multi-byte characters are not split.

ChatComposer::handle_non_ascii_char1686–1758 ↗
fn handle_non_ascii_char(&mut self, input: KeyEvent, now: Instant) -> (InputResult, bool)

Purpose: Processes typed non-ASCII characters, including input-method bursts that may behave like paste.

Data flow: It receives a key event and time, either inserts directly when paste-burst detection is disabled, or updates paste-burst buffering, possibly removing recently inserted prefix text, flushing old buffers, inserting the character, and pruning stale pending paste placeholders.

Call relations: Basic input handling calls this for non-ASCII character input so international text and paste detection both work safely.

Call graph: calls 1 internal fn (handle_paste); called by 1 (handle_input_basic_with_time); 2 external calls (clamp_to_char_boundary, unreachable!).

ChatComposer::handle_key_event_with_file_popup1761–1871 ↗
fn handle_key_event_with_file_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

Purpose: Handles keys while the file suggestion popup is open.

Data flow: It receives a key event, lets shortcut overlay keys act first, updates footer mode, moves selection with arrow/control keys, dismisses with Escape, accepts with Tab or Enter, attaches images when selected paths are valid images, otherwise inserts the selected path or falls back to normal input.

Call relations: The main key router calls this whenever the active popup is the file popup.

Call graph: calls 7 internal fn (attach_image, handle_input_basic, handle_key_event_without_popup, handle_shortcut_overlay_key, insert_selected_path, esc_hint_mode, reset_mode_after_activity); called by 1 (handle_key_event); 8 external calls (from, clamp_to_char_boundary, current_at_token, is_image_path, image_dimensions, debug!, trace!, unreachable!).

ChatComposer::handle_key_event_with_skill_popup1874–1945 ↗
fn handle_key_event_with_skill_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

Purpose: Handles keys while the skill mention popup is open.

Data flow: It receives a key event, supports shortcut overlay handling, moves selection, dismisses with Escape, accepts the selected mention with Tab or Enter, or passes ordinary typing to basic input, then closes the popup when needed.

Call relations: The main key router calls this when skill suggestions are visible.

Call graph: calls 5 internal fn (current_mention_token, handle_input_basic, handle_shortcut_overlay_key, insert_selected_mention, reset_mode_after_activity); called by 1 (handle_key_event); 1 external calls (unreachable!).

ChatComposer::handle_key_event_with_mentions_v2_popup1947–2060 ↗
fn handle_key_event_with_mentions_v2_popup(
        &mut self,
        key_event: KeyEvent,
    ) -> (InputResult, bool)

Purpose: Handles keys while the newer combined mention popup is open.

Data flow: It receives a key event, supports selection movement, left/right search-mode switching when possible, dismissal, accepting a selected file or tool mention, and falling back to normal submission if Enter is pressed with no selection.

Call relations: The main key router calls this when the newer mention popup is active.

Call graph: calls 8 internal fn (current_editable_at_token, current_mentions_v2_token, handle_input_basic, handle_key_event_without_popup, handle_shortcut_overlay_key, insert_selected_file_path, insert_selected_mention, reset_mode_after_activity); called by 1 (handle_key_event); 1 external calls (unreachable!).

ChatComposer::is_image_path2062–2069 ↗
fn is_image_path(path: &str) -> bool

Purpose: Checks whether a file path looks like a supported image by its extension.

Data flow: It lowercases the path and returns true for common image endings such as png, jpg, gif, and webp.

Call relations: File selection helpers use this before trying to attach a path as an image.

ChatComposer::insert_selected_file_path2071–2108 ↗
fn insert_selected_file_path(&mut self, selected_path: &str)

Purpose: Inserts a selected file path, or attaches it as an image when it is a readable image file.

Data flow: It receives a path string, checks the extension and image dimensions, removes the currently typed token, attaches the image and inserts a trailing space on success, or inserts the path as plain text on failure.

Call relations: The newer mention popup calls this when the selected result is a file.

Call graph: calls 2 internal fn (attach_image, insert_selected_path); called by 1 (handle_key_event_with_mentions_v2_popup); 6 external calls (from, clamp_to_char_boundary, is_image_path, image_dimensions, debug!, trace!).

ChatComposer::trim_text_elements2110–2144 ↗
fn trim_text_elements(
        original: &str,
        trimmed: &str,
        elements: Vec<TextElement>,
    ) -> Vec<TextElement>

Purpose: Adjusts rich text elements after surrounding whitespace has been trimmed from text.

Data flow: It receives original text, trimmed text, and elements, removes elements outside the trimmed range, shifts overlapping ranges to the new text, and returns adjusted elements.

Call relations: This utility is available to text-preparation paths that need element ranges to keep matching trimmed text.

Call graph: 1 external calls (new).

ChatComposer::expand_pending_pastes2147–2213 ↗
fn expand_pending_pastes(
        text: &str,
        mut elements: Vec<TextElement>,
        pending_pastes: &[(String, String)],
    ) -> (String, Vec<TextElement>)

Purpose: Replaces large-paste placeholders in text with their stored full paste contents.

Data flow: It receives visible text, text elements, and pending paste pairs, walks elements in order, substitutes matching paste placeholders with real content, keeps other elements with updated byte ranges, and returns the rebuilt text and remaining elements.

Call relations: Submission and draft materialization paths call this when compact display placeholders must become real message text.

Call graph: calls 1 internal fn (new); called by 3 (text_with_pending, text_with_pending, materialize_goal_draft); 3 external calls (new, with_capacity, with_capacity).

ChatComposer::skills2215–2217 ↗
fn skills(&self) -> Option<&Vec<SkillMetadata>>

Purpose: Returns the current skill metadata available to the composer.

Data flow: It reads the optional skill list and returns it by reference if present.

Call relations: Outside callers use this to inspect what skill mentions the composer can suggest.

Call graph: called by 1 (skills).

ChatComposer::plugins2219–2221 ↗
fn plugins(&self) -> Option<&Vec<PluginCapabilitySummary>>

Purpose: Returns the current plugin summaries available to the composer.

Data flow: It reads the optional plugin list and returns it by reference if present.

Call relations: Outside callers use this to inspect what plugin mentions or plugin-related features are available.

Call graph: called by 1 (plugins).

ChatComposer::mentions_enabled2223–2238 ↗
fn mentions_enabled(&self) -> bool

Purpose: Checks whether dollar-sign mention suggestions should be available. Mentions are useful only if there is at least one skill, plugin, or enabled connector to mention.

Data flow: It reads the composer’s current lists of skills, plugins, and connector snapshot. If any of those sources has usable entries, it returns true; otherwise it returns false and nothing else changes.

Call relations: When the composer looks for the current dollar-sign mention token, it first calls this guard. If this says mentions are unavailable, the mention-token lookup stops early.

Call graph: called by 1 (current_mention_token).

ChatComposer::current_prefixed_token_range2255–2358 ↗
fn current_prefixed_token_range(
        textarea: &TextArea,
        prefix: char,
        allow_empty: bool,
    ) -> Option<(Range<usize>, String)>

Purpose: Finds the word near the cursor that starts with a chosen prefix, such as '@' or '$'. It returns both where that word sits in the text and the part after the prefix.

Data flow: It takes a text area, a prefix character, and a flag saying whether an empty token is allowed. It reads the cursor and text, adjusts the cursor to a safe character boundary, checks the token on the left and right of the cursor, and returns the matching byte range plus the token text without the prefix. If there is no suitable token, it returns nothing.

Call relations: This is the shared scanner behind file, mention, and command-popup behavior. Other helpers call it so they do not each need to reimplement the careful cursor-and-whitespace rules.

Call graph: calls 2 internal fn (cursor, text).

ChatComposer::current_prefixed_token2360–2366 ↗
fn current_prefixed_token(
        textarea: &TextArea,
        prefix: char,
        allow_empty: bool,
    ) -> Option<String>

Purpose: Returns only the text part of the current prefixed token, hiding the range details. It is a convenience wrapper for callers that only need the query string.

Data flow: It receives the same inputs as the range-finding helper. It asks that helper for a range and token, discards the range, and returns just the token text or nothing.

Call relations: The '@' and '$' token helpers use this when they only need the current search text and do not need to edit the underlying range.

Call graph: 1 external calls (current_prefixed_token_range).

ChatComposer::current_at_token2371–2373 ↗
fn current_at_token(textarea: &TextArea) -> Option<String>

Purpose: Finds the current '@' token around the cursor. This is used for file-style mentions, where typing '@docs' can open file search.

Data flow: It reads the provided text area and asks the generic prefixed-token helper to look for '@'. Empty '@' tokens are not accepted here, so it returns a query only after there is text after the '@'.

Call relations: Command-popup synchronization uses this to avoid showing slash command suggestions while the cursor is inside an '@' file token. Tests also exercise it heavily because cursor position around '@' tokens is subtle.

Call graph: called by 6 (test_current_at_token_allows_file_queries_with_second_at, test_current_at_token_basic_cases, test_current_at_token_cursor_positions, test_current_at_token_ignores_mid_word_at, test_current_at_token_tracks_tokens_with_second_at, test_current_at_token_whitespace_boundaries); 1 external calls (current_prefixed_token).

ChatComposer::current_editable_at_token_with_options2375–2406 ↗
fn current_editable_at_token_with_options(&self, allow_empty: bool) -> Option<String>

Purpose: Finds an '@' token only if it is plain editable text, not an already-inserted atomic mention element. This prevents the composer from trying to autocomplete inside something the user selected earlier.

Data flow: It scans the draft text area for an '@' token and gets its range. It checks whether that exact range is already a protected text element; if so, it returns nothing. It also detects a partially matching protected mention at the start of a larger token and rejects that too. Otherwise it returns the editable token text.

Call relations: The normal file-token helper and the newer mentions-v2 helper both call this. It relies on the generic token scanner and mention-ending checks to separate editable text from inserted mention elements.

Call graph: calls 1 internal fn (ends_plaintext_at_mention); called by 2 (current_editable_at_token, current_mentions_v2_token); 1 external calls (current_prefixed_token_range).

ChatComposer::current_editable_at_token2408–2410 ↗
fn current_editable_at_token(&self) -> Option<String>

Purpose: Gets the current editable '@' token for the older file-search flow. It does not treat a bare '@' as enough to show suggestions.

Data flow: It reads the composer’s draft text area through the options-based helper with empty tokens disallowed. The output is either the current file query text or nothing.

Call relations: Popup synchronization and file-popup key handling call this when deciding whether to show or update the file search popup.

Call graph: calls 1 internal fn (current_editable_at_token_with_options); called by 2 (handle_key_event_with_mentions_v2_popup, sync_popups).

ChatComposer::current_mentions_v2_token2412–2417 ↗
fn current_mentions_v2_token(&self) -> Option<String>

Purpose: Gets the current editable '@' token for the newer combined mention system. Unlike the older helper, it allows a bare '@' so the popup can open immediately.

Data flow: It first checks whether mentions-v2 is enabled. If not, it returns nothing. If enabled, it scans the draft text area for an editable '@' token and allows the token text to be empty.

Call relations: Popup synchronization, mention-popup key handling, and file-search result handling use this when the newer mention experience is active.

Call graph: calls 1 internal fn (current_editable_at_token_with_options); called by 3 (handle_key_event_with_mentions_v2_popup, on_file_search_result, sync_popups).

ChatComposer::current_mention_token2419–2424 ↗
fn current_mention_token(&self) -> Option<String>

Purpose: Finds the current '$' mention token for skills, plugins, or connectors. It only works when there is something mentionable.

Data flow: It checks whether mention sources are available. If they are, it scans the draft text area for a '$' token and allows an empty query, so typing just '$' can show suggestions. It returns the query text or nothing.

Call relations: Skill-popup key handling and popup synchronization call this after the availability check supplied by mentions_enabled.

Call graph: calls 1 internal fn (mentions_enabled); called by 2 (handle_key_event_with_skill_popup, sync_popups); 1 external calls (current_prefixed_token).

ChatComposer::insert_selected_path2431–2471 ↗
fn insert_selected_path(&mut self, path: &str)

Purpose: Replaces the active '@' token with a selected file path. It also adds quotes around paths with spaces when safe, so later command parsing treats the path as one argument.

Data flow: It reads the cursor and current text, safely finds the word around the cursor, builds the path text to insert, replaces only that active word, adds a trailing space, and moves the cursor after the inserted path. The draft text area is changed.

Call relations: File-popup handlers call this after the user chooses a file. It uses character-boundary clamping so editing Unicode text does not accidentally slice through a character.

Call graph: called by 2 (handle_key_event_with_file_popup, insert_selected_file_path); 2 external calls (clamp_to_char_boundary, format!).

ChatComposer::insert_selected_mention2473–2517 ↗
fn insert_selected_mention(&mut self, insert_text: &str, path: Option<&str>)

Purpose: Replaces the active mention token with a selected mention as an atomic text element. Atomic means the composer can remember extra information about it, such as the real path behind the visible mention.

Data flow: It finds the current token around the cursor, removes it, inserts the selected mention text as a text element, optionally records a binding from that element to a path, inserts a trailing space, and moves the cursor after the mention.

Call relations: Both the newer mention popup and the skill popup call this when the user chooses an item. It uses mention_token_from_insert_text to decide whether the inserted text has a valid mention name that can be bound to a path.

Call graph: called by 2 (handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup); 2 external calls (clamp_to_char_boundary, mention_token_from_insert_text).

ChatComposer::mention_token_from_insert_text2519–2537 ↗
fn mention_token_from_insert_text(insert_text: &str) -> Option<(char, String)>

Purpose: Parses inserted mention text like '$skill' or '@file' into its prefix and name. It accepts only simple mention names made of allowed mention characters.

Data flow: It receives visible inserted text. It checks that the first character is '$' or '@', that a name follows, and that every byte in the name is allowed. It returns the prefix and name, or nothing if the text is not a valid mention token.

Call relations: Mention insertion and mention snapshotting use this as their shared definition of what counts as a bindable mention element.

Call graph: 1 external calls (matches!).

ChatComposer::current_mention_elements2539–2549 ↗
fn current_mention_elements(&self) -> Vec<(u64, char, String)>

Purpose: Collects the mention elements currently present in the text area. This lets the composer preserve only bindings for mentions that still exist.

Data flow: It asks the text area for snapshots of its atomic text elements. For each snapshot, it parses the visible text as a mention and returns the element id, prefix, and mention name for valid mentions.

Call relations: Snapshot and take-binding logic call this before saving mention metadata. It depends on mention_token_from_insert_text to ignore non-mention elements.

Call graph: called by 2 (snapshot_mention_bindings, take_mention_bindings).

ChatComposer::snapshot_mention_bindings2551–2566 ↗
fn snapshot_mention_bindings(&self) -> Vec<MentionBinding>

Purpose: Creates an ordered list of the current mention-to-path bindings. This is how the composer remembers what a visible mention actually points to when saving history or submitting.

Data flow: It walks the current mention elements in text order. For each element id, it checks the draft’s binding table and keeps the binding only if the visible prefix and name still match. It returns a clean list of bindings without changing the draft.

Call relations: Submission preparation, history staging, draft snapshotting, and clearing flows call this before text is changed or sent.

Call graph: calls 1 internal fn (current_mention_elements); called by 6 (clear_for_ctrl_c, handle_submission_with_time, mention_bindings, prepare_submission_text_with_options, snapshot_draft, stage_slash_command_history_text); 1 external calls (new).

ChatComposer::bind_mentions_from_snapshot2568–2604 ↗
fn bind_mentions_from_snapshot(&mut self, mention_bindings: Vec<MentionBinding>)

Purpose: Restores mention bindings after text is loaded from a saved snapshot or history entry. It reconnects visible mention text to its hidden path metadata.

Data flow: It clears the current binding table, then scans the current text for each saved mention token in order. When it finds a token, it marks that range as a text element if needed and stores the binding under the element id.

Call relations: Text-loading code calls this after setting text content with saved mention bindings. It uses the mention-token range finder so bindings attach to the right occurrence, not just any matching text.

Call graph: calls 1 internal fn (find_next_mention_token_range); called by 1 (set_text_content_with_mention_bindings); 1 external calls (format!).

ChatComposer::plugin_at_mention_highlights2606–2619 ↗
fn plugin_at_mention_highlights(&self) -> Vec<(Range<usize>, Style)>

Purpose: Finds plugin '@' mentions that should be colored specially. This gives plugin mentions a visual cue in the composer.

Data flow: It scans atomic text elements, looks up each element’s binding, and keeps only those whose path starts with 'plugin://' and whose visible text starts with '@'. It returns ranges paired with a magenta style.

Call relations: Rendering calls this to add highlight ranges while drawing the text area.

Call graph: called by 1 (render_with_mask_and_textarea_right_reserve).

ChatComposer::prepare_submission_text2625–2634 ↗
fn prepare_submission_text(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

Purpose: Prepares the current draft for normal submission using the default rules. It validates slash commands immediately and expands pending paste placeholders.

Data flow: It receives a flag saying whether to record history. It delegates to the options-based submission preparer and returns the final text plus text elements, or nothing if submission should not happen.

Call relations: Submission handling and inline-argument preparation use this simpler entry point when they do not need special queueing behavior.

Call graph: calls 1 internal fn (prepare_submission_text_with_options); called by 2 (handle_submission_with_time, prepare_inline_args_submission).

ChatComposer::prepare_submission_text_with_options2636–2734 ↗
fn prepare_submission_text_with_options(
        &mut self,
        record_history: bool,
        slash_validation: SlashValidation,
        pending_paste_handling: PendingPasteHandling,
    ) -> Opti

Purpose: Turns the current draft into sendable text while preserving or restoring important state. It is the main safety checkpoint before a message or queued command leaves the composer.

Data flow: It copies the current text, text elements, mention bindings, images, and pending pastes. It clears the input area, optionally expands paste placeholders, trims the text, validates slash commands, checks the maximum size, prunes unused image attachments, records history if requested, clears pending pastes, and returns the final text and elements. If validation fails, it restores the original draft and returns nothing.

Call relations: Submission handling calls this for both immediate and queued sends. It also talks to the app event channel to show error or info messages when submission is rejected.

Call graph: calls 8 internal fn (send, current_text, current_text_elements, set_text_content_with_mention_bindings, slash_input, snapshot_mention_bindings, user_input_too_large_message, record_local_submission); called by 2 (handle_submission_with_time, prepare_submission_text); 12 external calls (new, expand_pending_pastes, trim_text_elements, new, InsertHistoryCell, is_empty, local_image_paths, prune_local_images_for_submission, remote_image_urls, format! (+2 more)).

ChatComposer::handle_submission2738–2742 ↗
fn handle_submission(&mut self, should_queue: bool) -> (InputResult, bool)

Purpose: Handles a user pressing the submit key using the current time. After a successful dispatch, it puts Vim-style editing back into normal mode.

Data flow: It receives a flag saying whether the input should be queued. It calls the time-aware submission handler, inspects the resulting input action, possibly changes the text area’s Vim mode, and returns the action plus redraw flag.

Call relations: Normal key handling calls this when submit or queue shortcuts are pressed. It wraps handle_submission_with_time so tests or lower-level code can use an explicit timestamp.

Call graph: calls 2 internal fn (handle_submission_with_time, reset_vim_mode_after_successful_dispatch); called by 1 (handle_key_event_without_popup); 1 external calls (now).

ChatComposer::reset_vim_mode_after_successful_dispatch2744–2755 ↗
fn reset_vim_mode_after_successful_dispatch(&mut self, result: &InputResult)

Purpose: Switches the editor back to Vim normal mode after the composer successfully sends or dispatches something. This keeps Vim users from staying in insert mode after an action completes.

Data flow: It receives an input result. If the result means a submission, queue, or command was dispatched, it tells the text area to enter Vim normal mode; otherwise it leaves the mode unchanged.

Call relations: Submission handling and top-level key handling call this after actions that may have sent work to the rest of the app.

Call graph: called by 2 (handle_key_event, handle_submission); 1 external calls (matches!).

ChatComposer::handle_submission_with_time2757–2894 ↗
fn handle_submission_with_time(
        &mut self,
        should_queue: bool,
        now: Instant,
    ) -> (InputResult, bool)

Purpose: Decides what pressing submit actually means right now: queue text, run a slash command, add a newline during a paste burst, or send a normal message.

Data flow: It reads queue state, paste-burst state, slash-command context, current draft text, elements, attachments, and mention bindings. It may flush pasted text, preserve or expand pending pastes, dispatch slash commands, prepare final submission text, or restore the draft if nothing should be sent. It returns an InputResult and a redraw flag.

Call relations: handle_submission calls this. Inside, it coordinates many specialized helpers: paste handling, slash command dispatch, submission preparation, and draft restoration.

Call graph: calls 13 internal fn (current_text, current_text_elements, handle_paste, prepare_submission_text, prepare_submission_text_with_options, set_text_content_with_mention_bindings, slash_commands_enabled, slash_input, snapshot_mention_bindings, try_dispatch_bare_slash_command (+3 more)); called by 1 (handle_submission); 3 external calls (new, local_image_paths, matches!).

ChatComposer::try_dispatch_bare_slash_command2898–2914 ↗
fn try_dispatch_bare_slash_command(&mut self) -> Option<InputResult>

Purpose: Runs a slash command that has no arguments, such as a completed '/diff' command. This works even if the command suggestion popup is no longer visible.

Data flow: It asks the slash-input parser whether the current text is a bare command. If no, it returns nothing. If yes, it checks whether the command is allowed, stages it for history, clears the draft when dispatching, and returns the appropriate command result.

Call relations: Submission handling tries this before treating Enter as normal message submission. It uses availability checks and history staging to keep command behavior consistent.

Call graph: calls 4 internal fn (record_pending_slash_command_history, reject_slash_command_if_unavailable, slash_input, stage_slash_command_history); called by 1 (handle_submission_with_time); 2 external calls (Command, ServiceTierCommand).

ChatComposer::try_dispatch_slash_command_with_args2918–2945 ↗
fn try_dispatch_slash_command_with_args(&mut self) -> Option<InputResult>

Purpose: Runs a slash command that includes inline arguments. It separates the command name from the argument text and carries along any text elements that belong to the arguments.

Data flow: It parses the current text as an inline command. If unavailable, it records the attempted command and returns a no-op result. If valid and built in, it extracts and trims the argument text, trims matching text elements, and returns a command-with-arguments result.

Call relations: Submission handling calls this before normal message preparation. It shares command rejection and history staging with the bare-command path.

Call graph: calls 5 internal fn (record_pending_slash_command_history, reject_slash_command_if_unavailable, slash_input, stage_slash_command_history, args_elements); called by 1 (handle_submission_with_time); 2 external calls (trim_text_elements, CommandWithArgs).

ChatComposer::prepare_inline_args_submission2957–2968 ↗
fn prepare_inline_args_submission(
        &mut self,
        record_history: bool,
    ) -> Option<(String, Vec<TextElement>)>

Purpose: Prepares a draft that contains a slash command and returns only the command’s argument text. This is useful when another flow needs the cleaned-up arguments rather than the whole submitted line.

Data flow: It first prepares the full submission text. Then it asks the slash parser for the argument slice, extracts the matching text elements, trims whitespace from the argument text, trims the elements to match, and returns the cleaned arguments.

Call relations: This function builds on normal submission preparation and the slash-input argument helpers, so inline arguments get the same paste expansion and history behavior as regular submissions.

Call graph: calls 3 internal fn (prepare_submission_text, args_elements, prepared_args); called by 1 (prepare_inline_args_submission); 1 external calls (trim_text_elements).

ChatComposer::reject_slash_command_if_unavailable2970–2982 ↗
fn reject_slash_command_if_unavailable(&self, command: &SlashCommandItem) -> bool

Purpose: Blocks slash commands that are not allowed while a task is already running. It also explains the problem to the user.

Data flow: It reads whether a task is running and asks the command whether it is available during a task. If the command is blocked, it sends an error history cell through the app event channel and returns true. Otherwise it returns false.

Call relations: Both bare-command and command-with-arguments dispatch call this before they actually dispatch a slash command.

Call graph: calls 2 internal fn (send, available_during_task); called by 2 (try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args); 4 external calls (new, InsertHistoryCell, format!, new_error_event).

ChatComposer::stage_slash_command_history2989–2994 ↗
fn stage_slash_command_history(&mut self, command: &SlashCommandItem)

Purpose: Prepares the current slash command text to be saved in history after command handling. It skips the clear command so clearing the screen does not itself become a recalled entry.

Data flow: It receives a slash command item. If it is not the clear command, it trims the current text and passes it to the history-staging helper.

Call relations: Both slash-command dispatch paths call this before returning their command result or no-op result.

Call graph: calls 1 internal fn (stage_slash_command_history_text); called by 2 (try_dispatch_bare_slash_command, try_dispatch_slash_command_with_args); 1 external calls (matches!).

ChatComposer::stage_selected_slash_command_history3000–3005 ↗
fn stage_selected_slash_command_history(&mut self, command: &CommandItem)

Purpose: Stages a slash command chosen from a popup for history. Like the normal staging path, it avoids recording the clear command.

Data flow: It receives a selected command item. If it is not clear, it formats the command name with a leading slash and stores it through the history-staging helper.

Call relations: This supports popup-based command selection, using the same final staging helper as typed slash commands.

Call graph: calls 1 internal fn (stage_slash_command_history_text); 2 external calls (format!, matches!).

ChatComposer::stage_slash_command_history_text3012–3021 ↗
fn stage_slash_command_history_text(&mut self, text: String)

Purpose: Stores a full history entry for a slash command that is about to run. It includes not just text, but elements, attachments, mention bindings, and pending paste placeholders.

Data flow: It receives the text to record, reads the current text elements, attachment paths and URLs, mention bindings, and pending pastes, and saves them into a pending history entry on the composer.

Call relations: Slash-command staging helpers call this so command history preserves the same rich draft information as normal message history.

Call graph: calls 1 internal fn (snapshot_mention_bindings); called by 2 (stage_selected_slash_command_history, stage_slash_command_history); 2 external calls (local_image_paths, remote_image_urls).

ChatComposer::handle_remote_image_selection_key3023–3029 ↗
fn handle_remote_image_selection_key(
        &mut self,
        key_event: &KeyEvent,
    ) -> Option<(InputResult, bool)>

Purpose: Lets attachment handling react to keys when a remote image is selected. This keeps image-selection keyboard behavior out of the general text-editing path.

Data flow: It receives a key event and passes it, along with the draft text area, to the attachment subsystem. The attachment subsystem may return an input result and redraw decision.

Call relations: Key handling without a popup tries this first, so image selection gets priority before normal typing or submission shortcuts.

Call graph: called by 1 (handle_key_event_without_popup); 1 external calls (handle_remote_image_selection_key).

ChatComposer::handle_key_event_without_popup3032–3162 ↗
fn handle_key_event_without_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool)

Purpose: Handles a keyboard event when no suggestion popup is taking over the keys. It is the main decision tree for normal typing, submitting, history navigation, shell mode, Vim mode, and shortcuts.

Data flow: It receives one key event. It first gives remote image selection and shortcut overlays a chance, handles special shell and Vim cases, updates footer hints, checks queue and submit shortcuts, handles Ctrl-D on an empty input, optionally navigates input history, and finally falls back to basic text input. It returns the resulting action and whether the screen should redraw.

Call relations: The top-level key handler and popup-specific handlers call this when the key should be treated as normal composer input. It delegates submission, paste, history, and basic input to specialized helpers.

Call graph: calls 16 internal fn (apply_history_entry, current_text, handle_input_basic, handle_paste, handle_remote_image_selection_key, handle_shortcut_overlay_key, handle_submission, history_navigation_cursor, is_bang_shell_command, is_empty (+6 more)); called by 3 (handle_key_event, handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup); 2 external calls (clear_remote_image_selection, matches!).

ChatComposer::is_bang_shell_command3164–3166 ↗
fn is_bang_shell_command(&self) -> bool

Purpose: Checks whether the current draft starts with '!', which marks it as a shell-style command. Leading spaces are ignored.

Data flow: It reads the current text, trims only the start, and returns true if the first meaningful character is '!'. It does not change anything.

Call relations: Key handling uses this when deciding whether queue shortcuts should submit or queue. Rendering uses it to show the shell-mode footer line.

Call graph: calls 1 internal fn (current_text); called by 2 (handle_key_event_without_popup, shell_mode_footer_line).

ChatComposer::handle_paste_burst_flush3182–3194 ↗
fn handle_paste_burst_flush(&mut self, now: Instant) -> bool

Purpose: Flushes buffered fast input when it is time to decide whether it was a paste or just typed characters. This prevents pasted text from being treated as many separate keystrokes.

Data flow: It asks the paste-burst detector whether anything is due to flush. A paste is sent to paste handling, a single typed character is inserted normally, and no pending data means no change. It returns whether anything was flushed.

Call relations: Basic input handling calls this before processing new keys, and timed flushing calls it when the paste-burst window expires.

Call graph: calls 2 internal fn (handle_paste, insert_str); called by 2 (flush_paste_burst_if_due, handle_input_basic_with_time).

ChatComposer::handle_input_basic3210–3218 ↗
fn handle_input_basic(&mut self, input: KeyEvent) -> (InputResult, bool)

Purpose: Handles ordinary text-area input, ignoring key-release events so a key is not processed twice. It uses the current time for paste-burst detection.

Data flow: It receives a key event. If the event is not a press or repeat, it returns no action and no redraw. Otherwise it passes the key and current timestamp to the time-aware basic input handler.

Call relations: All key-handling paths use this as the fallback when a key is not consumed by a popup, submission shortcut, or special mode.

Call graph: calls 1 internal fn (handle_input_basic_with_time); called by 4 (handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup); 2 external calls (now, matches!).

ChatComposer::handle_input_basic_with_time3220–3363 ↗
fn handle_input_basic_with_time(
        &mut self,
        input: KeyEvent,
        now: Instant,
    ) -> (InputResult, bool)

Purpose: Applies a normal keypress to the text area while detecting paste bursts, maintaining shell mode, and cleaning up deleted elements. It is the low-level typing path.

Data flow: It receives a key event and timestamp. It flushes old paste bursts, updates footer state, may buffer fast plain characters as a likely paste, flushes buffered text before modified or non-character keys, records elements before editing if needed, updates bash mode on special backspace or leading '!', sends the key to the text area, reconciles deleted elements, updates the paste-burst timing window, and returns no external action with redraw requested.

Call relations: handle_input_basic calls this. It coordinates paste handling, non-ASCII handling, text-area editing, element cleanup, and mode synchronization.

Call graph: calls 7 internal fn (handle_non_ascii_char, handle_paste, handle_paste_burst_flush, reconcile_deleted_elements, sync_bash_mode_from_text, reset_mode_after_activity, has_ctrl_or_alt); called by 1 (handle_input_basic); 3 external calls (clamp_to_char_boundary, is_empty, matches!).

ChatComposer::sync_bash_mode_from_text3365–3370 ↗
fn sync_bash_mode_from_text(&mut self)

Purpose: Turns a leading '!' into internal bash mode instead of leaving the exclamation mark in the text. This lets the composer remember the mode separately from the command text.

Data flow: It checks whether bash mode is off and the text starts with '!'. If so, it removes that first character from the text area and sets bash mode on.

Call relations: Basic input handling calls this after text edits, and direct string insertion also calls it, so both typed and programmatic text can activate bash mode.

Call graph: called by 2 (handle_input_basic_with_time, insert_str).

ChatComposer::reconcile_deleted_elements3372–3385 ↗
fn reconcile_deleted_elements(&mut self, elements_before: Vec<String>)

Purpose: Cleans up hidden state for atomic text elements that the user deleted. This prevents stale paste placeholders or image references from surviving after their visible marker is gone.

Data flow: It receives the list of element payloads from before an edit. It compares them with the payloads still present after the edit, removes pending paste records for deleted payloads, and asks the attachment subsystem to remove deleted local-image placeholders.

Call relations: Basic input handling calls this after editing when pending pastes or attachments exist. It relies on the text area’s element payload list instead of scanning all text manually.

Call graph: called by 1 (handle_input_basic_with_time); 1 external calls (remove_deleted_local_placeholders).

ChatComposer::handle_shortcut_overlay_key3393–3414 ↗
fn handle_shortcut_overlay_key(&mut self, key_event: &KeyEvent) -> bool

Purpose: Toggles the shortcut-help overlay when the configured key is pressed on an empty composer. It avoids toggling while a paste burst is in progress.

Data flow: It receives a key event. If it is not a key press or the toggle conditions are not met, it returns false. Otherwise it computes the next footer mode, stores it, and returns whether the mode changed.

Call relations: Normal and popup key handlers call this early so shortcut-help keys can be consumed before regular typing.

Call graph: calls 4 internal fn (is_empty, is_in_paste_burst, quit_shortcut_hint_visible, toggle_shortcut_mode); called by 4 (handle_key_event_with_file_popup, handle_key_event_with_mentions_v2_popup, handle_key_event_with_skill_popup, handle_key_event_without_popup).

ChatComposer::sync_popups3499–3589 ↗
fn sync_popups(&mut self)

Purpose: Keeps suggestion popups in sync with what the user is currently typing. It decides whether command suggestions, file search, skill mentions, or newer combined mentions should be shown.

Data flow: It updates slash-command elements, exits early during history search or when popups are disabled, finds current '@' and '$' tokens, suppresses popups during history navigation, lets the command popup take priority when appropriate, then shows or updates the mention/file popup that matches the current token. It also cancels stale file searches and clears dismissed-token state when needed.

Call relations: Many editing flows call this after text or cursor changes, including key handling, paste handling, history response handling, external edits, and cursor movement. It delegates each popup type to its own sync helper.

Call graph: calls 13 internal fn (send, current_editable_at_token, current_mention_token, current_mentions_v2_token, current_text, history_navigation_cursor, popups_enabled, slash_commands_enabled, sync_command_popup, sync_file_search_popup (+3 more)); called by 17 (on_history_entry_response, pre_draw_tick_at, apply_external_edit, handle_key_event, handle_paste, insert_str, move_cursor_to_end, move_cursor_to_history_entry_end, restore_draft, set_connector_mentions (+7 more)); 3 external calls (new, StartFileSearch, matches!).

ChatComposer::sync_command_popup3594–3644 ↗
fn sync_command_popup(&mut self, allow: bool)

Purpose: Shows, updates, or hides slash command suggestions based on whether the cursor is editing the first-line command name. It avoids competing with file search inside '@' tokens.

Data flow: It receives a flag saying whether command popups are allowed. If not, it closes an active command popup. If allowed, it reads the first line and cursor position, asks the slash parser for command-name editing state and filter text, suppresses itself inside an '@' token, and then updates an existing popup or creates a new one.

Call relations: sync_popups calls this before file and mention popup handling. It uses slash-input helpers to understand command syntax.

Call graph: calls 1 internal fn (slash_input); called by 1 (sync_popups); 3 external calls (current_at_token, matches!, Command).

ChatComposer::sync_file_search_popup3647–3685 ↗
fn sync_file_search_popup(&mut self, query: String)

Purpose: Starts or updates file search suggestions for the current '@' query. It also records which query is currently being searched.

Data flow: It receives the query string. If that exact token was dismissed, it does nothing. Otherwise it sends a file-search request through the app event channel, updates or creates a file popup, records the current query when non-empty, and clears the dismissed marker.

Call relations: sync_popups calls this when the current editable '@' token belongs to the older file-search flow.

Call graph: calls 2 internal fn (send, new); called by 1 (sync_popups); 3 external calls (new, StartFileSearch, File).

ChatComposer::sync_mention_popup3687–3709 ↗
fn sync_mention_popup(&mut self, query: String)

Purpose: Shows or updates the older '$' mention popup for skills, plugins, and connectors. It filters the list using the user’s current query.

Data flow: It receives a query string. If that query was dismissed, it stops. Otherwise it builds the current mention item list. If the list is empty, it closes popups; if not, it updates an existing skill popup or creates a new one with the query.

Call relations: sync_popups calls this when a dollar-sign mention token is active. It gets its candidate items from mention_items.

Call graph: calls 2 internal fn (mention_items, new); called by 1 (sync_popups); 1 external calls (Skill).

ChatComposer::sync_mentions_v2_popup3711–3744 ↗
fn sync_mentions_v2_popup(&mut self, query: String)

Purpose: Shows or updates the newer combined mention popup for '@' mentions. This flow can search both files and mention candidates together.

Data flow: It receives the query string. If dismissed, it stops. It sends a file-search request for the query, updates the current file-query state, builds a search catalog from skills and plugins, then updates or creates the mentions-v2 popup with that query and candidate list.

Call relations: sync_popups calls this when mentions-v2 is enabled and an editable '@' token is active.

Call graph: calls 1 internal fn (send); called by 1 (sync_popups); 5 external calls (new, new, StartFileSearch, build_search_catalog, MentionV2).

ChatComposer::mention_items3746–3847 ↗
fn mention_items(&self) -> Vec<MentionItem>

Purpose: Builds the list of things that can appear in the older mention popup: skills, plugins, and enabled app connectors. Each item includes display text, search terms, insertion text, category label, and optional path.

Data flow: It reads available skills, plugins, and connector snapshot data. For each usable item, it creates a MentionItem with a human-friendly name, description, text to insert, searchable aliases, path metadata, category tag, and sorting rank. It returns the full list.

Call relations: sync_mention_popup calls this whenever it needs fresh mention candidates. It uses helper functions to format connector names and descriptions.

Call graph: calls 4 internal fn (connector_display_label, connector_mention_slug, skill_description, skill_display_name); called by 1 (sync_mention_popup); 4 external calls (connector_brief_description, new, format!, vec!).

ChatComposer::connector_brief_description3849–3851 ↗
fn connector_brief_description(connector: &AppInfo) -> String

Purpose: Returns a short description for an app connector, falling back to an empty string when none is available. This keeps mention items from displaying missing optional text awkwardly.

Data flow: It receives connector metadata, asks the fuller description helper for cleaned text, and returns that text or an empty string.

Call relations: mention_items uses this while building connector mention candidates.

Call graph: 1 external calls (connector_description).

ChatComposer::connector_description3853–3860 ↗
fn connector_description(connector: &AppInfo) -> Option<String>

Purpose: Extracts a cleaned connector description if one exists. Blank or whitespace-only descriptions are treated as missing.

Data flow: It receives connector metadata, reads the optional description, trims surrounding whitespace, filters out empty results, and returns the cleaned string or nothing.

Call relations: connector_brief_description calls this so connector mention items get tidy description text.

ChatComposer::set_has_focus3862–3864 ↗
fn set_has_focus(&mut self, has_focus: bool)

Purpose: Records whether the composer currently has keyboard focus. Other UI behavior can use this to decide which hints or reminders should be visible.

Data flow: It receives a boolean and stores it in the composer’s focus field. It returns nothing.

Call relations: Quit-shortcut hint helpers call this when focus changes as part of showing or clearing the hint.

Call graph: called by 2 (clear_quit_shortcut_hint, show_quit_shortcut_hint).

ChatComposer::set_input_enabled3867–3875 ↗
fn set_input_enabled(&mut self, enabled: bool, placeholder: Option<String>)

Purpose: Enables or disables typing in the composer, optionally showing placeholder text while disabled. It also closes interactive popups when input is blocked.

Data flow: It receives an enabled flag and optional placeholder. It stores the input-enabled state, keeps the placeholder only when disabled, and if disabling while a popup is active, closes the popup.

Call relations: Higher-level UI flows call this when composer input should be blocked, such as during shutdown or through an external input-enabled setting.

Call graph: calls 1 internal fn (active); called by 2 (set_composer_input_enabled, show_shutdown_in_progress).

ChatComposer::show_shutdown_in_progress3877–3884 ↗
fn show_shutdown_in_progress(&mut self)

Purpose: Puts the composer into a safe shutdown display state. It disables typing and replaces normal footer hints with a quiet “Shutting down...” state so the user cannot start new input while the app is closing.

Data flow: It starts with the current composer state. It turns input off with a shutdown message, clears quit reminders, footer hints, plan-mode nudges, and flash messages, and leaves the footer in an empty-composer mode.

Call relations: When shutdown handling calls this composer method, it delegates the actual input lockout to set_input_enabled and then cleans up footer state so rendering later shows only the shutdown-safe view.

Call graph: calls 1 internal fn (set_input_enabled); called by 1 (show_shutdown_in_progress); 1 external calls (new).

ChatComposer::set_task_running3886–3888 ↗
fn set_task_running(&mut self, running: bool)

Purpose: Records whether the assistant or another task is currently running. The composer uses this to decide which hints to show, such as whether a new message would be queued instead of sent immediately.

Data flow: It receives a true-or-false value and stores it in the composer. Nothing is returned; later footer and input logic read this flag.

Call relations: Other parts of the app call this when work starts or stops. Rendering code later reads the flag to choose footer modes and hints.

Call graph: called by 1 (set_task_running).

ChatComposer::set_queue_submissions3890–3892 ↗
fn set_queue_submissions(&mut self, queue_submissions: bool)

Purpose: Records whether typed submissions should be queued while another task is active. This affects how the composer explains Enter or submission behaviour to the user.

Data flow: It receives a true-or-false value and saves it on the composer. The visible effect appears later when footer text is built.

Call relations: The wider app updates this setting when queueing policy changes. Footer rendering uses it when showing shortcut or queue hints.

Call graph: called by 1 (set_queue_submissions).

ChatComposer::set_context_window3894–3902 ↗
fn set_context_window(&mut self, percent: Option<i64>, used_tokens: Option<i64>)

Purpose: Updates the footer’s context-window display, which tells the user how much conversation memory is being used. A context window is the model’s limited space for remembering the current conversation.

Data flow: It receives an optional percentage and optional token count. If both are unchanged, it does nothing; otherwise it stores the new values in the footer.

Call relations: The app calls this when context usage changes. Later footer rendering includes the updated usage information when there is room.

Call graph: called by 1 (set_context_window).

ChatComposer::set_esc_backtrack_hint3904–3911 ↗
fn set_esc_backtrack_hint(&mut self, show: bool)

Purpose: Shows or hides a footer hint about using Escape to go back or cancel. It also adjusts the footer mode so the hint fits with whether a task is currently running.

Data flow: It receives a true-or-false request. When true, it marks the hint visible and asks esc_hint_mode for the right footer mode; when false, it clears the hint and resets the footer mode after activity.

Call relations: Clear/show wrapper methods call this when Escape guidance changes. It hands mode selection to esc_hint_mode or reset_mode_after_activity so the footer remains consistent.

Call graph: calls 2 internal fn (esc_hint_mode, reset_mode_after_activity); called by 2 (clear_esc_backtrack_hint, show_esc_backtrack_hint).

ChatComposer::set_status_line3913–3919 ↗
fn set_status_line(&mut self, status_line: Option<Line<'static>>) -> bool

Purpose: Changes the passive status line shown in the footer, such as model, directory, or context information. It reports whether anything actually changed so callers can avoid unnecessary redraws.

Data flow: It receives an optional rendered line. If it matches the current line, it returns false; otherwise it stores the new line and returns true.

Call relations: The app calls this when status text changes. Rendering later reads the stored line when passive footer status is enabled.

Call graph: called by 1 (set_status_line).

ChatComposer::set_status_line_enabled3929–3935 ↗
fn set_status_line_enabled(&mut self, enabled: bool) -> bool

Purpose: Turns the footer status-line layout on or off. This lets the app switch between ordinary shortcut hints and a more status-focused footer.

Data flow: It receives a true-or-false value. If it is already set, it returns false; otherwise it stores the new setting and returns true.

Call relations: The rest of the interface calls this when status-line display should change. Footer rendering checks this flag before choosing the passive status layout.

Call graph: called by 1 (set_status_line_enabled).

ChatComposer::set_side_conversation_context_label3937–3943 ↗
fn set_side_conversation_context_label(&mut self, label: Option<String>) -> bool

Purpose: Sets a right-side footer label for a side conversation or alternate context. This helps the user see which extra conversation context is active.

Data flow: It receives an optional label. If the label is unchanged it returns false; otherwise it saves it and returns true.

Call relations: Conversation-context code calls this when the active side context changes. Rendering gives this label priority on the right side of the footer.

Call graph: called by 1 (set_side_conversation_context_label).

ChatComposer::set_active_agent_label3950–3956 ↗
fn set_active_agent_label(&mut self, active_agent_label: Option<String>) -> bool

Purpose: Stores a label for the currently active agent. This gives the footer enough information to name the active assistant mode or agent when needed.

Data flow: It receives an optional label string, compares it with the current value, stores it only if different, and returns whether it changed.

Call relations: Agent-selection code calls this when the active agent changes. Footer-building logic later uses the stored label as part of the visible status.

Call graph: called by 1 (set_active_agent_label).

skill_description3976–3985 ↗
fn skill_description(skill: &SkillMetadata) -> Option<String>

Purpose: Chooses a short, readable description for a skill to show in mention or skill-picking UI. It prefers the most specific short description available and falls back to the full description.

Data flow: It receives skill metadata, checks the interface short description, then the skill short description, then the full description, trims whitespace, and returns text only if something non-empty remains.

Call relations: Mention item building calls this while preparing choices for the user. It keeps empty descriptions out of the popup.

Call graph: called by 1 (mention_items).

is_mention_name_char3987–3989 ↗
fn is_mention_name_char(byte: u8) -> bool

Purpose: Checks whether a byte can be part of a mention name. Mention names allow letters, numbers, underscore, and hyphen.

Data flow: It receives one byte and returns true if that byte is an allowed mention-name character, otherwise false.

Call relations: Mention-scanning logic uses this when deciding where a mention ends, especially for non-@ mention forms.

Call graph: 1 external calls (matches!).

ends_plaintext_at_mention3991–4004 ↗
fn ends_plaintext_at_mention(bytes: &[u8], index: usize) -> bool

Purpose: Decides whether an @ mention ends cleanly at a given byte position. This prevents partial matches inside paths or longer names from being treated as real mentions.

Data flow: It receives the text as bytes and an index just after a candidate mention. It checks the next byte, if any, and returns true only when the following character is a safe boundary such as whitespace or punctuation that is not part of a name or path.

Call relations: Mention detection calls this after finding a possible @ mention. It is one of the guards that stops restored mentions from attaching to the wrong text.

Call graph: called by 2 (current_editable_at_token_with_options, find_next_mention_token_range).

starts_plaintext_at_mention4006–4014 ↗
fn starts_plaintext_at_mention(text: &str, index: usize) -> bool

Purpose: Decides whether an @ mention starts at a safe place in plain text. This avoids treating the @ inside an email address or embedded word as a mention.

Data flow: It receives the full text and an index where a candidate mention starts. It returns true at the beginning of text, or when the previous character is whitespace or not allowed in mention names.

Call relations: find_next_mention_token_range calls this for @ mentions before accepting a match. It relies on is_mention_name_char_char for character-level checks.

Call graph: called by 1 (find_next_mention_token_range).

is_mention_name_char_char4016–4018 ↗
fn is_mention_name_char_char(ch: char) -> bool

Purpose: Checks whether a character can be part of a mention name. This is the character version of the byte helper.

Data flow: It receives one character and returns true for ASCII letters, digits, underscore, or hyphen.

Call relations: starts_plaintext_at_mention uses this when looking at the character before a possible @ mention.

Call graph: 1 external calls (matches!).

find_next_mention_token_range4020–4069 ↗
fn find_next_mention_token_range(text: &str, token: &str, from: usize) -> Option<Range<usize>>

Purpose: Finds the next real occurrence of a mention token in text. It is careful not to match text that merely contains the same characters inside an email address, path, or longer name.

Data flow: It receives the full text, the token to find, and a starting byte index. It scans forward for the token’s first character, checks the full token, verifies safe boundaries, and returns the byte range of the first valid match if one exists.

Call relations: Mention rebinding code calls this when restoring mention metadata from saved text. It uses the start and end boundary helpers for @ mentions, and the simpler name-character check for other mention sigils.

Call graph: calls 2 internal fn (ends_plaintext_at_mention, starts_plaintext_at_mention); called by 1 (bind_mentions_from_snapshot).

ChatComposer::cursor_pos4072–4074 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Reports where the terminal cursor should appear for this composer. This lets the outer UI place the real blinking cursor over the text input.

Data flow: It receives the drawing area and asks the more general cursor-position method to calculate the position with no right-side reserved space.

Call relations: The terminal UI calls this through the widget interface. It delegates to cursor_pos_with_textarea_right_reserve so the same logic can also serve layouts with reserved space.

Call graph: calls 1 internal fn (cursor_pos_with_textarea_right_reserve); called by 2 (cursor_pos, cursor_pos).

ChatComposer::cursor_style4076–4082 ↗
fn cursor_style(&self, _area: Rect) -> crossterm::cursor::SetCursorStyle

Purpose: Chooses the shape of the terminal cursor. In Vim insert mode it uses a steady vertical bar, otherwise it leaves the user’s default cursor shape alone.

Data flow: It reads the textarea’s Vim cursor setting and returns the appropriate crossterm cursor style command.

Call relations: The UI asks this while drawing or focusing the composer. It does not call other project logic in this chunk; it simply translates editor mode into terminal cursor appearance.

Call graph: called by 1 (cursor_style).

ChatComposer::desired_height4084–4086 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows the composer wants. This helps the rest of the layout reserve enough space for the input, attachments, popups, and footer.

Data flow: It receives a width and delegates to the wider height calculator with no right-side reserved space.

Call relations: Layout code calls this when deciding input height. It shares the real calculation with desired_height_with_textarea_right_reserve.

Call graph: calls 1 internal fn (desired_height_with_textarea_right_reserve); called by 3 (input_height, notes_input_height, desired_height).

ChatComposer::render4088–4090 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the composer normally, without masking typed text. This is the standard render entry point for the widget.

Data flow: It receives a rectangle and a terminal buffer, then forwards them to render_with_mask with no mask character.

Call relations: The terminal renderer and tests call this when the composer should be painted. It delegates to the more flexible masked rendering path.

Call graph: calls 1 internal fn (render_with_mask); called by 3 (plugin_mention_foreground_color, render_input, render_ref).

ChatComposer::desired_height_with_textarea_right_reserve4094–4126 ↗
fn desired_height_with_textarea_right_reserve(
        &self,
        width: u16,
        textarea_right_reserve: u16,
    ) -> u16

Purpose: Calculates the composer height when part of the input row must be reserved for something on the right. It accounts for text wrapping, image attachments, footer spacing, and open popups.

Data flow: It receives the available width and a right-side reserve. It subtracts prompt and reserve columns, asks the textarea for its height, adds remote-image preview rows and separators, adds fixed padding, and then adds either footer height or popup height.

Call relations: desired_height and other layout code call this before rendering. It reads footer properties, custom footer height, attachments, and active popup type to produce the final row count.

Call graph: calls 2 internal fn (custom_footer_height, footer_props); called by 2 (desired_height, desired_height); 3 external calls (footer_spacing, remote_image_lines, from).

ChatComposer::render_with_mask4130–4134 ↗
fn render_with_mask(&self, area: Rect, buf: &mut Buffer, mask_char: Option<char>)

Purpose: Draws the composer while optionally hiding the actual typed characters behind a mask character. This is useful for sensitive input.

Data flow: It receives the drawing area, buffer, and optional mask character, then forwards them with no right-side reserve.

Call relations: Normal render and input-specific rendering call this. The full work is done by render_with_mask_and_textarea_right_reserve.

Call graph: calls 1 internal fn (render_with_mask_and_textarea_right_reserve); called by 2 (render, render_input).

ChatComposer::render_with_mask_and_textarea_right_reserve4136–4462 ↗
fn render_with_mask_and_textarea_right_reserve(
        &self,
        area: Rect,
        buf: &mut Buffer,
        mask_char: Option<char>,
        textarea_right_reserve: u16,
    )

Purpose: Draws the entire composer: popup, footer, attachment previews, prompt, text area, highlights, and placeholder text. It is the main painting routine for this UI component.

Data flow: It receives a screen rectangle, output buffer, optional mask character, and optional right-side reserve. It splits the area into parts, draws any active popup or footer hints, paints the composer background, draws remote image lines, draws the prompt, renders the text area with masking or highlights, and finally draws placeholder text when input is empty or disabled.

Call relations: render_with_mask and render paths hand off to this function. It pulls together many helpers, including layout calculation, footer property building, shell-mode footer text, context display, paste-burst state, and plugin mention highlighting.

Call graph: calls 25 internal fn (custom_footer_height, footer_props, is_in_paste_burst, layout_areas_with_textarea_right_reserve, mode_indicator_line, plugin_at_mention_highlights, right_footer_line_with_context, shell_mode_footer_line, flash_visible, plan_mode_nudge_line (+15 more)); called by 2 (render, render_with_mask); 15 external calls (default, set_span, Length, vertical, from, new, new, footer_spacing, from, render_ref (+5 more)).

tests::snapshot_composer_state_with_width4619–4649 ↗
fn snapshot_composer_state_with_width(
        name: &str,
        width: u16,
        enhanced_keys_supported: bool,
        setup: F,
    )

Purpose: Renders a composer in a test terminal of a chosen width and saves a snapshot of the result. Snapshot tests compare the full visual output against a known-good version.

Data flow: It receives a snapshot name, width, enhanced-key support flag, and setup function. It builds a composer, applies setup, calculates a suitable height, renders into a test backend, and records the snapshot.

Call relations: Many footer tests call this helper to avoid repeating setup. It uses footer height and spacing calculations before calling render.

Call graph: calls 4 internal fn (new, footer_spacing, new, footer_height); 3 external calls (new, assert_snapshot!, new).

tests::snapshot_composer_state4651–4661 ↗
fn snapshot_composer_state(name: &str, enhanced_keys_supported: bool, setup: F)

Purpose: A convenience wrapper for snapshotting the composer at the standard test width. It keeps most visual tests shorter.

Data flow: It receives a snapshot name, enhanced-key flag, and setup function, then calls snapshot_composer_state_with_width using width 100.

Call relations: Footer-mode snapshot tests call this when they do not need a special width.

Call graph: 1 external calls (snapshot_composer_state_with_width).

tests::shell_command_cursor_uses_absorbed_prefix4802–4821 ↗
fn shell_command_cursor_uses_absorbed_prefix()

Purpose: Checks that cursor placement is correct in shell command mode, where the leading exclamation mark is treated specially. The user should see the cursor at the expected visual column.

Data flow: The test sets shell-style text, moves the cursor to the end, asks for the cursor position, and compares it with expected coordinates for two command forms.

Call relations: The test runner invokes this to protect cursor_pos behaviour in shell mode.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, new, assert_eq!).

tests::shell_command_uses_shell_accent_style4824–4859 ↗
fn shell_command_uses_shell_accent_style()

Purpose: Verifies that shell command mode is visually marked with the shell accent color. This helps users notice they are typing a terminal command, not a normal chat message.

Data flow: The test enables the status line, sets shell command text, renders the composer, and checks that both the prompt and the “Shell mode” footer label use light red styling.

Call relations: The test runner invokes this. It exercises render and footer styling for shell mode.

Call graph: calls 2 internal fn (new, new); 5 external calls (empty, from, new, new, assert_eq!).

tests::plugin_mention_foreground_color4861–4880 ↗
fn plugin_mention_foreground_color(composer: &ChatComposer) -> Option<Color>

Purpose: Test helper that reads the foreground color used for a rendered plugin @ mention. It gives other tests a simple way to verify mention styling.

Data flow: It receives a composer, renders it into a fake buffer, finds “@sample” in the text row, and returns the foreground color of that cell.

Call relations: Plugin mention tests call this helper after setting up mention bindings. It depends on ChatComposer::render to produce styled cells.

Call graph: calls 1 internal fn (render); 2 external calls (empty, new).

tests::plugin_at_mentions_use_plugin_accent_style4883–4908 ↗
fn plugin_at_mentions_use_plugin_accent_style()

Purpose: Checks that plugin @ mentions are colored with the plugin accent color. This makes plugin references stand out from plain text.

Data flow: The test creates a composer with an @sample plugin mention binding, renders through the color helper, and asserts that the mention is magenta.

Call relations: The test runner invokes this. It uses plugin_mention_foreground_color to inspect the render result.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::plugin_at_mentions_render_with_plugin_accent_snapshot4911–4959 ↗
fn plugin_at_mentions_render_with_plugin_accent_snapshot()

Purpose: Captures a focused snapshot showing exactly which characters in a plugin @ mention are magenta. This protects the highlight range, not just the presence of a color.

Data flow: The test renders a composer with a plugin mention, builds one line of visible text and a second marker line showing magenta cells, then stores that as a snapshot.

Call relations: The test runner invokes this. It exercises mention binding and render highlighting.

Call graph: calls 2 internal fn (new, new); 6 external calls (empty, new, new, new, assert_snapshot!, vec!).

tests::recalled_plugin_at_mentions_keep_plugin_accent_style4962–4995 ↗
fn recalled_plugin_at_mentions_keep_plugin_accent_style()

Purpose: Verifies that plugin mention styling survives after a message is submitted and later recalled from history. This prevents restored history from losing its mention metadata.

Data flow: The test submits text containing a plugin mention, clears the composer, recalls the previous entry with Up, and checks that the mention still renders magenta.

Call relations: The test runner invokes this. It covers submission, history recall, mention rebinding, and rendering.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, new, new, assert!, assert_eq!, vec!).

tests::esc_exits_empty_shell_mode5031–5057 ↗
fn esc_exits_empty_shell_mode()

Purpose: Verifies that pressing Escape after typing only the shell-mode prefix exits shell mode and clears the prefix. This gives users a quick way to back out of accidental shell mode.

Data flow: The test types “!”, confirms shell mode is active, sends Escape, and asserts that shell mode is off, text is empty, and a redraw is needed.

Call relations: The test runner invokes this. It exercises key handling and shell-mode state transitions.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, type_chars_humanlike).

tests::esc_keeps_shell_mode_when_paste_burst_flushes_pending_text5060–5087 ↗
fn esc_keeps_shell_mode_when_paste_burst_flushes_pending_text()

Purpose: Checks a paste-burst edge case: Escape should first flush pending pasted text instead of exiting shell mode. A paste burst is a fast group of typed characters treated as a possible paste.

Data flow: The test types “!”, sends another character that remains pending, confirms the visible text has not caught up, presses Escape, and verifies the pending text appears while shell mode stays active.

Call relations: The test runner invokes this. It protects the interaction between paste-burst buffering and shell-mode Escape handling.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, type_chars_humanlike).

tests::esc_hint_stays_hidden_with_draft_content5329–5355 ↗
fn esc_hint_stays_hidden_with_draft_content()

Purpose: Verifies that pressing Escape while draft text exists does not show the empty-composer Escape hint. The footer should not distract the user with the wrong hint while text is being edited.

Data flow: The test types a character, checks the composer has draft content, presses Escape, and asserts the footer mode and Escape hint flag remain unchanged.

Call relations: The test runner invokes this. It exercises key handling and footer mode rules.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, type_chars_humanlike).

tests::empty_vim_insert_escape_enters_normal_without_esc_hint5358–5393 ↗
fn empty_vim_insert_escape_enters_normal_without_esc_hint()

Purpose: Checks that in Vim mode, Escape from empty insert mode switches to normal mode instead of showing the composer Escape hint. Vim mode is an editor style where Escape has a well-known meaning.

Data flow: The test enables Vim mode, enters insert mode, presses Escape, and verifies the composer remains empty, Vim mode becomes normal, and the footer Escape hint stays hidden.

Call relations: The test runner invokes this. It protects the priority of Vim key behaviour over footer hint behaviour.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::slash_opens_command_popup_in_vim_normal_mode5396–5424 ↗
fn slash_opens_command_popup_in_vim_normal_mode()

Purpose: Verifies that pressing slash in Vim normal mode starts a slash command instead of acting like normal Vim search. This makes command entry work naturally in the composer.

Data flow: The test enables Vim mode, sends “/”, and checks that the text contains slash, the cursor is after it, the command popup is open, and Vim has switched to insert mode.

Call relations: The test runner invokes this. It exercises key handling, command popup activation, and Vim mode transitions.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::slash_command_can_be_typed_and_dispatched_after_vim_normal_slash5427–5459 ↗
fn slash_command_can_be_typed_and_dispatched_after_vim_normal_slash()

Purpose: Checks that a slash command opened from Vim normal mode can be completed and submitted. This protects the full flow from opening the command popup to dispatching the selected command.

Data flow: The test enables Vim mode, types “/diff”, confirms the command popup is active, presses Enter, and asserts the composer clears, Vim returns to normal mode, and the Diff command is produced.

Call relations: The test runner invokes this. It covers command typing, popup dispatch, composer clearing, and Vim-mode cleanup.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::inline_slash_command_dispatch_resets_vim_mode_to_normal5462–5498 ↗
fn inline_slash_command_dispatch_resets_vim_mode_to_normal()

Purpose: Checks that submitting an inline slash command while using Vim-style editing sends the command and returns Vim mode to Normal. This protects users from being left in Insert mode after a command is dispatched.

Data flow: The test builds a focused composer, enables collaboration commands and Vim mode, switches to Insert mode, and puts /plan investigate this into the draft. Pressing Enter turns that text into a Plan command with the argument investigate this, clears any text elements, requests a redraw, and leaves the Vim indicator showing Normal.

Call relations: The test runner calls this as a regression test. It drives ChatComposer::handle_key_event the same way the real keyboard loop would, then checks the InputResult that would be handed back to the rest of the app.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, panic!).

tests::bang_enters_shell_mode_in_vim_normal_mode5501–5529 ↗
fn bang_enters_shell_mode_in_vim_normal_mode()

Purpose: Checks that pressing ! in Vim Normal mode starts shell-command entry. This mirrors the familiar Vim pattern where ! begins a command-like action.

Data flow: The test creates a composer with Vim enabled and sends a ! key press. The composer does not submit anything, asks for a redraw, marks the draft as bash mode, shows ! as the current text, keeps the editable textarea empty after the prefix, and switches the Vim indicator to Insert.

Call relations: The test runner calls this to verify keyboard handling. It exercises the composer’s key-event path and inspects the draft state that later typing and submission will use.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::shell_command_can_be_typed_after_vim_normal_bang5532–5556 ↗
fn shell_command_can_be_typed_after_vim_normal_bang()

Purpose: Checks that after ! starts shell mode, ordinary characters become the shell command body. This ensures users can immediately type commands such as !echo.

Data flow: The test sends the characters !, e, c, h, and o to a Vim-enabled composer. The visible current text becomes !echo, the underlying command text becomes echo, bash mode stays on, and no popup is opened.

Call relations: The test runner calls this after shell-mode behavior is implemented. It uses the same handle_key_event path as live typing and confirms the composer feeds later submission with the correct command text.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::clear_for_ctrl_c_records_cleared_draft5585–5604 ↗
fn clear_for_ctrl_c_records_cleared_draft()

Purpose: Checks that Ctrl-C clearing a normal draft also saves that draft into history. This prevents accidental loss when a user cancels input.

Data flow: The composer starts with draft text. clear_for_ctrl_c returns that text, empties the composer, and stores a matching history entry. Navigating history upward later returns the cleared draft.

Call relations: The test runner calls this to verify the clear-and-save flow. It exercises clear_for_ctrl_c and then the history navigation used when a user presses Up to recover previous input.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::clear_for_ctrl_c_preserves_pending_paste_history_entry5607–5676 ↗
fn clear_for_ctrl_c_preserves_pending_paste_history_entry()

Purpose: Checks that clearing a draft containing a large paste does not lose the hidden pasted content. The visible placeholder and the real pasted text must travel together through history.

Data flow: A large string is pasted, so the composer shows a placeholder like [Pasted Content N chars] and stores the real text separately. After Ctrl-C clears the draft, history contains the placeholder, its text element marker, and the pending paste payload. Restoring that history entry recreates the placeholder and payload, and pressing Enter submits the original large text.

Call relations: The test runner calls this to cover the path from paste handling to Ctrl-C clearing, history restore, and final submission. It proves that handle_paste, clear_for_ctrl_c, apply_history_entry, and handle_key_event cooperate without dropping data.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, assert!, assert_eq!, format!, panic!, vec!).

tests::large_paste_numbering_reuses_after_ctrl_c_clear5679–5705 ↗
fn large_paste_numbering_reuses_after_ctrl_c_clear()

Purpose: Checks that clearing a large paste resets the draft enough that the same paste gets the same placeholder label again. This avoids confusing placeholder numbering after cancellation.

Data flow: The test pastes a large string and sees one placeholder. clear_for_ctrl_c returns the placeholder text, empties the visible text, and removes pending paste records. Pasting the same content again creates the same base placeholder with one pending paste.

Call relations: The test runner calls this to protect paste cleanup. It verifies that handle_paste can safely run again after clear_for_ctrl_c without stale pending-paste state.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, format!).

tests::vim_mode_resets_to_normal_after_submission5708–5746 ↗
fn vim_mode_resets_to_normal_after_submission()

Purpose: Checks that sending a normal prompt resets Vim mode to Normal while keeping Vim support enabled. This matches the expectation that a completed edit exits insert-style typing.

Data flow: The test enables steering and Vim, enters Insert mode, sets draft text to h, and presses Enter. The result is a submitted prompt containing h; the draft is empty afterward; Vim remains enabled but its indicator returns to Normal.

Call relations: The test runner calls this through the same key-event submission path used by the UI. It confirms that successful submission updates both output and editor mode.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, panic!).

tests::vim_mode_resets_to_normal_after_queued_submission5749–5780 ↗
fn vim_mode_resets_to_normal_after_queued_submission()

Purpose: Checks that when a task is already running and the user queues a message, Vim mode still returns to Normal. Queued submissions should behave like real submissions from the editor’s point of view.

Data flow: The composer is marked as having a running task, Vim is enabled, and the draft text is queued. handle_submission returns a Queued result with the text, clears the draft, and shows Vim: Normal.

Call relations: The test runner calls this to cover the direct submission helper rather than only Enter key handling. It protects the queueing branch used when the app cannot send immediately.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, new, assert!, assert_eq!, panic!).

tests::vim_mode_stays_insert_after_suppressed_submission5783–5811 ↗
fn vim_mode_stays_insert_after_suppressed_submission()

Purpose: Checks that Vim mode does not reset when Enter does not actually submit. If the user typed an invalid slash command, they should stay in Insert mode to fix it.

Data flow: The test enters Vim Insert mode, fills the draft with /not-a-command, and presses Enter. The composer returns no action, keeps the text unchanged, and leaves the Vim indicator in Insert.

Call relations: The test runner calls this to protect the rejected-submission path. It shows that only successful dispatch or submission should reset Vim mode.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, new, assert!, assert_eq!).

tests::esc_switches_vim_insert_to_normal5814–5848 ↗
fn esc_switches_vim_insert_to_normal()

Purpose: Checks that Escape leaves Vim Insert mode and enters Normal mode. It also verifies the cursor movement that Vim users expect when leaving insert mode.

Data flow: The test enters Insert mode, sets the text to hey, and places the cursor at the end. Pressing Escape changes the indicator to Normal and moves the cursor one character left, from after y to on y.

Call relations: The test runner calls this through handle_key_event. It protects the basic Vim-mode transition that many later key behaviors depend on.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, new, assert_eq!).

tests::vim_insert_uses_bar_cursor_style5851–5888 ↗
fn vim_insert_uses_bar_cursor_style()

Purpose: Checks that the terminal cursor shape changes in Vim Insert mode. A bar cursor is a visual cue that typing will insert text.

Data flow: The test compares the encoded terminal cursor style before Vim, after enabling Vim, after entering Insert mode, and after Escape. The composer uses the default cursor normally, switches to a steady bar in Insert mode, then returns to default in Normal mode.

Call relations: The test runner calls this to verify the render-facing cursor_style method. It connects key handling with the rendering layer that sends cursor-style commands to the terminal.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, new, new, assert_eq!).

tests::clear_for_ctrl_c_preserves_image_draft_state5891–5934 ↗
fn clear_for_ctrl_c_preserves_image_draft_state()

Purpose: Checks that Ctrl-C clearing a draft with an attached local image saves enough information to restore it from history. The visible image label and the file path must stay linked.

Data flow: The test attaches example.png, which creates an image placeholder. After clearing, history contains the placeholder text, its text element marker, and the local image path. Applying that history entry restores the placeholder, image path, and element payload.

Call relations: The test runner calls this to protect image attachment recovery. It exercises attach_image, clear_for_ctrl_c, history navigation, and apply_history_entry as one user story.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 4 external calls (from, assert!, assert_eq!, vec!).

tests::clear_for_ctrl_c_preserves_remote_offset_image_labels5937–5976 ↗
fn clear_for_ctrl_c_preserves_remote_offset_image_labels()

Purpose: Checks that local image labels remain correct when remote images already occupy earlier image numbers. This avoids rebinding [Image #2] to the wrong image after clearing.

Data flow: The composer is given one remote image URL, then draft text containing [Image #2] with a local image path. clear_for_ctrl_c saves the current text, text element, local path, and remote URL together in history.

Call relations: The test runner calls this to cover mixed remote and local image state. It verifies that history entries preserve the numbering context needed by later restore.

Call graph: calls 2 internal fn (new, new); 3 external calls (from, assert_eq!, vec!).

tests::apply_history_entry_preserves_local_placeholders_after_remote_prefix5979–6013 ↗
fn apply_history_entry_preserves_local_placeholders_after_remote_prefix()

Purpose: Checks that restoring history with both remote and local images keeps the local placeholder label unchanged. This protects drafts where remote images affect local numbering.

Data flow: The test applies a history entry whose text is [Image #2] draft, with one local image path and one remote image URL. The restored composer has the same text, one text element covering [Image #2], the local path, and the remote URL.

Call relations: The test runner calls this to focus on apply_history_entry. It verifies the restore half of the clear/history flow covered by nearby tests.

Call graph: calls 2 internal fn (new, new); 5 external calls (from, new, assert_eq!, with_pending_and_remote, vec!).

tests::question_mark_only_toggles_on_first_char6018–6055 ↗
fn question_mark_only_toggles_on_first_char()

Purpose: Checks that ? opens the shortcut overlay only when typed as the first character in an empty composer. Later, after draft text exists, ? should be ordinary text.

Data flow: The first ? toggles the footer to ShortcutOverlay and requests a redraw. A second ? closes it. After typing h, another ? is accepted as draft text, producing h?, while footer_mode still reports that a draft exists.

Call relations: The test runner calls this through keyboard handling and paste-burst flushing helpers. It protects the distinction between a shortcut key and normal typed content.

Call graph: calls 2 internal fn (new, new); 6 external calls (Char, new, assert!, assert_eq!, flush_after_paste_burst, type_chars_humanlike).

tests::shift_question_mark_toggles_shortcut_overlay_when_empty6058–6079 ↗
fn shift_question_mark_toggles_shortcut_overlay_when_empty()

Purpose: Checks that Shift+? also opens the shortcut overlay when the composer is empty. This supports terminals that report question mark with a Shift modifier.

Data flow: The test enables steering, sends Shift+?, and receives no input action but does get a redraw request. The footer mode becomes ShortcutOverlay.

Call relations: The test runner calls this to cover a keyboard-reporting variation. It feeds handle_key_event exactly as terminal input would.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::question_mark_does_not_toggle_during_paste_burst6084–6115 ↗
fn question_mark_does_not_toggle_during_paste_burst()

Purpose: Checks that a ? inside a fast paste is treated as pasted text, not as a shortcut. This prevents pasted content from accidentally opening UI overlays.

Data flow: The test forces paste-burst mode, sends the characters hi?there, and confirms the textarea stays empty until the burst is flushed. After flushing, the text is hi?there and the shortcut overlay is not active.

Call relations: The test runner calls this to protect paste detection. It verifies that handle_key_event respects the paste-burst buffer before shortcut handling.

Call graph: calls 2 internal fn (new, new); 8 external calls (now, Char, new, new, assert!, assert_eq!, assert_ne!, flush_after_paste_burst).

tests::set_connector_mentions_refreshes_open_mention_popup6118–6157 ↗
fn set_connector_mentions_refreshes_open_mention_popup()

Purpose: Checks that when app connector data arrives, a waiting $ mention popup opens and shows the connector. This lets suggestions appear even if data loads after the user starts typing.

Data flow: The composer has connectors enabled and contains just $. When a snapshot with an enabled Notion connector is set, the active popup becomes a skill-style mention popup whose selected item inserts $notion and points to app://connector_1.

Call relations: The test runner calls this to cover data-refresh behavior. It uses set_connector_mentions to feed new connector information into the popup-building code.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, panic!, vec!).

tests::set_connector_mentions_skips_disabled_connectors6160–6195 ↗
fn set_connector_mentions_skips_disabled_connectors()

Purpose: Checks that disabled app connectors do not appear as mention suggestions. Users should not be offered tools they cannot use.

Data flow: The composer is ready for a $ mention, but the connector snapshot contains Notion marked as disabled. After setting the snapshot, no popup opens.

Call relations: The test runner calls this to guard filtering inside set_connector_mentions. It pairs with the enabled-connector test to define both sides of the behavior.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, vec!).

tests::set_plugin_mentions_refreshes_open_mention_popup6198–6228 ↗
fn set_plugin_mentions_refreshes_open_mention_popup()

Purpose: Checks that plugin data can open or refresh a $ mention popup. This supports typing a plugin reference before plugin suggestions have loaded.

Data flow: The composer contains $, then receives one plugin summary named Sample Plugin. The popup opens with a selected mention that inserts $sample and points to plugin://sample@test.

Call relations: The test runner calls this to verify set_plugin_mentions. It confirms plugin summaries are converted into mention items for the same popup system as other tool mentions.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, panic!, vec!).

tests::set_skill_mentions_refreshes_open_mention_popup6231–6265 ↗
fn set_skill_mentions_refreshes_open_mention_popup()

Purpose: Checks that skill metadata can open or refresh a $ mention popup. This makes repository or user skills discoverable from the composer.

Data flow: The composer contains $, then receives one skill named codex with a path to SKILL.md. The popup opens and selects a mention that inserts $codex and stores the skill file path.

Call relations: The test runner calls this to exercise set_skill_mentions. It verifies skills join the mention suggestion flow used by the composer UI.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, assert!, assert_eq!, test_path_buf, panic!, vec!).

tests::mention_items_show_plugin_owned_skill_and_app_duplicates6268–6340 ↗
fn mention_items_show_plugin_owned_skill_and_app_duplicates()

Purpose: Checks that similar skill, plugin, and app connector entries are all shown, not collapsed into one. The category tags help users tell them apart.

Data flow: The composer is given a Google Calendar skill, plugin, and enabled app connector. mention_items returns three entries in order: a Skill with the skill path, a Plugin with a plugin URL, and an App with an app URL.

Call relations: The test runner calls this to inspect the mention item builder directly. It verifies that set_skill_mentions, set_plugin_mentions, and set_connector_mentions feed one combined suggestion list.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_eq!, test_path_buf, vec!).

tests::plugin_mention_popup_snapshot6343–6362 ↗
fn plugin_mention_popup_snapshot()

Purpose: Captures the rendered appearance of a plugin mention popup. Snapshot tests help notice accidental visual changes.

Data flow: The snapshot helper builds a composer, types $sa, and supplies a sample plugin with description and connector ownership data. The rendered composer state is compared against a stored snapshot named plugin_mention_popup.

Call relations: The test runner calls this through the snapshot system. It hands setup work to snapshot_composer_state, which renders the composer after the closure configures it.

Call graph: 1 external calls (snapshot_composer_state).

tests::default_unified_mention_popup_snapshot6365–6384 ↗
fn default_unified_mention_popup_snapshot()

Purpose: Captures the default appearance of the newer unified mention popup. This checks how @ mentions render when the MentionsV2 feature is enabled by default.

Data flow: The snapshot helper creates a composer, enables the feature flag value from defaults, types @sa, and supplies one plugin. The resulting UI is compared with the default_unified_mention_popup snapshot.

Call relations: The test runner calls this as a visual regression test. It relies on snapshot_composer_state to create, configure, render, and compare the composer.

Call graph: 1 external calls (snapshot_composer_state).

tests::mention_popup_type_prefixes_snapshot6387–6443 ↗
fn mention_popup_type_prefixes_snapshot()

Purpose: Captures how mention suggestions show type prefixes such as Skill, Plugin, and App. This protects the visual labels that disambiguate similar results.

Data flow: The snapshot setup enables connectors, types $goog, and provides matching skill, plugin, and connector data. The composer is rendered at a fixed width and compared with a stored snapshot.

Call relations: The test runner calls this through snapshot_composer_state_with_width. The fixed width makes the visual output stable so layout changes are easy to detect.

Call graph: 1 external calls (snapshot_composer_state_with_width).

tests::set_connector_mentions_excludes_disabled_apps_from_mention_popup6446–6477 ↗
fn set_connector_mentions_excludes_disabled_apps_from_mention_popup()

Purpose: Checks again that disabled apps are excluded from connector mention popups. It protects the app-suggestion path from offering unavailable connectors.

Data flow: The composer is set up for a $ mention and receives a connector snapshot containing one disabled Notion app. The active popup remains None.

Call relations: The test runner calls this to cover the connector-filtering behavior from another named scenario. It exercises set_connector_mentions after the text is already ready to trigger suggestions.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, vec!).

tests::shortcut_overlay_persists_while_task_running6480–6502 ↗
fn shortcut_overlay_persists_while_task_running()

Purpose: Checks that the shortcut overlay stays visible if a task starts running. The UI should not hide help just because background work begins.

Data flow: The test opens the shortcut overlay with ?, then marks a task as running. The stored footer mode and computed footer_mode both remain ShortcutOverlay.

Call relations: The test runner calls this to protect the interaction between keyboard shortcuts and task status. It verifies set_task_running does not overwrite an intentional overlay state.

Call graph: calls 2 internal fn (new, new); 3 external calls (Char, new, assert_eq!).

tests::test_current_at_token_basic_cases6505–6560 ↗
fn test_current_at_token_basic_cases()

Purpose: Checks basic detection of the current @ token near the cursor. This is the foundation for file or mention autocomplete.

Data flow: For each sample string, the test inserts text into a textarea, places the cursor, and asks ChatComposer::current_at_token for the token. The result is compared with the expected query, including ASCII, Unicode, emoji, empty @, and invalid spaced cases.

Call relations: The test runner calls this to exercise current_at_token directly. That helper is used by popup synchronization when deciding whether an @ autocomplete should open.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert_eq!, vec!).

tests::test_current_at_token_cursor_positions6563–6594 ↗
fn test_current_at_token_cursor_positions()

Purpose: Checks that @ token detection works at different cursor positions inside the token. Autocomplete should not disappear just because the cursor is at the beginning or end.

Data flow: The test creates textareas with tokens like @test and @file1 @file2, sets the cursor at selected positions, and asks for the current token. The helper returns the token under or beside the cursor, or None for empty input.

Call relations: The test runner calls this to define cursor-sensitive behavior for current_at_token. Popup code depends on this to keep suggestions stable while the user moves around.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert_eq!, vec!).

tests::test_current_at_token_whitespace_boundaries6597–6651 ↗
fn test_current_at_token_whitespace_boundaries()

Purpose: Checks that @ tokens are recognized only when separated by whitespace-like boundaries, not when embedded in the middle of a word. This avoids treating email-like or joined text as a mention.

Data flow: The test feeds examples with normal spaces, full-width spaces, tabs, and connected text such as aaa@aaa. current_at_token returns a query only when @ begins a separate token.

Call relations: The test runner calls this to protect boundary rules used before opening autocomplete. It makes sure the helper works with non-English spacing too.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert_eq!, vec!).

tests::test_current_at_token_tracks_tokens_with_second_at6654–6679 ↗
fn test_current_at_token_tracks_tokens_with_second_at()

Purpose: Checks that tokens can contain a second @ after the leading one, such as scoped package names with versions. This prevents command text like @scope/package@latest from being split incorrectly.

Data flow: The test uses npx -y @kaeawc/auto-mobile@latest and places the cursor at several points. current_at_token always returns kaeawc/auto-mobile@latest.

Call relations: The test runner calls this to cover package-name style input. It protects the file/mention popup logic from interrupting common command strings.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert_eq!, vec!).

tests::test_current_at_token_allows_file_queries_with_second_at6682–6705 ↗
fn test_current_at_token_allows_file_queries_with_second_at()

Purpose: Checks that file-like @ queries with another @ inside remain valid. This supports names such as icon@2x.png.

Data flow: The test inserts @icons/icon@2x.png, places the cursor before, on, and after the second @, and confirms current_at_token returns some token each time.

Call relations: The test runner calls this to define allowed characters in @ queries. It supports autocomplete behavior for filenames and paths that contain @.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert!, vec!).

tests::test_current_at_token_ignores_mid_word_at6708–6727 ↗
fn test_current_at_token_ignores_mid_word_at()

Purpose: Checks that an @ in the middle of a word is ignored. This prevents accidental autocomplete inside text like foo@bar.

Data flow: The test places the cursor on the @ and at the end of foo@bar. current_at_token returns None both times.

Call relations: The test runner calls this to guard against noisy popup opening. It complements the boundary tests by focusing on a common mid-word case.

Call graph: calls 2 internal fn (current_at_token, new); 2 external calls (assert_eq!, vec!).

tests::set_text_content_rebinds_at_sigiled_mentions6730–6754 ↗
fn set_text_content_rebinds_at_sigiled_mentions()

Purpose: Checks that setting text with an @ mention binding recreates the binding inside the composer. A binding is the hidden link between visible mention text and the thing it refers to.

Data flow: The test supplies text @figma please and a binding saying @figma points to a skill file. After setting the content, composer.mention_bindings returns the same binding.

Call relations: The test runner calls this to verify set_text_content_with_mention_bindings. This restore path is used when drafts or history entries need mentions to remain meaningful.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::set_text_content_rebinds_matching_sigil_only6757–6787 ↗
fn set_text_content_rebinds_matching_sigil_only()

Purpose: Checks that a $ binding does not accidentally attach to an @ token with the same name. The symbol, or sigil, is part of the mention’s identity.

Data flow: The text contains both @figma and $figma, but the binding is only for $figma. The composer marks only the $ token as bound and stores the original binding.

Call relations: The test runner calls this to protect mention rebinding logic. It verifies that set_text_content_with_mention_bindings respects both the mention name and its sigil.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::set_text_content_rebinds_both_sigil_forms6790–6830 ↗
fn set_text_content_rebinds_both_sigil_forms()

Purpose: Checks that @ and $ mentions with the same name can both be rebound when both bindings are provided. This supports separate meanings for the same word.

Data flow: The text contains @figma and $figma, with one plugin binding and one app binding. The composer creates two bound mention elements, one for each sigil, and keeps both bindings.

Call relations: The test runner calls this to cover the multi-binding case. It ensures the rebinding code can represent two different targets without overwriting one.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::set_text_content_rebinds_at_mentions_after_email_substrings6833–6871 ↗
fn set_text_content_rebinds_at_mentions_after_email_substrings()

Purpose: Checks that mention rebinding ignores @ inside an email-like substring and binds only the real mention later in the text. This avoids making part of an email uneditable as if it were a mention.

Data flow: The text is foo@sample.com then @sample, with a binding for @sample. After setting content, only the final @sample range becomes an atomic text element, and the binding is preserved.

Call relations: The test runner calls this to protect mention boundary detection during restore. It combines rebinding rules with the same kind of mid-word @ handling tested elsewhere.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::set_text_content_rebinds_at_mentions_after_punctuation6874–6911 ↗
fn set_text_content_rebinds_at_mentions_after_punctuation()

Purpose: Checks that an @ mention after punctuation, such as inside parentheses, can still be rebound. Mentions should work in normal sentences, not only after spaces.

Data flow: The text Please ask (@sample) is set with a binding for @sample. The composer creates one text element covering the @sample range and keeps the binding.

Call relations: The test runner calls this to verify punctuation boundary handling in set_text_content_with_mention_bindings. It ensures restored mentions remain atomic in sentence contexts.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::bound_at_mentions_do_not_block_arrow_navigation6914–6956 ↗
fn bound_at_mentions_do_not_block_arrow_navigation()

Purpose: Checks that bound @ mentions behave like a single protected chunk but still allow left and right arrow movement around them. Navigation should not open suggestion popups.

Data flow: The composer loads go @figma now with @figma bound to a plugin and starts the cursor after go. Right moves over the space, then jumps across the bound mention; Left jumps back before the mention, then to the earlier position. No popup opens during these moves.

Call relations: The test runner calls this through handle_key_event with arrow keys. It protects the interaction between atomic mention elements, cursor movement, and popup triggering.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, new, assert!, assert_eq!, vec!).

tests::restored_bound_at_mentions_do_not_open_mention_popup6959–7012 ↗
fn restored_bound_at_mentions_do_not_open_mention_popup()

Purpose: Checks that restored bound @ mentions submit normally instead of reopening autocomplete. A mention that already has a target should not be treated as an unfinished query.

Data flow: For two text shapes, the composer loads a plugin mention binding for @sample. No popup is active after restore, and pressing Enter submits the original text unchanged.

Call relations: The test runner calls this to cover restored drafts. It exercises set_text_content_with_mention_bindings and then the Enter key path, confirming bound mentions do not block submission.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, new, assert!, assert_eq!, panic!, vec!).

tests::enter_submits_when_file_popup_has_no_selection7015–7044 ↗
fn enter_submits_when_file_popup_has_no_selection()

Purpose: Checks that Enter submits text when an @-triggered file popup is open but has no selected completion. This prevents autocomplete from trapping normal command input.

Data flow: The composer contains npx -y @kaeawc/auto-mobile@latest, syncs popups, and opens a File popup. Pressing Enter is consumed as a submission and returns the full original text.

Call relations: The test runner calls this to cover the boundary between autocomplete and submission. It verifies sync_popups may open a popup, but handle_key_event still submits when there is no selection to insert.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::enter_submits_when_unified_mention_popup_has_no_selection7047–7077 ↗
fn enter_submits_when_unified_mention_popup_has_no_selection()

Purpose: Checks that pressing Enter still submits the typed text when the newer mention popup is open but nothing is selected. This prevents an unrelated popup from trapping the Enter key.

Data flow: The test creates a composer, enables the newer mention system, inserts text containing an @ package name, and syncs popups. It then sends an Enter key event and expects the same text to come out as a submitted message.

Call relations: The test harness calls this test. Inside it, the composer is built with ChatComposer::new, the popup state is refreshed, and handle_key_event is used as the same entry point real keyboard input would use.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::ascii_prefix_survives_non_ascii_followup7083–7109 ↗
fn ascii_prefix_survives_non_ascii_followup()

Purpose: Checks that an ASCII character held during paste-burst detection is not lost when a non-ASCII character follows it. This matters for mixed-language input such as numbers followed by Japanese text.

Data flow: The test types '1', confirms the composer is temporarily buffering it as a possible paste, then types 'あ'. Pressing Enter should submit the combined text '1あ'.

Call relations: The test drives the normal key handling path. It relies on handle_key_event to perform paste-burst detection, then verifies the final InputResult.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, panic!).

tests::non_ascii_char_inserts_immediately_without_burst_state7114–7133 ↗
fn non_ascii_char_inserts_immediately_without_burst_state()

Purpose: Checks that a single non-ASCII character is inserted right away instead of being delayed as a possible paste. This keeps normal international typing responsive.

Data flow: The test sends one Japanese character into a new composer. The textarea should immediately contain that character, and the composer should not be in paste-burst mode.

Call relations: The test calls the same handle_key_event path used by real typing and inspects the composer draft afterward.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::non_ascii_burst_buffers_enter_and_flushes_multiline7138–7167 ↗
fn non_ascii_burst_buffers_enter_and_flushes_multiline()

Purpose: Checks that an active paste burst can include Unicode characters and Enter as a newline. This prevents pasted multi-line non-English text from being submitted too early.

Data flow: The test manually starts a paste burst, feeds Chinese characters, Enter, and ASCII letters, then confirms nothing appears in the textarea until the burst is flushed. After flushing, the textarea contains '你好\nhi'.

Call relations: The test forces the paste-burst state, sends input through handle_key_event, then uses the helper tests::flush_after_paste_burst to finish the buffered paste.

Call graph: calls 2 internal fn (new, new); 7 external calls (now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst).

tests::non_ascii_burst_preserves_ideographic_space_and_ascii7172–7203 ↗
fn non_ascii_burst_preserves_ideographic_space_and_ascii()

Purpose: Checks that paste buffering preserves a wide Unicode space along with Chinese and ASCII characters. This guards against losing or normalizing meaningful spacing.

Data flow: The test starts a paste burst, feeds '你 好', Enter, and 'hi'. After the delayed flush, the composer should contain exactly '你 好\nhi'.

Call relations: It uses the composer’s normal key handling while the burst is active, then hands off to tests::flush_after_paste_burst to make the pending text appear.

Call graph: calls 2 internal fn (new, new); 7 external calls (now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst).

tests::non_ascii_burst_buffers_large_multiline_mixed_ascii_and_unicode7209–7254 ↗
fn non_ascii_burst_buffers_large_multiline_mixed_ascii_and_unicode()

Purpose: Checks that a large pasted block with Chinese text, ASCII text, spaces, blank lines, and newlines survives paste buffering unchanged. This protects real-world multi-line paste behavior.

Data flow: The test forces a paste burst and sends every character from a large mixed-language payload, turning newline characters into Enter key events. Before flushing the textarea is empty; after flushing it exactly matches the original payload.

Call relations: The test uses handle_key_event for each simulated key and then calls tests::flush_after_paste_burst, exercising the same buffering path used for fast terminal input.

Call graph: calls 2 internal fn (new, new); 7 external calls (now, Char, new, new, assert!, assert_eq!, flush_after_paste_burst).

tests::ascii_burst_treats_enter_as_newline7259–7307 ↗
fn ascii_burst_treats_enter_as_newline()

Purpose: Checks that Enter inside a fast ASCII paste burst becomes a newline instead of submitting the message. This is important when users paste multi-line English text.

Data flow: The test sends 'hi' with very small time gaps, presses Enter through the timed submission path, then types 'there'. After the burst timeout, flushing should place 'hi\nthere' in the textarea.

Call relations: It calls the time-aware input and submission methods directly so the paste timing can be controlled, then uses handle_paste_burst_flush to finish the burst.

Call graph: calls 3 internal fn (new, new, recommended_active_flush_delay); 6 external calls (from_millis, now, Char, new, assert!, assert_eq!).

tests::queued_submission_flushes_ascii_burst_instead_of_inserting_newline7312–7351 ↗
fn queued_submission_flushes_ascii_burst_instead_of_inserting_newline()

Purpose: Checks that when a submission is being queued, a pending ASCII paste burst is flushed into the queued text instead of treating Enter as another newline. This keeps queued prompts complete.

Data flow: The test rapidly types 'hi' so it is buffered, then calls the timed submission path with queueing enabled. The result should be a queued plain input containing 'hi', with no remaining textarea text or paste-burst state.

Call relations: It uses handle_input_basic_with_time to create the burst and handle_submission_with_time to test the queue-specific branch.

Call graph: calls 2 internal fn (new, new); 6 external calls (from_millis, now, Char, new, assert!, assert_eq!).

tests::slash_context_enter_ignores_paste_burst_enter_suppression7356–7382 ↗
fn slash_context_enter_ignores_paste_burst_enter_suppression()

Purpose: Checks that a slash command still runs when Enter is pressed, even if paste-burst mode is active. Slash commands should not be blocked by paste protection.

Data flow: The test puts '/diff' into the composer, starts a paste burst, then sends Enter. The result should be the Diff slash command rather than a buffered newline.

Call relations: It drives the normal handle_key_event path and verifies that slash-command parsing takes priority over paste-burst Enter suppression.

Call graph: calls 2 internal fn (new, new); 4 external calls (now, new, new, assert!).

tests::non_char_key_flushes_active_burst_before_input7387–7418 ↗
fn non_char_key_flushes_active_burst_before_input()

Purpose: Checks that pressing a non-character key, such as Left Arrow, first commits any buffered paste text. This keeps cursor movement from acting on an empty or stale textarea.

Data flow: The test starts a burst, types 'hi' into the buffer, then presses Left. The buffered text should appear, the cursor should move left inside it, and the burst should end.

Call relations: It uses handle_key_event for both character and navigation keys, matching how the composer receives real terminal events.

Call graph: calls 2 internal fn (new, new); 6 external calls (now, Char, new, new, assert!, assert_eq!).

tests::disable_paste_burst_flushes_pending_first_char_and_inserts_immediately7423–7451 ↗
fn disable_paste_burst_flushes_pending_first_char_and_inserts_immediately()

Purpose: Checks that turning off paste-burst detection does not drop a character that was already being held. This protects users when the setting changes while typing.

Data flow: The test types 'a', which is initially buffered, then disables paste-burst behavior. The 'a' appears immediately; typing 'b' afterward inserts directly, producing 'ab'.

Call relations: It combines handle_key_event with set_disable_paste_burst to verify that the setting change flushes pending input safely.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::handle_paste_small_inserts_text7456–7482 ↗
fn handle_paste_small_inserts_text()

Purpose: Checks that a small paste is inserted directly into the composer. Small pastes should behave like ordinary typed text.

Data flow: The test pastes 'hello', expects the textarea to contain 'hello' and no pending paste placeholders, then presses Enter and expects a submitted message containing 'hello'.

Call relations: It calls handle_paste for paste input and handle_key_event for submission, covering the normal small-paste-to-submit flow.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::empty_enter_returns_none7485–7509 ↗
fn empty_enter_returns_none()

Purpose: Checks that pressing Enter on an empty composer does nothing. This prevents blank messages from being submitted accidentally.

Data flow: The test starts with an empty composer and sends Enter. The returned input result should be None, meaning no submission, command, or queue action happened.

Call relations: It uses handle_key_event exactly as the UI would when the user presses Enter.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, panic!).

tests::handle_paste_large_uses_placeholder_and_replaces_on_submit7514–7545 ↗
fn handle_paste_large_uses_placeholder_and_replaces_on_submit()

Purpose: Checks that a large paste is shown as a short placeholder in the UI but restored to the full text when submitted. This keeps the terminal readable without losing content.

Data flow: The test creates text larger than the paste threshold, pastes it, and sees a placeholder like '[Pasted Content ... chars]' in the textarea. On Enter, the submitted text is the original large content, and the pending paste record is cleared.

Call relations: It exercises handle_paste for large input and handle_key_event for final submission, verifying how pending_pastes connects display text to real submitted text.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, format!, panic!).

tests::submit_at_character_limit_succeeds7548–7573 ↗
fn submit_at_character_limit_succeeds()

Purpose: Checks that text exactly at the maximum allowed size can still be submitted. The limit should reject only text that is too large, not text that exactly fits.

Data flow: The test fills the composer with the maximum number of characters, enables normal submission mode, and presses Enter. The result should be a submitted message with that full text.

Call relations: It uses the normal Enter handling path and verifies the boundary behavior around MAX_USER_INPUT_TEXT_CHARS.

Call graph: calls 2 internal fn (new, new); 2 external calls (new, assert!).

tests::oversized_submit_reports_error_and_restores_draft7576–7615 ↗
fn oversized_submit_reports_error_and_restores_draft()

Purpose: Checks that an oversized direct submission is rejected with a user-visible error, while preserving the draft text. This prevents data loss when a prompt is too long.

Data flow: The test inserts one character more than the limit and presses Enter. It expects no input result, the textarea still containing the original text, and an error history cell sent through the app event channel.

Call relations: It uses handle_key_event for submission and reads AppEvent messages from the receiver to confirm that the composer reports the problem to the rest of the UI.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::oversized_queued_submission_reports_error_and_restores_draft7618–7657 ↗
fn oversized_queued_submission_reports_error_and_restores_draft()

Purpose: Checks that an oversized queued submission is also rejected safely. Queueing should not bypass the same size limit users see for normal submission.

Data flow: The test disables immediate submission mode, enters text over the limit, and presses Enter. The result is None, the draft remains unchanged, and an error message is emitted as a history cell.

Call relations: It follows the same app-event reporting path as oversized direct submission, but with the composer configured for queued behavior.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::edit_clears_pending_paste7662–7684 ↗
fn edit_clears_pending_paste()

Purpose: Checks that editing away a large-paste placeholder also removes the hidden pending paste record. This prevents stale hidden content from being submitted later.

Data flow: The test pastes a large value, verifies one pending paste exists, then presses Backspace. Once the placeholder is edited, pending_pastes should be empty.

Call relations: It combines handle_paste with handle_key_event for editing, testing the link between visible placeholder text and hidden stored paste content.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::ui_snapshots7687–7747 ↗
fn ui_snapshots()

Purpose: Checks the rendered composer UI for several paste-related states using snapshots. A snapshot is a saved expected screen image in text form.

Data flow: For each case, the test builds a composer, applies optional pastes or edits, renders it into a test terminal buffer, and compares that buffer with the stored snapshot.

Call relations: It calls ChatComposer::render through ratatui’s test terminal, so it verifies not just internal state but what a user would see on screen.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, new, assert_snapshot!, panic!, new, vec!).

tests::image_placeholder_snapshots7750–7767 ↗
fn image_placeholder_snapshots()

Purpose: Checks how local image attachments appear in the composer. It covers both one image and multiple images.

Data flow: The test uses a snapshot helper to create a composer, attach image file paths, render the composer state, and compare the output with saved snapshots.

Call relations: It delegates setup and rendering to snapshot_composer_state, while the closure supplies the image-attachment actions being tested.

Call graph: 1 external calls (snapshot_composer_state).

tests::remote_image_rows_snapshots7770–7817 ↗
fn remote_image_rows_snapshots()

Purpose: Checks how remote image URL rows are displayed, selected, and deleted. This protects the attachment UI for images referenced by web links.

Data flow: Each snapshot case sets remote image URLs and text content. Some cases move selection with Up keys or delete the selected row, then the rendered composer is compared with a snapshot.

Call relations: The test uses snapshot_composer_state for rendering and handle_key_event for selection and deletion behavior, matching the real keyboard flow.

Call graph: 1 external calls (snapshot_composer_state).

tests::slash_popup_model_first_for_mo_ui7820–7848 ↗
fn slash_popup_model_first_for_mo_ui()

Purpose: Checks the visual slash-command popup after typing '/mo'. The expected UI should show '/model' as the first suggestion.

Data flow: The test types '/', 'm', and 'o' with realistic pauses, renders the composer in a test terminal, and compares the screen with a saved snapshot.

Call relations: It uses tests::type_chars_humanlike to avoid paste detection, then calls render through a ratatui test terminal.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert_snapshot!, panic!, type_chars_humanlike, new).

tests::slash_popup_model_first_for_mo_logic7851–7876 ↗
fn slash_popup_model_first_for_mo_logic()

Purpose: Checks the underlying selection logic for '/mo', separate from the visual snapshot. The selected command should be the built-in 'model' command.

Data flow: The test types '/mo', inspects the active popup, and verifies that the selected item is a built-in command whose command text is 'model'.

Call relations: It uses tests::type_chars_humanlike for input and then reads composer.popups.active to confirm the command popup’s internal state.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, type_chars_humanlike).

tests::slash_popup_resume_for_res_ui7879–7904 ↗
fn slash_popup_resume_for_res_ui()

Purpose: Checks the visual slash-command popup after typing '/res'. The expected first suggestion is '/resume'.

Data flow: The test types the prefix, renders the composer into a fixed-size test terminal, and compares the result to a snapshot.

Call relations: It relies on tests::type_chars_humanlike for realistic typing and ratatui rendering for the displayed result.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_snapshot!, type_chars_humanlike, new).

tests::slash_popup_archive_for_ar_ui7907–7930 ↗
fn slash_popup_archive_for_ar_ui()

Purpose: Checks the visual slash-command popup after typing '/ar'. This protects the ordering and appearance of suggestions such as '/archive'.

Data flow: The test types '/ar', renders the composer in a test backend, and checks the rendered buffer against a stored snapshot.

Call relations: It follows the same snapshot-rendering path as the other slash popup UI tests.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_snapshot!, type_chars_humanlike, new).

tests::slash_popup_resume_for_res_logic7933–7958 ↗
fn slash_popup_resume_for_res_logic()

Purpose: Checks that the selected command for '/res' is the built-in 'resume' command. This makes sure the popup’s choice matches the visual expectation.

Data flow: The test types '/res', inspects the active command popup, and expects the selected built-in command to report 'resume'.

Call relations: It pairs with the UI snapshot test by checking the internal selected item directly.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, type_chars_humanlike).

tests::slash_popup_pets_for_pet_ui7961–7984 ↗
fn slash_popup_pets_for_pet_ui()

Purpose: Checks the visual popup for the '/pet' prefix. It ensures the suggested '/pets' command appears as expected.

Data flow: The test types '/pet', renders the composer into a test terminal, and compares the output with a snapshot.

Call relations: It uses tests::type_chars_humanlike for input and the composer render path for visual verification.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_snapshot!, type_chars_humanlike, new).

tests::slash_popup_pets_for_pet_logic7987–8012 ↗
fn slash_popup_pets_for_pet_logic()

Purpose: Checks that typing '/pet' selects the built-in 'pets' command. This verifies the matching logic behind the popup.

Data flow: The test types the prefix, reads the active popup, and asserts that the selected built-in command is 'pets'.

Call relations: It complements tests::slash_popup_pets_for_pet_ui by testing state rather than screen output.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, type_chars_humanlike).

tests::slash_popup_btw_for_bt_ui8015–8038 ↗
fn slash_popup_btw_for_bt_ui()

Purpose: Checks the visual popup for the '/bt' prefix. It protects the suggestion list that should include the 'btw' command first.

Data flow: The test types '/bt', renders the composer, and compares the test terminal buffer with the saved snapshot.

Call relations: It uses the standard humanlike typing helper and the composer’s render method.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_snapshot!, type_chars_humanlike, new).

tests::slash_popup_btw_for_bt_logic8041–8066 ↗
fn slash_popup_btw_for_bt_logic()

Purpose: Checks that typing '/bt' selects the built-in 'btw' command. This ensures fuzzy or prefix matching chooses the intended command.

Data flow: The test types '/bt', inspects the command popup, and expects the selected built-in command to be 'btw'.

Call relations: It verifies the same behavior shown by the matching UI snapshot, but through direct popup state.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, type_chars_humanlike).

tests::slash_popup_side_for_si_ui8069–8092 ↗
fn slash_popup_side_for_si_ui()

Purpose: Checks the visual popup for the '/si' prefix. The popup should present the 'side' command as expected.

Data flow: The test types '/si', draws the composer to a test terminal, and compares that drawing with a stored snapshot.

Call relations: It uses tests::type_chars_humanlike and the normal render path, like the other slash popup snapshot tests.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_snapshot!, type_chars_humanlike, new).

tests::slash_popup_side_for_si_logic8095–8120 ↗
fn slash_popup_side_for_si_logic()

Purpose: Checks that typing '/si' selects the built-in 'side' command. This protects command suggestion ordering and matching.

Data flow: The test types the prefix, looks at the active command popup, and verifies the selected item’s command text is 'side'.

Call relations: It pairs with the UI snapshot test by checking the popup’s internal selected command.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert_eq!, panic!, type_chars_humanlike).

tests::service_tier_slash_command_dispatches_from_catalog_name8123–8152 ↗
fn service_tier_slash_command_dispatches_from_catalog_name()

Purpose: Checks that service-tier commands supplied from a catalog can be run by their displayed slash name. This lets dynamic commands behave like built-in slash commands.

Data flow: The test enables service-tier commands, registers one named 'fast', types '/fast', and presses Enter. The result should be an InputResult carrying that exact ServiceTierCommand record.

Call relations: It uses composer configuration setters, tests::type_chars_humanlike for input, and handle_key_event for dispatch.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_eq!, type_chars_humanlike, vec!).

tests::flush_after_paste_burst8154–8157 ↗
fn flush_after_paste_burst(composer: &mut ChatComposer) -> bool

Purpose: Provides a small test helper that waits long enough for paste-burst buffering to expire, then asks the composer to flush it. Tests use it to avoid guessing timing by hand.

Data flow: It receives a mutable composer, sleeps for the recommended active flush delay, calls the composer’s flush method, and returns whether anything was flushed.

Call relations: Paste-burst tests call this helper after feeding buffered characters, so they can check the final textarea contents.

Call graph: calls 2 internal fn (flush_paste_burst_if_due, recommended_active_flush_delay); 1 external calls (sleep).

tests::type_chars_humanlike8160–8177 ↗
fn type_chars_humanlike(composer: &mut ChatComposer, chars: &[char])

Purpose: Provides a test helper that types characters slowly enough to look like human typing rather than a paste. This keeps slash-command tests from being confused by paste-burst detection.

Data flow: It receives a composer and a list of characters. For each character it sends a key event, waits for the recommended paste flush delay, flushes if needed, and sends a release event for spaces.

Call relations: Many slash popup and command-dispatch tests call this helper before inspecting popup state or pressing Enter.

Call graph: calls 3 internal fn (flush_paste_burst_if_due, handle_key_event, recommended_paste_flush_delay); 4 external calls (Char, new, new_with_kind, sleep).

tests::slash_init_dispatches_command_and_does_not_submit_literal_text8180–8226 ↗
fn slash_init_dispatches_command_and_does_not_submit_literal_text()

Purpose: Checks that typing '/init' and pressing Enter runs the slash command instead of submitting the literal text '/init'.

Data flow: The test types '/init' humanlike, presses Enter, and expects an InputResult::Command whose command is 'init'. It also checks that the textarea is cleared afterward.

Call relations: It uses tests::type_chars_humanlike for input and handle_key_event for command dispatch, covering the real command-entry flow.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, panic!, type_chars_humanlike).

tests::kill_buffer_persists_after_submit8229–8260 ↗
fn kill_buffer_persists_after_submit()

Purpose: Checks that the kill buffer survives after submitting a normal message. The kill buffer is like a clipboard for text removed with Control-K and restored with Control-Y.

Data flow: The test inserts 'restore me', moves the cursor to the start, presses Control-K to remove and save it, submits a separate 'hello' message, then presses Control-Y. The original 'restore me' should return.

Call relations: It drives Emacs-style editing shortcuts and normal submission through handle_key_event, verifying that submit cleanup does not erase the kill buffer.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert!, assert_eq!).

tests::kill_buffer_persists_after_slash_command_dispatch8263–8298 ↗
fn kill_buffer_persists_after_slash_command_dispatch()

Purpose: Checks that the kill buffer also survives after running a slash command. Command dispatch should not wipe text the user previously cut.

Data flow: The test kills 'restore me', enters '/diff', presses Enter, confirms the Diff command ran, then presses Control-Y and expects 'restore me' to be restored.

Call relations: It combines editing shortcuts with slash-command dispatch through handle_key_event.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, panic!).

tests::slash_command_disabled_while_task_running_keeps_text8301–8342 ↗
fn slash_command_disabled_while_task_running_keeps_text()

Purpose: Checks that slash commands that cannot run during an active task are rejected without deleting the user’s text. This prevents a failed command from losing work.

Data flow: The test marks a task as running, enters '/review these changes', and presses Enter. It expects no input result, the same text still in the composer, and an error history cell explaining the command is disabled while a task is in progress.

Call relations: It uses set_task_running to simulate busy state, handle_key_event for Enter, and the app event channel to verify user-facing error reporting.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::enter_queues_when_queue_submissions_is_enabled8345–8377 ↗
fn enter_queues_when_queue_submissions_is_enabled()

Purpose: Checks that Enter queues text instead of submitting it when queue-submissions mode is enabled. This is useful before a session is ready or while work must be delayed.

Data flow: The test enables queueing, fills the textarea, and presses Enter. The result should be a queued plain input containing the draft text.

Call relations: It tests the normal Enter path after set_queue_submissions changes the composer’s mode.

Call graph: calls 2 internal fn (new, new); 2 external calls (new, assert_eq!).

tests::tab_queues_slash_led_prompts_while_task_running_without_validation8380–8425 ↗
fn tab_queues_slash_led_prompts_while_task_running_without_validation()

Purpose: Checks that Tab can queue slash-led prompts while a task is running, without immediately validating whether the slash command exists. This lets queued input be interpreted later.

Data flow: For several inputs starting with '/', the inner helper creates a busy composer, presses Tab, and expects a queued result with ParseSlash action. The textarea is cleared and no error event is emitted.

Call relations: The test uses handle_key_event with Tab as the queue trigger and checks both the returned InputResult and the absence of app-event errors.

tests::remapped_submit_does_not_fall_back_to_enter8428–8461 ↗
fn remapped_submit_does_not_fall_back_to_enter()

Purpose: Checks that when the submit key is remapped, plain Enter no longer submits. This respects user-customized key bindings.

Data flow: The test remaps submit to Control-J, enters text, then presses Enter. The result should be None, and Enter should insert a newline into the textarea instead.

Call relations: It configures a RuntimeKeymap, applies it to the composer, and then tests handle_key_event with the old default key.

Call graph: calls 3 internal fn (new, new, defaults); 3 external calls (new, assert_eq!, vec!).

tests::remapped_queue_does_not_fall_back_to_tab8464–8494 ↗
fn remapped_queue_does_not_fall_back_to_tab()

Purpose: Checks that when the queue key is remapped, plain Tab no longer queues input. This prevents old default shortcuts from staying active by accident.

Data flow: The test marks a task as running, remaps queue to Control-Q, enters text, and presses Tab. The result should be None and the draft should remain unchanged.

Call relations: It uses RuntimeKeymap defaults as a base, changes the queue binding, applies it, and verifies the old Tab behavior is disabled.

Call graph: calls 3 internal fn (new, new, defaults); 3 external calls (new, assert_eq!, vec!).

tests::remapped_history_search_does_not_fall_back_to_ctrl_r8497–8522 ↗
fn remapped_history_search_does_not_fall_back_to_ctrl_r()

Purpose: Checks that remapping history search removes the old Control-R shortcut. History search should open only from the configured key.

Data flow: The test remaps previous-history search to F2. Control-R should not activate history search, while F2 should.

Call relations: It applies a custom RuntimeKeymap and uses handle_key_event twice, checking composer.history_search_active after each key.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (Char, F, new, assert!, vec!).

tests::tab_queues_leading_space_slash_as_plain_text_while_task_running8525–8555 ↗
fn tab_queues_leading_space_slash_as_plain_text_while_task_running()

Purpose: Checks that text with a leading space before a slash is queued as plain text, not as a slash command. The leading space means the user likely meant ordinary text.

Data flow: The test enters ' /does-not-exist' while a task is running and presses Tab. The queued text is trimmed to '/does-not-exist', but the queue action is Plain.

Call relations: It uses the task-running queue path through handle_key_event and verifies the action chosen for the queued input.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, panic!).

tests::tab_queues_bang_shell_prompts_while_task_running_without_execution8558–8602 ↗
fn tab_queues_bang_shell_prompts_while_task_running_without_execution()

Purpose: Checks that bang-prefixed shell prompts, such as '!echo hi', are queued while a task is running instead of being executed immediately. This prevents commands from running at the wrong time.

Data flow: For each sample input, the inner helper creates a busy composer, presses Tab, and expects a queued result with RunShell action. The composer is cleared and no immediate shell-help event is emitted.

Call relations: It tests the Tab queue path for shell-style input and confirms that queueing does not trigger execution or help output right away.

tests::slash_tab_completion_moves_cursor_to_end8605–8630 ↗
fn slash_tab_completion_moves_cursor_to_end()

Purpose: Checks that Tab completion for a slash command fills in the full command and moves the cursor to the end. This makes continued typing natural after completion.

Data flow: The test types '/c', presses Tab, and expects the textarea to become '/compact ' with the cursor positioned after the trailing space.

Call relations: It uses tests::type_chars_humanlike to open the slash popup, then handle_key_event with Tab to test completion.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, type_chars_humanlike).

tests::slash_tab_completion_wins_over_queueing_while_task_running8633–8660 ↗
fn slash_tab_completion_wins_over_queueing_while_task_running()

Purpose: Checks that slash-command completion takes priority over queueing when a task is running. If a popup selection is active, Tab should complete it rather than queue the partial text.

Data flow: The test marks the composer busy, types '/mo', presses Tab, and expects no queued result. The textarea should become '/model ' and the cursor should be at the end.

Call relations: It combines task-running state with the slash popup flow, proving handle_key_event chooses completion before the queue behavior.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, type_chars_humanlike).

tests::slash_key_completes_selected_slash_command_as_text8663–8689 ↗
fn slash_key_completes_selected_slash_command_as_text()

Purpose: Checks that pressing '/' while a slash-command suggestion is selected completes that command as text. This gives users another way to accept a suggested slash command.

Data flow: The test types '/m', then sends another '/' key. The result is None, and the textarea becomes '/model ' with the cursor at the end.

Call relations: It uses tests::type_chars_humanlike to create the popup state, then handle_key_event for the accepting '/' key.

Call graph: calls 2 internal fn (new, new); 4 external calls (Char, new, assert_eq!, type_chars_humanlike).

tests::slash_tab_then_enter_dispatches_builtin_command8692–8730 ↗
fn slash_tab_then_enter_dispatches_builtin_command()

Purpose: Checks that completing a slash command with Tab still makes Enter run the command. This protects the common flow where a user types part of /diff, accepts the completion, and then submits it.

Data flow: It starts with an empty composer, types /di, presses Tab, and expects the visible draft to become /diff . Then it presses Enter and expects a command result for diff, with the draft cleared afterward.

Call relations: The test runner calls this test. Inside it, the test builds a ChatComposer, uses type_chars_humanlike to mimic typing, and sends key events through handle_key_event, which is the same path real keyboard input uses.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, panic!, type_chars_humanlike).

tests::slash_command_elementizes_on_space8733–8752 ↗
fn slash_command_elementizes_on_space()

Purpose: Checks that a known slash command becomes a special text element once the user types a space after it. A text element is tracked metadata attached to part of the text, like a label on a package.

Data flow: It enables collaboration modes, types /plan , then reads the raw text and its attached elements. The result should be the text /plan plus one element marking /plan as a placeholder-like command token.

Call relations: The test exercises the composer through normal typing with type_chars_humanlike. It verifies that the text area’s element-tracking layer is updated as part of ordinary input.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, type_chars_humanlike).

tests::slash_command_elementizes_only_known_commands8755–8773 ↗
fn slash_command_elementizes_only_known_commands()

Purpose: Checks that ordinary text starting with a slash is not treated as a command unless the command is known. This prevents paths or names like /Users from being decorated as special commands.

Data flow: It types /Users into a composer with collaboration modes enabled. The text remains /Users , and the list of special text elements stays empty.

Call relations: The test runner calls this as a guard around slash-command recognition. It uses the same typing helper as real character input, then inspects the composer’s stored text elements.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, type_chars_humanlike).

tests::slash_command_element_removed_when_not_at_start8776–8801 ↗
fn slash_command_element_removed_when_not_at_start()

Purpose: Checks that a slash command only stays special when it is at the beginning of the message. If text is inserted before it, it should become ordinary text.

Data flow: It first types /review and confirms one command element exists. Then it moves the cursor to the start, inserts x, and expects the text to become x/review with no command element left.

Call relations: This test connects cursor editing with command metadata. It simulates a user editing before an already-recognized command and verifies the composer cleans up stale metadata.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, type_chars_humanlike).

tests::tab_submits_when_no_task_running8804–8829 ↗
fn tab_submits_when_no_task_running()

Purpose: Checks that pressing Tab can submit a normal message when no task is running. This protects a shortcut-style submission path.

Data flow: It types hi, presses Tab, and expects a submitted message containing hi. After submission, the draft text area should be empty.

Call relations: The test sends the Tab key through handle_key_event. It confirms that the composer routes Tab to submission instead of completion when there is no active command or shell-command context.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, type_chars_humanlike).

tests::tab_does_not_submit_for_bang_shell_command8832–8858 ↗
fn tab_does_not_submit_for_bang_shell_command()

Purpose: Checks that Tab does not submit text that starts with !, which represents a shell-style command. This avoids accidentally running or sending shell commands too early.

Data flow: It types !ls, presses Tab, and expects no submission. The composer should still contain text beginning with !ls.

Call relations: This test covers a special branch in key handling. It proves that the Tab shortcut used for normal messages is suppressed for bang-prefixed shell commands.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, type_chars_humanlike).

tests::bang_prefixed_slash_text_submits_literal_shell_command8861–8885 ↗
fn bang_prefixed_slash_text_submits_literal_shell_command()

Purpose: Checks that !/diff is treated as literal bang-prefixed text, not as the /diff slash command. This keeps shell-style input separate from app commands.

Data flow: It types !/diff, presses Enter, and expects a normal submitted message with exactly that text.

Call relations: The test uses the normal Enter handling path. It verifies that slash-command detection does not look past a leading !.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, type_chars_humanlike).

tests::slash_mention_dispatches_command_and_inserts_at8888–8932 ↗
fn slash_mention_dispatches_command_and_inserts_at()

Purpose: Checks that /mention is dispatched as a command and that the composer can accept new text afterward. This protects the flow where a command opens or prepares mention insertion.

Data flow: It types /mention, presses Enter, and expects a command result named mention. The draft is cleared, then inserting @ leaves the composer containing @.

Call relations: The test combines command submission with later direct insertion. It proves that command dispatch resets the composer cleanly enough for the next editing step.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, assert!, assert_eq!, panic!, type_chars_humanlike).

tests::slash_plan_args_preserve_text_elements8935–8970 ↗
fn slash_plan_args_preserve_text_elements()

Purpose: Checks that /plan submitted with an attached image preserves the image placeholder metadata in the command arguments. This matters because the visible [Image #1] text must still point to the attached file.

Data flow: It types /plan , attaches an image, presses Enter, and expects a command-with-arguments result. The argument text is the image placeholder, and one text element still marks that placeholder.

Call relations: This test links slash-command parsing, image attachment, and text-element preservation. It exercises attach_image before the normal Enter submission path.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 5 external calls (new, from, assert_eq!, panic!, type_chars_humanlike).

tests::file_completion_preserves_large_paste_placeholder_elements8973–9026 ↗
fn file_completion_preserves_large_paste_placeholder_elements()

Purpose: Checks that file completion does not destroy metadata for a large pasted block. Large pastes are shown as short placeholders while editing, then expanded on submit.

Data flow: It pastes a large string, inserts a file mention prefix, supplies a file-search result, and presses Tab to complete it. The draft should show the paste placeholder plus src/main.rs; when submitted, the placeholder expands back to the original large text.

Call relations: The test ties together paste handling, file-search completion, and final submission. It verifies that completion edits the visible text without losing the pending paste payload.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, assert!, assert_eq!, format!, panic!, vec!).

tests::test_multiple_pastes_submission9031–9107 ↗
fn test_multiple_pastes_submission()

Purpose: Checks that several pasted chunks, both large and small, are combined correctly when submitted. Large chunks should be hidden behind placeholders during editing but restored later.

Data flow: It applies a large paste, a small paste, and another large paste. The draft shows placeholders for only the large ones; pressing Enter produces the full original combined text.

Call relations: This test stresses handle_paste over repeated calls, then uses Enter submission to make sure the pending paste list is expanded in the right order.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, new, assert_eq!, panic!).

tests::test_placeholder_deletion9110–9182 ↗
fn test_placeholder_deletion()

Purpose: Checks that deleting large-paste placeholders also removes their stored hidden content. This prevents deleted pasted content from accidentally being submitted later.

Data flow: It adds two large paste placeholders separated by normal text. Backspacing each placeholder removes the visible placeholder and reduces the pending-paste count until none remain.

Call relations: The test routes deletion through handle_key_event with Backspace. It confirms that the text editor and the composer’s hidden paste storage stay in sync.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::deleting_duplicate_length_pastes_removes_only_target9187–9224 ↗
fn deleting_duplicate_length_pastes_removes_only_target()

Purpose: Checks that two large pastes of the same length can still be deleted separately. This avoids confusing one placeholder for another just because their labels are similar.

Data flow: It creates two identical-size large paste placeholders, where the second gets a #2 suffix. Backspacing the second removes only that second entry, leaving the first placeholder and payload intact.

Call relations: This test protects the placeholder identity logic used by deletion. It proves that deletion follows the exact placeholder instance, not just the pasted text length.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, format!).

tests::large_paste_numbering_continues_with_same_length_placeholder9229–9269 ↗
fn large_paste_numbering_continues_with_same_length_placeholder()

Purpose: Checks that numbering for same-length large paste placeholders keeps increasing while any older matching placeholder remains. This keeps labels unique and unambiguous.

Data flow: It creates two same-length paste placeholders, deletes the first, then adds another paste of the same length. The remaining old one keeps #2, and the new one becomes #3.

Call relations: The test combines paste creation, deletion, and another paste. It verifies that the composer does not reuse a number too early and create duplicate labels.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, format!).

tests::large_paste_numbering_reuses_after_all_deleted9274–9308 ↗
fn large_paste_numbering_reuses_after_all_deleted()

Purpose: Checks that large-paste numbering resets once all matching placeholders are gone. This keeps the UI tidy after the user deletes everything.

Data flow: It adds one large paste, deletes it, confirms the draft and pending-paste list are empty, then adds the same paste again. The placeholder uses the base label again without a suffix.

Call relations: This test covers the reset case after placeholder cleanup. It ensures deletion fully clears the state that affects future placeholder names.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, format!).

tests::test_partial_placeholder_deletion9311–9360 ↗
fn test_partial_placeholder_deletion()

Purpose: Checks that editing inside or at the edge of a large-paste placeholder clears its tracking. A broken placeholder should not still expand to hidden content.

Data flow: For each case, it inserts a large paste, moves the cursor near or at the placeholder end, presses Backspace, and checks that the placeholder is gone and no pending paste remains.

Call relations: The test uses Backspace through the normal key handler. It verifies that partial placeholder edits are treated as removing the whole tracked paste reference.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, format!).

tests::attach_image_and_submit_includes_local_image_paths9364–9399 ↗
fn attach_image_and_submit_includes_local_image_paths()

Purpose: Checks that attaching an image and submitting text includes both the visible image placeholder and the image path. This is how the rest of the app knows which local file belongs to [Image #1].

Data flow: It attaches /tmp/image1.png, pastes hi, and presses Enter. The submitted text is [Image #1] hi, one text element marks [Image #1], and the recent submission image list contains the path.

Call relations: This test connects attach_image, paste input, submission, and later retrieval with take_recent_submission_images. It confirms image metadata survives submission.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, from, assert_eq!, panic!).

tests::submit_captures_recent_mention_bindings_before_clearing_textarea9402–9433 ↗
fn submit_captures_recent_mention_bindings_before_clearing_textarea()

Purpose: Checks that mention bindings are saved for the just-submitted message before the composer clears itself. A mention binding links visible mention text to a real target such as a skill file.

Data flow: It loads text containing $figma with a binding to a path, presses Enter, and expects a submission. Afterward, recent submission bindings still contain the binding, while the active draft bindings are empty.

Call relations: The test exercises set_text_content_with_mention_bindings and Enter submission. It verifies that cleanup does not erase metadata needed by the submitted message.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, new, assert!, assert_eq!, vec!).

tests::history_navigation_restores_remote_and_local_image_attachments9436–9467 ↗
fn history_navigation_restores_remote_and_local_image_attachments()

Purpose: Checks that going back through input history restores both remote image URLs and local image attachments. This lets a user recall and edit an earlier image submission.

Data flow: It sets one remote image URL, attaches one local image, submits, clears current attachment state, then presses Up. The recalled draft has an image placeholder, the local path, and the remote URL restored.

Call relations: The test ties submission history to attachment restoration. It uses Enter to record history and Up-arrow handling to recall it.

Call graph: calls 2 internal fn (new, new); 7 external calls (new, from, new, new, assert!, assert_eq!, vec!).

tests::history_navigation_restores_remote_only_submissions9470–9499 ↗
fn history_navigation_restores_remote_only_submissions()

Purpose: Checks that a submission with only remote images and no text can still be recalled from history. Empty text should not mean the history entry is lost.

Data flow: It sets two remote image URLs and prepares a history-recorded submission with empty text. After clearing current state and pressing Up, the composer restores the remote URLs while leaving text empty.

Call relations: This test uses prepare_submission_text directly instead of pressing Enter. It verifies that history stores attachment-only submissions, not just typed text.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, new, new, assert!, assert_eq!, vec!).

tests::history_navigation_leaves_cursor_at_end_of_line9502–9554 ↗
fn history_navigation_leaves_cursor_at_end_of_line()

Purpose: Checks that normal history navigation leaves the cursor at the end of the recalled text. This matches what users expect when they press Up and then continue typing.

Data flow: It submits first and second, then presses Up and Down through history. Each recalled draft has the expected text and the cursor positioned after the last character.

Call relations: The test exercises history movement through handle_key_event using arrow keys. It protects cursor placement after history recall.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, type_chars_humanlike).

tests::vim_normal_j_k_navigate_history_at_history_boundaries9557–9602 ↗
fn vim_normal_j_k_navigate_history_at_history_boundaries()

Purpose: Checks that Vim normal mode uses k and j to move through history when appropriate. Vim normal mode is an editing style where letters act like commands instead of typed characters.

Data flow: It submits two messages, enables Vim mode, presses k twice to recall newer then older history, and presses j twice to move back down to an empty draft. The cursor lands in Vim-style positions.

Call relations: This test routes Vim keys through the same key handler as all input. It confirms Vim history movement works at both ends of the history list.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, type_chars_humanlike).

tests::remapped_vim_normal_history_navigation_does_not_fall_back_to_j_k9605–9638 ↗
fn remapped_vim_normal_history_navigation_does_not_fall_back_to_j_k()

Purpose: Checks that if Vim history keys are remapped, the old j and k keys no longer secretly work as history shortcuts. This makes custom keybindings predictable.

Data flow: It submits first, changes Vim move-up to F2 and move-down to F3, then enables Vim mode. Pressing k does nothing to history, F2 recalls first, and F3 returns to an empty draft.

Call relations: The test uses RuntimeKeymap::defaults, changes bindings, and gives them to the composer. It verifies that key handling respects the configured keymap over built-in defaults.

Call graph: calls 3 internal fn (new, new, defaults); 7 external calls (Char, F, new, assert!, assert_eq!, type_chars_humanlike, vec!).

tests::vim_normal_j_k_fall_back_to_multiline_cursor_movement9641–9663 ↗
fn vim_normal_j_k_fall_back_to_multiline_cursor_movement()

Purpose: Checks that Vim j and k move within a multi-line draft when there is text to move through. They should not always mean history navigation.

Data flow: It sets the draft to one\ntwo, places the cursor at the start, enables Vim mode, presses j, and expects the cursor to move to the second line. Pressing k moves it back to the start.

Call relations: This test focuses on the fallback path inside Vim key handling. It confirms the composer chooses text cursor movement before history recall in a multi-line editing situation.

Call graph: calls 2 internal fn (new, new); 3 external calls (Char, new, assert_eq!).

tests::vim_normal_operator_motion_does_not_navigate_history9666–9696 ↗
fn vim_normal_operator_motion_does_not_navigate_history()

Purpose: Checks that a Vim operator followed by k does not navigate history. In Vim, an operator like d waits for a motion key, so dk should be treated as an edit command attempt, not history recall.

Data flow: It submits two messages, enables Vim mode, recalls second with k, then presses d and k. The draft becomes empty rather than moving to another history entry.

Call relations: The test protects the interaction between Vim operator-pending state and history navigation. It ensures handle_key_event lets the text area consume operator motions first.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, type_chars_humanlike).

tests::vim_normal_operator_pending_consumes_submit_key9699–9725 ↗
fn vim_normal_operator_pending_consumes_submit_key()

Purpose: Checks that pressing Enter while a Vim operator is waiting does not submit the message. Instead, it cancels or resolves the pending Vim state.

Data flow: It sets the draft to hello, enables Vim mode, presses d to enter operator-pending state, then presses Enter. The result is no submission, the text stays hello, and Vim returns to normal mode.

Call relations: This test ensures submission keys are not always submission keys. It verifies that Vim editing state gets first chance to consume Enter.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, new, assert!, assert_eq!).

tests::remapped_editor_history_navigation_does_not_fall_back_to_up9728–9756 ↗
fn remapped_editor_history_navigation_does_not_fall_back_to_up()

Purpose: Checks that if normal editor history navigation is remapped away from the Up arrow, Up no longer recalls history. This makes non-Vim key customization reliable.

Data flow: It submits first, changes editor move-up to F2, then presses Up and sees the draft remain empty. Pressing F2 recalls first.

Call relations: The test configures a runtime keymap and then sends key events through the composer. It verifies the editor-mode keymap is honored rather than mixed with old defaults.

Call graph: calls 3 internal fn (new, new, defaults); 6 external calls (F, new, assert!, assert_eq!, type_chars_humanlike, vec!).

tests::history_navigation_from_start_of_bang_command_recalls_older_entry9759–9792 ↗
fn history_navigation_from_start_of_bang_command_recalls_older_entry()

Purpose: Checks that pressing Up from the start of a recalled bang command moves to the older history entry. This helps users navigate past shell-style entries.

Data flow: It submits first, then submits !git. Pressing Up recalls !git; moving the cursor to the start and pressing Up again recalls first.

Call relations: This test covers history navigation with special bang-prefixed text. It verifies cursor position affects whether Up moves within text or through history.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, type_chars_humanlike).

tests::vim_normal_history_navigation_from_start_of_bang_command_recalls_older_entry9795–9827 ↗
fn vim_normal_history_navigation_from_start_of_bang_command_recalls_older_entry()

Purpose: Checks the same bang-command history behavior in Vim normal mode. It ensures Vim users can move past a recalled !git entry to older history.

Data flow: It submits first and !git, enables Vim mode, and presses k twice. The first press recalls !git; the second recalls first, with Vim-style cursor placement.

Call relations: The test mirrors the arrow-key bang-history case but uses Vim key handling. It confirms the special history behavior is shared across input modes.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, assert!, assert_eq!, type_chars_humanlike).

tests::set_text_content_reattaches_images_without_placeholder_metadata9830–9852 ↗
fn set_text_content_reattaches_images_without_placeholder_metadata()

Purpose: Checks that setting text content can restore local image paths even when the text element lacks explicit placeholder text. This supports restoring older or simplified saved state.

Data flow: It builds text containing an image label, creates a text element over that label without stored placeholder metadata, and passes a local image path. The composer’s local image list is restored with that path.

Call relations: The test calls set_text_content directly. It verifies that image attachment restoration can infer enough from position and supplied paths, not only from rich placeholder metadata.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 4 external calls (from, assert_eq!, format!, vec!).

tests::large_paste_preserves_image_text_elements_on_submit9855–9895 ↗
fn large_paste_preserves_image_text_elements_on_submit()

Purpose: Checks that a large paste before an image does not shift or lose the image’s text element when submitted. The image marker must still point to the correct part of the final expanded text.

Data flow: It inserts a large paste, a space, and an image. On Enter, the large paste expands, the submitted text ends with [Image #1], and the image element’s byte range points to that placeholder.

Call relations: This test combines large-paste expansion with image attachment metadata. It protects the position-adjustment logic used during submission.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, from, assert_eq!, format!, panic!).

tests::large_paste_with_leading_whitespace_trims_and_shifts_elements9898–9938 ↗
fn large_paste_with_leading_whitespace_trims_and_shifts_elements()

Purpose: Checks that trimming leading whitespace from a large paste still leaves image element positions correct. This prevents metadata from pointing a few bytes too early or too late.

Data flow: It pastes a large string with leading spaces, adds a space and an image, then submits. The submitted text uses the trimmed paste, and the image element starts after the trimmed content plus one space.

Call relations: The test stresses submission cleanup where text is both expanded and trimmed. It confirms the image placeholder’s range is recalculated after those changes.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, from, assert_eq!, format!, panic!).

tests::pasted_crlf_normalizes_newlines_for_elements9941–9981 ↗
fn pasted_crlf_normalizes_newlines_for_elements()

Purpose: Checks that pasted Windows-style newlines are normalized before submission and that text element positions still match. Windows-style newlines use carriage return plus newline, written as CRLF.

Data flow: It pastes line1\r\nline2\r\n, adds a space and an image, then submits. The final text contains only \n newlines, and the image element range points to [Image #1] after the normalized text.

Call relations: This test connects paste normalization with image metadata. It ensures newline cleanup does not corrupt byte ranges used by attached elements.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, from, assert!, assert_eq!, panic!).

tests::suppressed_submission_restores_pending_paste_payload9984–10034 ↗
fn suppressed_submission_restores_pending_paste_payload()

Purpose: Checks that if submission is blocked, a large paste remains available for a later valid submission. This prevents hidden pasted content from being lost after a failed command attempt.

Data flow: It creates text beginning with /unknown , adds a large paste placeholder, and presses Enter. Submission is suppressed, and the pending paste remains. After changing the text so it is no longer treated as a command, Enter submits the expanded large content.

Call relations: The test covers a failure-and-retry path through handle_key_event. It verifies command validation does not accidentally consume or clear pending paste data.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::attach_image_without_text_submits_empty_text_and_images10037–10073 ↗
fn attach_image_without_text_submits_empty_text_and_images()

Purpose: Checks that an image-only message can be submitted. A user should not need to type words just to send an attachment.

Data flow: It attaches /tmp/image2.png and presses Enter. The submitted text is [Image #1], one text element marks it, the recent submission image list contains the path, and active local attachments are cleared.

Call relations: This test exercises the shortest image submission path: attach_image followed directly by Enter. It verifies both the outgoing metadata and composer cleanup.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, from, assert!, assert_eq!, panic!).

tests::duplicate_image_placeholders_get_suffix10076–10102 ↗
fn duplicate_image_placeholders_get_suffix()

Purpose: Checks that multiple image attachments get distinct labels like [Image #1] and [Image #2]. Unique labels keep each visible placeholder tied to the right file.

Data flow: It attaches an image, inserts a space, and attaches another image. The draft contains both labels, and the stored attachment records use matching placeholders.

Call relations: The test focuses on image placeholder naming during repeated attach_image calls. It protects against duplicate labels that would make later lookup ambiguous.

Call graph: calls 2 internal fn (new, new); 3 external calls (from, assert!, assert_eq!).

tests::image_placeholder_backspace_behaves_like_text_placeholder10105–10141 ↗
fn image_placeholder_backspace_behaves_like_text_placeholder()

Purpose: Checks that backspacing an image placeholder removes the image attachment, but editing inside the placeholder does not accidentally remove it. This keeps image deletion predictable.

Data flow: It attaches an image and backspaces at the end, which removes the placeholder and clears the attachment list. Then it reattaches the image, moves the cursor into the middle of the placeholder, backspaces, and confirms the placeholder and attachment remain.

Call relations: The test sends Backspace through normal key handling. It verifies image placeholders follow the same protected deletion rules as other special text placeholders.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, from, assert!, assert_eq!, panic!).

tests::backspace_with_multibyte_text_before_placeholder_does_not_panic10144–10171 ↗
fn backspace_with_multibyte_text_before_placeholder_does_not_panic()

Purpose: Checks that Backspace works safely when non-English multi-byte characters are near an image placeholder. Multi-byte characters take more than one byte in memory, so careless indexing can crash.

Data flow: It attaches an image, inserts Japanese text after it, and presses Backspace at the end. The composer should not panic, the image attachment remains, and the text still starts with [Image #1].

Call relations: This test protects byte-versus-character boundary logic in deletion. It uses the normal Backspace path so the same safety applies during real editing.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, from, assert!, assert_eq!).

tests::deleting_one_of_duplicate_image_placeholders_removes_one_entry10174–10227 ↗
fn deleting_one_of_duplicate_image_placeholders_removes_one_entry()

Purpose: Checks that deleting one of two image placeholders removes only one image record and relabels the remaining image cleanly. This prevents stale attachment entries after edits.

Data flow: It attaches two images separated by a space, places the cursor after the first placeholder, and presses Backspace. One placeholder remains, it is relabeled to [Image #1], and the attachment list contains only the second image path with the new label.

Call relations: The test exercises image deletion plus relabeling through handle_key_event. It confirms the visible draft and stored attachment list are rewritten together.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, from, assert_eq!).

tests::deleting_reordered_image_one_renumbers_text_in_place10230–10290 ↗
fn deleting_reordered_image_one_renumbers_text_in_place()

Purpose: Checks that deleting one attached image updates the numbering of the remaining image even when the image labels appear out of order in the text. This prevents the visible label and the actual image list from drifting apart.

Data flow: The test starts with two image paths and text where [Image #2] appears before [Image #1]. It places the cursor after the first image label and simulates Backspace. Afterward, the deleted image is gone, the second image becomes [Image #1], and the attachment list points to the second file with the new label.

Call relations: The test runner calls this test. The test builds a ChatComposer, uses set_text_content to install custom text and image elements, then sends a Backspace through handle_key_event to exercise the same deletion path a user would trigger.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 5 external calls (new, from, assert_eq!, format!, vec!).

tests::deleting_first_text_element_renumbers_following_text_element10293–10329 ↗
fn deleting_first_text_element_renumbers_following_text_element()

Purpose: Checks that deleting the first of two adjacent image placeholders renumbers the second one correctly. This protects the case where two image labels touch each other with no normal text between them.

Data flow: The test attaches two local images, producing [Image #1][Image #2]. It moves the cursor to the start and sends a Delete key. The first placeholder and first image are removed, while the second image stays attached and is relabeled as [Image #1] in both the text and the attachment list.

Call relations: The test runner calls this test. It uses attach_image to create normal composer state, then uses handle_key_event so the deletion goes through the regular editor path rather than a special test-only shortcut.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, from, assert_eq!).

tests::pasting_filepath_attaches_image10332–10355 ↗
fn pasting_filepath_attaches_image()

Purpose: Checks that pasting the path to an image file attaches it as an image instead of inserting the path as plain text. This makes drag-copy or clipboard workflows work naturally.

Data flow: The test creates a temporary PNG file, pastes its file path into the composer, and checks that a redraw is requested. The visible text begins with [Image #1], and when recent submission images are taken, the stored path is the pasted file path.

Call relations: The test runner calls this test. It creates a real small image file, calls handle_paste, and then asks take_recent_submission_images what the composer stored for submission.

Call graph: calls 2 internal fn (new, new); 4 external calls (from_fn, assert!, assert_eq!, tempdir).

tests::slash_path_input_submits_without_command_error10358–10392 ↗
fn slash_path_input_submits_without_command_error()

Purpose: Checks that an absolute file path beginning with / is submitted as normal text, not treated as an invalid slash command. This matters because many Unix-style paths start with a slash.

Data flow: The test puts /Users/example/project/src/main.rs into the editor and presses Enter. The result is a normal submitted message with that path as the text, the editor is cleared, and no app event is sent as an error or command side effect.

Call relations: The test runner calls this test. It sets the text directly, then routes Enter through handle_key_event, which is the same decision point used for submitting messages or commands.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::slash_with_leading_space_submits_as_text10395–10429 ↗
fn slash_with_leading_space_submits_as_text()

Purpose: Checks that text with a leading space before a slash is treated as message text rather than a command. This lets users intentionally write slash-looking content.

Data flow: The test fills the composer with /this-looks-like-a-command and presses Enter. The submitted text is trimmed to /this-looks-like-a-command, the editor becomes empty, and no unexpected event is received.

Call relations: The test runner calls this test. It uses handle_key_event for Enter so the normal command-versus-message logic is tested.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert!, assert_eq!, panic!).

tests::pending_first_ascii_char_flushes_as_typed10434–10458 ↗
fn pending_first_ascii_char_flushes_as_typed()

Purpose: Checks the paste-burst detector when only one normal character has been typed. The character is briefly buffered, then appears as ordinary typed text after the delay.

Data flow: The test sends the character h. At first, the composer is in paste-burst mode and the text area is empty. After waiting for the recommended delay and flushing, the text becomes h and paste-burst mode ends.

Call relations: The test runner calls this test. It drives input through handle_key_event, waits using recommended_paste_flush_delay, and then calls flush_paste_burst_if_due to test the delayed flush path.

Call graph: calls 3 internal fn (new, new, recommended_paste_flush_delay); 5 external calls (Char, new, assert!, assert_eq!, sleep).

tests::burst_paste_fast_small_buffers_and_flushes_on_stop10463–10509 ↗
fn burst_paste_fast_small_buffers_and_flushes_on_stop()

Purpose: Checks that a fast but small burst of characters is buffered during the burst and inserted as plain text once the burst stops. This avoids flicker while still preserving normal pasted text.

Data flow: The test sends 32 a characters one millisecond apart. During the burst, the visible text stays empty. After enough quiet time passes, flushing inserts all the a characters and does not create a large-paste placeholder.

Call relations: The test runner calls this test. It uses handle_input_basic_with_time to control the clock precisely, then calls handle_paste_burst_flush to simulate the moment when rapid input has stopped.

Call graph: calls 3 internal fn (new, new, recommended_active_flush_delay); 6 external calls (from_millis, now, Char, new, assert!, assert_eq!).

tests::burst_paste_fast_large_inserts_placeholder_on_flush10514–10548 ↗
fn burst_paste_fast_large_inserts_placeholder_on_flush()

Purpose: Checks that a very large fast paste becomes a short placeholder in the editor instead of dumping the whole text visibly. This keeps the terminal input area usable after large pastes.

Data flow: The test sends more characters than the large-paste threshold, all very quickly. Nothing appears while input is still arriving. When flushed, the editor shows a placeholder like [Pasted Content N chars], while the full pasted text is stored separately in pending_pastes.

Call relations: The test runner calls this test. It feeds timed characters through handle_input_basic_with_time and then uses handle_paste_burst_flush to trigger the large-paste conversion.

Call graph: calls 3 internal fn (new, new, recommended_active_flush_delay); 7 external calls (from_millis, now, Char, new, assert!, assert_eq!, format!).

tests::humanlike_typing_1000_chars_appears_live_no_placeholder10553–10570 ↗
fn humanlike_typing_1000_chars_appears_live_no_placeholder()

Purpose: Checks that slow, human-like typing is not mistaken for a large paste. Even many characters should appear normally if they arrive at a human pace.

Data flow: The test prepares many z characters and types them with a helper that simulates realistic timing. The final editor text contains all the z characters, and there are no pending paste placeholders.

Call relations: The test runner calls this test. It relies on type_chars_humanlike to drive the composer in a way that should bypass paste-burst behavior.

Call graph: calls 2 internal fn (new, new); 4 external calls (assert!, assert_eq!, type_chars_humanlike, vec!).

tests::slash_popup_not_activated_for_slash_space_text_history_like_input10573–10602 ↗
fn slash_popup_not_activated_for_slash_space_text_history_like_input()

Purpose: Checks that text like / test does not open the slash-command popup. This protects recalled history or normal text that starts with slash-space from being treated as a command search.

Data flow: The test installs / test as the composer text. The active popup remains None. Pressing Up then follows the normal history/navigation path and returns no special input result.

Call relations: The test runner calls this test. It uses set_text_content, which also refreshes popup state, and then sends Up through handle_key_event to ensure no command popup intercepts it.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, new, assert!, assert_eq!).

tests::slash_popup_activated_for_bare_slash_and_valid_prefixes10605–10648 ↗
fn slash_popup_activated_for_bare_slash_and_valid_prefixes()

Purpose: Checks when the slash-command popup should appear. A bare slash and valid command-like prefixes should show it, while a completely unmatched prefix should not.

Data flow: The test sets the composer text to /, /re, /ac, and /zzz in turn. The first three activate the command popup, including a fuzzy match where letters match in order, and /zzz leaves no popup active.

Call relations: The test runner calls this test. Each call to set_text_content forces the composer to resync popup state, which is what this test inspects.

Call graph: calls 2 internal fn (new, new); 2 external calls (new, assert!).

tests::bare_slash_command_can_be_recalled_after_recording_pending_history10651–10673 ↗
fn bare_slash_command_can_be_recalled_after_recording_pending_history()

Purpose: Checks that a completed slash command can be saved into history and later recalled. This lets users press Up to bring back a command they just ran.

Data flow: The test enters /diff and presses Enter, producing the Diff command result. It records pending slash-command history, then presses Up. The composer text becomes /diff again.

Call relations: The test runner calls this test. It routes Enter and Up through handle_key_event, with record_pending_slash_command_history linking the executed command to the history system.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, new, assert_eq!).

tests::popup_selected_slash_command_records_canonical_command_history10676–10698 ↗
fn popup_selected_slash_command_records_canonical_command_history()

Purpose: Checks that choosing a command from a partial popup entry records the full canonical command in history. For example, /di should come back as /diff.

Data flow: The test enters /di, presses Enter, and gets the Diff command. After recording pending history and pressing Up, the composer recalls /diff, not the partial text /di.

Call relations: The test runner calls this test. It exercises the popup-selection path through handle_key_event and then uses the same history recording and recall path as real command submission.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, new, assert_eq!).

tests::inline_slash_command_can_be_recalled_after_recording_pending_history10701–10732 ↗
fn inline_slash_command_can_be_recalled_after_recording_pending_history()

Purpose: Checks that slash commands with extra arguments are parsed, recorded, and recalled with their arguments intact. This is important for commands like planning or review requests that include user text.

Data flow: The test enables collaboration modes, enters /plan investigate this, disables the popup, and presses Enter. The result is a Plan command with investigate this as its argument. After recording history and pressing Up, the full original command line returns.

Call relations: The test runner calls this test. It uses set_collaboration_modes_enabled, set_text_content, and handle_key_event to exercise inline command parsing, then verifies recall through the history path.

Call graph: calls 2 internal fn (new, new); 5 external calls (new, new, assert!, assert_eq!, panic!).

tests::apply_external_edit_rebuilds_text_and_attachments10735–10773 ↗
fn apply_external_edit_rebuilds_text_and_attachments()

Purpose: Checks that text returned from an external editor is rebuilt into the composer while preserving valid image placeholders. It also verifies stale pending paste placeholders are cleared.

Data flow: The test starts with an image placeholder, one attached image, and one pending paste. It applies edited text containing the same image placeholder. The composer text becomes the edited text, pending pastes are removed, the image attachment remains, and the cursor moves to the end.

Call relations: The test runner calls this test. It builds internal state directly, then calls apply_external_edit, which is the path used when edited text comes back from outside the composer.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 4 external calls (from, assert!, assert_eq!, format!).

tests::apply_external_edit_absorbs_bash_prefix_without_duplication10776–10793 ↗
fn apply_external_edit_absorbs_bash_prefix_without_duplication()

Purpose: Checks that an external edit containing a leading ! keeps bash mode without duplicating the exclamation mark. Bash mode means the composer treats the input as a shell command.

Data flow: The test starts with !git status, applies the same external text, and checks that bash mode is on. Internally the text area holds git status, while current_text shows !git status to represent the mode.

Call relations: The test runner calls this test. It uses set_text_content to enter bash-style state, then calls apply_external_edit to verify the prefix is interpreted as mode information rather than literal duplicated text.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::apply_external_edit_can_leave_bash_mode10796–10813 ↗
fn apply_external_edit_can_leave_bash_mode()

Purpose: Checks that an external edit can remove bash mode when the edited text no longer begins with !. This lets users change their mind in the external editor.

Data flow: The test starts in bash mode with !git status. It applies git status without the prefix. Bash mode turns off, and both the internal editor text and current visible text are git status.

Call relations: The test runner calls this test. It uses apply_external_edit after setting bash-style text to confirm the external-edit path can change composer mode.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::apply_external_edit_can_enter_bash_mode10816–10833 ↗
fn apply_external_edit_can_enter_bash_mode()

Purpose: Checks that an external edit can turn normal text into bash mode by adding a leading !. This keeps external editing consistent with typing directly in the composer.

Data flow: The test starts with normal git status, then applies !git status. Bash mode turns on, the internal text area stores git status, and current_text reports !git status.

Call relations: The test runner calls this test. It drives the mode transition through apply_external_edit, the same path used after closing an external editor.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert!, assert_eq!).

tests::apply_external_edit_drops_missing_attachments10836–10858 ↗
fn apply_external_edit_drops_missing_attachments()

Purpose: Checks that if an external edit removes an image placeholder, the matching attached image is also removed. This prevents hidden attachments from being submitted accidentally.

Data flow: The test starts with one image placeholder and one attached image. It applies text that contains no image placeholder. The composer text becomes No images here, and the local image list becomes empty.

Call relations: The test runner calls this test. It sets up an attachment manually, then calls apply_external_edit to verify the rebuild step removes attachments that no longer appear in the text.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 3 external calls (from, assert!, assert_eq!).

tests::apply_external_edit_renumbers_image_placeholders10861–10892 ↗
fn apply_external_edit_renumbers_image_placeholders()

Purpose: Checks that when an external edit keeps only a later image, that image is renumbered to close the gap. This keeps labels like [Image #1] and the attachment list in order.

Data flow: The test attaches two images, then applies edited text that keeps only [Image #2]. The composer rewrites the text to use [Image #1], keeps only the second image path, and updates the text element payload to the new label.

Call relations: The test runner calls this test. It uses normal attach_image setup, then sends edited text through apply_external_edit, which performs the attachment filtering and relabeling.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 3 external calls (from, assert_eq!, format!).

tests::current_text_with_pending_expands_placeholders10895–10918 ↗
fn current_text_with_pending_expands_placeholders()

Purpose: Checks that a large-paste placeholder expands back to the original pasted text when asking for submission-ready text. This protects pasted content from being replaced by its short label.

Data flow: The test inserts a placeholder into the text area and stores hello as the pending paste behind it. Calling current_text_with_pending returns hello instead of the placeholder string.

Call relations: The test runner calls this test. It directly prepares the pending-paste state and then calls current_text_with_pending, the method that turns editor display text back into real message text.

Call graph: calls 2 internal fn (new, new); 1 external calls (assert_eq!).

tests::current_text_with_pending_expands_overlapping_placeholders10921–10945 ↗
fn current_text_with_pending_expands_overlapping_placeholders()

Purpose: Checks that multiple large-paste placeholders with similar names expand to the correct full texts. This avoids replacing the wrong placeholder when labels overlap or share prefixes.

Data flow: The test pastes two large blocks, one made of a characters and one made of b characters. The visible current text contains two placeholder labels, while current_text_with_pending returns the two full pasted blocks in order.

Call relations: The test runner calls this test. It uses handle_paste twice to create realistic pending-paste entries, then calls current_text and current_text_with_pending to compare display text with submission text.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, format!).

tests::apply_external_edit_limits_duplicates_to_occurrences10948–10973 ↗
fn apply_external_edit_limits_duplicates_to_occurrences()

Purpose: Checks that duplicating an image placeholder in externally edited text does not duplicate the underlying attachment. One file attachment should remain one attachment even if its label appears twice.

Data flow: The test starts with one image placeholder and one attached image. It applies text containing that placeholder twice. The visible text keeps both occurrences, but the attachment list still contains only one image.

Call relations: The test runner calls this test. It sends duplicate placeholder text through apply_external_edit to ensure rebuilding text elements does not incorrectly clone image attachments.

Call graph: calls 3 internal fn (local_image_label_text, new, new); 3 external calls (from, assert_eq!, format!).

tests::remote_images_do_not_modify_textarea_text_or_elements10976–10994 ↗
fn remote_images_do_not_modify_textarea_text_or_elements()

Purpose: Checks that setting remote image URLs does not insert text into the editor or create local text elements. Remote images are tracked separately from typed text.

Data flow: The test sets two remote image URLs. The current composer text remains empty, and text_elements returns an empty list.

Call relations: The test runner calls this test. It calls set_remote_image_urls and then inspects current_text and text_elements to confirm remote images stay out of the text area.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, vec!).

tests::attach_image_after_remote_prefix_uses_offset_label10997–11019 ↗
fn attach_image_after_remote_prefix_uses_offset_label()

Purpose: Checks that local image numbering starts after any remote images. If two remote images already exist, the first local image should be labeled [Image #3].

Data flow: The test sets two remote image URLs, then attaches one local image. The local attachment placeholder becomes [Image #3], and the visible editor text is [Image #3].

Call relations: The test runner calls this test. It combines set_remote_image_urls and attach_image to verify the numbering rule across remote and local image sources.

Call graph: calls 2 internal fn (new, new); 3 external calls (from, assert_eq!, vec!).

tests::prepare_submission_keeps_remote_offset_local_placeholder_numbering11022–11056 ↗
fn prepare_submission_keeps_remote_offset_local_placeholder_numbering()

Purpose: Checks that preparing a submission preserves local image labels that are offset by remote images. This keeps the submitted text aligned with the image order the user sees.

Data flow: The test sets one remote image, then installs text with a local placeholder [Image #2] and one local path. Preparing submission returns the same text and matching text element, rather than renumbering it to [Image #1].

Call relations: The test runner calls this test. It uses set_text_content to mimic existing composer state and then calls prepare_submission_text, the method used before sending a message.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_eq!, vec!).

tests::prepare_submission_with_only_remote_images_returns_empty_text11059–11076 ↗
fn prepare_submission_with_only_remote_images_returns_empty_text()

Purpose: Checks that a submission with only remote images and no typed text is still allowed to be prepared. The text part should simply be empty.

Data flow: The test sets one remote image URL and calls prepare_submission_text. The result contains an empty submitted text string and no text elements.

Call relations: The test runner calls this test. It verifies prepare_submission_text can produce a submission even when there are remote image attachments but no editor content.

Call graph: calls 2 internal fn (new, new); 3 external calls (assert!, assert_eq!, vec!).

tests::delete_selected_remote_image_relabels_local_placeholders11079–11117 ↗
fn delete_selected_remote_image_relabels_local_placeholders()

Purpose: Checks that deleting selected remote images updates the numbering of local image placeholders. This keeps local labels correct when earlier remote images are removed.

Data flow: The test starts with two remote images and one local image labeled [Image #3]. It selects and deletes a remote image, leaving one remote image and relabeling the local image to [Image #2]. It deletes the remaining remote image, leaving the local image relabeled as [Image #1].

Call relations: The test runner calls this test. It uses Up to select remote images and Delete through handle_key_event, so the relabeling is tested through the same keyboard path a user would use.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, from, assert_eq!, vec!).

tests::input_disabled_ignores_keypresses_and_hides_cursor11120–11155 ↗
fn input_disabled_ignores_keypresses_and_hides_cursor()

Purpose: Checks that when input is disabled, keystrokes do not change the composer and the cursor is hidden. This is useful when the app is in a state where typing should not be accepted.

Data flow: The test sets text to hello, disables input with a message, and sends the character x. The result is no input action, no redraw request, the text remains hello, and cursor_pos returns None.

Call relations: The test runner calls this test. It uses set_input_enabled to enter disabled state, then sends a normal key through handle_key_event and asks cursor_pos how rendering should treat the cursor.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, new, new, assert!, assert_eq!).

Mentions and file-search popup pipeline

This group assembles mention candidates from plugins and skills, manages search modes and filtering, and drives the unified mentions popup backed by live file search.

tui/src/app/plugin_mentions.rssource ↗
domain_logicplugin mention refresh

The TUI needs to know which plugins a user can refer to, similar to typing a mention like “use this plugin.” The app server already has a full plugin list, but that list is broader than what the mention picker needs. This file acts like a translator: it asks for the plugin inventory, then reduces it to a simple list of mention-ready plugin summaries.

The main path starts with fetch_plugin_mentions, which requests the plugin list for the current working directory. The response may contain several marketplaces, and each marketplace may contain many plugins. The file walks through all of them, keeps only plugins that are installed, enabled, and not blocked by an administrator, and turns each one into a PluginCapabilitySummary.

For each eligible plugin, it chooses a friendly display name. If the plugin provides a non-empty interface display name, that is used; otherwise it falls back to the plugin’s regular name. It also chooses a short description, preferring the plugin’s own short description and falling back to the marketplace name. The resulting summaries currently say that no extra skills, MCP servers, or app connector IDs are known, because the server list does not yet provide that per-session detail.

Function details8
fetch_plugin_mentions14–20 ↗
async fn fetch_plugin_mentions(
    request_handle: AppServerRequestHandle,
    cwd: PathBuf,
) -> Result<Vec<PluginCapabilitySummary>>

Purpose: This is the public entry point in this file for getting mention-ready plugin information. It asks the app server for the current plugin list, then converts that larger answer into a smaller list the TUI can show for plugin mentions.

Data flow: It receives a request handle for talking to the app server and a current working directory. It sends those to the plugin-list request, waits for the response, then passes that response through the conversion step. It returns either a list of PluginCapabilitySummary values or an error if the server request failed.

Call relations: When refresh_plugin_mentions needs fresh mention data, it calls this function. This function first hands off to request_plugin_list to get the raw inventory, then hands the result to plugin_mentions_from_list_response to shape it into the TUI-friendly form.

Call graph: calls 2 internal fn (request_plugin_list, plugin_mentions_from_list_response); called by 1 (refresh_plugin_mentions).

plugin_mentions_from_list_response22–36 ↗
fn plugin_mentions_from_list_response(
    response: PluginListResponse,
) -> Vec<PluginCapabilitySummary>

Purpose: This function converts the full plugin-list response into only the plugin summaries that are useful for mentions. It flattens plugins from all marketplaces into one simple list.

Data flow: It takes a PluginListResponse, which contains marketplaces and their plugins. For each marketplace, it looks at each plugin and keeps only the ones that can become mention summaries. The output is a vector of PluginCapabilitySummary items.

Call relations: It is called by fetch_plugin_mentions after the app server returns the plugin list. Inside the conversion flow, it relies on the per-plugin mention-building logic so the rest of the TUI receives one clean list instead of the server’s nested marketplace structure.

Call graph: called by 1 (fetch_plugin_mentions).

plugin_is_eligible_for_mentions38–40 ↗
fn plugin_is_eligible_for_mentions(plugin: &PluginSummary) -> bool

Purpose: This function answers a yes-or-no question: should this plugin appear as something the user can mention? It keeps mention suggestions aligned with the GUI rules by excluding unusable plugins.

Data flow: It reads one plugin summary. If the plugin is installed, enabled, and not disabled by an administrator, it returns true; otherwise it returns false. It does not change the plugin.

Call relations: It is called by plugin_mention_from_summary before any mention summary is built. This makes it the gatekeeper that prevents disabled, uninstalled, or admin-blocked plugins from entering the mention list.

Call graph: called by 1 (plugin_mention_from_summary).

plugin_mention_from_summary42–58 ↗
fn plugin_mention_from_summary(
    marketplace_name: &str,
    plugin: PluginSummary,
) -> Option<PluginCapabilitySummary>

Purpose: This function turns one server plugin summary into one mention summary, if the plugin is allowed to appear. If the plugin is not eligible, it produces nothing.

Data flow: It receives the marketplace name and a plugin summary. It first checks eligibility. If the plugin fails that check, it returns None. If it passes, it builds a PluginCapabilitySummary using the plugin ID as the config name, a cleaned-up display name, an optional description, and empty capability lists for details the API does not currently provide here.

Call relations: It is used during the response conversion step for each plugin. It calls plugin_is_eligible_for_mentions to decide whether to continue, then calls plugin_mention_display_name and plugin_mention_description to fill in the human-facing text before constructing the summary.

Call graph: calls 3 internal fn (plugin_is_eligible_for_mentions, plugin_mention_description, plugin_mention_display_name); 1 external calls (new).

plugin_mention_display_name60–69 ↗
fn plugin_mention_display_name(plugin: &PluginSummary) -> String

Purpose: This function chooses the name the user should see for a plugin mention. It prefers a polished interface display name when one exists, but falls back safely to the plugin’s normal name.

Data flow: It reads a plugin summary. If the plugin has an interface display name, it trims extra spaces and uses it only if something remains. If not, it returns the plugin’s name field. The output is always a string.

Call relations: It is called by plugin_mention_from_summary while building the final mention summary. Its job is to keep the mention list readable instead of exposing blank or messy names.

Call graph: called by 1 (plugin_mention_from_summary).

plugin_mention_description71–83 ↗
fn plugin_mention_description(marketplace_name: &str, plugin: &PluginSummary) -> Option<String>

Purpose: This function chooses a short piece of descriptive text for a plugin mention. It helps the mention picker show context, not just a name.

Data flow: It receives the marketplace name and a plugin summary. It first looks for the plugin’s own short description, trims it, and uses it if it is not empty. If that is missing, it trims the marketplace name and uses that if available. If neither has useful text, it returns None.

Call relations: It is called by plugin_mention_from_summary when creating the mention summary. It supplies the optional description field that can help users distinguish similar plugins.

Call graph: called by 1 (plugin_mention_from_summary).

tests::plugin_mentions_use_plugin_list_summaries_and_gui_eligibility97–128 ↗
fn plugin_mentions_use_plugin_list_summaries_and_gui_eligibility()

Purpose: This test checks that plugin mentions are built from the plugin-list response and that the eligibility rules match the intended GUI behavior. It proves that only active, usable plugins appear in the final mention list.

Data flow: It creates four sample plugins: one active, one disabled by an administrator, one disabled by the user or system, and one not installed. It places them into a fake plugin-list response, runs the conversion, and compares the result with the one expected mention summary for the active plugin.

Call relations: This test exercises the conversion path directly, using tests::plugin_summary to make sample plugin records. It verifies the same filtering and summary-building behavior that fetch_plugin_mentions depends on after a real server response arrives.

Call graph: 4 external calls (new, assert_eq!, plugin_summary, vec!).

tests::plugin_summary130–146 ↗
fn plugin_summary(name: &str) -> PluginSummary

Purpose: This test helper creates a realistic plugin summary with sensible default values. It keeps the test focused on what changes between plugins instead of repeating a long setup block each time.

Data flow: It receives a short plugin name. It builds a PluginSummary whose ID and remote plugin ID include that name, and whose default state is installed, enabled, available, and remote. The result is returned to the test, which can then tweak individual fields.

Call relations: It is used by tests::plugin_mentions_use_plugin_list_summaries_and_gui_eligibility to create the sample plugin records. The helper provides the normal “active plugin” shape, while the test changes selected fields to represent blocked or unavailable plugins.

Call graph: 2 external calls (new, format!).

tui/src/file_search.rssource ↗
orchestrationmain loop, while the user edits @ file references

When a user types an @ reference in the chat composer, the interface needs to quickly suggest matching files. This file is the bridge between those keystrokes and the lower-level codex-file-search library that actually scans and ranks files.

The main piece is FileSearchManager. It remembers the current search folder, the latest text after @, and the active search session. A session is like a temporary helper dedicated to one search area. If the user keeps typing, the manager does not start over from scratch each time; it updates the existing session with the new query. If the query becomes empty, it drops the session because there is nothing to search for.

The file also defines TuiSessionReporter, which receives search snapshots from the file-search library. A snapshot is a point-in-time result: the query plus its matches. Before forwarding it to the rest of the app, the reporter checks that the result still belongs to the current session and that the user has not cleared the query. This matters because searches can finish after the user has already moved on. The session token works like a coat-check ticket: only results with the current ticket are allowed through.

Function details7
FileSearchManager::new29–39 ↗
fn new(search_dir: PathBuf, tx: AppEventSender) -> Self

Purpose: Creates a new file-search manager for a starting folder and an app event channel. The rest of the TUI uses this manager whenever the user types an @ file search.

Data flow: It receives a folder path and a sender for app events. It builds shared internal state with an empty latest query, no active search session, and a starting session token of zero. It returns a ready-to-use FileSearchManager that can later start and update searches.

Call relations: This is called during app setup, including normal startup and test setup. It prepares the object that later reacts to user query changes and reports matching files back into the app event stream.

Call graph: called by 4 (run, make_test_app, make_test_app, make_test_app_with_channels); 3 external calls (new, new, new).

FileSearchManager::update_search_dir44–50 ↗
fn update_search_dir(&mut self, new_dir: PathBuf)

Purpose: Changes the folder that future file searches should use. This is needed when the TUI session resumes in a different current working directory.

Data flow: It receives a new folder path. It stores that path, locks the shared search state, removes any existing search session, and clears the remembered query. After this, the next non-empty query will create a fresh session rooted in the new folder.

Call relations: This sits on the boundary between session state changes and file search. When some outside part of the app knows the working directory has changed, it calls this so old search results from the previous folder do not keep being used.

FileSearchManager::on_user_query53–73 ↗
fn on_user_query(&self, query: String)

Purpose: Responds to each change in the user's @ search text. It avoids duplicate work, starts a search session when needed, updates the current query, and stops searching when the query is empty.

Data flow: It receives the latest query string from the composer. It compares it with the remembered query; if nothing changed, it does nothing. If the query changed, it stores the new text. An empty query clears the active session. A non-empty query ensures a session exists, then sends the new query into that session so it can produce updated matches.

Call relations: This is the main entry point for keystroke-driven search updates. When it needs a new search worker, it calls FileSearchManager::start_session_locked. The session later reports results through TuiSessionReporter.

Call graph: calls 1 internal fn (start_session_locked).

FileSearchManager::start_session_locked75–99 ↗
fn start_session_locked(&self, st: &mut SearchState)

Purpose: Starts a new file-search session while the manager's shared state is already locked. It also creates the reporter that will send search results back to the TUI.

Data flow: It increments the session token, captures that token in a new TuiSessionReporter, and asks the file-search library to create a session for the current search directory. It enables index computation, which lets the search library prepare faster lookup data. If session creation succeeds, it stores the session in the manager state. If it fails, it logs a warning and leaves the manager without an active session.

Call relations: This is called by FileSearchManager::on_user_query the first time a non-empty query needs a session. It hands the file-search library a reporter, so future search updates can flow back through TuiSessionReporter::on_update and then into the app event system.

Call graph: called by 1 (on_user_query); 6 external calls (new, default, create_session, warn!, clone, vec!).

TuiSessionReporter::send_snapshot109–124 ↗
fn send_snapshot(&self, snapshot: &file_search::FileSearchSnapshot)

Purpose: Turns a file-search snapshot into a TUI app event, but only if the snapshot is still relevant. This protects the interface from showing stale results from an old or cleared search.

Data flow: It receives a snapshot containing a query and matching files. It checks the shared state to make sure its session token is still current, the manager still has a non-empty latest query, and the snapshot itself has a non-empty query. If any check fails, nothing is sent. Otherwise, it copies the query and matches and sends an AppEvent::FileSearchResult to the app.

Call relations: This is called by TuiSessionReporter::on_update whenever the file-search library has new results. It is the final gate before search results enter the TUI's normal event flow.

Call graph: calls 1 internal fn (send); called by 1 (on_update).

TuiSessionReporter::on_update128–130 ↗
fn on_update(&self, snapshot: &file_search::FileSearchSnapshot)

Purpose: Receives an update from the file-search library whenever search results change. It delegates the real filtering and event sending to TuiSessionReporter::send_snapshot.

Data flow: It receives a search snapshot from the library and passes it unchanged to send_snapshot. Any app event that comes out is produced there, after the relevance checks are done.

Call relations: The file-search session calls this through the SessionReporter interface. This makes TuiSessionReporter the callback object that connects background search progress to the TUI.

Call graph: calls 1 internal fn (send_snapshot).

TuiSessionReporter::on_complete132–132 ↗
fn on_complete(&self)

Purpose: Receives the file-search library's 'search is complete' signal. In this TUI integration, completion does not require any extra action.

Data flow: It takes no useful input and produces no output or state change. Search results are already sent as updates arrive, so there is nothing special to do at the end.

Call relations: The file-search library may call this through the SessionReporter interface when a session finishes its work. This implementation intentionally leaves it empty because the TUI only needs the result snapshots sent through on_update.

tui/src/bottom_pane/mentions_v2/mod.rssource ↗
orchestrationrequest handling

In this app, a “mention popup” is the small chooser that appears when the user wants to refer to something, such as a file or another searchable item, while typing in the terminal interface. Older code used separate popups for different kinds of mentions. This module represents the newer combined version, where searching and choosing are presented as one unified experience.

The file itself does not contain the popup logic. Instead, it works like a signpost and doorway. It declares the internal building blocks: candidate selection, filtering, footer display, popup behavior, rendering, the searchable catalog, and search mode. Those parts live in separate files so each concern stays smaller and easier to understand.

It then re-exports three items for the rest of the terminal UI to use: MentionV2Selection, which describes what the user picked; MentionV2Popup, which is the popup component itself; and build_search_catalog, which prepares the list of things the popup can search through. The comment also explains that the mentions_v2 feature flag is kept as a safety switch. If the new popup causes trouble, disabling that feature can return the app to the older split-popup behavior.

tui/src/bottom_pane/mentions_v2/candidate.rssource ↗
data_modelmention search and picker display

When a user opens the mention picker, the interface may suggest several kinds of things: files, directories, plugins, or skills. This file gives those suggestions a shared shape so the rest of the picker can treat them consistently. Think of it like the labels and cards in a library catalog: each card has a title, optional notes, searchable words, a category label, and instructions for what should happen if the user picks it.

The Selection enum records what choosing a suggestion means. For a file, it stores a filesystem path. For a tool-like item such as a plugin or skill, it stores the text to insert and, optionally, a path connected to it.

MentionType names the category of a suggestion. It also knows how to answer whether the item belongs to the filesystem, and how to draw its category tag with the right color in the terminal user interface.

Candidate is the full suggestion before search ranking. SearchResult is the same suggestion after a search has been run, with extra information such as the matching character positions and a score. This separation lets the picker keep a clean list of possible items, then produce ranked results without changing the originals.

Function details4
MentionType::is_filesystem28–30 ↗
fn is_filesystem(self) -> bool

Purpose: This function tells whether a mention type points to something in the filesystem. It returns true for files and directories, and false for plugins and skills.

Data flow: It takes one MentionType value as input. It checks which category it is. The output is a simple yes-or-no value: yes for File or Directory, no for the other categories.

Call relations: Other parts of the mention picker can call this when they need to treat filesystem items differently from tool-like items. Inside, it uses Rust's pattern matching to make that category check directly.

Call graph: 1 external calls (matches!).

MentionType::span32–40 ↗
fn span(self, base_style: Style) -> Span<'static>

Purpose: This function turns a mention type into a colored text tag for display in the terminal. For example, it can produce a padded label such as File or Plugin with a category-specific color.

Data flow: It receives a base text style. It chooses a variation of that style depending on the mention type: plugins become magenta, skills are dimmed, files become cyan, and directories keep the base style. It then asks label for the visible word, pads it to a fixed width so the list lines up neatly, applies the style, and returns a terminal text span.

Call relations: The picker display can call this while drawing each suggestion row. This function calls MentionType::label to get the human-readable category name, then uses terminal styling helpers to color the label before handing it back to the UI renderer.

Call graph: 4 external calls (cyan, dim, magenta, format!).

MentionType::label42–49 ↗
fn label(self) -> &'static str

Purpose: This function provides the short display name for each mention category. It keeps the visible wording in one place so category tags stay consistent.

Data flow: It takes one MentionType value. It matches that value to a fixed text label: Plugin, Skill, File, or Dir. It returns that label as static text.

Call relations: This is a helper for MentionType::span, which needs the label before it can color and pad it for display. Keeping this helper private means outside code uses the styled version rather than duplicating label text.

Candidate::to_result72–81 ↗
fn to_result(&self, match_indices: Option<Vec<usize>>, score: i32) -> SearchResult

Purpose: This function converts a stored suggestion into a search result after matching and scoring have happened. It preserves the suggestion's display information and adds search-specific details such as matched positions and score.

Data flow: It starts with a Candidate, plus optional match indices and a numeric score from the search process. It copies the candidate's name, description, type, and selection, attaches the match information and score, and returns a new SearchResult. The original candidate is left unchanged.

Call relations: Search code can call this when it has compared a candidate against the user's query. The function packages the candidate together with the search outcome so later UI code can sort results, highlight matched characters, and perform the right action when the user selects one.

Call graph: 1 external calls (clone).

tui/src/bottom_pane/mentions_v2/search_mode.rssource ↗
data_modelrequest handling

When a user opens the mentions search, the app may have many possible things to suggest: previous results, files, folders, plugins, and skills. This file gives that search panel a small set of viewing modes so the user can narrow what they see. Think of it like tabs on a search page: one tab shows everything, one tab shows only files and folders, and one tab shows plugin-like tools.

The central piece is the SearchMode enum, which is a small fixed list of allowed modes. Results means show all available mention candidates. FilesystemOnly means show only files and directories. Tools means show plugin and skill candidates, even though its visible label is currently Plugins.

The helper methods make the enum useful in the interface. previous and next define the wraparound order when the user moves between modes, so going past the last mode returns to the first. accepts answers the key filtering question: “should this candidate appear in the current mode?” label provides the human-readable text that can be displayed in the UI. Without this file, different parts of the search panel would need to duplicate these rules, which could easily lead to confusing mismatches between what a mode says and what it actually shows.

Function details4
SearchMode::previous11–17 ↗
fn previous(self) -> Self

Purpose: This returns the search mode that comes before the current one. It is used when the user cycles backward through the available filter modes.

Data flow: It starts with the current SearchMode value. It maps that value to the prior mode in a fixed loop: from all results back to tools, from filesystem-only back to all results, and from tools back to filesystem-only. It returns the new mode and does not change anything else by itself.

Call relations: When the surrounding mentions UI receives a request to move to the previous search mode, previous_search_mode calls this function. This function supplies the next state for that UI action, keeping the wraparound order in one place.

Call graph: called by 1 (previous_search_mode).

SearchMode::next19–25 ↗
fn next(self) -> Self

Purpose: This returns the search mode that comes after the current one. It is used when the user cycles forward through the available filter modes.

Data flow: It takes the current SearchMode value and chooses the next one in a fixed loop: all results goes to filesystem-only, filesystem-only goes to tools, and tools goes back to all results. The output is the new mode; the function does not directly update the screen or stored state.

Call relations: When the surrounding mentions UI receives a request to move to the next search mode, next_search_mode calls this function. This keeps the mode order consistent everywhere the UI needs to advance the filter.

Call graph: called by 1 (next_search_mode).

SearchMode::accepts27–35 ↗
fn accepts(self, mention_type: MentionType) -> bool

Purpose: This decides whether a specific mention candidate belongs in the current search mode. It is the filter rule that turns a broad list of candidates into the smaller list the user should see.

Data flow: It receives the current SearchMode and a candidate's MentionType, which describes what kind of thing the candidate is. In all-results mode it accepts everything. In filesystem-only mode it accepts only files and directories. In tools mode it accepts only plugins and skills. It returns true when the candidate should be shown and false when it should be hidden.

Call relations: filtered_candidates calls this while building the visible candidate list for the mentions panel. This function does the yes-or-no decision for each candidate, using Rust's matches! check to compare the candidate type against the types allowed by the selected mode.

Call graph: called by 1 (filtered_candidates); 1 external calls (matches!).

SearchMode::label37–43 ↗
fn label(self) -> &'static str

Purpose: This provides the text shown to the user for each search mode. It keeps the display names tied to the actual mode values.

Data flow: It takes the current SearchMode and turns it into a fixed text label: All Results, Filesystem Only, or Plugins. It returns that text as a static string, meaning it is built into the program and does not need to be created each time.

Call relations: This is the presentation companion to the mode logic. Other UI code can call it when drawing the search mode selector or status text, so the screen uses the same mode definitions that filtering and navigation use.

tui/src/bottom_pane/mentions_v2/search_catalog.rssource ↗
domain_logicmention search / request handling

When a user opens the mention search in the bottom pane, the interface needs a clean catalog of choices. Skills and plugins come from different parts of the system and have different shapes, so this file acts like a shopkeeper arranging mixed inventory onto one shelf. It converts each skill or plugin into a shared Candidate object that the search UI can display and filter.

For skills, it chooses a friendly display name, adds a short description if one exists, and prepares insertion text like "$skillname". It also records the path to the skill’s SKILLS.md file, so the selected item can point back to its source.

For plugins, it does a little more polishing. Plugin names may include marketplace suffixes like "name@market", while display names may use spaces or capital letters. The file builds useful search terms from all of these, chooses mention text like "@Plugin-Name", and creates a fallback description if the plugin did not provide one. That fallback can mention capabilities such as skills, MCP servers, or app connectors. MCP means "Model Context Protocol", a way for external tools or services to connect to the assistant.

The small helper functions mostly exist to make plugin names look natural while still preserving separators such as hyphens and underscores.

Function details12
build_search_catalog11–25 ↗
fn build_search_catalog(
    skills: Option<&[SkillMetadata]>,
    plugins: Option<&[PluginCapabilitySummary]>,
) -> Vec<Candidate>

Purpose: Builds one combined list of searchable mention candidates from the available skills and plugins. The UI can use this list to show matching choices when the user searches for something to mention.

Data flow: It receives optional lists of skill metadata and plugin summaries. It starts with an empty list, adds one candidate for each skill if skills were provided, then adds one candidate for each plugin if plugins were provided. It returns the finished list of candidates without changing the original skill or plugin data.

Call relations: This is the file’s main entry point for catalog creation. When the mention search needs choices, this function gathers both sources into one list and relies on the skill and plugin conversion logic in this file to make each item usable by the UI.

Call graph: 1 external calls (new).

skill_candidate27–46 ↗
fn skill_candidate(skill: &SkillMetadata) -> Candidate

Purpose: Turns one skill into one search result item. It gives the UI a display name, optional description, search words, and the exact text to insert if the user chooses the skill.

Data flow: It reads the skill’s name, display information, description, and path to its SKILLS.md file. It builds search terms from the raw skill name and, when different, the friendly display name. It returns a Candidate marked as a skill, with insertion text like "$skillname" and a path pointing to the skill file.

Call relations: This function is used as part of building the skill side of the catalog. It calls skill_display_name to get the friendly name and optional_skill_description to avoid showing blank descriptions.

Call graph: calls 2 internal fn (optional_skill_description, skill_display_name); 2 external calls (format!, vec!).

plugin_candidate48–72 ↗
fn plugin_candidate(plugin: &PluginCapabilitySummary) -> Candidate

Purpose: Turns one plugin into one search result item. It prepares the plugin so it can be searched by several names and inserted into the prompt as a mention.

Data flow: It reads the plugin’s configured name, display name, description, and capability information. If the configured name includes a marketplace part after an "@", it separates that out for searching. It then builds search terms, chooses the mention text, creates a description, and returns a Candidate marked as a plugin with insertion text like "@PluginName" and a plugin URL-style path.

Call relations: This function is used when plugins are added to the search catalog. It calls plugin_mention_name to make the typed mention look friendly, and plugin_description to choose either the plugin’s own description or a useful fallback.

Call graph: calls 2 internal fn (plugin_description, plugin_mention_name); 2 external calls (format!, vec!).

plugin_mention_name74–96 ↗
fn plugin_mention_name(plugin_name: &str, display_name: &str) -> String

Purpose: Chooses the clean name that should be inserted when a plugin is mentioned. Its goal is to preserve helpful capitalization from the display name without losing separators like hyphens or underscores from the plugin name.

Data flow: It receives the plugin’s technical name and its human-facing display name. It splits both into word-like pieces, compares them case-insensitively, and if they match, rebuilds the name using the display name’s capitalization and the plugin name’s separators. If they do not line up, it returns a simple title-cased version of the plugin name.

Call relations: plugin_candidate calls this when it needs the text for an inserted plugin mention. This function coordinates the name-splitting helpers and falls back to title_case_plugin_name when the display name cannot safely be reused.

Call graph: calls 3 internal fn (split_display_name_segments, split_plugin_name_segments, title_case_plugin_name); called by 1 (plugin_candidate); 1 external calls (new).

split_plugin_name_segments98–117 ↗
fn split_plugin_name_segments(plugin_name: &str) -> Vec<(String, Option<char>)>

Purpose: Breaks a technical plugin name into pieces while remembering whether each piece was followed by a hyphen or underscore. This lets the code compare words without throwing away the original punctuation style.

Data flow: It receives a plugin name string. It walks through each character, collecting normal characters into the current segment and ending a segment when it sees "-" or "_". It returns a list of text segments paired with the separator that followed them, if any.

Call relations: plugin_mention_name calls this before comparing the technical plugin name to the display name. The saved separators are later used to rebuild a nicer mention name that still looks like the original plugin identifier.

Call graph: called by 1 (plugin_mention_name); 4 external calls (new, new, matches!, take).

split_display_name_segments119–125 ↗
fn split_display_name_segments(display_name: &str) -> Vec<String>

Purpose: Breaks a human display name into plain word pieces. It ignores spaces and punctuation so names like "MCP Search" can be compared with plugin names like "mcp-search".

Data flow: It receives a display name string. It splits the string anywhere the character is not an ASCII letter or number, drops empty pieces, and returns the remaining pieces as strings.

Call relations: plugin_mention_name calls this alongside split_plugin_name_segments. Together, the two split results let the code decide whether the display name is just a prettier version of the plugin name.

Call graph: called by 1 (plugin_mention_name).

title_case_plugin_name127–148 ↗
fn title_case_plugin_name(plugin_name: &str) -> String

Purpose: Creates a safe fallback mention name by capitalizing the first letter of each plugin-name segment. It keeps hyphens and underscores in place.

Data flow: It receives a plugin name string. It scans from left to right, capitalizing the next alphabetic character after the start, a hyphen, or an underscore. It returns the transformed string.

Call relations: plugin_mention_name calls this when the display name does not match the plugin name closely enough to reuse. It is the conservative path: make the name nicer, but do not invent a different structure.

Call graph: called by 1 (plugin_mention_name); 2 external calls (with_capacity, matches!).

plugin_description150–159 ↗
fn plugin_description(plugin: &PluginCapabilitySummary) -> Option<String>

Purpose: Chooses the description text shown for a plugin in the search results. If the plugin has its own description, that wins; otherwise this function creates a short useful label from what the plugin can do.

Data flow: It receives a plugin summary. It asks plugin_capability_labels for labels such as "skills" or "1 MCP server", then returns the plugin’s own description if present. If not, it returns "Plugin" by itself or "Plugin - ..." followed by the capability labels.

Call relations: plugin_candidate calls this while building a plugin search item. It delegates the capability wording to plugin_capability_labels so the description logic stays easy to read.

Call graph: calls 1 internal fn (plugin_capability_labels); called by 1 (plugin_candidate).

plugin_capability_labels161–183 ↗
fn plugin_capability_labels(plugin: &PluginCapabilitySummary) -> Vec<String>

Purpose: Builds short human-readable labels for a plugin’s available capabilities. These labels are used when the plugin does not provide a custom description.

Data flow: It reads booleans and lists from the plugin summary: whether it has skills, how many MCP servers it exposes, and how many app connectors it has. It turns those facts into phrases with correct singular or plural wording, then returns the list of phrases.

Call relations: plugin_description calls this to create fallback description text. This helper keeps the counting and wording details out of the higher-level description choice.

Call graph: called by 1 (plugin_description); 2 external calls (new, format!).

optional_skill_description185–188 ↗
fn optional_skill_description(skill: &SkillMetadata) -> Option<String>

Purpose: Returns a skill description only when there is real text to show. This prevents the search UI from displaying empty or whitespace-only descriptions.

Data flow: It receives skill metadata and asks skill_description for the skill’s description. It trims surrounding whitespace, checks whether anything remains, and returns either the cleaned description or no description at all.

Call relations: skill_candidate calls this while turning a skill into a search candidate. It relies on the shared skill_description helper, then adds the extra rule that blank text should be treated as missing.

Call graph: calls 1 internal fn (skill_description); called by 1 (skill_candidate).

tests::plugin_mention_name_uses_display_segments_when_they_match_plugin_name196–205 ↗
fn plugin_mention_name_uses_display_segments_when_they_match_plugin_name()

Purpose: Checks that plugin mention names reuse the display name’s capitalization when the display name and plugin name contain the same words. This protects the friendly formatting behavior for names with hyphens or underscores.

Data flow: It supplies example plugin names and display names, such as "mcp-search" with "MCP Search". It compares the produced mention name with the expected result, like "MCP-Search". The test passes only if the output keeps the plugin separator while using the display name’s capitalization.

Call relations: This test directly exercises plugin_mention_name. It documents the intended path where split plugin segments and split display segments match and are recombined.

Call graph: 1 external calls (assert_eq!).

tests::plugin_mention_name_falls_back_to_title_cased_plugin_name208–214 ↗
fn plugin_mention_name_falls_back_to_title_cased_plugin_name()

Purpose: Checks that plugin mention names fall back to title-casing the technical plugin name when the display name is not a simple word-for-word match. This protects against inserting a misleading or overly long mention.

Data flow: It gives plugin_mention_name examples where the display name has extra wording or where the safe fallback should be used. It compares the returned string with the expected title-cased plugin name. The test changes nothing outside itself.

Call relations: This test directly exercises plugin_mention_name’s fallback path, which uses title_case_plugin_name when the display name cannot be safely mapped onto the plugin name.

Call graph: 1 external calls (assert_eq!).

tui/src/bottom_pane/mentions_v2/filter.rssource ↗
domain_logicinteractive mention search while the user types

This file is the “shortlist maker” for the mentions menu. When a user types a mention query, the interface may have many possible things to suggest: plugins, skills, files, and directories. This code trims that list down to items that fit the current search mode, checks whether the typed text matches each item, and then sorts the results so the most useful choices appear first.

The main flow starts with a list of known candidates and, optionally, a list of file matches from the file search system. If the user has not typed a filter yet, matching is skipped and the accepted candidates are shown directly. If there is a filter, each candidate is tested with fuzzy matching. Fuzzy matching means the typed letters can match a name even if they are not side by side, like typing “cfg” to find “config”.

The file treats different suggestion types differently. Plugins and skills are ranked before files and directories. File-system results use their search score in descending order, while non-file items prefer direct display-name matches over matches found only in hidden search terms. File search results are converted into the same SearchResult shape as regular candidates, so the rest of the UI can display one unified list.

Function details5
filtered_candidates11–46 ↗
fn filtered_candidates(
    candidates: &[Candidate],
    file_matches: &[FileMatch],
    query: &str,
    search_mode: SearchMode,
    show_file_matches: bool,
) -> Vec<SearchResult>

Purpose: Builds the final list of mention suggestions for the current query. It filters by search mode, applies fuzzy matching when the user has typed text, optionally adds file search results, and sorts everything for display.

Data flow: It receives the available candidates, file search matches, the user’s query text, the active search mode, and a flag saying whether file matches should be shown. It trims the query, keeps only candidates allowed by the search mode, turns matching items into SearchResult rows, adds converted file matches if requested, sorts the rows, and returns the finished list for the UI.

Call relations: This is the main function in the file and is called by rows, which is likely preparing the visible mention rows. During that work it asks best_tool_match whether a candidate fits the typed query, uses each search mode’s accepts check to reject the wrong kinds of mentions, converts file matches through the local mapping helper, and finally hands the complete list to sort_rows before returning it.

Call graph: calls 3 internal fn (best_tool_match, sort_rows, accepts); called by 1 (rows); 2 external calls (new, iter).

best_tool_match48–60 ↗
fn best_tool_match(candidate: &Candidate, filter: &str) -> Option<(Option<Vec<usize>>, i32)>

Purpose: Finds the best fuzzy match between a typed filter and one candidate. It prefers a match against the visible display name, but can also match extra search terms that help people find the item by aliases or related words.

Data flow: It receives one candidate and the typed filter text. First it tries fuzzy matching against the candidate’s display name; if that works, it returns the matched character positions and score. If not, it tries the candidate’s other search terms, ignores terms identical to the display name, keeps the best available score, and returns that score without highlight positions. If nothing matches, it returns no result.

Call relations: filtered_candidates calls this when the user has typed a non-empty filter. This function delegates the actual letter-by-letter fuzzy comparison to the external fuzzy_match helper, then gives filtered_candidates enough information to build a result row and, when possible, highlight the matched letters.

Call graph: called by 1 (filtered_candidates); 1 external calls (fuzzy_match).

sort_rows62–75 ↗
fn sort_rows(rows: &mut [SearchResult], filter: &str)

Purpose: Orders the finished suggestion rows so the menu feels predictable and useful. It groups suggestion types first, then applies more detailed ranking rules within each group.

Data flow: It receives a mutable list of SearchResult rows and the current filter text. It sorts the list in place: plugins first, then skills, then files and directories. Within those groups it uses the comparison rules from compare_within_rank, and finally falls back to alphabetical order by display name when other rules tie.

Call relations: filtered_candidates calls this after collecting candidate rows and optional file rows. It uses Rust’s built-in sorting operation and relies on the local within-rank comparison logic so the rest of the UI receives an already ordered list.

Call graph: called by 1 (filtered_candidates); 1 external calls (sort_by).

compare_within_rank77–89 ↗
fn compare_within_rank(a: &SearchResult, b: &SearchResult, filter: &str) -> std::cmp::Ordering

Purpose: Decides which of two suggestions should come first when they are already in the same broad type group. It contains the fine-grained ranking rules that make files, empty searches, and fuzzy matches sort differently.

Data flow: It receives two result rows and the current filter text. If both rows are files or directories, it puts the higher file-search score first. If there is no filter text, it sorts by display name. Otherwise, it favors rows that matched the visible display name, then compares fuzzy-match scores.

Call relations: This function is used as part of the ordering done by sort_rows. It does not create or change rows; it only answers the question, “between these two rows, which should be displayed first?”

file_match_to_row91–107 ↗
fn file_match_to_row(file_match: &FileMatch) -> SearchResult

Purpose: Turns a file-search result into the same SearchResult format used by normal mention candidates. This lets files and directories appear in the same suggestions menu as plugins and skills.

Data flow: It receives one FileMatch from the file search system. It translates the file-search match type into this pane’s mention type, converts the path into display text, stores the path as the selected file target, copies any matched character positions for highlighting, converts the score into the local score type, and returns a ready-to-display result row.

Call relations: filtered_candidates uses this when file matches should be included. The converted row then goes through the same search-mode filtering and sorting path as the other results, so downstream UI code can treat all suggestions uniformly.

Call graph: 1 external calls (File).

tui/src/bottom_pane/mentions_v2/popup.rssource ↗
domain_logicactive while the mentions popup is open during terminal input

This file is the control center for a small search pop-up in the terminal UI. When a user types a mention query, the pop-up needs to show matching items, let the user move the highlighted choice up and down, switch between search modes, and stay visually tidy even when results change. Without this file, the mention picker would not know what query is active, which row is selected, or what to render.

The main type, Popup, holds the user’s query, the full candidate list, file-search results, the current search mode, and a ScrollState, which is like a bookmark that remembers which row is selected and which part of the list is visible. Each time the query, candidates, file matches, or search mode changes, the pop-up recalculates its rows and clamps the selection so it cannot point past the end of the list.

FileSearch is a small helper for asynchronous file results. It remembers the query it is waiting for, ignores file results that belong to an older query, and shows either “loading...” or “no matches” when there is nothing to display. Rendering is delegated to render_popup, so this file focuses on state and behavior rather than drawing details.

Function details17
Popup::new24–32 ↗
fn new(candidates: Vec<Candidate>) -> Self

Purpose: Creates a fresh mention pop-up from an initial list of possible candidates. It starts with an empty query, no file-search results, the default search mode, and a new scroll/selection state.

Data flow: It receives a list of candidates. It stores that list, creates an empty FileSearch, sets the search mode to results, and creates a new ScrollState; the output is a ready-to-use Popup.

Call relations: This is the starting point for outside UI code that wants to show the popup. It relies on the scroll state constructor and the default file-search helper so later actions like typing, moving, and rendering have state to work with.

Call graph: calls 1 internal fn (new); 2 external calls (new, default).

Popup::set_candidates34–37 ↗
fn set_candidates(&mut self, candidates: Vec<Candidate>)

Purpose: Replaces the popup’s base list of candidates. This is used when the available mention choices change.

Data flow: It takes a new candidate list, stores it over the old one, then checks whether the current selected row is still valid. The popup is changed in place and returns nothing.

Call relations: After updating the list, it calls Popup::clamp_selection because the old selected row may no longer exist. This keeps later calls to selection, movement, or rendering from pointing at an invalid row.

Call graph: calls 1 internal fn (clamp_selection).

Popup::set_query39–43 ↗
fn set_query(&mut self, query: &str)

Purpose: Updates the text the user is searching for. It also tells the file-search helper that a new file query may now be pending.

Data flow: It receives the new query string, copies it into the popup, forwards it to FileSearch::set_query, then adjusts the selected row so it still fits the newly filtered results.

Call relations: Outside input code calls this when the user edits the mention text. It hands the query to FileSearch::set_query and then calls Popup::clamp_selection so the UI stays consistent after the result list changes.

Call graph: calls 2 internal fn (set_query, clamp_selection).

Popup::set_file_matches45–48 ↗
fn set_file_matches(&mut self, query: &str, matches: Vec<FileMatch>)

Purpose: Adds file-search results that came back for a query. It is careful not to accept stale results for an older query.

Data flow: It receives the query that produced the matches and the matching files. It asks FileSearch::set_matches to store them only if they belong to the current pending query, then it fixes the selection against the updated rows.

Call relations: This is called when a file search finishes elsewhere. It delegates the freshness check to FileSearch::set_matches, then calls Popup::clamp_selection because new or changed file rows can affect what can be selected.

Call graph: calls 2 internal fn (set_matches, clamp_selection).

Popup::selected50–54 ↗
fn selected(&self) -> Option<Selection>

Purpose: Returns the item currently highlighted by the user, if there is one. This is what the surrounding UI would use when the user confirms a choice.

Data flow: It builds the current visible rows, reads the selected index from the scroll state, and looks up that row. If the index exists, it returns a copy of that row’s Selection; otherwise it returns nothing.

Call relations: This function depends on Popup::rows so it always uses the same filtered list that movement and rendering use. It is the bridge from visual selection to the actual mention choice.

Call graph: calls 1 internal fn (rows).

Popup::move_up56–60 ↗
fn move_up(&mut self)

Purpose: Moves the highlighted row upward, wrapping around if needed. It also scrolls the visible window so the highlighted row remains on screen.

Data flow: It calculates how many rows currently exist, asks the scroll state to move the selection upward with wraparound, then asks it to keep the selected row visible within the popup’s maximum height.

Call relations: Keyboard handling code would call this when the user presses an up key. It first uses Popup::rows to know the current list length, then relies on ScrollState movement and visibility helpers.

Call graph: calls 3 internal fn (rows, ensure_visible, move_up_wrap).

Popup::move_down62–66 ↗
fn move_down(&mut self)

Purpose: Moves the highlighted row downward, wrapping around if needed. It keeps the selected row visible inside the limited popup height.

Data flow: It calculates the current row count, moves the selected index down through the scroll state, and then adjusts the scroll offset so the chosen row can be seen.

Call relations: Keyboard handling code would call this when the user presses a down key. Like Popup::move_up, it uses Popup::rows for the current list length and then lets ScrollState update the selection and view.

Call graph: calls 3 internal fn (rows, ensure_visible, move_down_wrap).

Popup::previous_search_mode68–71 ↗
fn previous_search_mode(&mut self)

Purpose: Switches the popup to the previous search mode. Search modes are different ways of interpreting or grouping the search results.

Data flow: It asks the current SearchMode for its previous value, stores that new mode, then recalculates whether the selected row is still valid.

Call relations: Outside UI code calls this when the user cycles backward through modes. It calls the mode’s previous helper, then Popup::clamp_selection because a different mode can produce a different row list.

Call graph: calls 2 internal fn (clamp_selection, previous).

Popup::next_search_mode73–76 ↗
fn next_search_mode(&mut self)

Purpose: Switches the popup to the next search mode. This lets the user cycle through different kinds of result views.

Data flow: It asks the current SearchMode for its next value, saves it, then clamps the selection to fit the rows produced by the new mode.

Call relations: Outside UI code calls this when the user cycles forward through modes. It uses the search mode’s next helper and then calls Popup::clamp_selection to keep selection safe.

Call graph: calls 2 internal fn (clamp_selection, next).

Popup::calculate_required_height78–80 ↗
fn calculate_required_height(&self, _width: u16) -> u16

Purpose: Reports how tall the popup wants to be. It reserves room for the maximum number of result rows plus extra space for the popup frame or padding.

Data flow: It receives a width value but does not need it. It returns a height based on MAX_POPUP_ROWS plus two extra rows, using safe addition to avoid overflow.

Call relations: Layout code can ask this before rendering so it can reserve enough terminal space. This function does not call other helpers because the popup height is fixed by the configured maximum row count.

Popup::clamp_selection82–86 ↗
fn clamp_selection(&mut self)

Purpose: Keeps the selected row valid after the result list changes. It prevents the popup from remembering a row number that no longer exists.

Data flow: It rebuilds the current rows, counts them, asks the scroll state to clamp the selection within that count, and then makes sure the selected row is visible within the popup’s maximum row window.

Call relations: This is the cleanup step used after changing candidates, query text, file matches, or search mode. It depends on Popup::rows for the current list and on ScrollState to repair selection and scrolling.

Call graph: calls 3 internal fn (rows, clamp_selection, ensure_visible); called by 5 (next_search_mode, previous_search_mode, set_candidates, set_file_matches, set_query).

Popup::rows88–96 ↗
fn rows(&self) -> Vec<SearchResult>

Purpose: Builds the list of rows the popup should currently show. It combines normal candidates, file-search matches, the query text, and the selected search mode into one filtered result list.

Data flow: It reads the popup’s candidates, file-search matches, query, search mode, and whether file matches should be shown. It passes those into filtered_candidates and returns the resulting SearchResult rows.

Call relations: This is the shared source of truth for selection, movement, clamping, and rendering. Popup::selected, movement functions, Popup::clamp_selection, and Popup::render_ref all call it so they agree on what the visible list is.

Call graph: calls 2 internal fn (filtered_candidates, should_show_matches); called by 5 (clamp_selection, move_down, move_up, render_ref, selected).

Popup::render_ref100–109 ↗
fn render_ref(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the popup into the terminal screen buffer. It does not decide what the rows mean; it gathers the current state and hands it to the rendering helper.

Data flow: It receives a screen area and a mutable buffer to draw into. It builds the current rows, gets the empty-state message from file search, and passes all display information to render_popup, which writes into the buffer.

Call relations: The terminal UI framework calls this because Popup implements WidgetRef, a trait for drawable widgets. It connects the popup’s state to render_popup, the lower-level drawing routine.

Call graph: calls 3 internal fn (empty_message, rows, render_popup).

FileSearch::set_query121–131 ↗
fn set_query(&mut self, query: &str)

Purpose: Updates the file-search helper when the user’s query changes. It marks file results as pending when there is a new non-empty query, and clears everything when the query is empty.

Data flow: It receives the new query string. If the query is empty, it clears pending text, displayed text, waiting state, and stored matches; if the query is different from the current pending query, it stores it and marks the helper as waiting.

Call relations: Popup::set_query calls this whenever the popup query changes. Its state is later used by FileSearch::set_matches, FileSearch::should_show_matches, and FileSearch::empty_message to decide what file-search information the popup can show.

Call graph: called by 1 (set_query).

FileSearch::set_matches133–141 ↗
fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>)

Purpose: Stores file-search results when they belong to the latest query. It protects the UI from showing results that arrived late for an older query.

Data flow: It receives the query attached to the results and a list of file matches. If the query is not the current pending query, it ignores them; otherwise it records the query, keeps only up to the popup’s maximum number of matches, stores them, and marks waiting as finished.

Call relations: Popup::set_file_matches calls this when file-search work completes. The stored matches are later read by Popup::rows, while the waiting flag is read by FileSearch::empty_message during rendering.

Call graph: called by 1 (set_file_matches).

FileSearch::should_show_matches143–145 ↗
fn should_show_matches(&self) -> bool

Purpose: Answers whether there are file-search matches worth showing. It is a simple yes-or-no check based on whether the stored match list is empty.

Data flow: It reads the stored file matches. If there is at least one match, it returns true; otherwise it returns false and changes nothing.

Call relations: Popup::rows calls this before asking filtered_candidates to build the visible rows. This lets the row-building step know whether file matches should be included.

Call graph: called by 1 (rows).

FileSearch::empty_message147–153 ↗
fn empty_message(&self) -> &'static str

Purpose: Chooses the message shown when there are no file matches to display. It tells the user whether the search is still loading or has finished with no results.

Data flow: It reads the waiting flag. If a file search is pending, it returns “loading...”; otherwise it returns “no matches”.

Call relations: Popup::render_ref calls this right before drawing. The returned text is handed to render_popup so the user sees a helpful empty-state message instead of a blank area.

Call graph: called by 1 (render_ref).

Reusable selection and settings popups

These files provide generic list and multi-select selection widgets and then apply them to concrete settings and toggle popups throughout the bottom pane.

tui/src/bottom_pane/list_selection_view.rssource ↗
domain_logicbottom-pane popup interaction and rendering

This file is the engine behind many popup menus in the terminal interface. It solves the common problem of asking the user to choose one item from a list without every feature having to reinvent search, keyboard movement, scrolling, layout, and rendering. Think of it like a reusable restaurant menu board: callers provide the dishes and notes, and this file decides how to display them, how the cursor moves, and what happens when a choice is made.

The main setup type is SelectionViewParams, which describes the menu before it opens: its title, items, tabs, whether search is allowed, footer text, and any preview panel. Each SelectionItem describes one row and can include actions to run when accepted, a toggle checkbox, disabled state, and search text.

ListSelectionView is the live popup. It keeps the current search query, selected row, scroll position, active tab, and completion state. It maps visible filtered rows back to the original item list, so accepting a filtered result still runs the correct action. It also avoids selecting disabled rows during keyboard navigation.

Rendering is responsive. On wide terminals, optional side content can appear next to the list. On narrow terminals, it falls back underneath. The file also contains tests that lock down spacing, search behavior, key remapping, disabled-row skipping, side-panel layout, and wrapping behavior.

Function details107
SideContentWidth::default68–70 ↗
fn default() -> Self

Purpose: Chooses the safe default for preview-panel width. By default, side content is disabled rather than taking screen space unexpectedly.

Data flow: No outside data comes in. It creates a Fixed width value of 0, which means no side panel. The result is used whenever callers do not explicitly ask for side content.

Call relations: SelectionViewParams::default relies on this so ordinary selection popups stay list-only unless a caller opts into a preview panel.

Call graph: called by 1 (default); 1 external calls (Fixed).

popup_content_width75–77 ↗
fn popup_content_width(total_width: u16) -> u16

Purpose: Calculates how much horizontal room is available inside the shared popup surface. It subtracts the fixed left and right padding used around menu content.

Data flow: It receives the full popup width, subtracts the horizontal inset, and never lets the result go below zero. It returns the usable content width.

Call relations: The height calculation and renderer both call this before laying out headers, rows, tabs, and preview content, so all parts agree on the same inner width.

Call graph: called by 2 (desired_height, render).

side_by_side_layout_widths82–97 ↗
fn side_by_side_layout_widths(
    content_width: u16,
    side_content_width: SideContentWidth,
    side_content_min_width: u16,
) -> Option<(u16, u16)>

Purpose: Decides whether the list and preview panel can fit next to each other. If not, it tells the caller to use the stacked layout instead.

Data flow: It receives the available content width, the requested side-panel sizing rule, and a minimum allowed side width. It computes a side width, checks that both the side panel and remaining list are usable, and returns the two widths or nothing.

Call relations: ListSelectionView::side_layout_width uses this helper so both measuring and drawing follow the same side-by-side versus stacked decision.

Call graph: called by 1 (side_layout_width).

SelectionViewParams::default211–238 ↗
fn default() -> Self

Purpose: Provides a complete set of harmless defaults for constructing a selection popup. This lets callers specify only the parts they care about.

Data flow: No caller data is required. It fills in empty text, no tabs, no items, no search, default wrapping, empty renderable placeholders, default side-content settings, and cancellation enabled.

Call relations: Most popup builders start from these defaults and override a few fields before passing the result to ListSelectionView::new.

Call graph: calls 1 internal fn (default); 2 external calls (new, new).

ListSelectionView::new292–360 ↗
fn new(
        params: SelectionViewParams,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

Purpose: Creates a live selection popup from the caller's configuration. It also prepares the initial tab, header, filtering state, and selected row.

Data flow: It receives SelectionViewParams, an app event sender, and the list keymap. It folds title and subtitle into the header, chooses the starting tab, stores all runtime state, applies the initial filter, and returns a ready-to-render view.

Call relations: Higher-level bottom-pane code calls this when it needs to show or replace a selection popup. It immediately calls apply_filter so later input and rendering start from a valid selection.

Call graph: calls 2 internal fn (new, with); called by 23 (replace_active_views_with_selection_view, replace_selection_view_if_active, replace_selection_view_if_present, show_selection_view, new, set_current, auto_all_rows_col_width_does_not_shift_when_scrolling, c0_ctrl_n_respects_unbound_list_move_down, c0_ctrl_p_respects_remapped_list_move_down, c0_ctrl_p_respects_unbound_list_move_up (+13 more)); 3 external calls (new, new, new).

ListSelectionView::visible_len362–364 ↗
fn visible_len(&self) -> usize

Purpose: Reports how many rows are currently visible after filtering. This is the list length that keyboard movement should use.

Data flow: It reads the filtered index list and returns its length. It does not change state.

Call relations: Movement and disabled-row skipping helpers call this before moving the scroll state, because they operate on the filtered view rather than the full source list.

Call graph: called by 10 (jump_bottom, jump_top, move_down, move_up, page_down, page_up, skip_disabled_down, skip_disabled_down_clamped, skip_disabled_up, skip_disabled_up_clamped).

ListSelectionView::tabs_enabled366–368 ↗
fn tabs_enabled(&self) -> bool

Purpose: Checks whether this popup is currently using tabs. It is a small guard for tab-navigation keys.

Data flow: It reads whether an active tab index exists and returns true or false. Nothing is changed.

Call relations: handle_key_event calls this before letting left and right movement switch tabs.

Call graph: called by 1 (handle_key_event).

ListSelectionView::active_items370–375 ↗
fn active_items(&self) -> &[SelectionItem]

Purpose: Returns the item list that should be used right now. If tabs are active, that means the active tab's items; otherwise it means the main item list.

Data flow: It reads the active tab index and item storage. It returns a shared slice of the currently relevant rows.

Call relations: Filtering, accepting, number shortcuts, and enable checks all call this so they consistently work on the current tab or the untabbed list.

Call graph: called by 4 (accept, actual_idx_for_enabled_number, apply_filter, enabled_actual_idx).

ListSelectionView::active_items_mut377–384 ↗
fn active_items_mut(&mut self) -> &mut [SelectionItem]

Purpose: Returns the current item list in a form that can be changed. It is used when toggling a checkbox-like row.

Data flow: It checks the active tab, finds that tab's items when present, otherwise falls back to the main items, and returns mutable access.

Call relations: toggle_selected calls this so a toggle updates the item in the currently visible list, not a hidden tab or the wrong backing list.

Call graph: called by 1 (toggle_selected).

ListSelectionView::active_header386–391 ↗
fn active_header(&self) -> &dyn Renderable

Purpose: Chooses which header to render. Tabbed popups can have a different header per tab; untabbed popups use the main header.

Data flow: It reads the active tab and returns the matching renderable header, or the default header if no tab header applies.

Call relations: desired_height and render call this so measuring and drawing use the same header.

Call graph: called by 2 (desired_height, render).

ListSelectionView::max_visible_rows409–411 ↗
fn max_visible_rows(len: usize) -> usize

Purpose: Caps how many list rows are shown at once. This keeps popups from growing too tall when there are many choices.

Data flow: It receives the number of available rows, applies the global maximum, and still allows at least one row of space. It returns that row count.

Call relations: Navigation and height measurement use this cap so scrolling and popup size agree.

ListSelectionView::selected_actual_idx413–417 ↗
fn selected_actual_idx(&self) -> Option<usize>

Purpose: Translates the highlighted visible row back to the original item index. This matters because search filtering changes which rows are visible.

Data flow: It reads the selected visible index from scroll state, looks it up in filtered_indices, and returns the source item index if one exists.

Call relations: Selection callbacks, accepting, toggling, and movement-change checks use this so actions always apply to the real item.

Call graph: called by 12 (apply_filter, fire_selection_changed, jump_bottom, jump_top, move_down, move_up, page_down, page_up, selected_index, selected_item_has_toggle (+2 more)).

ListSelectionView::apply_filter419–489 ↗
fn apply_filter(&mut self)

Purpose: Rebuilds the visible row list after search text or tab state changes. It also preserves the user's selection when possible.

Data flow: It reads the current search query, active items, previous selection, current/default/initial selection hints, and disabled states. It rebuilds filtered_indices, picks a valid selected visible row, clamps scrolling, and may notify the selection-changed callback.

Call relations: Construction, typed search, pasted search text, tests, and tab switching call this. It hands off to ScrollState helpers for selection bounds and to fire_selection_changed when the highlighted real item changes.

Call graph: calls 5 internal fn (active_items, fire_selection_changed, selected_actual_idx, clamp_selection, ensure_visible); called by 4 (handle_key_event, handle_paste, set_search_query, switch_tab); 1 external calls (max_visible_rows).

ListSelectionView::build_rows491–561 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

Purpose: Turns raw selection items into display rows ready for the common row renderer. It adds numbering, current/default markers, toggle text, disabled text, and descriptions.

Data flow: It reads filtered items and current selection. For each visible item it builds a GenericDisplayRow containing the text and styling hints needed for drawing. Disabled rows are marked and enabled rows get user-facing numbers.

Call relations: desired_height calls it to measure row height, and render calls it to draw the same rows. This keeps measurement and display based on identical row content.

Call graph: called by 2 (desired_height, render).

ListSelectionView::switch_tab563–585 ↗
fn switch_tab(&mut self, step: isize)

Purpose: Moves to the previous or next tab. It resets search and selection state so the new tab starts cleanly.

Data flow: It receives a step direction, wraps around the tab list, updates the active tab, clears the search query, resets scrolling, applies filtering, chooses a first enabled row if needed, and fires the selection callback.

Call relations: handle_key_event calls this when tab navigation keys are pressed. It then relies on apply_filter and select_first_enabled_row to make the new tab usable.

Call graph: calls 4 internal fn (apply_filter, fire_selection_changed, select_first_enabled_row, reset); called by 1 (handle_key_event).

ListSelectionView::select_first_enabled_row587–593 ↗
fn select_first_enabled_row(&mut self)

Purpose: Highlights the first selectable row. It is used as a fallback when no previous or preferred selection is available.

Data flow: It looks for the first enabled visible row, or row zero if all rows are disabled but visible. It stores that visible index and scrolls back to the top.

Call relations: switch_tab calls this after changing tabs if filtering did not produce a selected row.

Call graph: calls 1 internal fn (first_enabled_visible_idx); called by 1 (switch_tab).

ListSelectionView::first_enabled_visible_idx595–601 ↗
fn first_enabled_visible_idx(&self) -> Option<usize>

Purpose: Finds the first visible row that the user is allowed to choose. Disabled rows are skipped.

Data flow: It walks filtered_indices, maps each visible row to the active item list, and returns the first visible position whose item is enabled.

Call relations: select_first_enabled_row and apply_filter use this when they need a safe default highlight.

Call graph: called by 1 (select_first_enabled_row).

ListSelectionView::enabled_actual_idx603–608 ↗
fn enabled_actual_idx(&self, actual_idx: usize) -> Option<usize>

Purpose: Checks whether a source item index points to an enabled item. If it does, the same index is returned.

Data flow: It receives an item index, reads the active item list, checks the disabled flags, and returns that index only when the item can be selected.

Call relations: apply_filter uses this to decide whether a previous or initial item index is still a valid selection.

Call graph: calls 1 internal fn (active_items).

ListSelectionView::item_is_enabled610–612 ↗
fn item_is_enabled(item: &SelectionItem) -> bool

Purpose: Defines the single rule for whether a row is selectable. A row is enabled only when it is not marked disabled and has no disabled reason.

Data flow: It receives one SelectionItem and reads its disabled fields. It returns true for selectable rows and false otherwise.

Call relations: Filtering, numbering, selection preservation, and toggling use this shared rule so disabled behavior stays consistent.

ListSelectionView::selected_item_has_toggle614–618 ↗
fn selected_item_has_toggle(&self) -> bool

Purpose: Checks whether the highlighted row has a real toggle that Space can flip. It also requires the row to be enabled.

Data flow: It finds the selected source item, reads its toggle and disabled state, and returns true only for an enabled row with a toggle.

Call relations: handle_key_event uses this before treating Space as a toggle command.

Call graph: calls 1 internal fn (selected_actual_idx); called by 1 (handle_key_event).

ListSelectionView::selected_item_has_toggle_placeholder620–628 ↗
fn selected_item_has_toggle_placeholder(&self) -> bool

Purpose: Checks whether the highlighted row has only placeholder toggle text. This lets Space be ignored in one special searchable-list case instead of changing the search query.

Data flow: It finds the selected source item and checks that it is enabled, has no real toggle, and has a toggle placeholder. It returns a boolean.

Call relations: handle_key_event uses this to keep placeholder rows from accidentally accepting a space when the search query is empty.

Call graph: calls 1 internal fn (selected_actual_idx); called by 1 (handle_key_event).

ListSelectionView::actual_idx_for_enabled_number630–641 ↗
fn actual_idx_for_enabled_number(&self, number: usize) -> Option<usize>

Purpose: Maps a displayed number shortcut to the real item index. Numbering skips disabled rows, so this translation is required.

Data flow: It receives a number, ignores zero, walks enabled active items only, and returns the source index of that numbered choice.

Call relations: handle_key_event calls this in non-searchable lists when the user presses a digit to choose an item immediately.

Call graph: calls 1 internal fn (active_items).

ListSelectionView::toggle_selected643–660 ↗
fn toggle_selected(&mut self)

Purpose: Flips the toggle state of the highlighted item and runs that toggle's callback. This powers checkbox-like rows.

Data flow: It finds the selected real item, gets mutable access to the active items, checks that the item is enabled and has a toggle, flips the stored on/off value, and calls the toggle action with the new value and event sender.

Call relations: handle_key_event calls this when Space is pressed on a selected toggle row.

Call graph: calls 2 internal fn (active_items_mut, selected_actual_idx); called by 1 (handle_key_event); 2 external calls (item_is_enabled, clone).

ListSelectionView::move_up662–672 ↗
fn move_up(&mut self)

Purpose: Moves the highlight one row upward, wrapping from top to bottom. Disabled rows are skipped.

Data flow: It records the selected real item, moves the scroll state upward, skips disabled rows, ensures the result is visible, and fires the selection callback only if the real selection changed.

Call relations: handle_key_event calls this for the configured move-up key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_up, visible_len, ensure_visible, move_up_wrap); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::move_down674–684 ↗
fn move_down(&mut self)

Purpose: Moves the highlight one row downward, wrapping from bottom to top. Disabled rows are skipped.

Data flow: It records the selected real item, moves the scroll state downward, skips disabled rows, keeps the selected row in view, and notifies listeners only on a real selection change.

Call relations: handle_key_event calls this for the configured move-down key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_down, visible_len, ensure_visible, move_down_wrap); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::page_up686–696 ↗
fn page_up(&mut self)

Purpose: Moves the highlight up by roughly one page without wrapping past the list edges. It avoids landing on disabled rows.

Data flow: It records the previous selected item, asks ScrollState to page upward within bounds, searches for an enabled row near that landing point, adjusts scrolling, and may fire the selection callback.

Call relations: handle_key_event calls this for the configured page-up key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_up_clamped, visible_len, ensure_visible, page_up_clamped); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::page_down698–708 ↗
fn page_down(&mut self)

Purpose: Moves the highlight down by roughly one page without wrapping past the list edges. It avoids landing on disabled rows.

Data flow: It records the previous selected item, pages downward within bounds, moves off disabled rows if needed, keeps the result visible, and may notify selection listeners.

Call relations: handle_key_event calls this for the configured page-down key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_down_clamped, visible_len, ensure_visible, page_down_clamped); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::jump_top710–720 ↗
fn jump_top(&mut self)

Purpose: Moves the highlight to the top of the visible list. If the top row is disabled, it moves to the next available enabled row.

Data flow: It records the old selected item, jumps the scroll state to the beginning, skips disabled rows downward without wrapping off the list, ensures visibility, and may fire a selection-changed callback.

Call relations: handle_key_event calls this for the configured jump-to-top key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_down_clamped, visible_len, ensure_visible, jump_top); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::jump_bottom722–732 ↗
fn jump_bottom(&mut self)

Purpose: Moves the highlight to the bottom of the visible list. If the bottom row is disabled, it moves upward to an enabled row.

Data flow: It records the old selected item, jumps the scroll state to the end, skips disabled rows upward without wrapping, ensures visibility, and may notify listeners.

Call relations: handle_key_event calls this for the configured jump-to-bottom key.

Call graph: calls 6 internal fn (fire_selection_changed, selected_actual_idx, skip_disabled_up_clamped, visible_len, ensure_visible, jump_bottom); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

ListSelectionView::fire_selection_changed734–740 ↗
fn fire_selection_changed(&self)

Purpose: Runs the optional callback for live selection changes. This is used by features such as previewing a theme before accepting it.

Data flow: It reads the callback and current selected source index. If both exist, it calls the callback with that item index and the app event sender.

Call relations: Filtering, movement, paging, jumping, and tab switching call this after they may have changed the highlighted real item.

Call graph: calls 1 internal fn (selected_actual_idx); called by 8 (apply_filter, jump_bottom, jump_top, move_down, move_up, page_down, page_up, switch_tab).

ListSelectionView::accept742–772 ↗
fn accept(&mut self)

Purpose: Accepts the highlighted choice. It runs the selected item's actions and marks the popup complete when the item says it should dismiss.

Data flow: It maps the visible selection to a source item, verifies the item is enabled, saves the chosen source index, runs each item action with the event sender, and updates completion or parent-dismiss flags. If there is no selected item, it treats Enter as cancellation and runs the cancel callback.

Call relations: handle_key_event calls this for Enter or shortcut acceptance. Other code can later read the saved last selected index.

Call graph: calls 1 internal fn (active_items); called by 1 (handle_key_event).

ListSelectionView::set_search_query775–778 ↗
fn set_search_query(&mut self, query: String)

Purpose: Test-only helper that replaces the search query directly. It lets tests set up filtered states without simulating every keystroke.

Data flow: It receives a query string, stores it, and reapplies filtering. The visible rows and selected row may change.

Call relations: Search-related tests call this before checking rendering or acceptance behavior.

Call graph: calls 1 internal fn (apply_filter).

ListSelectionView::take_last_selected_index780–782 ↗
fn take_last_selected_index(&mut self) -> Option<usize>

Purpose: Returns and clears the most recently accepted source item index. Taking it prevents the same selection from being consumed twice.

Data flow: It reads last_selected_actual_idx, replaces it with none, and returns the previous value.

Call relations: Selection-handling code and tests call this after acceptance to learn which real item was chosen.

Call graph: called by 2 (handle_key_event, selected_choice).

ListSelectionView::rows_width784–786 ↗
fn rows_width(total_width: u16) -> u16

Purpose: Calculates the width available for list rows inside the popup. It removes a small amount of horizontal padding.

Data flow: It receives a total width, subtracts two columns safely, and returns the result.

Call relations: desired_height and render use the same row-width rule so row wrapping matches what is actually drawn.

ListSelectionView::clear_to_terminal_bg788–807 ↗
fn clear_to_terminal_bg(buf: &mut Buffer, area: Rect)

Purpose: Clears a rectangular area to blank cells with reset styling. This removes popup background before drawing preview content.

Data flow: It receives a buffer and rectangle, clips that rectangle to the buffer bounds, and fills every covered cell with a space and reset style.

Call relations: render calls this before drawing side or stacked preview content so previews appear on the terminal's normal background.

Call graph: 2 external calls (area, reset).

ListSelectionView::force_bg_to_terminal_bg809–826 ↗
fn force_bg_to_terminal_bg(buf: &mut Buffer, area: Rect)

Purpose: Resets only the background color in a rectangular area. This keeps preview symbols while removing unwanted popup background color.

Data flow: It receives a buffer and rectangle, clips to the buffer bounds, and changes each cell's background to reset.

Call relations: render calls this after side-content drawing unless the caller asked to preserve side-content background colors.

Call graph: 1 external calls (area).

ListSelectionView::stacked_side_content828–832 ↗
fn stacked_side_content(&self) -> &dyn Renderable

Purpose: Chooses what preview content to show when the side-by-side layout does not fit. A special stacked version can be provided, otherwise the normal side content is reused.

Data flow: It reads the optional stacked renderable. It returns that renderable if present, or the normal side_content renderable otherwise.

Call relations: desired_height and render call this only in the narrow stacked layout path.

Call graph: called by 2 (desired_height, render).

ListSelectionView::side_layout_width836–843 ↗
fn side_layout_width(&self, content_width: u16) -> Option<u16>

Purpose: Checks whether the preview panel should be placed beside the list and returns its width if so.

Data flow: It receives the content width, passes the stored side-panel settings to side_by_side_layout_widths, and keeps only the side width from the result.

Call relations: desired_height and render call this so measuring and drawing make the same side-by-side decision.

Call graph: calls 1 internal fn (side_by_side_layout_widths); called by 2 (desired_height, render).

ListSelectionView::skip_disabled_down845–854 ↗
fn skip_disabled_down(&mut self)

Purpose: Moves downward until the selected row is not disabled, wrapping if necessary. It is used after single-step downward movement.

Data flow: It checks the current visible row and repeatedly moves down while it is disabled, stopping after at most one full pass through the visible rows.

Call relations: move_down calls this after changing the selection.

Call graph: calls 3 internal fn (selected_visible_idx_is_disabled, visible_len, move_down_wrap); called by 1 (move_down).

ListSelectionView::skip_disabled_up856–865 ↗
fn skip_disabled_up(&mut self)

Purpose: Moves upward until the selected row is not disabled, wrapping if necessary. It is used after single-step upward movement.

Data flow: It checks the current visible row and repeatedly moves up while it is disabled, stopping after at most one full pass through the visible rows.

Call relations: move_up calls this after changing the selection.

Call graph: calls 3 internal fn (selected_visible_idx_is_disabled, visible_len, move_up_wrap); called by 1 (move_up).

ListSelectionView::skip_disabled_down_clamped867–884 ↗
fn skip_disabled_down_clamped(&mut self)

Purpose: Moves off a disabled row after a non-wrapping jump or page-down. It prefers rows below the landing point, then rows above.

Data flow: It reads the current selected visible index. If that row is disabled, it searches forward for an enabled row, then backward, and stores the best result.

Call relations: page_down and jump_top call this because those motions should not wrap around like single-step movement.

Call graph: calls 2 internal fn (visible_idx_is_disabled, visible_len); called by 2 (jump_top, page_down).

ListSelectionView::skip_disabled_up_clamped886–900 ↗
fn skip_disabled_up_clamped(&mut self)

Purpose: Moves off a disabled row after a non-wrapping jump or page-up. It prefers rows above the landing point, then rows below.

Data flow: It reads the current selected visible index. If disabled, it searches backward for an enabled row, then forward, and stores the best result.

Call relations: page_up and jump_bottom call this because those motions should stay near the page or edge destination.

Call graph: calls 2 internal fn (visible_idx_is_disabled, visible_len); called by 2 (jump_bottom, page_up).

ListSelectionView::selected_visible_idx_is_disabled902–906 ↗
fn selected_visible_idx_is_disabled(&self) -> bool

Purpose: Checks whether the currently highlighted visible row is disabled. It is a convenience wrapper around the more general visible-row check.

Data flow: It reads the current selected visible index and asks whether that visible index is disabled. If there is no selection, it returns false.

Call relations: skip_disabled_down and skip_disabled_up use this during wrap-around skipping.

Call graph: called by 2 (skip_disabled_down, skip_disabled_up).

ListSelectionView::visible_idx_is_disabled908–913 ↗
fn visible_idx_is_disabled(&self, idx: usize) -> bool

Purpose: Checks whether a given visible row points to a disabled item. This works through the filtered-to-source index mapping.

Data flow: It receives a visible index, maps it through filtered_indices to the active item list, reads disabled fields, and returns true only for disabled rows.

Call relations: The clamped disabled-row skipping helpers use this when searching around a page or jump landing point.

Call graph: called by 2 (skip_disabled_down_clamped, skip_disabled_up_clamped).

ListSelectionView::handle_key_event917–1025 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Responds to keyboard input while the popup is open. It turns keys into movement, tab switching, search editing, toggling, accepting, cancellation, and shortcuts.

Data flow: It receives one key event, compares it with the configured keymap and current mode, then updates selection, search text, tab state, toggle state, or completion. Some paths run item actions or cancellation callbacks.

Call relations: The bottom-pane event loop calls this through the BottomPaneView trait. It dispatches to focused helpers such as move_down, apply_filter, toggle_selected, accept, and on_ctrl_c.

Call graph: calls 15 internal fn (accept, apply_filter, jump_bottom, jump_top, move_down, move_up, on_ctrl_c, page_down, page_up, selected_item_has_toggle (+5 more)); called by 1 (handle_key_event).

ListSelectionView::handle_paste1027–1037 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Adds pasted text to the search query when the list is searchable. It ignores paste for non-search lists or meaningless pasted text.

Data flow: It receives pasted text, checks search mode, normalizes the text into a safe search query fragment, appends it to the query, reapplies filtering, and returns whether the paste was used.

Call relations: The bottom-pane paste path calls this so paste behaves like typing into the search field.

Call graph: calls 2 internal fn (apply_filter, normalize_pasted_search_query).

ListSelectionView::is_complete1039–1041 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether this popup has finished by being accepted or cancelled.

Data flow: It reads the completion field and returns true if a completion value is stored.

Call relations: Higher-level selection handling checks this to know when to close or consume the view.

Call graph: called by 1 (selected_choice).

ListSelectionView::completion1043–1045 ↗
fn completion(&self) -> Option<ViewCompletion>

Purpose: Returns the popup's final result, if it has one. The result says whether the popup was accepted or cancelled.

Data flow: It reads the stored completion value and returns it. It does not clear or change anything.

Call relations: Bottom-pane orchestration can call this after is_complete to decide what happened.

ListSelectionView::dismiss_after_child_accept1047–1049 ↗
fn dismiss_after_child_accept(&self) -> bool

Purpose: Reports whether a parent popup should close after a child popup accepts. This supports nested selection flows.

Data flow: It reads the dismiss_after_child_accept flag and returns it.

Call relations: Parent view orchestration checks this after child completion.

ListSelectionView::clear_dismiss_after_child_accept1051–1053 ↗
fn clear_dismiss_after_child_accept(&mut self)

Purpose: Clears the request to dismiss a parent after child acceptance. This prevents the same request from being acted on repeatedly.

Data flow: It sets dismiss_after_child_accept to false and returns nothing.

Call relations: Bottom-pane orchestration calls this after it has honored the dismiss request.

ListSelectionView::view_id1055–1057 ↗
fn view_id(&self) -> Option<&'static str>

Purpose: Returns the optional stable identifier for this popup. Other code can use it to recognize a specific active view.

Data flow: It reads view_id and returns it as an optional string reference.

Call relations: View replacement and lookup code use this through the BottomPaneView trait.

ListSelectionView::selected_index1059–1061 ↗
fn selected_index(&self) -> Option<usize>

Purpose: Returns the source item index for the current selection. It hides the details of filtering from outside callers.

Data flow: It asks selected_actual_idx to translate the visible selection to the real item index and returns that optional value.

Call relations: External bottom-pane code calls this through the trait when it needs to know which item is highlighted.

Call graph: calls 1 internal fn (selected_actual_idx).

ListSelectionView::active_tab_id1063–1065 ↗
fn active_tab_id(&self) -> Option<&str>

Purpose: Returns the id of the active tab, if the popup has tabs. This lets outside code and footer logic know which tab is current.

Data flow: It reads the active tab index, looks up the corresponding tab, and returns its id as text if available.

Call relations: active_footer_hint uses this to choose tab-specific hints, and the BottomPaneView trait exposes the same information to callers.

Call graph: called by 1 (active_footer_hint).

ListSelectionView::prefer_esc_to_handle_key_event1067–1069 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

Purpose: Tells the wider UI that Escape should be offered to this view's key handler first. That lets the popup run its own cancellation behavior.

Data flow: It returns true and reads no state.

Call relations: The bottom-pane key dispatcher uses this preference when deciding how to route Escape.

ListSelectionView::on_ctrl_c1071–1080 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Cancels the popup when cancellation is allowed. It also runs the optional cancel callback.

Data flow: It checks allow_cancel. If cancellation is blocked, it returns NotHandled. Otherwise it calls the cancel callback if present, stores Cancelled completion, and returns Handled.

Call relations: handle_key_event calls this for configured cancel keys, and wider cancellation handling can use it through the BottomPaneView trait.

Call graph: called by 1 (handle_key_event).

ListSelectionView::desired_height1084–1140 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows the popup wants at a given width. This allows the surrounding layout to reserve enough space before drawing.

Data flow: It receives a width, computes inner width, side-panel mode, row wrapping height, header height, tab height, search line, stacked preview height, footer note wrapping, and hint height. It returns the total desired height.

Call relations: Rendering code and tests call this before drawing. It shares helpers with render so the measured size matches the final layout.

Call graph: calls 10 internal fn (active_footer_hint, active_header, build_rows, side_layout_width, stacked_side_content, popup_content_width, new, measure_rows_height_with_col_width_mode, wrap_styled_line, tab_bar_height); called by 4 (desired_height, render_lines_with_width, render_picker_from_view, render_lines); 2 external calls (rows_width, from).

ListSelectionView::render1142–1370 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the complete selection popup into the terminal buffer. It paints the surface, header, tabs, search bar, list rows, optional preview panel, and footer.

Data flow: It receives a screen rectangle and buffer. It computes sub-areas, builds display rows, renders each section, clears preview backgrounds when needed, and writes styled cells into the buffer.

Call relations: The TUI rendering loop calls this through the Renderable trait. It uses the same measurement helpers as desired_height and delegates common row drawing to selection_popup_common.

Call graph: calls 13 internal fn (active_footer_hint, active_header, build_rows, side_layout_width, stacked_side_content, popup_content_width, new, measure_rows_height_with_col_width_mode, render_menu_surface, render_rows_single_line_with_col_width_mode (+3 more)); called by 5 (render, render_lines_in_area, render_picker_from_view, render_ref, render_lines); 12 external calls (Fill, Length, Max, vertical, from, new, new, clear_to_terminal_bg, force_bg_to_terminal_bg, rows_width (+2 more)).

tests::MarkerRenderable::render1395–1403 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Test helper that fills an area with a repeated marker character. It makes it easy to see where preview content was drawn.

Data flow: It receives an area and buffer, clips to the buffer size, and writes the marker symbol into every covered cell.

Call relations: Side-content layout tests use this fake renderable instead of real preview content.

Call graph: 1 external calls (area).

tests::MarkerRenderable::desired_height1405–1407 ↗
fn desired_height(&self, _width: u16) -> u16

Purpose: Reports the fixed height requested by the marker test renderable.

Data flow: It ignores the width and returns the stored height.

Call relations: Height and layout tests use this through the Renderable trait.

tests::StyledMarkerRenderable::render1417–1425 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Test helper that fills an area with a marker and a specific style. It is used to verify background-color preservation and clearing.

Data flow: It receives an area and buffer, clips to the buffer size, and writes styled marker cells into the area.

Call relations: Side-content background tests use this to check whether render preserves or resets custom styles.

Call graph: 1 external calls (area).

tests::StyledMarkerRenderable::desired_height1427–1429 ↗
fn desired_height(&self, _width: u16) -> u16

Purpose: Reports the fixed height requested by the styled marker test renderable.

Data flow: It ignores the width and returns the stored height.

Call relations: The render tests use this when ListSelectionView measures side content.

tests::new_view1432–1434 ↗
fn new_view(params: SelectionViewParams, tx: AppEventSender) -> ListSelectionView

Purpose: Test helper that creates a ListSelectionView with the default runtime list keymap. It reduces repeated setup in tests.

Data flow: It receives selection parameters and an event sender, adds the default list keymap, and returns a new view.

Call relations: Many tests call this instead of invoking ListSelectionView::new directly.

Call graph: calls 2 internal fn (new, defaults).

tests::make_selection_view1436–1465 ↗
fn make_selection_view(subtitle: Option<&str>) -> ListSelectionView

Purpose: Builds a small approval-mode picker used by spacing snapshot tests. It can include or omit a subtitle.

Data flow: It creates a test event sender, two sample items, title/footer configuration, optional subtitle, and returns a ready view.

Call relations: The title/subtitle spacing tests call this before rendering snapshots.

Call graph: calls 2 internal fn (new, standard_popup_hint_line); 3 external calls (default, new_view, vec!).

tests::render_lines1467–1469 ↗
fn render_lines(view: &ListSelectionView) -> String

Purpose: Renders a test view at a standard width and returns the screen as plain text. This makes snapshot assertions easy to read.

Data flow: It receives a view, uses width 48, and returns the rendered text from render_lines_with_width.

Call relations: Multiple snapshot and assertion tests call this helper.

Call graph: 1 external calls (render_lines_with_width).

tests::render_lines_with_width1471–1473 ↗
fn render_lines_with_width(view: &ListSelectionView, width: u16) -> String

Purpose: Renders a test view at a chosen width using the view's desired height.

Data flow: It receives a view and width, asks the view for its desired height, renders into that area, and returns plain text.

Call relations: Most rendering tests call this to compare output at specific terminal widths.

Call graph: calls 1 internal fn (desired_height); 1 external calls (render_lines_in_area).

tests::render_lines_in_area1475–1495 ↗
fn render_lines_in_area(view: &ListSelectionView, width: u16, height: u16) -> String

Purpose: Renders a test view into an exact width and height and converts the buffer to text. It is the low-level render helper for tests.

Data flow: It creates a buffer for the requested rectangle, calls render, reads every cell symbol row by row, and returns newline-separated text.

Call relations: The other test render helpers and fixed-size layout tests call this.

Call graph: calls 1 internal fn (render); 2 external calls (empty, new).

tests::description_col1497–1504 ↗
fn description_col(rendered: &str, item_marker: &str, description: &str) -> usize

Purpose: Finds the column where a row description appears in rendered text. Tests use it to check column alignment.

Data flow: It receives rendered text, an item marker, and a description. It finds the line containing both and returns the description's character position.

Call relations: Column-width tests call this before comparing positions before and after scrolling or width overrides.

tests::make_scrolling_width_items1506–1522 ↗
fn make_scrolling_width_items() -> Vec<SelectionItem>

Purpose: Creates sample items where the later rows include a much longer name. This helps test whether columns shift during scrolling.

Data flow: It builds eight normal items and one long-name item, each with descriptions, and returns the vector.

Call relations: Column-width and scrolling tests use this common dataset.

Call graph: 1 external calls (default).

tests::render_before_after_scroll_snapshot1524–1545 ↗
fn render_before_after_scroll_snapshot(col_width_mode: ColumnWidthMode, width: u16) -> String

Purpose: Renders a list before and after scrolling so snapshot tests can compare column-width behavior.

Data flow: It receives a column-width mode and width, creates a view with scrolling sample items, renders once, sends several Down keys, renders again, and returns both outputs in one string.

Call relations: The three column-width snapshot tests call this with different width modes.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (default, from, format!, make_scrolling_width_items, render_lines_with_width).

tests::renders_blank_line_between_title_and_items_without_subtitle1548–1554 ↗
fn renders_blank_line_between_title_and_items_without_subtitle()

Purpose: Checks that a popup with a title but no subtitle still has the expected spacing before list items.

Data flow: It builds the sample selection view without a subtitle, renders it, and compares the output to a stored snapshot.

Call relations: This guards against visual regressions in the header/list gap.

Call graph: 2 external calls (assert_snapshot!, make_selection_view).

tests::renders_blank_line_between_subtitle_and_items1557–1560 ↗
fn renders_blank_line_between_subtitle_and_items()

Purpose: Checks spacing when both title and subtitle are present.

Data flow: It builds the sample selection view with a subtitle, renders it, and compares against a snapshot.

Call relations: This complements the no-subtitle spacing test.

Call graph: 2 external calls (assert_snapshot!, make_selection_view).

tests::theme_picker_subtitle_uses_fallback_text_in_94x35_terminal1563–1577 ↗
fn theme_picker_subtitle_uses_fallback_text_in_94x35_terminal()

Purpose: Verifies that the theme picker shows useful fallback subtitle text in a specific terminal size.

Data flow: It builds theme-picker parameters for a 94-column terminal, renders into a 94 by 35 area, and checks for the expected instruction text.

Call relations: This test connects the generic list view to the theme-picker configuration.

Call graph: calls 2 internal fn (new, build_theme_picker_params); 4 external calls (assert!, home_dir, new_view, render_lines_in_area).

tests::theme_picker_enables_side_content_background_preservation1580–1590 ↗
fn theme_picker_enables_side_content_background_preservation()

Purpose: Ensures the theme picker asks the list view to preserve preview background colors. This matters for styled diff previews.

Data flow: It builds theme-picker parameters and checks the preserve_side_content_bg flag.

Call relations: This protects the contract between the theme picker and the side-content renderer.

Call graph: calls 1 internal fn (build_theme_picker_params); 1 external calls (assert!).

tests::preserve_side_content_bg_keeps_rendered_background_colors1593–1633 ↗
fn preserve_side_content_bg_keeps_rendered_background_colors()

Purpose: Checks that side content can keep its own background colors when requested.

Data flow: It builds a view with styled side content and preservation enabled, renders it, finds a marker cell, and asserts that its blue background remains.

Call relations: This specifically tests the render path that skips force_bg_to_terminal_bg.

Call graph: calls 1 internal fn (new); 8 external calls (new, empty, default, new, default, assert_eq!, new_view, vec!).

tests::renders_search_query_line_when_enabled1668–1696 ↗
fn renders_search_query_line_when_enabled()

Purpose: Verifies that searchable lists show the active search query line.

Data flow: It creates a searchable view, sets the query to a known word, renders, and checks that the word appears.

Call relations: This tests the render path for the search bar and the test-only set_search_query helper.

Call graph: calls 2 internal fn (new, standard_popup_hint_line); 5 external calls (default, assert!, new_view, render_lines, vec!).

tests::paste_appends_to_search_query_and_filters_items1699–1728 ↗
fn paste_appends_to_search_query_and_filters_items()

Purpose: Checks that pasted search text is normalized, appended, and used for filtering.

Data flow: It creates a searchable list, types one character, pastes the rest of a branch name with a newline, and asserts the final query, filtered index, and selected item.

Call relations: This exercises handle_key_event for typing and handle_paste for paste input.

Call graph: calls 1 internal fn (new); 7 external calls (default, Char, new, assert!, assert_eq!, new_view, vec!).

tests::whitespace_only_paste_is_ignored1731–1751 ↗
fn whitespace_only_paste_is_ignored()

Purpose: Checks that a paste containing only whitespace does not affect search.

Data flow: It creates a searchable view, pastes whitespace, and asserts that the query and filtered rows remain unchanged.

Call relations: This protects the normalize-and-ignore branch in handle_paste.

Call graph: calls 1 internal fn (new); 5 external calls (default, assert!, assert_eq!, new_view, vec!).

tests::tabbed_view_preserves_current_row_on_initial_selection_and_tab_switch1806–1863 ↗
fn tabbed_view_preserves_current_row_on_initial_selection_and_tab_switch()

Purpose: Checks that tabbed views select the row marked current when opening and after switching tabs.

Data flow: It builds two tabs with a current row at the same position, opens on the second tab, switches left, and asserts that the current row index is selected in both tabs.

Call relations: This protects apply_filter's current-row selection behavior in tabbed popups.

Call graph: calls 3 internal fn (new, new, defaults); 4 external calls (default, from, assert_eq!, vec!).

tests::space_appends_to_active_search_instead_of_toggling_selected_item1866–1903 ↗
fn space_appends_to_active_search_instead_of_toggling_selected_item()

Purpose: Ensures Space becomes search text when a searchable list already has an active query, even if the selected item has a toggle.

Data flow: It creates a searchable toggle row, sets the query, presses Space, and checks that the query gained a space while the toggle and event channel stayed unchanged.

Call relations: This tests the ordering of Space handling in handle_key_event.

Call graph: calls 3 internal fn (new, new, defaults); 6 external calls (default, Char, new, assert!, assert_eq!, vec!).

tests::single_line_row_display_truncates_instead_of_wrapping1906–1958 ↗
fn single_line_row_display_truncates_instead_of_wrapping()

Purpose: Checks that single-line row mode truncates long content instead of increasing popup height.

Data flow: It builds one single-line view and one wrapped view with long text, renders the single-line version, and compares desired heights.

Call relations: This protects the SelectionRowDisplay::SingleLine path in desired_height and render.

Call graph: calls 3 internal fn (new, new, defaults); 4 external calls (default, assert!, render_lines_with_width, vec!).

tests::name_column_width_override_moves_description_column_right1961–2025 ↗
fn name_column_width_override_moves_description_column_right()

Purpose: Verifies that an explicit name-column width changes where descriptions start.

Data flow: It renders one view with automatic width and another with a wider name column, finds the description column in both, and checks that the override moves it right.

Call relations: This tests ColumnWidthConfig use through the list view's rendering path.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (default, assert!, description_col, render_lines_with_width, vec!).

tests::enter_with_no_matches_triggers_cancel_callback2028–2056 ↗
fn enter_with_no_matches_triggers_cancel_callback()

Purpose: Checks that pressing Enter with no filtered match cancels and runs the cancellation callback.

Data flow: It creates a searchable view with a cancel callback, filters to no matches, presses Enter, and asserts completion plus the emitted event.

Call relations: This exercises accept's no-selected-item cancellation behavior through handle_key_event.

Call graph: calls 1 internal fn (new); 7 external calls (new, default, from, assert!, panic!, new_view, vec!).

tests::move_down_without_selection_change_does_not_fire_callback2059–2085 ↗
fn move_down_without_selection_change_does_not_fire_callback()

Purpose: Ensures moving in a one-item list does not falsely report a selection change.

Data flow: It creates a one-item view with a selection-change callback, clears any pending events, presses Down, and checks that no event was sent.

Call relations: This protects move_down's before-and-after selected item comparison.

Call graph: calls 1 internal fn (new); 6 external calls (new, default, from, assert!, new_view, vec!).

tests::disabled_current_rows_skip_default_selection_and_number_shortcuts2088–2143 ↗
fn disabled_current_rows_skip_default_selection_and_number_shortcuts()

Purpose: Verifies disabled rows are not selected by default and are not counted in number shortcuts.

Data flow: It builds a list with disabled and enabled rows, checks that the first enabled row is selected and numbered correctly, presses 2, and checks that the second enabled row was accepted.

Call relations: This exercises apply_filter, build_rows numbering, actual_idx_for_enabled_number, and accept.

Call graph: calls 3 internal fn (new, new, defaults); 7 external calls (default, Char, new, assert!, assert_eq!, render_lines_with_width, vec!).

tests::c0_ctrl_p_respects_unbound_list_move_up2146–2175 ↗
fn c0_ctrl_p_respects_unbound_list_move_up()

Purpose: Checks that a control character does not move selection when the corresponding key binding was removed.

Data flow: It clears the move-up binding, creates a searchable list with the second row initially selected, sends the Ctrl-P control character, and asserts selection and search are unchanged.

Call relations: This protects handle_key_event's keymap-based navigation behavior.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (default, Char, new, assert_eq!, vec!).

tests::c0_ctrl_n_respects_unbound_list_move_down2178–2206 ↗
fn c0_ctrl_n_respects_unbound_list_move_down()

Purpose: Checks that Ctrl-N does not move selection when move-down has been unbound.

Data flow: It clears the move-down binding, creates a searchable list, sends the Ctrl-N control character, and asserts the selection and search query stay as they were.

Call relations: This verifies handle_key_event respects the runtime keymap.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (default, Char, new, assert_eq!, vec!).

tests::c0_ctrl_p_respects_remapped_list_move_down2209–2237 ↗
fn c0_ctrl_p_respects_remapped_list_move_down()

Purpose: Checks that a control key can be remapped to move down.

Data flow: It clears move-up, maps move-down to Ctrl-P, creates a searchable list, sends Ctrl-P, and asserts the selection moved to the second item.

Call relations: This protects custom keymap support in handle_key_event.

Call graph: calls 3 internal fn (new, new, defaults); 5 external calls (default, Char, new, assert_eq!, vec!).

tests::page_and_jump_navigation_use_list_keymap2240–2276 ↗
fn page_and_jump_navigation_use_list_keymap()

Purpose: Verifies page and jump commands follow the configured keymap rather than hardcoded keys.

Data flow: It remaps page down, page up, jump bottom, and jump top to control keys, creates a 12-item view, presses default and custom keys, and checks the selected item after each.

Call relations: This exercises page_down, page_up, jump_bottom, and jump_top through handle_key_event.

Call graph: calls 3 internal fn (new, new, defaults); 6 external calls (default, Char, from, new, assert_eq!, vec!).

tests::page_and_jump_navigation_skip_trailing_disabled_rows_without_wrapping2279–2308 ↗
fn page_and_jump_navigation_skip_trailing_disabled_rows_without_wrapping()

Purpose: Checks that page-down and end-style jumps stop on the last enabled row when the list ends with disabled rows.

Data flow: It creates a 12-item list with the last four disabled, presses PageDown and End, then verifies selection is item 7 and still visible.

Call relations: This protects the clamped disabled-row skipping helpers used by page_down and jump_bottom.

Call graph: calls 3 internal fn (new, new, defaults); 4 external calls (default, from, assert!, assert_eq!).

tests::wraps_long_option_without_overflowing_columns2311–2349 ↗
fn wraps_long_option_without_overflowing_columns()

Purpose: Ensures very long option text wraps cleanly instead of overflowing or truncating important text.

Data flow: It renders a narrow approval list containing a long command and checks that wrapped lines align under the numbered prefix and include the command pieces.

Call relations: This tests build_rows and wrapped row rendering together.

Call graph: calls 1 internal fn (new); 5 external calls (default, assert!, new_view, render_lines_with_width, vec!).

tests::width_changes_do_not_hide_rows2352–2403 ↗
fn width_changes_do_not_hide_rows()

Purpose: Checks that changing popup width across a range does not accidentally hide later rows.

Data flow: It renders a three-item model picker at widths 60 through 90 and records any width where the third option disappears, then asserts none did.

Call relations: This guards the interaction between desired_height, wrapping measurement, and render.

Call graph: calls 1 internal fn (new); 6 external calls (default, new, assert!, new_view, render_lines_with_width, vec!).

tests::narrow_width_keeps_all_rows_visible2406–2431 ↗
fn narrow_width_keeps_all_rows_visible()

Purpose: Verifies that even a narrow popup keeps all rows visible in a simple list.

Data flow: It creates three short items with descriptions, renders at width 24, and checks that the third row is present.

Call relations: This complements the width-range test for very narrow terminals.

Call graph: calls 1 internal fn (new); 4 external calls (default, assert!, new_view, render_lines_with_width).

tests::snapshot_model_picker_width_802434–2478 ↗
fn snapshot_model_picker_width_80()

Purpose: Records the expected rendering of a model picker at width 80.

Data flow: It builds a three-model selection view, renders at width 80, and compares with a snapshot.

Call relations: This protects the common model-selection popup's visual layout.

Call graph: calls 1 internal fn (new); 4 external calls (default, assert_snapshot!, new_view, vec!).

tests::snapshot_narrow_width_preserves_third_option2481–2505 ↗
fn snapshot_narrow_width_preserves_third_option()

Purpose: Records the expected narrow-width rendering for a three-item list.

Data flow: It builds a simple three-item view, renders at width 24, and compares with a snapshot.

Call relations: This locks down the narrow layout covered by narrow_width_keeps_all_rows_visible.

Call graph: calls 1 internal fn (new); 3 external calls (default, assert_snapshot!, new_view).

tests::snapshot_auto_visible_col_width_mode_scroll_behavior2508–2513 ↗
fn snapshot_auto_visible_col_width_mode_scroll_behavior()

Purpose: Snapshots how automatic visible-row column sizing behaves before and after scrolling.

Data flow: It calls the shared before/after scroll renderer with AutoVisible mode and stores the output snapshot.

Call relations: This documents the mode where column widths may depend on currently visible rows.

Call graph: 1 external calls (assert_snapshot!).

tests::snapshot_auto_all_rows_col_width_mode_scroll_behavior2516–2521 ↗
fn snapshot_auto_all_rows_col_width_mode_scroll_behavior()

Purpose: Snapshots how all-row automatic column sizing behaves before and after scrolling.

Data flow: It calls the shared before/after scroll renderer with AutoAllRows mode and stores the output snapshot.

Call relations: This documents the mode intended to keep columns stable while scrolling.

Call graph: 1 external calls (assert_snapshot!).

tests::snapshot_fixed_col_width_mode_scroll_behavior2524–2529 ↗
fn snapshot_fixed_col_width_mode_scroll_behavior()

Purpose: Snapshots how fixed column sizing behaves before and after scrolling.

Data flow: It calls the shared before/after scroll renderer with Fixed mode and stores the output snapshot.

Call relations: This documents the fixed 30/70 column layout behavior.

Call graph: 1 external calls (assert_snapshot!).

tests::auto_all_rows_col_width_does_not_shift_when_scrolling2532–2564 ↗
fn auto_all_rows_col_width_does_not_shift_when_scrolling()

Purpose: Checks that AutoAllRows mode keeps the description column in the same place after scrolling.

Data flow: It renders a scrolling list before and after moving down, confirms the long row appears, and compares the description column for the same item.

Call relations: This validates the stable-column promise of AutoAllRows mode.

Call graph: calls 3 internal fn (new, new, defaults); 7 external calls (default, from, assert!, assert_eq!, description_col, make_scrolling_width_items, render_lines_with_width).

tests::fixed_col_width_is_30_70_and_does_not_shift_when_scrolling2567–2599 ↗
fn fixed_col_width_is_30_70_and_does_not_shift_when_scrolling()

Purpose: Checks that fixed column mode uses a 30/70 split and remains stable while scrolling.

Data flow: It renders before scrolling, calculates the expected description column from width, compares it, scrolls, and checks the same column again.

Call relations: This validates the Fixed column-width path used by row rendering.

Call graph: calls 3 internal fn (new, new, defaults); 6 external calls (default, from, assert_eq!, description_col, make_scrolling_width_items, render_lines_with_width).

tests::side_layout_width_half_uses_exact_split2602–2626 ↗
fn side_layout_width_half_uses_exact_split()

Purpose: Verifies that Half side-content mode computes an exact half-width split after the gap.

Data flow: It creates a view with half-width side content, calculates the expected side width for content width 120, and compares it with side_layout_width.

Call relations: This tests the side_by_side_layout_widths decision through the view helper.

Call graph: calls 1 internal fn (new); 5 external calls (new, default, assert_eq!, new_view, vec!).

tests::side_layout_width_half_falls_back_when_list_would_be_too_narrow2629–2651 ↗
fn side_layout_width_half_falls_back_when_list_would_be_too_narrow()

Purpose: Checks that side-by-side preview is disabled when it would leave too little room for the list.

Data flow: It creates a half-width side-content view with a high minimum width, asks for layout at content width 80, and expects no side layout.

Call relations: This protects the fallback decision used by desired_height and render.

Call graph: calls 1 internal fn (new); 5 external calls (new, default, assert_eq!, new_view, vec!).

tests::stacked_side_content_is_used_when_side_by_side_does_not_fit2654–2689 ↗
fn stacked_side_content_is_used_when_side_by_side_does_not_fit()

Purpose: Verifies that narrow layouts use the stacked fallback preview instead of the wide side preview.

Data flow: It creates different marker renderables for wide and stacked preview content, renders at a width too narrow for side-by-side, and checks that only the stacked marker appears.

Call relations: This exercises stacked_side_content through the render fallback path.

Call graph: calls 1 internal fn (new); 6 external calls (new, default, assert!, new_view, render_lines_with_width, vec!).

tests::side_content_clearing_resets_symbols_and_style2692–2748 ↗
fn side_content_clearing_resets_symbols_and_style()

Purpose: Checks that side-content rendering clears old buffer symbols and resets unwanted background styling.

Data flow: It pre-fills a buffer with red X cells, renders a side-content view, then checks that cleared cells are blank/reset and marker cells have reset backgrounds.

Call relations: This tests clear_to_terminal_bg and force_bg_to_terminal_bg as used by render.

Call graph: calls 1 internal fn (new); 9 external calls (new, empty, default, new, default, assert!, assert_eq!, new_view, vec!).

tests::side_content_clearing_handles_non_zero_buffer_origin2751–2789 ↗
fn side_content_clearing_handles_non_zero_buffer_origin()

Purpose: Verifies that side-content clearing works even when the buffer rectangle starts below row zero.

Data flow: It creates a buffer whose area starts at y=20, pre-fills it, renders the view, and checks a cleared cell at the shifted origin.

Call relations: This protects the clipping math in clear_to_terminal_bg and force_bg_to_terminal_bg.

Call graph: calls 1 internal fn (new); 8 external calls (new, empty, default, new, default, assert_eq!, new_view, vec!).

tui/src/bottom_pane/multi_select_picker.rssource ↗
domain_logicbottom pane interaction and rendering

This file solves a common terminal user interface problem: letting someone choose several things from a long list without making them scroll forever. The picker works like a small checklist with a search box at the top. As the user types, it uses fuzzy search, meaning the typed letters only need to roughly match an item name, not match it exactly. The visible list is rebuilt from the matching items, and the cursor stays on the same item when possible.

Each row shows a cursor marker, a checkbox, the item name, and sometimes a dimmed description. Space flips the checkbox. Enter gathers the IDs of all checked items and reports them through a callback. Escape or Ctrl-C closes the picker without confirming. If ordering is enabled, left and right movement keys can swap an item with its neighbor, but only when no search filter is active. This avoids confusing the user by reordering a partial filtered view.

The file also draws the whole widget: title, optional subtitle, search prompt, scrollable rows, optional live preview, and keyboard hints. The builder at the bottom is the setup tool: it collects the title, items, callbacks, key bindings, and options, then produces a ready-to-use picker.

Function details41
MultiSelectItem::default134–143 ↗
fn default() -> Self

Purpose: Creates a blank selectable item with sensible starting values. It is useful in tests and callers that want to fill in only a few fields.

Data flow: No outside item data goes in. It creates empty strings for the ID and name, no description, an unchecked state, and marks the item as movable by default. The result is a complete MultiSelectItem ready to be customized.

Call relations: Helper code and tests use this when building sample items. It does not start any picker behavior by itself; it only supplies a safe base item.

Call graph: 1 external calls (new).

MultiSelectPicker::builder217–223 ↗
fn builder(
        title: String,
        subtitle: Option<String>,
        app_event_tx: AppEventSender,
    ) -> MultiSelectPickerBuilder

Purpose: Starts the step-by-step construction of a multi-select picker. Callers use it when they know the title, optional subtitle, and application event sender but still need to add items and callbacks.

Data flow: It receives display text and the channel used to send application events. It passes those values into a new MultiSelectPickerBuilder. The output is a builder object that can be further configured.

Call relations: Tests and production setup code call this first, then chain builder methods such as items, on_confirm, or enable_ordering. It hands the real construction work to MultiSelectPickerBuilder::new.

Call graph: calls 1 internal fn (new); called by 4 (page_and_jump_navigation_use_list_keymap, test_picker, new, new).

MultiSelectPicker::apply_filter230–272 ↗
fn apply_filter(&mut self)

Purpose: Recomputes which items should be visible after the user changes the search text. It also keeps the cursor in a reasonable place so the list does not feel jumpy.

Data flow: It reads the current search query, the full item list, and the currently selected visible row. If the query is empty, every item is shown. Otherwise it asks match_item whether each item matches, sorts matches by score and name, stores their original item indexes, then clamps and scrolls the selection into view.

Call relations: Keyboard input calls this after typing or backspacing. Reordering also calls it after swapping items so the visible index list matches the underlying item order.

Call graph: calls 3 internal fn (match_item, clamp_selection, ensure_visible); called by 2 (handle_key_event, move_selected_item); 2 external calls (max_visible_rows, new).

MultiSelectPicker::visible_len275–277 ↗
fn visible_len(&self) -> usize

Purpose: Reports how many items are currently visible after filtering. Navigation uses this so it does not move past the end of the shown list.

Data flow: It reads the filtered index list and returns its length. Nothing else changes.

Call relations: The movement functions call this before moving up, down, paging, or jumping. It is a small shared measurement used to keep cursor behavior consistent.

Call graph: called by 6 (jump_bottom, jump_top, move_down, move_up, page_down, page_up).

MultiSelectPicker::max_visible_rows280–282 ↗
fn max_visible_rows(len: usize) -> usize

Purpose: Decides how many list rows may be shown at once. This keeps the pop-up from growing too tall while still showing at least one row area.

Data flow: It receives the number of visible items. It compares that number with the global maximum row limit and returns the smaller practical display height, treating an empty list as needing one row for messages like “no matches.”

Call relations: Filtering and navigation use this value when adjusting scroll position. Rendering uses related row height calculations so the visual list and cursor math agree.

MultiSelectPicker::rows_width285–287 ↗
fn rows_width(total_width: u16) -> u16

Purpose: Calculates the horizontal space available for list text after accounting for the pop-up’s side padding or border area.

Data flow: It receives the total available width. It subtracts two columns if possible and returns the remaining width, never going below zero.

Call relations: The render path uses this before drawing the row list. It keeps row drawing from spilling into the surrounding layout.

MultiSelectPicker::rows_height290–296 ↗
fn rows_height(&self, rows: &BuiltRows) -> u16

Purpose: Calculates how tall the list area should be for the rows that are about to be drawn. It caps the height so long lists stay scrollable instead of taking over the screen.

Data flow: It receives built display rows. It counts them, clamps that count between one row and the maximum pop-up row count, and returns the height as a terminal row number.

Call relations: Both desired_height and render call this after build_rows. That way the widget asks for and uses the same amount of vertical space.

Call graph: called by 2 (desired_height, render).

MultiSelectPicker::build_rows302–345 ↗
fn build_rows(&self) -> BuiltRows

Purpose: Turns the current filtered items into the plain rows that can be drawn on screen. This is where checkboxes, cursor markers, truncated names, descriptions, and divider rows are assembled.

Data flow: It reads the full item list, the filtered item indexes, and the current selected row. For each visible item it creates a display row like › [x] Item, optionally adds a separator row, and translates picker selection state into row-renderer selection state. The output is the list of drawable rows plus matching scroll state.

Call relations: desired_height uses this to know how much space is needed, and render uses it to actually draw the list. It relies on shared row-rendering types from the selection popup code.

Call graph: calls 1 internal fn (truncate_text); called by 2 (desired_height, render); 4 external calls (default, new, with_capacity, format!).

MultiSelectPicker::move_up348–353 ↗
fn move_up(&mut self)

Purpose: Moves the cursor one visible item upward, wrapping to the bottom when it starts at the top. This gives quick keyboard navigation through the filtered list.

Data flow: It reads the current number of visible items and the current scroll state. It asks the scroll state to move upward with wraparound, then ensures the new cursor position is visible. The picker’s selection and scroll position may change.

Call relations: handle_key_event calls this when the configured upward movement key is pressed. It depends on visible_len and the shared ScrollState movement helpers.

Call graph: calls 3 internal fn (visible_len, ensure_visible, move_up_wrap); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::move_down356–361 ↗
fn move_down(&mut self)

Purpose: Moves the cursor one visible item downward, wrapping to the top when it starts at the bottom. This is the companion to upward movement.

Data flow: It reads the visible item count and current scroll state. It moves the selection down with wraparound, then adjusts scrolling so the selected row remains on screen. The picker’s selection and scroll position may change.

Call relations: handle_key_event calls this when the configured downward movement key is pressed. It shares the same scroll rules as move_up.

Call graph: calls 3 internal fn (visible_len, ensure_visible, move_down_wrap); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::page_up363–367 ↗
fn page_up(&mut self)

Purpose: Moves the cursor up by a page rather than by a single item. This helps users move through longer lists quickly.

Data flow: It reads the visible item count and the maximum number of rows that fit on screen. It asks the scroll state to move upward by that page size while staying within the list. The selection and scroll position may change.

Call relations: handle_key_event calls this when the page-up key binding is pressed. The exact key can come from the runtime keymap.

Call graph: calls 2 internal fn (visible_len, page_up_clamped); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::page_down369–373 ↗
fn page_down(&mut self)

Purpose: Moves the cursor down by a page. This is faster than pressing down repeatedly in a long picker.

Data flow: It reads the visible item count and displayable row count. It asks the scroll state to move downward by that amount without going beyond the last item. The selection and scroll position may change.

Call relations: handle_key_event calls this when the page-down key binding is pressed. Tests verify that custom key bindings can trigger it.

Call graph: calls 2 internal fn (visible_len, page_down_clamped); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::jump_top375–379 ↗
fn jump_top(&mut self)

Purpose: Moves the cursor directly to the first visible item. This is a shortcut for returning to the start of the filtered list.

Data flow: It reads how many items are visible and how many can fit on screen. It updates the scroll state so the first item is selected and visible. The picker’s cursor and scroll top may change.

Call relations: handle_key_event calls this for the configured jump-to-top key. It uses the same scroll state machinery as page movement.

Call graph: calls 2 internal fn (visible_len, jump_top); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::jump_bottom381–385 ↗
fn jump_bottom(&mut self)

Purpose: Moves the cursor directly to the last visible item. This is a shortcut for reaching the end of the current list or search results.

Data flow: It reads the visible item count and maximum visible rows. It updates the scroll state so the last item is selected and brought into view. The picker’s cursor and scroll top may change.

Call relations: handle_key_event calls this for the configured jump-to-bottom key. It is tested together with custom list key bindings.

Call graph: calls 2 internal fn (visible_len, jump_bottom); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

MultiSelectPicker::toggle_selected390–406 ↗
fn toggle_selected(&mut self)

Purpose: Turns the currently highlighted item on if it is off, or off if it is on. This is the checklist action behind pressing Space.

Data flow: It reads the selected visible index, translates it to the real item index, and flips that item’s enabled flag. It then refreshes the preview line and, if an on_change callback exists, sends the full updated item list to that callback.

Call relations: handle_key_event calls this for an unmodified Space key. It hands changed state to update_preview_line and optionally to caller-provided change logic.

Call graph: calls 1 internal fn (update_preview_line); called by 1 (handle_key_event).

MultiSelectPicker::confirm_selection412–427 ↗
fn confirm_selection(&mut self)

Purpose: Finishes the picker by accepting the current checked items. It is the action behind pressing Enter or the configured accept key.

Data flow: It first checks whether the picker has already completed. If not, it marks it complete, collects the IDs of every enabled item, and passes those IDs plus the app event sender to the confirm callback if one was provided.

Call relations: handle_key_event calls this when the accept binding is pressed. After it runs, is_complete reports that the bottom-pane view can be closed.

Call graph: called by 1 (handle_key_event).

MultiSelectPicker::move_selected_item436–491 ↗
fn move_selected_item(&mut self, direction: Direction)

Purpose: Swaps the highlighted item with its neighbor to change item order. It only works when ordering is enabled by the key path, the search box is empty, and both items allow reordering.

Data flow: It reads the selected visible item, maps it to the real list position, checks that the item and target neighbor are orderable, then swaps them. Afterward it refreshes the preview, runs the change callback if present, reapplies the filter, and moves the cursor back onto the moved item.

Call relations: handle_key_event calls this when left or right movement bindings are pressed while ordering is enabled. It calls apply_filter afterward so the visible index list stays synchronized.

Call graph: calls 2 internal fn (apply_filter, update_preview_line); called by 1 (handle_key_event).

MultiSelectPicker::update_preview_line496–501 ↗
fn update_preview_line(&mut self)

Purpose: Rebuilds the optional one-line preview shown below the list. This lets the picker show a live summary, such as the chosen order or selected count.

Data flow: It reads the optional preview callback and the full current item list. If a callback exists, it asks the callback for a line to show; if not, or if the callback returns nothing, the preview is hidden. The picker’s cached preview line is replaced.

Call relations: Toggling and reordering call this after changing item state. Rendering later reads the cached preview line and draws it if present.

Call graph: called by 2 (move_selected_item, toggle_selected).

MultiSelectPicker::close506–514 ↗
fn close(&mut self)

Purpose: Closes the picker without accepting the current selection. It is used for cancellation from Escape or Ctrl-C.

Data flow: It checks whether the picker is already complete. If not, it marks it complete and calls the cancel callback with the app event sender if such a callback was configured.

Call relations: handle_key_event calls this for the cancel binding, and on_ctrl_c calls it for Ctrl-C. Once closed, is_complete lets the surrounding bottom pane stop showing it.

Call graph: called by 4 (handle_key_event, on_ctrl_c, on_ctrl_c, on_ctrl_c).

MultiSelectPicker::is_complete518–520 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the picker has finished, either by confirmation or cancellation. The surrounding UI uses this to know when the pop-up should disappear.

Data flow: It reads the picker’s complete flag and returns that value. It does not change anything.

Call relations: This is part of the BottomPaneView interface. The bottom pane framework calls it while deciding whether this view is still active.

MultiSelectPicker::on_ctrl_c522–525 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Treats Ctrl-C as a handled cancellation for this picker. This prevents Ctrl-C from leaking past the picker when the user clearly meant to dismiss it.

Data flow: It receives no extra data beyond the picker itself. It calls close, which marks the picker complete and may run the cancel callback, then returns a value saying the cancellation was handled.

Call relations: The bottom-pane framework calls this when Ctrl-C is pressed. It delegates the actual closing behavior to close.

Call graph: calls 1 internal fn (close).

MultiSelectPicker::handle_key_event527–589 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Interprets every key press while the picker is active. It decides whether the key means search text, movement, toggle, confirm, cancel, paging, jumping, or reordering.

Data flow: It receives a terminal key event and checks it against the picker’s keymap and modifier keys. Depending on the match, it updates the search query, selection, item states, order, or completion flag. Most actions change picker state; unrecognized keys are ignored.

Call relations: The UI event loop calls this while the picker has focus. It dispatches to helpers such as move_up, toggle_selected, apply_filter, confirm_selection, and close so each behavior stays separate.

Call graph: calls 12 internal fn (apply_filter, close, confirm_selection, jump_bottom, jump_top, move_down, move_selected_item, move_up, page_down, page_up (+2 more)); called by 2 (handle_key_event, handle_key_event).

MultiSelectPicker::desired_height593–602 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows the picker would like to occupy. This helps the layout system reserve enough space before drawing.

Data flow: It receives the available width, builds the current display rows, measures the row list, checks whether a preview line exists, and combines those with the header, search area, and footer heights. It returns the desired height as a row count.

Call relations: The rendering framework calls this before layout. It uses build_rows and rows_height so the requested height matches what render will draw.

Call graph: calls 2 internal fn (build_rows, rows_height); called by 2 (desired_height, desired_height).

MultiSelectPicker::render604–699 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the picker into the terminal screen buffer. This is where the in-memory picker state becomes visible text, spacing, colors, and rows.

Data flow: It receives a rectangular screen area and a buffer to draw into. It splits the area into content and footer sections, draws the background, header, search prompt, list rows, optional preview, and keyboard hints. The buffer is changed; the picker state is not.

Call relations: The terminal UI rendering system calls this when it needs to paint the bottom pane. It calls build_rows, rows_height, the shared row renderer, and preview truncation helpers.

Call graph: calls 6 internal fn (build_rows, rows_height, render_rows_single_line, truncate_line_with_ellipsis_if_overflow, vh, user_message_style); called by 2 (render, render); 9 external calls (default, Fill, Length, Max, vertical, clone, from, rows_width, vec!).

MultiSelectPickerBuilder::new731–745 ↗
fn new(title: String, subtitle: Option<String>, app_event_tx: AppEventSender) -> Self

Purpose: Creates an empty builder with the required title, subtitle, and app event sender already stored. It also installs default list key bindings.

Data flow: It receives title text, optional subtitle text, and an event sender. It fills all optional fields with defaults: no items, ordering off, no callbacks, default runtime keymap, and no preview. The output is a configurable builder.

Call relations: MultiSelectPicker::builder calls this. Later builder methods add items, callbacks, or keymap changes before build creates the actual picker.

Call graph: calls 1 internal fn (defaults); called by 1 (builder); 1 external calls (new).

MultiSelectPickerBuilder::items748–751 ↗
fn items(mut self, items: Vec<MultiSelectItem>) -> Self

Purpose: Sets the list of items that the picker will show. Callers use it to provide the checklist contents.

Data flow: It receives the builder and a vector of MultiSelectItem values. It stores that vector in the builder and returns the builder for continued chaining.

Call relations: Setup code normally calls this before build. The final picker receives these items as its full unfiltered list.

MultiSelectPickerBuilder::enable_ordering756–759 ↗
fn enable_ordering(mut self) -> Self

Purpose: Turns on keyboard support for reordering items. Without this, the picker is only a search-and-toggle checklist.

Data flow: It receives the builder, sets its ordering flag to true, and returns the builder. No items are moved at this stage.

Call relations: Callers use this during setup. handle_key_event later respects this flag before allowing reorder keys to call move_selected_item.

MultiSelectPickerBuilder::list_keymap762–765 ↗
fn list_keymap(mut self, keymap: ListKeymap) -> Self

Purpose: Replaces the default navigation and accept/cancel keys with a supplied keymap. This lets the picker follow user or application key preferences.

Data flow: It receives the builder and a ListKeymap, which is a group of key bindings for list-style controls. It stores the keymap and returns the builder.

Call relations: Tests use this to verify paging and jump keys. The built picker uses the stored keymap inside handle_key_event and when building footer hints.

MultiSelectPickerBuilder::on_preview771–777 ↗
fn on_preview(mut self, callback: F) -> Self

Purpose: Registers a function that can produce a live preview line from the current item list. This is useful when selections have a summary the user should see before confirming.

Data flow: It receives a callback, boxes it so it can be stored uniformly, saves it in the builder, and returns the builder. The callback is not run until the picker is built or item state changes.

Call relations: build transfers this callback into the picker. update_preview_line later calls it after toggles or reordering.

Call graph: 1 external calls (new).

MultiSelectPickerBuilder::on_change783–789 ↗
fn on_change(mut self, callback: F) -> Self

Purpose: Registers a function to run whenever the user toggles or reorders items. This lets the rest of the app react before the user presses confirm.

Data flow: It receives a callback that accepts the full item list and the app event sender. It stores that callback in the builder and returns the builder. No event is sent immediately.

Call relations: build transfers this callback into the picker. toggle_selected and move_selected_item call it after changing item state.

Call graph: 1 external calls (new).

MultiSelectPickerBuilder::on_confirm794–800 ↗
fn on_confirm(mut self, callback: F) -> Self

Purpose: Registers a function to run when the user accepts the selection. The callback receives only the IDs of currently enabled items, not the whole display list.

Data flow: It receives a confirmation callback, stores it in the builder, and returns the builder. Later, confirmation will collect selected IDs and pass them to this callback.

Call relations: build transfers this callback into the picker. confirm_selection calls it when the accept key is pressed.

Call graph: 1 external calls (new).

MultiSelectPickerBuilder::on_cancel803–809 ↗
fn on_cancel(mut self, callback: F) -> Self

Purpose: Registers a function to run when the user cancels the picker. This gives the surrounding app a chance to clean up or restore state.

Data flow: It receives a cancellation callback, stores it in the builder, and returns the builder. The callback is not run until the picker closes by cancellation.

Call relations: build transfers this callback into the picker. close calls it for Escape, cancel bindings, or Ctrl-C.

Call graph: 1 external calls (new).

MultiSelectPickerBuilder::build815–876 ↗
fn build(self) -> MultiSelectPicker

Purpose: Turns the collected builder settings into a working MultiSelectPicker. It prepares the header, keyboard hint text, initial filtering, and optional preview.

Data flow: It consumes the builder, creates a header from the title and subtitle, creates default instruction text if none was supplied, and fills a picker with items, keymap, callbacks, search state, and scroll state. It initializes the visible list and preview before returning the picker.

Call relations: This is the final step after chaining builder options. It uses key-hint helpers such as primary_binding so the footer tells the user the actual configured keys.

Call graph: calls 3 internal fn (new, primary_binding, new); 5 external calls (new, from, new, new, vec!).

match_item895–909 ↗
fn match_item(
    filter: &str,
    display_name: &str,
    name: &str,
) -> Option<(Option<Vec<usize>>, i32)>

Purpose: Checks whether a search query fuzzily matches an item name. It supports forgiving search, where letters can match in order without requiring an exact substring.

Data flow: It receives the filter text, the display name, and a fallback canonical name. It first fuzzy-matches the display name and returns match positions plus a score if successful. If that fails and the fallback name differs, it tries the fallback and returns only the score. If neither matches, it returns nothing.

Call relations: apply_filter calls this for each item while rebuilding search results. The returned score is used to sort the matching items.

Call graph: called by 1 (apply_filter); 1 external calls (fuzzy_match).

tests::test_picker918–928 ↗
fn test_picker(items: Vec<MultiSelectItem>) -> MultiSelectPicker

Purpose: Builds a small picker for tests with ordering enabled. It avoids repeating setup code in every test.

Data flow: It receives a list of test items, creates a dummy app event channel, starts a picker builder, adds the items, enables ordering, and builds the picker. The output is a ready-to-test MultiSelectPicker.

Call relations: Several tests call this before exercising movement, rendering rows, or search behavior. It uses the public builder path so tests cover realistic construction.

Call graph: calls 2 internal fn (new, builder).

tests::item930–938 ↗
fn item(id: &str, orderable: bool, section_break_after: bool) -> MultiSelectItem

Purpose: Creates a simple test item with a chosen ID and a few important flags. It keeps each test focused on behavior rather than object setup.

Data flow: It receives an ID string, an orderable flag, and a section-break flag. It fills ID and name from the ID, applies the flags, and uses default values for the remaining fields. The result is one MultiSelectItem for a test list.

Call relations: The test cases call this when assembling picker contents. It relies on MultiSelectItem::default for unimportant fields.

Call graph: 1 external calls (default).

tests::non_orderable_items_cannot_move_or_be_crossed941–976 ↗
fn non_orderable_items_cannot_move_or_be_crossed()

Purpose: Verifies that fixed items stay fixed and cannot be jumped over by reordered items. This protects section headers or special rows from being accidentally rearranged.

Data flow: It builds a picker with one non-orderable item followed by movable items. It tries moving the fixed item down, then tries moving a movable item up across the fixed item. It checks that the item ID order has not changed.

Call relations: This test directly exercises move_selected_item through the test picker. It confirms the guard logic that checks both the selected item and its neighbor before swapping.

Call graph: 3 external calls (assert_eq!, test_picker, vec!).

tests::horizontal_list_keys_reorder_orderable_items979–1008 ↗
fn horizontal_list_keys_reorder_orderable_items()

Purpose: Verifies that the configured horizontal movement keys reorder items when ordering is enabled. It makes sure keyboard input, not just direct helper calls, can move items.

Data flow: It builds a two-item picker, sends a Ctrl-L key event to move the first item down, checks the order, then sends Ctrl-H to move it back and checks again.

Call relations: This test calls handle_key_event, which then calls move_selected_item. It proves the keymap path and reorder logic work together.

Call graph: 5 external calls (Char, new, assert_eq!, test_picker, vec!).

tests::section_break_after_item_renders_separator_row1011–1033 ↗
fn section_break_after_item_renders_separator_row()

Purpose: Verifies that an item marked with a section break produces a visible divider row after it. This keeps grouped lists readable.

Data flow: It builds a picker with a section break after the first item, calls build_rows, and compares the produced row names and selected row index with the expected values.

Call relations: This test focuses on build_rows. It confirms that divider rows appear in the rendered row list but do not steal the selection from the real item.

Call graph: 3 external calls (assert_eq!, test_picker, vec!).

tests::searchable_plain_j_updates_query_instead_of_navigating1036–1051 ↗
fn searchable_plain_j_updates_query_instead_of_navigating()

Purpose: Verifies that typing a normal letter goes into the search box instead of being treated as a movement shortcut. This matters for users whose keymap may use Vim-like letters such as j for movement.

Data flow: It builds a picker with items named alpha and jupiter, sends an unmodified j key, and checks that the search query becomes j, the filtered list contains only jupiter, and selection starts at the first filtered result.

Call relations: This test exercises handle_key_event and then apply_filter. It protects the rule that plain printable characters are search input unless modified.

Call graph: 5 external calls (Char, new, assert_eq!, test_picker, vec!).

tests::page_and_jump_navigation_use_list_keymap1054–1094 ↗
fn page_and_jump_navigation_use_list_keymap()

Purpose: Verifies that page and jump navigation obey the supplied list keymap. This ensures custom key bindings actually control the picker.

Data flow: It creates a custom keymap for page down, page up, jump bottom, and jump top, builds a picker with twelve items, then sends both an unconfigured PageDown and the configured control-key events. It checks that only the configured keys move the selection as expected.

Call relations: This test uses MultiSelectPicker::builder, list_keymap, and handle_key_event. It confirms that navigation helpers such as page_down, page_up, jump_bottom, and jump_top are reached through the keymap.

Call graph: calls 3 internal fn (new, builder, defaults); 5 external calls (Char, from, new, assert_eq!, vec!).

tui/src/bottom_pane/status_line_setup.rssource ↗
domain_logicsettings popup / bottom-pane interaction

The status line is the small strip at the bottom of the terminal that can show useful facts like the model name, current folder, Git branch, token use, or session title. This file gives users a built-in way to customize that strip instead of being stuck with one fixed layout.

It has two main parts. StatusLineItem is the menu of possible things that can appear in the status line. Each item has a stable text ID, such as model or git-branch, so it can be stored in configuration files. Some older names are still accepted, which keeps old user settings from breaking after names change.

StatusLineSetupView is the actual popup-like view shown in the bottom pane. It wraps a reusable multi-select picker, like a checklist where items can also be reordered. When the view opens, it puts currently enabled items first, in their saved order, then adds the rest as disabled choices. It also adds a special non-orderable option for whether the status line should use theme colors.

As the user changes selections, the view asks preview data to build a live sample status line. When the user confirms, it sends an app event containing the selected items and color preference. When the user cancels, it sends a cancellation event. Without this file, users would have no guided in-app way to customize or preview the status bar.

Function details23
StatusLineItem::description146–192 ↗
fn description(self) -> &'static str

Purpose: Gives each status line option a short explanation for users. The picker uses this text so the settings screen says what an item means, not just its short ID.

Data flow: It receives one StatusLineItem value, chooses the matching human-readable sentence, and returns that sentence as fixed text. It does not change anything.

Call relations: When StatusLineSetupView::status_line_select_item builds a picker row, it calls this function to fill in the row’s help text. That makes the raw configuration item understandable in the menu.

Call graph: called by 1 (status_line_select_item).

StatusLineItem::preview_item194–222 ↗
fn preview_item(self) -> StatusSurfacePreviewItem

Purpose: Connects a configurable status line item to the matching preview data item. This lets the setup screen show realistic sample text for the thing the user selected.

Data flow: It receives a StatusLineItem, maps it to the corresponding StatusSurfacePreviewItem, and returns that preview key. It does not read or change outside state.

Call relations: The setup view calls this while creating picker rows, especially for rate-limit items whose labels can be adjusted from preview data. The live preview later uses the same kind of mapping to display selected items.

Call graph: called by 1 (status_line_select_item).

StatusLineSetupView::new250–337 ↗
fn new(
        status_line_items: Option<&[String]>,
        use_theme_colors: bool,
        preview_data: StatusSurfacePreviewData,
        app_event_tx: AppEventSender,
        list_keymap: ListKey

Purpose: Creates the full status line configuration view. It prepares the checklist, preserves the user’s existing order, wires up the live preview, and defines what happens on confirm or cancel.

Data flow: It takes the current saved item IDs, the theme-color setting, preview data, an app-event sender, and key bindings. It turns valid saved IDs into enabled picker rows, skips duplicates and unknown old/bad IDs, appends all remaining possible items as disabled rows, and builds a MultiSelectPicker. The result is a ready-to-render StatusLineSetupView that can send app events when the user finishes.

Call relations: This is called when the app opens the status-line setup screen, and by a snapshot test. Inside, it calls StatusLineSetupView::status_line_select_item for each real status item, uses the picker builder to configure ordering and callbacks, and sends AppEvent::StatusLineSetup or AppEvent::StatusLineSetupCancelled when the picker reports the user’s decision.

Call graph: calls 1 internal fn (builder); called by 2 (setup_view_snapshot_uses_runtime_preview_values, open_status_line_setup); 4 external calls (new, status_line_select_item, iter, vec!).

StatusLineSetupView::status_line_select_item340–363 ↗
fn status_line_select_item(
        item: StatusLineItem,
        enabled: bool,
        preview_data: &StatusSurfacePreviewData,
    ) -> MultiSelectItem

Purpose: Turns one status line option into one row in the multi-select picker. It decides the row’s ID, display name, description, whether it starts checked, and whether it can be reordered.

Data flow: It receives a status item, an enabled-or-disabled starting value, and preview data. It gets the item’s normal text ID and description, optionally customizes rate-limit labels from runtime preview data, and returns a MultiSelectItem ready for the picker.

Call relations: This helper is used by StatusLineSetupView::new while building the list. It calls StatusLineItem::description and StatusLineItem::preview_item, and for usage-limit rows it asks the preview data for clearer names and descriptions.

Call graph: calls 4 internal fn (description, preview_item, rate_limit_item_description, rate_limit_item_name); 1 external calls (to_string).

StatusLineSetupView::handle_key_event367–369 ↗
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent)

Purpose: Passes keyboard input to the underlying picker. This lets arrow keys, selection keys, and ordering keys work without this view reimplementing the picker behavior.

Data flow: It receives a terminal key event and forwards it to self.picker. The picker may update its highlighted row, checked items, ordering, completion state, or send events depending on the key.

Call relations: The bottom-pane system calls this when the user presses a key while this setup view is active. It delegates directly to the reusable MultiSelectPicker.

Call graph: calls 1 internal fn (handle_key_event).

StatusLineSetupView::is_complete371–373 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the setup view is finished and can be closed. The surrounding bottom-pane code uses this to know when the picker has confirmed or cancelled.

Data flow: It reads the picker’s complete flag and returns it as a boolean. It does not change anything.

Call relations: After key handling or cancellation, the bottom-pane controller can ask this function whether the view should remain open. The answer comes entirely from the wrapped picker.

StatusLineSetupView::on_ctrl_c375–378 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Closes the setup view when the user presses Ctrl-C. It treats Ctrl-C as handled locally so the app does not need to perform a broader interruption.

Data flow: It tells the picker to close, which marks the picker as no longer active, then returns CancellationEvent::Handled. The visible effect is that the setup interaction ends.

Call relations: The bottom-pane system calls this for Ctrl-C while the view is active. It hands the close action to the picker and reports back that cancellation was dealt with here.

Call graph: calls 1 internal fn (close).

StatusLineSetupView::render382–384 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the status line setup screen into the terminal buffer. It relies on the picker to draw the title, choices, selection marks, ordering hints, and preview.

Data flow: It receives a rectangular area of the terminal and a mutable drawing buffer. It forwards both to the picker, which writes characters and styling into the buffer. It returns nothing.

Call relations: The terminal rendering system calls this when the bottom pane needs to be painted. The test helper tests::render_lines also calls it to capture a text snapshot.

Call graph: calls 1 internal fn (render); called by 1 (render_lines).

StatusLineSetupView::desired_height386–388 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Asks how many terminal rows the setup view would like to use at a given width. This helps the surrounding layout give the picker enough space.

Data flow: It receives the available width, asks the picker to calculate its preferred height for that width, and returns that number.

Call relations: The layout/rendering code uses this before drawing. The test helper tests::render_lines calls it so it can create a buffer of the right size before rendering.

Call graph: calls 1 internal fn (desired_height); called by 1 (render_lines).

tests::context_used_accepts_context_usage_legacy_id405–415 ↗
fn context_used_accepts_context_usage_legacy_id()

Purpose: Checks that the modern context-used ID is the normal saved name, while the older context-usage ID still loads correctly. This protects existing user configuration files.

Data flow: It converts StatusLineItem::ContextUsed to text, parses both modern and legacy strings back into the enum, and compares the results with the expected value.

Call relations: This test exercises the string conversion rules generated for StatusLineItem. It supports the setup view because StatusLineSetupView::new parses saved IDs using those same rules.

Call graph: 1 external calls (assert_eq!).

tests::context_remaining_is_selectable_id418–427 ↗
fn context_remaining_is_selectable_id()

Purpose: Checks that context-remaining is a valid selectable status line ID. This prevents accidental renaming or removal of that configuration option.

Data flow: It parses the string context-remaining into a status line item, converts the enum back to text, and verifies both directions match expectations.

Call relations: This test guards the ID format used by saved settings and by the setup picker’s item IDs.

Call graph: 1 external calls (assert_eq!).

tests::project_name_is_canonical_and_accepts_legacy_ids429–443 ↗
fn project_name_is_canonical_and_accepts_legacy_ids()

Purpose: Checks that the project item now saves as project-name but still accepts older names like project and project-root. This keeps old settings compatible.

Data flow: It converts the project item to its canonical text, parses each accepted text form, and verifies all forms produce StatusLineItem::ProjectRoot.

Call relations: This test protects the parsing used when StatusLineSetupView::new reads configured status line items.

Call graph: 1 external calls (assert_eq!).

tests::model_is_canonical_and_accepts_model_name_legacy_id446–456 ↗
fn model_is_canonical_and_accepts_model_name_legacy_id()

Purpose: Checks that the model item saves as model while still accepting the older model-name setting. This avoids breaking users who already have the old value stored.

Data flow: It converts the model enum value to text, parses both model and model-name, and verifies they all refer to StatusLineItem::ModelName.

Call relations: This test guards the item IDs that the setup view reads from configuration and writes back after confirmation.

Call graph: 1 external calls (assert_eq!).

tests::reasoning_is_selectable_id459–465 ↗
fn reasoning_is_selectable_id()

Purpose: Checks that reasoning is a valid status line option ID. This confirms the reasoning-level item can be saved and loaded by name.

Data flow: It converts the reasoning enum value to text, parses the same text back, and verifies the result is StatusLineItem::Reasoning.

Call relations: This test protects the string conversion that the picker relies on for matching saved IDs to status line items.

Call graph: 1 external calls (assert_eq!).

tests::run_state_is_canonical_and_accepts_status_legacy_id468–478 ↗
fn run_state_is_canonical_and_accepts_status_legacy_id()

Purpose: Checks that the run-state item saves as run-state but still accepts the older status name. This preserves compatibility with older configuration values.

Data flow: It converts StatusLineItem::Status to text, parses both the new and old strings, and verifies both parse to the same enum value.

Call relations: This test supports the setup flow because saved IDs are parsed when the setup view is built, and old IDs should not disappear from the user’s chosen list.

Call graph: 1 external calls (assert_eq!).

tests::git_summary_items_are_selectable_ids481–490 ↗
fn git_summary_items_are_selectable_ids()

Purpose: Checks that pull request number and branch change summary items can be parsed from their saved text IDs. This makes sure newer Git-related status line options are available through configuration.

Data flow: It parses pull-request-number and branch-changes and verifies they become the expected StatusLineItem values.

Call relations: This test guards the enum parsing used by both the setup view and any configuration loading that refers to these Git summary items.

Call graph: 1 external calls (assert_eq!).

tests::parse_status_line_items_accepts_title_only_variants493–502 ↗
fn parse_status_line_items_accepts_title_only_variants()

Purpose: Checks that selected status line IDs can be parsed as a small list, including items such as run state and task progress. This protects list-style configuration loading.

Data flow: It starts with an array of text IDs, parses each into a StatusLineItem, collects the results into a vector, and compares that vector with the expected items.

Call relations: This mirrors the way StatusLineSetupView::new receives saved item IDs and parses them before building enabled picker rows.

Call graph: 1 external calls (assert_eq!).

tests::preview_uses_runtime_values505–546 ↗
fn preview_uses_runtime_values()

Purpose: Checks that the preview status line uses real runtime values when they are available. For example, it should show the actual model and directory rather than generic placeholders.

Data flow: It builds preview data containing a model name and current directory, creates enabled picker-style items for those choices, asks the preview data to make a status line, extracts the plain text, and verifies it reads gpt-5 · /repo.

Call relations: This test exercises the preview path that StatusLineSetupView::new installs in the picker’s preview callback. It relies on tests::line_text to turn the styled preview line into ordinary text.

Call graph: calls 1 internal fn (from_iter); 2 external calls (new, assert_eq!).

tests::preview_uses_placeholders_when_runtime_values_are_missing549–584 ↗
fn preview_uses_placeholders_when_runtime_values_are_missing()

Purpose: Checks that the preview still looks useful when some live data is missing. Instead of leaving a blank spot, it can use a realistic placeholder, such as a sample Git branch.

Data flow: It creates preview data with only a model value, asks for a preview containing model and Git branch, extracts the text, and verifies the missing Git branch is represented by a placeholder.

Call relations: This test protects the live preview used by the setup picker. It shows that the preview callback can display meaningful examples even before real runtime data exists.

Call graph: calls 1 internal fn (from_iter); 2 external calls (new, assert_eq!).

tests::preview_includes_thread_title587–628 ↗
fn preview_includes_thread_title()

Purpose: Checks that the thread title option appears correctly in the preview when a title is available. This makes sure conversation-specific status information can be shown.

Data flow: It builds preview data with a model name and a thread title, asks for a preview containing both, extracts the plain text, and verifies the two pieces are joined in the expected order.

Call relations: This test covers the same preview machinery used by the setup view’s live preview, with tests::line_text helping compare the rendered line as simple text.

Call graph: calls 1 internal fn (from_iter); 2 external calls (new, assert_eq!).

tests::setup_view_snapshot_uses_runtime_preview_values631–663 ↗
fn setup_view_snapshot_uses_runtime_preview_values()

Purpose: Checks the full rendered setup view against a saved snapshot, including live preview values. This catches accidental visual or wording changes in the configuration popup.

Data flow: It creates a fake app-event channel, builds a StatusLineSetupView with selected items and preview data, renders the view to text through tests::render_lines, and compares that output to a stored snapshot.

Call relations: This test calls StatusLineSetupView::new, then uses tests::render_lines, which in turn calls the view’s desired_height and render methods. It verifies the pieces work together as a user would see them.

Call graph: calls 4 internal fn (new, new, from_iter, defaults); 1 external calls (assert_snapshot!).

tests::render_lines665–686 ↗
fn render_lines(view: &StatusLineSetupView, width: u16) -> String

Purpose: Renders a setup view into a plain string for tests. This makes visual output easy to compare in snapshots.

Data flow: It receives a StatusLineSetupView and a width, asks the view for its desired height, creates an empty terminal buffer, renders the view into it, then reads every cell back into lines of text. The returned string is the whole rendered screen area joined with newline characters.

Call relations: Snapshot tests call this helper when they need a stable text version of the terminal UI. It calls StatusLineSetupView::desired_height and StatusLineSetupView::render to exercise the same rendering path used in the real app.

Call graph: calls 2 internal fn (desired_height, render); 2 external calls (empty, new).

tests::line_text688–695 ↗
fn line_text(line: Option<Line<'static>>) -> Option<String>

Purpose: Turns an optional styled terminal line into optional plain text for test comparisons. It ignores styling and keeps only the visible words.

Data flow: It receives an optional Line. If there is no line, it returns None. If there is a line, it joins the text content of all spans and returns that combined string.

Call relations: Preview-related tests call this helper after generating a status line preview. It lets those tests compare the preview’s visible text without caring about colors or terminal styling.

tui/src/bottom_pane/title_setup.rssource ↗
domain_logicduring terminal title setup in the bottom pane

The terminal title is the short text shown by the terminal window or tab, like a label on a folder. This file defines the menu that lets a user decide which labels Codex should put there: project name, current model, Git branch, usage limits, task progress, and so on. Without this file, users would not have an in-app way to customize that title safely.

The file has two main pieces. TerminalTitleItem is the list of possible title ingredients. Each item has a stable text identifier, such as project-name, because these identifiers are stored in user configuration files. Some older names, such as project, are still accepted so old configs keep working.

TerminalTitleSetupView turns those possible ingredients into a selectable list. It starts with the user's existing configured order, skips unknown entries, then adds the remaining choices. The picker supports enabling, disabling, and reordering items. As the user changes the list, the view can create a preview line using example status data. If the special activity item is included, the preview uses the same “action required” title-building path as the real app, because that item has special spacing and wording.

When the user confirms, this view sends an app event with the selected title items. If they cancel or press Ctrl-C, it closes cleanly instead of saving anything.

Function details24
TerminalTitleItem::description92–132 ↗
fn description(self) -> &'static str

Purpose: Returns a plain description of one possible terminal-title item. The picker uses this text to help users understand what each choice would add to their terminal title.

Data flow: It receives one TerminalTitleItem, such as project name or Git branch. It matches that item to a human-readable sentence. It returns that sentence as fixed text and does not change anything else.

Call relations: When the setup view builds each selectable row, TerminalTitleSetupView::title_select_item asks this function for the help text shown beside that option.

Call graph: called by 1 (title_select_item).

TerminalTitleItem::preview_item134–162 ↗
fn preview_item(self) -> Option<StatusSurfacePreviewItem>

Purpose: Connects a configurable title item to the preview system's matching sample value. For example, the title item for the Git branch maps to the preview item that knows how to show a sample branch name.

Data flow: It receives one terminal-title item. If that item can be represented by the status preview data, it returns the matching preview-data key. If the item is special, such as the activity spinner, it returns nothing because that piece is built in a different way.

Call relations: The setup view uses this through TerminalTitleSetupView::title_select_item to customize labels, especially for rate-limit items. The preview-building path also relies on the same mapping to turn selected title items into sample text.

Call graph: called by 1 (title_select_item).

TerminalTitleItem::separator_from_previous169–179 ↗
fn separator_from_previous(self, previous: Option<Self>) -> &'static str

Purpose: Chooses the text that should go between two title parts. Most parts are separated with | , but the activity indicator uses simple spaces so the title reads naturally.

Data flow: It receives the current title item and, if there is one, the item that came before it. If this is the first item, it returns an empty separator. If either neighbor is the activity item, it returns one space. Otherwise it returns a vertical-bar separator.

Call relations: This is used when building a preview title line, so the preview mirrors the spacing rules of the real title.

preview_line_for_title_items182–221 ↗
fn preview_line_for_title_items(
    items: &[TerminalTitleItem],
    preview_data: &StatusSurfacePreviewData,
) -> Option<Line<'static>>

Purpose: Builds the one-line preview shown in the setup picker for the currently selected title parts. It lets the user see what their terminal title will roughly look like before saving.

Data flow: It receives an ordered list of selected TerminalTitleItem values and a bundle of sample preview data. It looks up sample text for each selected item, joins the visible pieces with the right separators, and returns a rendered Line for the terminal UI. If nothing produces visible text, it returns nothing. If the activity item is included, it uses the app's action-required title builder so that special case is previewed consistently.

Call relations: The picker created by TerminalTitleSetupView::new calls this through its preview callback whenever the selected items change. It hands off to the shared action-required title builder when the activity item is present, because that part has special title behavior.

Call graph: 5 external calls (from, new, build_action_required_title_text, contains, iter).

parse_terminal_title_items223–234 ↗
fn parse_terminal_title_items(ids: impl Iterator<Item = T>) -> Option<Vec<TerminalTitleItem>>

Purpose: Turns stored or picker-provided text IDs into the internal title-item values. It deliberately accepts the whole list only if every ID is valid, so the app never saves or previews a half-understood selection.

Data flow: It receives an iterator of strings such as project-name or activity. It tries to parse each string into a TerminalTitleItem. If all succeed, it returns the ordered list of parsed items. If any one string is unknown, it returns nothing.

Call relations: The setup flow uses this when converting picker IDs back into real title items for previewing and saving. The tests tests::parse_terminal_title_items_preserves_order, tests::parse_terminal_title_items_rejects_invalid_ids, and tests::parse_terminal_title_items_accepts_kebab_case_variants check its important promises.

Call graph: called by 3 (parse_terminal_title_items_accepts_kebab_case_variants, parse_terminal_title_items_preserves_order, parse_terminal_title_items_rejects_invalid_ids); 1 external calls (map).

TerminalTitleSetupView::new248–316 ↗
fn new(
        title_items: Option<&[String]>,
        preview_data: StatusSurfacePreviewData,
        app_event_tx: AppEventSender,
        list_keymap: ListKeymap,
    ) -> Self

Purpose: Creates the full terminal-title setup screen. It prepares the selectable list, keeps the user's existing order first, wires up preview, save, and cancel behavior, and returns a view ready to show in the bottom pane.

Data flow: It receives the current configured title IDs, preview data, an app-event sender, and the key bindings for list navigation. It parses known configured IDs, removes duplicates, marks those items as selected, appends all remaining available title items as unselected, and builds a MultiSelectPicker. The returned TerminalTitleSetupView contains that picker and can now respond to keys, render itself, and send app events.

Call relations: The wider app calls this from open_terminal_title_setup when the user opens terminal-title configuration. The snapshot test tests::renders_title_setup_popup also calls it to verify the screen layout. Inside the picker callbacks, it sends preview, confirm, and cancel events back to the app.

Call graph: calls 1 internal fn (builder); called by 2 (renders_title_setup_popup, open_terminal_title_setup); 1 external calls (iter).

TerminalTitleSetupView::title_select_item318–344 ↗
fn title_select_item(
        item: TerminalTitleItem,
        enabled: bool,
        preview_data: &StatusSurfacePreviewData,
    ) -> MultiSelectItem

Purpose: Converts one possible terminal-title ingredient into one row in the multi-select picker. This is where the enum choice becomes something the user can read, select, and reorder.

Data flow: It receives a TerminalTitleItem, whether it should start enabled, and preview data. It creates the row ID from the item's stable string name, gets a description, and may adjust the displayed name and description for rate-limit items using current preview data. It returns a MultiSelectItem with an ID, label, description, enabled state, and ordering flag.

Call relations: TerminalTitleSetupView::new calls this for both already-selected items and available-but-unselected items. It calls TerminalTitleItem::description, TerminalTitleItem::preview_item, and rate-limit preview helpers to make the row clear to the user.

Call graph: calls 4 internal fn (rate_limit_item_description, rate_limit_item_name, description, preview_item); 1 external calls (to_string).

TerminalTitleSetupView::handle_key_event348–350 ↗
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent)

Purpose: Passes keyboard input to the picker. This lets arrow keys, selection keys, reorder commands, and confirm or cancel commands work inside this setup screen.

Data flow: It receives a terminal key event. It forwards that event to the inner MultiSelectPicker, which updates its own selection, cursor, ordering, or completion state. This function itself returns nothing.

Call relations: The bottom-pane system calls this through the BottomPaneView interface whenever the user presses a key while this view is active. It delegates the actual key behavior to the picker.

Call graph: calls 1 internal fn (handle_key_event).

TerminalTitleSetupView::is_complete352–354 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether this setup view is finished. The bottom pane can use this to know when it should stop showing the picker.

Data flow: It reads the complete flag from the inner picker. It returns true if the picker has completed, otherwise false, and does not change state.

Call relations: The bottom-pane framework calls this through the BottomPaneView interface after user interaction. It reflects the picker's own completion state.

TerminalTitleSetupView::on_ctrl_c356–359 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Treats Ctrl-C as a handled cancellation for this screen. It closes the picker without saving a new terminal-title configuration.

Data flow: It receives mutable access to the view. It tells the picker to close, then returns a cancellation result saying the Ctrl-C was handled here. The app can then avoid treating Ctrl-C as a larger shutdown signal for this interaction.

Call relations: The bottom-pane framework calls this through the BottomPaneView interface when the user presses Ctrl-C. It hands the close action to the picker and reports that cancellation was taken care of.

Call graph: calls 1 internal fn (close).

TerminalTitleSetupView::render363–365 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the terminal-title setup screen into the terminal UI buffer. It does not decide what to show itself; it asks the picker to draw its current state.

Data flow: It receives a rectangular screen area and a mutable drawing buffer. It forwards both to the inner picker, which writes the visible text and styling into the buffer. Nothing is returned.

Call relations: The rendering system calls this through the Renderable interface while this bottom-pane view is visible. The test helper tests::render_lines also calls it to capture the output for snapshot testing.

Call graph: calls 1 internal fn (render); called by 1 (render_lines).

TerminalTitleSetupView::desired_height367–369 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Asks how many terminal rows this setup view would like to use at a given width. This helps the bottom pane reserve enough space for the picker.

Data flow: It receives the available width. It forwards that width to the inner picker, which calculates the needed height based on its title, help text, items, and wrapping. It returns that height as a number of rows.

Call relations: The layout system calls this through the Renderable interface before drawing. The test helper tests::render_lines also calls it so the test buffer is the right size.

Call graph: calls 1 internal fn (desired_height); called by 1 (render_lines).

tests::render_lines379–400 ↗
fn render_lines(view: &TerminalTitleSetupView, width: u16) -> String

Purpose: Renders a setup view into a plain string so tests can compare the screen output. It turns the terminal drawing buffer into ordinary lines of text.

Data flow: It receives a TerminalTitleSetupView and a width. It asks the view for its desired height, creates an empty buffer of that size, renders the view into it, then reads every cell back into strings. It returns the joined lines.

Call relations: tests::renders_title_setup_popup uses this helper before snapshot comparison. It calls the view's desired_height and render methods just like the real UI would.

Call graph: calls 2 internal fn (desired_height, render); 2 external calls (empty, new).

tests::renders_title_setup_popup403–422 ↗
fn renders_title_setup_popup()

Purpose: Checks that the terminal-title setup popup renders as expected. This protects the visible UI from accidental layout or wording changes.

Data flow: It creates a fake app-event channel, builds a setup view with several selected title items and default preview data, renders the view to text, and compares that text to a saved snapshot. The test passes only if the rendered output matches the expected screen.

Call relations: This test calls TerminalTitleSetupView::new to build the view and tests::render_lines indirectly through the snapshot assertion path. It exercises the same setup and render path used when a real user opens the popup.

Call graph: calls 4 internal fn (new, default, new, defaults); 1 external calls (assert_snapshot!).

tests::parse_terminal_title_items_preserves_order425–438 ↗
fn parse_terminal_title_items_preserves_order()

Purpose: Verifies that parsing title item IDs keeps the user's chosen order. Order matters because it controls the left-to-right order of the terminal title.

Data flow: It feeds several valid IDs into parse_terminal_title_items. It expects the returned list to contain the matching enum values in the same sequence. It does not change any app state.

Call relations: This test directly calls parse_terminal_title_items and compares the result with the expected ordered list.

Call graph: calls 1 internal fn (parse_terminal_title_items); 1 external calls (assert_eq!).

tests::parse_terminal_title_items_rejects_invalid_ids441–444 ↗
fn parse_terminal_title_items_rejects_invalid_ids()

Purpose: Verifies that parsing fails if any title item ID is unknown. This protects the app from saving a partially interpreted configuration.

Data flow: It gives parse_terminal_title_items one legacy-valid ID and one invalid ID. Because the invalid ID cannot be parsed, the expected result is no list at all. The test only checks the returned value.

Call relations: This test directly calls parse_terminal_title_items and confirms its all-or-nothing behavior.

Call graph: calls 1 internal fn (parse_terminal_title_items); 1 external calls (assert_eq!).

tests::activity_is_canonical_and_accepts_spinner_legacy_id447–457 ↗
fn activity_is_canonical_and_accepts_spinner_legacy_id()

Purpose: Checks that the activity item saves using its current ID while still accepting its old ID. This keeps old user configs working without changing the new preferred spelling.

Data flow: It converts the activity item to a string and expects activity. It also parses both activity and the older spinner name and expects both to produce the same item.

Call relations: This test checks the string conversion and parsing behavior generated for TerminalTitleItem::Spinner, which is important because these IDs are stored in configuration.

Call graph: 1 external calls (assert_eq!).

tests::project_name_is_canonical_and_accepts_project_legacy_id460–470 ↗
fn project_name_is_canonical_and_accepts_project_legacy_id()

Purpose: Checks that the project title item uses project-name as its current saved ID while still accepting the older project ID.

Data flow: It turns the project item into a string and expects project-name. It then parses both project-name and project and expects both to become the project item.

Call relations: This test protects backward compatibility for user configuration files that used the older project ID.

Call graph: 1 external calls (assert_eq!).

tests::thread_title_is_canonical_and_accepts_thread_legacy_id473–483 ↗
fn thread_title_is_canonical_and_accepts_thread_legacy_id()

Purpose: Checks that the thread title item saves as thread-title while still accepting the older thread ID.

Data flow: It verifies the current string form of the thread item, then parses both the current and legacy strings. Both parsed values should point to the same thread title item.

Call relations: This test protects the title-item identifier contract for thread-related configuration.

Call graph: 1 external calls (assert_eq!).

tests::model_is_canonical_and_accepts_model_name_legacy_id486–496 ↗
fn model_is_canonical_and_accepts_model_name_legacy_id()

Purpose: Checks that the model title item saves as model while still accepting the older model-name ID.

Data flow: It converts the model item to its saved string and verifies it is model. It parses both accepted strings and expects both to become the model item.

Call relations: This test keeps model-title configuration compatible across ID naming changes.

Call graph: 1 external calls (assert_eq!).

tests::run_state_is_canonical_and_accepts_status_legacy_id499–509 ↗
fn run_state_is_canonical_and_accepts_status_legacy_id()

Purpose: Checks that the run-state item saves as run-state while still accepting the older status ID.

Data flow: It verifies the current string form, then parses both run-state and status. Both should produce the same status/run-state title item.

Call relations: This test protects old configs that used status while confirming the current canonical name used by new saves.

Call graph: 1 external calls (assert_eq!).

tests::model_with_reasoning_has_distinct_id512–521 ↗
fn model_with_reasoning_has_distinct_id()

Purpose: Confirms that the combined model-and-reasoning title item has its own separate ID. This prevents it from being confused with the plain model item or the reasoning-only item.

Data flow: It converts the combined item to a string and expects model-with-reasoning. It also parses that string and expects the combined item back.

Call relations: This test checks the identifier contract for TerminalTitleItem::ModelWithReasoning, which is one of the selectable terminal-title parts.

Call graph: 1 external calls (assert_eq!).

tests::reasoning_is_selectable_id524–530 ↗
fn reasoning_is_selectable_id()

Purpose: Confirms that the reasoning-level title item has a usable saved ID. This makes sure users can select and persist reasoning level as its own title part.

Data flow: It checks that the reasoning item turns into the string reasoning. It then parses reasoning and expects the reasoning item back.

Call relations: This test protects the identifier used when saving or loading the reasoning title item.

Call graph: 1 external calls (assert_eq!).

tests::parse_terminal_title_items_accepts_kebab_case_variants533–580 ↗
fn parse_terminal_title_items_accepts_kebab_case_variants()

Purpose: Checks that the parser accepts the normal kebab-case IDs for many title items. Kebab-case means lowercase words joined with hyphens, which is the format stored in configuration.

Data flow: It sends a long list of valid title-item IDs into parse_terminal_title_items. It expects a matching ordered list of TerminalTitleItem values. If any accepted ID stopped parsing, this test would fail.

Call relations: This test directly exercises parse_terminal_title_items across the main set of selectable title parts, giving broad coverage for saved configuration IDs.

Call graph: calls 1 internal fn (parse_terminal_title_items); 1 external calls (assert_eq!).

tui/src/chatwidget/settings_popups.rssource ↗
orchestrationrequest handling

This file is part of the terminal user interface, specifically the ChatWidget, which is the main chat area the user interacts with. Its job is to open settings-style popups when the user asks for them. Think of it like the small control panels behind buttons in an app: one panel for appearance, one for tone of voice, and one for experimental options.

For the theme picker, it gathers a few pieces of context, such as the current theme, the Codex home folder, and the terminal width, then asks the bottom part of the chat screen to show a selection list.

For personality selection, it is more careful. It first checks that startup has finished, because changing personality before the session is ready would not be safe. It also checks whether the current model supports personalities. If either check fails, it shows the user a clear message instead of opening a broken menu. If all is well, it builds a list of personality choices. Choosing one sends events that update the current session, update the interface state, and save the choice.

For experimental features, it reads the known feature list, keeps only those meant to appear in the experimental menu, marks which ones are enabled, and opens a dedicated view for them.

Function details6
ChatWidget::open_theme_picker9–21 ↗
fn open_theme_picker(&mut self)

Purpose: Opens the theme selection popup so the user can choose how the terminal interface looks. It prepares the information the theme picker needs, such as the current theme and screen width, before showing it.

Data flow: It starts with the chat widget’s current configuration and last rendered terminal width. It also tries to find the Codex home folder, which may contain theme-related files or settings. It passes these pieces into build_theme_picker_params, gets back ready-to-display picker settings, and tells the bottom pane to show that selection view.

Call relations: This function is called when the user asks to change the theme. It relies on find_codex_home to locate Codex’s local folder and on build_theme_picker_params to turn raw context into a popup configuration. Once that is built, it hands control to the bottom pane, which is the part of the interface responsible for showing popup-like views.

Call graph: calls 1 internal fn (build_theme_picker_params); 1 external calls (find_codex_home).

ChatWidget::open_personality_popup23–39 ↗
fn open_personality_popup(&mut self)

Purpose: Starts the personality selection flow, but only if it is safe and meaningful to do so. It protects the user from seeing a choice that cannot actually be applied.

Data flow: It reads the chat widget’s session state and current model. If startup is not finished, it adds an informational message explaining that personality selection is not ready yet. If the model does not support personalities, it adds an error message naming the current model and suggesting /model. If both checks pass, it opens the real personality picker.

Call relations: This is the public-facing gatekeeper for personality selection inside the chat widget. When the user triggers the personality menu, this function decides whether to show a helpful message or continue to ChatWidget::open_personality_popup_for_current_model, which builds the actual list of choices.

Call graph: calls 1 internal fn (open_personality_popup_for_current_model); 1 external calls (format!).

ChatWidget::open_personality_popup_for_current_model41–91 ↗
fn open_personality_popup_for_current_model(&mut self)

Purpose: Builds and displays the personality picker for the current model. It creates the selectable rows, marks the current personality, and attaches the actions that should happen when the user picks one.

Data flow: It reads the currently saved personality from configuration, falling back to Friendly if none is set. It creates menu items for the available personalities, giving each a label, description, current-selection marker, disabled state, and selection action. When an item is chosen, its action sends events to update the turn context, update the visible app state, and persist the selection. Finally, it adds a header and footer hint and asks the bottom pane to show the selection view.

Call relations: This function is called only after ChatWidget::open_personality_popup has checked that the session is ready and the model supports personality choices. It builds the menu itself, using helper functions in this file for the human-readable labels and descriptions, then hands the finished view to the bottom pane for display.

Call graph: calls 1 internal fn (new); called by 1 (open_personality_popup); 3 external calls (new, default, from).

ChatWidget::open_experimental_popup93–114 ↗
fn open_experimental_popup(&mut self)

Purpose: Opens the experimental features screen, where users can see feature flags that are meant to be exposed in the interface. A feature flag is a switch that can turn a still-new or optional behavior on or off.

Data flow: It reads the global feature list and filters it down to features that provide a menu name and description for the experimental screen. For each one, it records the feature id, display name, description, and whether it is currently enabled in configuration. It then creates an ExperimentalFeaturesView with those items, the app event sender, and the current list key controls, and shows that view in the bottom pane.

Call relations: This function is used when the user opens the experimental features menu. It gathers feature information from the shared feature definitions, combines it with the current configuration, and passes the finished view to the bottom pane. The view receives the app event sender so later user actions inside that screen can report changes back to the rest of the app.

Call graph: calls 1 internal fn (new); 1 external calls (new).

ChatWidget::personality_label116–122 ↗
fn personality_label(personality: Personality) -> &'static str

Purpose: Turns a personality value into the short name shown in the menu. This keeps the user-facing wording in one place.

Data flow: It takes a Personality value as input and matches it to a fixed text label: None, Friendly, or Pragmatic. It returns that label without changing any state.

Call relations: The personality picker uses this helper while building each row of the selection list. It is a small translation step between the internal personality value used by the program and the plain name the user sees.

ChatWidget::personality_description124–130 ↗
fn personality_description(personality: Personality) -> &'static str

Purpose: Provides the short explanatory sentence shown under each personality choice. This helps the user understand what each option means before selecting it.

Data flow: It takes a Personality value and returns a fixed description string. None explains that no personality instructions are used, Friendly describes a warm and collaborative style, and Pragmatic describes a concise and direct style. It does not modify anything.

Call relations: The personality picker calls this helper while creating menu items. Together with ChatWidget::personality_label, it supplies the human-readable text for each personality option.

tui/src/bottom_pane/skills_toggle_view.rssource ↗
domain_logicmain loop, while the manage-skills popup is open

This file is the user-facing control panel for enabling and disabling “skills” inside the terminal interface. A skill is shown as a row with a name, a short description, and a checkbox-like marker. Without this view, users would not have an in-app way to browse skills, search for one, toggle it, and close the panel cleanly.

The main type, SkillsToggleView, keeps the full list of skills, the current search text, which rows match that search, and which row is currently selected. Think of it like a small filing cabinet: the full list stays in the cabinet, while the search box decides which folders are currently visible on the desk. When the user types, the view filters and sorts the visible rows. When the user moves up or down, it changes the selected row and keeps it within the scrollable window. When the user presses space or the configured accept key, it flips the selected skill between enabled and disabled, then sends an app event so the setting can be saved elsewhere.

The same file also contains the drawing code. It lays out a header, search prompt, skill rows, and footer hints. The tests at the bottom render the popup into text and check that the basic layout and custom key hints look right.

Function details24
SkillsToggleView::new60–84 ↗
fn new(
        items: Vec<SkillsToggleItem>,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

Purpose: Creates a new skills toggle popup with its skill list, event sender, and keyboard settings. It prepares the title text, footer hint, scroll state, and initial filtered list so the view is ready to draw and respond to input.

Data flow: It receives a list of skill items, a channel for sending app events, and the list keymap. It builds a header, stores the inputs, makes an empty search query, creates the footer hint from the keymap, then applies the initial filter. The result is a fully initialized SkillsToggleView.

Call relations: This is used when the manage-skills popup is opened, and also by the tests that inspect rendering and footer hints. During setup it calls skills_toggle_hint_line to turn the active key bindings into readable instructions.

Call graph: calls 3 internal fn (new, skills_toggle_hint_line, new); called by 3 (footer_hint_uses_list_keymap_accept_and_cancel, renders_basic_popup, open_manage_skills_popup); 4 external calls (new, from, new, new).

SkillsToggleView::visible_len86–88 ↗
fn visible_len(&self) -> usize

Purpose: Returns how many skills are currently visible after search filtering. Navigation uses this number so it moves only through rows the user can actually see.

Data flow: It reads the filtered index list and returns its length. It does not change any state.

Call relations: Movement helpers call this before moving the selection, so they know the current bounds of the visible list.

Call graph: called by 6 (jump_bottom, jump_top, move_down, move_up, page_down, page_up).

SkillsToggleView::max_visible_rows90–92 ↗
fn max_visible_rows(len: usize) -> usize

Purpose: Decides the largest number of skill rows the popup should show at once. It keeps the list from growing taller than the shared popup limit, while still allowing at least one row of space.

Data flow: It receives a row count and compares it with the maximum popup row setting. It returns the smaller practical height, using one row when the list is empty.

Call relations: Filtering, movement, paging, and rendering use this helper whenever they need the visible list height.

SkillsToggleView::apply_filter94–137 ↗
fn apply_filter(&mut self)

Purpose: Updates which skills are shown based on the current search text. It also tries to keep the same skill selected after the list changes, which makes searching feel stable instead of jumpy.

Data flow: It reads the current search query, current selection, old filtered list, and full skill list. If the search is empty, it shows all items. Otherwise it asks match_skill whether each item matches and sorts matches by score and name. It then updates filtered_indices, fixes the selected row, clamps it to a valid position, and adjusts scrolling so the selection remains visible.

Call relations: This runs during construction and whenever typing or backspace changes the search in handle_key_event. It relies on match_skill for fuzzy matching and on ScrollState helpers to keep the selected row valid.

Call graph: calls 3 internal fn (clamp_selection, ensure_visible, match_skill); called by 1 (handle_key_event); 2 external calls (max_visible_rows, new).

SkillsToggleView::build_rows139–158 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

Purpose: Turns the current filtered skill list into rows that the shared popup renderer can draw. Each row includes a selection arrow, an enabled marker, the skill name, and the description.

Data flow: It reads the filtered indices, current selected index, and skill items. For each visible skill, it builds display text such as an arrow and [x] marker, truncates long names, attaches the description, and returns a vector of display rows.

Call relations: desired_height calls this to know how tall the popup should be. render calls it to get the rows that are passed to the common single-line row renderer.

Call graph: called by 2 (desired_height, render).

SkillsToggleView::move_up160–165 ↗
fn move_up(&mut self)

Purpose: Moves the selected row one step upward, wrapping around if needed. It then adjusts scrolling so the new selection is visible.

Data flow: It reads the number of visible rows, asks the scroll state to move upward with wraparound, calculates how many rows can be displayed, and makes sure the selected row is inside that display window.

Call relations: handle_key_event calls this when the configured move-up key is pressed. It delegates the actual selection math to ScrollState.

Call graph: calls 3 internal fn (ensure_visible, move_up_wrap, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::move_down167–172 ↗
fn move_down(&mut self)

Purpose: Moves the selected row one step downward, wrapping around if needed. It keeps the scroll position aligned with the newly selected row.

Data flow: It reads the number of visible rows, asks the scroll state to move downward with wraparound, calculates the visible window size, and updates scrolling if the selection would otherwise be off-screen.

Call relations: handle_key_event calls this when the configured move-down key is pressed. It uses ScrollState for the detailed movement behavior.

Call graph: calls 3 internal fn (ensure_visible, move_down_wrap, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::page_up174–178 ↗
fn page_up(&mut self)

Purpose: Moves the selection upward by about one visible page. This gives users a faster way to move through a long skill list.

Data flow: It reads the visible row count, calculates the current page size, and asks the scroll state to move up by that page while staying within the list bounds.

Call relations: handle_key_event calls this when the configured page-up key is pressed.

Call graph: calls 2 internal fn (page_up_clamped, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::page_down180–184 ↗
fn page_down(&mut self)

Purpose: Moves the selection downward by about one visible page. This helps users jump through longer filtered results without pressing down repeatedly.

Data flow: It reads the visible row count, calculates the current page size, and asks the scroll state to move down by that page while staying within the list bounds.

Call relations: handle_key_event calls this when the configured page-down key is pressed.

Call graph: calls 2 internal fn (page_down_clamped, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::jump_top186–190 ↗
fn jump_top(&mut self)

Purpose: Moves the selection to the first visible skill. It is the quick “go to start” action for the popup list.

Data flow: It reads the number of visible rows and current display height, then asks the scroll state to select the top item and set scrolling appropriately.

Call relations: handle_key_event calls this when the configured jump-to-top key is pressed.

Call graph: calls 2 internal fn (jump_top, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::jump_bottom192–196 ↗
fn jump_bottom(&mut self)

Purpose: Moves the selection to the last visible skill. It is the quick “go to end” action for the popup list.

Data flow: It reads the number of visible rows and current display height, then asks the scroll state to select the bottom item and set scrolling appropriately.

Call relations: handle_key_event calls this when the configured jump-to-bottom key is pressed.

Call graph: calls 2 internal fn (jump_bottom, visible_len); called by 1 (handle_key_event); 1 external calls (max_visible_rows).

SkillsToggleView::toggle_selected198–214 ↗
fn toggle_selected(&mut self)

Purpose: Turns the currently selected skill on if it is off, or off if it is on. It immediately notifies the app so the change can be saved.

Data flow: It reads the selected visible row, maps that back to the real item in the full skill list, flips that item’s enabled value, and sends an AppEvent::SetSkillEnabled containing the skill file path and new enabled state. If there is no valid selected item, it does nothing.

Call relations: handle_key_event calls this when the user presses space or the configured accept key. It hands the actual persistence work to the wider app by sending an event through AppEventSender.

Call graph: calls 1 internal fn (send); called by 1 (handle_key_event).

SkillsToggleView::close216–224 ↗
fn close(&mut self)

Purpose: Marks the popup as finished and tells the rest of the app that skill management has closed. It also requests a refreshed skill list, so later views see up-to-date enabled states.

Data flow: It checks whether the view has already completed. If not, it sets complete to true, sends ManageSkillsClosed, and asks the app to list skills again with forced reload enabled. Calling it again after completion has no effect.

Call relations: on_ctrl_c calls this when the user cancels or closes the popup. It communicates closure and refresh needs through AppEventSender.

Call graph: calls 2 internal fn (list_skills, send); called by 1 (on_ctrl_c); 1 external calls (new).

SkillsToggleView::rows_width226–228 ↗
fn rows_width(total_width: u16) -> u16

Purpose: Calculates the width available for drawing skill rows inside the popup. It subtracts a small amount for the popup’s side padding.

Data flow: It receives the total content width and subtracts two columns, using safe subtraction so very small widths do not underflow. It returns the row drawing width.

Call relations: render uses this when preparing the rectangle passed to the row renderer.

SkillsToggleView::rows_height230–232 ↗
fn rows_height(&self, rows: &[GenericDisplayRow]) -> u16

Purpose: Calculates how tall the visible skill list should be. It keeps the list at least one row high and no taller than the popup’s maximum row limit.

Data flow: It receives the rows that could be displayed, reads their count, clamps that count between one and the maximum popup height, and returns the result as a terminal row height.

Call relations: desired_height uses this to reserve enough space. render uses it to lay out the list area.

Call graph: called by 2 (desired_height, render); 1 external calls (len).

SkillsToggleView::handle_key_event236–288 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Interprets keyboard input while the skills popup is open. It decides whether a key should move through the list, edit the search text, toggle a skill, or close the popup.

Data flow: It receives one key event. Plain printable characters are treated as search input, so typing letters filters skills instead of accidentally triggering navigation shortcuts like j or k. Navigation bindings move the selection, backspace edits the search, space or accept toggles the selected skill, and cancel closes the view. It changes view state and may send app events through helper methods.

Call relations: The bottom pane system calls this when keys arrive. It dispatches to movement helpers, apply_filter, toggle_selected, or on_ctrl_c depending on the key.

Call graph: calls 10 internal fn (apply_filter, jump_bottom, jump_top, move_down, move_up, on_ctrl_c, page_down, page_up, toggle_selected, is_plain_text_key_event).

SkillsToggleView::is_complete290–292 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether this popup has finished and should no longer stay open. The surrounding UI can use this to remove the view.

Data flow: It reads the complete flag and returns it. It does not change anything.

Call relations: This is part of the BottomPaneView behavior. The bottom pane can ask it after input or updates to know whether the close path has run.

SkillsToggleView::on_ctrl_c294–297 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Handles cancellation for this popup. It closes the popup and tells the caller that the cancellation was handled here.

Data flow: It calls close, which marks the view complete and sends app events, then returns CancellationEvent::Handled.

Call relations: handle_key_event calls this when the cancel binding is pressed. It is also the BottomPaneView cancellation hook used by the surrounding pane system.

Call graph: calls 1 internal fn (close); called by 1 (handle_key_event).

SkillsToggleView::desired_height301–309 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Computes how many terminal rows the popup wants for its current contents. This helps the layout system reserve enough space before drawing.

Data flow: It receives the available width, builds the current display rows, measures the row area, asks the header how much height it needs, then adds space for the search box, padding, list, and footer. It returns the desired height.

Call relations: The test helper tests::render_lines calls this before rendering. In normal UI layout, the renderable system can use it to size the bottom pane.

Call graph: calls 2 internal fn (build_rows, rows_height); called by 1 (render_lines).

SkillsToggleView::render311–387 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the complete skills popup into the terminal buffer. It paints the background, header, search prompt, filtered skill rows, and footer key hints.

Data flow: It receives a screen rectangle and a mutable terminal buffer. If the rectangle has no space, it stops. Otherwise it splits the area into content and footer, draws the content style, measures and renders the header, draws either the search placeholder or current query, renders the visible rows with the common row renderer, and finally draws the dim footer hint.

Call relations: The terminal rendering system calls this when the popup needs to appear. The tests call it through tests::render_lines to capture text snapshots.

Call graph: calls 5 internal fn (render_rows_single_line, build_rows, rows_height, vh, user_message_style); called by 1 (render_lines); 9 external calls (default, Fill, Length, Max, vertical, clone, from, rows_width, vec!).

skills_toggle_hint_line390–421 ↗
fn skills_toggle_hint_line(keymap: &ListKeymap) -> Line<'static>

Purpose: Builds the footer instruction text that tells users which keys toggle a skill and which key closes the popup. It adapts to the active keymap instead of hard-coding every hint.

Data flow: It receives the list keymap, creates the fixed space-key hint, looks up the primary accept and cancel bindings, and returns a formatted line. If the accept key is also space, it avoids repeating it.

Call relations: SkillsToggleView::new calls this while creating the popup, so the footer matches the user’s current keyboard configuration.

Call graph: calls 2 internal fn (plain, primary_binding); called by 1 (new); 3 external calls (Char, from, vec!).

tests::render_lines433–454 ↗
fn render_lines(view: &SkillsToggleView, width: u16) -> String

Purpose: Renders a SkillsToggleView into plain text so tests can compare what the popup would look like. This turns terminal drawing into a readable string snapshot.

Data flow: It receives a view and width, asks the view for its desired height, creates an empty buffer of that size, renders the view into the buffer, then walks every cell and joins the symbols into newline-separated text.

Call relations: The rendering tests call this helper before making snapshot or string assertions. It uses the same desired_height and render methods that production rendering uses.

Call graph: calls 2 internal fn (desired_height, render); 2 external calls (empty, new).

tests::renders_basic_popup457–478 ↗
fn renders_basic_popup()

Purpose: Checks that a normal skills popup renders as expected. It covers the title, explanatory text, enabled and disabled markers, descriptions, search area, and footer in one snapshot.

Data flow: It creates a test event sender, builds two sample skill items, constructs a SkillsToggleView with the default keymap, renders it to text, and compares the result with a stored snapshot.

Call relations: This test exercises SkillsToggleView::new, desired_height, and render through tests::render_lines. It protects the basic visual layout from accidental changes.

Call graph: calls 3 internal fn (new, new, defaults); 2 external calls (assert_snapshot!, vec!).

tui/src/bottom_pane/memories_settings_view.rssource ↗
io_transportactive while the memories settings popup is open

This file is the user-facing control panel for Codex “memories” inside the terminal interface. Memories are saved information Codex can use later, so this popup lets the user decide whether Codex should use existing memories, create new ones, or delete the local memory files. Without this file, those choices would still exist somewhere in configuration, but the user would not have this guided terminal menu for changing them safely.

The main struct, MemoriesSettingsView, keeps a short list of menu rows, remembers which row is selected, and knows whether the user is in the normal settings screen or a separate reset-confirmation screen. That confirmation screen is important because “reset all memories” is destructive; it works like a second door before entering a dangerous room.

The view listens for list-navigation keys, space to toggle checkboxes, Enter to save or choose an action, and cancel keys to back out. When the user saves, it sends an application event such as “update memory settings” or “reset memories” to the rest of the app. It also draws the popup: header text, rows, a documentation link, and footer hints that explain the available keys.

Function details28
MemoriesSettingsView::new71–109 ↗
fn new(
        use_memories: bool,
        generate_memories: bool,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

Purpose: Creates a new memories settings popup with the current memory settings already filled in. It prepares the menu rows, the documentation link, the key bindings, and the channel used to tell the rest of the app what the user chose.

Data flow: It receives the current values for using and generating memories, plus an app-event sender and a keymap. It builds three menu entries: two checkbox-style settings and one reset action. It then selects the first row if any rows exist and returns the ready-to-render view.

Call relations: When the app opens the memories popup, open_memories_popup calls this constructor. The constructor sets up the view and calls MemoriesSettingsView::initialize_selection so keyboard navigation starts from a sensible place.

Call graph: calls 1 internal fn (new); called by 1 (open_memories_popup); 2 external calls (from, vec!).

MemoriesSettingsView::initialize_selection111–113 ↗
fn initialize_selection(&mut self)

Purpose: Chooses the first menu row as the starting selection. This makes the popup immediately usable with the keyboard.

Data flow: It looks at the current list of menu items. If the list is not empty, it stores index 0 as the selected row; otherwise, it leaves the selection empty.

Call relations: This is used during setup by MemoriesSettingsView::new. It is a small helper that gives the newly created popup its initial cursor position.

MemoriesSettingsView::settings_header115–122 ↗
fn settings_header(&self) -> ColumnRenderable<'_>

Purpose: Builds the title and explanatory text for the normal memories settings screen. This tells the user what the popup is for and where changes are saved.

Data flow: It creates a small vertical block of text containing a bold “Memories” title and a dimmed explanation about choosing how Codex uses and creates memories. The result is returned as something the renderer can measure and draw.

Call relations: MemoriesSettingsView::render uses this when drawing the normal settings screen, and MemoriesSettingsView::desired_height uses it when calculating how much vertical space the popup wants.

Call graph: calls 1 internal fn (new); called by 2 (desired_height, render); 1 external calls (from).

MemoriesSettingsView::reset_confirmation_header124–132 ↗
fn reset_confirmation_header(&self) -> ColumnRenderable<'_>

Purpose: Builds the title and warning text for the reset confirmation screen. It makes the destructive action clear before the user confirms it.

Data flow: It creates a small vertical block of text with a bold “Reset all memories?” title and a dimmed warning explaining that local memory files and summaries will be cleared. The result is returned for measuring and drawing.

Call relations: MemoriesSettingsView::render uses this when the view is in reset-confirmation mode, and MemoriesSettingsView::desired_height uses it to size that confirmation view.

Call graph: calls 1 internal fn (new); called by 2 (desired_height, render); 1 external calls (from).

MemoriesSettingsView::active_state134–136 ↗
fn active_state(&self) -> &ScrollState

Purpose: Returns the scroll and selection state that is currently in charge. The normal menu and reset confirmation each have their own selection state, and this chooses the right one.

Data flow: It checks whether a reset-confirmation state exists. If so, it returns that; otherwise, it returns the normal settings menu state.

Call relations: MemoriesSettingsView::render and MemoriesSettingsView::desired_height use this so they can draw or measure whichever screen is active without duplicating the choice logic.

Call graph: called by 2 (desired_height, render).

MemoriesSettingsView::active_state_mut138–140 ↗
fn active_state_mut(&mut self) -> &mut ScrollState

Purpose: Returns the currently active selection state in a form that can be changed. Navigation functions use this to move the highlighted row.

Data flow: It checks whether the reset confirmation is open. If it is, it gives back the confirmation screen’s scroll state; if not, it gives back the main settings screen’s scroll state.

Call relations: The movement helpers, such as MemoriesSettingsView::move_up, MemoriesSettingsView::move_down, MemoriesSettingsView::page_up, and jump functions, call this so the same keys work in both the normal menu and the confirmation menu.

Call graph: called by 6 (jump_bottom, jump_top, move_down, move_up, page_down, page_up).

MemoriesSettingsView::visible_len142–148 ↗
fn visible_len(&self) -> usize

Purpose: Reports how many selectable rows are visible in the current screen. Navigation needs this number so it knows where the top and bottom are.

Data flow: It checks whether the reset confirmation is open. In confirmation mode it returns 2, for “Reset all memories” and “Go back”; otherwise it returns the number of normal menu items.

Call relations: All navigation helpers call this before moving the selection. It keeps movement correct whether the user is in the main settings list or the shorter confirmation list.

Call graph: called by 6 (jump_bottom, jump_top, move_down, move_up, page_down, page_up).

MemoriesSettingsView::build_rows150–202 ↗
fn build_rows(&self) -> Vec<GenericDisplayRow>

Purpose: Turns the internal menu items into display rows that can be drawn on screen. It adds selection arrows, checkbox marks, names, and explanatory descriptions.

Data flow: It reads the current screen mode and selected row. In confirmation mode, it builds two rows: reset and go back. In normal mode, it builds rows for the memory settings and reset action, including [x] or [ ] for enabled or disabled settings. It returns a list of display-ready rows.

Call relations: MemoriesSettingsView::render calls this before drawing the list, and MemoriesSettingsView::desired_height calls it before measuring the list. It is the bridge between stored menu data and what the user actually sees.

Call graph: called by 2 (desired_height, render).

MemoriesSettingsView::move_up204–212 ↗
fn move_up(&mut self)

Purpose: Moves the highlighted row up by one, wrapping around if needed. This lets users navigate with an “up” key without getting stuck at the top.

Data flow: It reads the number of visible rows. If there are none, it does nothing. Otherwise, it changes the active selection state to the previous row and adjusts scrolling so the selected row stays visible.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured move-up key is pressed. It uses MemoriesSettingsView::visible_len and MemoriesSettingsView::active_state_mut to work in either screen mode.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::move_down214–222 ↗
fn move_down(&mut self)

Purpose: Moves the highlighted row down by one, wrapping around if needed. This is the matching movement for the “down” key.

Data flow: It reads the number of visible rows. If there are no rows, it returns immediately. Otherwise, it changes the active selection to the next row and adjusts scrolling so the selection remains on screen.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured move-down key is pressed. It relies on MemoriesSettingsView::visible_len and MemoriesSettingsView::active_state_mut so the same logic works for the main list and confirmation list.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::page_up224–228 ↗
fn page_up(&mut self)

Purpose: Moves the selection upward by a page of rows. This is useful if a list is taller than the visible popup area.

Data flow: It reads the current row count and calculates how many rows can be shown at once. It then asks the active scroll state to move upward by that visible amount, staying within valid bounds.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured page-up key is pressed. It uses MemoriesSettingsView::visible_len and MemoriesSettingsView::active_state_mut to apply the movement to the current screen.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::page_down230–234 ↗
fn page_down(&mut self)

Purpose: Moves the selection downward by a page of rows. It helps users move faster through longer lists.

Data flow: It reads the current row count and calculates the visible page size. It then changes the active scroll state to move downward by about one page, without going past the end.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured page-down key is pressed. The helper works through MemoriesSettingsView::visible_len and MemoriesSettingsView::active_state_mut so it follows the currently active menu.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::jump_top236–240 ↗
fn jump_top(&mut self)

Purpose: Moves the selection directly to the first row. This gives users a quick way back to the top.

Data flow: It reads the number of visible rows and the visible page size. It then updates the active selection state so the first row is selected and visible.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured jump-to-top key is pressed. It delegates the actual movement to the active scroll state.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::jump_bottom242–246 ↗
fn jump_bottom(&mut self)

Purpose: Moves the selection directly to the last row. This gives users a quick way to reach the bottom action, such as reset.

Data flow: It reads the number of visible rows and the visible page size. It then updates the active selection state so the final row is selected and visible.

Call relations: MemoriesSettingsView::handle_key_event calls this when the configured jump-to-bottom key is pressed. It uses the same active-state helper as the other navigation functions.

Call graph: calls 2 internal fn (active_state_mut, visible_len); called by 1 (handle_key_event).

MemoriesSettingsView::toggle_selected248–260 ↗
fn toggle_selected(&mut self)

Purpose: Turns the selected memory setting on or off when the user presses Space. It only changes checkbox-style settings, not actions like reset.

Data flow: It first checks that the reset confirmation screen is not open. Then it looks up the selected normal menu row. If that row is a setting, it flips its enabled value from true to false or from false to true; otherwise, it leaves everything unchanged.

Call relations: MemoriesSettingsView::handle_key_event calls this for the Space key. Later, if the user saves, MemoriesSettingsView::save reads these updated values and sends them to the app.

Call graph: called by 1 (handle_key_event).

MemoriesSettingsView::rows_width262–264 ↗
fn rows_width(total_width: u16) -> u16

Purpose: Calculates how wide the list rows should be inside the popup. It leaves a little horizontal room for padding and borders.

Data flow: It receives the total available width and subtracts two columns, never going below zero. It returns the usable row width.

Call relations: MemoriesSettingsView::desired_height uses this while estimating how tall the rows will be after wrapping. Rendering uses the same idea directly so measurement and drawing stay aligned.

MemoriesSettingsView::current_setting266–278 ↗
fn current_setting(&self, setting: MemoriesSetting) -> bool

Purpose: Looks up the current on/off value for one specific memory setting. It is used when saving so the app receives the latest choices.

Data flow: It receives the setting to look for, scans the menu items, and returns the enabled value for the matching setting. If no matching setting is found, it returns false as a safe default.

Call relations: MemoriesSettingsView::save calls this for both “Use memories” and “Generate memories” before sending the update event to the rest of the application.

Call graph: called by 1 (save).

MemoriesSettingsView::open_reset_confirmation280–284 ↗
fn open_reset_confirmation(&mut self)

Purpose: Switches the popup from the normal settings screen to the reset confirmation screen. This prevents an accidental memory reset from a single key press.

Data flow: It creates a fresh scroll state, selects the first confirmation row, and stores that state as the active reset confirmation. After this, rendering and navigation use the confirmation list instead of the main list.

Call relations: MemoriesSettingsView::save calls this when the user presses Enter on the “Reset all memories” action. The next Enter either confirms the reset or returns, depending on the selected confirmation row.

Call graph: calls 1 internal fn (new); called by 1 (save).

MemoriesSettingsView::close_reset_confirmation286–289 ↗
fn close_reset_confirmation(&mut self)

Purpose: Leaves the reset confirmation screen and returns to the normal memories settings list. It places the selection back on the reset action row.

Data flow: It removes the confirmation state. Then it sets the normal list selection to the last item, which is the reset action if the menu has its expected rows.

Call relations: MemoriesSettingsView::save calls this when the user chooses “Go back” in the confirmation screen. MemoriesSettingsView::cancel also calls it when cancellation happens while confirmation is open.

Call graph: called by 2 (cancel, save).

MemoriesSettingsView::handle_key_event301–318 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Translates a keyboard event into an action in the popup. It is the main input dispatcher for moving, toggling, saving, and canceling.

Data flow: It receives one key event. It compares that event with the configured key bindings and with the Space key. Depending on the match, it moves the selection, toggles a setting, saves, cancels, or ignores the key.

Call relations: The bottom-pane framework calls this when the user presses a key while this view is active. It hands work to helpers such as MemoriesSettingsView::move_up, MemoriesSettingsView::toggle_selected, MemoriesSettingsView::save, and MemoriesSettingsView::cancel.

Call graph: calls 9 internal fn (cancel, jump_bottom, jump_top, move_down, move_up, page_down, page_up, save, toggle_selected).

MemoriesSettingsView::is_complete320–322 ↗
fn is_complete(&self) -> bool

Purpose: Tells the surrounding UI whether this popup is finished and can be closed. A popup becomes complete after saving, resetting, or canceling from the main screen.

Data flow: It reads the view’s complete flag and returns it unchanged.

Call relations: The bottom-pane framework can call this after input handling to decide whether to remove the view. Other functions in this file, especially MemoriesSettingsView::save and MemoriesSettingsView::cancel, are the ones that set the flag.

MemoriesSettingsView::on_ctrl_c324–327 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Defines what happens when the user presses Ctrl-C while this popup is open. It treats Ctrl-C as a cancel action and says the key was handled.

Data flow: It calls the normal cancel logic, which either backs out of reset confirmation or completes the popup. It then returns a value saying the cancellation event has been handled.

Call relations: The bottom-pane framework calls this for Ctrl-C. It delegates to MemoriesSettingsView::cancel so Ctrl-C behaves like the usual cancel key.

Call graph: calls 1 internal fn (cancel).

MemoriesSettingsView::save331–357 ↗
fn save(&mut self)

Purpose: Applies the user’s current choice. It either opens the reset confirmation, confirms a reset, goes back from confirmation, or sends updated memory settings to the app.

Data flow: If the reset confirmation screen is active, it reads the selected confirmation row. Choosing the reset row sends a ResetMemories event and marks the popup complete; choosing “Go back” closes confirmation. If the normal screen is active and the reset action is selected, it opens confirmation. Otherwise, it reads both memory setting values, sends an UpdateMemorySettings event, and marks the popup complete.

Call relations: MemoriesSettingsView::handle_key_event calls this when the accept key is pressed. It uses MemoriesSettingsView::current_setting to gather checkbox values, MemoriesSettingsView::open_reset_confirmation and MemoriesSettingsView::close_reset_confirmation for the reset flow, and the app-event sender to notify the wider application.

Call graph: calls 4 internal fn (send, close_reset_confirmation, current_setting, open_reset_confirmation); called by 1 (handle_key_event); 1 external calls (unreachable!).

MemoriesSettingsView::cancel359–365 ↗
fn cancel(&mut self)

Purpose: Backs out of the current popup state. If the user is confirming a reset, cancel returns to the settings screen; otherwise it closes the popup.

Data flow: It checks whether reset confirmation is active. If so, it removes that confirmation state. If not, it marks the whole view as complete.

Call relations: MemoriesSettingsView::handle_key_event calls this for the cancel key, and MemoriesSettingsView::on_ctrl_c calls it for Ctrl-C. It uses MemoriesSettingsView::close_reset_confirmation when only the confirmation screen should close.

Call graph: calls 1 internal fn (close_reset_confirmation); called by 2 (handle_key_event, on_ctrl_c).

MemoriesSettingsView::render369–434 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the memories settings popup into the terminal screen buffer. It lays out the header, menu rows, documentation link, and footer hint.

Data flow: It receives a rectangular screen area and a mutable terminal buffer. If the area is empty, it does nothing. Otherwise, it splits the space into content and footer regions, chooses the right header, builds the display rows, measures how tall they need to be, draws the rows, optionally draws the documentation link, marks that link as clickable when supported, and draws the footer hint.

Call relations: The rendering system calls this whenever the popup needs to be displayed. It pulls together helpers such as MemoriesSettingsView::settings_header, MemoriesSettingsView::reset_confirmation_header, MemoriesSettingsView::build_rows, MemoriesSettingsView::active_state, and MemoriesSettingsView::footer_hint, then hands the row drawing to shared popup rendering utilities.

Call graph: calls 10 internal fn (active_state, build_rows, footer_hint, reset_confirmation_header, settings_header, measure_rows_height, render_rows, vh, user_message_style, mark_url_hyperlink); 7 external calls (default, Fill, Length, Max, vertical, clone, rows_width).

MemoriesSettingsView::desired_height436–459 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Estimates how tall the popup wants to be for a given width. This helps the surrounding layout reserve enough space before drawing.

Data flow: It receives the available width. It chooses the correct header, builds the current rows, calculates the row width, measures how much height wrapped rows need, adds room for spacing, footer, and the documentation link when present, and returns the final height.

Call relations: The layout system calls this before rendering. It uses the same building blocks as MemoriesSettingsView::render, including the active header, current rows, active scroll state, and row-height measurement, so the size estimate matches what will be drawn.

Call graph: calls 5 internal fn (active_state, build_rows, reset_confirmation_header, settings_header, measure_rows_height); 1 external calls (rows_width).

memories_settings_hint_line462–470 ↗
fn memories_settings_hint_line() -> Line<'static>

Purpose: Builds the normal footer help text for the memories settings screen. It tells the user that Space toggles a setting and Enter saves or selects.

Data flow: It creates a line of styled text from plain words and key labels. The returned line is ready to render in the popup footer.

Call relations: MemoriesSettingsView::footer_hint calls this when the reset confirmation screen is not open. In confirmation mode, the footer uses the shared standard popup hint instead.

Call graph: called by 1 (footer_hint); 2 external calls (from, vec!).

tui/src/bottom_pane/hooks_browser_view.rssource ↗
domain_logicactive while the hooks browser bottom-pane popup is open

This file is the hooks browser for the text user interface. It gives users a safe way to see which hooks are installed, which ones are active, and which ones need review before they are allowed to run. Without this view, users would have less visibility into commands that can run automatically during Codex’s work, and they would have no direct terminal UI for trusting or disabling them.

The view has two pages. The first page is an overview grouped by event, like a dashboard that says, “For this kind of moment, here is how many hooks exist, how many can run, and how many need review.” The second page shows the individual hooks for one event, with details such as where the hook came from, its command, timeout, and trust state.

The file also listens for keyboard input. Arrow-like movement changes the selected row, Enter opens an event or toggles a hook, Space toggles a hook, t trusts one or all review-needed hooks, and Escape goes back or closes the browser. When the user changes trust or enabled state, the view updates its local copy immediately and sends an app event so the rest of the program can save the change.

Rendering uses Ratatui, a terminal drawing library, to lay out headers, tables, details, and footer hints.

Function details77
HooksBrowserView::new60–76 ↗
fn new(
        hooks: Vec<HookMetadata>,
        warnings: Vec<String>,
        errors: Vec<codex_app_server_protocol::HookErrorInfo>,
        app_event_tx: AppEventSender,
    ) -> Self

Purpose: Creates a hooks browser view for tests from separate lists of hooks, warnings, and errors. It is a convenience wrapper so tests do not need to build the full server entry object by hand.

Data flow: It receives hook records, warning text, error records, and an app-event sender. It wraps them into a hooks list entry with an empty working directory, uses the default list key bindings, and returns a ready-to-use view.

Call relations: Test helpers and many test cases call this to build a browser quickly. It hands the real setup work to HooksBrowserView::from_entry, after getting default keymap settings.

Call graph: calls 1 internal fn (defaults); called by 18 (assert_unmanaged_toggle_key, managed_hooks_count_as_active, renders_command_details_with_three_line_cap, renders_empty_handler_browser_message, renders_event_browser_with_issues, renders_event_browser_with_review_column_when_needed, renders_review_needed_handler, renders_scrolled_handler_window, renders_selected_managed_handler, renders_untrusted_enabled_handler_as_inactive (+8 more)); 2 external calls (from_entry, new).

HooksBrowserView::from_entry78–101 ↗
fn from_entry(
        mut entry: HooksListEntry,
        app_event_tx: AppEventSender,
        keymap: ListKeymap,
    ) -> Self

Purpose: Creates the real hooks browser view from a complete hooks list entry. This is the constructor used by the app when opening the browser.

Data flow: It receives the current hooks data, an app-event sender, and list key bindings. It sorts hooks by display order, starts on the event overview page, initializes scrolling, and selects the first event that needs review if one exists; otherwise it selects the first event.

Call relations: The app’s open_hooks_browser flow calls this when the user opens the hooks browser. Test-only HooksBrowserView::new also delegates to it.

Call graph: calls 1 internal fn (new); called by 1 (open_hooks_browser).

HooksBrowserView::event_rows103–133 ↗
fn event_rows(&self) -> Vec<EventRow>

Purpose: Builds the summary rows for the overview page, one row for each possible hook event. Each row counts installed hooks, active hooks, and hooks needing review.

Data flow: It reads the view’s hook list and the known set of hook event names. For every event name, it counts matching hooks, active trusted hooks, and review-needed hooks, then returns a list of EventRow summaries.

Call relations: event_table_lines calls this when drawing the overview table. It uses the shared hook event list so the overview shows events even when no hooks are installed for them.

Call graph: called by 1 (event_table_lines); 1 external calls (iter).

HooksBrowserView::handlers_for_event135–140 ↗
fn handlers_for_event(&self, event_name: HookEventName) -> impl Iterator<Item = &HookMetadata>

Purpose: Finds the hooks that belong to one specific lifecycle event. It is the filter used whenever the handler page needs only the hooks for the selected event.

Data flow: It receives an event name and reads the stored hook list. It yields only hooks whose event name matches the requested event.

Call relations: handler_row_lines, page_len, and review_needed_count use it to count or display hooks on the handler page.

Call graph: called by 3 (handler_row_lines, page_len, review_needed_count).

HooksBrowserView::selected_event142–147 ↗
fn selected_event(&self) -> Option<HookEventName>

Purpose: Translates the currently selected overview row into the hook event it represents. It answers, “Which event is highlighted right now?”

Data flow: It reads the selected row index from scroll state. If there is a selection and it matches a known event position, it returns that event name; otherwise it returns nothing.

Call relations: open_selected_event calls this before switching from the overview page to the per-event hook list.

Call graph: called by 1 (open_selected_event).

HooksBrowserView::selected_hook_index149–158 ↗
fn selected_hook_index(&self, event_name: HookEventName) -> Option<usize>

Purpose: Finds the real position in the full hook list for the hook selected on a handler page. This matters because the visible list is filtered by event, while the stored list contains all hooks.

Data flow: It reads the selected visible row and the full hook list. It filters hooks to the chosen event, maps the visible selection back to the original hook index, and returns that index if found.

Call relations: selected_hook, toggle_selected_hook, and trust_selected_hook call this before reading or changing the selected hook.

Call graph: called by 3 (selected_hook, toggle_selected_hook, trust_selected_hook).

HooksBrowserView::selected_hook160–163 ↗
fn selected_hook(&self, event_name: HookEventName) -> Option<&HookMetadata>

Purpose: Returns the selected hook record for a specific event, if there is one. It is used when the UI needs to show details or decide which footer hint to display.

Data flow: It receives an event name, asks selected_hook_index for the matching position in the full list, then reads that hook from storage and returns it by reference.

Call relations: detail_lines uses it to show the selected hook’s information. render_footer uses it to decide whether to show toggle, trust, managed-hook, or back-only instructions.

Call graph: calls 1 internal fn (selected_hook_index); called by 2 (detail_lines, render_footer).

HooksBrowserView::move_up165–169 ↗
fn move_up(&mut self)

Purpose: Moves the selection one row upward, wrapping around when appropriate. It also keeps the selected row visible inside the scroll window.

Data flow: It calculates the current page length and maximum visible rows. It updates the scroll state to move upward and then adjusts the top scroll position if needed.

Call relations: handle_key_event calls this when the configured move-up key is pressed.

Call graph: calls 4 internal fn (max_visible_rows, page_len, ensure_visible, move_up_wrap); called by 1 (handle_key_event).

HooksBrowserView::move_down171–175 ↗
fn move_down(&mut self)

Purpose: Moves the selection one row downward, wrapping around when appropriate. It keeps scrolling aligned so the highlighted row stays on screen.

Data flow: It calculates page size and visible capacity, changes the selected row through scroll state, and updates the visible window.

Call relations: handle_key_event calls this when the configured move-down key is pressed.

Call graph: calls 4 internal fn (max_visible_rows, page_len, ensure_visible, move_down_wrap); called by 1 (handle_key_event).

HooksBrowserView::page_up177–180 ↗
fn page_up(&mut self)

Purpose: Moves the selection upward by roughly one visible page. It is a faster navigation action for longer hook lists.

Data flow: It reads the page length and visible row count, then asks scroll state to move up by a page without going outside the list.

Call relations: handle_key_event calls this when the configured page-up key is pressed.

Call graph: calls 3 internal fn (max_visible_rows, page_len, page_up_clamped); called by 1 (handle_key_event).

HooksBrowserView::page_down182–185 ↗
fn page_down(&mut self)

Purpose: Moves the selection downward by roughly one visible page. It helps users browse long handler lists without pressing down repeatedly.

Data flow: It reads the total row count and visible row count, then asks scroll state to move down by a page while staying within bounds.

Call relations: handle_key_event calls this when the configured page-down key is pressed.

Call graph: calls 3 internal fn (max_visible_rows, page_len, page_down_clamped); called by 1 (handle_key_event).

HooksBrowserView::jump_top187–190 ↗
fn jump_top(&mut self)

Purpose: Moves the selection to the first row of the current page. This is the “go to top” navigation action.

Data flow: It reads the current page length and visible row count, then updates scroll state so the first row is selected and visible.

Call relations: handle_key_event calls this when the configured jump-to-top key is pressed.

Call graph: calls 3 internal fn (max_visible_rows, page_len, jump_top); called by 1 (handle_key_event).

HooksBrowserView::jump_bottom192–195 ↗
fn jump_bottom(&mut self)

Purpose: Moves the selection to the last row of the current page. This is the “go to bottom” navigation action.

Data flow: It reads the current page length and visible row count, then updates scroll state so the final row is selected and visible.

Call relations: handle_key_event calls this when the configured jump-to-bottom key is pressed.

Call graph: calls 3 internal fn (max_visible_rows, page_len, jump_bottom); called by 1 (handle_key_event).

HooksBrowserView::page_len197–202 ↗
fn page_len(&self) -> usize

Purpose: Reports how many selectable rows exist on the current page. On the overview page this is the number of hook event types; on a handler page it is the number of hooks for that event.

Data flow: It checks which page is active. It either counts all known event names or counts hooks filtered to the selected event, then returns that number.

Call relations: Navigation functions and open_selected_event use this to avoid selecting rows that do not exist.

Call graph: calls 1 internal fn (handlers_for_event); called by 8 (jump_bottom, jump_top, max_visible_rows, move_down, move_up, open_selected_event, page_down, page_up); 1 external calls (iter).

HooksBrowserView::max_visible_rows204–206 ↗
fn max_visible_rows(&self) -> usize

Purpose: Decides how many list rows should be visible before scrolling is needed. It caps the display at the shared popup row limit.

Data flow: It reads the current page length, treats an empty page as needing at least one display slot, and returns the smaller of that value and the maximum popup row count.

Call relations: Movement and paging functions call this so selection and scrolling match the amount of list space the UI can show.

Call graph: calls 1 internal fn (page_len); called by 6 (jump_bottom, jump_top, move_down, move_up, page_down, page_up).

HooksBrowserView::open_selected_event208–217 ↗
fn open_selected_event(&mut self)

Purpose: Switches from the event overview to the handler list for the highlighted event. This is what Enter does on the overview page.

Data flow: It asks which event is selected. If one exists, it changes the page to that event’s handler page, resets scrolling, and selects the first hook if there are hooks to show.

Call relations: handle_key_event calls this when the accept key is pressed on the event overview.

Call graph: calls 3 internal fn (page_len, selected_event, new); called by 1 (handle_key_event); 1 external calls (Handlers).

HooksBrowserView::toggle_selected_hook219–238 ↗
fn toggle_selected_hook(&mut self, event_name: HookEventName)

Purpose: Turns the selected hook on or off, but only when the hook is user-controllable and already trusted. Managed hooks and review-needed hooks are protected from toggling.

Data flow: It maps the visible selection to a stored hook, checks whether it is managed or needs review, flips its enabled flag if allowed, and sends an app event with the hook key and new enabled value.

Call relations: handle_key_event calls this when Enter or Space is pressed on a handler page. It sends AppEvent::SetHookEnabled so the wider app can persist the change.

Call graph: calls 3 internal fn (send, selected_hook_index, hook_needs_review); called by 1 (handle_key_event).

HooksBrowserView::trust_selected_hook240–256 ↗
fn trust_selected_hook(&mut self, event_name: HookEventName)

Purpose: Marks the selected review-needed hook as trusted. This lets a new or modified hook run in the future without also changing whether it is enabled.

Data flow: It finds the selected hook for the event. If that hook needs review, it changes its trust status locally to trusted and sends an app event containing the hook key and current hash.

Call relations: handle_key_event calls this when t is pressed on a handler page. It sends AppEvent::TrustHook so the trust decision can be recorded outside the UI.

Call graph: calls 3 internal fn (send, selected_hook_index, hook_needs_review); called by 1 (handle_key_event).

HooksBrowserView::trust_all_hooks258–274 ↗
fn trust_all_hooks(&mut self)

Purpose: Trusts every hook in the list that currently needs review. It is the bulk version of trusting one hook.

Data flow: It scans all stored hooks, changes each review-needed hook to trusted, collects the hook keys and hashes, and sends one app event if there was anything to update.

Call relations: handle_key_event calls this when t is pressed on the event overview page. It sends AppEvent::TrustHooks with all trust updates together.

Call graph: calls 2 internal fn (send, hook_needs_review); called by 1 (handle_key_event); 1 external calls (new).

HooksBrowserView::close276–278 ↗
fn close(&mut self)

Purpose: Marks the hooks browser as finished so the bottom pane can dismiss it. It does not save data by itself.

Data flow: It changes the view’s complete flag from false to true. Later, callers can read that flag through is_complete.

Call relations: handle_key_event calls this when Escape closes the overview page. on_ctrl_c also calls it when Ctrl-C is handled by this view.

Call graph: called by 2 (handle_key_event, on_ctrl_c).

HooksBrowserView::return_to_events280–293 ↗
fn return_to_events(&mut self)

Purpose: Goes back from a handler page to the event overview while keeping the same event highlighted. This makes Escape feel like a breadcrumb back button.

Data flow: It remembers the event for the current handler page, switches back to the overview page, resets scrolling, and selects the matching event row if possible.

Call relations: handle_key_event calls this when Escape is pressed on a handler page.

Call graph: calls 1 internal fn (new); called by 1 (handle_key_event).

HooksBrowserView::event_header_lines295–302 ↗
fn event_header_lines() -> Vec<Line<'static>>

Purpose: Builds the title and subtitle for the event overview page. These lines explain that the page is about lifecycle hooks from config and plugins.

Data flow: It creates two styled text lines: a bold title and a dim explanatory subtitle. It returns them for rendering.

Call relations: event_page_lines uses this as the first part of the overview page.

Call graph: 1 external calls (vec!).

HooksBrowserView::review_needed_total_count304–310 ↗
fn review_needed_total_count(&self) -> usize

Purpose: Counts how many hooks in the whole browser still need review. This drives warning messages and footer shortcuts.

Data flow: It reads all stored hooks, filters to those considered review-needed, and returns the count.

Call relations: event_page_lines uses the count for the overview warning. render_footer uses it to decide whether to show the “trust all” hint.

Call graph: called by 2 (event_page_lines, render_footer).

HooksBrowserView::handler_header_lines313–327 ↗
fn handler_header_lines(
        event_name: HookEventName,
        review_needed_count: usize,
    ) -> Vec<Line<'static>>

Purpose: Builds the heading for a handler page. It either gives normal toggle instructions or warns that some hooks need review.

Data flow: It receives an event name and review-needed count. It creates a bold title from the event label, then adds either a dim instruction line or a yellow warning message.

Call relations: desired_height uses it when calculating layout size, and render uses it when drawing a handler page. It relies on review_needed_message for the warning text.

Call graph: calls 1 internal fn (review_needed_message); 1 external calls (vec!).

HooksBrowserView::review_needed_count329–333 ↗
fn review_needed_count(&self, event_name: HookEventName) -> usize

Purpose: Counts review-needed hooks for one specific event. It is the per-event version of the total review count.

Data flow: It filters hooks to the requested event, then counts only the ones that need review.

Call relations: desired_height and render call it while preparing a handler page so the header can show the right warning.

Call graph: calls 1 internal fn (handlers_for_event); called by 2 (desired_height, render).

HooksBrowserView::event_table_lines336–388 ↗
fn event_table_lines(&self) -> Vec<Line<'static>>

Purpose: Builds the overview table shown on the event page. The table shows event names, installed counts, active counts, optional review counts, and short descriptions.

Data flow: It gets event summaries from event_rows, decides whether a review column is needed, builds a header row, then builds one styled row per event. The selected row gets the accent style, while less important counts and descriptions are dimmed.

Call relations: event_page_lines calls this after headers, warnings, and issue messages are prepared.

Call graph: calls 3 internal fn (event_rows, event_description, accent_style); called by 1 (event_page_lines); 5 external calls (from, from, new, format!, vec!).

HooksBrowserView::event_issue_lines390–409 ↗
fn event_issue_lines(&self) -> Vec<Line<'static>>

Purpose: Builds the warning and error section for the overview page. This tells the user about hook configuration problems that were found while loading hooks.

Data flow: It checks stored warnings and errors. If any exist, it creates an “Issues” heading, adds warning lines, and adds red error lines with file paths and messages.

Call relations: event_page_lines calls this and inserts the issue block only when there is something to show.

Call graph: called by 1 (event_page_lines); 1 external calls (new).

HooksBrowserView::event_page_lines412–429 ↗
fn event_page_lines(&self) -> Vec<Line<'static>>

Purpose: Assembles all text lines for the event overview page. It combines the title, review warning, load issues, and event summary table.

Data flow: It starts with header lines, adds spacing, adds a yellow review warning if needed, adds warning/error issue lines if present, and finally appends the event table.

Call relations: desired_height calls it to know how tall the overview needs to be. render calls it to draw the overview.

Call graph: calls 4 internal fn (event_issue_lines, event_table_lines, review_needed_total_count, review_needed_message); called by 2 (desired_height, render); 3 external calls (default, event_header_lines, format!).

HooksBrowserView::handler_row_lines432–469 ↗
fn handler_row_lines(&self, event_name: HookEventName, width: usize) -> Vec<Line<'static>>

Purpose: Builds the selectable rows for hooks under one event. Each row shows whether a hook is active, inactive, or awaiting review.

Data flow: It filters hooks by event, numbers them as “Hook 1”, “Hook 2”, and so on, adds a marker, notes whether a hook is new or modified, truncates long text to fit, and styles selected, review-needed, or managed rows differently.

Call relations: desired_height uses these rows to size the list area. render uses them to draw the scrollable handler list.

Call graph: calls 1 internal fn (handlers_for_event); called by 2 (desired_height, render).

HooksBrowserView::detail_lines471–497 ↗
fn detail_lines(&self, event_name: HookEventName, width: usize) -> Vec<Line<'static>>

Purpose: Builds the detail panel for the selected hook. This is where the user sees the event, matcher, source, command, timeout, and trust status.

Data flow: It asks for the selected hook. If none exists, it returns a dim empty-message line; otherwise it creates labeled lines, wrapping long fields and limiting the command field to a few lines.

Call relations: desired_height uses it to include detail height in layout calculations. render uses it below the handler list.

Call graph: calls 5 internal fn (selected_hook, detail_line, detail_source_value, detail_wrapped_lines, hook_trust_label); called by 2 (desired_height, render); 2 external calls (format!, vec!).

HooksBrowserView::handle_key_event563–604 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Responds to keyboard input for the hooks browser. It turns key presses into navigation, opening pages, toggling hooks, trusting hooks, going back, or closing.

Data flow: It receives a key event, compares it against configured list key bindings and direct keys like Space and t, then calls the matching action method. If the key is not relevant, it leaves the view unchanged.

Call relations: The bottom-pane framework calls this while the popup is active. It dispatches to movement methods, page switching, toggle/trust actions, and close/back behavior.

Call graph: calls 12 internal fn (close, jump_bottom, jump_top, move_down, move_up, open_selected_event, page_down, page_up, return_to_events, toggle_selected_hook (+2 more)).

HooksBrowserView::is_complete606–608 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the hooks browser has finished and should be dismissed. It is the public check for the complete flag.

Data flow: It reads the stored boolean flag and returns it unchanged.

Call relations: The bottom-pane framework can call this after input handling to know whether the view should remain open.

HooksBrowserView::on_ctrl_c610–613 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Handles Ctrl-C while the hooks browser is open. Instead of letting the key do something global, this view closes itself and says the cancellation was handled.

Data flow: It marks the view complete by calling close, then returns a handled cancellation result.

Call relations: The bottom-pane framework calls this for Ctrl-C. It uses close so Ctrl-C behaves like dismissing the popup.

Call graph: calls 1 internal fn (close).

HooksBrowserView::prefer_esc_to_handle_key_event615–617 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

Purpose: Tells the surrounding UI that Escape should be delivered to this view first. That allows Escape to go back from handler details before closing broader UI layers.

Data flow: It returns true every time.

Call relations: The bottom-pane framework reads this preference when routing Escape key presses.

HooksBrowserView::desired_height621–643 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how tall the popup wants to be for a given terminal width. This helps the surrounding layout reserve enough space for the current page.

Data flow: It receives a width, subtracts padding, counts the lines needed for the overview or handler page, includes list and detail space as needed, adds room for borders/footer, and returns a height capped to u16.

Call relations: Rendering tests call it before drawing into a buffer. The render system can use it to size the view before calling render.

Call graph: calls 4 internal fn (detail_lines, event_page_lines, handler_row_lines, review_needed_count); called by 2 (render_buffer, render_lines); 1 external calls (handler_header_lines).

HooksBrowserView::render645–695 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the hooks browser into the terminal buffer. It creates the visible popup surface, page content, scrollable handler list, detail panel, and footer.

Data flow: It receives a screen rectangle and buffer. If there is space, it splits the area into content and footer, builds the right lines for the current page, renders them, and returns after updating the buffer.

Call relations: The render system calls this whenever the popup needs to be painted. Test helpers also call it to snapshot the UI.

Call graph: calls 6 internal fn (detail_lines, event_page_lines, handler_row_lines, render_footer, review_needed_count, render_menu_surface); called by 2 (render_buffer, render_lines); 9 external calls (Fill, Length, vertical, default, from, new, is_empty, handler_header_lines, vec!).

hook_is_active698–704 ↗
fn hook_is_active(hook: &HookMetadata) -> bool

Purpose: Decides whether a hook should count as active. A hook is active only if it is enabled and its trust state allows it to run.

Data flow: It reads a hook’s enabled flag and trust status. It returns true for enabled hooks that are managed or trusted, and false otherwise.

Call relations: event_rows uses this logic so untrusted or modified hooks do not appear as active even if their enabled flag is true.

Call graph: 1 external calls (matches!).

review_needed_message706–712 ↗
fn review_needed_message(count: usize) -> Option<String>

Purpose: Creates a human-readable warning for hooks that need review. It handles the singular and plural wording.

Data flow: It receives a count. It returns no message for zero, a one-hook sentence for one, or a plural sentence for larger counts.

Call relations: event_page_lines and handler_header_lines call it to show review warnings in the overview and handler headers.

Call graph: called by 2 (event_page_lines, handler_header_lines); 1 external calls (format!).

hook_trust_label721–728 ↗
fn hook_trust_label(status: HookTrustStatus) -> &'static str

Purpose: Turns an internal trust status into text a user can understand. It explains whether a hook is managed, trusted, new, or modified.

Data flow: It receives a trust status and returns a fixed label string for that status.

Call relations: detail_lines uses this when building the selected hook’s detail panel.

Call graph: called by 1 (detail_lines).

event_label730–743 ↗
fn event_label(event_name: HookEventName) -> &'static str

Purpose: Turns a hook event name into the label shown in the UI. The labels match the event names users may see in configuration.

Data flow: It receives an event enum value and returns the corresponding display string.

Call relations: Display-building code in this file uses it for overview rows, handler headers, and hook detail lines.

event_description745–758 ↗
fn event_description(event_name: HookEventName) -> &'static str

Purpose: Provides a short plain-language description for each hook event. This helps users understand when hooks of that type run.

Data flow: It receives an event name and returns a short sentence such as “Before a tool executes” or “When a new session starts.”

Call relations: event_table_lines uses it for the Description column in the overview table.

Call graph: called by 1 (event_table_lines).

hook_title760–762 ↗
fn hook_title(idx: usize) -> String

Purpose: Creates a simple numbered title for a hook row, such as “Hook 1”. It avoids exposing long internal keys in the compact list.

Data flow: It receives a zero-based row index, adds one for user-friendly numbering, and returns the formatted title.

Call relations: handler_row_lines uses this when naming each visible hook row.

Call graph: 1 external calls (format!).

hook_source_summary764–773 ↗
fn hook_source_summary(hook: &HookMetadata) -> String

Purpose: Creates a short summary of where a hook came from. Plugin hooks include the plugin id when available; other hooks use a source label.

Data flow: It reads the hook’s source and optional plugin id. It returns text like “Plugin - name” or a config source label.

Call relations: detail_source_value calls this for plugin hooks before showing the source in the detail panel.

Call graph: calls 1 internal fn (config_source_label); called by 1 (detail_source_value).

detail_source_value775–790 ↗
fn detail_source_value(hook: &HookMetadata) -> String

Purpose: Builds the full source text shown in the selected hook’s details. It explains whether the hook came from a plugin, admin config, user config, project config, or another source.

Data flow: It reads the hook source, plugin id, and source path. For sources that should not show a path, it returns only a label; for file-based sources, it returns a label plus a formatted path.

Call relations: detail_lines calls this when creating the “Source” detail row.

Call graph: calls 2 internal fn (config_source_label, hook_source_summary); called by 1 (detail_lines); 1 external calls (format!).

config_source_label792–806 ↗
fn config_source_label(source: HookSource) -> &'static str

Purpose: Maps a hook source type to a user-facing label. This hides internal source names behind terms like “Admin config” or “User config.”

Data flow: It receives a source enum value and returns the matching label. Plugin sources are not expected here because plugin hooks are handled separately.

Call relations: hook_source_summary and detail_source_value call this while building source text.

Call graph: called by 2 (detail_source_value, hook_source_summary); 1 external calls (unreachable!).

detail_line808–810 ↗
fn detail_line(label: &str, value: &str) -> Line<'static>

Purpose: Builds one labeled detail line for the hook detail panel. It keeps labels aligned so the panel is easy to scan.

Data flow: It receives a label and value, pads the label to a fixed width, dims the value, and returns a styled terminal text line.

Call relations: detail_lines uses it for short fields such as Event, Timeout, and Trust.

Call graph: called by 1 (detail_lines); 2 external calls (from, vec!).

detail_wrapped_lines812–854 ↗
fn detail_wrapped_lines(
    label: &str,
    value: &str,
    width: usize,
    max_lines: Option<usize>,
) -> Vec<Line<'static>>

Purpose: Builds one or more aligned detail lines for long values. It wraps text to fit the available width and can cap the number of lines with an ellipsis.

Data flow: It receives a label, value, available width, and optional maximum line count. It wraps the value under the label, dims the text, truncates extra lines if needed, and returns the resulting lines.

Call relations: detail_lines uses it for fields that can be long, such as matcher, source, and command.

Call graph: calls 1 internal fn (truncate_line_with_ellipsis_if_overflow); called by 1 (detail_lines); 4 external calls (from, format!, wrap, vec!).

tests::render_lines881–906 ↗
fn render_lines(view: &HooksBrowserView, width: u16) -> String

Purpose: Renders a hooks browser view into plain text for snapshot tests. This makes visual output easy to compare in tests.

Data flow: It receives a view and width, asks the view for its desired height, renders it into a test buffer, normalizes platform-specific paths, and returns the screen contents as a newline-separated string.

Call relations: Many rendering tests call this before comparing the output with stored snapshots.

Call graph: calls 2 internal fn (desired_height, render); 2 external calls (empty, new).

tests::render_buffer908–914 ↗
fn render_buffer(view: &HooksBrowserView, width: u16) -> Buffer

Purpose: Renders a hooks browser view into a Ratatui buffer for tests that inspect styling. It is used when text alone is not enough.

Data flow: It receives a view and width, calculates height, creates an empty buffer, renders the view, and returns the filled buffer.

Call relations: selected_event_rows_use_the_shared_accent_style calls this to check the selected row’s style.

Call graph: calls 2 internal fn (desired_height, render); 2 external calls (empty, new).

tests::hook917–949 ↗
fn hook(
        key: &str,
        event_name: HookEventName,
        source: HookSource,
        plugin_id: Option<&str>,
        command: &str,
        enabled: bool,
        is_managed: bool,

Purpose: Creates a realistic test hook record with common defaults. Tests use it to avoid repeating all hook metadata fields.

Data flow: It receives key details such as key, event, source, command, enabled state, managed state, and order. It fills in standard matcher, timeout, path, hash, and trust status, then returns a HookMetadata value.

Call relations: Most hook-browser tests call this when setting up sample hook data.

Call graph: 1 external calls (test_path_buf).

tests::view951–990 ↗
fn view() -> HooksBrowserView

Purpose: Builds a standard sample hooks browser used by many tests. The sample includes plugin, user, and managed hooks.

Data flow: It creates a test app-event channel, builds several hooks with tests::hook, and returns a new browser view with no warnings or errors.

Call relations: Snapshot and interaction tests call this when they need a representative hooks browser state.

Call graph: calls 2 internal fn (new, new); 2 external calls (new, vec!).

tests::renders_event_browser993–996 ↗
fn renders_event_browser()

Purpose: Checks that the default event overview renders as expected. It protects the main overview layout from accidental changes.

Data flow: It builds the standard test view, renders it to text at a fixed width, and compares it with a saved snapshot.

Call relations: This test uses tests::view and tests::render_lines to exercise desired_height and render together.

Call graph: 2 external calls (assert_snapshot!, view).

tests::selected_event_rows_use_the_shared_accent_style999–1016 ↗
fn selected_event_rows_use_the_shared_accent_style()

Purpose: Verifies that the selected event row uses the shared accent style. This keeps the hooks browser visually consistent with the rest of the UI.

Data flow: It renders the standard view into a buffer, searches for a selected cell with the expected foreground color and bold modifier, and asserts the style matches.

Call relations: It uses tests::render_buffer and compares against accent_style.

Call graph: calls 1 internal fn (accent_style); 3 external calls (assert_eq!, render_buffer, view).

tests::renders_event_browser_with_review_column_when_needed1019–1053 ↗
fn renders_event_browser_with_review_column_when_needed()

Purpose: Checks that the overview adds a Review column when any hook needs review. It also checks that the review count is highlighted.

Data flow: It creates an untrusted hook, builds a view, renders the overview snapshot, then inspects the review-count cell style.

Call relations: This test drives event_table_lines through a view built by HooksBrowserView::new.

Call graph: calls 2 internal fn (new, new); 6 external calls (new, assert!, assert_eq!, assert_snapshot!, hook, vec!).

tests::renders_event_browser_with_issues1056–1072 ↗
fn renders_event_browser_with_issues()

Purpose: Checks that warnings and errors from hook loading appear on the overview page. This protects the user-facing diagnostics section.

Data flow: It builds a view with no hooks but with one warning and one error, renders it, and compares the text with a snapshot.

Call relations: It exercises event_issue_lines through the normal render path.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_snapshot!, vec!).

tests::renders_handler_browser_with_details1075–1079 ↗
fn renders_handler_browser_with_details()

Purpose: Checks that opening an event shows the handler list and selected hook details. It protects the main second-page layout.

Data flow: It builds the standard view, sends Enter to open the selected event, renders the result, and compares it with a snapshot.

Call relations: It exercises handle_key_event, open_selected_event, and the handler-page render path.

Call graph: 3 external calls (from, assert_snapshot!, view).

tests::renders_untrusted_enabled_handler_as_inactive1082–1107 ↗
fn renders_untrusted_enabled_handler_as_inactive()

Purpose: Verifies that an enabled but untrusted hook is shown as not active. This is important because unreviewed hooks must not appear ready to run.

Data flow: It creates an enabled hook with untrusted status, opens its handler page, renders it, and compares with a snapshot.

Call relations: It exercises hook_is_active, handler_row_lines, and handler rendering through normal input.

Call graph: calls 2 internal fn (new, new); 5 external calls (from, new, assert_snapshot!, hook, vec!).

tests::review_needed_handler_rows_use_warning_color1110–1149 ↗
fn review_needed_handler_rows_use_warning_color()

Purpose: Checks that review-needed handler rows are colored as warnings. This helps users notice hooks that need attention.

Data flow: It builds a view with one trusted and one untrusted hook, opens the handler page, reads the untrusted row’s style, and asserts it is yellow.

Call relations: It directly checks output from handler_row_lines after navigation through handle_key_event.

Call graph: calls 2 internal fn (new, new); 5 external calls (from, new, assert_eq!, hook, vec!).

tests::review_needed_handler_header_uses_warning_color1152–1163 ↗
fn review_needed_handler_header_uses_warning_color()

Purpose: Checks that a handler page header warning is yellow when review is needed. This protects the warning styling above the list.

Data flow: It calls handler_header_lines with a review-needed count of one and asserts the second line is yellow.

Call relations: It tests handler_header_lines and its use of review_needed_message without rendering a full view.

Call graph: 1 external calls (assert_eq!).

tests::renders_managed_handler_without_toggle_hint1166–1174 ↗
fn renders_managed_handler_without_toggle_hint()

Purpose: Checks that managed hooks do not show a toggle instruction. Managed hooks are controlled by administrators and should not appear user-editable.

Data flow: It builds the standard view, selects the managed event, opens it, renders the page, and compares with a snapshot.

Call relations: It exercises render_footer, which chooses the managed-hook footer message.

Call graph: 3 external calls (from, assert_snapshot!, view).

tests::renders_selected_managed_handler1177–1212 ↗
fn renders_selected_managed_handler()

Purpose: Checks rendering when a managed hook row is selected. It ensures managed rows still show selection clearly while remaining distinct from editable hooks.

Data flow: It builds a view with two managed hooks, opens the handler page, moves selection down, renders, and compares with a snapshot.

Call relations: It exercises selection movement and managed-hook styling in handler_row_lines.

Call graph: calls 2 internal fn (new, new); 4 external calls (from, new, assert_snapshot!, vec!).

tests::renders_scrolled_handler_window1215–1241 ↗
fn renders_scrolled_handler_window()

Purpose: Checks that long handler lists scroll correctly. This protects the visible-window behavior when there are more hooks than popup rows.

Data flow: It creates one more hook than the maximum visible row count, opens the handler page, moves down repeatedly, renders, and compares with a snapshot.

Call relations: It exercises move_down, scroll state updates, and the handler-list rendering window.

Call graph: calls 2 internal fn (new, new); 3 external calls (from, new, assert_snapshot!).

tests::renders_command_details_with_three_line_cap1244–1268 ↗
fn renders_command_details_with_three_line_cap()

Purpose: Checks that very long command details are capped to three lines. This prevents one command from taking over the popup.

Data flow: It creates a hook with a long command and narrow render width, opens the handler page, renders, and compares with a snapshot.

Call relations: It exercises detail_wrapped_lines through detail_lines and the normal render path.

Call graph: calls 2 internal fn (new, new); 6 external calls (from, new, assert_snapshot!, test_path_buf, hook, vec!).

tests::renders_empty_handler_browser_message1271–1285 ↗
fn renders_empty_handler_browser_message()

Purpose: Checks the message shown when an event has no hooks. Empty pages should still tell the user what happened.

Data flow: It builds a view with no hooks, selects an event, opens the handler page, renders it, and compares with a snapshot.

Call relations: It exercises the empty-handler branch in render.

Call graph: calls 2 internal fn (new, new); 3 external calls (from, new, assert_snapshot!).

tests::managed_hooks_count_as_active1288–1314 ↗
fn managed_hooks_count_as_active()

Purpose: Verifies that managed trusted hooks count as active. Managed hooks are always controlled externally but still count as runnable when enabled.

Data flow: It builds a view with one managed hook, asks for event rows, finds the matching event row, and asserts installed and active counts.

Call relations: It directly exercises event_rows and hook_is_active.

Call graph: calls 2 internal fn (new, new); 3 external calls (new, assert_eq!, vec!).

tests::review_needed_hooks_are_not_active1317–1346 ↗
fn review_needed_hooks_are_not_active()

Purpose: Verifies that review-needed hooks do not count as active even when enabled. This protects the safety model for new or modified hooks.

Data flow: It builds an enabled untrusted hook, gets event rows, finds the relevant row, and checks installed, active, and review-needed counts.

Call relations: It directly exercises event_rows, hook_is_active, and review-needed counting.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_eq!, hook, vec!).

tests::review_needed_event_is_selected_by_default1349–1373 ↗
fn review_needed_event_is_selected_by_default()

Purpose: Checks that the overview initially highlights an event with a hook needing review. This guides users to the most urgent item first.

Data flow: It builds a view with an untrusted hook on the permission event and asserts that selected_event returns that event.

Call relations: It tests the initial-selection logic in HooksBrowserView::from_entry through the test constructor.

Call graph: calls 2 internal fn (new, new); 4 external calls (new, assert_eq!, hook, vec!).

tests::renders_review_needed_handler1376–1401 ↗
fn renders_review_needed_handler()

Purpose: Checks how a review-needed hook is rendered on the handler page. It protects the warning text, row marker, and trust-related hints.

Data flow: It creates an untrusted hook, opens its handler page, renders the result, and compares with a snapshot.

Call relations: It exercises handler_header_lines, handler_row_lines, detail_lines, and render_footer through normal navigation.

Call graph: calls 2 internal fn (new, new); 5 external calls (from, new, assert_snapshot!, hook, vec!).

tests::assert_unmanaged_toggle_key1403–1430 ↗
fn assert_unmanaged_toggle_key(key_code: KeyCode)

Purpose: Shared test helper that verifies a key toggles an unmanaged trusted hook. It is used to test both Enter and Space behavior.

Data flow: It builds a view with one editable hook, opens its handler page, sends the chosen key, receives the emitted app event, and asserts the hook was disabled.

Call relations: tests::toggle_keys_toggle_unmanaged_handler calls this for each toggle key. It exercises toggle_selected_hook and event sending.

Call graph: calls 2 internal fn (new, new); 6 external calls (from, new, assert!, assert_eq!, panic!, vec!).

tests::toggle_keys_toggle_unmanaged_handler1433–1437 ↗
fn toggle_keys_toggle_unmanaged_handler()

Purpose: Checks that both Space and Enter toggle an unmanaged hook on the handler page. This protects the intended keyboard shortcuts.

Data flow: It loops over the two key codes and calls the shared assertion helper for each one.

Call relations: It delegates the full setup and event assertion to tests::assert_unmanaged_toggle_key.

Call graph: 2 external calls (Char, assert_unmanaged_toggle_key).

tests::space_does_not_toggle_managed_handler1440–1461 ↗
fn space_does_not_toggle_managed_handler()

Purpose: Verifies that Space cannot toggle a managed hook. This protects administrator-controlled hooks from user changes.

Data flow: It builds a view with one managed hook, opens the handler page, sends Space, and asserts no app event was sent.

Call relations: It exercises the guard inside toggle_selected_hook through normal key handling.

Call graph: calls 2 internal fn (new, new); 5 external calls (Char, from, new, assert!, vec!).

tests::trust_key_trusts_review_needed_handler_without_changing_enablement1464–1497 ↗
fn trust_key_trusts_review_needed_handler_without_changing_enablement()

Purpose: Checks that pressing t trusts a selected untrusted hook without turning it on. Trust and enabled state are separate decisions.

Data flow: It creates a disabled untrusted hook, opens its handler page, presses t, receives the trust event, and checks the hook key and hash.

Call relations: It exercises trust_selected_hook and the AppEvent::TrustHook path.

Call graph: calls 2 internal fn (new, new); 7 external calls (Char, from, new, assert_eq!, panic!, hook, vec!).

tests::trust_key_preserves_disabled_modified_handler1500–1536 ↗
fn trust_key_preserves_disabled_modified_handler()

Purpose: Checks that a modified disabled hook becomes trusted but stays disabled. This matters when a user trusts a changed hook but does not want it enabled.

Data flow: It creates a disabled modified hook, opens the handler page, presses t, checks the stored hook state, and verifies the emitted trust event.

Call relations: It exercises the modified-hook branch of trust_selected_hook.

Call graph: calls 2 internal fn (new, new); 8 external calls (Char, from, new, assert!, assert_eq!, panic!, hook, vec!).

tests::trust_key_on_event_page_trusts_all_review_needed_hooks1539–1614 ↗
fn trust_key_on_event_page_trusts_all_review_needed_hooks()

Purpose: Checks that pressing t on the overview trusts all review-needed hooks at once. It also ensures already trusted hooks are not included in the update list.

Data flow: It creates untrusted, modified, and trusted hooks, presses t on the overview page, checks all local trust states, and verifies the bulk trust event contains only the needed updates.

Call relations: It exercises trust_all_hooks through handle_key_event.

Call graph: calls 2 internal fn (new, new); 8 external calls (Char, from, new, assert!, assert_eq!, panic!, hook, vec!).

tests::escape_returns_to_the_selected_event1617–1628 ↗
fn escape_returns_to_the_selected_event()

Purpose: Checks that Escape from a handler page returns to the overview with the same event selected. This protects the back-navigation experience.

Data flow: It builds the standard view, moves to another event, opens it, presses Escape, then asserts the page and selected event are restored correctly.

Call relations: It exercises return_to_events through handle_key_event.

Call graph: 3 external calls (from, assert_eq!, view).

tests::esc_routes_through_the_view1631–1633 ↗
fn esc_routes_through_the_view()

Purpose: Checks that the hooks browser asks to receive Escape directly. This ensures its own back/close behavior gets priority.

Data flow: It creates the standard view and asserts prefer_esc_to_handle_key_event returns true.

Call relations: It directly tests the Escape-routing preference used by the bottom-pane framework.

Call graph: 1 external calls (assert!).

Modal entry and feedback views

This group contains the smaller bottom-pane text-entry and feedback-oriented modal views used for focused user prompts and note capture.

tui/src/bottom_pane/custom_prompt_view.rssource ↗
domain_logicactive while an interactive bottom-pane custom prompt is open

This file is a self-contained view for collecting free-form text in the terminal user interface. Think of it like a small notepad that appears at the bottom of the screen: it has a title, an optional context line, a text box, placeholder text when empty, and a hint line telling the user what keys do.

The main type is CustomPromptView. It owns a TextArea, which stores and edits the user's text, and a small amount of state needed to draw the cursor correctly. It also stores a callback called when the user presses Enter on non-empty text. That callback lets the rest of the app decide what to do with the submitted prompt.

The file also pays special attention to pasted text. Terminals sometimes deliver a paste as a very fast stream of normal key presses. Without extra care, pressing or pasting Enter could accidentally submit the prompt instead of inserting a new line. PasteBurst acts like a short “this is probably a paste” timer, so newlines inside pasted content are inserted into the text box rather than treated as submission.

Cancellation is simple: Escape or Ctrl-C marks the view as cancelled. Rendering is also careful about limited space, using a gutter marker, clearing blank areas, wrapping the text area height, and reporting the cursor position so the terminal can show the caret in the right place.

Function details12
CustomPromptView::new46–69 ↗
fn new(
        title: String,
        placeholder: String,
        initial_text: String,
        context_label: Option<String>,
        on_submit: PromptSubmitted,
    ) -> Self

Purpose: Creates a new custom prompt view with its title, placeholder text, optional starting text, optional context label, and the action to run when the user submits. This is used whenever the app needs to ask the user for a short custom piece of text.

Data flow: It receives display text, initial input, an optional context label, and a submit callback. It builds a fresh text area, fills it with the initial text if there is any, moves the cursor to the end, and creates default drawing and paste-tracking state. The result is a ready-to-use CustomPromptView.

Call relations: Higher-level flows such as custom prompt creation, goal editing, rename prompting, marketplace add prompting, and custom review prompting call this when they need a reusable text-entry popup. It sets up the TextArea and default state that later input, rendering, and completion checks rely on.

Call graph: calls 1 internal fn (new); called by 5 (custom_prompt_view, show_goal_edit_prompt, show_rename_prompt, open_marketplace_add_prompt, show_review_custom_prompt); 3 external calls (new, default, default).

CustomPromptView::handle_key_event_at71–125 ↗
fn handle_key_event_at(&mut self, key_event: KeyEvent, now: Instant)

Purpose: Interprets one keyboard event for the prompt. It decides whether the key means cancel, submit, insert text, insert a newline, or continue a likely paste operation.

Data flow: It receives a key event and a timestamp. Escape becomes cancellation. Plain Enter submits trimmed non-empty text, unless the recent input looks like a paste, in which case Enter is inserted as a newline. Normal printable characters and Tab are passed into the text area, while the paste tracker is updated. Other keys are also passed to the text area, and explicit paste tracking is cleared. The prompt's text, paste state, and completion state may change.

Call relations: The public handle_key_event method calls this with the current time. This split lets tests or other code provide a controlled time. Inside, it hands editing work to TextArea, cancellation to on_ctrl_c, and paste timing decisions to PasteBurst.

Call graph: calls 10 internal fn (on_ctrl_c, clear_after_explicit_paste, direct_insert_newline_should_insert, extend_window, on_plain_char_no_hold, allows_paste_burst, input, insert_str, text, has_ctrl_or_alt); called by 1 (handle_key_event).

CustomPromptView::handle_key_event129–131 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Handles a real keyboard event during normal use. It is the standard entry point required by the bottom-pane view interface.

Data flow: It receives a key event from the terminal input loop, reads the current clock time, and forwards both to handle_key_event_at. Any resulting text edits, submission, cancellation, or paste-state changes happen there.

Call relations: The bottom-pane system calls this when the prompt is active and a key is pressed. It delegates to handle_key_event_at so the detailed key behavior stays in one place.

Call graph: calls 1 internal fn (handle_key_event_at); 1 external calls (now).

CustomPromptView::on_ctrl_c133–136 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Cancels the prompt and tells the caller that the cancellation was handled here. This is used for Ctrl-C behavior and also for Escape in this view.

Data flow: It does not need outside input beyond the view itself. It changes the view's completion state to Cancelled and returns a CancellationEvent::Handled value. After this, the prompt is considered finished.

Call relations: handle_key_event_at calls this when Escape is pressed. The broader bottom-pane system can also call it through the BottomPaneView interface when Ctrl-C is received.

Call graph: called by 1 (handle_key_event_at).

CustomPromptView::is_complete138–140 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether this prompt is done, either because the user submitted text or because they cancelled. The rest of the interface uses this to know when the popup can be closed.

Data flow: It reads the view's completion field. If that field contains an accepted or cancelled result, it returns true; otherwise it returns false. It does not change anything.

Call relations: The bottom-pane controller can ask this after input events. It depends on handle_key_event_at and on_ctrl_c setting the completion state when the user accepts or cancels.

CustomPromptView::completion142–144 ↗
fn completion(&self) -> Option<ViewCompletion>

Purpose: Returns the final outcome of the prompt, if there is one. The caller can use this to distinguish between accepted text and cancellation.

Data flow: It reads the stored completion value and returns it as-is. If the prompt is still open, the result is empty. It does not modify the view.

Call relations: The surrounding bottom-pane flow calls this after is_complete or when it needs the current outcome. The value it returns is set by submission inside handle_key_event_at or cancellation inside on_ctrl_c.

CustomPromptView::handle_paste146–153 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Adds explicitly pasted text into the prompt. This is for paste events that the terminal reports as a paste rather than as many separate key presses.

Data flow: It receives a pasted string. If the string is empty, it returns false and changes nothing. Otherwise it inserts the pasted text into the text area, clears the paste-burst tracker because this paste was already recognized directly, and returns true.

Call relations: The bottom-pane input system calls this when it receives a paste event. It hands the actual insertion to TextArea and resets PasteBurst so later key handling does not confuse an explicit paste with a guessed paste burst.

Call graph: calls 2 internal fn (clear_after_explicit_paste, insert_str).

CustomPromptView::desired_height161–164 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows this prompt would like to occupy. This helps the layout system reserve enough room for the title, optional context, text box, spacing, and hints.

Data flow: It receives the available width, checks whether there is a context label, asks input_height how tall the text input should be, and adds the fixed rows used by the rest of the prompt. It returns a row count.

Call relations: The rendering layout calls this before drawing the view. It relies on input_height, which in turn asks the TextArea how much height its current content needs.

Call graph: calls 1 internal fn (input_height).

CustomPromptView::render166–266 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the custom prompt into the terminal screen buffer. It paints the title, optional context line, input area, placeholder text, blank spacing, and key-hint line.

Data flow: It receives a rectangle describing where it may draw and a mutable screen buffer. If there is no space, it returns immediately. Otherwise it calculates the input height, draws the title and context, paints gutter markers, clears areas that should be blank, renders the text area with its saved state, overlays placeholder text when the input is empty, and draws the standard hint line near the bottom.

Call relations: The terminal UI rendering loop calls this while the prompt is visible. It uses input_height for sizing, gutter for the cyan left marker, TextArea for the editable text, and standard_popup_hint_line for the shared instruction line.

Call graph: calls 3 internal fn (input_height, standard_popup_hint_line, text); 4 external calls (from, new, render_ref, vec!).

CustomPromptView::cursor_pos268–286 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Finds where the text cursor should appear on the terminal screen. This lets the terminal show the caret at the correct place inside the prompt's text area.

Data flow: It receives the same drawing rectangle used for rendering. If the area is too small, it returns nothing. Otherwise it computes the inner text-area rectangle, accounting for the title and optional context line, reads the current text-area drawing state, and asks the TextArea to translate its internal cursor into screen coordinates.

Call relations: The UI asks this after or during rendering when it needs to place the terminal cursor. It uses the same input_height calculation as render, so the cursor position matches the drawn text box.

Call graph: calls 2 internal fn (input_height, cursor_pos_with_state).

CustomPromptView::input_height290–294 ↗
fn input_height(&self, width: u16) -> u16

Purpose: Computes the height of just the input box portion of the prompt. It keeps the text area usable while preventing it from growing too tall.

Data flow: It receives the available width, subtracts the gutter width, asks the TextArea how many rows it wants, clamps that text height between 1 and 8 rows, then adds one extra row used by the prompt's input layout. The returned height is capped at 9 rows.

Call relations: desired_height, render, and cursor_pos all call this so they agree on how tall the input area is. It delegates content-based sizing to TextArea but applies this prompt's own maximum size.

Call graph: calls 1 internal fn (desired_height); called by 3 (cursor_pos, desired_height, render).

gutter297–299 ↗
fn gutter() -> Span<'static>

Purpose: Creates the small cyan gutter marker shown at the left side of the prompt. It gives the popup a consistent visual edge.

Data flow: It takes no input. It returns a styled text span containing the marker characters colored cyan. It does not change any state.

Call relations: render uses this whenever it draws the title, context, or input rows. Keeping it in one helper makes the prompt's left-side marker consistent everywhere it appears.

tui/src/bottom_pane/feedback_view.rssource ↗
domain_logicuser feedback flow

This file is the feedback desk for the terminal user interface. When a user says something was good, bad, buggy, blocked by a safety check, or something else, this code shows the right small popup, asks whether logs may be uploaded, collects an optional note, and sends a feedback event to the rest of the app. Without it, users could not give structured feedback from the TUI, and the app would not know whether to include logs or what follow-up instructions to show.

The main piece is FeedbackNoteView, a small overlay with a text box. It watches keyboard input: plain Enter submits, Escape or Ctrl-C closes, and pasted text goes into the note. Rendering code draws a colored gutter, a title such as “Tell us more (bug),” placeholder text, and the standard popup hint line.

The file also builds selection menus. One menu asks “How was this?” and turns each choice into an app event. Another menu asks permission to upload logs and lists exactly which files would be sent, including diagnostics when useful. After upload, helper functions create a history cell with a thread ID and, depending on the audience, either an internal OpenAI follow-up link or an external GitHub issue URL. The code is careful not to show internal links to external users.

Function details43
FeedbackNoteView::new61–76 ↗
fn new(
        category: FeedbackCategory,
        turn_id: Option<String>,
        app_event_tx: AppEventSender,
        include_logs: bool,
    ) -> Self

Purpose: Creates a new feedback note popup. It remembers what kind of feedback is being sent, which conversation turn it belongs to, whether logs should be included, and where to send the final app event.

Data flow: It receives a feedback category, optional turn ID, event sender, and log-upload choice. It builds an empty text area with fresh text-area state and marks the view as not complete. The result is a ready-to-render feedback note view.

Call relations: The wider app calls this when opening the note step of the feedback flow, and tests use it to build sample views. It prepares the state later used by keyboard handling, rendering, and submission.

Call graph: calls 1 internal fn (new); called by 5 (feedback_view_with_connectivity_diagnostics, make_view, submit_feedback_emits_submit_event_with_trimmed_note, submit_feedback_omits_empty_note, show_feedback_note); 2 external calls (new, default).

FeedbackNoteView::submit78–88 ↗
fn submit(&mut self)

Purpose: Sends the user's feedback note to the app. It trims extra spaces and treats an empty note as no note at all.

Data flow: It reads the current text box contents, trims them, converts non-empty text into a reason, and sends an AppEvent::SubmitFeedback with the category, reason, turn ID, and log choice. It then marks the popup as complete.

Call relations: This is called when FeedbackNoteView::handle_key_event sees a plain Enter key. It hands the finished feedback request to the app event system, which continues the upload or recording flow.

Call graph: calls 2 internal fn (send, text); called by 1 (handle_key_event).

FeedbackNoteView::handle_key_event92–116 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Decides what a keyboard press means while the feedback note popup is open. It separates cancel, submit, and normal text editing.

Data flow: It receives a key event. Escape is turned into cancellation, plain Enter submits the note, modified Enter is passed into the text box, and all other keys are also passed into the text box. It changes the view only through those actions.

Call relations: The bottom-pane UI calls this during user interaction. It calls FeedbackNoteView::on_ctrl_c, FeedbackNoteView::submit, or the text area's input method depending on the key.

Call graph: calls 3 internal fn (on_ctrl_c, submit, input).

FeedbackNoteView::on_ctrl_c118–121 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Closes the feedback note popup without submitting anything. It is used for cancellation.

Data flow: It takes the current view, marks it as complete, and returns a cancellation result saying the cancel action was handled. No feedback event is sent.

Call relations: Keyboard handling calls this when Escape is pressed. The bottom-pane system can then remove the popup because is_complete will report that it is done.

Call graph: called by 1 (handle_key_event).

FeedbackNoteView::is_complete123–125 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether this popup should be closed. It is the simple status check used by the surrounding UI.

Data flow: It reads the view's complete flag and returns that value. It does not change anything.

Call relations: The bottom-pane framework can call this after input or submission to know whether to keep showing the view.

FeedbackNoteView::handle_paste127–133 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Adds pasted text to the feedback note. Empty pastes are ignored.

Data flow: It receives pasted text. If the text is empty, it returns false and changes nothing; otherwise it inserts the text into the text area and returns true.

Call relations: The bottom-pane framework calls this when the terminal reports a paste. It delegates the actual text insertion to the text area.

Call graph: calls 1 internal fn (insert_str).

FeedbackNoteView::desired_height137–139 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Tells the layout system how tall the feedback note popup wants to be. This helps the terminal UI reserve enough rows.

Data flow: It receives the available width, counts the intro lines, asks how tall the input should be, adds spacing, and returns a height in terminal rows.

Call relations: Rendering helpers and tests call this before drawing the view. It relies on FeedbackNoteView::intro_lines and FeedbackNoteView::input_height so the measured size matches what render will draw.

Call graph: calls 2 internal fn (input_height, intro_lines); called by 1 (render).

FeedbackNoteView::cursor_pos141–158 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Finds where the terminal cursor should appear inside the note box. This lets the user see where typing will happen.

Data flow: It receives the screen area for the popup, checks that there is enough space, computes the rectangle used by the text area, reads the saved text-area state, and asks the text area for the cursor location. It returns either a row-and-column position or nothing if there is no room.

Call relations: The renderable UI system calls this when it needs to place the terminal cursor. It uses the same sizing helpers as render, keeping cursor placement aligned with the drawn box.

Call graph: calls 3 internal fn (input_height, intro_lines, cursor_pos_with_state).

FeedbackNoteView::render160–249 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the feedback note popup into the terminal buffer. It paints the title, input area, placeholder, and help hint.

Data flow: It receives a screen rectangle and a buffer to draw into. It computes the title and input height, writes intro lines, draws the left gutter, renders the text area, shows placeholder text when the note is empty, clears spacing, and draws the standard hint line. The output is visual content in the buffer.

Call relations: The terminal UI calls this whenever the feedback note view must appear or refresh. It uses helpers such as intro_lines, input_height, feedback_title_and_placeholder, and the shared popup hint function.

Call graph: calls 5 internal fn (input_height, intro_lines, feedback_title_and_placeholder, standard_popup_hint_line, text); called by 1 (render); 4 external calls (from, new, render_ref, vec!).

FeedbackNoteView::input_height253–257 ↗
fn input_height(&self, width: u16) -> u16

Purpose: Chooses how many terminal rows the note input should use. It grows with the text but stays within a small popup-friendly limit.

Data flow: It receives the available width, subtracts the gutter space, asks the text area how much height it wants, clamps that height between a minimum and maximum, adds one row for spacing, and returns the final input height.

Call relations: Layout, cursor placement, and rendering all call this so the popup measures and draws the input area consistently.

Call graph: calls 1 internal fn (desired_height); called by 3 (cursor_pos, desired_height, render).

FeedbackNoteView::intro_lines259–262 ↗
fn intro_lines(&self, _width: u16) -> Vec<Line<'static>>

Purpose: Builds the title line shown above the feedback note box. The title changes based on the feedback category.

Data flow: It reads the feedback category from the view, gets the matching title, combines it with the colored gutter, and returns the line to draw.

Call relations: Height calculation, cursor placement, and rendering call this. It gets the text from feedback_title_and_placeholder.

Call graph: calls 1 internal fn (feedback_title_and_placeholder); called by 3 (cursor_pos, desired_height, render); 1 external calls (vec!).

should_show_feedback_connectivity_details265–270 ↗
fn should_show_feedback_connectivity_details(
    category: FeedbackCategory,
    diagnostics: &FeedbackDiagnostics,
) -> bool

Purpose: Decides whether network/connectivity diagnostic details should be shown in the log-upload consent popup. It avoids showing this extra detail for positive feedback.

Data flow: It receives the feedback category and diagnostics. It returns true only when the category is not GoodResult and the diagnostics list is not empty.

Call relations: The upload-consent builder calls this while preparing the list of information the user may upload.

Call graph: calls 1 internal fn (is_empty); called by 1 (feedback_upload_consent_params).

gutter272–274 ↗
fn gutter() -> Span<'static>

Purpose: Creates the small colored marker shown at the left edge of feedback popup lines. It gives the popup a consistent visual style.

Data flow: It has no inputs. It returns a cyan text span containing the gutter symbol and a following space.

Call relations: The note view uses this while building intro lines and while drawing the input area.

feedback_title_and_placeholder276–299 ↗
fn feedback_title_and_placeholder(category: FeedbackCategory) -> (String, String)

Purpose: Chooses the title and placeholder text for the note popup. This makes the prompt fit the kind of feedback the user selected.

Data flow: It receives a feedback category and returns two strings: a title such as “Tell us more (bug)” and an optional-note prompt. Safety-check feedback gets a more specific placeholder asking what was refused.

Call relations: Rendering and intro-line creation call this so the visible wording matches the chosen category.

Call graph: called by 2 (intro_lines, render).

feedback_classification301–309 ↗
fn feedback_classification(category: FeedbackCategory) -> &'static str

Purpose: Converts a feedback category into a stable machine-friendly label. These labels can be used when sending or storing feedback.

Data flow: It receives a category and returns a short lowercase string such as bad_result or safety_check. It does not change any state.

Call relations: This helper stands apart from the rendering flow and provides a shared naming convention for feedback categories.

feedback_success_cell311–363 ↗
fn feedback_success_cell(
    category: FeedbackCategory,
    include_logs: bool,
    thread_id: &str,
    feedback_audience: FeedbackAudience,
) -> history_cell::WebHyperlinkHistoryCell

Purpose: Builds the message shown after feedback is recorded or uploaded. It thanks the user and, when useful, tells them where to follow up.

Data flow: It receives the feedback category, whether logs were included, a thread ID, and the feedback audience. It chooses a prefix, asks for the right follow-up URL if any, builds formatted terminal lines, and returns a history cell that can show clickable-looking links.

Call relations: The feedback completion flow can use this to add a result message to conversation history. It calls issue_url_for_category to decide whether the message should point to GitHub, an internal OpenAI link, or just show the thread ID.

Call graph: calls 2 internal fn (issue_url_for_category, new); called by 4 (feedback_success_cell_matches_employee_bug_copy, feedback_success_cell_matches_external_bug_copy, feedback_success_cell_matches_good_result_copy, feedback_success_cell_uses_issue_links_for_remaining_categories); 2 external calls (from, vec!).

issue_url_for_category365–385 ↗
fn issue_url_for_category(
    category: FeedbackCategory,
    thread_id: &str,
    feedback_audience: FeedbackAudience,
) -> Option<String>

Purpose: Chooses the follow-up URL for a feedback category and audience. It keeps internal OpenAI links separate from external GitHub issue links.

Data flow: It receives a category, thread ID, and audience. For bug, bad result, safety-check, and other feedback, it returns either an internal feedback URL or a GitHub issue URL containing the thread ID. For good-result feedback, it returns no URL.

Call relations: The success-message builder calls this when deciding what instructions to show after feedback. Tests also check it directly to guard the audience-specific behavior.

Call graph: calls 1 internal fn (slack_feedback_url); called by 2 (feedback_success_cell, issue_url_available_for_bug_bad_result_safety_check_and_other); 1 external calls (format!).

slack_feedback_url391–393 ↗
fn slack_feedback_url(_thread_id: &str) -> String

Purpose: Returns the internal follow-up link used for OpenAI employee feedback. The thread ID is accepted for symmetry with the external path, but is not currently placed into the URL.

Data flow: It receives a thread ID but does not use it. It returns the fixed internal feedback URL as a string.

Call relations: issue_url_for_category calls this when the audience is OpenAiEmployee and the category needs follow-up.

Call graph: called by 1 (issue_url_for_category).

feedback_selection_params396–435 ↗
fn feedback_selection_params(
    app_event_tx: AppEventSender,
) -> super::SelectionViewParams

Purpose: Builds the first feedback menu, the one asking “How was this?”. Each menu item represents a feedback category.

Data flow: It receives an app event sender. It creates menu items for bug, bad result, good result, safety check, and other, each with a short explanation and an action that opens the next feedback step. It returns the full selection-view configuration.

Call relations: The app uses these parameters to show the category picker. It uses make_feedback_item to turn each category into a selectable row.

Call graph: 2 external calls (default, vec!).

feedback_disabled_params438–450 ↗
fn feedback_disabled_params() -> super::SelectionViewParams

Purpose: Builds a simple popup shown when feedback sending is disabled by configuration. It gives the user a clear reason instead of silently doing nothing.

Data flow: It creates selection-view parameters with a title, subtitle, standard footer hint, and one Close item. The result is a popup configuration that can be rendered by the shared selection view.

Call relations: The feedback entry flow can use this instead of the normal feedback picker when settings forbid feedback submission.

Call graph: calls 1 internal fn (standard_popup_hint_line); 2 external calls (default, vec!).

make_feedback_item452–468 ↗
fn make_feedback_item(
    app_event_tx: AppEventSender,
    name: &str,
    description: &str,
    category: FeedbackCategory,
) -> super::SelectionItem

Purpose: Creates one selectable row in the feedback category menu. Selecting the row sends an event to open the log-consent step for that category.

Data flow: It receives an event sender, display name, description, and category. It builds an action closure that sends AppEvent::OpenFeedbackConsent, then returns a selection item with that action and dismiss-on-select behavior.

Call relations: feedback_selection_params calls this once for each feedback category. The action it creates hands control back to the app event loop.

Call graph: 3 external calls (new, default, vec!).

tests::render587–593 ↗
fn render(view: &FeedbackNoteView, width: u16) -> String

Purpose: Test helper that turns a FeedbackNoteView into plain text. This makes visual snapshots easy to compare.

Data flow: It receives a view and width, asks the view for its desired height, creates an empty terminal buffer, renders the view into it, and converts the buffer into a string.

Call relations: Snapshot tests call this to check how different feedback categories appear on screen.

Call graph: calls 2 internal fn (desired_height, render); 3 external calls (empty, new, render_buffer).

tests::render_renderable595–601 ↗
fn render_renderable(renderable: &dyn Renderable, width: u16) -> String

Purpose: Test helper that renders any renderable UI piece into plain text. It is used for popup headers and other non-view renderables.

Data flow: It receives a renderable object and width, measures its desired height, draws it into an empty buffer, and converts that buffer into a string.

Call relations: Consent-popup tests call this to inspect the generated header listing uploaded files.

Call graph: 5 external calls (empty, new, render_buffer, desired_height, render).

tests::render_buffer603–626 ↗
fn render_buffer(area: Rect, buf: &Buffer) -> String

Purpose: Test helper that converts a terminal drawing buffer into a trimmed multi-line string. It removes empty outer whitespace so snapshots are stable.

Data flow: It reads every cell in the given rectangle, substitutes spaces for empty cells, trims trailing spaces from each line, drops blank lines at the top and bottom, and joins the rest with newlines.

Call relations: Both rendering helpers use this as the final step before comparing UI output in tests.

tests::render_cell628–641 ↗
fn render_cell(cell: &impl history_cell::HistoryCell, width: u16) -> String

Purpose: Test helper that turns a history cell into readable plain text. It strips styling so tests can compare the message content.

Data flow: It receives a history cell and width, asks the cell for display lines, concatenates the text spans in each line, trims line endings, and joins the lines into one string.

Call relations: Success-message tests use this to verify the exact wording and links produced by feedback_success_cell.

Call graph: 1 external calls (display_lines).

tests::make_view643–649 ↗
fn make_view(category: FeedbackCategory) -> FeedbackNoteView

Purpose: Test helper that creates a feedback note view with a dummy event channel. It keeps the individual rendering tests short.

Data flow: It receives a category, creates an unbounded app-event channel, wraps its sender, and returns a FeedbackNoteView with logs included and no turn ID.

Call relations: The category-specific snapshot tests call this before rendering a view.

Call graph: calls 2 internal fn (new, new).

tests::feedback_view_bad_result652–656 ↗
fn feedback_view_bad_result()

Purpose: Checks the rendered feedback note popup for bad-result feedback. It protects the wording and layout from accidental changes.

Data flow: It creates a bad-result view, renders it at a fixed width, and compares the output with a stored snapshot.

Call relations: The test runner calls this as part of the UI test suite. It uses tests::make_view and tests::render.

Call graph: 3 external calls (assert_snapshot!, make_view, render).

tests::feedback_view_good_result659–663 ↗
fn feedback_view_good_result()

Purpose: Checks the rendered feedback note popup for good-result feedback. It confirms positive feedback gets the right title and placeholder.

Data flow: It creates a good-result view, renders it at a fixed width, and compares the output with a stored snapshot.

Call relations: The test runner calls this with the other feedback view snapshots. It relies on the shared test helpers.

Call graph: 3 external calls (assert_snapshot!, make_view, render).

tests::feedback_view_bug666–670 ↗
fn feedback_view_bug()

Purpose: Checks the rendered feedback note popup for bug feedback. It makes sure bug reports show the expected prompt.

Data flow: It creates a bug view, renders it at a fixed width, and compares the output with a stored snapshot.

Call relations: The test runner calls this during snapshot testing of the feedback note UI.

Call graph: 3 external calls (assert_snapshot!, make_view, render).

tests::feedback_view_other673–677 ↗
fn feedback_view_other()

Purpose: Checks the rendered feedback note popup for the catch-all “other” category. It guards that category's visible wording.

Data flow: It creates an other-category view, renders it at a fixed width, and compares the output with a stored snapshot.

Call relations: The test runner calls this alongside the other category rendering tests.

Call graph: 3 external calls (assert_snapshot!, make_view, render).

tests::feedback_view_safety_check680–684 ↗
fn feedback_view_safety_check()

Purpose: Checks the rendered feedback note popup for safety-check feedback. This is important because its placeholder asks a more specific question.

Data flow: It creates a safety-check view, renders it at a fixed width, and compares the output with a stored snapshot.

Call relations: The test runner calls this to catch unwanted changes in safety-check feedback copy.

Call graph: 3 external calls (assert_snapshot!, make_view, render).

tests::feedback_view_with_connectivity_diagnostics687–699 ↗
fn feedback_view_with_connectivity_diagnostics()

Purpose: Checks rendering of a feedback note view in a case named for connectivity diagnostics. The constructed view itself is a bug feedback note without logs.

Data flow: It creates an event channel, builds a bug feedback view with log inclusion set to false, renders it, and compares the result with a snapshot.

Call relations: The test runner calls this as part of the feedback view snapshot set. It directly uses FeedbackNoteView::new and the render helper.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert_snapshot!, render).

tests::submit_feedback_emits_submit_event_with_trimmed_note741–765 ↗
fn submit_feedback_emits_submit_event_with_trimmed_note()

Purpose: Checks that submitting feedback sends the right app event and trims extra spaces from the note. It also checks the view marks itself complete.

Data flow: It creates a feedback view with a turn ID, inserts text with leading and trailing spaces, submits the view, reads the event from the channel, and asserts that the reason is the trimmed text.

Call relations: The test runner calls this to verify FeedbackNoteView::submit, even though submit is normally reached through keyboard handling.

Call graph: calls 2 internal fn (new, new); 2 external calls (assert!, assert_eq!).

tests::submit_feedback_omits_empty_note768–790 ↗
fn submit_feedback_omits_empty_note()

Purpose: Checks that an empty feedback note is sent as no reason rather than an empty string. This keeps downstream feedback data cleaner.

Data flow: It creates a feedback view with no typed text, submits it, receives the emitted app event, and asserts that the reason and turn ID are absent.

Call relations: The test runner calls this to cover the empty-note path in FeedbackNoteView::submit.

Call graph: calls 2 internal fn (new, new); 1 external calls (assert!).

tests::should_show_feedback_connectivity_details_only_for_non_good_result_with_diagnostics793–815 ↗
fn should_show_feedback_connectivity_details_only_for_non_good_result_with_diagnostics()

Purpose: Checks the rule for showing connectivity diagnostics in the consent popup. Diagnostics should appear only when there is something to show and the feedback is not positive.

Data flow: It builds a sample diagnostic, calls the rule with bug, good-result, and empty-diagnostic cases, and asserts the expected true or false results.

Call relations: The test runner calls this to protect the behavior used by feedback_upload_consent_params.

Call graph: calls 1 internal fn (new); 2 external calls (assert_eq!, vec!).

tests::issue_url_available_for_bug_bad_result_safety_check_and_other818–860 ↗
fn issue_url_available_for_bug_bad_result_safety_check_and_other()

Purpose: Checks which categories receive follow-up URLs and which audience gets which kind of URL. It protects the split between internal and external routing.

Data flow: It calls issue_url_for_category with several categories and audiences, then verifies that non-good categories get URLs, good-result feedback does not, employees get the internal link, and external users get a GitHub issue URL with the thread ID.

Call relations: The test runner calls this to verify the helper used by feedback_success_cell.

Call graph: calls 1 internal fn (issue_url_for_category); 2 external calls (assert!, assert_eq!).

tests::feedback_success_cell_matches_external_bug_copy863–877 ↗
fn feedback_success_cell_matches_external_bug_copy()

Purpose: Checks the exact success message shown to an external user after uploading bug feedback. It verifies the GitHub follow-up wording.

Data flow: It builds a success cell for external bug feedback with logs included, renders it as plain text, and compares it with the expected message.

Call relations: The test runner calls this to protect the external-user branch of feedback_success_cell.

Call graph: calls 1 internal fn (feedback_success_cell); 2 external calls (assert_eq!, render_cell).

tests::feedback_success_cell_matches_employee_bug_copy880–894 ↗
fn feedback_success_cell_matches_employee_bug_copy()

Purpose: Checks the exact success message shown to an OpenAI employee after uploading bug feedback. It verifies that internal follow-up instructions are used.

Data flow: It builds a success cell for employee bug feedback, renders it, and compares it with the expected internal link and thread-specific sharing text.

Call relations: The test runner calls this to protect the employee branch of feedback_success_cell.

Call graph: calls 1 internal fn (feedback_success_cell); 2 external calls (assert_eq!, render_cell).

tests::feedback_success_cell_matches_good_result_copy897–911 ↗
fn feedback_success_cell_matches_good_result_copy()

Purpose: Checks the success message for positive feedback without logs. Positive feedback should thank the user and show the thread ID, without asking for an issue.

Data flow: It builds a success cell for good-result feedback with logs excluded, renders it, and compares the text with the expected output.

Call relations: The test runner calls this to verify the no-follow-up-link path in feedback_success_cell.

Call graph: calls 1 internal fn (feedback_success_cell); 2 external calls (assert_eq!, render_cell).

Bottom-pane request and external-action overlays

These files implement the specialized overlays for app-link flows and server-driven elicitation or request-user-input interactions that sit above the persistent composer.

tui/src/bottom_pane/mcp_server_elicitation.rssource ↗
domain_logicrequest handling

An MCP server can pause work and ask the user for something: a text answer, a yes/no choice, a single option from a list, or permission to run a tool. This file is the bridge between that machine-readable request and a usable terminal interface. Without it, those requests would either be invisible to the user or impossible to answer safely.

The file first translates the incoming request into an internal form. It reads a JSON schema, which is a structured description of expected fields, and supports simple text, booleans, and single-choice lists. If the server sends only a message, the file treats it as an approval question and creates choices like “Allow”, “Deny”, or “Cancel”. For tool approvals, it can also show a short summary of tool parameters so the user knows what is being approved.

The main overlay keeps track of the current field, typed drafts, selected options, validation errors, and queued requests. It renders the prompt, input area, and footer hints. It also reacts to keys: arrows move through options, Enter commits or submits, Escape cancels, and Ctrl+C either clears typed text or cancels. When the user submits, it sends an accept, decline, or cancel response back through the app event channel.

Function details105
ComposerDraft::text_with_pending86–100 ↗
fn text_with_pending(&self) -> String

Purpose: Returns the draft text as the user would expect to submit it, including paste content that has not yet been fully expanded into the composer. This matters because pasted text may be stored temporarily before it is committed into the visible text.

Data flow: It starts with the saved draft text and any pending paste records. If there are no pending pastes, it returns the text unchanged. If there are pending pastes, it asks the chat composer to expand them and returns the expanded text.

Call relations: Answer collection calls this when turning a text field into a final value. It relies on the composer paste-expansion helper so form submission sees the same text the user intended.

Call graph: calls 1 internal fn (expand_pending_pastes); 1 external calls (debug_assert!).

FooterTip::new191–196 ↗
fn new(text: impl Into<String>) -> Self

Purpose: Creates a normal footer hint, such as “esc to cancel”. These hints tell the user what keys are available without visually emphasizing the hint.

Data flow: It receives any text-like value, converts it into a string, marks it as not highlighted, and returns a footer tip object.

Call relations: The overlay uses this while building footer instructions and option status messages. Rendering later decides how to draw the tip based on the highlight flag.

Call graph: called by 4 (footer_tips, render_footer, footer_tips, render_ui_at); 1 external calls (into).

FooterTip::highlighted198–203 ↗
fn highlighted(text: impl Into<String>) -> Self

Purpose: Creates an emphasized footer hint, such as the main submit action or a validation error. Highlighting draws attention to the most important next step.

Data flow: It receives text, converts it into a string, marks it as highlighted, and returns a footer tip object.

Call relations: Footer-building code calls this for important actions and errors. The footer renderer later displays highlighted tips in a stronger style.

Call graph: called by 3 (footer_tip_lines, footer_tips, footer_tips); 1 external calls (into).

McpServerElicitationFormRequest::from_app_server_request207–235 ↗
fn from_app_server_request(
        thread_id: ThreadId,
        request_id: AppServerRequestId,
        request: McpServerElicitationRequestParams,
    ) -> Option<Self>

Purpose: Converts a raw app-server MCP elicitation request into the form request used by the terminal overlay. It filters out request shapes this overlay cannot display.

Data flow: It receives a thread id, request id, and server request. If the request is a form request, it serializes the requested schema into JSON and passes the pieces onward; otherwise it returns no form.

Call relations: Higher-level request handling calls this before showing an overlay. It hands the real interpretation work to McpServerElicitationFormRequest::from_parts.

Call graph: called by 4 (interactive_request_for_thread_request, from_form_request, resolved_request_dismisses_overlay_without_emitting_events, handle_elicitation_request_now); 2 external calls (from_parts, to_value).

McpServerElicitationFormRequest::from_parts237–354 ↗
fn from_parts(
        thread_id: ThreadId,
        server_name: String,
        request_id: AppServerRequestId,
        meta: Option<Value>,
        message: String,
        requested_schema: Value,

Purpose: Builds the complete internal form description from the server name, message, metadata, and schema. It decides whether the request is a normal form, an approval prompt, a tool approval, or a tool suggestion.

Data flow: It reads metadata and schema JSON, checks for special approval and tool-suggestion markers, then creates fields and response mode. It returns a ready-to-render form request or nothing if the schema is unsupported.

Call relations: This is the main parser behind from_app_server_request. It calls helpers for tool suggestions, persistent approval options, tool-parameter summaries, and schema fields.

Call graph: calls 4 internal fn (approval_supports_persist_mode, parse_fields_from_schema, parse_tool_approval_display_params, parse_tool_suggestion_request); 5 external calls (String, as_object, is_null, new, vec!).

McpServerElicitationFormRequest::tool_suggestion356–358 ↗
fn tool_suggestion(&self) -> Option<&ToolSuggestionRequest>

Purpose: Returns the parsed tool suggestion, if this request is asking the UI to suggest installing or enabling a tool. Callers use this to show special tool-suggestion UI outside the regular form flow.

Data flow: It reads the stored optional tool-suggestion value and returns it by reference if present.

Call relations: The code that pushes elicitation requests into the UI checks this accessor to decide how to present or route the request.

Call graph: called by 1 (push_mcp_server_elicitation_request).

McpServerElicitationFormRequest::thread_id360–362 ↗
fn thread_id(&self) -> ThreadId

Purpose: Returns the conversation thread this request belongs to. This keeps the eventual answer tied to the right chat thread.

Data flow: It reads the stored thread id and returns it.

Call relations: Request-pushing code uses this when deciding where the elicitation belongs and when preparing responses.

Call graph: called by 1 (push_mcp_server_elicitation_request).

McpServerElicitationFormRequest::server_name364–366 ↗
fn server_name(&self) -> &str

Purpose: Returns the name of the MCP server that asked the question. The response must include this so the app can resolve the correct server-side request.

Data flow: It reads the stored server name and returns it as text.

Call relations: Request-pushing code uses this identity information when tracking and resolving elicitation requests.

Call graph: called by 1 (push_mcp_server_elicitation_request).

McpServerElicitationFormRequest::request_id368–370 ↗
fn request_id(&self) -> &AppServerRequestId

Purpose: Returns the server request id. This id is the receipt number that lets the app match the user's answer to the original question.

Data flow: It reads the stored request id and returns a reference to it.

Call relations: Request-pushing and resolution logic use this id to avoid answering or dismissing the wrong request.

Call graph: called by 1 (push_mcp_server_elicitation_request).

parse_tool_suggestion_request373–409 ↗
fn parse_tool_suggestion_request(meta: Option<&Value>) -> Option<ToolSuggestionRequest>

Purpose: Extracts a tool suggestion from request metadata. A tool suggestion is a request to install or enable a connector or plugin.

Data flow: It receives optional JSON metadata, checks for the tool-suggestion kind, reads required fields such as tool type, reason, id, and name, and returns a structured suggestion when all required data is valid.

Call relations: from_parts calls this before deciding how to build the form. If it succeeds on a message-only request, the form can represent a tool suggestion without normal fields.

Call graph: called by 1 (from_parts).

approval_supports_persist_mode411–427 ↗
fn approval_supports_persist_mode(meta: Option<&Value>, expected_mode: &str) -> bool

Purpose: Checks whether an approval request allows the user's choice to be remembered. This is what enables options such as “Allow for this session” or “Always allow”.

Data flow: It reads the metadata field for persistence, accepts either a single string or a list of strings, and returns true only when the expected mode is present.

Call relations: from_parts uses this while constructing approval choices. The result changes which options the user sees.

Call graph: called by 1 (from_parts).

parse_tool_approval_display_params429–464 ↗
fn parse_tool_approval_display_params(meta: Option<&Value>) -> Vec<McpToolApprovalDisplayParam>

Purpose: Builds a short list of tool parameters to show in an approval prompt. This helps the user understand what a tool is about to do before approving it.

Data flow: It reads metadata, first preferring an explicit display list. If that is missing, it falls back to raw tool parameters sorted by name. It returns display-ready parameter records.

Call relations: from_parts calls this for message-only tool approvals. The formatted prompt later uses these records to show a compact parameter summary.

Call graph: called by 1 (from_parts); 1 external calls (new).

parse_tool_approval_display_param466–485 ↗
fn parse_tool_approval_display_param(value: &Value) -> Option<McpToolApprovalDisplayParam>

Purpose: Parses one explicitly provided tool-parameter display entry. It rejects incomplete or blank entries so the approval message stays meaningful.

Data flow: It receives a JSON value, expects an object with a non-empty name, display name, and value, then returns a structured display parameter or nothing.

Call relations: The parameter-list parser applies this to each explicit display entry. Valid entries are later formatted for the approval prompt.

Call graph: 2 external calls (as_object, get).

format_tool_approval_display_message487–511 ↗
fn format_tool_approval_display_message(
    message: &str,
    approval_display_params: &[McpToolApprovalDisplayParam],
) -> String

Purpose: Combines the approval message with a short tool-parameter summary. This makes approvals more transparent without flooding the screen.

Data flow: It trims the main message, formats up to a small limit of parameters, joins the sections with blank lines, and returns the resulting prompt text.

Call relations: The overlay's prompt builder calls this whenever it needs the current prompt text. It delegates individual parameter lines to formatting helpers.

Call graph: called by 1 (current_prompt_text); 3 external calls (new, is_empty, iter).

format_tool_approval_display_param_line513–519 ↗
fn format_tool_approval_display_param_line(param: &McpToolApprovalDisplayParam) -> String

Purpose: Formats one tool parameter as a readable line like “Calendar: primary”. It is a small helper for the approval summary.

Data flow: It receives one display parameter, formats its value, combines it with the display name, and returns one line of text.

Call relations: The approval-message formatter uses this for each parameter it chooses to show.

Call graph: 1 external calls (format!).

format_tool_approval_display_param_value521–530 ↗
fn format_tool_approval_display_param_value(value: &Value) -> String

Purpose: Turns a tool parameter value into compact, safe-to-display text. Long or complex values are shortened so the approval box remains readable.

Data flow: It receives a JSON value. Strings have extra whitespace collapsed; other values are compacted as JSON when possible. The final text is truncated to a fixed length and returned.

Call relations: Parameter-line formatting calls this before inserting the value into the prompt.

Call graph: calls 2 internal fn (format_json_compact, truncate_text); 1 external calls (to_string).

parse_fields_from_schema532–557 ↗
fn parse_fields_from_schema(requested_schema: &Value) -> Option<Vec<McpServerElicitationField>>

Purpose: Turns a JSON object schema into the list of fields the terminal form can display. It supports only simple field types that the overlay knows how to ask for.

Data flow: It checks that the schema is an object, gathers required field names, loops through properties, parses each property, and returns a non-empty field list if all fields are supported.

Call relations: from_parts calls this for normal form content. It hands each property to parse_field.

Call graph: calls 1 internal fn (parse_field); called by 1 (from_parts); 2 external calls (as_object, new).

parse_field559–640 ↗
fn parse_field(
    id: &str,
    property: McpElicitationPrimitiveSchema,
    required: bool,
) -> Option<McpServerElicitationField>

Purpose: Converts one schema property into one form field. It knows how to represent strings as text input, booleans as true/false choices, and supported enums as choice lists.

Data flow: It receives a field id, a parsed primitive schema, and whether the field is required. It picks labels, prompts, defaults, and input kind, then returns a field or rejects unsupported types.

Call relations: parse_fields_from_schema calls this for each property. For newer single-select enum shapes, it delegates to parse_single_select_field.

Call graph: calls 1 internal fn (parse_single_select_field); called by 1 (parse_fields_from_schema).

parse_single_select_field642–705 ↗
fn parse_single_select_field(
    id: &str,
    schema: McpElicitationSingleSelectEnumSchema,
    required: bool,
) -> Option<McpServerElicitationField>

Purpose: Builds a single-choice form field from the supported single-select enum schema variants. This lets servers provide a list of named choices.

Data flow: It receives a field id, enum schema, and required flag. It extracts labels, prompts, default selection, and options, then returns a select-style field.

Call relations: parse_field calls this when it sees a single-select enum. The returned field is later displayed as a selectable list.

Call graph: called by 1 (parse_field).

McpServerElicitationOverlay::new721–736 ↗
fn new(
        request: McpServerElicitationFormRequest,
        app_event_tx: AppEventSender,
        has_input_focus: bool,
        enhanced_keys_supported: bool,
        disable_paste_burst: bool,

Purpose: Creates an overlay using the default runtime key bindings. This constructor is mainly used by tests.

Data flow: It receives the request, event sender, input-focus settings, keyboard support flag, and paste setting. It adds the default list keymap and forwards everything to new_with_keymap.

Call relations: Tests call this to build overlays. Production setup normally uses new_with_keymap so it can pass the active keymap.

Call graph: calls 1 internal fn (defaults); called by 13 (approval_form_tool_approval_snapshot, approval_form_tool_approval_with_param_summary_snapshot, approval_form_tool_approval_with_persist_options_snapshot, boolean_form_snapshot, ctrl_c_cancels_elicitation, empty_tool_approval_schema_always_allow_sets_persist_meta, empty_tool_approval_schema_session_choice_sets_persist_meta, horizontal_list_keys_move_between_select_fields, message_only_form_snapshot, message_only_form_with_persist_options_snapshot (+3 more)); 1 external calls (new_with_keymap).

McpServerElicitationOverlay::new_with_keymap738–769 ↗
fn new_with_keymap(
        request: McpServerElicitationFormRequest,
        app_event_tx: AppEventSender,
        has_input_focus: bool,
        enhanced_keys_supported: bool,
        disable_paste_

Purpose: Creates a fully initialized elicitation overlay. It prepares the text composer, stores the request, and initializes per-field answer state.

Data flow: It receives a form request, event sender, input settings, paste setting, and list keymap. It creates a plain-text composer, clears footer hints, resets answers for the request, restores the first draft, and returns the overlay.

Call relations: The UI request flow calls this when showing a new elicitation. It sets up the state that rendering and key handling use afterward.

Call graph: calls 2 internal fn (new_with_config, plain_text); called by 1 (push_mcp_server_elicitation_request); 3 external calls (new, new, clone).

McpServerElicitationOverlay::reset_for_request771–798 ↗
fn reset_for_request(&mut self)

Purpose: Clears overlay state and prepares answer slots for the current request. This is needed when the overlay starts or moves to the next queued request.

Data flow: It reads the request fields and creates one answer state per field. Select fields get a default selected option when available; text fields start empty. It also resets the current index, errors, and composer text.

Call relations: Construction and queue advancement call this before showing a request. restore_current_draft usually follows to sync the composer with the selected field.

Call graph: calls 1 internal fn (set_text_content); called by 1 (advance_queue_or_complete); 2 external calls (new, new).

McpServerElicitationOverlay::field_count800–802 ↗
fn field_count(&self) -> usize

Purpose: Returns how many fields are in the current form. Many parts of the UI need this to show progress and decide when to submit.

Data flow: It reads the current request's field list length and returns it.

Call relations: Navigation, footer hints, submission flow, and rendering call this to understand the size of the form.

Call graph: called by 5 (footer_tips, go_next_or_submit, jump_to_field, move_field, render).

McpServerElicitationOverlay::current_index804–806 ↗
fn current_index(&self) -> usize

Purpose: Returns the index of the field the user is currently viewing. This is the overlay's current position in the form.

Data flow: It reads the stored current index and returns it.

Call relations: Current-field lookup, footer text, submission decisions, and rendering use this position.

Call graph: called by 6 (current_answer, current_field, footer_tips, go_next_or_submit, is_current_field_answered, render).

McpServerElicitationOverlay::current_field808–810 ↗
fn current_field(&self) -> Option<&McpServerElicitationField>

Purpose: Returns the field currently being shown, if there is one. It is the central lookup for prompt, input type, and validation behavior.

Data flow: It reads the current index, looks up that field in the request, and returns it if present.

Call relations: Prompt building, placeholder selection, and option lookup all call this to know what the user is answering now.

Call graph: calls 1 internal fn (current_index); called by 3 (answer_placeholder, current_options, current_prompt_text).

McpServerElicitationOverlay::current_answer812–814 ↗
fn current_answer(&self) -> Option<&McpServerElicitationAnswerState>

Purpose: Returns the saved answer state for the current field. This includes the selected option or typed draft.

Data flow: It reads the current index, looks up the matching answer state, and returns it if present.

Call relations: Rendering and selection helpers call this to display the current answer and calculate list height.

Call graph: calls 1 internal fn (current_index); called by 4 (options_required_height, render_input, restore_current_draft, selected_option_index).

McpServerElicitationOverlay::current_answer_mut816–819 ↗
fn current_answer_mut(&mut self) -> Option<&mut McpServerElicitationAnswerState>

Purpose: Returns a changeable answer state for the current field. This is how key handling updates selections and drafts.

Data flow: It reads the current index, looks up the matching answer state mutably, and returns it if present.

Call relations: Draft saving, option selection, paste handling, and submission processing use this to update the user's answer.

Call graph: called by 7 (apply_submission_to_draft, clear_current_draft, clear_selection, handle_key_event, handle_paste, save_current_draft, select_current_option).

McpServerElicitationOverlay::capture_composer_draft821–833 ↗
fn capture_composer_draft(&self) -> ComposerDraft

Purpose: Takes a snapshot of the text composer for the current text field. This preserves what the user typed when they move between fields.

Data flow: It reads current text, text elements, local image paths, and pending pastes from the composer, then packs them into a ComposerDraft.

Call relations: Draft saving and key handling call this before comparing or storing text-field changes.

Call graph: calls 4 internal fn (current_text, local_images, pending_pastes, text_elements); called by 2 (handle_key_event, save_current_draft).

McpServerElicitationOverlay::restore_current_draft835–856 ↗
fn restore_current_draft(&mut self)

Purpose: Loads the saved draft for the current field back into the composer. It keeps each text field's unfinished answer when the user moves around the form.

Data flow: It updates the placeholder and footer hints. For select fields or missing answers, it clears the composer; for text fields, it restores saved text, images, and pending pastes, then moves the cursor to the end.

Call relations: Navigation and queue advancement call this after changing the active field or request.

Call graph: calls 8 internal fn (move_cursor_to_end, set_footer_hint_override, set_pending_pastes, set_placeholder_text, set_text_content, answer_placeholder, current_answer, current_field_is_select); called by 3 (advance_queue_or_complete, jump_to_field, move_field); 2 external calls (new, new).

McpServerElicitationOverlay::save_current_draft858–869 ↗
fn save_current_draft(&mut self)

Purpose: Stores the current composer contents into the answer state for a text field. This prevents typed text from being lost when navigating away.

Data flow: If the current field is not a select field, it captures the composer draft. If a previously committed answer has changed, it marks it uncommitted, then stores the new draft.

Call relations: Field navigation and submission call this before changing focus or validating answers.

Call graph: calls 3 internal fn (capture_composer_draft, current_answer_mut, current_field_is_select); called by 3 (jump_to_field, move_field, submit_answers).

McpServerElicitationOverlay::clear_current_draft871–882 ↗
fn clear_current_draft(&mut self)

Purpose: Clears the current text-field answer. This is used when Ctrl+C should erase typed input instead of canceling the whole request.

Data flow: If the current field is text, it resets the saved draft and committed flag, clears the composer text, and moves the cursor to the end.

Call relations: on_ctrl_c calls this when there is text to clear. Select fields are not affected.

Call graph: calls 4 internal fn (move_cursor_to_end, set_text_content, current_answer_mut, current_field_is_select); called by 1 (on_ctrl_c); 3 external calls (new, new, default).

McpServerElicitationOverlay::answer_placeholder884–892 ↗
fn answer_placeholder(&self) -> &'static str

Purpose: Chooses the placeholder text for the current text field. Optional fields get a placeholder that says the answer is optional.

Data flow: It reads the current field. Required fields use “Type your answer”; optional fields use “Type your answer (optional)”.

Call relations: restore_current_draft calls this when updating the composer for the active field.

Call graph: calls 1 internal fn (current_field); called by 1 (restore_current_draft).

McpServerElicitationOverlay::current_field_is_select894–899 ↗
fn current_field_is_select(&self) -> bool

Purpose: Reports whether the current field is a choice list instead of free text. This decides whether keys control a list or the text composer.

Data flow: It checks the current field's input kind and returns true for select fields.

Call relations: Rendering, key handling, paste handling, cursor placement, and draft logic all branch on this helper.

Call graph: called by 12 (clear_current_draft, cursor_pos, footer_tips, handle_key_event, handle_paste, input_height, on_ctrl_c, render, render_footer, render_input (+2 more)); 1 external calls (matches!).

McpServerElicitationOverlay::current_field_is_secret901–906 ↗
fn current_field_is_secret(&self) -> bool

Purpose: Reports whether the current text field should hide what the user types. Secret fields are rendered with masking characters.

Data flow: It checks the current field's input kind and returns true only for text fields marked secret.

Call relations: render_input calls this before deciding whether to draw normal text or masked text.

Call graph: called by 1 (render_input); 1 external calls (matches!).

McpServerElicitationOverlay::selected_option_index908–911 ↗
fn selected_option_index(&self) -> Option<usize>

Purpose: Returns the selected option number for the current select field, if one is selected. It is used for display and submission.

Data flow: It reads the current answer state and returns its selected index.

Call relations: Option row building, footer status, and key handling use this to show and act on the current choice.

Call graph: calls 1 internal fn (current_answer); called by 3 (handle_key_event, option_rows, render_footer).

McpServerElicitationOverlay::options_len913–915 ↗
fn options_len(&self) -> usize

Purpose: Returns how many choices the current select field has. This helps clamp movement and validate digit shortcuts.

Data flow: It reads the current option slice and returns its length.

Call relations: Selection movement, digit selection, footer status, and option committing call this.

Call graph: calls 1 internal fn (current_options); called by 4 (handle_key_event, option_index_for_digit, render_footer, select_current_option).

McpServerElicitationOverlay::current_options917–922 ↗
fn current_options(&self) -> &[McpServerElicitationOption]

Purpose: Returns the choices for the current select field. For text fields, it returns an empty list.

Data flow: It reads the current field and, if it is a select input, returns its options; otherwise it returns an empty slice.

Call relations: Option counting and row-building call this before rendering or changing selection.

Call graph: calls 1 internal fn (current_field); called by 2 (option_rows, options_len).

McpServerElicitationOverlay::option_rows924–946 ↗
fn option_rows(&self) -> Vec<GenericDisplayRow>

Purpose: Builds display rows for the current select options. It adds numbering and a marker for the selected choice.

Data flow: It reads current options and selected index, creates one display row per option with a prefix like “› 1.”, and includes optional descriptions.

Call relations: The select input renderer and height calculator use these rows to draw the menu and measure its size.

Call graph: calls 2 internal fn (current_options, selected_option_index); called by 2 (options_required_height, render_input).

McpServerElicitationOverlay::wrapped_prompt_lines948–953 ↗
fn wrapped_prompt_lines(&self, width: u16) -> Vec<String>

Purpose: Wraps the current prompt text to fit the available terminal width. This keeps long messages readable instead of running off-screen.

Data flow: It gets the current prompt text, wraps it to at least one column of width, and returns a list of lines.

Call relations: Rendering, height calculation, cursor placement, and prompt drawing call this whenever they need prompt layout.

Call graph: calls 1 internal fn (current_prompt_text); called by 4 (cursor_pos, desired_height, render, render_prompt); 1 external calls (wrap).

McpServerElicitationOverlay::current_prompt_text955–983 ↗
fn current_prompt_text(&self) -> String

Purpose: Builds the full prompt shown above the input area. It combines the server's message, any tool-approval parameter summary, and the current field's label or description.

Data flow: It formats the request message, reads the current field, chooses a clear field prompt, joins non-empty sections with blank lines, and returns the text.

Call relations: wrapped_prompt_lines calls this before wrapping the prompt for display.

Call graph: calls 2 internal fn (current_field, format_tool_approval_display_message); called by 1 (wrapped_prompt_lines); 2 external calls (new, format!).

McpServerElicitationOverlay::options_required_height1023–1036 ↗
fn options_required_height(&self, width: u16) -> u16

Purpose: Calculates how many terminal rows are needed to show the current option list. This helps the overlay size itself properly.

Data flow: It builds option rows, prepares selection state, and asks the common menu measurement helper how tall the rows are at the given width.

Call relations: input_height and render_footer use this to allocate space and detect when some options are hidden.

Call graph: calls 3 internal fn (current_answer, option_rows, measure_rows_height); called by 2 (input_height, render_footer).

McpServerElicitationOverlay::input_height1038–1045 ↗
fn input_height(&self, width: u16) -> u16

Purpose: Calculates the desired height of the input area. Select fields need enough space for options; text fields use the composer height within a small limit.

Data flow: It checks the current field type. For select fields it returns the option-list height; for text fields it asks the composer and clamps the result.

Call relations: Overall height calculation calls this when deciding how tall the overlay wants to be.

Call graph: calls 3 internal fn (desired_height, current_field_is_select, options_required_height); called by 1 (desired_height).

McpServerElicitationOverlay::move_field1047–1057 ↗
fn move_field(&mut self, next: bool)

Purpose: Moves to the next or previous field, wrapping around at the ends. It saves the current text before moving so work is not lost.

Data flow: It checks the number of fields, saves the current draft, updates the current index forward or backward, clears validation error, and restores the newly active draft.

Call relations: Keyboard navigation and go_next_or_submit call this when the form should advance without submitting yet.

Call graph: calls 3 internal fn (field_count, restore_current_draft, save_current_draft); called by 2 (go_next_or_submit, handle_key_event).

McpServerElicitationOverlay::jump_to_field1059–1066 ↗
fn jump_to_field(&mut self, idx: usize)

Purpose: Moves directly to a specific field. This is used to take the user to the first required field they forgot to answer.

Data flow: It checks the target index, saves the current draft, sets the current index, and restores the target field's draft.

Call relations: submit_answers calls this when validation finds a missing required answer.

Call graph: calls 3 internal fn (field_count, restore_current_draft, save_current_draft); called by 1 (submit_answers).

McpServerElicitationOverlay::field_value1068–1088 ↗
fn field_value(&self, idx: usize) -> Option<Value>

Purpose: Returns the submitted value for one field, if the user has committed an answer. It converts UI state into JSON that can be sent back to the server.

Data flow: It reads the field and answer state. For select fields, it returns the selected option value if committed; for text fields, it expands pending pasted text, trims it, and returns a string if non-empty.

Call relations: Submission, validation, and prompt styling call this to know whether a field has an answer.

Call graph: called by 2 (is_current_field_answered, submit_answers).

McpServerElicitationOverlay::required_unanswered_count1090–1097 ↗
fn required_unanswered_count(&self) -> usize

Purpose: Counts how many required fields are still unanswered. This number is shown in the progress line.

Data flow: It loops through fields, checks required status, asks field_value whether each has an answer, and returns the count of missing required answers.

Call relations: The main renderer calls this when drawing progress at the top of the overlay.

Call graph: called by 1 (render).

McpServerElicitationOverlay::first_required_unanswered_index1099–1106 ↗
fn first_required_unanswered_index(&self) -> Option<usize>

Purpose: Finds the first required field that still needs an answer. This lets the overlay guide the user straight to the problem.

Data flow: It scans fields in order, checks required status and current value, and returns the first missing field index.

Call relations: submit_answers calls this during validation before sending anything to the app.

Call graph: called by 1 (submit_answers).

McpServerElicitationOverlay::is_current_field_answered1108–1110 ↗
fn is_current_field_answered(&self) -> bool

Purpose: Reports whether the current field already has a committed value. The prompt color uses this to distinguish answered from unanswered fields.

Data flow: It reads the current index, asks for that field's value, and returns whether a value exists.

Call relations: render_prompt calls this before choosing the prompt style.

Call graph: calls 2 internal fn (current_index, field_value); called by 1 (render_prompt).

McpServerElicitationOverlay::option_index_for_digit1112–1119 ↗
fn option_index_for_digit(&self, ch: char) -> Option<usize>

Purpose: Maps a number key to an option index. Pressing 1 chooses the first option, 2 chooses the second, and so on.

Data flow: It receives a character, converts it to a digit, rejects zero and out-of-range digits, and returns a zero-based option index when valid.

Call relations: Select-field key handling calls this for quick option selection.

Call graph: calls 1 internal fn (options_len); called by 1 (handle_key_event).

McpServerElicitationOverlay::select_current_option1121–1127 ↗
fn select_current_option(&mut self, committed: bool)

Purpose: Commits or uncommits the currently highlighted option. It also makes sure the selection index stays inside the list.

Data flow: It reads the number of options, clamps the current selection to that range, sets the answer's committed flag, and changes the answer state.

Call relations: Select-field key handling calls this for Space, Enter, and digit shortcuts.

Call graph: calls 2 internal fn (current_answer_mut, options_len); called by 1 (handle_key_event).

McpServerElicitationOverlay::clear_selection1129–1134 ↗
fn clear_selection(&mut self)

Purpose: Clears the selected option for the current select field. This lets the user undo a choice before submitting.

Data flow: It resets the current answer's selection state and marks the answer as not committed.

Call relations: Select-field key handling calls this for Backspace or Delete.

Call graph: calls 1 internal fn (current_answer_mut); called by 1 (handle_key_event).

McpServerElicitationOverlay::dispatch_cancel1136–1145 ↗
fn dispatch_cancel(&self)

Purpose: Sends a cancel response for the current elicitation request. This tells the app and server that the user chose not to continue.

Data flow: It reads the current thread id, server name, and request id, then sends a resolve-elicitation event with a cancel action and no content.

Call relations: Escape handling and Ctrl+C cancellation call this before marking the overlay done.

Call graph: calls 1 internal fn (resolve_elicitation); called by 2 (handle_key_event, on_ctrl_c).

McpServerElicitationOverlay::advance_queue_or_complete1147–1155 ↗
fn advance_queue_or_complete(&mut self)

Purpose: Moves from the finished request to the next queued request, or closes the overlay if there are none. This lets multiple requests be handled in order.

Data flow: It pops the next request from the front of the queue. If one exists, it replaces the current request and resets state; otherwise it marks the overlay complete.

Call relations: Submission and external dismissal call this after the current request is resolved or removed.

Call graph: calls 2 internal fn (reset_for_request, restore_current_draft); called by 2 (dismiss_resolved_request, submit_answers); 1 external calls (pop_front).

McpServerElicitationOverlay::submit_answers1157–1212 ↗
fn submit_answers(&mut self)

Purpose: Validates the form and sends the user's answer back to the app. It handles both approval-style requests and normal form-content requests.

Data flow: It saves the current draft, checks for missing required fields, and either shows an error and jumps to the missing field or builds the response. Approval choices become accept, decline, or cancel actions; normal forms become a JSON object of field values.

Call relations: go_next_or_submit calls this when the user submits from the last field. It sends the response through the event sender, then advances the queue or completes.

Call graph: calls 6 internal fn (resolve_elicitation, advance_queue_or_complete, field_value, first_required_unanswered_index, jump_to_field, save_current_draft); called by 1 (go_next_or_submit); 2 external calls (Object, json!).

McpServerElicitationOverlay::dismiss_resolved_request1214–1233 ↗
fn dismiss_resolved_request(&mut self, request: &ResolvedAppServerRequest) -> bool

Purpose: Removes a request that was resolved somewhere else. This prevents the overlay from showing stale questions.

Data flow: It checks whether the resolved app-server request is an MCP elicitation. It removes matching queued requests, and if the active request matches, it advances to the next request or completes.

Call relations: dismiss_app_server_request calls this when the broader app reports that a pending request is already resolved.

Call graph: calls 1 internal fn (advance_queue_or_complete); called by 1 (dismiss_app_server_request); 2 external calls (len, retain).

McpServerElicitationOverlay::go_next_or_submit1235–1241 ↗
fn go_next_or_submit(&mut self)

Purpose: Decides whether Enter should move to the next field or submit the whole form. It keeps the Enter key behavior simple for the user.

Data flow: It compares the current index with the field count. If this is the last field, it submits; otherwise it moves forward one field.

Call relations: Text submission and select-field Enter or digit selection call this after an answer is committed.

Call graph: calls 4 internal fn (current_index, field_count, move_field, submit_answers); called by 2 (handle_composer_input_result, handle_key_event).

McpServerElicitationOverlay::apply_submission_to_draft1243–1263 ↗
fn apply_submission_to_draft(&mut self, text: String, text_elements: Vec<TextElement>)

Purpose: Stores text submitted by the composer as the current field's answer. This marks non-empty text as committed.

Data flow: It receives submitted text and text elements, reads local image paths from the composer, saves a draft with no pending pastes, updates the committed flag, reloads the composer, and resets footer hints.

Call relations: handle_composer_input_result calls this before moving to the next field or submitting.

Call graph: calls 5 internal fn (local_images, move_cursor_to_end, set_footer_hint_override, set_text_content, current_answer_mut); called by 1 (handle_composer_input_result); 1 external calls (new).

McpServerElicitationOverlay::handle_composer_input_result1265–1283 ↗
fn handle_composer_input_result(&mut self, result: InputResult) -> bool

Purpose: Interprets the result of a text-composer key event. It reacts only when the composer submitted or queued text.

Data flow: It receives an input result. For submitted or queued text, it stores the answer, clears validation error, advances or submits, and returns true. Other results return false.

Call relations: Text-field key handling calls this after passing a key to the composer.

Call graph: calls 2 internal fn (apply_submission_to_draft, go_next_or_submit); called by 1 (handle_key_event).

McpServerElicitationOverlay::render_prompt1285–1310 ↗
fn render_prompt(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the prompt text in the overlay. Unanswered fields are colored to draw the user's attention.

Data flow: It receives a screen area and buffer, wraps prompt lines, clips them to the area, styles them based on whether the field is answered, and writes them to the buffer.

Call relations: The main render method calls this after dividing the overlay into progress, prompt, input, and footer areas.

Call graph: calls 2 internal fn (is_current_field_answered, wrapped_prompt_lines); called by 1 (render); 2 external calls (from, new).

McpServerElicitationOverlay::render_input1312–1334 ↗
fn render_input(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the answer input area. It either shows a selectable option list or the text composer.

Data flow: It receives a screen area and buffer. For select fields, it builds rows, ensures the selected option is visible, and renders the list. For text fields, it renders normal or masked composer text.

Call relations: The main render method calls this after laying out the input area.

Call graph: calls 7 internal fn (render, render_with_mask, current_answer, current_field_is_secret, current_field_is_select, option_rows, render_rows); called by 1 (render).

McpServerElicitationOverlay::desired_height1389–1399 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: Reports how tall the overlay would like to be for a given width. The surrounding UI uses this to reserve enough terminal space.

Data flow: It calculates inner width, counts prompt lines, input height, footer lines, and menu padding, then returns at least the minimum overlay height.

Call relations: This implements the renderable interface used by the bottom pane layout system.

Call graph: calls 5 internal fn (footer_tip_lines, input_height, wrapped_prompt_lines, menu_surface_inset, menu_surface_padding_height); 1 external calls (new).

McpServerElicitationOverlay::render1401–1473 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: Draws the complete elicitation overlay. It lays out progress, prompt, input, and footer sections inside a menu-like surface.

Data flow: It receives a screen area and buffer, draws the surrounding surface, divides the inner area by available height, writes a progress line, and delegates prompt, input, and footer drawing to helper methods.

Call relations: The terminal UI calls this through the renderable interface whenever the screen needs updating.

Call graph: calls 10 internal fn (current_field_is_select, current_index, field_count, footer_tip_lines, render_footer, render_input, render_prompt, required_unanswered_count, wrapped_prompt_lines, render_menu_surface); called by 1 (render_snapshot); 4 external calls (from, new, format!, from).

McpServerElicitationOverlay::cursor_pos1475–1505 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: Reports where the terminal cursor should appear for text input. Select fields do not need a text cursor.

Data flow: It returns nothing for select fields. For text fields, it repeats the same layout math used by rendering to find the input area, then asks the composer for its cursor position.

Call relations: The bottom pane rendering system calls this so the real terminal cursor lines up with the composer.

Call graph: calls 5 internal fn (cursor_pos, current_field_is_select, footer_tip_lines, wrapped_prompt_lines, menu_surface_inset); 1 external calls (from).

McpServerElicitationOverlay::prefer_esc_to_handle_key_event1509–1511 ↗
fn prefer_esc_to_handle_key_event(&self) -> bool

Purpose: Says that this overlay wants first chance to handle Escape. That ensures Escape cancels the elicitation rather than being intercepted elsewhere.

Data flow: It returns true without reading other state.

Call relations: The bottom pane input system uses this preference before routing Escape key events.

McpServerElicitationOverlay::handle_key_event1513–1643 ↗
fn handle_key_event(&mut self, key_event: KeyEvent)

Purpose: Processes keyboard input for the overlay. It is the main control center for canceling, navigating fields, selecting options, and typing text.

Data flow: It receives a key event, ignores key releases, handles global keys like Escape and field navigation, then branches by field type. Select fields update selection or submit; text fields pass the key to the composer and compare drafts to clear stale validation state.

Call relations: The bottom pane calls this whenever the user presses a key while the overlay is active. It delegates to selection helpers, composer handling, cancellation, and submit/navigation helpers.

Call graph: calls 13 internal fn (handle_key_event, capture_composer_draft, clear_selection, current_answer_mut, current_field_is_select, dispatch_cancel, go_next_or_submit, handle_composer_input_result, move_field, option_index_for_digit (+3 more)); 1 external calls (matches!).

McpServerElicitationOverlay::terminal_title_requires_action1645–1647 ↗
fn terminal_title_requires_action(&self) -> bool

Purpose: Reports that this overlay represents something needing user attention. The terminal title can use this to signal that action is required.

Data flow: It returns true without reading other state.

Call relations: The bottom pane view system calls this as part of UI status reporting.

McpServerElicitationOverlay::on_ctrl_c1649–1658 ↗
fn on_ctrl_c(&mut self) -> CancellationEvent

Purpose: Handles Ctrl+C for the overlay. If the user has typed text, it clears the text first; otherwise it cancels the request.

Data flow: It checks whether the current field is text and whether the composer has content. If so, it clears the draft and reports the event handled. Otherwise it sends cancel, marks the overlay done, and reports handled.

Call relations: The bottom pane calls this for Ctrl+C. It uses clear_current_draft for soft cancellation and dispatch_cancel for full cancellation.

Call graph: calls 4 internal fn (current_text_with_pending, clear_current_draft, current_field_is_select, dispatch_cancel).

McpServerElicitationOverlay::is_complete1660–1662 ↗
fn is_complete(&self) -> bool

Purpose: Reports whether the overlay is finished and can be removed. Completion happens after cancellation, submission, or exhausting the request queue.

Data flow: It reads the done flag and returns it.

Call relations: The bottom pane checks this after events to know when to close the overlay.

McpServerElicitationOverlay::handle_paste1664–1673 ↗
fn handle_paste(&mut self, pasted: String) -> bool

Purpose: Accepts pasted text for text fields. Pasting is ignored for select fields because there is no text box to paste into.

Data flow: It receives pasted text, rejects empty paste or select fields, clears validation error, marks the current answer uncommitted, and forwards the paste to the composer.

Call relations: The bottom pane paste handler calls this when paste text arrives.

Call graph: calls 3 internal fn (handle_paste, current_answer_mut, current_field_is_select).

McpServerElicitationOverlay::flush_paste_burst_if_due1675–1677 ↗
fn flush_paste_burst_if_due(&mut self) -> bool

Purpose: Lets the composer finish a grouped paste operation when enough time has passed. This helps large pasted text arrive as one coherent edit.

Data flow: It asks the composer to flush any due paste burst and returns whether that changed anything.

Call relations: The bottom pane calls this during input processing or periodic checks while paste handling is active.

Call graph: calls 1 internal fn (flush_paste_burst_if_due).

McpServerElicitationOverlay::is_in_paste_burst1679–1681 ↗
fn is_in_paste_burst(&self) -> bool

Purpose: Reports whether the composer is currently collecting a burst of paste events. The UI can use this to avoid acting too early.

Data flow: It asks the composer for its paste-burst state and returns the result.

Call relations: The bottom pane calls this as part of paste handling coordination.

Call graph: calls 1 internal fn (is_in_paste_burst).

McpServerElicitationOverlay::try_consume_mcp_server_elicitation_request1683–1689 ↗
fn try_consume_mcp_server_elicitation_request(
        &mut self,
        request: McpServerElicitationFormRequest,
    ) -> Option<McpServerElicitationFormRequest>

Purpose: Queues another elicitation request behind the current one. The overlay handles requests in first-in, first-out order, like a line at a counter.

Data flow: It receives a form request, pushes it to the back of the queue, and returns no unconsumed request.

Call relations: The bottom pane calls this when a new MCP elicitation arrives while an overlay is already active.

Call graph: 1 external calls (push_back).

McpServerElicitationOverlay::dismiss_app_server_request1691–1693 ↗
fn dismiss_app_server_request(&mut self, request: &ResolvedAppServerRequest) -> bool

Purpose: Responds to a notification that an app-server request has been resolved elsewhere. It removes matching active or queued elicitation requests.

Data flow: It receives a resolved request description and forwards it to the internal dismissal helper, returning whether anything was dismissed.

Call relations: The bottom pane view interface calls this when app-level request state changes.

Call graph: calls 1 internal fn (dismiss_resolved_request).

tests::test_sender1746–1749 ↗
fn test_sender() -> (AppEventSender, UnboundedReceiver<AppEvent>)

Purpose: Creates a test event sender and receiver pair. Tests use it to inspect what the overlay sends back to the app.

Data flow: It creates an unbounded channel, wraps the sender in AppEventSender, and returns both sender and receiver.

Call relations: Submission and cancellation tests call this before creating an overlay.

Call graph: calls 1 internal fn (new).

tests::form_request1751–1767 ↗
fn form_request(
        message: &str,
        requested_schema: Value,
        meta: Option<Value>,
    ) -> McpServerElicitationRequestParams

Purpose: Builds a raw MCP form request for tests. It keeps test setup short and consistent.

Data flow: It receives a message, JSON schema, and optional metadata, converts the schema into the protocol type, and returns request parameters with fixed thread and server names.

Call relations: Most parser and overlay tests call this before passing the request through the real form parser.

Call graph: 1 external calls (from_value).

tests::request_id1769–1771 ↗
fn request_id(value: &str) -> AppServerRequestId

Purpose: Creates a test request id from text. This avoids repeating the protocol wrapper in every test.

Data flow: It receives a string, turns it into a protocol request id, and returns it.

Call relations: Request-building tests and expected event assertions call this.

Call graph: 1 external calls (String).

tests::from_form_request1773–1782 ↗
fn from_form_request(
        thread_id: ThreadId,
        request: McpServerElicitationRequestParams,
    ) -> Option<McpServerElicitationFormRequest>

Purpose: Runs a test request through the production parser. This makes tests exercise the same conversion path used by the app.

Data flow: It receives a thread id and request parameters, adds a standard test request id, and returns the parsed form request if supported.

Call relations: Most tests call this before constructing overlays or checking parsed request fields.

Call graph: calls 1 internal fn (from_app_server_request); 1 external calls (request_id).

tests::empty_object_schema1784–1789 ↗
fn empty_object_schema() -> Value

Purpose: Returns the message-only object schema used by approval fallback tests. It represents a form with no actual fields.

Data flow: It creates and returns JSON with object type and an empty properties map.

Call relations: Approval, message-only, and tool-suggestion tests call this as their requested schema.

Call graph: 1 external calls (json!).

tests::tool_approval_meta1791–1832 ↗
fn tool_approval_meta(
        persist_modes: &[&str],
        tool_params: Option<Value>,
        tool_params_display: Option<Vec<(&str, Value, &str)>>,
    ) -> Option<Value>

Purpose: Builds metadata for tool-approval tests. It can include persistence modes, raw tool parameters, and display-specific parameter summaries.

Data flow: It receives persistence modes and optional parameter data, creates a metadata object with the proper approval kind, inserts optional fields, and returns it as JSON.

Call relations: Tool approval parsing, submission, and snapshot tests use this helper.

Call graph: 4 external calls (Array, Object, String, from_iter).

tests::snapshot_buffer1834–1844 ↗
fn snapshot_buffer(buf: &Buffer) -> String

Purpose: Turns a rendered terminal buffer into plain text for snapshot tests. This makes visual output easy to compare.

Data flow: It receives a buffer, walks each cell row by row, collects visible symbols into strings, joins rows with newlines, and returns the snapshot text.

Call relations: render_snapshot calls this after rendering an overlay.

Call graph: 3 external calls (area, new, new).

tests::render_snapshot1846–1850 ↗
fn render_snapshot(overlay: &McpServerElicitationOverlay, area: Rect) -> String

Purpose: Renders an overlay into an empty buffer and returns its text snapshot. Snapshot tests use this to lock down the visible UI.

Data flow: It receives an overlay and screen area, creates an empty buffer, renders the overlay into it, converts the buffer to text, and returns that text.

Call relations: The visual snapshot tests call this before comparing against stored snapshots.

Call graph: calls 1 internal fn (render); 2 external calls (empty, snapshot_buffer).

tests::parses_boolean_form_request1853–1908 ↗
fn parses_boolean_form_request()

Purpose: Checks that a boolean schema becomes a required true/false select field. This protects the basic form parsing behavior.

Data flow: It builds a boolean form request, parses it, and compares the result to the expected internal request structure.

Call relations: This test exercises from_app_server_request, from_parts, and field parsing through the public test helper path.

Call graph: calls 1 internal fn (default); 4 external calls (assert_eq!, json!, form_request, from_form_request).

tests::unsupported_numeric_form_falls_back1911–1930 ↗
fn unsupported_numeric_form_falls_back()

Purpose: Checks that numeric fields are rejected because this overlay does not support number input. This prevents unsupported schemas from being shown incorrectly.

Data flow: It builds a request with an integer field, parses it, and expects no form request.

Call relations: This test verifies the unsupported branch in field parsing.

Call graph: calls 1 internal fn (default); 4 external calls (assert_eq!, json!, form_request, from_form_request).

tests::empty_object_schema_uses_approval_actions1933–1983 ↗
fn empty_object_schema_uses_approval_actions()

Purpose: Checks that an empty object schema becomes a generic approval prompt. This is the fallback for message-only requests.

Data flow: It builds a request with no properties, parses it, and compares the generated Allow, Deny, and Cancel options to the expected structure.

Call relations: This test covers approval fallback construction in from_parts.

Call graph: calls 1 internal fn (default); 4 external calls (assert_eq!, empty_object_schema, form_request, from_form_request).

tests::empty_tool_approval_schema_uses_approval_actions1986–2035 ↗
fn empty_tool_approval_schema_uses_approval_actions()

Purpose: Checks that a message-only tool approval gets tool-specific wording and choices. Tool approvals should offer running or canceling the tool.

Data flow: It builds an empty-schema request with tool-approval metadata, parses it, and compares the resulting approval field to the expected tool approval options.

Call relations: This test covers the tool-approval branch of from_parts.

Call graph: calls 1 internal fn (default); 5 external calls (assert_eq!, empty_object_schema, form_request, from_form_request, tool_approval_meta).

tests::tool_suggestion_meta_is_parsed_into_request_payload2038–2068 ↗
fn tool_suggestion_meta_is_parsed_into_request_payload()

Purpose: Checks that connector tool-suggestion metadata is parsed into a structured request. This ensures the UI can recognize install suggestions.

Data flow: It builds a message-only request with connector suggestion metadata, parses it, and compares the parsed tool-suggestion payload to the expected values.

Call relations: This test exercises parse_tool_suggestion_request through normal request parsing.

Call graph: calls 1 internal fn (default); 5 external calls (assert_eq!, json!, empty_object_schema, form_request, from_form_request).

tests::plugin_tool_suggestion_meta_without_install_url_is_parsed_into_request_payload2071–2100 ↗
fn plugin_tool_suggestion_meta_without_install_url_is_parsed_into_request_payload()

Purpose: Checks that plugin suggestions work even when no install URL is provided. Not every tool suggestion needs a URL.

Data flow: It builds suggestion metadata without an install URL, parses the request, and verifies the optional URL is absent while other fields are preserved.

Call relations: This test covers the optional-field path in tool suggestion parsing.

Call graph: calls 1 internal fn (default); 5 external calls (assert_eq!, json!, empty_object_schema, form_request, from_form_request).

tests::tool_approval_display_params_prefer_explicit_display_order2103–2147 ↗
fn tool_approval_display_params_prefer_explicit_display_order()

Purpose: Checks that explicit display parameters override raw parameter fallback order. This keeps approval summaries in the order chosen by the server.

Data flow: It builds tool-approval metadata with both raw parameters and explicit display parameters, parses the request, and verifies the explicit ordered list is used.

Call relations: This test exercises parse_tool_approval_display_params through request parsing.

Call graph: calls 1 internal fn (default); 7 external calls (assert_eq!, json!, empty_object_schema, form_request, from_form_request, tool_approval_meta, vec!).

tests::submit_sends_accept_with_typed_content2150–2201 ↗
fn submit_sends_accept_with_typed_content()

Purpose: Checks that submitting a normal form sends an accept event with JSON content. This proves the overlay turns selected answers into the protocol response.

Data flow: It creates an overlay for a boolean field, commits the current option, submits, receives the emitted event, and compares it to the expected resolve operation.

Call relations: This test covers option committing, submit_answers, and event sending.

Call graph: calls 2 internal fn (default, new); 6 external calls (assert_eq!, panic!, json!, form_request, from_form_request, test_sender).

tests::horizontal_list_keys_move_between_select_fields2204–2238 ↗
fn horizontal_list_keys_move_between_select_fields()

Purpose: Checks that configured horizontal movement keys can switch between select fields. This protects keyboard navigation behavior.

Data flow: It creates a two-field select form, sends movement key events, and asserts that the current field index changes forward and backward.

Call relations: This test exercises handle_key_event and field navigation for select fields.

Call graph: calls 2 internal fn (default, new); 7 external calls (Char, new, assert_eq!, json!, form_request, from_form_request, test_sender).

tests::empty_tool_approval_schema_session_choice_sets_persist_meta2241–2292 ↗
fn empty_tool_approval_schema_session_choice_sets_persist_meta()

Purpose: Checks that choosing “allow for this session” sends persistence metadata. The server needs this metadata to remember the choice only for the current session.

Data flow: It creates a tool approval with session and always options, selects the session option, submits, and verifies the emitted accept event includes session persistence metadata.

Call relations: This test covers approval option mapping inside submit_answers.

Call graph: calls 2 internal fn (default, new); 7 external calls (assert_eq!, panic!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta).

tests::empty_tool_approval_schema_always_allow_sets_persist_meta2295–2346 ↗
fn empty_tool_approval_schema_always_allow_sets_persist_meta()

Purpose: Checks that choosing “always allow” sends permanent persistence metadata. This protects the behavior of remembered approvals.

Data flow: It creates a tool approval with persistence options, selects the always option, submits, and verifies the emitted accept event includes always persistence metadata.

Call relations: This test covers another approval option mapping path inside submit_answers.

Call graph: calls 2 internal fn (default, new); 7 external calls (assert_eq!, panic!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta).

tests::ctrl_c_cancels_elicitation2349–2397 ↗
fn ctrl_c_cancels_elicitation()

Purpose: Checks that Ctrl+C cancels an elicitation when there is no text to clear. This protects the emergency-exit behavior.

Data flow: It creates an overlay, calls the Ctrl+C handler, receives the emitted event, and verifies it is a cancel resolution for the request.

Call relations: This test exercises on_ctrl_c, dispatch_cancel, and event sending.

Call graph: calls 2 internal fn (default, new); 6 external calls (assert_eq!, panic!, json!, form_request, from_form_request, test_sender).

tests::queues_requests_fifo2400–2469 ↗
fn queues_requests_fifo()

Purpose: Checks that multiple elicitation requests are handled in first-in, first-out order. This prevents later prompts from jumping ahead of earlier ones.

Data flow: It creates three requests, starts the overlay with the first, queues the second and third, submits each current request, and checks that the active message advances to the next queued request.

Call relations: This test exercises request queuing, submission, and advance_queue_or_complete.

Call graph: calls 2 internal fn (default, new); 5 external calls (assert_eq!, json!, form_request, from_form_request, test_sender).

tests::resolved_request_dismisses_overlay_without_emitting_events2472–2537 ↗
fn resolved_request_dismisses_overlay_without_emitting_events()

Purpose: Checks that externally resolved requests are removed without sending duplicate responses. This avoids answering a request twice.

Data flow: It creates an active request and a queued request, dismisses each by id, verifies the overlay advances or completes, and confirms no event was emitted.

Call relations: This test exercises dismiss_app_server_request and dismiss_resolved_request.

Call graph: calls 3 internal fn (default, from_app_server_request, new); 8 external calls (assert!, assert_eq!, from_value, json!, form_request, from_form_request, request_id, test_sender).

tests::boolean_form_snapshot2540–2570 ↗
fn boolean_form_snapshot()

Purpose: Captures the rendered appearance of a boolean form. This guards against accidental UI layout changes.

Data flow: It builds a boolean overlay, renders it into a fixed-size buffer, and compares the text snapshot.

Call relations: This test uses the real render path through render_snapshot.

Call graph: calls 2 internal fn (default, new); 5 external calls (assert_snapshot!, json!, form_request, from_form_request, test_sender).

tests::approval_form_tool_approval_snapshot2573–2597 ↗
fn approval_form_tool_approval_snapshot()

Purpose: Captures the rendered appearance of a basic tool-approval prompt. This protects the visual wording and layout.

Data flow: It builds a tool-approval overlay, renders it into a fixed-size buffer, and compares the snapshot.

Call relations: This test exercises request parsing and full overlay rendering for tool approvals.

Call graph: calls 2 internal fn (default, new); 6 external calls (assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta).

tests::message_only_form_snapshot2600–2620 ↗
fn message_only_form_snapshot()

Purpose: Captures the rendered appearance of a generic message-only approval prompt. This protects the approval fallback UI.

Data flow: It builds an empty-schema message-only overlay, renders it, and compares the snapshot.

Call relations: This test covers the approval fallback render path.

Call graph: calls 2 internal fn (default, new); 5 external calls (assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender).

tests::message_only_form_with_persist_options_snapshot2623–2648 ↗
fn message_only_form_with_persist_options_snapshot()

Purpose: Captures the UI when a message-only approval includes persistence choices. This ensures the extra options fit and read correctly.

Data flow: It builds an approval request with session and always persistence metadata, renders the overlay, and compares the snapshot.

Call relations: This test covers persistent approval option rendering.

Call graph: calls 2 internal fn (default, new); 6 external calls (assert_snapshot!, json!, empty_object_schema, form_request, from_form_request, test_sender).

tests::approval_form_tool_approval_with_persist_options_snapshot2651–2678 ↗
fn approval_form_tool_approval_with_persist_options_snapshot()

Purpose: Captures the UI for a tool approval with persistence choices. Tool-specific wording and extra options are both checked.

Data flow: It builds a tool approval with session and always options, renders the overlay, and compares the snapshot.

Call relations: This test covers the render path for persistent tool approvals.

Call graph: calls 2 internal fn (default, new); 6 external calls (assert_snapshot!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta).

tests::approval_form_tool_approval_with_param_summary_snapshot2681–2731 ↗
fn approval_form_tool_approval_with_param_summary_snapshot()

Purpose: Captures the UI for a tool approval that shows parameter summaries. This ensures important tool details appear without overwhelming the prompt.

Data flow: It builds tool-approval metadata with several display parameters, renders the overlay, and compares the snapshot.

Call relations: This test covers parameter parsing, parameter formatting, prompt building, and full rendering.

Call graph: calls 2 internal fn (default, new); 8 external calls (assert_snapshot!, json!, empty_object_schema, form_request, from_form_request, test_sender, tool_approval_meta, vec!).

tui/src/bottom_pane/request_user_input/render.rssource ↗
domain_logicrequest handling

This file is the visual side of a bottom-pane prompt: the part of the app that asks the user to answer questions. Without it, the app might still know what question to ask, but the user would not see a clear prompt, selectable options, a notes box, progress text, or keyboard hints.

The code works like a careful page layout designer for a very small screen. First it estimates how much height the overlay needs. It accounts for the question text, answer options, an optional notes composer, progress text, spacing, and footer tips. Then, when asked to render, it paints a shared popup-style surface and fills the inside area section by section.

The normal view shows progress at the top, then the current question, then any answer options, then the notes input if it is visible, and finally footer hints such as key commands. If there are too many options to show, it keeps the selected option visible and adds a small “option X/Y” tip. Secret answers are masked with asterisks, like a password field.

There is also a separate confirmation view for leaving questions unanswered. That view shows a title, a count of unanswered questions, a scrollable list, and hint text. A few helper functions keep text wrapping, bottom-aligned option rows, and footer truncation tidy so the overlay remains readable in narrow terminals.

Function details14
line_to_owned47–60 ↗
fn line_to_owned(line: Line<'_>) -> Line<'static>

Purpose: This helper turns a styled terminal text line that may borrow text from elsewhere into a fully owned line. That matters because some layout data is stored and reused after wrapping, and it needs text that will not disappear when temporary values go away.

Data flow: It receives one styled line made of styled spans. It copies the line’s style, alignment, and every span’s text into owned strings, then returns a new line with the same visual appearance but independent storage.

Call relations: The unanswered-confirmation layout uses this after wrapping title, subtitle, and hint text. It acts as a small safety step between temporary wrapped lines and the longer-lived layout structure used for measuring and drawing.

RequestUserInputOverlay::desired_height63–106 ↗
fn desired_height(&self, width: u16) -> u16

Purpose: This tells the rest of the terminal UI how much vertical space this overlay would like. It helps prevent the prompt from being cramped or cutting off important parts such as options, notes, or footer hints.

Data flow: It receives the available width. If the unanswered-question confirmation screen is active, it delegates to that screen’s height calculation. Otherwise it calculates the inner popup width, counts wrapped question lines, option rows, notes height, spacing, footer height, and popup padding, then returns at least a fixed minimum height.

Call relations: The broader rendering system asks this through the Renderable interface before drawing. It relies on shared popup-sizing helpers so this overlay sizes itself consistently with other bottom-pane popups.

Call graph: calls 3 internal fn (unanswered_confirmation_height, menu_surface_inset, menu_surface_padding_height); 1 external calls (new).

RequestUserInputOverlay::render108–110 ↗
fn render(&self, area: Rect, buf: &mut Buffer)

Purpose: This is the standard render entry for the overlay. It draws the current state of the prompt into the terminal buffer using the current time for time-sensitive text such as an auto-resolution countdown.

Data flow: It receives a screen rectangle and a mutable terminal buffer. It gets the current time, passes the area, buffer, and time into the main rendering routine, and leaves the buffer changed to contain the overlay’s visual content.

Call relations: The terminal UI calls this through the Renderable interface. It is a thin wrapper that hands control to RequestUserInputOverlay::render_ui_at, which does the real drawing.

Call graph: calls 1 internal fn (render_ui_at); 1 external calls (now).

RequestUserInputOverlay::cursor_pos112–114 ↗
fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)>

Purpose: This reports where the text cursor should appear when the user is typing notes. If notes are not currently editable or visible, it reports no cursor.

Data flow: It receives the overlay’s screen rectangle. It asks the internal cursor-position routine to calculate the cursor location based on the current layout and focus state, then returns either screen coordinates or nothing.

Call relations: The terminal UI calls this through the Renderable interface when it needs to place the hardware cursor. It delegates to RequestUserInputOverlay::cursor_pos_impl so the layout-specific checks stay in one place.

Call graph: calls 1 internal fn (cursor_pos_impl).

RequestUserInputOverlay::unanswered_confirmation_data118–131 ↗
fn unanswered_confirmation_data(&self) -> UnansweredConfirmationData

Purpose: This gathers the raw content needed for the confirmation screen shown when some questions are still unanswered. It prepares the title, the count of unanswered questions, the hint line, the list rows, and the scroll position.

Data flow: It reads the overlay’s unanswered-question count, unanswered rows, and confirmation scroll state. It builds a bold title, a dim subtitle like “2 unanswered questions,” the standard popup hint line, and returns them bundled together.

Call relations: This is the first step in building the unanswered-confirmation view. RequestUserInputOverlay::unanswered_confirmation_layout calls it, then wraps the text to fit the available width.

Call graph: calls 1 internal fn (standard_popup_hint_line); called by 1 (unanswered_confirmation_layout); 2 external calls (from, format!).

RequestUserInputOverlay::unanswered_confirmation_layout133–150 ↗
fn unanswered_confirmation_layout(&self, width: u16) -> UnansweredConfirmationLayout

Purpose: This turns the unanswered-confirmation content into a layout that fits a particular width. It wraps long title, subtitle, and hint text so they do not spill past the popup edge.

Data flow: It receives a width. It gathers confirmation data, wraps the header and hint lines to that width, converts those wrapped lines into owned text, and returns a layout containing header lines, hint lines, rows, and scroll state.

Call relations: Both measuring and drawing use this function. The height calculation calls it to know how many lines are needed, and the render function calls it to draw the same wrapped content on screen.

Call graph: calls 2 internal fn (unanswered_confirmation_data, wrap_styled_line); called by 2 (render_unanswered_confirmation, unanswered_confirmation_height).

RequestUserInputOverlay::unanswered_confirmation_height152–170 ↗
fn unanswered_confirmation_height(&self, width: u16) -> u16

Purpose: This calculates how tall the unanswered-confirmation popup should be. It makes room for the wrapped header, the scrollable unanswered list, hint text, spacing, and popup padding.

Data flow: It receives the available outer width. It computes the inner popup width, builds the confirmation layout, measures how tall the row list wants to be, adds header and hint lines plus spacer rows and padding, then returns at least the minimum overlay height.

Call relations: The general height request path calls this when the confirmation screen is active. It shares layout and row-measuring helpers with the renderer so the measured height matches what will actually be drawn.

Call graph: calls 4 internal fn (unanswered_confirmation_layout, measure_rows_height, menu_surface_inset, menu_surface_padding_height); called by 1 (desired_height); 1 external calls (new).

RequestUserInputOverlay::render_unanswered_confirmation172–246 ↗
fn render_unanswered_confirmation(&self, area: Rect, buf: &mut Buffer)

Purpose: This draws the confirmation screen for unanswered questions. It shows a header, a scrollable list of unanswered items, and hint text inside the standard popup surface.

Data flow: It receives a screen area and terminal buffer. It paints the popup surface, builds the wrapped confirmation layout, writes header lines from the top, leaves a spacer, renders the unanswered rows in the remaining middle area, then writes hint lines near the bottom.

Call relations: RequestUserInputOverlay::render_ui_at calls this instead of the normal question UI whenever unanswered-confirmation mode is active. It hands the row list to the shared row renderer so scrolling and selection styling match other popup menus.

Call graph: calls 3 internal fn (unanswered_confirmation_layout, render_menu_surface, render_rows); called by 1 (render_ui_at); 2 external calls (new, from).

RequestUserInputOverlay::render_ui_at248–380 ↗
fn render_ui_at(&self, area: Rect, buf: &mut Buffer, now: Instant)

Purpose: This is the main drawing routine for the request-input overlay. It paints the normal question screen, including progress, question text, answer options, notes input, and footer tips.

Data flow: It receives a screen area, terminal buffer, and current time. If the area is empty it does nothing. If unanswered confirmation is active, it draws that screen and stops. Otherwise it paints the popup surface, lays out internal sections, draws progress and countdown text, highlights unanswered question text, renders options, renders notes if visible, and writes footer hint lines trimmed to fit the width.

Call relations: RequestUserInputOverlay::render calls this as the main renderer. Inside, it coordinates several smaller helpers: unanswered confirmation rendering for the alternate screen, bottom-aligned row rendering for options, notes rendering for typed text, and line truncation for footer hints that would otherwise run past the edge.

Call graph: calls 6 internal fn (new, render_notes_input, render_unanswered_confirmation, render_rows_bottom_aligned, truncate_line_word_boundary_with_ellipsis, render_menu_surface); called by 1 (render); 5 external calls (from, new, new, format!, vec!).

RequestUserInputOverlay::cursor_pos_impl383–406 ↗
fn cursor_pos_impl(&self, area: Rect) -> Option<(u16, u16)>

Purpose: This calculates the cursor position for the notes editor, but only when showing a cursor makes sense. It prevents the cursor from appearing on the wrong screen or when the notes field is hidden.

Data flow: It receives the overlay area. It checks whether unanswered confirmation is active, whether focus is on notes, whether notes are visible, and whether the content and notes areas have size. If all checks pass, it asks the composer for the cursor position inside the notes area and returns it.

Call relations: RequestUserInputOverlay::cursor_pos calls this when the terminal UI asks where to place the cursor. It uses the same section layout as rendering, so the cursor lines up with the visible notes input.

Call graph: calls 1 internal fn (menu_surface_inset); called by 1 (cursor_pos).

RequestUserInputOverlay::render_notes_input409–421 ↗
fn render_notes_input(&self, area: Rect, buf: &mut Buffer)

Purpose: This draws the notes text box. If the current question is marked secret, it masks the typed text with asterisks so sensitive input is not displayed openly.

Data flow: It receives a notes area and terminal buffer. If the area is empty it does nothing. Otherwise it checks whether the current question is secret, then asks the composer to render either masked text or normal text into the buffer.

Call relations: RequestUserInputOverlay::render_ui_at calls this only when the notes UI is visible and has space. It delegates the actual text-editor drawing to the composer, adding only the secret-input decision.

Call graph: called by 1 (render_ui_at).

line_width424–428 ↗
fn line_width(line: &Line<'_>) -> usize

Purpose: This measures how wide a styled line will appear in the terminal. It counts display width rather than just characters, because some Unicode characters take more or less space than ordinary letters.

Data flow: It receives a styled line made of spans. It adds up the terminal display width of each span’s text and returns the total width as a number.

Call relations: The footer truncation helper calls this before shortening text. It is the quick check that decides whether a line already fits or needs an ellipsis.

Call graph: called by 1 (truncate_line_word_boundary_with_ellipsis); 1 external calls (iter).

render_rows_bottom_aligned435–470 ↗
fn render_rows_bottom_aligned(
    area: Rect,
    buf: &mut Buffer,
    rows: &[crate::bottom_pane::selection_popup_common::GenericDisplayRow],
    state: &ScrollState,
    max_results: usize,
    em

Purpose: This draws selectable rows so that, when there are fewer visible rows than the available space, they sit at the bottom of their allotted area. That keeps the footer spacing steady instead of making the options block float upward.

Data flow: It receives a target area, terminal buffer, rows, scroll state, a maximum row count, and an empty-list message. It copies the target area into a temporary buffer, renders rows at the top of that temporary buffer, measures how many lines were actually drawn, then copies those lines back into the real buffer shifted downward as needed.

Call relations: RequestUserInputOverlay::render_ui_at uses this for answer options. It wraps the shared render_rows function with the extra bottom-alignment behavior needed by this overlay’s layout.

Call graph: calls 1 internal fn (render_rows); called by 1 (render_ui_at); 2 external calls (empty, new).

truncate_line_word_boundary_with_ellipsis479–578 ↗
fn truncate_line_word_boundary_with_ellipsis(
    line: Line<'static>,
    max_width: usize,
) -> Line<'static>

Purpose: This shortens a styled line so it fits within a given width, adding an ellipsis when text is cut off. It tries to cut at a word boundary first, so footer hints stay readable instead of ending in the middle of a word when possible.

Data flow: It receives a styled line and a maximum display width. If the line already fits, it returns it unchanged. If not, it reserves room for an ellipsis, walks through the text character by character while respecting Unicode display width, chooses the best safe cut point, removes trailing spaces, adds a styled ellipsis, and returns the shortened line.

Call relations: RequestUserInputOverlay::render_ui_at uses this when drawing footer tips. It depends on line_width for the initial fit check, then preserves span styling so highlighted or dimmed footer text still looks consistent after truncation.

Call graph: calls 1 internal fn (line_width); called by 1 (render_ui_at); 6 external calls (from, styled, width, width, new, new).