Skip to content

Devana fixes - #10

Merged
bnomei merged 27 commits into
mainfrom
devana-fixes
Jun 29, 2026
Merged

Devana fixes#10
bnomei merged 27 commits into
mainfrom
devana-fixes

Conversation

@bnomei

@bnomei bnomei commented Jun 27, 2026

Copy link
Copy Markdown
Owner

No description provided.

bnomei and others added 25 commits June 27, 2026 14:47
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
@bnomei bnomei self-assigned this Jun 27, 2026
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@bnomei, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee92f40a-2716-43d5-a7ed-3795e2059f5a

📥 Commits

Reviewing files that changed from the base of the PR and between 2da6047 and 5e735eb.

📒 Files selected for processing (10)
  • README.md
  • src/admin-locale.ts
  • src/admin.tsx
  • src/i18n.ts
  • src/index.ts
  • src/schema.ts
  • src/types.ts
  • test/semantics.test.mjs
  • tests/numeric-input.test.mjs
  • tests/transformations.test.mjs

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.

@bnomei

bnomei commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

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

Copy link
Copy Markdown

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: 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".

Comment thread src/admin.tsx Outdated
Comment on lines +498 to +499
if (type === "select") {
return normalizeSelectSubfieldValue(value, field.options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@bnomei
bnomei merged commit fb754bd into main Jun 29, 2026
1 check passed
@bnomei
bnomei deleted the devana-fixes branch June 29, 2026 12:28
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