Skip to content

feat(settings): support plain field-id keys for dependency lookups#90

Merged
mrabbani merged 1 commit into
mainfrom
feat/settings-id-keyed-dependencies
May 18, 2026
Merged

feat(settings): support plain field-id keys for dependency lookups#90
mrabbani merged 1 commit into
mainfrom
feat/settings-id-keyed-dependencies

Conversation

@mrabbani
Copy link
Copy Markdown
Member

@mrabbani mrabbani commented May 14, 2026

Summary

  • Adds an additive id-keyed fallback to evaluateDependencies() so dependencies declared with a plain field id ('commission_type') resolve correctly alongside the existing dot-path format ('commission.commission.commission_type').
  • Exports a new buildIdIndex(schema) helper from settings-formatter that maps each field's id to its reconstructed dependency_key.
  • SettingsProvider builds the index (memoised on schema) and threads it through shouldDisplay → evaluateDependencies.

Why

formatSettingsData() rebuilds every element's dependency_key as a parent.child.field dot-path and uses that as the values-map key. Consumers whose backend declares dependencies with plain field ids (the schema is the source of truth for which field they reference) silently fail to resolve because values[dep.key] returns undefined.

Dokan's settings refactor lands on a flat-storage model where every field id is globally unique; the natural way to reference fields is by id, not by structural path. Path-based keys also break on structural reorganization (moving a field changes its dot-path), while id-based keys remain stable.

Behavior

// Dot-path (existing — still works):
{ key: 'commission.commission.commission_type', value: 'category_based', effect: 'show' }

// Plain id (new — also works):
{ key: 'commission_type', value: 'category_based', effect: 'show' }

Resolution order in evaluateDependencies():

  1. values[dep.key] direct lookup (existing behavior).
  2. If undefined and an idIndex is supplied, values[idIndex[dep.key]].

Backwards Compatibility

  • evaluateDependencies()'s third idIndex parameter is optional. Callers that don't pass it see byte-identical behavior.
  • No changes to extractValues(), updateValue, or the values-map shape.
  • Existing dot-path dependencies in consumer schemas continue to resolve via the direct lookup path.

