Add native folder dialog to pending project tabs#177
Merged
Conversation
Augments the existing inline "Project directory:" prompt with a clickable [ Browse… ] hint that opens a native folder picker (rfd) starting at $HOME, spawning the project tab immediately on selection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Require the main-thread dialog spawn pattern (build the rfd future in update(), await in Task::perform) instead of constructing the dialog inside the async block, which Iced polls off-main-thread - Guard open_project_from_path against stale tab_ids (dialog is non-modal; pending tab can be closed or submitted while it is open) - Add a re-entrancy guard against double-spawned dialogs - Put the pending check first in the mouse-press arm and swallow other mouse handling for pending tabs; add Pointer hover affordance - Acknowledge the missing App-level test harness and push testable logic into pure functions / TabStore methods - Pin down rfd version/feature notes, $HOME consistency, unparented dialog behavior, and a drag-and-drop follow-up enabled by the helper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo run --example rfd_spike: builds the AsyncFileDialog future synchronously in update() (main thread, verified by thread-name logging) and awaits it via Task::perform. On macOS / Iced 0.14 / Rust 1.94 the picker opens with the app responsive, returns the picked path, and returns None on cancel — no freezes or panics. rfd 0.17 lands as a dev-dependency for now; promote to a real dependency when the feature is wired. Spec's risk section updated with the validation result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A clickable [ Browse… ] hint below the Project directory: prompt opens the OS folder picker (rfd::AsyncFileDialog); picking a folder spawns the project tab exactly like typing the path and pressing Enter. The inline text prompt is unchanged. Implementation notes, per the design spec: - The dialog future is built inside update() (main thread — required by rfd on macOS) and only awaited via Task::perform. - The Submit arm's canonicalize → dedup → spawn → focus sequence moved into a shared open_project_from_path helper, which no-ops when the pending tab is gone: the dialog is non-modal, so its result can arrive after the tab was closed or submitted by typing. - An app-level project_dialog_open flag drops re-entrant Browse clicks while a picker is in flight. - Pending tabs swallow mouse presses so clicks no longer fall through to the selection machinery on the empty grid; hovering the hint shows a pointer cursor. - Pending-tab text renders in the configured terminal font so the drawn label matches the char-metric-based click rect (Font::MONOSPACE has a different advance and clipped the trailing bracket). - ~ expansion extracted to expand_tilde and unit-tested, along with browse_hint_rect geometry and hit-testing. Manually verified on macOS: hint hover/click, picker open/cancel, spawn-on-pick, dedup-to-existing-project, and re-entrancy guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The spec served its purpose during design, spike, and implementation; the PR carries the relevant context in its description and commit messages instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It validated the macOS main-thread dialog spawn before the feature was wired; the real Browse… flow now exercises the same pattern, so the example had no remaining job and would only bitrot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
astex
reviewed
Jun 15, 2026
| /// Open the native folder picker for the pending tab `tab_id`. | ||
| OpenProjectDialog(usize), | ||
| /// Folder picker resolved; `None` means cancelled. | ||
| ProjectDialogResult { tab_id: usize, path: Option<PathBuf> }, |
Owner
There was a problem hiding this comment.
nit. This isn't in keeping with the naming convention of the rest of these messages. How about SpawnOrFocusProjectTab? Same with the corresponding handler.
Contributor
Author
There was a problem hiding this comment.
Done in 245bf6b — renamed ProjectDialogResult → SpawnOrFocusProjectTab and handle_project_dialog_result → handle_spawn_or_focus_project_tab. Left OpenProjectDialog as-is since it parallels OpenPr and describes opening the picker rather than the spawn/focus outcome — happy to rename that too if you'd prefer.
astex
approved these changes
Jun 15, 2026
Per PR review: the result message and its handler are now named for the action they perform (spawn a new project tab or focus an existing one) rather than the dialog mechanism, matching the action-oriented convention of the other Message variants. OpenProjectDialog keeps its name — it parallels OpenPr and accurately describes opening the picker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a clickable
[ Browse… ]hint to the pending project tab (Ctrl+T) that opens the native OS folder picker viarfd. Picking a folder spawns the project tab exactly like typing the path and pressing Enter — canonicalize, dedup against already-open projects, spawn, focus. The inline text prompt is unchanged; the dialog is an additional option.Implementation notes
update()(main thread) and only awaited viaTask::performon Iced's executor. Building it inside the async block would spawn the dialog off-main-thread, which rfd warns against on macOS and which has a history of winit event-loop freezes. Validated standalone with a spike before wiring (see branch history).open_project_from_pathhelper no-ops when the result'stab_idno longer exists; tab IDs are never reused, so a stale result can't alias onto a newer tab.project_dialog_openflag drops Browse clicks while a picker is already in flight.Font::MONOSPACEhas a different advance and clipped the trailing bracket).Submitarm's canonicalize → dedup → spawn → focus sequence moved into the shared helper;~expansion was extracted to a pureexpand_tildefunction.Testing
expand_tilde(4) andbrowse_hint_rectgeometry/hit-testing (3); full suite passes (85).Out of scope (deliberately): parenting the dialog to the main window via
set_parent, remembering the last-used directory, and folder drag-and-drop onto the pending tab (the shared helper makes that a cheap follow-up).🤖 Generated with Claude Code