feat(gpui): sidebar quick-access profiles - #144
Open
AnxForever wants to merge 6 commits into
Open
AnxForever wants to merge 6 commits into
AnxForever wants to merge 6 commits into
Conversation
`nebula_app/src/gpui_shell/workspace.rs` is pinned at zero growth: the effective line limit is `min(4798, base_line_count)` per `scripts/architecture/budgets.py`, which currently evaluates to exactly the file's line count. `scripts/check_architecture.py --base` rejects even a one-line increase, so room has to be made before any feature work lands. Move `launcher_chip_counts` and `quick_jump_chip_counts` into a new `workspace/palette_support.rs`. Both only read the already-computed `palette_override` row snapshot — no window state, no rendering — which makes them the most self-contained block to lift. No behaviour change: the bodies are unchanged apart from becoming `pub(super)`. Child modules may read a parent module's private fields, so no field visibility had to be widened. workspace.rs: 4363 -> 4330 lines.
The screen watchdog downgrades `Working -> Done` after the pane's screen has been static for 5 ticks (it samples at 1 Hz), and that transition used to notify unconditionally. But a static screen is not evidence that the agent stopped: Claude Code prints nothing while it thinks, and a long command such as a full build writes nothing for minutes. The result was a "回合完成" toast while the task was still running. The screen is only a fallback for when the hook never arrives. When hooks are live, the agent CLI's own `TurnDone` is the authoritative terminal state, so keep the screen-derived transition (the sidebar dot still updates) but stay silent about it. Clients without hooks keep the old behaviour, where the screen really is the only evidence. This is the notification half of the problem. The other half is that `active_background_tasks` never fires for Claude Code — its `Stop` payload carries no `background_tasks` field — so the guard at the top of the `TurnDone` arm is dead code for that CLI. Left as-is for now.
The right-hand column of a launcher row answers "where will this open?", but for a quick-launch profile it printed the executable path — `C:\Program Files\Git\bin\bash.exe` for a project entry the user set up to open in `D:\huozigemima`. Two profiles that differ only by directory were therefore indistinguishable, and a WSL profile showed `wsl.exe` rather than the project it points at. Prefer `cwd` when the profile has one and fall back to the command otherwise: profiles produced by `scan_directory` never carry a `cwd` (it is hard-coded to `None` there), and the column must not be left blank. The directory also joins the search string, so a project can be found by its folder name when the display name is not memorable. Detected-shell rows are deliberately unchanged: for a bare shell the executable path *is* its identity — it is what tells PowerShell 5.1 apart from 7 — whereas for a profile the directory is.
…iles filter Two related gaps in how quick-launch profiles surface. **Sidebar section.** Profiles were reachable only through Ctrl+K or by memorising Ctrl+Shift+<n>. A resident list above the tab list matches how a file manager's quick access works, which is what these entries are: named project directories, not shells. - New `workspace/quick_access.rs`: collapsible section, icon + name + working directory per row, click to open, hover `×` to delete, `+` in the header to pick a directory. - Icons come from the same `shell_id` -> brand-art path the palette uses, so a `wsl:Ubuntu` entry gets the Ubuntu mark without any new icon configuration. - Picking a directory that resolves to `\wsl.localhost\<distro>\…` builds a proper WSL entry (`wsl.exe -d <distro> --cd <guest>`, `shell_id = wsl:<distro>`) rather than a host path a guest cannot see. The reverse of `wsl_unc_path` is new in `shell_detect`. - Rows are cached in `NebulaWorkspace` and refreshed on `TerminalProfilesChanged`: the sidebar repaints per frame, so loading the store during render is not an option. **Store.** `TerminalProfiles::add`/`remove` are keyed by `id`, deliberately not reusing `upsert` — that one collapses by command, which is right for the import path (one row per shell executable) but would silently merge two projects that both open through `wsl.exe`. **Profiles filter.** The search placeholder has always read "shells, profiles and SSH hosts" while the filter bar offered only All/Ssh/Shell, and `Shell` folded profiles in. Split them: `Shell` now matches `LaunchShell` only, and `Profiles` is its own chip. `LauncherFilter` moved to `display/launcher_filter.rs` to make room — `command_palette.rs` is pinned at zero growth by `architecture/file-budgets.txt`.
A WSL entry carries no `cwd` — the directory travels to the guest in `--cd` — so the right-hand column fell back to the command path and every Ubuntu row read `C:\Windows\System32\wsl.exe`. Read the guest directory out of the argv instead. `wsl_launch_guest` mirrors the boundary `wsl_args_at` already writes with: a `--cd` that follows the guest command belongs to that command, not to the launch, so it is not mistaken for the startup directory. `profile_location` renders that as `Ubuntu:/home/me/project` — the same shape the Git panel uses for a WSL location — and falls back to `cwd`, then to the command, exactly as before for every other row. The sidebar and the Ctrl+K picker share the function, so the two columns cannot drift apart again. The picker's search string no longer repeats the command once the location joined it.
Three things kept the rows from behaving: - The × handed `Profile::settings_id()` (`profile:<shell>|<store id>`) to `TerminalProfiles::remove`, which compares the bare id — so the click never matched anything and the row stayed put. Hand it `terminal_profile_id`, and keep a row without one from rendering a button that cannot work. - Every click now leaves a visible result: the row disappears when it is gone, a profile another window already deleted is dropped from the snapshot instead of leaving a dead button, and a store that cannot be written says so in a toast instead of only a log line. - The "where it opens" column was recomputed for every row on every repaint — two argv scans and a format on the render path. Compute it once in `refresh_quick_access`, next to the row it belongs to.
6 tasks
This was referenced Sep 17, 2026
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.
Result / 用户结果
Sidebar gains a resident Quick access section above the tab list: one row per
profile, click to launch,
+to add the directory currently being browsed. TheCtrl+K palette gains a
Profilesfilter alongside the shell list.Opening a WSL entry used to be the one row that could not be acted on: the
×clicked through and nothing happened, and the "where it opens" column read
C:\Windows\System32\wsl.exefor every Ubuntu profile.Design / 设计边界
workspace/quick_access.rsowns the section (rows, add,remove);
workspace/shell_picker.rsowns the shared row vocabulary(
profile_location, glyph fallback) for both the sidebar and the palette;terminal_profiles.rsstays the single store (%APPDATA%\Pebrel\terminal_profiles.json).TerminalProfiles::load(). Rendering work stays out of the render path — rowscarry a precomputed display string, refreshed only when the store changes.
shell=key, and the launch endpoints (
add_terminal_with) are untouched.terminal_profile_idno longerrender a delete button rather than offering one that cannot work; a profile
another window already deleted is dropped from the snapshot on the next click;
a store that cannot be written reports it in a toast.
Evidence / 验证依据
cargo test -p nebula --bin pebrel quick_access→ 1 passed;... palette→ 49 passed;... gpui_shell::workspace→ 99 passed.python scripts/check_architecture.py --base main→ exit 0.quick_access_delete_is_keyed_by_the_store_idfails before thefix —
TerminalProfiles::removecompares the bare id while the row handed itProfile::settings_id()(profile:<shell>|<id>), so the click never matched.the Windows Explorer integration is covered by the separate installer change.
Required Review / 必须确认
CONTRIBUTING.md,docs/architecture.md, anddocs/project-constraints.md.python3 scripts/check_architecture.py --base mainpasses; budgets were not inflated to fit the change.