{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "update-notification", "title": "Update Notification", "description": "A headless web update detector with toast, banner, and inline notification presenters", "dependencies": ["daisyui", "lucide-react"], "files": [ { "path": "registry/default/ui/update-notification/use-update-notification.ts", "content": "'use client';\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\n\nexport const UPDATE_NOTIFICATION_DEFAULT_INTERVAL = 5 * 60 * 1000;\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport type UpdateCheckStatus = 'idle' | 'checking' | 'ready' | 'error';\nexport type UpdateAvailabilityStatus = 'unknown' | 'current' | 'available' | 'dismissed';\nexport type UpdateCheckReason = 'mount' | 'visibility' | 'interval' | 'configuration' | 'manual';\n\nexport type UpdateCheckContext = {\n\treason: UpdateCheckReason;\n};\n\nexport type UpdateDetectedEvent = {\n\tcurrentVersion: string;\n\tlatestVersion: string;\n\tcheckedAt: number;\n\treason: UpdateCheckReason;\n};\n\nexport type UpdateNotificationState = {\n\tcheckStatus: UpdateCheckStatus;\n\tupdateStatus: UpdateAvailabilityStatus;\n\tcurrentVersion?: string;\n\tlatestVersion?: string;\n\tcheckedAt?: number;\n\terror?: unknown;\n\topen: boolean;\n};\n\nexport type UpdateNotificationController = UpdateNotificationState & {\n\tcheck: (reason?: UpdateCheckReason) => Promise;\n\tdismiss: () => void;\n\treopen: () => void;\n};\n\nexport type UseUpdateNotificationOptions = {\n\t/** Fetches the deployed version. Pair semantic source changes with sourceKey; callback identity may change freely. */\n\tgetVersion: (context: UpdateCheckContext) => string | undefined | Promise;\n\t/** Loaded-page version, captured on first render. When omitted, the first successful check becomes the baseline. */\n\tcurrentVersion?: string;\n\t/** Change this key when the version source changes to invalidate an in-flight check and check the new source. */\n\tsourceKey?: string | number;\n\tenabled?: boolean;\n\tinterval?: number | false;\n\tcheckOnMount?: boolean;\n\tcheckOnVisibility?: boolean;\n\tonUpdate?: (event: UpdateDetectedEvent) => void;\n\tonError?: (error: unknown) => void;\n};\n\nexport type ResolveUpdateVersionInput = {\n\tcurrentVersion?: string;\n\tlatestVersion: string;\n\tdismissedVersion?: string;\n};\n\nexport type ResolvedUpdateVersion = {\n\tcurrentVersion: string;\n\tlatestVersion: string;\n\tupdateStatus: Exclude;\n\topen: boolean;\n};\n\nconst initialState: UpdateNotificationState = {\n\tcheckStatus: 'idle',\n\tupdateStatus: 'unknown',\n\topen: false,\n};\n\ntype UpdateConfiguration = {\n\tenabled: boolean;\n\tgeneration: number;\n\tgetVersion: UseUpdateNotificationOptions['getVersion'];\n\tsourceKey?: string | number;\n};\n\ntype InFlightCheck = {\n\tgeneration: number;\n\tpromise: Promise;\n};\n\nexport function useUpdateNotification({\n\tgetVersion,\n\tcurrentVersion,\n\tsourceKey,\n\tenabled = true,\n\tinterval = UPDATE_NOTIFICATION_DEFAULT_INTERVAL,\n\tcheckOnMount = true,\n\tcheckOnVisibility = true,\n\tonUpdate,\n\tonError,\n}: UseUpdateNotificationOptions): UpdateNotificationController {\n\tconst configuredCurrentVersion = currentVersion?.trim() || undefined;\n\tconst [state, setState] = useState(() => ({\n\t\t...initialState,\n\t\tcurrentVersion: configuredCurrentVersion,\n\t}));\n\tconst mountedRef = useRef(false);\n\tconst configurationRef = useRef({ enabled, generation: 0, getVersion, sourceKey });\n\tconst onUpdateRef = useRef(onUpdate);\n\tconst onErrorRef = useRef(onError);\n\tconst currentVersionRef = useRef(configuredCurrentVersion);\n\tconst latestVersionRef = useRef(undefined);\n\tconst dismissedVersionRef = useRef(undefined);\n\tconst inFlightRef = useRef(undefined);\n\tconst pendingConfigurationRef = useRef(false);\n\tconst pageVisible = usePageVisibility();\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tconst previous = configurationRef.current;\n\t\tconst sourceChanged = previous.sourceKey !== sourceKey;\n\t\tconst configurationChanged = previous.enabled !== enabled || sourceChanged;\n\t\tif (sourceChanged) pendingConfigurationRef.current = true;\n\t\tconfigurationRef.current = {\n\t\t\tenabled,\n\t\t\tgeneration: previous.generation + (configurationChanged ? 1 : 0),\n\t\t\tgetVersion,\n\t\t\tsourceKey,\n\t\t};\n\t\tonUpdateRef.current = onUpdate;\n\t\tonErrorRef.current = onError;\n\t\tif (configurationChanged) {\n\t\t\tsetState((current) => (current.checkStatus === 'checking' ? { ...current, checkStatus: 'idle' } : current));\n\t\t}\n\t}, [enabled, getVersion, onError, onUpdate, sourceKey]);\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tmountedRef.current = true;\n\t\treturn () => {\n\t\t\tmountedRef.current = false;\n\t\t};\n\t}, []);\n\n\tconst check = useCallback(async (reason: UpdateCheckReason = 'manual') => {\n\t\tconst configuration = configurationRef.current;\n\t\tif (!configuration.enabled) return;\n\t\tif (inFlightRef.current?.generation === configuration.generation) return inFlightRef.current.promise;\n\n\t\tconst isCurrentConfiguration = () =>\n\t\t\tmountedRef.current &&\n\t\t\tconfigurationRef.current.enabled &&\n\t\t\tconfigurationRef.current.generation === configuration.generation;\n\t\tconst run = async () => {\n\t\t\tif (mountedRef.current) {\n\t\t\t\tsetState((current) => ({ ...current, checkStatus: 'checking', error: undefined }));\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst value = await configuration.getVersion({ reason });\n\t\t\t\tif (!isCurrentConfiguration()) return;\n\t\t\t\tconst latestVersion = value?.trim();\n\t\t\t\tconst checkedAt = Date.now();\n\t\t\t\tif (!latestVersion) {\n\t\t\t\t\tsetState((current) => ({ ...current, checkStatus: 'ready', checkedAt, error: undefined }));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst previousLatestVersion = latestVersionRef.current;\n\t\t\t\tconst resolved = resolveUpdateVersion({\n\t\t\t\t\tcurrentVersion: currentVersionRef.current,\n\t\t\t\t\tlatestVersion,\n\t\t\t\t\tdismissedVersion: dismissedVersionRef.current,\n\t\t\t\t});\n\t\t\t\tcurrentVersionRef.current = resolved.currentVersion;\n\t\t\t\tlatestVersionRef.current = latestVersion;\n\t\t\t\tif (resolved.updateStatus === 'current') dismissedVersionRef.current = undefined;\n\t\t\t\tsetState({ ...resolved, checkStatus: 'ready', checkedAt, error: undefined });\n\n\t\t\t\tif (resolved.updateStatus === 'available' && previousLatestVersion !== latestVersion) {\n\t\t\t\t\tnotifyCallback(\n\t\t\t\t\t\t() => onUpdateRef.current?.({ ...resolved, checkedAt, reason }),\n\t\t\t\t\t\t'[UpdateNotification] onUpdate failed.',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCurrentConfiguration()) return;\n\t\t\t\tsetState((current) => ({ ...current, checkStatus: 'error', checkedAt: Date.now(), error }));\n\t\t\t\tnotifyCallback(() => onErrorRef.current?.(error), '[UpdateNotification] onError failed.');\n\t\t\t}\n\t\t};\n\n\t\tconst promise = run();\n\t\tconst request = { generation: configuration.generation, promise };\n\t\tinFlightRef.current = request;\n\t\ttry {\n\t\t\tawait promise;\n\t\t} finally {\n\t\t\tif (inFlightRef.current === request) inFlightRef.current = undefined;\n\t\t}\n\t}, []);\n\n\tconst dismiss = useCallback(() => {\n\t\tconst latestVersion = latestVersionRef.current;\n\t\tif (!latestVersion || latestVersion === currentVersionRef.current) return;\n\t\tdismissedVersionRef.current = latestVersion;\n\t\tsetState((current) => ({ ...current, updateStatus: 'dismissed', open: false }));\n\t}, []);\n\n\tconst reopen = useCallback(() => {\n\t\tconst latestVersion = latestVersionRef.current;\n\t\tif (!latestVersion || latestVersion === currentVersionRef.current) return;\n\t\tdismissedVersionRef.current = undefined;\n\t\tsetState((current) => ({ ...current, updateStatus: 'available', open: true }));\n\t}, []);\n\n\tconst autoCheckRef = useRef({ activated: false, enabled: false, visible: pageVisible });\n\tuseEffect(() => {\n\t\tconst previous = autoCheckRef.current;\n\t\tconst visible = getPageVisibility();\n\t\tlet reason: UpdateCheckReason | undefined;\n\t\tif (enabled && visible) {\n\t\t\tif (pendingConfigurationRef.current) {\n\t\t\t\treason = 'configuration';\n\t\t\t} else if (!previous.activated) {\n\t\t\t\tif (checkOnMount || (!previous.visible && checkOnVisibility)) {\n\t\t\t\t\treason = previous.visible ? 'mount' : 'visibility';\n\t\t\t\t}\n\t\t\t} else if (!previous.enabled) {\n\t\t\t\tif (checkOnMount) reason = 'mount';\n\t\t\t} else if (!previous.visible && checkOnVisibility) {\n\t\t\t\treason = 'visibility';\n\t\t\t}\n\t\t}\n\t\tautoCheckRef.current = {\n\t\t\tactivated: previous.activated || (enabled && visible),\n\t\t\tenabled,\n\t\t\tvisible,\n\t\t};\n\t\tif (reason) {\n\t\t\tpendingConfigurationRef.current = false;\n\t\t\tvoid check(reason);\n\t\t}\n\t}, [check, checkOnMount, checkOnVisibility, enabled, pageVisible, sourceKey]);\n\n\tuseEffect(() => {\n\t\tif (!enabled || !getPageVisibility() || interval === false) return;\n\t\tconst delay = normalizeInterval(interval);\n\t\tif (delay === 0) return;\n\t\tconst handle = window.setInterval(() => void check('interval'), delay);\n\t\treturn () => window.clearInterval(handle);\n\t}, [check, enabled, interval, pageVisible]);\n\n\treturn { ...state, check, dismiss, reopen };\n}\n\nexport function resolveUpdateVersion({\n\tcurrentVersion,\n\tlatestVersion,\n\tdismissedVersion,\n}: ResolveUpdateVersionInput): ResolvedUpdateVersion {\n\tconst baseline = currentVersion ?? latestVersion;\n\tif (baseline === latestVersion) {\n\t\treturn { currentVersion: baseline, latestVersion, updateStatus: 'current', open: false };\n\t}\n\tconst dismissed = dismissedVersion === latestVersion;\n\treturn {\n\t\tcurrentVersion: baseline,\n\t\tlatestVersion,\n\t\tupdateStatus: dismissed ? 'dismissed' : 'available',\n\t\topen: !dismissed,\n\t};\n}\n\nfunction usePageVisibility() {\n\treturn useSyncExternalStore(subscribePageVisibility, getPageVisibility, getServerPageVisibility);\n}\n\nfunction subscribePageVisibility(onStoreChange: () => void) {\n\tif (typeof document === 'undefined') return () => undefined;\n\tdocument.addEventListener('visibilitychange', onStoreChange);\n\treturn () => document.removeEventListener('visibilitychange', onStoreChange);\n}\n\nfunction getPageVisibility() {\n\treturn typeof document === 'undefined' || !document.hidden;\n}\n\nfunction getServerPageVisibility() {\n\treturn true;\n}\n\nfunction normalizeInterval(interval: number) {\n\tif (!Number.isFinite(interval) || interval <= 0) return 0;\n\treturn Math.max(1000, interval);\n}\n\nfunction notifyCallback(callback: () => void, message: string) {\n\ttry {\n\t\tcallback();\n\t} catch (error) {\n\t\tconsole.error(message, error);\n\t}\n}\n", "type": "registry:hook", "target": "@components/update-notification/use-update-notification.ts" }, { "path": "registry/default/ui/update-notification/update-notification-display.tsx", "content": "'use client';\n\nimport { ArrowRight, RefreshCw, Rocket, X } from 'lucide-react';\nimport type { ComponentPropsWithRef, ReactNode } from 'react';\nimport { useEffect, useId, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { UpdateNotificationController } from './use-update-notification';\n\nexport type UpdateNotificationLabels = {\n\ttitle: ReactNode;\n\tdescription: ReactNode;\n\tannouncement: string;\n\tcurrentVersion: string;\n\tlatestVersion: string;\n\trefresh: string;\n\tdismiss: string;\n};\n\nexport type UpdateNotificationDisplayBaseProps = {\n\tnotification: UpdateNotificationController;\n\tlabels?: Partial;\n\tonRefresh?: (notification: UpdateNotificationController) => void;\n};\n\nexport type UpdateNotificationToastProps = ComponentPropsWithRef<'aside'> & UpdateNotificationDisplayBaseProps;\nexport type UpdateNotificationBannerProps = ComponentPropsWithRef<'aside'> & UpdateNotificationDisplayBaseProps;\nexport type UpdateNotificationInlineProps = ComponentPropsWithRef<'section'> & UpdateNotificationDisplayBaseProps;\n\nexport const defaultUpdateNotificationLabels: UpdateNotificationLabels = {\n\ttitle: '发现新版本',\n\tdescription: '新版本已经发布,刷新页面后即可使用。',\n\tannouncement: '发现新版本,请刷新页面。',\n\tcurrentVersion: '当前',\n\tlatestVersion: '最新',\n\trefresh: '立即刷新',\n\tdismiss: '稍后处理',\n};\n\nexport function UpdateNotificationToast({\n\tnotification,\n\tlabels: labelOverrides,\n\tonRefresh,\n\tclassName,\n\t...props\n}: UpdateNotificationToastProps) {\n\tconst mounted = useMounted();\n\tconst titleId = useId();\n\tconst labels = { ...defaultUpdateNotificationLabels, ...labelOverrides };\n\tconst announcement = useUpdateAnnouncement(notification, labels);\n\tif (!mounted) return null;\n\n\treturn createPortal(\n\t\t<>\n\t\t\t\n\t\t\t{notification.open && (\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{labels.title}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t
{labels.description}
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t)}\n\t\t,\n\t\tdocument.body,\n\t);\n}\n\nexport function UpdateNotificationBanner({\n\tnotification,\n\tlabels: labelOverrides,\n\tonRefresh,\n\tclassName,\n\t...props\n}: UpdateNotificationBannerProps) {\n\tconst mounted = useMounted();\n\tconst titleId = useId();\n\tconst labels = { ...defaultUpdateNotificationLabels, ...labelOverrides };\n\tconst announcement = useUpdateAnnouncement(notification, labels);\n\tif (!mounted) return null;\n\n\treturn createPortal(\n\t\t<>\n\t\t\t\n\t\t\t{notification.open && (\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{labels.title}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t
{labels.description}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t,\n\t\tdocument.body,\n\t);\n}\n\nexport function UpdateNotificationInline({\n\tnotification,\n\tlabels: labelOverrides,\n\tonRefresh,\n\tclassName,\n\t...props\n}: UpdateNotificationInlineProps) {\n\tconst titleId = useId();\n\tconst labels = { ...defaultUpdateNotificationLabels, ...labelOverrides };\n\tconst announcement = useUpdateAnnouncement(notification, labels);\n\n\treturn (\n\t\t<>\n\t\t\t\n\t\t\t{notification.open && (\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{labels.title}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t
{labels.description}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t\n\t);\n}\n\ntype VersionTransitionProps = {\n\tnotification: UpdateNotificationController;\n\tlabels: UpdateNotificationLabels;\n\tclassName?: string;\n};\n\nfunction VersionTransition({ notification, labels, className }: VersionTransitionProps) {\n\tif (!notification.currentVersion || !notification.latestVersion) return null;\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{labels.currentVersion}{' '}\n\t\t\t\t\t{notification.currentVersion}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t{labels.latestVersion}{' '}\n\t\t\t\t{notification.latestVersion}\n\t\t\t\n\t\t
\n\t);\n}\n\ntype NotificationActionProps = {\n\tnotification: UpdateNotificationController;\n\tlabel: string;\n\tclassName?: string;\n};\n\nfunction DismissButton({ notification, label, className }: NotificationActionProps) {\n\treturn (\n\t\t\n\t\t\t\n\t\t\n\t);\n}\n\ntype RefreshButtonProps = NotificationActionProps & {\n\tonRefresh?: (notification: UpdateNotificationController) => void;\n};\n\nfunction RefreshButton({ notification, label, onRefresh }: RefreshButtonProps) {\n\treturn (\n\t\t (onRefresh ?? refreshPage)(notification)}\n\t\t>\n\t\t\t\n\t\t\t{label}\n\t\t\n\t);\n}\n\nfunction LiveAnnouncement({ text }: { text: string }) {\n\treturn (\n\t\t\n\t\t\t{text}\n\t\t\n\t);\n}\n\nfunction useUpdateAnnouncement(notification: UpdateNotificationController, labels: UpdateNotificationLabels) {\n\tconst [announcement, setAnnouncement] = useState('');\n\tconst message = notification.open\n\t\t? [\n\t\t\t\tlabels.announcement,\n\t\t\t\tnotification.currentVersion && `${labels.currentVersion} ${notification.currentVersion}.`,\n\t\t\t\tnotification.latestVersion && `${labels.latestVersion} ${notification.latestVersion}.`,\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' ')\n\t\t: '';\n\tuseEffect(() => {\n\t\tconst handle = window.setTimeout(() => setAnnouncement(message), 0);\n\t\treturn () => window.clearTimeout(handle);\n\t}, [message]);\n\treturn announcement;\n}\n\nfunction refreshPage() {\n\tif (typeof window !== 'undefined') window.location.reload();\n}\n\nfunction useMounted() {\n\tconst [mounted, setMounted] = useState(false);\n\tuseEffect(() => setMounted(true), []);\n\treturn mounted;\n}\n\nfunction joinClassNames(...classNames: Array) {\n\treturn classNames.filter(Boolean).join(' ');\n}\n", "type": "registry:component", "target": "@components/update-notification/update-notification-display.tsx" }, { "path": "registry/default/ui/update-notification/update-notification.tsx", "content": "'use client';\n\nimport type { ReactNode } from 'react';\nimport {\n\tUpdateNotificationBanner,\n\ttype UpdateNotificationDisplayBaseProps,\n\tUpdateNotificationInline,\n\tUpdateNotificationToast,\n} from './update-notification-display';\nimport {\n\ttype UpdateNotificationController,\n\ttype UseUpdateNotificationOptions,\n\tuseUpdateNotification,\n} from './use-update-notification';\n\nexport type UpdateNotificationDisplay = 'toast' | 'banner' | 'inline';\n\nexport type UpdateNotificationProps = UseUpdateNotificationOptions & {\n\tdisplay?: UpdateNotificationDisplay;\n\tlabels?: UpdateNotificationDisplayBaseProps['labels'];\n\tonRefresh?: UpdateNotificationDisplayBaseProps['onRefresh'];\n\tclassName?: string;\n\trender?: (notification: UpdateNotificationController) => ReactNode;\n};\n\nexport type UpdateNotificationPresenterProps = UpdateNotificationDisplayBaseProps & {\n\tdisplay?: UpdateNotificationDisplay;\n\tclassName?: string;\n};\n\nexport function UpdateNotification({\n\tdisplay = 'toast',\n\tlabels,\n\tonRefresh,\n\tclassName,\n\trender,\n\t...options\n}: UpdateNotificationProps) {\n\tconst notification = useUpdateNotification(options);\n\tif (render) return render(notification);\n\treturn (\n\t\t\n\t);\n}\n\nexport function UpdateNotificationPresenter({\n\tdisplay = 'toast',\n\tnotification,\n\tlabels,\n\tonRefresh,\n\tclassName,\n}: UpdateNotificationPresenterProps) {\n\tconst props = { notification, labels, onRefresh, className };\n\tswitch (display) {\n\t\tcase 'banner':\n\t\t\treturn ;\n\t\tcase 'inline':\n\t\t\treturn ;\n\t\tdefault:\n\t\t\treturn ;\n\t}\n}\n", "type": "registry:component", "target": "@components/update-notification/update-notification.tsx" }, { "path": "registry/default/ui/update-notification/index.ts", "content": "export {\n\tUpdateNotification,\n\ttype UpdateNotificationDisplay,\n\tUpdateNotificationPresenter,\n\ttype UpdateNotificationPresenterProps,\n\ttype UpdateNotificationProps,\n} from './update-notification';\nexport {\n\tdefaultUpdateNotificationLabels,\n\tUpdateNotificationBanner,\n\ttype UpdateNotificationBannerProps,\n\ttype UpdateNotificationDisplayBaseProps,\n\tUpdateNotificationInline,\n\ttype UpdateNotificationInlineProps,\n\ttype UpdateNotificationLabels,\n\tUpdateNotificationToast,\n\ttype UpdateNotificationToastProps,\n} from './update-notification-display';\nexport {\n\ttype ResolvedUpdateVersion,\n\ttype ResolveUpdateVersionInput,\n\tresolveUpdateVersion,\n\tUPDATE_NOTIFICATION_DEFAULT_INTERVAL,\n\ttype UpdateAvailabilityStatus,\n\ttype UpdateCheckContext,\n\ttype UpdateCheckReason,\n\ttype UpdateCheckStatus,\n\ttype UpdateDetectedEvent,\n\ttype UpdateNotificationController,\n\ttype UpdateNotificationState,\n\ttype UseUpdateNotificationOptions,\n\tuseUpdateNotification,\n} from './use-update-notification';\n", "type": "registry:component", "target": "@components/update-notification/index.ts" } ], "type": "registry:component" }