Test plan

  • Run the existing Settings stories — dot-path dependencies still drive visibility correctly.
  • Add a fixture with a plain-id dependency and verify it resolves (matches target field's value).
  • Verify a dependency with neither a matching dot-path nor a matching id falls through to currentValue === undefined, preserving the legacy fail-closed behavior.
  • No regressions in dirty tracking, save handlers, or initial-page selection.

Summary by CodeRabbit

  • Refactor
    • Enhanced the settings dependency evaluation system to more reliably resolve field visibility conditions, improving support for complex interdependent settings configurations.

Review Change Stack

Plugin-ui's formatSettingsData() rebuilds dependency_key as a dot-path
(parent.child.field) and the values map is keyed by that dot-path.
Dependencies declared with a plain field id (e.g., 'commission_type'
instead of 'commission.commission.commission_type') therefore failed to
resolve via values[dep.key].

Add an id-keyed fallback so both formats work side by side:

- Export buildIdIndex(schema) from settings-formatter — produces a
  { field_id: dependency_key } map from the hierarchical schema.
- evaluateDependencies() accepts an optional idIndex argument. When
  values[dep.key] is undefined and an idIndex is supplied, the function
  resolves dep.key as a field id and re-reads values via the index.
- SettingsProvider builds the idIndex (memoised on schema) and threads
  it through shouldDisplay → evaluateDependencies.

Backwards compatible: callers that don't pass idIndex see identical
behavior. Consumers whose backend guarantees globally-unique field ids
(e.g., flat-storage schemas) can now use id-keyed dependencies, which
stay valid across structural moves (no parent path to update).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR enhances dependency evaluation in the settings system by introducing a schema-derived index for resolving field dependencies. A new buildIdIndex function maps field IDs to their dependency keys, and evaluateDependencies is extended with a fallback lookup mechanism that uses this index when direct value resolution fails.

Changes

Schema-driven dependency resolution

Layer / File(s) Summary
Index builder contract and dependency evaluation signature
src/components/settings/settings-formatter.ts
New buildIdIndex function traverses the settings schema hierarchy and builds a first-writer mapping from field id to dependency_key. The evaluateDependencies export signature is extended with an optional idIndex parameter and documentation for the fallback resolution behavior.
Dependency evaluation fallback implementation
src/components/settings/settings-formatter.ts
Dependency evaluation computes currentValue from values[dep.key], then falls back to resolving dep.key through the index when the result is undefined and idIndex is provided, before re-reading from values with the resolved key.
Settings provider memoization and wiring
src/components/settings/settings-context.tsx
SettingsProvider imports buildIdIndex, memoizes the schema-derived index, and passes it to evaluateDependencies in the shouldDisplay computation, updating hook dependencies to include the memoized index.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through settings fair,
Where fields dance in dependent air,
An index maps where paths once lost,
Now fallback trails resolve the cost,
🐰 dependency chains, no longer crossed!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.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 clearly and concisely describes the main change: adding support for plain field-id keys in dependency lookups, which is the core objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/settings-id-keyed-dependencies

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/settings/settings-formatter.ts`:
- Around line 312-327: The idIndex map should not be a plain {} because
prototype keys can collide; change its creation to a prototypeless map (use
Object.create(null)) and replace any "el.id in idIndex" checks and any direct
property reads with own-property checks (e.g.,
Object.prototype.hasOwnProperty.call(idIndex, el.id) or Object.hasOwn(idIndex,
el.id)) before treating or reading idIndex[el.id]; update the checks inside the
walk function and wherever idIndex is read (referencing idIndex and the walk /
SettingsElement logic) to use these own-property checks so prototype properties
like "toString" or "constructor" won't be treated as existing IDs.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25a5fb2c-0c0c-4f09-a903-e7bd8134220a

📥 Commits

Reviewing files that changed from the base of the PR and between e0b02a0 and ed8ae03.

📒 Files selected for processing (2)
  • src/components/settings/settings-context.tsx
  • src/components/settings/settings-formatter.ts

Comment on lines +312 to +327
const idIndex: Record<string, string> = {};

const walk = (elements: SettingsElement[]) => {
for (const el of elements) {
if (
el.type === 'field' &&
el.id &&
el.dependency_key &&
el.id !== el.dependency_key
) {
// First writer wins — if two fields share an id (a schema
// bug consumers should detect with their own validator),
// we don't silently clobber the earlier mapping.
if (!(el.id in idIndex)) {
idIndex[el.id] = el.dependency_key;
}
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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify prototype-sensitive dictionary usage in the touched file.
rg -n -C2 "Record<string, string> = \{\}|\\bin idIndex\\b|idIndex\\[dep\\.key\\]" src/components/settings/settings-formatter.ts

Repository: getdokan/plugin-ui

Length of output: 1073


Use own-property checks for idIndex to avoid prototype-key collisions.

Using a plain object {} for the idIndex dictionary with the in operator (line 325) and direct property access (line 369) is unsafe. If an ID matches a prototype property like constructor, toString, or __proto__, the in check will incorrectly return true, skipping the mapping, and direct property reads will return inherited values instead of undefined.

Use Object.create(null) and Object.prototype.hasOwnProperty.call() for safe own-property checks.

Proposed fix
-export function buildIdIndex(
-    schema: SettingsElement[]
-): Record<string, string> {
-    const idIndex: Record<string, string> = {};
+export function buildIdIndex(
+    schema: SettingsElement[]
+): Record<string, string> {
+    const idIndex = Object.create(null) as Record<string, string>;
@@
-                if (!(el.id in idIndex)) {
+                if (!Object.prototype.hasOwnProperty.call(idIndex, el.id)) {
                     idIndex[el.id] = el.dependency_key;
                 }
@@
-        if (currentValue === undefined && idIndex) {
-            const resolved = idIndex[dep.key];
-            if (resolved !== undefined) {
+        if (
+            currentValue === undefined &&
+            idIndex &&
+            Object.prototype.hasOwnProperty.call(idIndex, dep.key)
+        ) {
+            const resolved = idIndex[dep.key];
+            if (resolved !== undefined) {
                 currentValue = values[resolved];
             }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/settings/settings-formatter.ts` around lines 312 - 327, The
idIndex map should not be a plain {} because prototype keys can collide; change
its creation to a prototypeless map (use Object.create(null)) and replace any
"el.id in idIndex" checks and any direct property reads with own-property checks
(e.g., Object.prototype.hasOwnProperty.call(idIndex, el.id) or
Object.hasOwn(idIndex, el.id)) before treating or reading idIndex[el.id]; update
the checks inside the walk function and wherever idIndex is read (referencing
idIndex and the walk / SettingsElement logic) to use these own-property checks
so prototype properties like "toString" or "constructor" won't be treated as
existing IDs.

@mrabbani mrabbani merged commit 827b418 into main May 18, 2026
1 check passed
@mrabbani mrabbani deleted the feat/settings-id-keyed-dependencies branch May 18, 2026 04:43
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