Skip to content

🛟 fix: Add Section-Level Reset to Escape Stuck Partial Overrides#48

Closed
dustinhealy wants to merge 1 commit into
mainfrom
fix/config-section-reset
Closed

🛟 fix: Add Section-Level Reset to Escape Stuck Partial Overrides#48
dustinhealy wants to merge 1 commit into
mainfrom
fix/config-section-reset

Conversation

@dustinhealy
Copy link
Copy Markdown
Contributor

Summary

Some config sections have cross-field invariants enforced by zod refinements (e.g. modelSpecs requires list.length > 0 when enforce: true). The admin panel's path-granular override stream has no atomic op to drop two fields at once, so a user who toggles enforce on and later deletes the last list entry ends up with a save the schema rejects and no per-field reset can unstick it.

This PR adds a "Reset section" affordance on every ConfigSection header that atomically clears every override under the section path in a single save. The handler drops pending sub-edits under the prefix and writes a single undefined at the section path; on save this routes through resetBaseConfigFieldFn$unset overrides.<sectionPath> server-side, leaving the section at its librechat.yaml defaults.

The button only renders when the user has edit permission and the section has at least one persisted DB override or pending edit, so it stays out of the way during normal editing.

Changes

  • ConfigPage.tsx -- handleResetSection callback that strips sub-edits from editedValues and queues undefined at the section path
  • ConfigTabContent.tsx -- wires onResetSection through to inline sections; for accordion sections, renders an AccordionSectionHeader carrying the reset button alongside the title
  • ConfigSection.tsx -- adds ResetSectionButton rendered in both the inline and collapsible header variants
  • types/config-ui.ts -- sectionPath, onResetSection, hasOverrides props on ConfigSectionProps; onResetSection on ConfigTabContentProps
  • locales/en/translation.json -- com_config_reset_section, com_config_section_reset_confirm, com_a11y_reset_section

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

  1. bun run dev against a LibreChat backend.
  2. Reproduce the stuck state: open the modelSpecs section, toggle enforce: true, add one entry, save. Then delete that entry and try to save, the schema rejects the save and there's no way out via per-field reset.
  3. Click the new "Reset section" button in the modelSpecs header, confirm the dialog, then save. The section should revert to its librechat.yaml defaults and the form unblocks.
  4. Verify the button hides when (a) the section has no overrides, (b) the user lacks edit permission, and (c) the form is in previewMode / fieldsDisabled.
  5. Verify per-field reset and ordinary edits/saves still work in adjacent sections.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • Local unit tests pass with my changes (550/550)

Some sections, like modelSpecs, have cross-field invariants enforced by
zod refinements (e.g. enforce: true requires list.length > 0). When a
user toggles enforce on, then later deletes the last list entry, the
admin panel cannot save: the path-granular override stream lacks an
atomic operation to drop both the toggle and the (now-empty) list at
once, so the partial state is rejected by the schema and the section
gets stuck.

Adds a "Reset section" affordance to every ConfigSection header that
clears all overrides under the section path in a single save. On
dispatch the handler drops any pending sub-edits under the prefix and
writes a single undefined at the section path; on save this routes
through resetBaseConfigFieldFn to a $unset overrides.<sectionPath> on
the LibreChat side, leaving the section back at its librechat.yaml
defaults.

The button only appears when the user has edit permission and the
section has at least one persisted DB override or pending edit, so it
stays out of the way during normal editing.

Fixes AI-896.
@dustinhealy
Copy link
Copy Markdown
Contributor Author

Closing in favor of an upstream fix that addresses the root cause rather than working around it.

The stuck-state happens because specsConfigSchema.list has an unconditional .min(1) in librechat-data-provider that rejects list: [] regardless of enforce. The admin panel's path-granular validateFieldValue therefore can't accept the empty-list payload even when batched with enforce: false, so the section becomes unrecoverable.

Upstream PR: danny-avila/LibreChat#13036 -- relaxes the schema to .default([]), adds a runtime warn for the enforce: true && list: [] state, and tightens the buildEndpointOption.js guard. Once that lands and a new librechat-data-provider is published, bumping the version in this repo picks up the fix automatically -- no admin-panel changes required.

The "Reset section" affordance this PR introduced is no longer load-bearing for the bug. Keeping it would just add a destructive footgun visible during normal editing for a workflow that the upstream fix removes entirely.

@dustinhealy dustinhealy closed this May 9, 2026
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.

1 participant