From f5d96190761e2ad2c203a7e2cc7342fe3f9c0c79 Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Sun, 22 Mar 2026 09:47:07 -0500 Subject: [PATCH 1/2] fix: validate usePluginData return type against default value shape The Go backend returns `any` from the data store, and the TypeScript generic cast (`result as T`) provides no runtime safety. When stored data has a different JSON type than expected (e.g. object instead of array), callers crash with confusing errors like "favorites.includes is not a function". Add a `matchesShape` check that validates the structural type (array, object, primitive) matches the default value before accepting the stored result. Falls back to the default with a console warning on mismatch. --- .../src/hooks/data/usePluginData.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts b/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts index 71cbe45e..ece87e27 100644 --- a/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts +++ b/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts @@ -8,6 +8,20 @@ type UsePluginDataResult = { isLoading: boolean; }; +/** + * Check whether a value from the data store structurally matches the expected + * type indicated by the default value. This catches cases where the Go backend + * returns a JSON type that doesn't match the TypeScript generic (e.g. an object + * was stored but the caller expects an array). + */ +function matchesShape(value: unknown, defaultValue: T): value is T { + if (Array.isArray(defaultValue)) return Array.isArray(value); + if (defaultValue !== null && typeof defaultValue === 'object') { + return typeof value === 'object' && value !== null && !Array.isArray(value); + } + return typeof value === typeof defaultValue; +} + /** * Generic hook for reading/writing plugin data from the Plugin Data Store. * Uses React Query for caching and optimistic updates. @@ -28,6 +42,14 @@ export function usePluginData( if (result === null || result === undefined) { return defaultValue; } + if (!matchesShape(result, defaultValue)) { + console.warn( + `[usePluginData] stored value for "${key}" has unexpected type ` + + `(expected ${Array.isArray(defaultValue) ? 'array' : typeof defaultValue}, ` + + `got ${Array.isArray(result) ? 'array' : typeof result}). Using default.`, + ); + return defaultValue; + } return result as T; }, }); From f05577fea66daac1a793734446bbe32afc293eba Mon Sep 17 00:00:00 2001 From: Joshua Pare Date: Sun, 22 Mar 2026 09:56:35 -0500 Subject: [PATCH 2/2] fix: handle null defaultValue in matchesShape typeof null === 'object' caused null defaults to match any object or array. Check for null explicitly before the object/array branches. --- packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts b/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts index ece87e27..c45cb3e7 100644 --- a/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts +++ b/packages/omniviewdev-runtime/src/hooks/data/usePluginData.ts @@ -15,8 +15,9 @@ type UsePluginDataResult = { * was stored but the caller expects an array). */ function matchesShape(value: unknown, defaultValue: T): value is T { + if (defaultValue === null) return value === null; if (Array.isArray(defaultValue)) return Array.isArray(value); - if (defaultValue !== null && typeof defaultValue === 'object') { + if (typeof defaultValue === 'object') { return typeof value === 'object' && value !== null && !Array.isArray(value); } return typeof value === typeof defaultValue;