Skip to content

feat: dynamic settings UI with auto-discovery and basic/advanced toggle#600

Merged
Aureliolo merged 8 commits intomainfrom
feat/dynamic-settings-ui
Mar 19, 2026
Merged

feat: dynamic settings UI with auto-discovery and basic/advanced toggle#600
Aureliolo merged 8 commits intomainfrom
feat/dynamic-settings-ui

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

@Aureliolo Aureliolo commented Mar 19, 2026

Summary

  • Add schema-driven dynamic settings UI that auto-discovers all 50 settings across 9 namespaces from the backend registry -- no frontend changes needed when new settings are added
  • Implement type-appropriate inputs for all 6 setting types: text, number, toggle, dropdown, textarea (JSON), password (sensitive)
  • Add Basic/Advanced toggle (localStorage-persisted) that filters settings by visibility level
  • Preserve custom Providers tab (ProviderCard CRUD) with dynamic settings below for routing/retry config
  • Add client-side validation from schema constraints (type, range, pattern, enum) with fullmatch regex semantics

New files (14)

  • web/src/api/endpoints/settings.ts -- API endpoint module (getSchema, getAllSettings, updateSetting, resetSetting)
  • web/src/stores/settings.ts -- Pinia store with generation-counter stale-response protection, exported validateSettingValue
  • web/src/components/settings/SettingField.vue -- Single setting input with dirty tracking, save/reset, validation
  • web/src/components/settings/SettingGroupRenderer.vue -- Namespace renderer grouping by group field with level filtering
  • web/src/components/settings/SettingSourceBadge.vue -- Value origin indicator (db/env/yaml/default)
  • web/src/components/settings/SettingRestartBadge.vue -- Restart-required warning badge
  • 8 test files (83 tests including property-based tests with fast-check)

Modified files (4)

  • web/src/api/types.ts -- Added SettingDefinition, SettingEntry, UpdateSettingRequest types
  • web/src/utils/constants.ts -- Added NAMESPACE_ORDER, NAMESPACE_DISPLAY_NAMES, SETTINGS_ADVANCED_KEY
  • web/src/views/SettingsPage.vue -- Refactored: dynamic namespace tabs, basic/advanced toggle, SettingGroupRenderer integration
  • CLAUDE.md -- Updated Package Structure (settings/ components + settings store)

Test plan

  • 661 Vitest tests passing (83 new)
  • vue-tsc type-check clean
  • ESLint clean (0 errors)
  • Pre-reviewed by 5 agents (frontend, API contract drift, docs consistency, issue resolution, test quality) -- 10 findings addressed
  • Manual: settings page loads with dynamic tabs for all namespaces
  • Manual: basic/advanced toggle filters settings correctly
  • Manual: save persists value, reset reverts to default
  • Manual: sensitive fields masked with reveal toggle

Closes #454

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Full Settings page: dynamic namespace tabs with friendly labels, grouped settings renderer, Providers/User tabs, loading skeleton, and a Basic/Advanced toggle persisted across sessions.
    • Per-setting editors: type-aware inputs (text/password, number, boolean, enum, JSON), Format JSON, restart/source badges, dirty tracking, Save/Reset with validation and resilient update/reset flows.
  • Tests
    • Extensive unit, property, and integration tests covering settings API, store, components, badges, and page behavior.

Aureliolo and others added 2 commits March 19, 2026 16:41
…anced toggle

Add a schema-driven settings UI that auto-discovers all 50 settings
across 9 namespaces from the backend registry. No frontend changes
needed when new settings are added to the backend.

New files:
- API endpoint module (settings.ts) wrapping GET/PUT/DELETE endpoints
- Pinia store (useSettingsStore) with generation-counter stale-response
  protection and localStorage-persisted basic/advanced toggle
- SettingField component with type-appropriate inputs (text, number,
  toggle, select, textarea, password), client-side validation from
  schema constraints, dirty tracking, and per-setting save/reset
- SettingGroupRenderer component grouping settings by group field with
  basic/advanced level filtering
- SettingSourceBadge and SettingRestartBadge display components
- 8 test files (64 new tests) including property-based tests

Refactored SettingsPage replaces static Company tab with dynamic
namespace tabs, adds basic/advanced toggle in header, preserves
custom Providers card UI with dynamic settings below, and keeps
User tab unchanged.

Closes #454

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 5 agents, 10 findings addressed:

- CLAUDE.md: add settings/ to components and stores package structure
- resetSetting: add unwrapVoid to check error envelope on DELETE
- validateSettingValue: use fullmatch semantics (^(?:pattern)$) for
  regex validation to match backend re.fullmatch()
- validateSettingValue: accept "1", "0", and case variants for bool
  to match backend _check_default_bool()
