Skip to content

refactor: Implement Component Registry Architecture for Settings System#1303

Merged
kwaroran merged 12 commits intokwaroran:mainfrom
SameDesu123:refactor/settings-registry
Mar 6, 2026
Merged

refactor: Implement Component Registry Architecture for Settings System#1303
kwaroran merged 12 commits intokwaroran:mainfrom
SameDesu123:refactor/settings-registry

Conversation

@SameDesu123
Copy link
Contributor

@SameDesu123 SameDesu123 commented Mar 3, 2026

PR Checklist

  • Required Checks
    • Have you added type definitions?
    • Have you tested your changes?
    • Have you checked that it won't break any existing features?
  • If your PR uses models1, check the following:
    • Have you checked if it works normally in all models?
    • Have you checked if it works normally in all web, local, and node-hosted versions? If it doesn't, have you blocked it in those versions?
  • If your PR is highly AI generated2, check the following:
    • Have you understood what the code does?
    • Have you cleaned up any unnecessary or redundant code?
    • Is it not a huge change?
      • We currently do not accept highly AI generated PRs that are large changes.

Summary

This PR overhauls the internal architecture of SettingRenderer by introducing a Component Registry Pattern and adopting a Svelte-5-native $state/$effect value binding approach. The "God Component" is deconstructed into 12 isolated wrapper components, each registered in a type-safe settingRegistry. Functional accessors (getValue/setValue) are added to SettingItem as a safer alternative to the legacy string-based bindKey/bindPath.

Related Issues

None.

Changes

  1. Getter/Setter Type Safety (types.ts)

    • Added getValue, setValue, and onChange functional accessors to SettingItem as a type-safe, reactive alternative to bindKey/bindPath.
    • Added labelKey property to SelectOption and SegmentOption interfaces for proper i18n support.
  2. Component Registry (settingRegistry.ts)

    • Created a centralized registry mapping SettingType → wrapper components.
    • Typed as Record<SettingType, WrapperComponent> so adding a new SettingType without registering a component causes a compile-time error.
  3. Deconstruction of the God Component (SettingRenderer.svelte)

    • Stripped ~200 lines of if-else branching into a ~40-line lightweight router.
    • Dynamically mounts isolated UI wrappers via {@const Component = settingRegistry[item.type]}.
  4. Isolated UI Wrappers (src/lib/Setting/Wrappers/*)

    • Separated every setting type into its own .svelte file (12 total: Check, Text, Number, Textarea, Slider, Select, Segmented, Color, Header, Button, Accordion, Custom).
    • Each input wrapper uses Svelte 5 $state/$effect for two-way DB binding — no plain JS getter/setter proxy.
  5. Svelte 5 Reactivity Compliance (utils.ts)

    • Resolved state_referenced_locally warnings by initializing $state(undefined) and using $effect for the initial sync.
    • Applied untrack() in Select/Segmented reset effects to prevent potential infinite loops.
    • Extracted shared utilities (getLabel, getSettingValue, setSettingValue, checkCondition) into a dedicated module.

Impact

  • Maintainability: Adding new setting UI types requires zero modifications to SettingRenderer. Create a wrapper and register it — done.
  • Type Safety: The registry is now Record<SettingType, WrapperComponent>, and SelectOption/SegmentOption properly type labelKey. Missing registrations cause compile errors.
  • Reactivity: Eliminates Svelte 5 compiler warnings (state_referenced_locally) and potential reactivity desyncs from the previous plain JS proxy approach.
  • Backward Compatibility: Fully backward-compatible. Legacy bindKey/bindPath setups continue to work via the fallback path in utils.ts.

Additional Notes

  • This PR focuses on the underlying engine. The actual migration of existing settings pages to getValue/setValue will follow in separate, smaller PRs.

Footnotes

  1. Modifies the behavior of prompting, requesting, or handling responses from AI models.

  2. Over 80% of the code is AI generated.

…SettingItem

This introduces a type-safe and reactivity-friendly alternative to the legacy string-based bindKey/bindPath approach.
Created utils.ts for handling getter/setter safely, and isolated Header, Check, Text, and Slider UI logic into dedicated wrapper components.
Deconstructed the God Component 'SettingRenderer.svelte' into isolated wrapper components mapped by 'settingRegistry.ts'. SettingRenderer now strictly acts as a lightweight dynamic component mounter, drastically improving maintainability and extensibility for future complex UI requirements.
Removed local $state bindings that caused 'state_referenced_locally' warnings. Implemented a getter/setter proxy object pattern (valueProxy) to perfectly map Svelte two-way bindings directly to the underlying getSettingValue/setSettingValue utilities.
… effect

Updated the effect that resets hidden options to use the new valueProxy pattern instead of the removed local 'value' state.
…pers

- Migrate 8 input wrappers from plain JS getter/setter proxy to $state + $effect
- Apply untrack() in Select/Segmented reset effects to prevent infinite loops
- Fix state_referenced_locally warnings with $state(undefined) initialization
- Clean up residual blank lines
- Add undefined check to skip initial state before DB sync completes
- Compare localValue with DB before writing to prevent unnecessary writes
- Eliminates potential infinite loop risk for reference types
- All DB writes now only happen on actual user-driven value changes
…ppers

- Replace $state(undefined) with $state(untrack(() => getSettingValue()))
  to eliminate Flash of Undefined on initial render
- Introduce UNINITIALIZED Symbol in utils.ts as a sentinel value
  so that legitimate undefined DB values are not silently ignored
- Update write-back guard from undefined check to UNINITIALIZED check
  across all 8 wrapper components
Add nullish coalescing assignment (??=) in setSettingValue to safely
auto-create intermediate objects when writing to nested paths like
'a.b.c', preventing TypeError crashes when 'a.b' is undefined.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants