Skip to content

fix(ui): stable stack layout prevents search focus reset on first keystroke - #89

Merged
thewrz merged 5 commits into
mainfrom
fix/search-focus-reset
May 17, 2026
Merged

fix(ui): stable stack layout prevents search focus reset on first keystroke#89
thewrz merged 5 commits into
mainfrom
fix/search-focus-reset

Conversation

@thewrz

@thewrz thewrz commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #87

Summary

  • view_search_bar() previously returned container(input) for empty query and stack![input, overlay] for non-empty query — a structural widget tree change that caused Iced's reconciler to treat the text_input as a new instance on first keystroke, resetting focus
  • Fix: always emit stack![input, overlay] regardless of query state; overlay is row![].into() (no hit area, no render cost) when empty, and the ✕ clear button container when non-empty
  • Widget tree shape is now stable across all query states — Iced preserves text_input identity and focus

Test Plan

  • cargo test — 178 tests pass
  • cargo clippy -- -D warnings — zero warnings
  • cargo fmt -- --check — clean
  • Manual: click search box once, type multiple characters without re-clicking — all characters appear
  • Manual: click outside search box — focus leaves
  • Manual: type a character, click ✕ — box clears

Summary by CodeRabbit

  • Bug Fixes

    • Fixed search bar focus loss so typing remains uninterrupted after the first character.
  • New Features

    • Escape key now closes any open context menu first; otherwise it first blurs the search (if focused) and on subsequent presses clears the query.
    • Search input shows a clear (×) button when non-empty.
  • Documentation

    • Added plan and approved design/spec detailing the fix and verification steps.
  • Tests

    • Added unit tests covering Escape and search-focus behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c5292fe5-8c6d-488a-a0f2-e6521274afe4

📥 Commits

Reviewing files that changed from the base of the PR and between 15cfa10 and 8ae8c7d.

📒 Files selected for processing (1)
  • src/app.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app.rs

📝 Walkthrough

Walkthrough

Stabilizes the Iced search bar widget tree by always rendering stack![input, overlay] (empty row![] when query is empty) and adds Message::EscapePressed plus search_had_focus state to handle Escape (consume blur or clear query) with unit tests.

Changes

Search bar focus bug fix

Layer / File(s) Summary
Problem diagnosis and solution design
docs/superpowers/specs/2026-05-16-search-focus-bug-design.md, docs/superpowers/plans/2026-05-16-search-focus-bug.md
Design spec and implementation plan document that view_search_bar() previously emitted different widget tree shapes (container vs stack) depending on query, causing text_input identity mismatch and focus reset. The plan prescribes always emitting stack![input, overlay] with an empty row![] for empty query and the clear-button overlay when non-empty, plus verification steps.
Widget tree stabilization implementation
src/ui/search_bar.rs
view_search_bar() always constructs an overlay and returns stack![input, overlay]; when query is empty overlay is row![] (no hit area), otherwise it renders the positioned clear () button. Updated imports to include row.
App Escape handling and search focus state
src/app.rs
Added Message::EscapePressed, search_had_focus state (initialized false in constructors), set on SearchChanged; keyboard mapping sends EscapePressed. update closes context menu first, then either consumes blur on first Escape (if search_had_focus) or clears the query. Unit tests added for these flows.

Sequence Diagram(s)

sequenceDiagram
  participant App as HonkHonk
  participant View as view_search_bar()
  participant Iced as Iced_reconciler
  participant TextInput as text_input
  App->>View: render search bar (always stack![input, overlay])
  View->>Iced: emit stable widget tree
  Iced->>TextInput: reuse widget instance (preserve focus)
  Note over App: Keyboard subscription maps Escape -> Message::EscapePressed
  App->>App: on Message::EscapePressed -> update: close menu / consume blur / clear query
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • wrzonance/HonkHonk#50: Earlier view_search_bar implementation in the same file; directly related to the widget-tree stabilization changes here.

"I hopped around the keyboard keys with glee,
A tiny bug chased every typed melody.
Now stack holds firm, focus stays in sight,
Escape whispers calm, and the search feels right. 🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main technical fix: ensuring a stable widget stack layout to prevent search focus reset on first keystroke.
Linked Issues check ✅ Passed The PR successfully addresses issue #87: stable stack layout preserves text_input identity, Escape handling with search_had_focus flag prevents focus reset, and all verification steps are documented.
Out of Scope Changes check ✅ Passed All changes are within scope: search_bar.rs fix for stable layout, app.rs Escape/focus-flag logic, and comprehensive documentation plans directly align with issue #87 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/search-focus-reset

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app.rs (1)

