Skip to content

Add dirtyOnly option to getFieldInput and getInput for filtering dirty fields#96

Draft
fabian-hiller wants to merge 2 commits into
mainfrom
dirty-only-option
Draft

Add dirtyOnly option to getFieldInput and getInput for filtering dirty fields#96
fabian-hiller wants to merge 2 commits into
mainfrom
dirty-only-option

Conversation

@fabian-hiller
Copy link
Copy Markdown
Member

@fabian-hiller fabian-hiller commented May 17, 2026

Fix #21


Summary by cubic

Add a dirtyOnly option to getInput and getFieldInput to return only changed fields and reduce payloads. Arrays are now treated as atomic: if any item is dirty, the full array is returned.

  • New Features
    • getFieldInput: accepts config with { dirtyOnly }. When true, clean subtrees return undefined; objects include only dirty keys; arrays return the full current array when any descendant is dirty; includes explicit undefined when a dirty value was cleared.
    • getInput: accepts dirtyOnly in both config variants, forwards it to getFieldInput, and supports scoping via path.
    • Docs and tests updated for the new dirtyOnly semantics, including scoped paths, arrays-as-atomic, and nullish/undefined cases.

Written for commit 0d8e53e. Summary will update on new commits. Review in cubic

Copilot AI review requested due to automatic review settings May 17, 2026 22:19
@vercel
Copy link
Copy Markdown

vercel Bot commented May 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
formisch Ready Ready Preview, Comment May 18, 2026 1:22am

Request Review

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. enhancement New feature or request labels May 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Review Change Stack

Walkthrough

This pull request adds a dirtyOnly option to getFieldInput and getInput. Core adds an exported GetFieldInputOptions type and a config parameter to getFieldInput that returns undefined for clean subtrees, filters object keys to dirty children, and treats arrays as atomic when any item is dirty. Methods adds matching config fields and forwards the option into getFieldInput. Tests cover primitives, nested objects, arrays (including sparse and nested-item cases), and website docs are updated to document the new option and its semantics.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding a dirtyOnly option to getFieldInput and getInput for filtering dirty fields.
Description check ✅ Passed The description is related to the changeset and explains the feature, implementation details, and what was updated including docs and tests.
Linked Issues check ✅ Passed The PR implements the primary objective from issue #21: adding an option to retrieve only dirty inputs for submission of changed fields. The dirtyOnly option in both getFieldInput and getInput addresses the core request.
Out of Scope Changes check ✅ Passed All changes are directly in scope: getFieldInput/getInput implementation and tests, config type updates, documentation, and changelog entries for the dirtyOnly feature.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6458822fa3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +63 to +65
const child = internalFieldStore.children[key];
if (!config?.dirtyOnly || child.isDirty.value) {
value[key] = getFieldInput(child, config);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Traverse clean parents when filtering dirty fields

When dirtyOnly is enabled, recursion is gated by child.isDirty.value, so traversal stops at any clean parent. In this codebase, parent isDirty often stays false for nested edits (for example, updating user.email in a non-null object), which means dirty descendants are dropped and getInput({ dirtyOnly: true }) can return {} even though fields changed. This breaks the documented "submit changed values" use case for common nested object/array updates.

Useful? React with 👍 / 👎.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/field/getFieldInput/getFieldInput.ts (1)

75-76: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

dirtyOnly is ignored for value-field root calls.

With { dirtyOnly: true }, this branch still returns clean scalar values. Path-scoped calls to primitive fields can therefore bypass the dirty filter.

Proposed fix
   // Return primitive value input
-  return internalFieldStore.input.value;
+  if (config?.dirtyOnly && !internalFieldStore.isDirty.value) {
+    return undefined;
+  }
+  return internalFieldStore.input.value;
🤖 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 `@packages/core/src/field/getFieldInput/getFieldInput.ts` around lines 75 - 76,
In getFieldInput, the root primitive return currently ignores the dirtyOnly
flag; update the branch that returns internalFieldStore.input.value to first
check the passed options.dirtyOnly and the field's dirty flag (e.g.,
internalFieldStore.meta.dirty or internalFieldStore.input.meta?.dirty) and only
return the scalar when dirtyOnly is false OR the field is marked dirty; if
dirtyOnly is true and the field is not dirty, return the "no value" sentinel
used by getFieldInput (e.g., undefined/null) so path-scoped callers cannot
bypass the dirty filter.
🤖 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.

Outside diff comments:
In `@packages/core/src/field/getFieldInput/getFieldInput.ts`:
- Around line 75-76: In getFieldInput, the root primitive return currently
ignores the dirtyOnly flag; update the branch that returns
internalFieldStore.input.value to first check the passed options.dirtyOnly and
the field's dirty flag (e.g., internalFieldStore.meta.dirty or
internalFieldStore.input.meta?.dirty) and only return the scalar when dirtyOnly
is false OR the field is marked dirty; if dirtyOnly is true and the field is not
dirty, return the "no value" sentinel used by getFieldInput (e.g.,
undefined/null) so path-scoped callers cannot bypass the dirty filter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78e68efe-98b4-4ca9-8274-0e73c9722dce

📥 Commits

Reviewing files that changed from the base of the PR and between 385bae2 and 6458822.

📒 Files selected for processing (11)
  • packages/core/CHANGELOG.md
  • packages/core/src/field/getFieldInput/getFieldInput.test.ts
  • packages/core/src/field/getFieldInput/getFieldInput.ts
  • packages/methods/CHANGELOG.md
  • packages/methods/src/getInput/getInput.test.ts
  • packages/methods/src/getInput/getInput.ts
  • website/src/routes/(docs)/methods/api/(methods)/getInput/index.mdx
  • website/src/routes/(docs)/methods/api/(types)/GetFieldInputConfig/index.mdx
  • website/src/routes/(docs)/methods/api/(types)/GetFieldInputConfig/properties.ts
  • website/src/routes/(docs)/methods/api/(types)/GetFormInputConfig/index.mdx
  • website/src/routes/(docs)/methods/api/(types)/GetFormInputConfig/properties.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a dirtyOnly option to input retrieval APIs so consumers can request only dirty values (based on isDirty) from getInput / getFieldInput, with accompanying docs, tests, and changelog entries.

Changes:

  • Add dirtyOnly to GetFormInputConfig / GetFieldInputConfig and pass it through getInput to core getFieldInput.
  • Extend core getFieldInput with a config parameter (GetFieldInputOptions) supporting dirty filtering.
  • Update docs + add tests + changelog entries for the new option.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
website/src/routes/(docs)/methods/api/(types)/GetFormInputConfig/properties.ts Documents the new dirtyOnly config property for form-level input retrieval.
website/src/routes/(docs)/methods/api/(types)/GetFormInputConfig/index.mdx Adds dirtyOnly to the rendered API docs list for GetFormInputConfig.
website/src/routes/(docs)/methods/api/(types)/GetFieldInputConfig/properties.ts Documents the new dirtyOnly config property for field-level input retrieval.
website/src/routes/(docs)/methods/api/(types)/GetFieldInputConfig/index.mdx Adds dirtyOnly to the rendered API docs list for GetFieldInputConfig.
website/src/routes/(docs)/methods/api/(methods)/getInput/index.mdx Documents how to use dirtyOnly when calling getInput.
packages/methods/src/getInput/getInput.ts Adds dirtyOnly to the public config types and forwards it to core getFieldInput.
packages/methods/src/getInput/getInput.test.ts Adds test coverage for getInput(..., { dirtyOnly: true }).
packages/methods/CHANGELOG.md Notes the new dirtyOnly option for getInput.
packages/core/src/field/getFieldInput/getFieldInput.ts Adds GetFieldInputOptions and implements dirtyOnly filtering during traversal.
packages/core/src/field/getFieldInput/getFieldInput.test.ts Adds tests for getFieldInput(..., { dirtyOnly: true }).
packages/core/CHANGELOG.md Notes the new GetFieldInputOptions / config param for getFieldInput.
Comments suppressed due to low confidence (1)

packages/core/src/field/getFieldInput/getFieldInput.ts:66

  • With dirtyOnly, this loop prunes recursion based on child.isDirty.value. For object/array field stores, isDirty doesn’t necessarily reflect dirty descendants (e.g. setFieldInput marks ancestor input truthy but doesn’t recompute ancestor isDirty). This can cause nested dirty leaves to be omitted from form-level results. Consider always descending into object/array children and only including the subtree when it contains at least one dirty leaf (or use getFieldBool(child, 'isDirty') as the gate).

      // Collect input from each object property
      for (const key in internalFieldStore.children) {
        const child = internalFieldStore.children[key];
        if (!config?.dirtyOnly || child.isDirty.value) {
          value[key] = getFieldInput(child, config);
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 32 to 47
if (internalFieldStore.input.value) {
// Create output array
const value = [];

// Collect input from each array item
for (
let index = 0;
index < internalFieldStore.items.value.length;
index++
) {
value[index] = getFieldInput(internalFieldStore.children[index]);
const child = internalFieldStore.children[index];
if (!config?.dirtyOnly || child.isDirty.value) {
value[index] = getFieldInput(child, config);
}
}
return value;
Comment on lines +144 to +154
test('should miss dirty leaves under a clean object parent', () => {
const store = createTestStore(
v.object({ user: v.object({ email: v.string(), name: v.string() }) }),
{ initialInput: { user: { email: 'a@x.com', name: 'John' } } }
);
const user = store.children.user;
if (user.kind !== 'object') throw new Error('expected object');
user.children.email.input.value = 'b@x.com';
user.children.email.isDirty.value = true;
expect(getFieldInput(store, { dirtyOnly: true })).toStrictEqual({});
});
Comment on lines 19 to 27
* The path to a field. Leave undefined to get the entire form input.
*/
readonly path?: undefined;
/**
* Whether to include only fields whose `isDirty` flag is set. Useful for
* submitting only the values that changed since the start input.
*/
readonly dirtyOnly?: boolean;
}
### Explanation

The `form` parameter is the form store to retrieve input from. The optional `config` parameter specifies which field to get input from - if omitted, returns input from the entire form.
The `form` parameter is the form store to retrieve input from. The optional `config` parameter specifies which field to get input from - if omitted, returns input from the entire form. Set `dirtyOnly` to `true` to include only fields whose `isDirty` flag is `true`, which is useful for submitting only the values that changed since the start input.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/src/field/getFieldInput/getFieldInput.ts">

<violation number="1" location="packages/core/src/field/getFieldInput/getFieldInput.ts:43">
P1: `dirtyOnly` filtering can hide nested dirty fields because recursion is gated by direct child `isDirty` only. Dirty leaf updates under a clean parent are omitted from the returned input.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Re-trigger cubic

Comment thread packages/core/src/field/getFieldInput/getFieldInput.ts Outdated
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/field/getFieldInput/getFieldInput.ts (1)

49-58: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

dirtyOnly is not applied to array children.

Line 57 calls getFieldInput without config, so in dirtyOnly mode clean indices are returned with their current values instead of undefined. That breaks dirty-only array filtering semantics.

Suggested fix
-      // Collect input from each array item. Once we enter an array, every
-      // item is fully populated regardless of dirty state — arrays are
-      // atomic in `dirtyOnly` mode.
+      // Collect input from each array item. In `dirtyOnly` mode, clean
+      // indices resolve to `undefined`, preserving positional semantics.
       for (
         let index = 0;
         index < internalFieldStore.items.value.length;
         index++
       ) {
-        value[index] = getFieldInput(internalFieldStore.children[index]);
+        value[index] = getFieldInput(internalFieldStore.children[index], config);
       }
🤖 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 `@packages/core/src/field/getFieldInput/getFieldInput.ts` around lines 49 - 58,
When collecting array item inputs inside getFieldInput, the recursive call uses
getFieldInput(internalFieldStore.children[index]) without passing the current
config, so dirtyOnly semantics are lost; fix by passing the same config
(including dirtyOnly) into the recursive call (i.e., call
getFieldInput(internalFieldStore.children[index], config)) so clean indices
return undefined when dirtyOnly is enabled and array children respect the
parent's dirtyOnly behavior.
🤖 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.

Outside diff comments:
In `@packages/core/src/field/getFieldInput/getFieldInput.ts`:
- Around line 49-58: When collecting array item inputs inside getFieldInput, the
recursive call uses getFieldInput(internalFieldStore.children[index]) without
passing the current config, so dirtyOnly semantics are lost; fix by passing the
same config (including dirtyOnly) into the recursive call (i.e., call
getFieldInput(internalFieldStore.children[index], config)) so clean indices
return undefined when dirtyOnly is enabled and array children respect the
parent's dirtyOnly behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00473e7e-cb1e-46bc-a7d7-e711db5c66dc

📥 Commits

Reviewing files that changed from the base of the PR and between 6458822 and 0d8e53e.

📒 Files selected for processing (5)
  • packages/core/src/field/getFieldInput/getFieldInput.test.ts
  • packages/core/src/field/getFieldInput/getFieldInput.ts
  • packages/methods/src/getInput/getInput.test.ts
  • packages/methods/src/getInput/getInput.ts
  • website/src/routes/(docs)/methods/api/(methods)/getInput/index.mdx
✅ Files skipped from review due to trivial changes (1)
  • website/src/routes/(docs)/methods/api/(methods)/getInput/index.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/methods/src/getInput/getInput.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d8e53e92b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return getFieldInput(
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL],
config?.dirtyOnly ? { dirtyOnly: true } : undefined
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Widen getInput return type for dirtyOnly mode

Enabling dirtyOnly now forwards { dirtyOnly: true } to getFieldInput, which returns undefined for clean forms/subtrees, but the public overloads still promise PartialValues<...> and do not include undefined. This makes TypeScript callers treat the result as always defined and can cause runtime errors on common code paths (for example, iterating keys of a clean result) when dirtyOnly is used.

Useful? React with 👍 / 👎.

@fabian-hiller fabian-hiller marked this pull request as draft May 18, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature-request: add option to get all dirty inputs

2 participants