- SettingField: add aria-label on password visibility toggle button
- Tests: fix silent-pass eye-toggle test (assert button exists)
- Tests: assert disabled is absent in save-enabled test
- Tests: add 19 unit tests for validateSettingValue (float, bool,
  enum, JSON, pattern branches)
- Tests: add resetSetting error propagation test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Introduces a dynamic settings feature: new API endpoint wrappers and TypeScript types for settings; a Pinia settings store with validation, fetch/update/reset actions, optimistic updates, and persisted basic/advanced toggle; Vue components for editing and grouping settings (SettingField, SettingGroupRenderer, SettingSourceBadge, SettingRestartBadge); updates to SettingsPage to render namespace-driven tabs and integrate the store; new constants for namespace ordering and labels; and many Vitest tests and PrimeVue test mocks covering endpoints, store logic, components, and page behavior.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: dynamic settings UI with auto-discovery and basic/advanced toggle' directly summarizes the main change: implementing a dynamic, auto-discovering settings interface with a basic/advanced toggle feature.
Linked Issues check ✅ Passed The pull request comprehensively implements all objectives from issue #454: schema-driven dynamic UI, type-appropriate inputs (text, number, toggle, dropdown, textarea, password), client-side validation, basic/advanced toggle with localStorage persistence, auto-discovery, and 83 tests covering the implementation.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #454 objectives. The PR adds settings API endpoints, store logic, UI components, comprehensive tests, and updates supporting infrastructure (types, constants, SettingsPage). No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's settings management by introducing a fully dynamic and extensible UI. The new system automatically adapts to backend setting definitions, providing tailored input fields and client-side validation without requiring manual frontend updates. This change streamlines the process of adding or modifying settings, improves user experience with intuitive controls, and ensures data integrity through robust validation.

Highlights

  • Dynamic Settings UI: Implemented a schema-driven dynamic settings UI that automatically discovers all settings from the backend, eliminating the need for frontend changes when new settings are introduced.
  • Type-Appropriate Inputs & Validation: Added support for six different setting types (text, number, toggle, dropdown, textarea for JSON, and password for sensitive fields) with corresponding client-side validation based on schema constraints like type, range, pattern, and enum values.
  • Basic/Advanced Toggle: Introduced a 'Basic/Advanced' toggle, persisted in local storage, allowing users to filter settings by their visibility level.
  • Integrated Provider Settings: Maintained the existing custom Providers tab while integrating dynamic settings for provider-specific configurations like routing and retry logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust and dynamic settings UI with auto-discovery, type-appropriate inputs, and basic/advanced toggling. The implementation is well-structured, leveraging Pinia for state management and comprehensive client-side validation. The addition of property-based tests using fast-check significantly enhances the reliability of the settings store. Overall, the changes are well-engineered, thoroughly tested, and greatly improve the configurability of the application.