468-479: ⚡ Quick win

Consider adding unit tests for the new Escape key behavior.

The new EscapePressed message and search_had_focus state machine lack automated test coverage. While the PR objectives mention manual testing, unit tests would prevent regressions and document the intended behavior.

Suggested test cases:

  • First Escape sets search_had_focus to false when true
  • Second Escape clears search_query when non-empty
  • Escape closes context menu when open
  • SearchChanged sets search_had_focus to true
📋 Example test structure
#[test]
fn escape_pressed_consumes_search_focus_flag() {
    let mut app = HonkHonk::new_for_test();
    let _ = app.update(Message::SearchChanged("honk".into()));
    assert!(app.search_had_focus);
    let _ = app.update(Message::EscapePressed);
    assert!(!app.search_had_focus);
    assert_eq!(app.search_query(), "honk"); // query not cleared yet
}

#[test]
fn escape_pressed_clears_query_after_focus_consumed() {
    let mut app = HonkHonk::new_for_test();
    let _ = app.update(Message::SearchChanged("honk".into()));
    let _ = app.update(Message::EscapePressed); // consume focus
    let _ = app.update(Message::EscapePressed); // clear query
    assert_eq!(app.search_query(), "");
}

#[test]
fn escape_pressed_closes_context_menu() {
    let mut app = HonkHonk::new_for_test();
    let _ = app.update(Message::OpenContextMenu("test-id".into()));
    let _ = app.update(Message::EscapePressed);
    assert!(app.context_menu().is_none());
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app.rs` around lines 468 - 479, Add unit tests covering the Escape key
behavior around Message::EscapePressed and the search focus state machine:
create tests that use HonkHonk::new_for_test() and call
app.update(Message::SearchChanged(...)) to assert search_had_focus becomes true,
then call app.update(Message::EscapePressed) to assert search_had_focus becomes
false without clearing search_query() on the first press, then call
app.update(Message::EscapePressed) again to assert search_query() is cleared;
add a separate test that opens the context menu via
app.update(Message::OpenContextMenu(...)) and then
app.update(Message::EscapePressed) to assert context_menu() is None; place tests
alongside existing app unit tests and make sure to use the public accessors
(search_query(), context_menu()) and update() to drive state transitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app.rs`:
- Around line 468-479: The EscapePressed handler currently clears
self.context_menu and also modifies self.search_had_focus, which consumes the
blur step; change the logic in Message::EscapePressed so that if a context menu
is open (self.context_menu.is_some()), you clear self.context_menu and
self.context_menu_pos and immediately return Task::none() without touching
self.search_had_focus or self.search_query, preserving the two-Escape semantics;
otherwise keep the existing blur/clear behavior for search_had_focus and
search_query.

---

Nitpick comments:
In `@src/app.rs`:
- Around line 468-479: Add unit tests covering the Escape key behavior around
Message::EscapePressed and the search focus state machine: create tests that use
HonkHonk::new_for_test() and call app.update(Message::SearchChanged(...)) to
assert search_had_focus becomes true, then call
app.update(Message::EscapePressed) to assert search_had_focus becomes false
without clearing search_query() on the first press, then call
app.update(Message::EscapePressed) again to assert search_query() is cleared;
add a separate test that opens the context menu via
app.update(Message::OpenContextMenu(...)) and then
app.update(Message::EscapePressed) to assert context_menu() is None; place tests
alongside existing app unit tests and make sure to use the public accessors
(search_query(), context_menu()) and update() to drive state transitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fe87edd-edf4-4401-8c32-7daefa07a11c

📥 Commits

Reviewing files that changed from the base of the PR and between 6bcd212 and 15cfa10.

📒 Files selected for processing (1)
  • src/app.rs

Comment thread src/app.rs
@thewrz

thewrz commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@thewrz
thewrz merged commit 4049b10 into main May 17, 2026
8 checks passed
@thewrz
thewrz deleted the fix/search-focus-reset branch May 17, 2026 01:52
@thewrz thewrz added this to HonkHonk Jun 21, 2026
@github-project-automation github-project-automation Bot moved this to Done in HonkHonk Jun 21, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Search box bug, single character and cursor activation issue

1 participant