Devana fixes - #10
Conversation
normalizeChoiceSelection returned [] for a scalar string in multiple mode, so the first checkbox toggle rebuilt selection from empty and lost the previously stored choice. Coerce scalar strings to one-element arrays in multiple mode. Adds regression test. Devana: 20260625T114006Z-P1-multiple-choice-scalar-lost
Non-object roots (array/scalar/null) cannot be represented in an object editor; valid object roots already preserve all keys including extras. Loss only occurs on explicit edit, never on load. Suggested mount-time emit would drop the same data earlier and cause spurious dirty state. Devana: 20260625T114007Z-P1-object-invalid-root-lost
Non-object rows are unrepresentable in the per-row object editor; preserving them would persist mixed-type rows that violate the library's tested normalize-every-row-to-object invariant. Object rows already keep all keys. Loss only on explicit mutation, never on load. Devana: 20260625T114008Z-P1-structure-row-coercion-lost
The boolean checkbox used Boolean(value), so the truthy string "false"
rendered as checked. Add isBooleanChecked helper with strict string
interpretation ("true"/"1" checked, "false"/"0"/"" unchecked) and
Boolean() semantics for non-strings. Adds regression test.
Devana: 20260625T114009Z-P2-boolean-string-false-checked
No data corruption: inputs are fully controlled and onChange closures refresh every render, so each slot always edits the item it displays. Reorder via button also moves focus off the input. Index keys are stylistic only here. Devana: 20260625T114010Z-P2-structure-reorder-wrong-row
A string array (e.g. legacy data after switching multiple:true->false) showed no selection. normalizeChoiceSelection now coerces an array to its first string in single mode, and the vertical Radio.Group uses a new normalizeSingleChoice helper so both renderers reflect it. Adds tests. Devana: 20260625T114011Z-P2-single-choice-array-deselected
Same class as the root-coercion findings: value persists intact on load, loss only on explicit action. Mount-time migration causes spurious dirty state and needs a fragile index-map heuristic; a recovery-warning UI is a feature, not a fix. Devana: 20260625T114012Z-P2-structure-non-array-empty-ui
normalizeChoices passed object choices through unchanged, so a choice missing value reached choiceInputId and threw on undefined.replace(). Now every normalized choice has a string value: synthesized from a string label when missing, otherwise the malformed choice is dropped. Adds regression tests. Devana: 20260625T120226Z-P3-choice-missing-value-crash
A bare string root maps naturally to the link's URL value, so
normalizeLinkValue now coerces a non-empty string to { value } instead of
{}. The URL is surfaced on load and preserved when editing other
subfields. Other non-object roots still normalize to {}. Updates tests.
Devana: 20260627T180001Z-P1-link-invalid-root-lost
ChoicesField used Boolean(options.multiple), so quoted "false" from serialized config enabled multi-select. Derive multiple via shared coerceBoolean (which isBooleanChecked now delegates to) so quoted booleans parse correctly both ways. Adds regression test. Devana: 20260627T180002Z-P2-choices-multiple-string-false
Number/integer subfields parsed and wrote back on every keystroke, blocking decimal entry and wiping integers on transient decimals. Add a NumericSubField with a local string draft (text input + inputMode) and a pure numericChangeCommit helper that holds in-progress invalid drafts instead of clearing the value. Adds unit tests. Devana: 20260627T180003Z-P2-numeric-keystroke-intermediate-loss
normalizeLinkValue passed through any string type/target, so alien values (javascript, _parent) displayed as defaults yet persisted. Now out-of-union type/target are dropped while value/text and extra keys are preserved, keeping storage consistent with the controls. Adds test. Devana: 20260627T180004Z-P2-link-alien-fields-persist
Duplicate values share a React key and one selection token, so the cards mirrored each other and could not be toggled independently. Independent selection is unrepresentable (storage is an array of value strings), so later duplicates are dropped (first wins), matching selection-set dedup. Adds test. Devana: 20260627T180005Z-P2-duplicate-choice-value-collapse
Auto-clamping loaded values would silently delete above-max rows or seed spurious below-min rows (dirtying entries on open). min/max are interactive guardrails on Add/Remove; document this in README rather than enforce destructively. Devana: 20260627T180006Z-P2-structure-min-max-bypass
A contradictory min > max disabled both Add and Remove at max, locking the editor below an unreachable floor. Add effectiveStructureBounds to clamp the floor to the ceiling so the field settles at exactly max, and drive both button guards from it. Adds unit test. Devana: 20260627T180007Z-P2-structure-min-gt-max-deadlock
ChoicesField used choices ?? options, so an empty choices:[] shadowed the options alias and showed the misconfiguration message. Fall back when choices is absent or empty. Adds SSR regression test. Devana: 20260627T180008Z-P2-choices-empty-blocks-options
Select subfields rendered blank for any non-string stored value. Add selectSubfieldValue to stringify numbers (legacy numeric JSON) so they match string options and render as selected, mirroring text inputs. Non-scalars still blank. Adds unit test. Devana: 20260627T180009Z-P2-select-subfield-non-string-hidden
Value-based onChange + synchronous controlled parent (EmDash's contract) re-renders between discrete events, so closures recompose from current state. The clobber needs a deferring/batching parent the widget can't detect; a correct fix requires routing all composition (buttons + per-row edits) through a value ref, an invasive refactor for a theoretical edge. Devana: 20260627T180010Z-P2-structure-stale-closure-clobber
Number/integer subfields now interpret stored values via interpretNumericValue: quoted numeric strings display as numbers (and edits emit a real Number), off-type shapes blank out. Also fixes a regression where the draft refactor blanked stored numeric strings. Adds unit + SSR tests. Devana: 20260627T180011Z-P2-number-subfield-string-persists
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
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. Comment |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35a2d0c6fe
ℹ️ 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".
| if (type === "select") { | ||
| return normalizeSelectSubfieldValue(value, field.options); |
There was a problem hiding this comment.
Preserve unknown select values on load
When a select subfield has a stored value that is not in the current option list, such as after options are renamed or temporarily omitted, this normalizes it to ""; because object and structure widgets now emit normalized values on mount, simply opening the editor clears the saved selection before the editor chooses a replacement. The rendered Select can still show a blank fallback, but persistence should keep the raw value until the user changes it.
Useful? React with 👍 / 👎.
No description provided.