Copy link
Copy Markdown

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/components/settings/SettingSourceBadge.test.ts`:
- Around line 13-53: Replace the repetitive individual it tests in the
SettingSourceBadge suite with a parameterized it.each table that iterates over
rows like [{ source: 'db', label: 'Database', severity: 'info' }, { source:
'env', label: 'Environment', severity: 'warn' }, { source: 'yaml', label:
'YAML', severity: 'secondary' }, { source: 'default', label: 'Default',
severity: 'contrast' }], and for each row mount SettingSourceBadge with
mount(SettingSourceBadge, { props: { source } }) then assert wrapper.text()
contains label and wrapper.find(`[data-severity="${severity}"]`).exists() is
true; keep the test suite name and component reference (SettingSourceBadge)
intact.

In `@web/src/stores/settings.ts`:
- Around line 143-146: The toggleAdvanced function currently writes to
localStorage without a guard; wrap the call to
localStorage.setItem(SETTINGS_ADVANCED_KEY, String(showAdvanced.value)) in a
try/catch so errors in restricted environments are swallowed or logged, keeping
the state flip (showAdvanced.value = !showAdvanced.value) intact; reference the
toggleAdvanced function, showAdvanced ref, and SETTINGS_ADVANCED_KEY when making
this change.
- Line 69: Guard the direct localStorage access used to initialize showAdvanced
(which currently calls localStorage.getItem(SETTINGS_ADVANCED_KEY) === 'true')
by wrapping the getItem call in a try/catch and defaulting to false on error;
update the initialization of showAdvanced to compute the boolean inside the
try/catch and use that value for ref(...). Also ensure any code that later
writes the same SETTINGS_ADVANCED_KEY (e.g., setters or watchers that update
localStorage when showAdvanced changes) uses try/catch to avoid throwing in
restricted contexts.
- Around line 50-58: The current client-side validation builds a RegExp from
validator_pattern and runs .test(value), which can be expensive for long inputs;
add an input-length guard before constructing/using the regex: define a sensible
MAX_VALIDATION_INPUT_LENGTH constant and, inside the block where
validator_pattern and value are validated (the code referencing
validator_pattern and value), if value.length exceeds the limit either skip the
regex test or return a concise validation error (e.g., "Input too long to
validate"), then only construct new RegExp(`^(?:${validator_pattern})$`) and
call .test(value) when value.length is within the limit; keep the existing
try/catch around RegExp construction to handle invalid patterns.

In `@web/src/views/SettingsPage.vue`:
- Line 207: The conditional update handler on TabView currently uses
"auth.mustChangePassword ? undefined : (activeTab = $event)" which returns
undefined but does not stop PrimeVue from visually switching tabs; remove this
ternary and restore a simple setter for the `@update`:value event (i.e., set
activeTab = $event) and rely on each TabPanel's :disabled binding
(auth.mustChangePassword) to prevent navigation; update the TabView's
`@update`:value handler to just assign activeTab and ensure TabPanel :disabled
still references auth.mustChangePassword.
- Around line 196-200: The ToggleSwitch handler currently calls
settingsStore.toggleAdvanced() and ignores the emitted boolean; update the event
binding so the emitted value from `@update`:model-value is passed into the store
(e.g., call settingsStore.toggleAdvanced(value) or create/use a setter like
settingsStore.setAdvanced(value)) so the store receives the explicit new boolean
from ToggleSwitch instead of always flipping state; update the usage of
ToggleSwitch, the settingsStore method signature (toggleAdvanced) or add
setAdvanced to accept the boolean, and ensure the template binding forwards the
emitted value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: c629eda1-cb46-45ec-a40f-941703b52f69

📥 Commits

Reviewing files that changed from the base of the PR and between c8bf862 and 618de54.

📒 Files selected for processing (19)
  • CLAUDE.md
  • web/src/__tests__/api/settings.test.ts
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/__tests__/components/settings/SettingRestartBadge.test.ts
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/__tests__/stores/settings.property.test.ts
  • web/src/__tests__/stores/settings.test.ts
  • web/src/__tests__/views/SettingsPage.dynamic.test.ts
  • web/src/__tests__/views/SettingsPage.test.ts
  • web/src/api/endpoints/settings.ts
  • web/src/api/types.ts
  • web/src/components/settings/SettingField.vue
  • web/src/components/settings/SettingGroupRenderer.vue
  • web/src/components/settings/SettingRestartBadge.vue
  • web/src/components/settings/SettingSourceBadge.vue
  • web/src/stores/settings.ts
  • web/src/utils/constants.ts
  • web/src/views/SettingsPage.vue

Comment on lines +196 to +200
<ToggleSwitch
:model-value="settingsStore.showAdvanced"
@update:model-value="settingsStore.toggleAdvanced()"
/>
<span>Advanced</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

ToggleSwitch event handler ignores emitted value.

@update:model-value provides the new boolean value, but toggleAdvanced() ignores it and simply flips the current state. This works correctly since it's a toggle, but directly passing the value would be more explicit.

♻️ Optional: Use emitted value directly

You could update the store's toggleAdvanced to accept an optional value, or use a setter:

-          `@update`:model-value="settingsStore.toggleAdvanced()"
+          `@update`:model-value="(val) => { settingsStore.showAdvanced = val; localStorage.setItem(SETTINGS_ADVANCED_KEY, String(val)) }"

Or keep the toggle pattern and acknowledge this is intentional design.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/views/SettingsPage.vue` around lines 196 - 200, The ToggleSwitch
handler currently calls settingsStore.toggleAdvanced() and ignores the emitted
boolean; update the event binding so the emitted value from `@update`:model-value
is passed into the store (e.g., call settingsStore.toggleAdvanced(value) or
create/use a setter like settingsStore.setAdvanced(value)) so the store receives
the explicit new boolean from ToggleSwitch instead of always flipping state;
update the usage of ToggleSwitch, the settingsStore method signature
(toggleAdvanced) or add setAdvanced to accept the boolean, and ensure the
template binding forwards the emitted value.

<ErrorBoundary :error="companyStore.configError ?? providerStore.error" @retry="retryFetch">
<ErrorBoundary :error="companyStore.configError ?? providerStore.error ?? settingsStore.error" @retry="retryFetch">
<LoadingSkeleton v-if="loading" :lines="6" />
<TabView v-else :value="activeTab" @update:value="auth.mustChangePassword ? undefined : (activeTab = $event)">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Clarify tab change prevention logic.

