fix(studio): fix multiple Sentry errors (#44715)

## Summary

-
[**SUPABASE-APP-E2R**](https://supabase.sentry.io/issues/SUPABASE-APP-E2R):
Guard against undefined entries in notifications array in
`AdvisorButton` (optional chaining on `.some()` callbacks)
-
[**SUPABASE-APP-EBA**](https://supabase.sentry.io/issues/SUPABASE-APP-EBA):
Remove render-time `handleError()` throw in `useEdgeFunctionsDiff` — the
hook already handles missing body data gracefully
-
[**SUPABASE-APP-BVN**](https://supabase.sentry.io/issues/SUPABASE-APP-BVN)
/
[**SUPABASE-APP-BTV**](https://supabase.sentry.io/issues/SUPABASE-APP-BTV):
Guard `localStorage` access in `FeaturePreviewContext` with try-catch,
matching the established pattern in `useLocalStorage.ts` (Safari private
browsing)
-
[**SUPABASE-APP-AV3**](https://supabase.sentry.io/issues/SUPABASE-APP-AV3):
Filter stale folder IDs before passing `expandedIds` to
`react-accessible-treeview` in the SQL editor nav

## Test plan

- [x] Verify AdvisorButton renders without errors when notifications
data has sparse pages
- [x] Verify branch merge page loads when edge function body fetch fails
- [x] Verify feature previews initialize correctly in Safari private
browsing
- [x] Verify SQL editor folder expand/collapse works after deleting a
folder

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Feature preview now falls back safely when browser storage is
unavailable
* Notifications display updated to tolerate missing entries without
errors
* Private snippets navigation no longer preserves expansion state for
removed nodes

* **Refactor**
  * Streamlined error aggregation in edge functions diff processing
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ali Waseem authored and GitHub committed 2026-04-09 11:10:47 -06:00
1 parent 2538b56b71
commit 3cb440e844
4 files changed
+27 -22

No files matched your search

@@ -39,10 +39,14 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}
setFlags(
featurePreviews.reduce((a, b) => {
const defaultOptIn = b.isDefaultOptIn
const localStorageValue = localStorage.getItem(b.key)
return {
...a,
[b.key]: !localStorageValue ? defaultOptIn : localStorageValue === 'true',
try {
const localStorageValue = window.localStorage.getItem(b.key)
return {
...a,
[b.key]: !localStorageValue ? defaultOptIn : localStorageValue === 'true',
}
} catch {
return { ...a, [b.key]: defaultOptIn }
}
}, {})
)
@@ -57,8 +61,12 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}
const value = {
flags,
onUpdateFlag: (key: string, value: boolean) => {
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, value.toString())
try {
if (typeof window !== 'undefined' && window.localStorage) {
window.localStorage.setItem(key, value ? 'true' : 'false')
}
} catch {
// Silently fail in restricted storage modes (e.g. Safari private browsing)
}
const updatedFlags = { ...flags, [key]: value }
setFlags(updatedFlags)
@@ -20,8 +20,8 @@ export const AdvisorButton = ({ projectRef }: { projectRef?: string }) => {
const notifications = useMemo(() => {
return notificationsData?.pages.flatMap((page) => page) ?? []
}, [notificationsData?.pages])
const hasUnreadNotifications = notifications.some((x) => x.status === 'new')
const hasCriticalNotifications = notifications.some((x) => x.priority === 'Critical')
const hasUnreadNotifications = notifications.some((x) => x?.status === 'new')
const hasCriticalNotifications = notifications.some((x) => x?.priority === 'Critical')
const hasCriticalIssues =
hasCriticalNotifications ||
@@ -171,6 +171,16 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
[folders, privateSnippets]
)
const privateSnippetsTreeNodeIds = useMemo(
() => new Set(privateSnippetsTreeState.map((node) => node.id.toString())),
[privateSnippetsTreeState]
)
const validExpandedFolderIds = useMemo(
() => expandedFolderIds.filter((id) => privateSnippetsTreeNodeIds.has(id)),
[expandedFolderIds, privateSnippetsTreeNodeIds]
)
const privateSnippetsLastItemIds = useMemo(
() => getLastItemIds(privateSnippetsTreeState),
[privateSnippetsTreeState]
@@ -644,7 +654,7 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
setExpandedFolderIds(expandedFolderIds.filter((x) => x !== folderId))
}
}}
expandedIds={expandedFolderIds}
expandedIds={validExpandedFolderIds}
nodeRenderer={({ element, ...props }) => {
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
@@ -11,7 +11,6 @@ import {
type EdgeFunctionsData,
} from '@/data/edge-functions/edge-functions-query'
import { edgeFunctionsKeys } from '@/data/edge-functions/keys'
import { handleError } from '@/data/fetchers'
interface UseEdgeFunctionsDiffProps {
currentBranchRef?: string
@@ -163,18 +162,6 @@ export const useEdgeFunctionsDiff = ({
isCurrentFunctionsLoading ||
isMainFunctionsLoading
// Aggregate errors across all queries and handle the first encountered error.
const firstError = [
...currentBodiesQueries,
...mainBodiesQueries,
...addedBodiesQueries,
...removedBodiesQueries,
].find((q) => q.error)?.error
if (firstError) {
handleError(firstError)
}
// Build lookup maps --------------------------------------------------------
const currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
currentBodiesQueries.forEach((q, idx) => {