Fix feed fidget settings resetting when switching fidgets#1646
Fix feed fidget settings resetting when switching fidgets#1646j-paterson merged 21 commits intocanaryfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughIntegrates zustand-backed per-instance fidget settings, adds activation gating to FidgetSettingsEditor, introduces useFidgetSettings hook, synchronizes save flows to avoid races, and adjusts effect dependencies and minor cleanups. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant FidgetWrapper
participant Hook as useFidgetSettings
participant Store as Zustand
participant Editor as FidgetSettingsEditor
participant Persist as saveConfig
User->>FidgetWrapper: Click "Edit"
FidgetWrapper->>Hook: request settings (spaceId, tabName, fidgetId)
Hook->>Store: subscribe/read settings
Store-->>Hook: return settings
Hook-->>FidgetWrapper: settingsWithDefaults
FidgetWrapper->>Editor: mount with settings, safeOnSave, isActive
User->>Editor: modify settings
User->>Editor: Save
Editor->>Editor: call isActive()? (activeIdRef check)
alt active
Editor->>Editor: fillWithDefaults & validate
Editor->>Persist: safeOnSave/saveWithValidation
Persist->>Store: persist updated settings
Store-->>Persist: confirm
Persist-->>Editor: success
Editor->>FidgetWrapper: unselect/callback
Editor->>Editor: trigger analytics
else inactive
Editor-->>User: ignore save (no-op)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/common/components/organisms/FidgetSettingsEditor.tsx (3)
181-193: Consider more robust type handling infillWithDefaults.The current logic for determining whether a value exists treats all non-string types uniformly (line 190). This means:
- Empty arrays
[]or empty objects{}are preserved instead of falling back to defaults- Boolean
falseand number0are correctly preserved (likely intended)If field values can be arrays or objects, consider adding specific checks for empty collections, or ensure this behavior is intentional.
🔎 Example: Enhanced type handling
const fillWithDefaults = (input: FidgetSettings) => properties.fields.reduce((acc, field) => { const value = input && typeof input === "object" ? (input as any)[field.fieldName] : undefined; const hasValue = value !== undefined && value !== null && - (typeof value !== "string" || value.trim() !== ""); + (typeof value !== "string" || value.trim() !== "") && + (!Array.isArray(value) || value.length > 0) && + (typeof value !== "object" || Object.keys(value).length > 0 || typeof value === "boolean"); acc[field.fieldName] = hasValue ? value : field.default ?? ""; return acc; }, {} as FidgetSettings);Note: Adjust based on your field types and intended behavior for empty collections.
199-201: Potential unnecessary re-renders fromproperties.fieldsdependency.The effect depends on
properties.fields, which could trigger re-renders if the parent component doesn't memoize thepropertiesobject. While this doesn't break functionality, it may cause the editor to re-sync state more frequently than necessary.Consider requesting that parent components memoize the
propertiesprop, or use a deep comparison forproperties.fieldsif this becomes a performance concern.
135-136: LGTM! Guards effectively prevent cross-instance state pollution.The
isActiveguard at the point of state update ensures that only the currently active editor instance can modify state. The inline callback creates a new function on each render, but the performance impact is negligible for this simple comparison.Optional: Memoize isActive callback
If you prefer to avoid creating new functions on every render:
+ const isActive = useCallback( + () => activeIdRef.current === fidgetId, + [fidgetId] + ); <FidgetSettingsGroup ... - isActive={() => activeIdRef.current === fidgetId} + isActive={isActive} />Also applies to: 143-143, 155-155, 264-265, 275-276, 287-288
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/common/components/organisms/FidgetSettingsEditor.tsxsrc/fidgets/farcaster/Feed.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/components/organisms/FidgetSettingsEditor.tsx (2)
src/common/fidgets/index.d.ts (1)
FidgetSettings(15-17)src/common/lib/hooks/useUIColors.ts (1)
useUIColors(15-49)
🔇 Additional comments (2)
src/common/components/organisms/FidgetSettingsEditor.tsx (1)
207-219: LGTM! Effective guard mechanism against stale updates.The
safeOnSavewrapper and the guard in_onSaveproperly prevent updates from inactive editor instances. Combined withfillWithDefaults, this ensures that only the active editor's state—with all defaults applied—is saved to the parent.src/fidgets/farcaster/Feed.tsx (1)
452-457: LGTM! Correctly removes unnecessary dependency.The effect body doesn't reference
settings, so removing it from the dependency array is correct. This prevents the thread stack from being cleared and the initial hash from being re-pushed when settings change, which aligns with the PR objective of preventing feed settings from resetting when switching fidgets.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/common/components/organisms/FidgetSettingsEditor.tsx (2)
201-202: ShadowedhasValuefunction with different semantics.The module-level
hasValue(line 201-202) only checks for non-empty strings, while the localhasValueinsidefillWithDefaults(lines 219-222) has broader semantics includingundefined/nullchecks. This shadowing can cause confusion and potential bugs if the wrong one is used.Consider renaming one of them for clarity:
🔎 Suggested rename
-const hasValue = (value: unknown) => - typeof value === "string" && value.trim().length > 0; +const hasNonEmptyString = (value: unknown) => + typeof value === "string" && value.trim().length > 0;Then update the usage at lines 286 and 289 to use
hasNonEmptyString.Also applies to: 219-222
240-243: Consider memoizing inner functions for consistency.The
fillWithDefaultsandnormalizeFilterTypefunctions are recreated on every render. While theuseMemodependency array correctly includesproperties.fields, consider extracting these as stable references or moving them outside the component if they don't need component-level closure access, for consistency with the memoization pattern used elsewhere.src/common/fidgets/FidgetWrapper.tsx (1)
41-63: Code duplication withfillWithDefaultsin FidgetSettingsEditor.This function has nearly identical logic to
fillWithDefaultsinFidgetSettingsEditor.tsx(lines 213-225). Consider extracting a shared utility to prevent future drift between these implementations.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/common/components/organisms/FidgetSettingsEditor.tsxsrc/common/fidgets/FidgetWrapper.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/common/fidgets/FidgetWrapper.tsx (2)
src/common/fidgets/index.d.ts (1)
FidgetSettings(15-17)src/common/components/organisms/FidgetSettingsEditor.tsx (1)
FidgetSettingsEditor(204-451)
src/common/components/organisms/FidgetSettingsEditor.tsx (3)
src/common/fidgets/index.d.ts (2)
FidgetProperties(75-88)FidgetSettings(15-17)src/common/lib/hooks/useUIColors.ts (1)
useUIColors(15-49)src/common/providers/AnalyticsProvider.tsx (1)
analytics(11-33)
🔇 Additional comments (9)
src/common/components/organisms/FidgetSettingsEditor.tsx (6)
20-28: LGTM on imports.The new imports for state management (
useRef), feed type validation (FeedType,FilterType), and user feedback (toast) are appropriately added for the new functionality.
139-167: Good defensive pattern for activation gating.The
isActiveguard prevents stale updates from reaching the wrong fidget instance when users switch between fidgets quickly. This addresses the root cause of the settings reset issue.
285-294: Verify intentional filter clearing still works.The logic at lines 292-294 preserves local filter state when incoming settings lack filters. Ensure this doesn't prevent legitimate filter clearing - though the
pendingSaveSignatureRefcheck at lines 277-283 should handle this case when saves originate locally.
309-332: Solid validation and save flow.The validation ensures Filter feeds have proper targets before saving, with appropriate user feedback via toast. The signature tracking prevents redundant state synchronization from the incoming settings effect.
339-348: LGTM on the submit handler.The handler correctly gates on active instance, validates before saving, and only tracks analytics on successful saves.
388-418: Consistent application of activation gating across all tab groups.All three
FidgetSettingsGroupinstances correctly receive the sameisActivecheck, ensuring consistent behavior across Settings, Style, and Code tabs.src/common/fidgets/FidgetWrapper.tsx (3)
76-77: Good solution for maintaining local state consistency.The
localSettingsOverridepattern provides optimistic updates while awaiting bundle config propagation, preventing the settings reset issue when switching between fidgets.Also applies to: 91-94
233-248: Proper synchronization with deduplication.The signature-based deduplication prevents unnecessary panel re-renders, and resetting
localSettingsOverrideonbundle.idchange ensures clean state when switching fidgets.
201-222:onStateChangeprop is optional and not required.
FidgetSettingsEditordefinesonStateChangeas an optional prop that's only invoked internally when state changes occur. SinceFidgetWrapperdoesn't need to track intermediate (unsaved) state changes—it only responds toonSavefor persisting settings—omitting this prop is correct. The codebase shows no usage ofonStateChangeoutside ofFidgetSettingsEditor, indicating it's either reserved for future use or an internal implementation detail. No changes needed.
There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where feed fidget settings were being reset when switching between different fidgets. The fix introduces local state management to preserve settings during editing and removes a problematic dependency that was causing unnecessary re-renders.
Key changes:
- Added
localSettingsOverridestate in FidgetWrapper to preserve user edits while switching fidgets - Enhanced settings synchronization between the wrapper and editor components using refs and effects
- Removed
settingsfrom the Feed component's initialization effect to prevent unwanted resets - Added Farcaster-specific validation to prevent saving invalid feed filter configurations
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| src/fidgets/farcaster/Feed.tsx | Removed settings from useEffect dependency array to prevent initialization effect from running when settings change |
| src/common/fidgets/FidgetWrapper.tsx | Implemented local settings override mechanism and synchronized settings panel updates with enhanced value detection logic |
| src/common/components/organisms/FidgetSettingsEditor.tsx | Added Feed-specific validation logic, complex state synchronization with refs, and active fidget checks to prevent cross-fidget updates |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| acc[f.fieldName] = hasValue ? value : f.default ?? ""; | ||
| return acc; | ||
| }, | ||
| {}, |
There was a problem hiding this comment.
The reduce function uses an empty object {} as the initial value, but the function signature expects to return FidgetSettings. This could cause type issues since the empty object isn't properly typed. Consider using {} as FidgetSettings or {} as Record<string, any> to match the FidgetSettings type definition.
| {}, | |
| {} as FidgetSettings, |
| const normalizedSettings = useMemo( | ||
| () => normalizeFilterType(fillWithDefaults(settings)), | ||
| [settings, properties.fields], | ||
| ); |
There was a problem hiding this comment.
The useMemo dependencies are missing the function definitions. The fillWithDefaults and normalizeFilterType functions are defined inline in the component body but are referenced in the normalizedSettings useMemo. Since properties.fields is in the dependency array, these functions will be recreated on every render where properties.fields changes, which could cause the memoization to be ineffective. Consider moving these functions outside the component or adding them to useCallback with proper dependencies.
| useEffect(() => { | ||
| const signature = JSON.stringify(normalizedSettings); | ||
|
|
||
| if (pendingSaveSignatureRef.current === signature) { | ||
| pendingSaveSignatureRef.current = null; | ||
| appliedSettingsSignatureRef.current = signature; | ||
| setState(normalizedSettings); | ||
| onStateChange?.(normalizedSettings); | ||
| return; | ||
| } | ||
|
|
||
| const hasIncomingFilters = FILTER_SETTING_FIELDS.some((field) => | ||
| hasValue(normalizedSettings[field]), | ||
| ); | ||
| const hasLocalFilters = FILTER_SETTING_FIELDS.some((field) => | ||
| hasValue(lastStateRef.current[field]), | ||
| ); | ||
|
|
||
| if (!hasIncomingFilters && hasLocalFilters) { | ||
| return; | ||
| } | ||
|
|
||
| if (appliedSettingsSignatureRef.current === signature) { | ||
| return; | ||
| } | ||
|
|
||
| appliedSettingsSignatureRef.current = signature; | ||
| setState(normalizedSettings); | ||
| onStateChange?.(normalizedSettings); | ||
| }, [normalizedSettings]); |
There was a problem hiding this comment.
The useEffect has a dependency on normalizedSettings which includes fidgetId but also depends on logic that recreates on every change. When the fidgetId changes in line 267, this effect at line 274 will also run because normalizedSettings changes. This could lead to the effect running twice when switching fidgets - once from the fidgetId change effect (lines 266-272) and once from this effect. The logic in lines 285-294 tries to handle this, but the dual effect execution could still cause subtle timing issues or unnecessary work.
| readonly properties: FidgetProperties; | ||
| settings: FidgetSettings; | ||
| onSave: (settings: FidgetSettings, shouldUnselect?: boolean) => void; | ||
| onStateChange?: (settings: FidgetSettings) => void; |
There was a problem hiding this comment.
The onStateChange prop is defined as optional but is never passed from the FidgetWrapper component (lines 204-211). This means the callback is always undefined and the calls to onStateChange?.(...) at lines 248, 281, 302, and 329 will never execute. If this callback is not intended to be used yet, consider removing it to reduce complexity. If it is needed, it should be passed from the parent component.
| properties.fields.reduce((acc, field) => { | ||
| const value = | ||
| input && typeof input === "object" | ||
| ? (input as any)[field.fieldName] | ||
| : undefined; | ||
| const hasValue = | ||
| value !== undefined && | ||
| value !== null && | ||
| (typeof value !== "string" || value.trim() !== ""); | ||
| acc[field.fieldName] = hasValue ? value : field.default ?? ""; |
There was a problem hiding this comment.
Using (input as any)[field.fieldName] bypasses TypeScript's type checking. This is the same type safety issue found in FidgetWrapper.tsx. Consider using a type-safe property access method instead of casting to any.
| properties.fields.reduce((acc, field) => { | |
| const value = | |
| input && typeof input === "object" | |
| ? (input as any)[field.fieldName] | |
| : undefined; | |
| const hasValue = | |
| value !== undefined && | |
| value !== null && | |
| (typeof value !== "string" || value.trim() !== ""); | |
| acc[field.fieldName] = hasValue ? value : field.default ?? ""; | |
| properties.fields.reduce<FidgetSettings>((acc, field) => { | |
| const key = field.fieldName as keyof FidgetSettings; | |
| const value = | |
| input && typeof input === "object" | |
| ? input[key] | |
| : undefined; | |
| const hasExistingValue = | |
| value !== undefined && | |
| value !== null && | |
| (typeof value !== "string" || value.trim() !== ""); | |
| acc[key] = (hasExistingValue ? value : field.default ?? "") as FidgetSettings[typeof key]; |
| import { FilterType as FarcasterFilterType } from "@/fidgets/farcaster/Feed"; | ||
|
|
There was a problem hiding this comment.
The imports of FeedType and FarcasterFilterType suggest this component now has Farcaster-specific logic. The FidgetSettingsEditor is meant to be a generic component for editing any fidget's settings, but the validation logic (lines 177-191, 227-238, 309-332) is specific to Feed fidgets. This violates the single responsibility principle and creates tight coupling. Consider moving the Feed-specific validation logic to a separate validator that can be passed as a prop, or handle it in the FidgetWrapper or Feed component itself.
| import { FilterType as FarcasterFilterType } from "@/fidgets/farcaster/Feed"; | |
| type FarcasterFilterType = string; |
| const hasValue = | ||
| value !== undefined && | ||
| value !== null && | ||
| (typeof value !== "string" || value.trim() !== ""); |
There was a problem hiding this comment.
The hasValue function defined at lines 201-202 shadows the hasValue variable defined inline at lines 219-222 within the fillWithDefaults function. While they perform similar logic, having two different implementations with the same name can be confusing and error-prone. Consider renaming one of them (e.g., hasNonEmptyValue for the outer function or using the outer function consistently).
| const hasValue = | ||
| value !== undefined && | ||
| value !== null && | ||
| (typeof value !== "string" || value.trim() !== ""); | ||
|
|
||
| acc[f.fieldName] = hasValue ? value : f.default ?? ""; | ||
| return acc; |
There was a problem hiding this comment.
The hasValue logic is duplicated across three places: in getSettingsWithDefaults here (lines 53-56), in FidgetSettingsEditor.fillWithDefaults (lines 219-222), and as a standalone function in FidgetSettingsEditor (lines 201-202). This duplication increases maintenance burden and could lead to inconsistencies if the logic needs to change. Consider extracting this to a shared utility function.
|
|
||
| lastSettingsSignatureRef.current = signature; | ||
| updateSettingsPanel(settingsWithDefaults); | ||
| }, [selectedFidgetID, bundle.id, settingsWithDefaults, updateSettingsPanel]); |
There was a problem hiding this comment.
The effect at lines 240-248 updates the settings panel whenever settingsWithDefaults changes while the fidget is selected. However, settingsWithDefaults is a memoized value that depends on derivedSettings and bundle.properties. The derivedSettings includes localSettingsOverride which is set in onSave (line 189). This creates a circular flow: user edits → onSave → setLocalSettingsOverride → derivedSettings changes → settingsWithDefaults changes → updateSettingsPanel. This could potentially cause unnecessary re-renders or create edge cases where the panel updates when not expected.
| }, [selectedFidgetID, bundle.id, settingsWithDefaults, updateSettingsPanel]); | |
| }, [selectedFidgetID, bundle.id, updateSettingsPanel]); |
| const value = | ||
| settings && typeof settings === "object" && f.fieldName in settings | ||
| ? (settings as any)[f.fieldName] |
There was a problem hiding this comment.
Using (settings as any)[f.fieldName] bypasses TypeScript's type checking. While FidgetSettings is defined as a Record type, this cast makes the code less type-safe. Consider using a type guard or accessing the property in a type-safe manner, such as settings[f.fieldName as keyof FidgetSettings] or checking if the settings object has the property before accessing it.
| const value = | |
| settings && typeof settings === "object" && f.fieldName in settings | |
| ? (settings as any)[f.fieldName] | |
| const key = f.fieldName as keyof FidgetSettings; | |
| const value = | |
| settings && typeof settings === "object" && key in settings | |
| ? settings[key] |
|
nice work @Jhonattan2121, looks great from the testing perspective. @sktbrd when you're avail, can you please review the implementation, and merge if it looks good to you? |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI Agents
In @src/common/components/organisms/FidgetSettingsEditor.tsx:
- Around line 380-410: The useEffect that reads normalizedSettings,
withFilterTypeFallback and calls onStateChange (inside the block using
pendingSaveSignatureRef, appliedSettingsSignatureRef, setState, and
lastStateRef) is missing onStateChange from its dependency array, which can
cause it to call a stale callback; update the dependency array for the effect
that references normalizedSettings and withFilterTypeFallback to also include
onStateChange (and ensure any lint-suggested stable refs are preserved) so the
latest onStateChange is used when the effect runs.
In @src/common/fidgets/FidgetWrapper.tsx:
- Around line 192-210: The onSave callback currently calls unselect() regardless
of whether saveConfig succeeded because the shouldUnselect branch is outside the
try/catch; move the shouldUnselect check (and call to unselect) into the try
block after the awaited saveConfig and state updates (or alternatively return
after the catch) so unselect() only runs on successful save; update references
in onSave (bundle.config, bundle.id, saveConfig, unselect,
lastSavedSettingsByFidget.set, setLocalSettingsOverride) accordingly.
🧹 Nitpick comments (2)
src/common/components/organisms/FidgetSettingsEditor.tsx (1)
141-171: Consider extractingapplyFilterTypeChangeoutsideFidgetSettingsGroup.This function is defined inside the component body but doesn't depend on any props or state. Moving it outside the component (or wrapping in
useCallbackwith empty deps) avoids recreating it on every render.🔎 Suggested refactor
+const applyFilterTypeChange = ( + current: FidgetSettings, + nextFilterType: string, +): FidgetSettings => { + const nextState: FidgetSettings = { + ...current, + filterType: nextFilterType, + }; + + if (nextFilterType === FarcasterFilterType.Users) { + nextState.channel = ""; + nextState.keyword = ""; + return nextState; + } + + if (nextFilterType === FarcasterFilterType.Channel) { + nextState.users = ""; + nextState.username = ""; + nextState.keyword = ""; + return nextState; + } + + if (nextFilterType === FarcasterFilterType.Keyword) { + nextState.users = ""; + nextState.username = ""; + nextState.channel = ""; + return nextState; + } + + return nextState; +}; + export const FidgetSettingsGroup: React.FC<{ // ... props -}> = ({ fields, state, setState, onSave, fidgetId, isActive }) => { - const applyFilterTypeChange = ( - current: FidgetSettings, - nextFilterType: string, - ): FidgetSettings => { - // ... function body - }; +}> = ({ fields, state, setState, onSave, fidgetId, isActive }) => {src/common/fidgets/FidgetWrapper.tsx (1)
65-65: Module-level cache accumulates entries with no cleanup when fidgets are removed.The
lastSavedSettingsByFidgetMap persists indefinitely at the module level. When fidgets are deleted viaremoveFidget(), their cached settings remain in memory. In long sessions with many fidget additions and removals, this accumulates unused entries. Consider clearing cache entries when fidgets are removed (e.g., in theremoveFidgethandler), or implementing a WeakMap-based pattern if fidget IDs could be object references.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/common/components/organisms/FidgetSettingsEditor.tsxsrc/common/fidgets/FidgetWrapper.tsxsrc/fidgets/layout/Grid.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/fidgets/FidgetWrapper.tsx (2)
src/common/fidgets/index.d.ts (1)
FidgetSettings(15-17)src/common/components/organisms/FidgetSettingsEditor.tsx (1)
FidgetSettingsEditor(260-565)
🔇 Additional comments (4)
src/fidgets/layout/Grid.tsx (1)
285-291: LGTM! Good fix for race conditions in rapid saves.The change correctly updates
fidgetInstanceDatumsRef.currentsynchronously before the asyncsaveFidgetInstanceDatumscall. This ensures subsequent rapid saves operate on the latest state rather than stale data, preventing settings from being overwritten.src/common/components/organisms/FidgetSettingsEditor.tsx (1)
416-444: Well-structured validation and save flow.The
saveWithValidationfunction properly:
- Normalizes and fills defaults before validation
- Guards against invalid Filter feeds when
enforceFilterTargetis true- Updates signature refs to prevent duplicate processing
- Notifies state changes before calling
onSaveThis addresses the core issue of settings resetting when switching fidgets.
src/common/fidgets/FidgetWrapper.tsx (2)
47-61: LGTM! Improved value checking logic.The updated
getSettingsWithDefaultsnow properly checks forundefined,null, and empty strings with clear logic. The pattern aligns with similar checks inFidgetSettingsEditor.fillWithDefaults.
244-259: Good synchronization pattern with signature-based deduplication.The effects correctly:
- Reset
localSettingsOverridewhen switching fidgets (line 248)- Use signature comparison to avoid redundant panel updates
- Only update when the fidget is selected
This effectively prevents the settings reset issue when switching fidgets.
4cba1d6 to
10b6751
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/common/fidgets/FidgetWrapper.tsx (1)
93-134: Missingbundle.idinderivedSettingsuseMemo dependencies.The memoization uses
lastSavedSettingsByFidget.get(bundle.id)at line 94, butbundle.idis not in the dependency array. Ifbundle.idchanges whilebundle.config.settingsremains the same (e.g., swapping between fidgets with identical settings), the cached lookup could return stale data.🔧 Suggested fix
- }, [bundle.config.settings, lastFetchSettings, localSettingsOverride]); + }, [bundle.id, bundle.config.settings, lastFetchSettings, localSettingsOverride]);
🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 38-40: Update and verify runtime/CI configuration to match the new
engines value in package.json ("node": "^22.12.0 || >=24.0.0"): ensure CI job
matrices (e.g., workflows that specify node versions), Dockerfiles, build
images, and any runtime environment configs accept Node 24; run the test suite
and linting under Node 24 to catch incompatibilities, and audit dependencies
(package.json / package-lock.json) for native modules that may require rebuilds
or updated versions for Node 24 compatibility; if any CI workflow or Dockerfile
pins an earlier Node version, update it to include 24 or the new range and add a
Node 24 test entry to the CI matrix.
🧹 Nitpick comments (5)
src/common/components/organisms/FidgetSettingsEditor.tsx (1)
269-289:hasValuevariable shadows the module-levelhasValuefunction.The
hasValuevariable at line 278 shadows thehasValuefunction defined at line 246. While functionally similar, this can cause confusion. Consider renaming the inner variable tohasExistingValuefor clarity.♻️ Suggested rename
const value = input && typeof input === "object" ? (input as any)[field.fieldName] : undefined; - const hasValue = + const hasExistingValue = value !== undefined && value !== null && (typeof value !== "string" || value.trim() !== ""); const skipDefault = options?.skipDefaults?.includes(field.fieldName); - acc[field.fieldName] = hasValue + acc[field.fieldName] = hasExistingValue ? value : skipDefault ? undefined : field.default ?? "";src/common/fidgets/FidgetWrapper.tsx (1)
211-232:onStateChangeprop not passed toFidgetSettingsEditor.The
FidgetSettingsEditorcomponent accepts an optionalonStateChangecallback (line 35 in that file), butupdateSettingsPaneldoesn't provide it. The internal calls toonStateChange?.()in the editor will be no-ops. If the callback is intended for future use, this is fine; otherwise, consider either passing a handler or removing the unused prop to reduce complexity.src/config/index.ts (2)
180-192: Type safety lost with...args: any[]pattern.Using
...args: any[]and casting withas anydiscards the parameter types defined in the community-specific implementations. This makes it impossible for TypeScript to catch mismatched arguments at compile time.Consider defining a union type or using function overloads to preserve type safety:
♻️ Example approach with explicit types
// Define the expected signature based on the underlying implementations export const createInitialTokenSpaceConfigForAddress = ( address: string, castHash: string | null, casterFid: string | null, symbol: string, isClankerToken: boolean, network?: string, ownerAddress?: string ) => { switch (resolveCommunity()) { case 'clanker': return clankerCreateInitialTokenSpaceConfigForAddress(address, castHash, casterFid, symbol, isClankerToken, network, ownerAddress); // ... etc } };
223-234: Inconsistent handling ofuserAddressparameter across communities.Only the
clankercase (line 227) passesuserAddressto the underlying function. Forexampleandnouns, the parameter is silently ignored and a static config is returned. This could confuse API consumers expecting theiruserAddressto be used.Consider either:
- Adding a comment explaining that only certain communities support user-specific homebase configs
- Making the communities that don't support it also accept (and ignore) the parameter consistently via a factory function
src/config/clanker/initialSpaces/initialHomebase.ts (1)
4-56: Tutorial content uses external IPFS-hosted images.The tutorial references multiple images hosted on
space.mypinata.cloud/ipfs/. These URLs depend on the Pinata gateway remaining available. Consider documenting the image CIDs separately or having a fallback if the gateway changes.
| "engines": { | ||
| "node": "^22.12.0" | ||
| "node": "^22.12.0 || >=24.0.0" | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check CI/CD configurations for Node version requirements
echo "=== Checking GitHub Actions workflows ==="
fd -e yml -e yaml . .github/workflows 2>/dev/null | while read file; do
echo "--- $file ---"
cat "$file" | grep -A 2 -B 2 "node" || echo "No node configuration found"
done
echo ""
echo "=== Checking for Dockerfile ==="
fd -g "Dockerfile*" . --max-depth 2 2>/dev/null | while read file; do
echo "--- $file ---"
cat "$file" | grep -i "node\|npm\|FROM" | head -10
done
echo ""
echo "=== Checking package.json for current Node requirement ==="
cat package.json | grep -A 2 '"engines"'Repository: blankdotspace/space-system
Length of output: 196
🌐 Web query:
Node.js version 24 release date 2025 2026
💡 Result:
Node.js 24 (v24.0.0) was first released on May 6, 2025. It entered Active LTS on October 28, 2025 (codename "Krypton") and its planned End-of-Life is April 30, 2028. [1][2][3]
Sources:
- Node.js v24.0.0 release blog (May 6, 2025). [1]
- Node.js release index (shows v24 first released May 6, 2025 and LTS info). [2]
- Node.js Release WG schedule on GitHub (Active LTS start Oct 28, 2025; EoL Apr 30, 2028). [3]
Verify Node 24 compatibility requirement in CI/CD and runtime environments.
Node.js 24 (v24.0.0) was released on May 6, 2025, and is now in Active LTS status (as of October 28, 2025). The updated engine requirement ^22.12.0 || >=24.0.0 appropriately supports this stable version. Confirm this aligns with your CI/CD pipeline, local development environments, and actual codebase compatibility with Node 24's runtime.
🤖 Prompt for AI Agents
In `@package.json` around lines 38 - 40, Update and verify runtime/CI
configuration to match the new engines value in package.json ("node": "^22.12.0
|| >=24.0.0"): ensure CI job matrices (e.g., workflows that specify node
versions), Dockerfiles, build images, and any runtime environment configs accept
Node 24; run the test suite and linting under Node 24 to catch
incompatibilities, and audit dependencies (package.json / package-lock.json) for
native modules that may require rebuilds or updated versions for Node 24
compatibility; if any CI workflow or Dockerfile pins an earlier Node version,
update it to include 24 or the new range and add a Node 24 test entry to the CI
matrix.
…dling and state management
- Remove clanker initialHomebase.ts file - Revert homebase config to use nouns version for all communities - Keep community-based resolution for other space creators
- Remove community-based resolution for space creators - Revert to simple re-exports from nouns/index - Matches canary branch implementation
b6225a4 to
4ce3d80
Compare
- Removed unused onStateChange prop and related code - Removed Feed-specific filter type normalization and validation - Removed hardcoded filterType field handling - Simplified saveWithValidation to be generic - Feed fidget already handles filter type logic internally
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/common/components/organisms/FidgetSettingsEditor.tsx`:
- Around line 214-233: The local state remains stale when switching to a fidget
with no zustand settings because the useEffect only runs when zustandSettings is
truthy; change the effect to derive from effectiveSettings instead and always
sync state: compute const normalized = fillWithDefaults(effectiveSettings) and
call setState(normalized) inside the effect, and update the effect dependency
array to [effectiveSettings, properties.fields] so state is updated whether
zustandSettings exists or is null/undefined.
| // Use zustand settings if available, fall back to props | ||
| const effectiveSettings = zustandSettings ?? settings; | ||
|
|
||
| const normalizedSettings = useMemo( | ||
| () => fillWithDefaults(effectiveSettings), | ||
| [effectiveSettings, properties.fields], | ||
| ); | ||
|
|
||
| const [state, setState] = useState<FidgetSettings>(normalizedSettings); | ||
| const activeIdRef = useRef(fidgetId); | ||
| const uiColors = useUIColors(); | ||
|
|
||
| // Update local state when zustand settings change | ||
| useEffect(() => { | ||
| if (zustandSettings) { | ||
| const normalized = fillWithDefaults(zustandSettings); | ||
| setState(normalized); | ||
| } | ||
| }, [zustandSettings, properties.fields]); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n --type=tsx -C3 '<FidgetSettingsEditor'Repository: blankdotspace/space-system
Length of output: 97
🏁 Script executed:
rg -n -C3 '<FidgetSettingsEditor' --type=tsRepository: blankdotspace/space-system
Length of output: 1568
🏁 Script executed:
# Check Grid.tsx around the usage
sed -n '340,355p' src/fidgets/layout/Grid.tsx
# Check FidgetWrapper.tsx around the usage
sed -n '205,225p' src/common/fidgets/FidgetWrapper.tsxRepository: blankdotspace/space-system
Length of output: 909
🏁 Script executed:
# Read the FidgetSettingsEditor component around the problematic lines
sed -n '200,240p' src/common/components/organisms/FidgetSettingsEditor.tsx
# Also check the component props and any zustand usage
sed -n '1,50p' src/common/components/organisms/FidgetSettingsEditor.tsxRepository: blankdotspace/space-system
Length of output: 3043
Fix stale state when switching between fidgets with missing or cleared settings.
The component isn't remounted on fidget switches (no key prop in parent usages), and the sync useEffect only updates state when zustandSettings is truthy. When switching to a fidget with no stored settings, the previous fidget's values persist in local state.
Update the useEffect to sync against effectiveSettings instead, which ensures state is kept in sync regardless of whether zustand settings exist:
Suggested fix
- // Update local state when zustand settings change
- useEffect(() => {
- if (zustandSettings) {
- const normalized = fillWithDefaults(zustandSettings);
- setState(normalized);
- }
- }, [zustandSettings, properties.fields]);
+ // Keep local state in sync when the effective settings source changes
+ useEffect(() => {
+ const normalized = fillWithDefaults(effectiveSettings);
+ setState(normalized);
+ }, [effectiveSettings, properties.fields]);🧰 Tools
🪛 Biome (2.1.2)
[error] 217-217: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 222-222: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 223-223: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 224-224: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 227-227: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🤖 Prompt for AI Agents
In `@src/common/components/organisms/FidgetSettingsEditor.tsx` around lines 214 -
233, The local state remains stale when switching to a fidget with no zustand
settings because the useEffect only runs when zustandSettings is truthy; change
the effect to derive from effectiveSettings instead and always sync state:
compute const normalized = fillWithDefaults(effectiveSettings) and call
setState(normalized) inside the effect, and update the effect dependency array
to [effectiveSettings, properties.fields] so state is updated whether
zustandSettings exists or is null/undefined.
- Remove .eslintrc.json (old format conflicts with ESLint 9) - Remove plugin:@next/next/recommended from extends (causes 'name' property error) - Remove unused Next.js plugin rule reference - ESLint 9 uses flat config format (eslint.config.mjs)
…match - Forces all packages to use the same version of @internationalized/date - Fixes type error in datetime-picker.tsx where @react-stately/calendar had a nested dependency causing version conflicts
- Update ESLint config to ignore config files and add Node.js globals - Remove all @next/next/no-img-element rule references (plugin removed) - Remove unused imports and variables - Fix constant binary expression errors (remove false && blocks) - Fix unused variable warnings by prefixing with _ or removing - Fix line length violations - Remove shadowing of undefined in networks.ts - Remove dead code (_onSelectQuery function) - All ESLint issues resolved: 0 errors, 0 warnings
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
153-158: Upgrade@typescript-eslint/eslint-pluginand@typescript-eslint/parserto v8.ESLint was upgraded to v9, but
@typescript-eslintpackages remain at v7. This combination is not officially supported and will produce peer-dependency warnings.@typescript-eslintv8 is required for full ESLint v9 compatibility.
🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 11-12: The lint:fix npm script still includes the deprecated --ext
flag; update the "lint:fix" script in package.json to remove --ext and invoke
ESLint's fix mode consistently with the existing "lint" script (e.g. change
"eslint --fix --ext .js,.jsx,.ts,.tsx src" to "eslint --fix ." so file selection
is handled by the ESLint flat config).
♻️ Duplicate comments (1)
src/common/components/organisms/FidgetSettingsEditor.tsx (1)
224-230: Potential stale state when switching to fidget with no stored settings.The effect only syncs state when
zustandSettingsis truthy. If a user switches to a fidget that has no stored settings in zustand, the local state from the previous fidget will persist.Consider syncing from
effectiveSettingsinstead:🔎 Suggested fix
- // Update local state when zustand settings change - useEffect(() => { - if (zustandSettings) { - const normalized = fillWithDefaults(zustandSettings); - setState(normalized); - } - }, [zustandSettings, properties.fields]); + // Update local state when effective settings change + useEffect(() => { + const normalized = fillWithDefaults(effectiveSettings); + setState(normalized); + }, [effectiveSettings, properties.fields]);
🧹 Nitpick comments (5)
tests/gridCleanup.test.ts (1)
84-89: Consider asserting onhasChangesinstead of discarding it.The underscore prefix silences ESLint, but since this test involves overlapping fidgets that get repositioned, asserting that
hasChangesistruewould strengthen test coverage and verify the cleanup actually detected modifications.💡 Optional: Add assertion for hasChanges
- const { cleanedLayout, cleanedFidgetInstanceDatums, hasChanges: _hasChanges } = comprehensiveCleanup( + const { cleanedLayout, cleanedFidgetInstanceDatums, hasChanges } = comprehensiveCleanup( layout, fidgetInstanceDatums, false, // hasProfile false // hasFeed );Then add an assertion after the existing ones:
// Cleanup should detect changes since overlapping fidgets were repositioned expect(hasChanges).toBe(true);src/common/data/stores/app/space/spaceStore.ts (1)
387-388: Consider removing unused variable.
_previousChangedNamesis cloned but never referenced. While the underscore prefix suppresses linter warnings, thecloneDeepcall still incurs unnecessary overhead. If this was intended for rollback logic that's no longer needed, consider removing it entirely.🧹 Suggested cleanup
const previousOrder = cloneDeep(baselineOrder); -const _previousChangedNames = cloneDeep(existingSpace.changedNames);src/common/components/organisms/FidgetSettingsEditor.tsx (1)
183-203: Consider memoizingfillWithDefaultswithuseCallback.This function is defined inline and called in multiple places:
useMemo(L216),useEffect(L227), andsaveWithValidation(L240). While the current implementation works, memoizing it would:
- Make the dependency relationships explicit
- Prevent subtle bugs if more effects depend on it later
♻️ Optional refactor
- const fillWithDefaults = ( - input: FidgetSettings, - options?: { skipDefaults?: string[] }, - ) => - properties.fields.reduce((acc, field) => { + const fillWithDefaults = React.useCallback( + (input: FidgetSettings, options?: { skipDefaults?: string[] }) => + properties.fields.reduce((acc, field) => { const value = input && typeof input === "object" ? (input as any)[field.fieldName] : undefined; const hasValue = value !== undefined && value !== null && (typeof value !== "string" || value.trim() !== ""); const skipDefault = options?.skipDefaults?.includes(field.fieldName); acc[field.fieldName] = hasValue ? value : skipDefault ? undefined : field.default ?? ""; return acc; - }, {} as FidgetSettings); + }, {} as FidgetSettings), + [properties.fields], + );eslint.config.mjs (2)
47-51: Inconsistent indentation in the settings block.The
settingsblock uses 4-space indentation while the rest of the config object (plugins, languageOptions, rules) uses 8-space indentation. This inconsistency doesn't affect functionality but harms readability.♻️ Suggested fix
- settings: { - react: { - version: "detect", - }, - }, + settings: { + react: { + version: "detect", + }, + },
23-34: Missing react-hooks plugin configuration.The
eslint-plugin-react-hooksis listed inpackage.jsondevDependencies but not configured here. React hooks rules (likerules-of-hooksandexhaustive-deps) are important for catching common React bugs.Consider either:
- Adding the react-hooks plugin and rules here, or
- Using
eslint-config-nextin extends (already added to package.json) which includes react-hooks rules♻️ Option 1: Add react-hooks plugin directly
import react from "eslint-plugin-react"; +import reactHooks from "eslint-plugin-react-hooks"; import globals from "globals";plugins: { "@stylistic": stylistic, "@typescript-eslint": typescriptEslint, react, + "react-hooks": reactHooks, },rules: { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/no-unused-vars": ["warn", {♻️ Option 2: Use eslint-config-next
...compat.extends("plugin:`@typescript-eslint/recommended`"), ...compat.extends("plugin:react/recommended"), + ...compat.extends("next/core-web-vitals"), ...compat.extends("prettier")
- Remove activeIdRef - rely on key={fidgetId} for component remounting
- Remove isActive prop and checks - component remounts on fidgetId change
- Convert saveWithValidation, safeOnSave, and _onSave to useCallback
- Add proper dependencies to useCallback hooks
- Move fillWithDefaults outside component to fix useMemo dependencies
- Cleaner, more functional code following React best practices
- Remove effectiveSettings variable - inline zustandSettings ?? settings - Remove unused return value from saveWithValidation - Simplify _onSave by removing unnecessary didSave check - Rename safeOnSave to onInlineSave for clarity
- Remove --ext flag from lint:fix script (deprecated in ESLint 9 flat config) - Update to use 'eslint --fix .' to match lint script pattern - File selection now handled by ESLint flat config
Summary by CodeRabbit
New Features
Bug Fixes
Performance
Chores
✏️ Tip: You can customize this high-level summary in your review settings.