Skip to content

fix(app): create new workspaces on the host you are already working on - #4173

Closed
mwhitman-steward wants to merge 6 commits into
getpaseo:mainfrom
mwhitman-steward:remember-new-workspace-host
Closed

mwhitman-steward wants to merge 6 commits into
getpaseo:mainfrom
mwhitman-steward:remember-new-workspace-host

Conversation

@mwhitman-steward

@mwhitman-steward mwhitman-steward commented Sep 1, 2026

Copy link
Copy Markdown

Linked issue

Closes #4172

Type of change

  • Bug fix
  • New feature
  • Enhancement
  • Refactor
  • Docs

Reasoning

If you work across two machines and a project is registered on both, the sidebar's per-project "+" always creates the workspace on the local daemon — even when you are currently working on the remote host. You notice after the agent starts, against the wrong checkout, and have to throw the workspace away and start again.

Paseo already has one rule for this: new work happens on the host you are already working on. Cmd+N follows it, and so does the composer on a bare /new. The sidebar "+" is the only entry point that ignores it, which is unfortunate because it is the one people reach for most.

Entry point Host it picks Follows the rule?
Cmd+N / global "New workspace" active workspace's serverId, passed on the route (use-global-new-workspace-action.ts) yes
Composer on a bare /new lastActiveProject, synthesised from the active workspace by hostProjectFromWorkspace, so it carries exactly one host yes
Sidebar per-project "+" first qualifying entry in the project's merged hosts[] no

The sidebar row is the odd one out because its SidebarProjectEntry.hosts is the merged multi-host array from buildWorkspaceStructureProjects, ordered by host registry, which puts the local daemon first in the common case. resolveNewWorkspaceTarget takes the first qualifying entry, so a project on both machines always resolves local. That decision is final rather than a default: the row navigates with an explicit ?serverId=, and resolveNewWorkspaceInitialServerId honours a route param outright, so the composer never gets a say.

Goals

  • The sidebar "+" targets the active workspace's host when the project is on that host and a workspace can be created there.
  • Fall back to today's behaviour, unchanged, whenever it cannot: no active workspace, the project is not on that host, the host cannot host a workspace, or the host is not online.
  • Require the host to be online. This matters more here than elsewhere — because the route param wins outright, an offline host would stick with nothing downstream to correct it.
  • No new persisted state.

Non-goals

  • The global "New workspace" / Cmd+N action is untouched. It already passes the active workspace's serverId, which is correct context propagation rather than the bug.
  • orderHostsLocalFirst is untouched. Local-first ordering is correct for display; this changes selection only.
  • No persisted host preference. Earlier commits on this branch took that approach — a stored "last host you picked by hand". Reviewing it against the code showed the composer already follows the active workspace, so the preference was a second competing convention rather than an extension of the existing one, and it did not fix the sidebar at all. The final commit removes it. Trade-off accepted: with no stored state, a cold start with no active workspace still falls back to the local-first walk.
  • Intermediate commits are left in place rather than force-pushed over the maintainer's merge. The net diff is three files.

QA

Reproduced before, confirmed after, on two live daemons. sidebar-project-grouping.spec.ts boots a real second host via startIsolatedHostDaemon and groups one repository across both through a shared Git remote, which is the exact precondition for this bug.

Open the secondary host's workspace so it is the active one, then click the project row's "+":

Composer opens on Workspace created on
main Primary Host (the bug) Primary Host
this branch Secondary Host Secondary Host

On main the assertion fails with Expected substring: "Secondary Host" / Received string: "Primary Host". The sibling test opens the primary workspace and asserts the "+" follows back to Primary; it passes on main too, because main always picks primary. That asymmetry is what makes the pair conclusive rather than merely green. Both tests assert the workspace is actually created on that host, not just preselected.

Commands run:

  • npm run typecheck — clean
  • npm run lint — clean
  • npm run format:check — clean
  • npx vitest run src/utils/sidebar-project-row-model.test.ts — 17 passed
  • npx playwright test --project=browser e2e/browser/sidebar-project-grouping.spec.ts — 9 passed
  • npx playwright test --project=browser e2e/browser/sidebar-workspace.spec.ts e2e/browser/new-workspace-entry.spec.ts — 1 pre-existing failure, new-workspace-entry.spec.ts › "Ctrl+P opens the project picker with search focused", reproduced identically on unmodified main with my changes reverted

No screenshots. There is no visual change — the sidebar row layout, host ordering and every label are untouched. The only difference is which serverId the "+" puts on the route. I have not tested this on iOS or Android.

Checklist

  • One focused change
  • npm run typecheck passes
  • npm run lint passes
  • npm run format passes
  • QA evidence — with the two limitations called out above
  • Tests added or updated where it made sense

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR aligns the sidebar project-level new-workspace action with the active workspace’s host when that host is online and supports creation for the project.

  • Passes active-host and connection-status context into the sidebar project row model.
  • Prefers the qualifying active host while retaining the existing ordered fallback.
  • Adds unit coverage for active-host selection and fallback conditions.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the scope of the follow-up review.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/app/src/components/sidebar-workspace-list.tsx Supplies active-host connection statuses to project rows and includes the new input in memoization boundaries.
packages/app/src/utils/sidebar-project-row-model.ts Extends new-workspace target resolution to prefer the qualifying, online active host before applying the existing fallback.
packages/app/src/utils/sidebar-project-row-model.test.ts Covers active-host preference and fallback when the active host is absent, offline, or unable to create the workspace.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Click[Sidebar project +] --> Qualify[Find hosts that can create a workspace]
  Qualify --> Active{Active host is qualifying and online?}
  Active -->|Yes| Preferred[Select active host]
  Active -->|No| Fallback[Select first qualifying host]
  Preferred --> Route[Build /new route with serverId]
  Fallback --> Route
