You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wire keyboard shortcuts and overlay state management into the winit event loop. All browser interaction is keyboard-driven: no visible menu bar, no persistent toolbars. Overlays (address bar, tab list, bookmarks) appear on demand and dismiss with Escape.
Prerequisites
Step 4 (navigation service, tab model, bookmarks)
Implementation
Action enum and keybinding resolver (keybindings.rs)
CloseTab → tab_manager.close_tab(active_id), log. If no tabs left, exit.
NextTab → tab_manager.next_tab()
PrevTab → tab_manager.prev_tab()
ShowTabList → overlay = TabList
BookmarkCurrentPage → if active tab has URL: bookmark_store.add(url, title)
ShowBookmarks → overlay = Bookmarks
GoBack → tab_manager.go_back()
GoForward → tab_manager.go_forward()
DismissOverlay → overlay = None
Quit → event_loop.exit()
Navigation async bridge
On address bar submit (Enter):
URL parsing rules: bare hostnames always get https:// prepended. User must type http:// explicitly, which will be blocked unless --allow-http was passed.
Instead of polling try_recv() in about_to_wait, use EventLoopProxy::send_event() from the async task to wake the event loop. This avoids the polling problem where the event loop doesn't know when new data is available.
Parent: #2
Goal
Wire keyboard shortcuts and overlay state management into the winit event loop. All browser interaction is keyboard-driven: no visible menu bar, no persistent toolbars. Overlays (address bar, tab list, bookmarks) appear on demand and dismiss with Escape.
Prerequisites
Implementation
Action enum and keybinding resolver (
keybindings.rs)Actionenum — all possible user actions:fn resolve_keybinding(event: &KeyEvent, modifiers: &ModifiersState) -> Option<Action>:ElementState::Pressed(not release)event.logical_key:Key::Named(NamedKey::Escape)→DismissOverlayKey::Named(NamedKey::Tab)+ Ctrl →NextTab(+ Shift →PrevTab)Key::Named(NamedKey::ArrowLeft)+ Alt →GoBackKey::Named(NamedKey::ArrowRight)+ Alt →GoForwardKey::Character("l")+ Ctrl →ShowAddressBarKey::Character("t")+ Ctrl →NewTab(+ Shift →ShowTabList)Key::Character("w")+ Ctrl →CloseTabKey::Character("d")+ Ctrl →BookmarkCurrentPageKey::Character("b")+ Ctrl + Shift →ShowBookmarksKey::Character("q")+ Ctrl →QuitNonefor unmapped keysOverlay state machine (
overlay.rs)OverlayStateenum:AddressBarStatestruct:AddressBarState::new(initial: &str) -> Self— cursor at end of initial textAddressBarState::insert_char(&mut self, c: char)— insert at cursor, advance cursorAddressBarState::delete_back(&mut self)— backspace: remove char before cursor (no-op if cursor at 0)AddressBarState::delete_forward(&mut self)— delete: remove char at cursor (no-op if cursor at end)AddressBarState::move_left(&mut self)— decrement cursor (clamp at 0)AddressBarState::move_right(&mut self)— increment cursor (clamp at input.len())AddressBarState::move_home(&mut self)— cursor to 0AddressBarState::move_end(&mut self)— cursor to input.len()AddressBarState::submit(&self) -> &str— return current input for URL parsingOverlayState::is_active(&self) -> bool— true unlessNoneEvent loop integration (
app.rs)Browserstruct fields:WindowEvent::ModifiersChanged→ store inself.modifiersWindowEvent::KeyboardInputhandling:AddressBar:Enter→ parse input as URL, trigger navigation, dismiss overlayEscape→ dismiss overlayBackspace→delete_back()Delete→delete_forward()Left/Right/Home/End→ cursor movementinsert_char()resolve_keybinding(), dispatch actionWindowEvent::KeyboardInputwhereevent.textisSome(text)and no Ctrl/Alt modifier — feed characters to address barShowAddressBar→overlay = AddressBar(AddressBarState::new(current_url_or_empty))NewTab→tab_manager.new_tab(), logCloseTab→tab_manager.close_tab(active_id), log. If no tabs left, exit.NextTab→tab_manager.next_tab()PrevTab→tab_manager.prev_tab()ShowTabList→overlay = TabListBookmarkCurrentPage→ if active tab has URL:bookmark_store.add(url, title)ShowBookmarks→overlay = BookmarksGoBack→tab_manager.go_back()GoForward→tab_manager.go_forward()DismissOverlay→overlay = NoneQuit→event_loop.exit()Navigation async bridge
https://prepended. User must typehttp://explicitly, which will be blocked unless--allow-httpwas passed.Winit event loop wake-up fix
Instead of polling
try_recv()inabout_to_wait, useEventLoopProxy::send_event()from the async task to wake the event loop. This avoids the polling problem where the event loop doesn't know when new data is available.UserEventenum:EventLoop::with_user_event()to create the event loopEvent::UserEvent(UserEvent::NavigationComplete(tab_id, result))in the event handler:Ok(NavigationResult)→ update tab: state = Loaded, store source, update titleErr(NavigationError)→ update tab: state = Error(msg)Tracing output
Since there is no rendering in Phase 1, all state changes should be logged via
tracing::info!:info!("new tab: id={}", id)info!("close tab: id={}", id)info!("switch tab: id={}", id)info!("navigate: tab={}, url={}", id, url)info!("navigation complete: tab={}, status={}", id, status)info!("overlay: {:?}", overlay_state)info!("bookmark added: {}", url)info!("go_back: tab={}", id)info!("go_forward: tab={}", id)Tests
Keybinding tests (
keybindings.rs)ShowAddressBarNewTabShowTabListCloseTabNextTabPrevTabBookmarkCurrentPageShowBookmarksGoBackGoForwardDismissOverlayQuitNoneNoneNone(only press triggers)OverlayState tests (
overlay.rs)None.is_active()→ falseAddressBar.is_active()→ trueTabList.is_active()→ trueAddressBarState tests (
overlay.rs)new("https://example.com")→ input matches, cursor at endnew("")→ empty input, cursor at 0insert_char('a')on empty → input is "a", cursor is 1insert_charat middle: "abc" cursor=1, insert 'x' → "axbc", cursor=2delete_backremoves char before cursor: "abc" cursor=2 → "ac" cursor=1delete_backat cursor=0 → no changedelete_forwardat cursor=1 in "abc" → "ac" cursor=1delete_forwardat end → no changemove_leftdecrements cursor, clamps at 0move_rightincrements cursor, clamps at lenmove_home→ cursor=0move_end→ cursor=lensubmitreturns current input stringAction dispatch tests
These test the logic in isolation (without winit):
ShowAddressBarsets overlay to AddressBar with current URLNewTabincrements tab countCloseTabdecrements tab countNextTab/PrevTabchanges active tabBookmarkCurrentPageadds to bookmark storeGoBackcallstab_manager.go_back()GoForwardcallstab_manager.go_forward()DismissOverlayresets overlay to NoneAcceptance Criteria
cargo test -p ie-shell— all tests passcargo clippy -p ie-shell -- -D warnings— no warningscargo run -p ie-shell— window opens, Ctrl+L/T/W etc. produce tracing output