Skip to content

Commit 227f0dc

Browse files
committed
fix(search): tighten review spacing and capture full UI states
1 parent f1b71c6 commit 227f0dc

4 files changed

Lines changed: 220 additions & 3 deletions

File tree

‎apps/sim/components/assistant-actions/action-preview.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function ActionPreview({ action, account, full = false }: ActionPreviewPr
4343
</div>
4444
)}
4545
{primary.length > 0 && (
46-
<div className='flex min-w-0 flex-col gap-2 border-[var(--border)] border-t pt-2'>
46+
<div className='flex min-w-0 flex-col gap-1 border-[var(--border)] border-t pt-2'>
4747
{primary.map((field, index) => (
4848
<PreviewField
4949
key={field.name}

‎apps/sim/components/assistant-actions/review.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ function ActionReview({ action, review }: ActionReviewProps) {
654654
) : undefined
655655
}
656656
>
657-
<div className='flex flex-col gap-3 px-2 pb-2'>
657+
<div className='flex flex-col gap-2 px-2 pb-2'>
658658
{editing ? (
659659
<div className='grid gap-3 sm:grid-cols-2'>
660660
<p className='col-span-full break-words text-[var(--text-body)] text-small'>

‎apps/sim/scripts/test-assistant-actions-e2e.ts‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ try {
598598
await capture('integrations-desktop')
599599
})
600600
await check(
601-
'Multiple accounts isolate permissions and disconnect the selected account only',
601+
'A synthetic duplicate-provider fixture isolates permissions and disconnects only the selected account',
602602
async () => {
603603
await page.goto(`${baseUrl.origin}${root}/integrations`)
604604
await page.getByRole('link', { name: 'Open Jira integration', exact: true }).click()
@@ -1037,6 +1037,16 @@ try {
10371037
.getByRole('region', { name: 'Create Jira issue receipt' })
10381038
.filter({ hasText: 'Approved historical title' })
10391039
).toBeAttached()
1040+
const historicalReceipt = page
1041+
.getByRole('region', { name: 'Create Jira issue receipt' })
1042+
.filter({ hasText: 'Approved historical title' })
1043+
await historicalReceipt
1044+
.getByRole('button', { name: 'View Create Jira issue receipt: Completed', exact: true })
1045+
.click()
1046+
await capture('action-receipt-completed-expanded')
1047+
await historicalReceipt
1048+
.getByRole('button', { name: 'Hide Create Jira issue receipt: Completed', exact: true })
1049+
.click()
10401050
await reviewCards.last().scrollIntoViewIfNeeded()
10411051
await capture('assistant-actions-desktop')
10421052
await capture('action-review-desktop')
@@ -1068,6 +1078,8 @@ try {
10681078
await expect(
10691079
page.getByRole('button', { name: 'Allow 2 shown', exact: true })
10701080
).toBeDisabled()
1081+
await reviewCards.nth(0).scrollIntoViewIfNeeded()
1082+
await capture('action-review-account-unavailable')
10711083
} finally {
10721084
await sql`update credential set managed_oauth_status = 'active' where id = ${credentialId}`
10731085
}
@@ -1112,6 +1124,8 @@ try {
11121124
exact: true,
11131125
})
11141126
).toBeVisible()
1127+
await approvedReceipt.scrollIntoViewIfNeeded()
1128+
await capture('action-review-approved-and-pending')
11151129
await approvedReceipt.getByRole('button', { name: 'View Create Jira issue receipt' }).click()
11161130
await expect(approvedReceipt.getByRole('textbox')).toHaveCount(0)
11171131
await expect(approvedReceipt.getByText('acme.atlassian.net', { exact: true })).toBeVisible()
@@ -1195,6 +1209,7 @@ try {
11951209
timeout: 15_000,
11961210
})
11971211
await expect(reviewCards).toHaveCount(0)
1212+
await capture('action-review-unavailable-retry')
11981213
} finally {
11991214
await page.unroute(reviewUrl)
12001215
}
@@ -1236,6 +1251,7 @@ try {
12361251
try {
12371252
await page.getByRole('button', { name: 'Allow 2 shown', exact: true }).click()
12381253
await expect.poll(() => firstDecisionRequested).toBe(true)
1254+
await capture('action-review-bulk-saving')
12391255
await insertProposal(lagRunId, lateCallId, 'A later proposal that was never approved')
12401256
await appendStreamEvents(lagChatId, lagStreamId, [
12411257
pendingToolEvent(lateCallId, 'A later proposal that was never approved'),
@@ -1311,6 +1327,7 @@ try {
13111327
await expect(reviewCards.nth(0).getByRole('alert')).toBeVisible()
13121328
await expect(allow).toBeEnabled()
13131329
await expect(allow).toBeFocused()
1330+
await capture('action-review-decision-error')
13141331
const [row] =
13151332
await sql`select permission_decision, execution_started_at from copilot_async_tool_calls where tool_call_id = ${keyboardCallId}`
13161333
assert.equal(row?.permission_decision, null)
@@ -1620,6 +1637,54 @@ try {
16201637
.filter({ hasText: fixture.summary })
16211638
).toContainText(fixture.state)
16221639
}
1640+
const [savedExecution] =
1641+
await sql`select status, execution_started_at, execution_settled_at, execution_owner_token, execution_lease_expires_at, completed_at, result, assistant_action from copilot_async_tool_calls where run_id = ${bulkRunId} and tool_call_id = ${precedingCallId}`
1642+
assert(savedExecution)
1643+
try {
1644+
for (const fixture of [
1645+
{ status: 'running', label: 'Running', settled: false, unknown: false },
1646+
{ status: 'failed', label: 'Failed', settled: true, unknown: false },
1647+
{ status: 'completed', label: 'Outcome unknown', settled: true, unknown: true },
1648+
] as const) {
1649+
const settledAt = fixture.settled ? new Date() : null
1650+
const leaseExpiresAt = fixture.settled ? null : new Date(Date.now() + 3_600_000)
1651+
await sql`update copilot_async_tool_calls set status = ${fixture.status}, execution_started_at = now(), execution_settled_at = ${settledAt}, execution_owner_token = ${generateId()}, execution_lease_expires_at = ${leaseExpiresAt}, completed_at = ${settledAt}, result = ${sql.json({ outcomeUnknown: fixture.unknown })} where run_id = ${bulkRunId} and tool_call_id = ${precedingCallId}`
1652+
await page.reload()
1653+
const receipt = page
1654+
.getByRole('region', { name: 'Create Jira issue receipt' })
1655+
.filter({ hasText: 'Batch issue 62' })
1656+
await receipt
1657+
.getByRole('button', {
1658+
name: `View Create Jira issue receipt: ${fixture.label}`,
1659+
exact: true,
1660+
})
1661+
.click()
1662+
await capture(`action-receipt-${fixture.label.toLowerCase().replaceAll(' ', '-')}`)
1663+
}
1664+
const incompatibleBinding = {
1665+
...toRecord(savedExecution.assistant_action),
1666+
operationVersion: 2,
1667+
}
1668+
for (const fixture of [
1669+
{ name: 'details-unavailable', binding: incompatibleBinding },
1670+
{
1671+
name: 'account-unavailable',
1672+
binding: { ...incompatibleBinding, credentialId: generateId() },
1673+
},
1674+
]) {
1675+
await sql`update copilot_async_tool_calls set assistant_action = ${sql.json(fixture.binding)} where run_id = ${bulkRunId} and tool_call_id = ${precedingCallId}`
1676+
await page.reload()
1677+
await page
1678+
.getByRole('button', {
1679+
name: 'View Create Jira issue receipt: Outcome unknown',
1680+
exact: true,
1681+
})
1682+
.click()
1683+
await capture(`action-receipt-${fixture.name}`)
1684+
}
1685+
} finally {
1686+
await sql`update copilot_async_tool_calls set status = ${savedExecution.status}, execution_started_at = ${savedExecution.execution_started_at}, execution_settled_at = ${savedExecution.execution_settled_at}, execution_owner_token = ${savedExecution.execution_owner_token}, execution_lease_expires_at = ${savedExecution.execution_lease_expires_at}, completed_at = ${savedExecution.completed_at}, result = ${savedExecution.result === null ? null : sql.json(savedExecution.result)}, assistant_action = ${sql.json(savedExecution.assistant_action)} where run_id = ${bulkRunId} and tool_call_id = ${precedingCallId}`
1687+
}
16231688
}
16241689
)
16251690
await check(

‎apps/sim/scripts/test-organization-search-e2e.ts‎

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,158 @@ try {
813813
}
814814
}
815815
})
816+
await check(
817+
'Search keeps results usable through partial coverage and a failed request',
818+
async () => {
819+
await page.setViewportSize({ width: 1440, height: 1000 })
820+
const samples = [
821+
{
822+
documentId: 'launch-brief',
823+
documentName: 'Launch brief',
824+
connectorType: 'google_drive',
825+
knowledgeBaseName: 'Google Drive',
826+
sourceUrl: 'https://docs.example.test/launch',
827+
content: 'The launch checklist is complete. Final owner sign-off is due Friday.',
828+
},
829+
{
830+
documentId: 'launch-readiness',
831+
documentName: 'Launch readiness',
832+
connectorType: 'confluence',
833+
knowledgeBaseName: 'Confluence',
834+
sourceUrl: 'https://wiki.example.test/launch',
835+
content: 'Engineering validation is complete. The support handoff is still in progress.',
836+
},
837+
].map((sample) => ({
838+
...sample,
839+
knowledgeBaseId: 'preview-source',
840+
author: 'Taylor',
841+
sourceModifiedAt: '2026-10-28T18:30:00.000Z',
842+
chunkIndex: 0,
843+
similarity: 0.9,
844+
}))
845+
let unavailable = false
846+
let partial = false
847+
const searchRoute = '**/api/knowledge/search'
848+
await page.route(searchRoute, async (route) => {
849+
if (unavailable) {
850+
await route.fulfill({ status: 503, json: { error: 'Search is temporarily unavailable' } })
851+
return
852+
}
853+
const body = toRecord(route.request().postDataJSON())
854+
const source = toRecord(body.filters).source
855+
await route.fulfill({
856+
json: searchWorkspaceKnowledgeContract.response.schema.parse({
857+
success: true,
858+
data: {
859+
query: body.query,
860+
results: samples.filter((sample) => !source || sample.connectorType === source),
861+
retrieval: { status: partial ? 'partial' : 'complete', timedOutLegs: [] },
862+
live: {
863+
backend: 'live',
864+
accounts: samples.map((sample) => ({
865+
accountId: sample.documentId,
866+
provider: sample.connectorType,
867+
displayName: 'taylor@example.test',
868+
status: partial && sample.connectorType === 'confluence' ? 'timeout' : 'ok',
869+
})),
870+
guidance: '',
871+
},
872+
},
873+
}),
874+
})
875+
})
876+
try {
877+
await page.goto(`${baseUrl.origin}${root}/search?q=Launch%20readiness`)
878+
await expect(page.getByText('Launch brief', { exact: true }).first()).toBeVisible()
879+
await capture('search-populated-desktop')
880+
await page.getByRole('button', { name: 'Filter by source: All sources' }).click()
881+
await capture('search-source-filter')
882+
await page.getByRole('menuitem', { name: 'Google Drive', exact: true }).click()
883+
await expect(page).toHaveURL((url) => url.searchParams.get('source') === 'google_drive')
884+
await expect(page.getByText('Launch readiness', { exact: true })).toHaveCount(0)
885+
await capture('search-filtered')
886+
await page.getByRole('button', { name: 'Filter by date: Any time' }).click()
887+
await capture('search-date-filter')
888+
await page.keyboard.press('Escape')
889+
await page.setViewportSize({ width: 390, height: 844 })
890+
await capture('search-populated-mobile')
891+
await page.setViewportSize({ width: 1440, height: 1000 })
892+
partial = true
893+
await page.goto(`${baseUrl.origin}${root}/search?q=Partial%20coverage`)
894+
await expect(
895+
page.getByText('Some apps couldn’t be searched. Showing available results.')
896+
).toBeVisible()
897+
await capture('search-partial-coverage')
898+
unavailable = true
899+
await page.goto(`${baseUrl.origin}${root}/search?q=Provider%20outage`)
900+
await expect(page.getByText('Search couldn’t run.', { exact: true })).toBeVisible()
901+
await capture('search-error-retry')
902+
unavailable = false
903+
partial = false
904+
await page.getByRole('button', { name: 'Try again', exact: true }).click()
905+
await expect(page.getByText('Launch brief', { exact: true }).first()).toBeVisible()
906+
await expect(page.getByText('Search couldn’t run.', { exact: true })).toHaveCount(0)
907+
908+
const citedChatId = generateId()
909+
const citedAnswer =
910+
'The checklist is complete, with final owner sign-off due Friday. <source>{"id":"document:launch-brief"}</source>\n\nThe support handoff is still in progress. <source>{"id":"document:launch-readiness"}</source>'
911+
await sql`insert into copilot_chats (id, user_id, organization_id, type, title, config)
912+
values (${citedChatId}, ${userId}, ${organizationId}, 'mothership', 'Cited launch update', ${sql.json({ conversationMode: 'assistant' })})`
913+
for (const [seq, role, content] of [
914+
[1, 'user', question],
915+
[2, 'assistant', citedAnswer],
916+
] as const) {
917+
const id = generateId()
918+
const contentBlocks =
919+
role === 'assistant'
920+
? [
921+
{
922+
type: 'tool',
923+
phase: 'call',
924+
toolCall: {
925+
id: 'preview-retrieval',
926+
name: 'search_workspace',
927+
state: 'success',
928+
result: {
929+
success: true,
930+
output: {
931+
success: true,
932+
data: {
933+
results: samples.map((sample) => ({
934+
...sample,
935+
citationId: `document:${sample.documentId}`,
936+
citationUrl: sample.sourceUrl,
937+
})),
938+
},
939+
},
940+
},
941+
},
942+
},
943+
{ type: 'text', channel: 'final', content },
944+
]
945+
: undefined
946+
await sql`insert into copilot_messages (id, chat_id, message_id, role, content, seq)
947+
values (${generateId()}, ${citedChatId}, ${id}, ${role}, ${sql.json({ id, role, content, contentBlocks, timestamp: new Date().toISOString() })}, ${seq})`
948+
}
949+
await page.goto(`${baseUrl.origin}${root}/search/chat/${citedChatId}`)
950+
await expect(page.getByRole('button', { name: '2 sources', exact: true })).toBeVisible()
951+
await capture('assistant-cited-answer')
952+
await page.getByRole('button', { name: '2 sources', exact: true }).click()
953+
await capture('assistant-sources-dropdown')
954+
await page.getByRole('button', { name: 'View all sources', exact: true }).click()
955+
const sourcesPanel = page.getByLabel('Cited sources', { exact: true })
956+
await expect(sourcesPanel).toBeVisible()
957+
await expect(sourcesPanel.getByText('Launch brief', { exact: true })).toBeVisible()
958+
await expect(sourcesPanel.getByText('Launch readiness', { exact: true })).toBeVisible()
959+
await capture('assistant-resources-panel')
960+
await page.setViewportSize({ width: 390, height: 844 })
961+
await expect(sourcesPanel).toBeVisible()
962+
await capture('assistant-resources-mobile')
963+
} finally {
964+
await page.unroute(searchRoute)
965+
}
966+
}
967+
)
816968
await check(
817969
'Search-only members can reopen assistant history without access to Home chat',
818970
async () => {

0 commit comments

Comments
 (0)