Loading

Reviews (7): Last reviewed commit: "Merge branch 'main' into remember-new-wo..." | Re-trigger Greptile

The composer already remembers provider, model, mode and isolation, but
the host reset on every visit. On a multi-host setup it defaulted back to
the local daemon even when the user had been working on a remote host:
a project registered on two hosts merges into a single project whose
hosts[] array follows host-registry order, so the last-active-project
walk and the first-online-host fallback both resolved local.

Store the host alongside the other composer preferences and consult it
after an explicit ?serverId= route param but before those fallbacks. Only
an explicit pick in the host picker is stored — persisting an
automatically resolved host would latch the preference onto whatever the
resolver happened to choose. A stored host is used only while it is still
registered and online, so a retired or unreachable host falls through to
the existing chain.

Host ordering is untouched: local-first ordering is correct for display,
and this changes selection only.
@mwhitman-steward
mwhitman-steward force-pushed the remember-new-workspace-host branch from e6582bb to f9421fe Compare September 1, 2026 17:16
The sidebar's per-project "+" navigates with an explicit ?serverId=, which
the composer honours outright, so the remembered host never got a say on
the flow most people use to start a workspace. That serverId came from the
same first-qualifying-entry walk over the project's hosts[], which follows
host-registry order and so resolves local for a project registered on both
machines.

Pick the remembered host when the project is on it and it can host a new
workspace there. Because the route param wins outright, an offline
remembered host would stick with nothing to correct it, so the host must
be online; otherwise the existing walk applies unchanged.
The composer and Cmd+N already follow the active workspace's host: the
global action passes its serverId on the route, and the composer's
last-active-project candidate is synthesised from that same workspace, so
it carries exactly one host. The sidebar's per-project "+" was the only
entry point that ignored it, walking the project's merged hosts[] in
registry order and so resolving local for a project registered on both
machines. Because that row navigates with an explicit ?serverId= the
composer honours outright, the walk decided the host outright too.

Use the active workspace's host there as well, when the project is on it
and it can host a workspace there. It must be online, since the route
param wins and nothing downstream would correct an unreachable host;
otherwise the existing walk applies unchanged.

This replaces the persisted host preference from the previous commits.
Following the workspace you are looking at needs no stored state, cannot
go stale, and self-corrects when you switch hosts.
@mwhitman-steward mwhitman-steward changed the title Remember the host picked in the New Workspace composer Create new workspaces on the host you are already working on Sep 2, 2026
@mwhitman-steward mwhitman-steward changed the title Create new workspaces on the host you are already working on fix(app): create new workspaces on the host you are already working on Sep 2, 2026
@boudra

boudra commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Closing for now: this needs a live two-host check showing the sidebar + creates the workspace on the active remote host when the project exists on both. Please reopen with that evidence.

@boudra boudra closed this Sep 8, 2026
@mwhitman-steward

Copy link
Copy Markdown
Author

Evidence added — and you were right to ask, because it caught something the unit tests could not.

I had said the e2e harness could only fake a second host as offline. That was wrong: startIsolatedHostDaemon boots a genuinely running second daemon, and sidebar-project-grouping.spec.ts already stands up two live hosts with one repository grouped across both via a shared Git remote. That is exactly the scenario you asked for, so I added the check there rather than inventing a new fixture.

Live two-host result, both daemons online, project registered on both:

Open the secondary host's workspace so it is the active one, then click the project row's "+":

Composer opens on Workspace created on
main Primary Host Primary Host
this branch Secondary Host Secondary Host

On main the assertion fails with:

Expected substring: "Secondary Host"
Received string:    "Primary Host"

The sibling test opens the primary workspace and asserts the "+" follows back to Primary. It passes on main too — because main always picks primary. That asymmetry is what makes the pair conclusive rather than merely green: one test pins the bug, the other proves the fix follows the active host instead of just hard-coding the other direction.

Both tests assert the workspace is actually created on that host, not merely that the composer preselects it.

Full spec on this branch: 9/9 passing.

Two things I want to be straight about:

  • My earlier QA section claimed this was untestable and offered a manual check. That was a limitation of my search, not of the harness. It is now automated and permanent regression coverage, which is better than the screenshots I offered.
  • I cannot reopen this PR myself — I only have read access here, and both gh pr reopen and the REST equivalent return a validation error. The branch is pushed and up to date with main. Could you reopen it? If you would rather have a fresh PR, say the word and I will open one.

@boudra

boudra commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Please disregard the earlier invitation to reopen with evidence. This PR remains closed unless I explicitly follow up. You don't need to resubmit this PR or provide further QA evidence.

Please read the updated PR policy. For now, I'm automatically closing feature PRs so I can focus more time on bug fixes and core improvements. Focused bug-fix PRs with a clear reproduction and QA are still welcome. The previous guide already asked contributors to submit only if they were comfortable with closure, and explained that unsolicited PRs could be closed without a detailed review.

For feature ideas, please start in GitHub Discussions and share your workflow: what you're trying to do, how you do it today, and where Paseo gets in the way. If there's already a discussion about it, join in and share your use case. I'll periodically review discussions for highly requested workflows and use that feedback to shape the roadmap.

I'll periodically look through closed PRs and choose which contributions to take forward, whether solicited or unsolicited. I may reopen a PR, including one closed by mistake, or use it as a reference for my own implementation, with attribution either way.

There are hundreds of PRs alongside ongoing development. I can't commit to reviewing every submission, providing individual feedback or giving timelines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidebar project + creates the workspace on the local host instead of the host you are working on

2 participants