From a0dd9c0f07a19e729c3b3e4d75e356f2209abd59 Mon Sep 17 00:00:00 2001 From: echobt Date: Fri, 27 Feb 2026 17:39:21 +0000 Subject: [PATCH] fix: guard against non-iterable extensions in settings panel (fixes PlatformNetwork/bounty-challenge#21906) The extensions() signal from ExtensionsContext can return undefined/null before initialization completes. Multiple components in the extensions directory were iterating over extensions() without defensive guards, causing 'currentExtensions is not iterable' TypeError in the settings Extensions panel. Added (extensions() || []) guards across all extension components: - ExtensionsPanel.tsx: filteredExtensions memo and badge count - PluginManager.tsx: metricsInfo, empty/list conditionals, For loop - ExtensionMarketplace.tsx: installedNames, filteredInstalled, count - ExtensionPackView.tsx: extensionStatuses memo - ExtensionBisect.tsx: foundExtensionDetails memo - ExtensionRuntimeStatus.tsx: runtimeState and stats memos - ExtensionProfiler.tsx: buildProfiles runtime states --- src/components/extensions/ExtensionBisect.tsx | 2 +- src/components/extensions/ExtensionMarketplace.tsx | 6 +++--- src/components/extensions/ExtensionPackView.tsx | 2 +- src/components/extensions/ExtensionProfiler.tsx | 4 ++-- src/components/extensions/ExtensionRuntimeStatus.tsx | 4 ++-- src/components/extensions/ExtensionsPanel.tsx | 4 ++-- src/components/extensions/PluginManager.tsx | 8 ++++---- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/extensions/ExtensionBisect.tsx b/src/components/extensions/ExtensionBisect.tsx index 625e81d..5ab9a05 100644 --- a/src/components/extensions/ExtensionBisect.tsx +++ b/src/components/extensions/ExtensionBisect.tsx @@ -40,7 +40,7 @@ export const ExtensionBisect: Component = (props) => { // Get the found extension details const foundExtensionDetails = createMemo(() => { if (!state.foundExtension) return null; - return extensions.extensions().find( + return (extensions.extensions() || []).find( e => e.manifest.name === state.foundExtension ); }); diff --git a/src/components/extensions/ExtensionMarketplace.tsx b/src/components/extensions/ExtensionMarketplace.tsx index bb6a42f..7ae7d78 100644 --- a/src/components/extensions/ExtensionMarketplace.tsx +++ b/src/components/extensions/ExtensionMarketplace.tsx @@ -84,7 +84,7 @@ export const ExtensionMarketplace: Component = (props // Get list of installed extension names for comparison const installedNames = createMemo(() => - new Set(extensions().map((ext) => ext.manifest.name)) + new Set((extensions() || []).map((ext) => ext.manifest.name)) ); // Filter marketplace extensions based on search and category @@ -114,7 +114,7 @@ export const ExtensionMarketplace: Component = (props // Filter installed extensions based on search and category const filteredInstalled = createMemo(() => { - let exts = extensions(); + let exts = extensions() || []; const query = searchQuery().toLowerCase(); // Apply text search @@ -418,7 +418,7 @@ export const ExtensionMarketplace: Component = (props "border-radius": "var(--cortex-radius-full)", }} > - {extensions().length} + {(extensions() || []).length} diff --git a/src/components/extensions/ExtensionPackView.tsx b/src/components/extensions/ExtensionPackView.tsx index 9889466..446a000 100644 --- a/src/components/extensions/ExtensionPackView.tsx +++ b/src/components/extensions/ExtensionPackView.tsx @@ -115,7 +115,7 @@ export const ExtensionPackView: Component = (props) => { const currentPack = pack(); if (!currentPack) return []; - const installedExtensions = extensions(); + const installedExtensions = extensions() || []; return currentPack.extensionIds.map((id) => { const ext = installedExtensions.find((e) => e.manifest.name === id); diff --git a/src/components/extensions/ExtensionProfiler.tsx b/src/components/extensions/ExtensionProfiler.tsx index 1b6080e..e778176 100644 --- a/src/components/extensions/ExtensionProfiler.tsx +++ b/src/components/extensions/ExtensionProfiler.tsx @@ -70,8 +70,8 @@ export const ExtensionProfiler: Component = (props) => { // Build profiles from runtime state const buildProfiles = (): ExtensionProfile[] => { - const runtimeStates = runtime.extensions(); - const enabledExts = enabledExtensions(); + const runtimeStates = runtime.extensions() || []; + const enabledExts = enabledExtensions() || []; return runtimeStates.map((state) => { const ext = enabledExts.find((e) => e.manifest.name === state.id); diff --git a/src/components/extensions/ExtensionRuntimeStatus.tsx b/src/components/extensions/ExtensionRuntimeStatus.tsx index e838094..0efcff2 100644 --- a/src/components/extensions/ExtensionRuntimeStatus.tsx +++ b/src/components/extensions/ExtensionRuntimeStatus.tsx @@ -86,7 +86,7 @@ export const RuntimeStatusBadge: Component = (props) => // Get runtime state for this extension const runtimeState = createMemo(() => { - return runtime.extensions().find((s) => s.id === props.extensionId); + return (runtime.extensions() || []).find((s) => s.id === props.extensionId); }); // Determine display status @@ -266,7 +266,7 @@ export const RuntimeStatusSummary: Component = () => { const runtime = useExtensionRuntime(); const stats = createMemo(() => { - const states = runtime.extensions(); + const states = runtime.extensions() || []; const active = states.filter( (s) => s.status === ExtensionStatus.Active ).length; diff --git a/src/components/extensions/ExtensionsPanel.tsx b/src/components/extensions/ExtensionsPanel.tsx index a79cab3..b562b71 100644 --- a/src/components/extensions/ExtensionsPanel.tsx +++ b/src/components/extensions/ExtensionsPanel.tsx @@ -71,7 +71,7 @@ export const ExtensionsPanel: Component = (props) => { return []; } - let exts = extensions(); + let exts = extensions() || []; const query = searchQuery().toLowerCase(); // Apply text search @@ -204,7 +204,7 @@ export const ExtensionsPanel: Component = (props) => {

Extensions

- {extensions().length} + {(extensions() || []).length} {/* Updates available badge */} 0}>
= (props) => { }; const metricsInfo = () => { - const exts = extensions(); + const exts = extensions() || []; const withTime = exts.filter((e) => e.activationTime != null); if (withTime.length === 0) return null; const avg = withTime.reduce((s, e) => s + e.activationTime!, 0) / withTime.length; @@ -227,13 +227,13 @@ export const PluginManager: Component = (props) => {
- + - 0}> + 0}>
- + {(ext) => { const busy = () => busyIds().has(ext.id); return (