Skip to content

Fix feed fidget settings resetting when switching fidgets#1646

Merged
j-paterson merged 21 commits intocanaryfrom
fix/feed-settings-reset
Jan 22, 2026
Merged

Fix feed fidget settings resetting when switching fidgets#1646
j-paterson merged 21 commits intocanaryfrom
fix/feed-settings-reset

Conversation

@Jhonattan2121
Copy link
Copy Markdown
Collaborator

@Jhonattan2121 Jhonattan2121 commented Dec 31, 2025

Summary by CodeRabbit

  • New Features

    • Per-instance activation gating for settings editors and lightweight save feedback.
  • Bug Fixes

    • Prevented cross-instance save races so only the active widget is updated and analytics fire on successful saves.
    • Avoided unnecessary reloads of initial feed data when unrelated settings change.
  • Performance

    • Faster, more reliable settings sync to reduce stale/lagging editor state.
  • Chores

    • Linting configuration and tooling updated; minor UI cleanup (removed unused elements and suggestions).

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 31, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
space-system Ready Ready Preview, Comment Jan 22, 2026 1:04am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 31, 2025

Warning

Rate limit exceeded

@j-paterson has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

Integrates 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

Cohort / File(s) Summary
Zustand settings hook
src/common/lib/hooks/useFidgetSettings.ts
New hook subscribing to per-space/tab/fidget settings from the zustand store; returns FidgetSettings or undefined based on spaceId, tabName, and fidgetId.
Settings editor / activation gating
src/common/components/organisms/FidgetSettingsEditor.tsx
Added isActive prop, safeOnSave/saveWithValidation guards, local normalization via fillWithDefaults, per-instance activeIdRef to avoid cross-instance races, zustand integration (useAppStore/useFidgetSettings/useUIColors), and analytics guarded to successful saves; toast feedback hook added.
Fidget wrapper synchronization
src/common/fidgets/FidgetWrapper.tsx
Now prefers zustand-derived settings (useFidgetSettings), computes settingsWithDefaults with empty-string fallbacks, memoizes onSave/unselect, updates backfill detection and editor mounting to use new callbacks.
Grid save ref sync
src/fidgets/layout/Grid.tsx
saveFidgetConfig now updates fidgetInstanceDatumsRef.current before persisting to avoid race conditions with rapid saves.
Effect dependency optimization
src/fidgets/farcaster/Feed.tsx
Removed settings from effect dependency array so initial-data effect re-runs only on initialData?.initialHash changes.
ESLint config migration
.eslintrc.json, eslint.config.mjs, package.json
Replaced legacy .eslintrc.json with eslint.config.mjs; updated lint script and eslint-related devDependencies in package.json.
Small cleanups / lint adjustments
src/config/index.ts, src/common/components/molecules/*, src/common/components/organisms/*, src/common/data/*, src/fidgets/*, tests/gridCleanup.test.ts
Removed unused imports, deleted dead conditionals, removed ESLint suppression comments in several files, minor renames/formatting and test import simplifications.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped into the stateful glen,
Guards at gates and hooks to mend—
Per-fidget gates keep races still,
Defaults stitched and saves that thrill,
A tiny rabbit cheers the blend.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main issue being fixed: feed fidget settings resetting when switching fidgets, which is the primary purpose of the PR.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Jhonattan2121 Jhonattan2121 marked this pull request as draft December 31, 2025 00:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/common/components/organisms/FidgetSettingsEditor.tsx (3)

181-193: Consider more robust type handling in fillWithDefaults.

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 false and number 0 are 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 from properties.fields dependency.

The effect depends on properties.fields, which could trigger re-renders if the parent component doesn't memoize the properties object. 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 properties prop, or use a deep comparison for properties.fields if this becomes a performance concern.


135-136: LGTM! Guards effectively prevent cross-instance state pollution.

The isActive guard 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0765d93 and 936303f.

📒 Files selected for processing (2)
  • src/common/components/organisms/FidgetSettingsEditor.tsx
  • src/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 safeOnSave wrapper and the guard in _onSave properly prevent updates from inactive editor instances. Combined with fillWithDefaults, 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/common/components/organisms/FidgetSettingsEditor.tsx (2)

201-202: Shadowed hasValue function with different semantics.

The module-level hasValue (line 201-202) only checks for non-empty strings, while the local hasValue inside fillWithDefaults (lines 219-222) has broader semantics including undefined/null checks. 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 fillWithDefaults and normalizeFilterType functions are recreated on every render. While the useMemo dependency array correctly includes properties.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 with fillWithDefaults in FidgetSettingsEditor.

This function has nearly identical logic to fillWithDefaults in FidgetSettingsEditor.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

📥 Commits

Reviewing files that changed from the base of the PR and between 936303f and c076fb8.

📒 Files selected for processing (2)
  • src/common/components/organisms/FidgetSettingsEditor.tsx
  • src/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 isActive guard 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 pendingSaveSignatureRef check 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 FidgetSettingsGroup instances correctly receive the same isActive check, 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 localSettingsOverride pattern 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 localSettingsOverride on bundle.id change ensures clean state when switching fidgets.


201-222: onStateChange prop is optional and not required.

FidgetSettingsEditor defines onStateChange as an optional prop that's only invoked internally when state changes occur. Since FidgetWrapper doesn't need to track intermediate (unsaved) state changes—it only responds to onSave for persisting settings—omitting this prop is correct. The codebase shows no usage of onStateChange outside of FidgetSettingsEditor, indicating it's either reserved for future use or an internal implementation detail. No changes needed.

Comment thread src/common/components/organisms/FidgetSettingsEditor.tsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 localSettingsOverride state in FidgetWrapper to preserve user edits while switching fidgets
  • Enhanced settings synchronization between the wrapper and editor components using refs and effects
  • Removed settings from 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;
},
{},
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
{},
{} as FidgetSettings,

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +243
const normalizedSettings = useMemo(
() => normalizeFilterType(fillWithDefaults(settings)),
[settings, properties.fields],
);
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +274 to +303
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]);
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
readonly properties: FidgetProperties;
settings: FidgetSettings;
onSave: (settings: FidgetSettings, shouldUnselect?: boolean) => void;
onStateChange?: (settings: FidgetSettings) => void;
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +214 to +223
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 ?? "";
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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];

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
import { FilterType as FarcasterFilterType } from "@/fidgets/farcaster/Feed";

Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import { FilterType as FarcasterFilterType } from "@/fidgets/farcaster/Feed";
type FarcasterFilterType = string;

Copilot uses AI. Check for mistakes.
Comment on lines +219 to +222
const hasValue =
value !== undefined &&
value !== null &&
(typeof value !== "string" || value.trim() !== "");
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +59
const hasValue =
value !== undefined &&
value !== null &&
(typeof value !== "string" || value.trim() !== "");

acc[f.fieldName] = hasValue ? value : f.default ?? "";
return acc;
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/common/fidgets/FidgetWrapper.tsx Outdated

lastSettingsSignatureRef.current = signature;
updateSettingsPanel(settingsWithDefaults);
}, [selectedFidgetID, bundle.id, settingsWithDefaults, updateSettingsPanel]);
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
}, [selectedFidgetID, bundle.id, settingsWithDefaults, updateSettingsPanel]);
}, [selectedFidgetID, bundle.id, updateSettingsPanel]);

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +50
const value =
settings && typeof settings === "object" && f.fieldName in settings
? (settings as any)[f.fieldName]
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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]

Copilot uses AI. Check for mistakes.
@willyogo willyogo added LGFTP Looks Good From Testing Perspective and removed LGFTP Looks Good From Testing Perspective labels Jan 5, 2026
@willyogo
Copy link
Copy Markdown
Member

willyogo commented Jan 5, 2026

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?

@willyogo willyogo requested a review from sktbrd January 5, 2026 20:39
@Jhonattan2121 Jhonattan2121 marked this pull request as draft January 5, 2026 21:02
@Jhonattan2121 Jhonattan2121 marked this pull request as ready for review January 6, 2026 10:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 extracting applyFilterTypeChange outside FidgetSettingsGroup.

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 useCallback with 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 lastSavedSettingsByFidget Map persists indefinitely at the module level. When fidgets are deleted via removeFidget(), 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 the removeFidget handler), or implementing a WeakMap-based pattern if fidget IDs could be object references.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c076fb8 and a0a5431.

📒 Files selected for processing (3)
  • src/common/components/organisms/FidgetSettingsEditor.tsx
  • src/common/fidgets/FidgetWrapper.tsx
  • src/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.current synchronously before the async saveFidgetInstanceDatums call. 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 saveWithValidation function properly:

  • Normalizes and fills defaults before validation
  • Guards against invalid Filter feeds when enforceFilterTarget is true
  • Updates signature refs to prevent duplicate processing
  • Notifies state changes before calling onSave

This 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 getSettingsWithDefaults now properly checks for undefined, null, and empty strings with clear logic. The pattern aligns with similar checks in FidgetSettingsEditor.fillWithDefaults.


244-259: Good synchronization pattern with signature-based deduplication.

The effects correctly:

  • Reset localSettingsOverride when 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.

Comment thread src/common/components/organisms/FidgetSettingsEditor.tsx Outdated
Comment thread src/common/fidgets/FidgetWrapper.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: Missing bundle.id in derivedSettings useMemo dependencies.

The memoization uses lastSavedSettingsByFidget.get(bundle.id) at line 94, but bundle.id is not in the dependency array. If bundle.id changes while bundle.config.settings remains 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: hasValue variable shadows the module-level hasValue function.

The hasValue variable at line 278 shadows the hasValue function defined at line 246. While functionally similar, this can cause confusion. Consider renaming the inner variable to hasExistingValue for 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: onStateChange prop not passed to FidgetSettingsEditor.

The FidgetSettingsEditor component accepts an optional onStateChange callback (line 35 in that file), but updateSettingsPanel doesn't provide it. The internal calls to onStateChange?.() 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 with as any discards 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 of userAddress parameter across communities.

Only the clanker case (line 227) passes userAddress to the underlying function. For example and nouns, the parameter is silently ignored and a static config is returned. This could confuse API consumers expecting their userAddress to be used.

Consider either:

  1. Adding a comment explaining that only certain communities support user-specific homebase configs
  2. 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.

Comment thread package.json
Comment on lines 38 to 40
"engines": {
"node": "^22.12.0"
"node": "^22.12.0 || >=24.0.0"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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.

Jhonattan2121 and others added 6 commits January 21, 2026 17:33
- 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
- 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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +214 to +233
// 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]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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=ts

Repository: 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.tsx

Repository: 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.tsx

Repository: 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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-plugin and @typescript-eslint/parser to v8.

ESLint was upgraded to v9, but @typescript-eslint packages remain at v7. This combination is not officially supported and will produce peer-dependency warnings. @typescript-eslint v8 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 zustandSettings is 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 effectiveSettings instead:

🔎 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 on hasChanges instead of discarding it.

The underscore prefix silences ESLint, but since this test involves overlapping fidgets that get repositioned, asserting that hasChanges is true would 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.

_previousChangedNames is cloned but never referenced. While the underscore prefix suppresses linter warnings, the cloneDeep call 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 memoizing fillWithDefaults with useCallback.

This function is defined inline and called in multiple places: useMemo (L216), useEffect (L227), and saveWithValidation (L240). While the current implementation works, memoizing it would:

  1. Make the dependency relationships explicit
  2. 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 settings block 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-hooks is listed in package.json devDependencies but not configured here. React hooks rules (like rules-of-hooks and exhaustive-deps) are important for catching common React bugs.

Consider either:

  1. Adding the react-hooks plugin and rules here, or
  2. Using eslint-config-next in 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")

Comment thread package.json Outdated
- 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
@j-paterson j-paterson merged commit c71b990 into canary Jan 22, 2026
5 checks passed
@j-paterson j-paterson deleted the fix/feed-settings-reset branch January 22, 2026 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LGFTP Looks Good From Testing Perspective

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants