Skip to content

fix(form): namespace schema $id by content to avoid stale validator#4067

Merged
jgroth merged 2 commits intomainfrom
fix/form-stale-validator-on-id-collision
May 8, 2026
Merged

fix(form): namespace schema $id by content to avoid stale validator#4067
jgroth merged 2 commits intomainfrom
fix/form-stale-validator-on-id-collision

Conversation

@FredrikWallstrom
Copy link
Copy Markdown
Contributor

@FredrikWallstrom FredrikWallstrom commented May 8, 2026

Summary

When a consumer renders limel-form with a schema that reuses the same $id while its content changes between renders (e.g. a server schema whose properties get filtered based on user input), the form's validate event would fire with errors that don't match the schema's actual content — most visibly, fields would be reported as "additional properties" even though they're declared in properties.

The root cause is that the validator caches compiled validators by $id. The first variant compiled under a given $id is reused for every later variant with the same $id, so swapping the schema's content silently has no effect on validation.

This fix wraps the schema in a copy whose $id becomes <original-$id>-<fingerprint>, where the fingerprint is a short hash of the stringified content (sans $id), before handing it to RJSF and the validator. Distinct content produces a distinct cache entry; identical content still hits the cache. The fingerprint — rather than the raw content — keeps $id short and URI-safe, since the validator parses it as a URI and chokes on JSON-shaped characters. The consumer's original schema is left untouched.

Fixes Lundalogik/crm-client#971.

Test plan

  • New regression test in form.e2e.tsx covers the scenario (same $id, schema gains a required property, value updates to satisfy it).
  • Existing form e2e suite still passes (42/42).
  • Existing form spec suite still passes (56/56).
  • Manual verification: open the bulk-edit dialog, pick a text field, type — the Update button enables. Lime Admin loads schemas without URI-malformed errors and at expected speed.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where form validation would not properly update when schema requirements changed dynamically while maintaining the same schema identifier. Forms now correctly re-validate against updated requirements.
  • Tests

    • Added end-to-end validation test covering dynamic schema requirement changes at runtime.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef3a96f3-5856-42ab-95a1-4d604e9959da

📥 Commits

Reviewing files that changed from the base of the PR and between c114783 and d8ec9c3.

📒 Files selected for processing (2)
  • src/components/form/form.e2e.tsx
  • src/components/form/form.tsx

📝 Walkthrough

Walkthrough

The form component now preprocesses schemas before validation and rendering by replacing the $id with a content-derived value. This prevents validator caching collisions when multiple schemas with identical $id but different content are used at runtime. A new test confirms validation behaves correctly when schemas are swapped.

Changes

Schema Identity Resolution

Layer / File(s) Summary
Schema Preparation Strategy
src/components/form/form.tsx
New prepareSchema helper derives the schema's $id from both the original $id and JSON-stringified schema content to create a content-based identity.
Validation & Rendering Integration
src/components/form/form.tsx
reactRender and validateFormData methods now call prepareSchema(this.schema) before passing the schema to JSONSchemaForm renderer and rjsfValidator, ensuring consistent identity-based validation.
End-to-End Test
src/components/form/form.e2e.tsx
Test case verifies that swapping between schemas with the same original $id but different content correctly triggers and clears validation errors based on the new schema's requirements.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • Lundalogik/lime-elements#3992: Addresses the same schema identity/caching problem by deriving $id from schema content rather than relying on the provided $id value.

Possibly related PRs

  • Lundalogik/lime-elements#3999: Both PRs resolve schema identity collisions by deriving IDs from schema content to prevent validator caching conflicts.

Suggested labels

released

Suggested reviewers

  • Befkadu1
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(form): namespace schema $id by content to avoid stale validator' directly and clearly describes the main change: fixing form validation by namespacing the schema's $id based on content to prevent stale validator issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/form-stale-validator-on-id-collision

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-4067/

@FredrikWallstrom FredrikWallstrom force-pushed the fix/form-stale-validator-on-id-collision branch 2 times, most recently from 591da52 to 8948275 Compare May 8, 2026 11:39
Ajv8 caches compiled validators by `$id`. When a consumer reuses the
same `$id` (typical for server-supplied schemas) with different content
between renders — e.g. by filtering its `properties` based on user
input — Ajv returns the validator it compiled for the first variant,
which silently rejects the new variant's properties as additional or
otherwise reports stale errors.

Wrap the schema in a copy whose `$id` becomes `<original-$id>-<fingerprint>`,
where the fingerprint is a short hash of the stringified content (sans
`$id`). Distinct content gets a distinct cache entry; identical content
still hits the cache. The fingerprint — rather than the raw content —
keeps `$id` short and URI-safe, since the validator parses it as a URI
and chokes on JSON-shaped characters.
@FredrikWallstrom FredrikWallstrom force-pushed the fix/form-stale-validator-on-id-collision branch from 8948275 to 8652ac6 Compare May 8, 2026 11:43
Comment on lines +234 to +240
let fingerprint = 5381;
for (let i = 0; i < json.length; i++) {
fingerprint =
((fingerprint << 5) + fingerprint + json.codePointAt(i)) &
0xff_ff_ff_ff;
}
const contentId = (fingerprint >>> 0).toString(36);
Copy link
Copy Markdown
Contributor Author

@FredrikWallstrom FredrikWallstrom May 8, 2026

Choose a reason for hiding this comment

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

I tried to keep it simple and just use JSON.stringify (or URLEncode) as $id, but unfortunately the $id became too big, which caused Lime Admin to crash on big schemas.

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.

So the ID would be a JSON encoded string of the schema itself? 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, that actually worked (but not on big schemas though).

@jgroth jgroth enabled auto-merge (rebase) May 8, 2026 13:25
@jgroth jgroth merged commit a788042 into main May 8, 2026
9 of 10 checks passed
@jgroth jgroth deleted the fix/form-stale-validator-on-id-collision branch May 8, 2026 13:28
@lime-opensource
Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 39.23.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants