Skip to content

Commit 06343eb

Browse files
committed
update UX nits
1 parent 064f254 commit 06343eb

9 files changed

Lines changed: 216 additions & 161 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ function ForkJobRow({ job }: { job: BackgroundWorkItem }) {
247247
{report.groups.map((group) => (
248248
<ReportGroupRow key={group.label} group={group} />
249249
))}
250-
{report.notes.map((note) => (
250+
{report.notes.map((note, index) => (
251251
<span
252-
key={note.value}
252+
key={`${index}:${note.value}`}
253253
className={cn(
254254
'text-caption',
255255
note.warning ? 'text-[var(--text-error)]' : 'text-[var(--text-tertiary)]'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ interface ResourceKindRowProps {
2222
label: string
2323
items: ForkResourcePickerItem[]
2424
selected: Set<string>
25-
onToggleAll: (selectAll: boolean) => void
25+
/** Toggle the given ids on/off. Used for select-all over the currently-VISIBLE (filtered) subset. */
26+
onToggleMany: (ids: string[], checked: boolean) => void
2627
onToggleItem: (id: string, checked: boolean) => void
2728
disabled?: boolean
2829
}
@@ -38,32 +39,39 @@ export function ResourceKindRow({
3839
label,
3940
items,
4041
selected,
41-
onToggleAll,
42+
onToggleMany,
4243
onToggleItem,
4344
disabled = false,
4445
}: ResourceKindRowProps) {
4546
const [expanded, setExpanded] = useState(false)
4647
const [query, setQuery] = useState('')
4748
const fieldId = useId()
4849

49-
const total = items.length
50-
const selectedCount = selected.size
51-
const headerState = selectedCount === 0 ? false : selectedCount === total ? true : 'indeterminate'
52-
5350
const filtered = useMemo(() => {
5451
const trimmed = query.trim().toLowerCase()
5552
if (!trimmed) return items
5653
return items.filter((item) => item.label.toLowerCase().includes(trimmed))
5754
}, [items, query])
5855

56+
// Count + header state + select-all are scoped to the VISIBLE (filtered) items so a search never
57+
// selects or counts hidden ones. With no filter, `filtered === items`, so behavior is unchanged.
58+
const total = filtered.length
59+
const selectedCount = filtered.reduce((count, item) => count + (selected.has(item.id) ? 1 : 0), 0)
60+
const headerState = selectedCount === 0 ? false : selectedCount === total ? true : 'indeterminate'
61+
5962
return (
6063
<div className='flex flex-col gap-1'>
6164
<div className='flex items-center gap-2 text-[var(--text-body)] text-sm'>
6265
<Checkbox
6366
size='sm'
6467
aria-label={`Copy all ${label}`}
6568
checked={headerState}
66-
onCheckedChange={() => onToggleAll(headerState !== true)}
69+
onCheckedChange={() =>
70+
onToggleMany(
71+
filtered.map((item) => item.id),
72+
headerState !== true
73+
)
74+
}
6775
disabled={disabled}
6876
/>
6977
<button

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,15 @@ export function ForkWorkspaceModal({
316316
label={kind.label}
317317
items={resources.data?.[kind.key] ?? []}
318318
selected={selected[kind.key]}
319-
onToggleAll={(selectAll) =>
320-
setSelected((prev) => ({
321-
...prev,
322-
[kind.key]: selectAll
323-
? new Set(
324-
(resources.data?.[kind.key] ?? []).map((item) => item.id)
325-
)
326-
: new Set<string>(),
327-
}))
319+
onToggleMany={(ids, checked) =>
320+
setSelected((prev) => {
321+
const next = new Set(prev[kind.key])
322+
for (const id of ids) {
323+
if (checked) next.add(id)
324+
else next.delete(id)
325+
}
326+
return { ...prev, [kind.key]: next }
327+
})
328328
}
329329
onToggleItem={(id, checked) =>
330330
setSelected((prev) => {

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.test.ts

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,54 @@ import { describe, expect, it } from 'vitest'
55
import type { ForkClearedRef } from '@/lib/api/contracts/workspace-fork'
66
import { selectVisibleClearedRefs } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list'
77

8-
const ref = (overrides: Partial<ForkClearedRef>): ForkClearedRef => ({
8+
type ReferenceRef = Extract<ForkClearedRef, { cause: 'reference' }>
9+
type WorkflowRef = Extract<ForkClearedRef, { cause: 'workflow' }>
10+
type DependentRef = Extract<ForkClearedRef, { cause: 'dependent' }>
11+
12+
const base = {
913
targetWorkflowId: 'wf-tgt',
1014
workflowName: 'Workflow',
1115
blockId: 'block-1',
1216
blockLabel: 'Block',
13-
fieldLabel: 'Field',
14-
kind: 'credential',
15-
sourceId: 'src-1',
1617
sourceLabel: 'Source',
17-
cause: 'reference',
18-
parentKind: null,
19-
parentSourceId: null,
20-
...overrides,
18+
}
19+
20+
const referenceRef = (
21+
kind: ReferenceRef['kind'],
22+
sourceId: string,
23+
fieldLabel = 'Field'
24+
): ReferenceRef => ({ ...base, fieldLabel, cause: 'reference', kind, sourceId })
25+
26+
const workflowRef = (sourceId: string, fieldLabel = 'Workflow'): WorkflowRef => ({
27+
...base,
28+
fieldLabel,
29+
cause: 'workflow',
30+
kind: 'workflow',
31+
sourceId,
2132
})
2233

23-
// The modal's predicate is `mapped || copied`; here we model each disposition separately so the
24-
// document-under-KB case is exercised for BOTH a copied parent and a mapped parent.
34+
const dependentRef = (
35+
parentKind: DependentRef['parentKind'],
36+
parentSourceId: string,
37+
fieldLabel = 'Field'
38+
): DependentRef => ({
39+
...base,
40+
fieldLabel,
41+
cause: 'dependent',
42+
kind: parentKind,
43+
sourceId: parentSourceId,
44+
parentKind,
45+
parentSourceId,
46+
})
47+
48+
// The modal's predicate is `mapped || copied`; here we model each disposition as a resolved key so
49+
// the document-under-KB case is exercised for both a copied parent and a mapped parent.
2550
const resolvedKeys = (...keys: string[]) => {
2651
const set = new Set(keys)
2752
return (kind: string, sourceId: string) => set.has(`${kind}:${sourceId}`)
2853
}
2954

30-
const documentDependent = ref({
31-
cause: 'dependent',
32-
fieldLabel: 'Document',
33-
kind: 'knowledge-base',
34-
sourceId: 'kb-1',
35-
parentKind: 'knowledge-base',
36-
parentSourceId: 'kb-1',
37-
})
55+
const documentDependent = dependentRef('knowledge-base', 'kb-1', 'Document')
3856

3957
describe('selectVisibleClearedRefs', () => {
4058
it('drops a document dependent when its parent KB is selected for copy', () => {
@@ -57,14 +75,7 @@ describe('selectVisibleClearedRefs', () => {
5775
})
5876

5977
it('keeps a credential-anchored dependent even when the credential is mapped (label still clears)', () => {
60-
const labelDependent = ref({
61-
cause: 'dependent',
62-
fieldLabel: 'Label',
63-
kind: 'credential',
64-
sourceId: 'cred-1',
65-
parentKind: 'credential',
66-
parentSourceId: 'cred-1',
67-
})
78+
const labelDependent = dependentRef('credential', 'cred-1', 'Label')
6879
// A mapped credential remaps to a different account, so the account-scoped label is cleared
6980
// regardless - the entry must stay even though the parent is "resolved".
7081
expect(selectVisibleClearedRefs([labelDependent], resolvedKeys('credential:cred-1'))).toEqual([
@@ -74,26 +85,14 @@ describe('selectVisibleClearedRefs', () => {
7485
})
7586

7687
it('keeps a table-anchored dependent even when the table is copied/mapped (column still clears)', () => {
77-
const columnDependent = ref({
78-
cause: 'dependent',
79-
fieldLabel: 'Column',
80-
kind: 'table',
81-
sourceId: 'tbl-1',
82-
parentKind: 'table',
83-
parentSourceId: 'tbl-1',
84-
})
88+
const columnDependent = dependentRef('table', 'tbl-1', 'Column')
8589
expect(selectVisibleClearedRefs([columnDependent], resolvedKeys('table:tbl-1'))).toEqual([
8690
columnDependent,
8791
])
8892
})
8993

9094
it('drops the parent KB reference AND its child document together when the KB is resolved', () => {
91-
const kbReference = ref({
92-
cause: 'reference',
93-
fieldLabel: 'Knowledge Base',
94-
kind: 'knowledge-base',
95-
sourceId: 'kb-1',
96-
})
95+
const kbReference = referenceRef('knowledge-base', 'kb-1', 'Knowledge Base')
9796
expect(
9897
selectVisibleClearedRefs(
9998
[kbReference, documentDependent],
@@ -103,7 +102,7 @@ describe('selectVisibleClearedRefs', () => {
103102
})
104103

105104
it('applies the same predicate to a reference entry (drops resolved, keeps unresolved)', () => {
106-
const credentialReference = ref({ cause: 'reference', kind: 'credential', sourceId: 'cred-1' })
105+
const credentialReference = referenceRef('credential', 'cred-1')
107106
expect(
108107
selectVisibleClearedRefs([credentialReference], resolvedKeys('credential:cred-1'))
109108
).toEqual([])
@@ -113,14 +112,9 @@ describe('selectVisibleClearedRefs', () => {
113112
})
114113

115114
it('always keeps a workflow reference (it cannot be resolved in the modal)', () => {
116-
const workflowReference = ref({ cause: 'workflow', kind: 'workflow', sourceId: 'wf-other' })
115+
const workflowReference = workflowRef('wf-other')
117116
expect(
118117
selectVisibleClearedRefs([workflowReference], resolvedKeys('workflow:wf-other'))
119118
).toEqual([workflowReference])
120119
})
121-
122-
it('keeps a dependent missing its parent identity (defensive)', () => {
123-
const orphanDependent = ref({ cause: 'dependent', parentKind: null, parentSourceId: null })
124-
expect(selectVisibleClearedRefs([orphanDependent], resolvedKeys())).toEqual([orphanDependent])
125-
})
126120
})

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const PARENT_KINDS_THAT_PRESERVE_CHILD: ReadonlySet<string> = new Set(['knowledg
1818
* - `reference`: drops off once its own resource is resolved (mapped or copied).
1919
* - `dependent`: drops off once its PARENT resource (`parentKind`/`parentSourceId`) is resolved -
2020
* using the SAME predicate as `reference` - but ONLY when the child follows that parent (a
21-
* document under a KB). A credential- or table-anchored dependent is cleared on any parent
22-
* remap, so it stays even after the parent is mapped. A dependent missing its parent stays.
21+
* document under a KB). A credential- or table-anchored dependent is cleared on any parent remap,
22+
* so it stays even after the parent is mapped.
2323
* - `workflow`: always stays - a cross-workflow reference cannot be resolved in the modal.
2424
*
2525
* Pure so the reactive list is unit-testable independent of the modal's selection state.
@@ -30,12 +30,8 @@ export function selectVisibleClearedRefs(
3030
): ForkClearedRef[] {
3131
return clearedRefs.filter((ref) => {
3232
if (ref.cause === 'reference') return !isResolved(ref.kind, ref.sourceId)
33-
if (
34-
ref.cause === 'dependent' &&
35-
ref.parentKind &&
36-
ref.parentSourceId &&
37-
PARENT_KINDS_THAT_PRESERVE_CHILD.has(ref.parentKind)
38-
) {
33+
// The discriminated union guarantees `parentKind`/`parentSourceId` on a `dependent` variant.
34+
if (ref.cause === 'dependent' && PARENT_KINDS_THAT_PRESERVE_CHILD.has(ref.parentKind)) {
3935
return !isResolved(ref.parentKind, ref.parentSourceId)
4036
}
4137
return true

0 commit comments

Comments
 (0)