Improve Linear setup toast layout

This commit is contained in:
brennanb2025
2026-06-12 17:49:37 -07:00
parent f8479327a2
commit 104d5da573
3 changed files with 85 additions and 33 deletions
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { act, type ComponentProps, type ReactNode } from 'react'
import { act, isValidElement, type ComponentProps, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { toast } from 'sonner'
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
@@ -137,6 +137,16 @@ async function clickBodyButton(label: string): Promise<void> {
})
}
type ReminderToastDescriptionProps = {
description?: string
onOpen?: () => void
}
function getLastReminderToastDescriptionProps(): ReminderToastDescriptionProps {
const description = vi.mocked(toast.warning).mock.calls.at(-1)?.[1]?.description
return isValidElement(description) ? (description.props as ReminderToastDescriptionProps) : {}
}
describe('LinearAgentSkillSetupPrompt', () => {
beforeEach(() => {
mocks.skillState.installed = false
@@ -415,14 +425,14 @@ describe('LinearAgentSkillSetupPrompt', () => {
'Orca CLI and Linear skill are missing',
expect.objectContaining({
id: 'linear-agent-skill-setup-orca.linearTicketsSkill.setupDismissed.host',
description:
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.',
action: {
label: 'Set up Linear access',
onClick: expect.any(Function)
}
description: expect.anything()
})
)
expect(getLastReminderToastDescriptionProps().description).toBe(
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.'
)
expect(getLastReminderToastDescriptionProps().onOpen).toEqual(expect.any(Function))
expect(vi.mocked(toast.warning).mock.calls.at(-1)?.[1]?.action).toBeUndefined()
})
it('treats closing the modal-only dialog as a casual dismissal', async () => {
@@ -442,10 +452,12 @@ describe('LinearAgentSkillSetupPrompt', () => {
'Orca CLI and Linear skill are missing',
expect.objectContaining({
id: 'linear-agent-skill-setup-orca.linearTicketsSkill.setupDismissed.host',
description:
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.'
description: expect.anything()
})
)
expect(getLastReminderToastDescriptionProps().description).toBe(
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.'
)
})
it('keeps remote setup nuance in reminder toast copy', async () => {
@@ -457,10 +469,12 @@ describe('LinearAgentSkillSetupPrompt', () => {
expect(toast.warning).toHaveBeenCalledWith(
'Orca CLI and Linear skill are missing',
expect.objectContaining({
description:
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI. Remote agent environments may need their own setup.'
description: expect.anything()
})
)
expect(getLastReminderToastDescriptionProps().description).toBe(
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI. Remote agent environments may need their own setup.'
)
})
it('keeps WSL target nuance in reminder toast copy', async () => {
@@ -490,10 +504,12 @@ describe('LinearAgentSkillSetupPrompt', () => {
expect(toast.warning).toHaveBeenCalledWith(
'Orca CLI and Linear skill are missing',
expect.objectContaining({
description:
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI. This setup runs in the selected WSL agent runtime.'
description: expect.anything()
})
)
expect(getLastReminderToastDescriptionProps().description).toBe(
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI. This setup runs in the selected WSL agent runtime.'
)
})
it('emits at most one reminder toast for a single eligible modal-only activation', async () => {
@@ -530,17 +546,18 @@ describe('LinearAgentSkillSetupPrompt', () => {
await unmountPrompt()
await renderPrompt({ linked: true, remote: false, surface: 'modal' })
const action = vi.mocked(toast.warning).mock.calls.at(-1)?.[1]?.action as
| { onClick?: () => void }
| undefined
const onOpen = getLastReminderToastDescriptionProps().onOpen
await act(async () => {
action?.onClick?.()
onOpen?.()
})
expect(document.body.textContent).toContain(
'Enable agents to read and edit the attached Linear ticket.'
)
expect(document.body.textContent).toContain('Mock install')
expect(toast.dismiss).toHaveBeenCalledWith(
'linear-agent-skill-setup-orca.linearTicketsSkill.setupDismissed.host'
)
})
it('permanently dismisses modal-only reminders after a toast has appeared', async () => {
@@ -549,16 +566,18 @@ describe('LinearAgentSkillSetupPrompt', () => {
await unmountPrompt()
await renderPrompt({ linked: true, remote: false, surface: 'modal' })
const action = vi.mocked(toast.warning).mock.calls.at(-1)?.[1]?.action as
| { onClick?: () => void }
| undefined
const onOpen = getLastReminderToastDescriptionProps().onOpen
await act(async () => {
action?.onClick?.()
onOpen?.()
})
expect(toast.dismiss).toHaveBeenCalledWith(
'linear-agent-skill-setup-orca.linearTicketsSkill.setupDismissed.host'
)
mocks.toastDismiss.mockClear()
await clickBodyButton("Don't show again")
expect(window.localStorage.getItem(HOST_DISMISS_STORAGE_KEY)).toBe('1')
expect(toast.dismiss).toHaveBeenCalledWith('linear-setup-toast-id')
expect(toast.dismiss).not.toHaveBeenCalled()
expect(document.body.textContent).not.toContain(
'Enable agents to read and edit the attached Linear ticket.'
)
@@ -618,9 +637,11 @@ describe('LinearAgentSkillSetupPrompt', () => {
'Orca CLI and Linear skill are missing',
expect.objectContaining({
id: 'linear-agent-skill-setup-orca.linearTicketsSkill.setupDismissed.host',
description:
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.'
description: expect.anything()
})
)
expect(getLastReminderToastDescriptionProps().description).toBe(
'Install the Orca CLI and the Linear skill to enable your agents to read and edit Linear tasks through the Orca CLI.'
)
})
})
@@ -44,6 +44,7 @@ import {
getLinearAgentSkillSetupToastTitle,
getLinearAgentSkillSetupToastDescription
} from './linear-agent-skill-setup-copy'
import { LinearAgentSkillSetupReminderToastBody } from './linear-agent-skill-setup-reminder-toast-body'
import {
getCurrentPlatform,
getLinearPromptAgentRuntime,
@@ -206,16 +207,20 @@ export function LinearAgentSkillSetupPrompt({
}
state.toastCount += 1
state.lastToastActivationId = activationId
const toastId = `linear-agent-skill-setup-${localDismissStorageKey}`
const openSetupFromToast = (): void => {
toast.dismiss(toastId)
state.activeToastId = undefined
setSetupDialogOpen(true)
}
state.activeToastId = toast.warning(toastTitle, {
id: `linear-agent-skill-setup-${localDismissStorageKey}`,
description: toastDescription,
action: {
label: translate(
'auto.components.sidebar.LinearAgentSkillSetupPrompt.openSetup',
'Set up Linear access'
),
onClick: () => setSetupDialogOpen(true)
}
id: toastId,
description: (
<LinearAgentSkillSetupReminderToastBody
description={toastDescription}
onOpen={openSetupFromToast}
/>
)
})
}, [localDismissStorageKey, missingSetup, setupDialogOpen, surface, toastDescription, toastTitle])
@@ -0,0 +1,26 @@
import { Button } from '@/components/ui/button'
import { translate } from '@/i18n/i18n'
// Why: Sonner's native action sits beside the description and pinches multi-line
// copy; this keeps the CTA in a full-width footer below the text.
export function LinearAgentSkillSetupReminderToastBody({
description,
onOpen
}: {
description: string
onOpen: () => void
}): React.JSX.Element {
return (
<div className="flex flex-col gap-3">
<p className="text-sm leading-snug text-popover-foreground/80">{description}</p>
<div className="flex justify-end">
<Button size="sm" onClick={onOpen}>
{translate(
'auto.components.sidebar.LinearAgentSkillSetupPrompt.openSetup',
'Set up Linear access'
)}
</Button>
</div>
</div>
)
}