The expression auth.mustChangePassword ? undefined : (activeTab = $event) returns undefined when password change is required, but this doesn't prevent PrimeVue's TabView from visually switching tabs. The :disabled prop on TabPanels (Line 214) is the actual mechanism preventing navigation.

Consider simplifying since the disabled state already handles restriction:

♻️ Simplified handler
-    <TabView v-else :value="activeTab" `@update`:value="auth.mustChangePassword ? undefined : (activeTab = $event)">
+    <TabView v-else :value="activeTab" `@update`:value="activeTab = $event">

The :disabled prop on each TabPanel already prevents interaction when auth.mustChangePassword is true.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TabView v-else :value="activeTab" @update:value="auth.mustChangePassword ? undefined : (activeTab = $event)">
<TabView v-else :value="activeTab" `@update`:value="activeTab = $event">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/views/SettingsPage.vue` at line 207, The conditional update handler
on TabView currently uses "auth.mustChangePassword ? undefined : (activeTab =
$event)" which returns undefined but does not stop PrimeVue from visually
switching tabs; remove this ternary and restore a simple setter for the
`@update`:value event (i.e., set activeTab = $event) and rely on each TabPanel's
:disabled binding (auth.mustChangePassword) to prevent navigation; update the
TabView's `@update`:value handler to just assign activeTab and ensure TabPanel
:disabled still references auth.mustChangePassword.

Critical:
- Migrate TabView (deprecated) to Tabs component (PrimeVue 4 API)
  -- fixes broken tab state sync, URL params, and tab lock logic
- Convert shared mutable test fixtures to factory functions

Major:
- Narrow namespace params from string to SettingNamespace in store + endpoints
- Log post-update/reset refresh failures instead of silent swallow
- Surface retryFetch errors in ErrorBoundary instead of console-only
- Add aria-labels to ToggleSwitch components (accessibility)
- Make enum_values readonly string[] to match backend immutability
- Document and validate backend max_length=8192 constraint

Medium:
- Add ReDoS input length guard for regex validation
- Guard localStorage access with try/catch for restricted contexts
- Log invalid validator_pattern regex with console.warn
- Refactor SettingSourceBadge tests to use it.each
- Extract shared PrimeVue test mocks to __mocks__/primevue.ts

Tests:
- Add partial-success tests (update/reset ok, refresh fails)
- Add formatJson button behavior tests
- Add Reset disabled state tests for non-db sources
- Add watch re-sync test when entry prop changes
- Add handleSave guard test when validation fails
- Add max-length validation tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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)
web/src/__tests__/views/SettingsPage.test.ts (1)

23-98: 🧹 Nitpick | 🔵 Trivial

Consolidate duplicated PrimeVue mocks using the shared mock registrar.

This file redefines mocks already centralized in web/src/__tests__/__mocks__/primevue.ts. The divergence (notably in interactive behavior) can make tests inconsistent across suites.

♻️ Suggested refactor
+import { registerPrimeVueMocks } from '@/__tests__/__mocks__/primevue'
+
+registerPrimeVueMocks(vi)
+
 vi.mock('primevue/tabs', () => ({
   default: { template: '<div><slot /></div>' },
 }))
@@
-vi.mock('primevue/inputtext', () => ({
-  default: {
-    props: ['modelValue', 'type', 'placeholder'],
-    template: '<input :type="type" :placeholder="placeholder" />',
-  },
-}))
-
-vi.mock('primevue/button', () => ({
-  default: {
-    props: ['label', 'icon', 'type', 'size', 'loading', 'disabled', 'severity', 'text'],
-    template: '<button :disabled="disabled">{{ label }}</button>',
-  },
-}))
-
-vi.mock('primevue/toggleswitch', () => ({
-  default: {
-    props: ['modelValue', 'disabled'],
-    emits: ['update:modelValue'],
-    template: '<button role="switch">{{ modelValue }}</button>',
-  },
-}))
-
-vi.mock('primevue/inputnumber', () => ({
-  default: {
-    props: ['modelValue', 'min', 'max', 'minFractionDigits', 'maxFractionDigits', 'useGrouping', 'disabled'],
-    template: '<input type="number" />',
-  },
-}))
-
-vi.mock('primevue/select', () => ({
-  default: {
-    props: ['modelValue', 'options', 'disabled'],
-    template: '<select></select>',
-  },
-}))
-
-vi.mock('primevue/textarea', () => ({
-  default: {
-    props: ['modelValue', 'rows', 'disabled'],
-    template: '<textarea></textarea>',
-  },
-}))
-
-vi.mock('primevue/tag', () => ({
-  default: {
-    props: ['value', 'severity'],
-    template: '<span>{{ value }}</span>',
-  },
-}))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/views/SettingsPage.test.ts` around lines 23 - 98, Tests
redefine many PrimeVue component mocks (e.g., the vi.mock calls for
'primevue/tabs', 'primevue/inputtext', 'primevue/button',
'primevue/toggleswitch', etc.) that are already centralized in
web/src/__tests__/__mocks__/primevue.ts; remove these duplicated vi.mock(...)
blocks from SettingsPage.test.ts and instead load the shared registrar by
importing or requiring the centralized mock module (e.g., add a top-level import
of web/src/__tests__/__mocks__/primevue or replace the individual vi.mock calls
with a single vi.mock('primevue', () =>
require('web/src/__tests__/__mocks__/primevue'))), so all tests use the same
mocked behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/stores/settings.ts`:
- Around line 52-58: The current JSON validation uses JSON.parse(value) which
accepts primitives (strings, numbers, booleans, null) as valid JSON; if you need
to restrict settings to only objects/arrays, parse the value then verify the
parsed result is a non-null object (Array.isArray(...) or typeof parsed ===
'object' && parsed !== null) and return the error 'Must be valid JSON' when the
parsed value is a primitive; update the validation branch that checks type ===
'json' (using the variables type and value in settings.ts) to perform this extra
check after parsing.

---

Outside diff comments:
In `@web/src/__tests__/views/SettingsPage.test.ts`:
- Around line 23-98: Tests redefine many PrimeVue component mocks (e.g., the
vi.mock calls for 'primevue/tabs', 'primevue/inputtext', 'primevue/button',
'primevue/toggleswitch', etc.) that are already centralized in
web/src/__tests__/__mocks__/primevue.ts; remove these duplicated vi.mock(...)
blocks from SettingsPage.test.ts and instead load the shared registrar by
importing or requiring the centralized mock module (e.g., add a top-level import
of web/src/__tests__/__mocks__/primevue or replace the individual vi.mock calls
with a single vi.mock('primevue', () =>
require('web/src/__tests__/__mocks__/primevue'))), so all tests use the same
mocked behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8b8dd9ba-d361-4c65-a0a4-6fa7f6e3c02c

📥 Commits

Reviewing files that changed from the base of the PR and between 618de54 and 4557866.

📒 Files selected for processing (13)
  • web/src/__tests__/__mocks__/primevue.ts
  • web/src/__tests__/api/settings.test.ts
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/__tests__/stores/settings.test.ts
  • web/src/__tests__/views/SettingsPage.dynamic.test.ts
  • web/src/__tests__/views/SettingsPage.test.ts
  • web/src/api/endpoints/settings.ts
  • web/src/api/types.ts
  • web/src/components/settings/SettingField.vue
  • web/src/stores/settings.ts
  • web/src/views/SettingsPage.vue

Primitives (strings, numbers, booleans, null) are valid JSON but have
their own dedicated setting types (str, int, float, bool). JSON settings
are for structural data only.

Also removes non-functional registerPrimeVueMocks helper -- vi.mock()
hoisting prevents using imported objects in factory scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/stores/settings.test.ts`:
- Around line 158-172: Add an advanced-only namespace fixture to the test by
creating another mock definition (e.g., mockAdvancedOnlyDefinition) whose
definitions all have level: 'advanced', include it in the mocked
settingsApi.getSchema response alongside mockSecurityDefinition, mockDefinition,
and mockAdvancedDefinition, then call useSettingsStore() and await
store.fetchAll(); assert that store.namespaces does not include the
advanced-only namespace while store.showAdvanced is false (optionally also
assert it appears when you set store.showAdvanced = true and refresh if needed).
Ensure you reference the existing test helpers and methods (mock...Definition
symbols, settingsApi.getSchema, useSettingsStore, store.fetchAll,
store.namespaces, store.showAdvanced) so the new fixture integrates with the
current test setup.

