Skip to content

Commit a9eda2e

Browse files
arvsrnBrendonovich
andauthored
feat(app): migrate to solid-sonner (#39519)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
1 parent 73009f0 commit a9eda2e

10 files changed

Lines changed: 406 additions & 209 deletions

File tree

bun.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"@sentry/solid": "10.36.0",
9191
"@sentry/vite-plugin": "4.6.0",
9292
"solid-js": "1.9.10",
93+
"solid-sonner": "0.3.1",
9394
"vite-plugin-solid": "2.11.10",
9495
"@lydell/node-pty": "1.2.0-beta.12"
9596
}

packages/app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
"build": "vite build",
2020
"serve": "vite preview",
2121
"test": "bun run test:unit && bun run test:browser",
22-
"test:unit": "bun test --only-failures --preload ./happydom.ts ./src",
22+
"test:unit": "bun test --conditions=solid --only-failures --preload ./happydom.ts ./src",
2323
"test:browser": "bun test --conditions=browser --preload ./happydom.ts ./test-browser",
24-
"test:unit:watch": "bun test --watch --preload ./happydom.ts ./src",
24+
"test:unit:watch": "bun test --conditions=solid --watch --preload ./happydom.ts ./src",
2525
"test:e2e": "playwright test",
2626
"test:e2e:local": "playwright test",
2727
"test:e2e:ui": "playwright test --ui",

packages/app/src/pages/layout.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ import { createStore, produce, reconcile } from "solid-js/store"
3333
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
3434
import type { DragEvent } from "@thisbeyond/solid-dnd"
3535
import { useProviders } from "@/hooks/use-providers"
36-
import { toaster } from "@opencode-ai/ui/toast"
37-
import { setV2Toast, showToast, ToastRegion } from "@/utils/toast"
36+
import { dismissToast, setV2Toast, showToast, ToastRegion } from "@/utils/toast"
3837
import { useServerSDK } from "@/context/server-sdk"
3938
import { normalizeProjectInfo } from "@/context/global-sync/utils"
4039
import { clearWorkspaceTerminals } from "@/context/terminal"
@@ -384,7 +383,7 @@ export default function LegacyLayout(props: ParentProps) {
384383
const dismissSessionAlert = (sessionKey: string) => {
385384
const toastId = toastBySession.get(sessionKey)
386385
if (toastId === undefined) return
387-
toaster.dismiss(toastId)
386+
dismissToast(toastId)
388387
toastBySession.delete(sessionKey)
389388
alertedAtBySession.delete(sessionKey)
390389
}
@@ -1459,7 +1458,7 @@ export default function LegacyLayout(props: ParentProps) {
14591458
title: language.t("workspace.resetting.title"),
14601459
description: language.t("workspace.resetting.description"),
14611460
})
1462-
const dismiss = () => toaster.dismiss(progress)
1461+
const dismiss = () => dismissToast(progress)
14631462

14641463
const sessions = await listAllSessions(serverSDK().api.session, { directory, order: "desc" }).catch(() => [])
14651464

@@ -2450,7 +2449,7 @@ function UpdateAvailableToast(props: {
24502449

24512450
onCleanup(() => {
24522451
if (toastId === undefined) return
2453-
toaster.dismiss(toastId)
2452+
dismissToast(toastId)
24542453
})
24552454

24562455
return null

packages/app/src/utils/toast.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Icon, type IconProps } from "@opencode-ai/ui/icon"
2-
import { Toast, showToast as showLegacyToast, type ToastOptions, type ToastVariant } from "@opencode-ai/ui/toast"
3-
import { ToastV2, showToastV2 } from "@opencode-ai/ui/v2/toast-v2"
2+
import {
3+
Toast,
4+
showToast as showLegacyToast,
5+
toaster as legacyToaster,
6+
type ToastOptions,
7+
type ToastVariant,
8+
} from "@opencode-ai/ui/toast"
9+
import { ToastV2, showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
410

511
let v2 = false
612

@@ -27,6 +33,13 @@ export function showToast(options: ToastOptions | string) {
2733
})
2834
}
2935

36+
// v1 and v2 ids come from separate registries, so dismissal has to use the same
37+
// implementation that issued the id.
38+
export function dismissToast(toastId: number) {
39+
if (!v2) return legacyToaster.dismiss(toastId)
40+
return toasterV2.dismiss(toastId)
41+
}
42+
3043
function resolveIcon(icon: IconProps["name"] | undefined, variant: ToastVariant | undefined) {
3144
const name = icon ?? (variant === "success" ? "check" : undefined)
3245
if (!name) return

packages/app/test-browser/toast-owner.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
1-
import { describe, expect, test } from "bun:test"
1+
import { beforeEach, describe, expect, test } from "bun:test"
22
import { createSignal, type JSX } from "solid-js"
33
import { showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
44

55
describe("showToastV2", () => {
6+
// The toast registry is module state, so each test starts from an empty stack.
7+
beforeEach(() => {
8+
toasterV2.dismiss()
9+
})
10+
11+
test("coalesces exact active content", () => {
12+
const first = showToastV2({ title: "Repeated error", description: "Try again" })
13+
const second = showToastV2({ title: "Repeated error", description: "Try again" })
14+
const different = showToastV2({ title: "Repeated error", description: "A different error" })
15+
16+
expect(second).toBe(first)
17+
expect(different).not.toBe(first)
18+
19+
toasterV2.dismiss(first)
20+
toasterV2.dismiss(different)
21+
})
22+
23+
test("allows dismissed content to appear again", () => {
24+
const first = showToastV2("Dismiss and retry")
25+
toasterV2.dismiss(first)
26+
27+
const second = showToastV2("Dismiss and retry")
28+
expect(second).not.toBe(first)
29+
30+
toasterV2.dismiss(second)
31+
})
32+
33+
test("recreates matching content when it is not the topmost toast", () => {
34+
const first = showToastV2("First toast")
35+
const topmost = showToastV2("Topmost toast")
36+
const repeated = showToastV2("First toast")
37+
38+
expect(repeated).not.toBe(first)
39+
40+
toasterV2.dismiss(topmost)
41+
toasterV2.dismiss(repeated)
42+
})
43+
644
test("creates no reactive computations at call time", () => {
745
const [tick, setTick] = createSignal(0)
846
let reads = 0

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"remend": "catalog:",
105105
"shiki": "catalog:",
106106
"solid-list": "catalog:",
107+
"solid-sonner": "catalog:",
107108
"strip-ansi": "7.1.2"
108109
},
109110
"peerDependencies": {

0 commit comments

Comments
 (0)