Skip to content

Keep a pick in a multiple select that becomes a drop-down - #216

Merged
joeldrapper merged 3 commits into
mainfrom
claude/project-thread-1mniku
Oct 17, 2026
Merged

joeldrapper merged 3 commits into
mainfrom
claude/project-thread-1mniku

Conversation

@joeldrapper

@joeldrapper joeldrapper commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Joel · project thread

Before: with preserveChanges, if the user selected an option in a <select multiple> and the target turned it into a drop-down, the user's option was lost. The browser keeps an option of its own choosing when multiple goes away, and an option the markup selects then takes the selection too. The nightly fuzz failed on this twice (seeds 39013173021 and 39013194161, both on Oct 17).

After: the user's option stays selected, as it already does in a select that was a drop-down all along (#208).

Picks are now noted in multiple selects too, and given back after multiple is removed, the same way they're given back after a selected or checked update. When the user picked several options, the ones whose value the target still has are given back last, so a pick the morph removes doesn't leave the drop-down on the default. An option gaining selected in a select that stays multiple can't take anything from the others, so it doesn't gather the picks at all.

The third failing seed (38701502299, Oct 15) was an oracle gap in the form-state fuzzer, not a library bug. The user typed in two textareas: one whose text matched an untouched sibling, and one with other text that the target removed. The fuzzer expected both typed textareas to survive. The morph gives the leftover target to the untouched textarea with the same text, as the design notes on identical siblings describe. The fuzzer now counts that target as taken.

How: isPick no longer excludes multiple selects. The attribute removal pass gets the held picks before removing multiple and gives them back after, ordered by picksKeptLast. There are three new tests in changed-default-pick, and each fails without its fix.

Known gap, left on purpose: when two options in one select share a value, the user selects both, and the target drops one of them while making the select a drop-down, the doomed twin can still be given back last. See the review thread for why this isn't worth matching options early.

Checks: format, lint, typecheck, and the full Chromium suite with 100% coverage all pass. All three seeds pass, and so do 12,500 fresh form-state seeds (2,500 of them in preserve-only mode). Benchmarked against main with the perf-sweep harness, and every scenario is within noise.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NCD2TWTPy7idzkWwiWwefa

With preserveChanges, an option the user selected in a multiple select was
lost when the target turned the select into a drop-down: removing `multiple`
lets the browser keep a single option of its own choosing, and an option the
markup selects then takes the selection too. Picks are now noted in multiple
selects as well, and given back after `multiple` is removed, as they already
are after a `selected` or `checked` update.

The form-state fuzzer also expected a typed textarea to take a target that
the morph gives to an untouched textarea with the same text, once a typed
twin of that untouched one has taken its first target. It now counts that
target as taken.

Nightly fuzz seeds 39013173021, 39013194161 and 38701502299.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCD2TWTPy7idzkWwiWwefa
@joeldrapper joeldrapper self-assigned this Oct 17, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T09:51:07.317237Z 8eb1549 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: cf77285468

ℹ️ 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/morphlex.ts Outdated
…mes a drop-down

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCD2TWTPy7idzkWwiWwefa

@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: 8eb1549f29

ℹ️ 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/morphlex.ts
// one the morph is about to remove, which would leave the drop-down showing neither.
function picksKeptLast(held: Array<Pick>, to: HTMLSelectElement): Array<Pick> {
const values = new Set(Array.from(to.options, (option) => option.value))
const kept = (pick: Pick) => isOptionElement(pick) && values.has(pick.value)

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 Track surviving options rather than only their values

When two dirty selected options share a value but only one survives in the target, values.has classifies both as kept. For example, if selected <option id="keep" value="x"> and <option id="drop" value="x"> are preceded by an unselected option and the target retains only keep, this ordering restores the doomed drop last; child reconciliation then removes it and the drop-down falls back to the first option, losing the surviving user pick. Fresh evidence beyond the prior comment is that duplicate-valued options defeat the new value-only partition, so this needs to distinguish which live options can actually survive, including duplicate counts or keyed identity.

AGENTS.md reference: AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Traced it. It needs four things at once: two options with the same value in one multiple select, the user selecting both, the target dropping one of them, and the same morph turning the select into a drop-down. Two options with the same value in one select submit the same thing, so markup like that is rare, and combined with the rest it's a pathological case. Telling which twin survives would mean matching options before the children are reconciled, which is far more code than the case is worth under the project's proportionate-fix rule. So I'm leaving this as is and putting the call to the maintainer.


Generated by Claude Code

Comment thread src/morphlex.ts
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCD2TWTPy7idzkWwiWwefa
@joeldrapper
joeldrapper merged commit 43c14a8 into main Oct 17, 2026
7 checks passed
@joeldrapper
joeldrapper deleted the claude/project-thread-1mniku branch October 17, 2026 11:07
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