In `@web/src/stores/settings.ts`:
- Around line 147-160: After deleting the override in resetSetting (where
settingsApi.resetSetting is called) you currently swallow failures from the
subsequent settingsApi.getAllSettings call which leaves entries showing stale
resolved values; change the flow so that if the refresh fails you do not report
success—either rethrow/propagate the refresh error (so callers see failure) or
proactively update entries to remove/invalidate the specific namespace/key entry
(using entries and generation) before returning; ensure you still clear
savingKey and maintain generation consistency so the UI cannot display an
authoritative stale entry after reset.
- Around line 95-98: The computed namespaces currently ignores the showAdvanced
filter so namespaces with only advanced settings still appear; update the
computed named namespaces to build the present set from schema.value filtered by
the showAdvanced state (e.g., include a definition only if showAdvanced.value is
true or def.level !== 'advanced'), then map to namespaces and apply
NAMESPACE_ORDER.filter as before; reference the computed symbol namespaces, the
reactive schema, and the showAdvanced ref when making this change.
- Around line 128-141: In updateSetting, use the SettingEntry returned by
settingsApi.updateSetting(namespace, key, { value }) to immediately update
entries (and generation if needed) before performing the best-effort refresh;
specifically, after the await of settingsApi.updateSetting(...) apply the
returned entry to the entries reactive list (and clear savingKey) so the store
reflects the saved value even if the subsequent settingsApi.getAllSettings()
refresh fails, and keep the existing generation check/gen increment logic to
avoid races.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb258bce-4ff5-4c43-895b-8b4cc8b9ffc3

