Skip to content

fix(OmegaForm): deep-fill defaults for nullable nested structs#759

Merged
MakhBeth merged 1 commit into
mainfrom
fix/nestedNullable
May 19, 2026
Merged

fix(OmegaForm): deep-fill defaults for nullable nested structs#759
MakhBeth merged 1 commit into
mainfrom
fix/nestedNullable

Conversation

@MakhBeth
Copy link
Copy Markdown
Contributor

Problem

A S.NullOr(S.Struct(...)) field has no slot in the form value until a child is filled. Once it materialises (the user fills one child), its untouched nullable siblings are still undefined — which strict S.NullOr(...) children reject with a spurious "field must not be empty" error, blocking submission.

Fix

New fillNestedDefaults (AST walk) backfills the untouched children of a materialised nullable struct with their schema default (or null). Applied in three places:

  • validation and decoding — via normalizeFormValue, so submit/validate see the normalized value;
  • live form statenormalizeNullableStructs() is called from OmegaInternalInput.handleChange (the only point a struct can materialise), so values stays consistent. No perpetual watch.

The always-present root struct is left untouched; only children reached through a nullable union are filled. Discriminated unions are matched by _tag; ambiguous unions are left alone.

Tests

  • Defaults.values.test.ts — unit tests for fillNestedDefaults (materialised struct, tagged-union safety, array elements, reference-preservation).
  • NullableNestedStructValidation.test.ts — regression repro: submits when only one child of a nullable struct is filled; keeps an untouched struct as null.

When a S.NullOr(S.Struct(...)) field materialises, its untouched
nullable siblings are normalized to null (or their schema default)
in the live form state, during validation and during decoding —
instead of being rejected as "field must not be empty".
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 19, 2026

Open in StackBlitz

@effect-app/cli

npm i https://pkg.pr.new/effect-app/libs/@effect-app/cli@759

effect-app

npm i https://pkg.pr.new/effect-app/libs/effect-app@759

@effect-app/eslint-codegen-model

npm i https://pkg.pr.new/effect-app/libs/@effect-app/eslint-codegen-model@759

@effect-app/eslint-shared-config

npm i https://pkg.pr.new/effect-app/libs/@effect-app/eslint-shared-config@759

@effect-app/infra

npm i https://pkg.pr.new/effect-app/libs/@effect-app/infra@759

@effect-app/vue

npm i https://pkg.pr.new/effect-app/libs/@effect-app/vue@759

@effect-app/vue-components

npm i https://pkg.pr.new/effect-app/libs/@effect-app/vue-components@759

commit: 59ff901

@MakhBeth MakhBeth requested a review from Copilot May 19, 2026 16:24
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

This PR fixes OmegaForm submission/validation failures caused by S.NullOr(S.Struct(...)) fields “materializing” mid-edit (after one child is filled) while their untouched nullable siblings remain undefined and fail strict S.NullOr(...) decoding/validation.

Changes:

  • Add fillNestedDefaults (AST walk) to backfill missing nullable children (to schema defaults or null) only for structs reached through nullable unions.
  • Normalize values before validation and decoding, and keep live form state consistent by invoking normalization after field changes.
  • Add regression/unit tests plus a Storybook story and a changeset.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/vue-components/stories/OmegaForm/NullableNestedStruct.vue New story demonstrating nullable nested-struct materialization behavior.
packages/vue-components/stories/OmegaForm.stories.ts Registers the new NullableNestedStruct story.
packages/vue-components/src/components/OmegaForm/useOmegaForm.ts Normalizes values for schema validation/decoding; adds live-state normalization hook on the form instance.
packages/vue-components/src/components/OmegaForm/types.ts Exposes normalizeNullableStructs() on the OmegaForm API type.
packages/vue-components/src/components/OmegaForm/OmegaInternalInput.vue Calls form.normalizeNullableStructs() after field changes to keep values consistent.
packages/vue-components/src/components/OmegaForm/meta/defaults.ts Implements fillNestedDefaults AST-based backfilling for materialized nullable structs (reference-preserving).
packages/vue-components/tests/OmegaForm/NullableNestedStructValidation.test.ts Regression test proving submission succeeds with only one nullable child filled; untouched struct remains null.
packages/vue-components/tests/OmegaForm/Defaults.values.test.ts Unit tests for fillNestedDefaults behavior (tagged unions, arrays, reference preservation).
.changeset/omega-nullable-nested-struct.md Patch changeset entry for @effect-app/vue-components.

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

@MakhBeth MakhBeth merged commit ffc27b4 into main May 19, 2026
8 of 9 checks passed
@MakhBeth MakhBeth deleted the fix/nestedNullable branch May 19, 2026 16:31
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.

2 participants