Skip to content

Commit e522bc4

Browse files
fix(forks): name the workspace a sync overwrites instead of "target" (#6822)
* fix(forks): name the workspace a sync overwrites instead of "target" Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(forks): name the target workspace in the blocker resolution line too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 56a270e commit e522bc4

5 files changed

Lines changed: 55 additions & 19 deletions

File tree

apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.test.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,36 @@ describe('splitForkClearedRefs', () => {
149149

150150
describe('forkBlockerResolution', () => {
151151
it('phrases each blocker reason with its actionable resolution', () => {
152-
expect(forkBlockerResolution(referenceRef('table', 'tbl-1'))).toBe(
152+
expect(forkBlockerResolution(referenceRef('table', 'tbl-1'), 'Acme Prod')).toBe(
153153
'map it to a target or select it for copy'
154154
)
155-
expect(forkBlockerResolution(referenceRef('mcp-server', 'srv-1'))).toBe(
155+
expect(forkBlockerResolution(referenceRef('mcp-server', 'srv-1'), 'Acme Prod')).toBe(
156156
'map it to a target or select it for copy'
157157
)
158-
expect(forkBlockerResolution(referenceRef('knowledge-base', 'kb-gone', 'KB', true))).toBe(
159-
'deleted in the source — map it to an existing knowledge base in the target'
160-
)
161-
expect(forkBlockerResolution(workflowRef('wf-other', 'Workflow'))).toBe(
158+
expect(
159+
forkBlockerResolution(referenceRef('knowledge-base', 'kb-gone', 'KB', true), 'Acme Prod')
160+
).toBe('deleted in the source — map it to an existing knowledge base in Acme Prod')
161+
expect(forkBlockerResolution(workflowRef('wf-other', 'Workflow'), 'Acme Prod')).toBe(
162162
'deploy "Source" in the source or remove the reference'
163163
)
164164
})
165165

166+
/**
167+
* The source-deleted line phrases the same resolution as the mapping row's hint, so it must
168+
* name the workspace the sync writes - "the target" is what this copy set out to remove.
169+
*/
170+
it('names the target workspace in the source-deleted resolution', () => {
171+
const resolution = forkBlockerResolution(
172+
referenceRef('knowledge-base', 'kb-gone', 'KB', true),
173+
'this workspace'
174+
)
175+
expect(resolution).toBe(
176+
'deleted in the source — map it to an existing knowledge base in this workspace'
177+
)
178+
expect(resolution).not.toContain('in the target')
179+
})
180+
166181
it('returns null for non-blocking dependent entries', () => {
167-
expect(forkBlockerResolution(dependentRef('credential', 'cred-1'))).toBeNull()
182+
expect(forkBlockerResolution(dependentRef('credential', 'cred-1'), 'Acme Prod')).toBeNull()
168183
})
169184
})

apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,24 @@ export const FORK_RESOURCE_KIND_LABEL: Record<string, string> = {
7777
/**
7878
* The actionable resolution line for a blocking entry, phrased for "{block} would lose {field}
7979
* in {workflow} - {resolution}". Null for non-blocking (dependent) entries.
80+
*
81+
* `targetWorkspaceName` is required rather than defaulted: the source-deleted line phrases the
82+
* same resolution as the mapping row's own hint, and naming the workspace is the only way either
83+
* says WHICH side the sync writes. A default would let "the target" quietly return.
8084
*/
81-
export function forkBlockerResolution(ref: ForkClearedRef): string | null {
85+
export function forkBlockerResolution(
86+
ref: ForkClearedRef,
87+
targetWorkspaceName: string
88+
): string | null {
8289
const reason = forkSyncBlockerReasonFor(ref)
8390
if (!reason) return null
8491
switch (reason) {
92+
// "a target" here is the target RESOURCE picked in the mapping row, not the workspace -
93+
// it matches the picker's own "Select target" label, so it stays unnamed.
8594
case 'unmapped-copyable':
8695
return 'map it to a target or select it for copy'
8796
case 'source-deleted':
88-
return `deleted in the source — map it to an existing ${FORK_RESOURCE_KIND_LABEL[ref.kind] ?? 'resource'} in the target`
97+
return `deleted in the source — map it to an existing ${FORK_RESOURCE_KIND_LABEL[ref.kind] ?? 'resource'} in ${targetWorkspaceName}`
8998
case 'workflow-missing':
9099
return `deploy "${ref.sourceLabel}" in the source or remove the reference`
91100
}

apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ function MappingEntry({ controller, group, entry }: MappingEntryProps) {
457457
{entry.sourceDeleted ? (
458458
<p className='text-[var(--text-muted)] text-small'>
459459
Deleted in the source — its name can't be shown. Map it to an existing{' '}
460-
{FORK_RESOURCE_KIND_LABEL[entry.kind] ?? 'resource'} in the target, or fix the reference
461-
in the source and redeploy.
460+
{FORK_RESOURCE_KIND_LABEL[entry.kind] ?? 'resource'} in {controller.targetWorkspaceName}
461+
, or fix the reference in the source and redeploy.
462462
</p>
463463
) : null}
464464
{entry.candidatesTruncated ? (
@@ -961,7 +961,8 @@ export function ForkSyncView({ controller, onDirectionChange }: ForkSyncViewProp
961961
<span className='min-w-0'>
962962
<span className='text-[var(--text-body)]'>{ref.blockLabel}</span> would lose{' '}
963963
<span className='text-[var(--text-body)]'>{ref.fieldLabel}</span> in{' '}
964-
{ref.workflowName}{forkBlockerResolution(ref)}
964+
{ref.workflowName}{' '}
965+
{forkBlockerResolution(ref, controller.targetWorkspaceName)}
965966
</span>
966967
{/* Only a source-deleted reference can be dropped: an unmapped copyable can still
967968
be copied and a missing workflow can still be deployed, so neither is a dead

apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ export interface ForkSyncController {
104104
otherWorkspaceName: string
105105
/**
106106
* The workspace this sync WRITES, named for user-facing copy: the other workspace on push,
107-
* "this workspace" on pull. Derived once here so every surface that names the target - the
108-
* overwrite confirm, the Trigger URLs heading - says the same thing.
107+
* this one on pull. Always a NAME rather than "the target" - the page header shows the OTHER
108+
* workspace's name, so an unnamed target reads as that one even on pull. Falls back to
109+
* "this workspace" only until the name loads. Derived once here so every surface that names
110+
* it - the overwrite confirm, the Trigger URLs heading - says the same thing.
109111
*/
110112
targetWorkspaceName: string
111113
isLoading: boolean
@@ -272,12 +274,15 @@ function takenTargetOwners(
272274
*/
273275
export function useForkSync(params: {
274276
workspaceId: string
277+
/** This workspace's name, for copy that must say which side a pull overwrites. */
278+
workspaceName?: string
275279
otherWorkspaceId?: string
276280
otherWorkspaceName: string
277281
direction: ForkDirection
278282
enabled: boolean
279283
}): ForkSyncController {
280-
const { workspaceId, otherWorkspaceId, otherWorkspaceName, direction, enabled } = params
284+
const { workspaceId, workspaceName, otherWorkspaceId, otherWorkspaceName, direction, enabled } =
285+
params
281286

282287
// User's IN-SESSION mapping overrides only - NOT the source of truth. The displayed/persisted
283288
// target falls back to each entry's stored `targetId` (see `targetFor`), so a reopened edge
@@ -982,7 +987,8 @@ export function useForkSync(params: {
982987
return {
983988
direction,
984989
otherWorkspaceName,
985-
targetWorkspaceName: direction === 'push' ? otherWorkspaceName : 'this workspace',
990+
targetWorkspaceName:
991+
direction === 'push' ? otherWorkspaceName : workspaceName || 'this workspace',
986992
isLoading: enabled && mapping.isLoading,
987993
isError: mapping.isError,
988994
errorMessage: mapping.isError ? getErrorMessage(mapping.error, 'Failed to load mapping') : null,

apps/sim/ee/workspace-forking/components/forks.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ function ForkListRow({ name, actions }: ForkListRowProps) {
8585
interface ForkSyncDetailViewProps {
8686
title: string
8787
workspaceId: string
88+
/** This workspace's name — a pull overwrites it, and the copy has to say which side that is. */
89+
workspaceName?: string
8890
/** The other side of the edge being synced (this workspace's parent). */
8991
otherWorkspaceId: string
9092
otherWorkspaceName: string
@@ -105,6 +107,7 @@ interface ForkSyncDetailViewProps {
105107
function ForkSyncDetailView({
106108
title,
107109
workspaceId,
110+
workspaceName,
108111
otherWorkspaceId,
109112
otherWorkspaceName,
110113
onBack,
@@ -118,6 +121,7 @@ function ForkSyncDetailView({
118121

119122
const controller = useForkSync({
120123
workspaceId,
124+
workspaceName,
121125
otherWorkspaceId,
122126
otherWorkspaceName,
123127
direction,
@@ -186,11 +190,11 @@ function ForkSyncDetailView({
186190
open={confirmSyncOpen}
187191
onOpenChange={setConfirmSyncOpen}
188192
srTitle='Sync workspace'
189-
title='Overwrite target workspace'
193+
title={`Overwrite ${targetWorkspaceName}`}
190194
text={[
191-
'The target may have been modified since the last sync. Syncing will ',
195+
'Syncing will ',
192196
{ text: 'overwrite any changes', bold: true },
193-
' there. Continue?',
197+
` made in ${targetWorkspaceName} since the last sync. Continue?`,
194198
]}
195199
confirm={{
196200
label: 'Sync',
@@ -431,6 +435,7 @@ export function Forks() {
431435
key={parent.id}
432436
title={parent.name}
433437
workspaceId={workspaceId}
438+
workspaceName={workspaceName}
434439
otherWorkspaceId={parent.id}
435440
otherWorkspaceName={parent.name}
436441
onBack={() => void setSelectedForkId(null, { history: 'replace' })}

0 commit comments

Comments
 (0)