Add rename for workspaces, terminals, and agent tabs#531
Merged
Conversation
16dfe7b to
8436c24
Compare
8436c24 to
7f9e25d
Compare
21b1bb4 to
02f47cf
Compare
3 tasks
|
+1 — really looking forward to this landing. Tab labels get unreadable once I have 5+ agents in the same workspace, and the auto-title race is exactly the pain. Any chance you can rebase to unblock review? Happy to help smoke-test once the conflicts are resolved. |
02f47cf to
30fa9e1
Compare
7ec394c to
5c90449
Compare
3c776c6 to
872e334
Compare
ff18008 to
6594ade
Compare
Surfaces rename via the sidebar workspace kebab (git branch rename with client-side slugify), the terminal tab context menu (stops OSC 2 auto-title overrides), and the agent tab context menu (locks the title against the metadata generator). A shared RenameModal wraps AdaptiveModalSheet and replaces the inline rename on the host page. Auto-title races are closed from both sides: AgentManager gains setGeneratedTitleIfUnset with an atomic per-agent write queue, and TerminalSession replaces lockedTitle with a titleMode discriminated union so user rename flips to manual and disposes the OSC subscription. New WebSocket messages are additive: rename_terminal_request/response and checkout_rename_branch_request/response (branch rename uses the CheckoutError family). A new @getpaseo/server/utils/branch-slug subpath export shares slugify + validateBranchSlug between server and app.
Unslop pass on the rename commit plus two rebase artifacts: - session.ts: collapse handleRenameTerminalRequest to a respond helper with early returns; restore workspaceGitWatchTargets.set() in syncWorkspaceGitObserver (lost during rebase onto main, which broke onBranchChanged firing on branch rename). - terminal.ts: remove DA1 query handler accidentally re-introduced by the rebase (main intentionally removed it); restore conditional onTitleChange registration under titleMode === "auto". - rename-modal.tsx: drop unconfident optional-call on setNativeProps and the unknown-cast HTMLInputElement narrowing dance. - sidebar-workspace-list.tsx: remove unused branch field from rename result; flatten validateRenameSlug into early returns. - host-page.tsx: drop ?? "" fallback on a typed-string field.
main refactored getScriptConfigs to take the parsed paseo.json config instead of a repo path. spawnWorktreeScripts (added in the rename feature) was still passing repoRoot. Read and parse the config first, matching how spawnWorkspaceScript already does it.
Post-rebase cleanup: drop the await on syncWorkspaceGitObservers so fetch_workspaces emits the response before any cold registration- triggered git work fires (workspace.id is already on the descriptor — no registry lookup needed). Restore the DA1 CSI handler that answers \x1b[?62;4;22c on the daemon-side xterm so foreground apps like nvim get a reply on stdin. Extract WorkspaceTabRenameModal/useWorkspaceTabRename to drop WorkspaceScreenContent below the cyclomatic-complexity ceiling. Switch session test internals to handleMessage so we exercise the public dispatcher and avoid casting through any. Convert literal 'type' aliases to interfaces and stop spawning callbacks inline so the codebase passes the post-rebase oxlint rules.
Bootstrap was missing workspaceGitService when constructing CreatePaseoWorktreeWorkflowDependencies after main's worktree workflow refactor. Worker terminal manager needed setTitle on the session and setTerminalTitle on the manager to satisfy the rename additions to TerminalSession/TerminalManager interfaces.
The rebase brought in a spawnWorktreeScripts helper and a call inside runWorktreeSetupInBackground that auto-started every configured workspace script after worktree setup completed. main never auto-started scripts — this regressed the workspace-setup-streaming Playwright test, which expects the "web" script to be idle so the user can click Run. Drops the helper, the call, and the workspaceGitService dep that only existed to feed it.
Six audit findings closed and ~450 net lines trimmed from the branch: - Remove the duplicate "rename-branch" union member in GitMutationRefreshReason. - Fold dispatchStashMessage back into handleSessionMessage; the split was a feature-first artifact of adding checkout_rename_branch_request, with no documented rationale. - Drop the protected beforeGeneratedTitleIfUnsetWrite test seam from AgentStorage; rewrite the race test to exercise real Promise.all concurrency against the existing per-agent write queue. - Reshape useWorkspaceTabRename so the hook returns state and handlers only; promote WorkspaceTabRenameModal to an exported component the consumer renders directly. - Rename RenameModal to AdaptiveRenameModal so it reads as a generic primitive next to AdaptiveModalSheet, and update host-page, sidebar-workspace-list, and the workspace tab rename hook to import it by its new name. - Trim duplicate matrix coverage and Zod self-tests across rename-modal.test.tsx, terminal.test.ts, session.test.ts, agent-storage.test.ts, messages.rename-entities.test.ts and the three rename e2e specs; introduce packages/app/e2e/helpers/rename.ts to share setup across the e2e specs without merging coverage. Behavior of the rename feature is unchanged; targeted vitest, branch-wide typecheck, and lint all green.
5e6aeb2 removed the agentMetadataMocks hoisted definition and its vi.mock wiring when it deleted the import describe block. The block was kept (it belongs to main's import feature) but the mock support was left behind.
setTerminalTitle tests used cwd: "/tmp" which is not a valid directory on Windows, causing node-pty error code 267 (ERROR_DIRECTORY). Use realpathSync(tmpdir()) like the rest of the test file.
AdaptiveModalSheet moved from a `title` string prop to a structured `header: SheetHeader`; update AdaptiveRenameModal to memoize and pass a SheetHeader. invalidateCheckoutGitQueriesForClient moved from git/actions-store to git/query-keys. useWorkspaceTerminals now owns the terminal query, so workspace-screen pulls queryKey from the hook and re-declares queryClient via useQueryClient.
The branch renamed AgentManager.setTitle to setGeneratedTitleIfUnset for the rename feature; the generateTitlePromptWithConfig helper still mocked the old method, so eight prompt-byte tests crashed with "setGeneratedTitleIfUnset is not a function" on both ubuntu and windows server-tests jobs. Sister mocks in the same file were already on the new name.
AdaptiveTextInput (introduced on main by 29ce665) is intentionally uncontrolled: it drops the `value` prop and seeds the native input once via `initialValue`. AdaptiveRenameModal still passed `value= draft` from the pre-rebase shape, so every rename modal opened with an empty textbox and a disabled Save button. Three playwright e2e specs (settings-host-page, sidebar-workspace-rename, workspace-agent- tab-rename) failed for this reason. Switch the rename modal to a plain controlled TextInput so the input seeds with the current label and the slug transform (used by sidebar workspace rename) reflects live in the textbox as the user types. The unit test's old AdaptiveTextInput mock is replaced with a react-native mock providing a controlled TextInput shim that captures the same onChangeText/onSubmitEditing handlers.
…Input" This reverts commit 6283dea.
AdaptiveTextInput is intentionally uncontrolled — it drops `value` and
seeds the native input once with `initialValue`. The rename modal was
passing `value={draft}` from the pre-rebase shape, so every rename
modal opened with an empty textbox (three playwright specs failing).
Pass `initialValue={draft}` and bump `resetKey` only when the
transform rewrote what the user typed. That seeds the native input
with the current label on open, lets the user keep typing inside
text without cursor jumps (no remount when transform is a no-op),
and remounts the native input with the slug when the transform
diverges so live slugification keeps working in sidebar workspace
rename. Keeping AdaptiveTextInput preserves the BottomSheetTextInput
swap on mobile so the keyboard stays above the sheet — using a plain
TextInput would break that.
The unit test mock is updated to read `initialValue`/`resetKey` so it
mirrors production behavior instead of pretending the input is
controlled.
The rename modal's transform prop remounted the native input every time the slug diverged from what the user typed, which lost focus mid-edit on every uppercase letter or space in the sidebar workspace rename. Live-rewriting what the user typed was also surprising — the expectation is that you type a name, the daemon stores a slug. Drop the transform prop from AdaptiveRenameModal entirely. Sidebar workspace rename now slugifies once in handleSubmitRename before calling renameBranch, and validateRenameSlug runs validateBranchSlug against the slugified value so the user sees inline errors. The playwright spec drops the live-slug assertion; it still verifies the post-submit branch on disk and the rename request payload.
docs/rpc-namespacing.md says new RPCs use dotted names with the direction as the final segment, and explicitly bans new flat snake_ case names. This PR introduced two flat ones; rename them in place before the protocol ships: - checkout_rename_branch_request → checkout.rename_branch.request - checkout_rename_branch_response → checkout.rename_branch.response - rename_terminal_request → terminal.rename.request - rename_terminal_response → terminal.rename.response Touched: the Zod literals in messages.ts, the discriminated union entries, the session dispatcher and its tests, the daemon client wrappers and their tests, the terminal session controller, and the message-parsing rename-entities test. No callers exist outside the server package — app and CLI go through the daemon-client wrappers, which now emit the dotted names.
Save uses Button variant=default so it gets the same accent background + white text as every other primary action in the app (open-project, settings host save, pair-link confirm, etc). The input previously fell back to the browser's blue focus outline on web because the rename modal never set outlineStyle: none — every other AdaptiveTextInput call site that wants accent feedback already does this. Kill the browser outline, track focus state, and switch borderColor to accent while focused.
public/index.html paints a 2px :focus-visible outline on every web element with a hard-coded #20744a (Paseo green). On the Claude theme, the rename modal's input got that green ring instead of the theme's brown accent — visible mismatch against every other accent-colored control (primary buttons, etc). Add an outlineColor entry to AdaptiveTextInput's stylesheet sourced from theme.colors.accent. Unistyles' Babel plugin tracks the read and updates the native ShadowTree on theme switch — no React re-render, no useUnistyles() call (forbidden on this hot path per docs/unistyles.md). The inline outline-color on the rendered DOM input overrides the selector-level color from index.html; outline-width/style/offset still come from the global rule. Consumer style merges in after, so existing callers that pass outlineColor (message-input, review/surface, question-form-card) still win. Also drop the half-baked isFocused/focusedBorderStyle local state I added to rename-modal earlier — the AdaptiveTextInput fix removes the need for it.
The rename modal only read theme.colors.foregroundMuted to pass as the TextInput's placeholderTextColor prop. The rest already went through StyleSheet.create((theme) => ...). Per docs/unistyles.md the hook is forbidden when an alternative exists — and this is exactly the alternative the rest of the codebase uses (project-settings-screen, add-host-modal, pair-link-modal, command-center): put the muted color in a tiny StyleSheet entry and read .color off it for the prop.
placeholderTextColor was being duplicated at every AdaptiveTextInput call site (add-host-modal, command-center, pair-link-modal, project-picker-modal, provider-diagnostic-sheet, combobox, the sheet's own search inputs, etc.) — all passing the same theme.colors.foregroundMuted. The shared input should own this. Move it into AdaptiveTextInput as a default sourced from the same StyleSheet.create(theme => ...) block as the accent outline. Consumers still override via the prop if they need a different color. Strip the redundant placeholderTextColor and local placeholderColor style entry from rename-modal; other call sites can be cleaned up in a follow-up.
Earlier commit (5d56249) renamed the new rename RPCs to the dotted convention but missed the two e2e specs that capture the WebSocket frames by type. captureWsSessionFrames matched the old flat names, so renameRequests / renameFrames stayed empty even though the rename went through end to end — the sidebar updated and the branch was renamed on disk. The toBeGreaterThan(0) assertion fired and the test failed in playwright CI.
bd4f57d to
052972f
Compare
Collaborator
Author
|
@wujieli0207 will release this very soon, sorry for the wait |
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
git branch -mwith client-side slugify), terminals (tab context menu → stops OSC 2 auto-title overrides), and agent tabs (tab context menu → locks the title against the metadata generator).AgentManager.setGeneratedTitleIfUnsetplus an atomic per-agent write queue, andTerminalSessionreplaceslockedTitlewith atitleMode: "auto" | "manual"discriminated union that disposes the OSC subscription on manual flip.RenameModalwrappingAdaptiveModalSheet(also replaces the inline rename on the host page), and a new@getpaseo/server/utils/branch-slugsubpath export sharingslugify+validateBranchSlugbetween server and app.rename_terminal_request/_responseandcheckout_rename_branch_request/_response(branch rename uses theCheckoutErrorfamily).Test plan
npm run typecheck(all packages)session.test.ts,terminal.test.ts,checkout-git.test.ts,messages.rename-entities.test.ts(189 passed)sidebar-workspace-rename.spec.ts,workspace-terminal-tab-rename.spec.ts,workspace-agent-tab-rename.spec.ts