📥 Commits

Reviewing files that changed from the base of the PR and between 4557866 and 5dd1c6c.

📒 Files selected for processing (3)
  • web/src/__tests__/__mocks__/primevue.ts
  • web/src/__tests__/stores/settings.test.ts
  • web/src/stores/settings.ts

- Filter namespaces by showAdvanced -- hide tabs for namespaces with
  only advanced settings in basic mode
- Use returned SettingEntry from updateSetting API to immediately update
  entries, avoiding stale UI when refresh fails
- Optimistically revert entry to default on reset so the UI reflects
  the deleted DB override even when refresh fails
- Add test for advanced-only namespace filtering
- Update partial-success tests to verify optimistic entry updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
web/src/stores/settings.ts (2)

19-20: 🧹 Nitpick | 🔵 Trivial

The regex length guard is currently unreachable.

Line 19 rejects values above 8192 chars, so the Line 64 guard at 10,000 never executes. Either lower MAX_VALIDATION_INPUT_LENGTH to <= MAX_SETTING_VALUE_LENGTH or remove the dead branch.

Also applies to: 64-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/settings.ts` around lines 19 - 20, The regex length guard is
unreachable because MAX_VALIDATION_INPUT_LENGTH exceeds
MAX_SETTING_VALUE_LENGTH; update the validation so the shorter limit is enforced
first: either set MAX_VALIDATION_INPUT_LENGTH to a value <=
MAX_SETTING_VALUE_LENGTH or remove the redundant branch that checks
MAX_VALIDATION_INPUT_LENGTH in the same validation flow (look for the constants
MAX_SETTING_VALUE_LENGTH and MAX_VALIDATION_INPUT_LENGTH and the validation
logic that returns `Value must be at most ${MAX_SETTING_VALUE_LENGTH}
characters` and the later regex-length guard) so only one consistent maximum is
applied.

134-139: ⚠️ Potential issue | 🟡 Minor

Optimistic update should upsert when the entry is missing.

At Line 137, the map-only replacement is a no-op if the target key is absent in entries. In that case, a successful PUT can still leave stale UI when the follow-up refresh fails.

Proposed fix
     try {
       const updatedEntry = await settingsApi.updateSetting(namespace, key, { value })
       // Immediately apply the returned entry so the UI reflects the change
       const gen = ++generation
-      entries.value = entries.value.map((e) =>
-        e.definition.namespace === namespace && e.definition.key === key ? updatedEntry : e,
-      )
+      const idx = entries.value.findIndex(
+        (e) => e.definition.namespace === namespace && e.definition.key === key,
+      )
+      if (idx >= 0) {
+        entries.value[idx] = updatedEntry
+      } else {
+        entries.value.push(updatedEntry)
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/settings.ts` around lines 134 - 139, The optimistic update
currently replaces entries via entries.value.map(...) after calling
settingsApi.updateSetting(namespace, key, { value }) but does nothing if the
entry is missing; change the logic that applies updatedEntry (and uses the
incremented generation) to perform an upsert: search entries.value for an item
matching e.definition.namespace === namespace && e.definition.key === key and if
found replace it with updatedEntry, otherwise append/insert updatedEntry into
entries.value so the UI reflects the successful PUT even when the entry wasn't
previously present; keep the existing generation increment and use the same
updatedEntry result from settingsApi.updateSetting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/stores/settings.test.ts`:
- Around line 298-303: Replace hardcoded localStorage key strings with the
shared constant SETTINGS_ADVANCED_KEY in the tests: update the assertions that
call localStorage.getItem('settings_show_advanced') to use
localStorage.getItem(SETTINGS_ADVANCED_KEY) so they stay in sync with the
implementation; do this for the assertions surrounding store.toggleAdvanced()
and store.showAdvanced in the failing test blocks (also apply the same change to
the similar assertions at lines noted in the comment).
- Around line 243-247: The test currently only mocks settingsApi.getAllSettings
once, but store.updateSetting(...) triggers a post-update refresh so the second
getAllSettings call is undefined; update the test to mock getAllSettings for
both calls (e.g., use
vi.mocked(settingsApi.getAllSettings).mockResolvedValueOnce(initialData) and
.mockResolvedValueOnce(refreshedData) or mockResolvedValue sequence) and ensure
the mocked settingsApi.updateSetting promise (resolveUpdate) is used to simulate
completion before asserting; apply the same dual-getAllSettings mocking fix to
the other test block that covers lines 251-256.

---

Duplicate comments:
In `@web/src/stores/settings.ts`:
- Around line 19-20: The regex length guard is unreachable because
MAX_VALIDATION_INPUT_LENGTH exceeds MAX_SETTING_VALUE_LENGTH; update the
validation so the shorter limit is enforced first: either set
MAX_VALIDATION_INPUT_LENGTH to a value <= MAX_SETTING_VALUE_LENGTH or remove the
redundant branch that checks MAX_VALIDATION_INPUT_LENGTH in the same validation
flow (look for the constants MAX_SETTING_VALUE_LENGTH and
MAX_VALIDATION_INPUT_LENGTH and the validation logic that returns `Value must be
at most ${MAX_SETTING_VALUE_LENGTH} characters` and the later regex-length
guard) so only one consistent maximum is applied.
- Around line 134-139: The optimistic update currently replaces entries via
entries.value.map(...) after calling settingsApi.updateSetting(namespace, key, {
value }) but does nothing if the entry is missing; change the logic that applies
updatedEntry (and uses the incremented generation) to perform an upsert: search
entries.value for an item matching e.definition.namespace === namespace &&
e.definition.key === key and if found replace it with updatedEntry, otherwise
append/insert updatedEntry into entries.value so the UI reflects the successful
PUT even when the entry wasn't previously present; keep the existing generation
increment and use the same updatedEntry result from settingsApi.updateSetting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 156719f6-e79b-434f-8382-d5a2ca50b6b6

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd1c6c and f72c969.

📒 Files selected for processing (2)
  • web/src/__tests__/stores/settings.test.ts
  • web/src/stores/settings.ts

Comment on lines +243 to +247
vi.mocked(settingsApi.getSchema).mockResolvedValue([mockDefinition])
vi.mocked(settingsApi.getAllSettings).mockResolvedValue([mockEntry])
vi.mocked(settingsApi.updateSetting).mockReturnValue(
new Promise((r) => { resolveUpdate = r }),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Mock getAllSettings for the post-update refresh in this test.

store.updateSetting(...) performs a refresh call after updateSetting. In this case, only the initial getAllSettings is mocked, so the second call is implicit/undefined and weakens test reliability.

Proposed fix
     vi.mocked(settingsApi.getSchema).mockResolvedValue([mockDefinition])
-    vi.mocked(settingsApi.getAllSettings).mockResolvedValue([mockEntry])
+    vi.mocked(settingsApi.getAllSettings)
+      .mockResolvedValueOnce([mockEntry]) // initial fetchAll
+      .mockResolvedValueOnce([mockEntry]) // refresh inside updateSetting

Also applies to: 251-256

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/stores/settings.test.ts` around lines 243 - 247, The test
currently only mocks settingsApi.getAllSettings once, but
store.updateSetting(...) triggers a post-update refresh so the second
getAllSettings call is undefined; update the test to mock getAllSettings for
both calls (e.g., use
vi.mocked(settingsApi.getAllSettings).mockResolvedValueOnce(initialData) and
.mockResolvedValueOnce(refreshedData) or mockResolvedValue sequence) and ensure
the mocked settingsApi.updateSetting promise (resolveUpdate) is used to simulate
completion before asserting; apply the same dual-getAllSettings mocking fix to
the other test block that covers lines 251-256.

