From db46d347272c940057dca1aa7fb4b4456eea8a9e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 3 Aug 2026 22:51:33 +0000 Subject: [PATCH] fix: base a new fork on the dev workspace when forking from one (#10489) * fix: base a new fork on the dev workspace when forking from one Co-Authored-By: Claude Opus 5 (1M context) * fix: carry dev-workspace fields on the superadmin-synthesized entry Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- .../sessions/WorkspaceFamilyPicker.svelte | 20 +++++++----- .../CreateWorkspaceInner.svelte | 11 ++++--- .../lib/utils/useForkableWorkspaces.svelte.ts | 4 +++ .../src/lib/utils/workspaceHierarchy.test.ts | 31 +++++++++++++++++++ frontend/src/lib/utils/workspaceHierarchy.ts | 25 +++++++++++++++ 5 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 frontend/src/lib/utils/workspaceHierarchy.test.ts diff --git a/frontend/src/lib/components/sessions/WorkspaceFamilyPicker.svelte b/frontend/src/lib/components/sessions/WorkspaceFamilyPicker.svelte index e1480d5097..6c41dd198b 100644 --- a/frontend/src/lib/components/sessions/WorkspaceFamilyPicker.svelte +++ b/frontend/src/lib/components/sessions/WorkspaceFamilyPicker.svelte @@ -12,6 +12,7 @@ import { findWorkspaceDescendants, findCanonicalDevWorkspace, + findDefaultForkBase, findWorkspaceRoot, buildWorkspaceHierarchy } from '$lib/utils/workspaceHierarchy' @@ -107,6 +108,9 @@ const devOfRoot = $derived( root ? findCanonicalDevWorkspace(root.id, forkableWorkspaces) : undefined ) + // Base a new fork gets by default: the dev workspace when the selection sits in its subtree, the + // family root otherwise. + const defaultForkBase = $derived(findDefaultForkBase(effectiveId, forkableWorkspaces)) const createForkLabel = 'Create new fork…' // Candidate bases ("targets") for a new fork: the root plus every fork/dev in the family, so a fork // can itself be the base — i.e. a fork of a fork. Root first, matching the list order below. @@ -205,8 +209,8 @@ // Bare fork id, without the wm-fork- prefix. Forks have no separate display // name — the id is the name (it also becomes the git branch). let newForkId = $state('') - // The base ("target") a new fork will branch from. Defaults to the root; the user can pick any fork - // in the family (via the inline target selector) to create a fork of a fork. + // The base ("target") a new fork will branch from. Defaults to `defaultForkBase`; the user can pick + // any fork in the family (via the inline target selector) to create a fork of a fork. let createForkBaseId = $state(undefined) // Manual keyboard navigation, modelled after SelectDropdown. melt's @@ -266,8 +270,8 @@ } function enterCreateMode(initialId?: string, baseId?: string) { - // Default the target to the root; the user can switch to any fork in the family (fork of a fork). - createForkBaseId = baseId ?? root?.id + // The user can switch to any fork in the family (fork of a fork). + createForkBaseId = baseId ?? defaultForkBase?.id creatingFork = true newForkId = initialId ?? defaultForkId() // Focus + select is handled by the input's own autofocus (it mounts with @@ -338,8 +342,8 @@ const wasOpen = lastDropdownOpen lastDropdownOpen = dropdownOpen if (dropdownOpen && !wasOpen && pendingFork && !creatingFork && showCreateFork) { - // Preserve the base the fork was staged from; without this the re-entry defaults to the root - // and silently re-parents a fork that was staged off another fork. + // Preserve the base the fork was staged from; without this the re-entry falls back to the + // default base and silently re-parents a fork that was staged off another workspace. void enterCreateMode( pendingFork.id.startsWith(WM_FORK_PREFIX) ? pendingFork.id.slice(WM_FORK_PREFIX.length) @@ -475,8 +479,8 @@ {#if showCreateFork}
{#if creatingFork} - +
Fork ID diff --git a/frontend/src/lib/components/workspaceSettings/CreateWorkspaceInner.svelte b/frontend/src/lib/components/workspaceSettings/CreateWorkspaceInner.svelte index 23ed37caad..5e54aaef89 100644 --- a/frontend/src/lib/components/workspaceSettings/CreateWorkspaceInner.svelte +++ b/frontend/src/lib/components/workspaceSettings/CreateWorkspaceInner.svelte @@ -24,7 +24,8 @@ import { workspaceIsFork, findWorkspaceRoot, - findWorkspaceDescendants + findWorkspaceDescendants, + findDefaultForkBase } from '$lib/utils/workspaceHierarchy' import { useForkableWorkspaces } from '$lib/utils/useForkableWorkspaces.svelte' import { @@ -115,7 +116,9 @@ subtitle: w.is_dev_workspace ? 'dev workspace' : w.id === familyRoot?.id ? undefined : 'fork' })) ) - let defaultBaseWorkspaceId = $derived(familyRoot?.id) + let defaultBaseWorkspaceId = $derived( + findDefaultForkBase($workspaceStore, forkableWorkspaces)?.id + ) // Seed the base once the family is known; keep an explicit user choice as long as it stays valid. $effect(() => { if (!isFork) return @@ -896,8 +899,8 @@ {#if createAsDevWorkspace} A dev workspace is always based on the root workspace. {:else} - Workspace to fork from. Defaults to the root; pick an existing fork to create a fork - of a fork (the new branch is based on the selected workspace's branch). + Workspace to fork from: the new branch is based on the selected workspace's branch. + Pick an existing fork to create a fork of a fork. {/if}