…n tests

- Remove unreachable MAX_VALIDATION_INPUT_LENGTH (10k) -- the
  MAX_SETTING_VALUE_LENGTH (8192) check already rejects longer values
  before the regex branch is reached
- Change updateSetting optimistic apply from map-only to upsert so
  newly created settings are appended if not already in the array
- Replace hardcoded 'settings_show_advanced' strings in tests with
  SETTINGS_ADVANCED_KEY constant import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/stores/settings.test.ts`:
- Around line 380-506: The test suite is missing coverage for integer-specific
validation in validateSettingValue; add new tests (using the existing helper
makeDef and pattern in this file) for type: 'int' that assert acceptance of
valid integers (e.g., "42"), rejection of decimals like "3.5", rejection of
empty string, and enforcement of min_value and max_value boundaries (e.g.,
min_value: 0 rejects "-1", max_value: 100 rejects "200"); place these alongside
the other type-specific tests so integer behavior and Number.isInteger()
semantics are verified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2080f614-5909-4df0-bce8-60c5b8e1153d

📥 Commits

Reviewing files that changed from the base of the PR and between f72c969 and 9f810e9.

📒 Files selected for processing (2)
  • web/src/__tests__/stores/settings.test.ts
  • web/src/stores/settings.ts

Aureliolo and others added 2 commits March 19, 2026 18:00
Covers valid integers, decimal rejection (Number.isInteger), empty
string, non-numeric input, min/max boundary enforcement, and boundary
value acceptance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

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

♻️ Duplicate comments (1)
web/src/stores/settings.ts (1)

155-175: ⚠️ Potential issue | 🟠 Major

Don't report a guessed default entry as the post-reset truth.

settingsApi.resetSetting() returns void, so this path does not know the resolved value/source until getAllSettings() succeeds. If that refresh fails, the store still resolves with definition.default, source: 'default', and the old updated_at preserved from { ...e }, even though the comment here already notes that YAML/env may override the default. That leaves the UI showing an authoritative-looking value that can be wrong.

♻️ One safe option: require a successful refresh before reporting success
   async function resetSetting(namespace: SettingNamespace, key: string): Promise<void> {
     savingKey.value = `${namespace}/${key}`
     try {
       await settingsApi.resetSetting(namespace, key)
-      // Optimistically revert entry to default since the DB override is deleted
       const gen = ++generation
-      entries.value = entries.value.map((e) => {
-        if (e.definition.namespace === namespace && e.definition.key === key) {
-          return { ...e, value: e.definition.default ?? '', source: 'default' as const }
-        }
-        return e
-      })
-      // Best-effort full refresh to get actual resolved values (env/yaml may override default)
-      try {
-        const entriesData = await settingsApi.getAllSettings()
-        if (gen === generation) {
-          entries.value = entriesData
-        }
-      } catch (refreshErr) {
-        console.warn('Settings refresh failed after reset:', refreshErr)
+      const entriesData = await settingsApi.getAllSettings()
+      if (gen === generation) {
+        entries.value = entriesData
       }
     } finally {
       savingKey.value = null
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/settings.ts` around lines 155 - 175, The resetSetting function
is prematurely mutating entries.value to a guessed default after calling
settingsApi.resetSetting; instead, stop optimistically replacing the entry with
{ ...e, value: e.definition.default, source: 'default' } and only update
entries.value from the authoritative settingsApi.getAllSettings() result; keep
savingKey and generation handling, call settingsApi.getAllSettings() after
reset, and if it succeeds (gen === generation) assign entries.value =
entriesData, otherwise do not modify the existing entry (or restore the prior
entry) and surface the refresh failure; reference resetSetting, generation,
entries.value, settingsApi.resetSetting and settingsApi.getAllSettings when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web/src/stores/settings.ts`:
- Around line 155-175: The resetSetting function is prematurely mutating
entries.value to a guessed default after calling settingsApi.resetSetting;
instead, stop optimistically replacing the entry with { ...e, value:
e.definition.default, source: 'default' } and only update entries.value from the
authoritative settingsApi.getAllSettings() result; keep savingKey and generation
handling, call settingsApi.getAllSettings() after reset, and if it succeeds (gen
=== generation) assign entries.value = entriesData, otherwise do not modify the
existing entry (or restore the prior entry) and surface the refresh failure;
reference resetSetting, generation, entries.value, settingsApi.resetSetting and
settingsApi.getAllSettings when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: defeaa4c-1379-413d-9f37-79f533e2b5f0

📥 Commits

Reviewing files that changed from the base of the PR and between f72c969 and 0eae7aa.

📒 Files selected for processing (2)
  • web/src/__tests__/stores/settings.test.ts
  • web/src/stores/settings.ts

@Aureliolo Aureliolo merged commit 11b32b7 into main Mar 19, 2026
28 checks passed
@Aureliolo Aureliolo deleted the feat/dynamic-settings-ui branch March 19, 2026 17:27
Aureliolo added a commit that referenced this pull request Mar 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.8](v0.3.7...v0.3.8)
(2026-03-19)


### Features

* dynamic settings UI with auto-discovery and basic/advanced toggle
([#600](#600))
([11b32b7](11b32b7))


### Bug Fixes

* **ci:** remove dst: . from GoReleaser archive config
([#598](#598))
([c8bf862](c8bf862))
* **engine:** wire compaction_callback and plan_execute_config through
auto-selection
([#601](#601))
([795327f](795327f))
* harden setup wizard completion and status checks
([#616](#616))
([d99d7b7](d99d7b7))


### Maintenance

* add /codebase-audit skill for deep parallel codebase auditing
([#613](#613))
([db02320](db02320))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

feat: implement dynamic settings UI with auto-discovery and basic/advanced toggle

1 participant