Skip to content

feat: multi-select picker, and builds that lock, write atomically and clean up - #23

Merged
codad5 merged 2 commits into
mainfrom
feat/build-robustness
Oct 12, 2026
Merged

codad5 merged 2 commits into
mainfrom
feat/build-robustness

Conversation

@codad5

@codad5 codad5 commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Two groups of changes for rc.8.

1. Multiple-choice fields (ADR-0027)

A select with ->multiple() was the browser's <select multiple>: one item per click, and a saved selection could never be cleared (an empty selection submits nothing, so the save treated the field as "not on this form"). It now renders real checkboxes: a plain list up to six options, a searchable chip picker above that. ->display('checkboxes' | 'tokens' | 'native') overrides.

  • The picker is about 120 lines of plain JavaScript over the checkboxes (no jQuery, no library). The checkboxes stay the source of truth, so keyboard and screen-reader use, and the stored value, are unchanged; without JavaScript it is a usable list.
  • Every rendering sends an empty marker first, so unticking everything is saved. Validation ignores empty entries of a multiple field.
  • Script and style are printed once in the footer by the first picker on the page.
  • Existing multiple selects change appearance; nothing stored changes.

2. Builds are safe to run twice and to stop (ADR-0026)

  • One build per project at a time (OS file lock in the temp directory); a second fails naming the holder, or waits with waitForLock() / --wait=N.
  • Zips and directories are written beside the target and moved into place, so a failed or stopped build leaves the previous output whole.
  • Ctrl+C / SIGTERM stops the running command and its children, removes staging and partial output, releases the lock. Staging older than six hours from builds killed outright is swept.
  • Every step ends with its duration; an interactive terminal also gets a spinner. Plain lines whenever stdout is not a terminal, or CI, NO_COLOR, TERM=dumb, quiet(), plain()/--plain or withOutput() applies.
  • The runner polls a file the shell redirects into. A descriptor handed to proc_open lost the child's output after certain earlier tests in the same PHP process; the cause was not identified, and this form does not depend on it.

Verification

  • Tests written first. PHPCS, PHPStan, lint and the POT check are clean; full suite 613 tests.
  • Also run on Linux (Ubuntu, PHP 8.1, via WSL): the whole suite, and the packager tests three times in a row after removing a timer-dependent lock test. That includes a real SIGINT sent to a running build: it stops promptly, its command stops, nothing is left behind, and the lock is free.
  • The picker was exercised in a real browser against the library's actual output: filtering, Enter to pick, removing chips, Escape, and the submitted form values.
  • New end-to-end test (todo example gains an 8-option field): search, pick, remove, save, check the stored row, clear and save, check it is gone.

Not verified

  • The spinner on a real interactive terminal (tested against a simulated one).
  • The end-to-end test has not run yet; CI will.
  • Ctrl+C on Windows (the handler is registered, but only the POSIX path was exercised for real).

Noticed, not changed: the media field renders data-wptoolkit-media for an admin script that does not exist, so its button does nothing.

🤖 Generated with Claude Code

codad5 and others added 2 commits October 12, 2026 14:48
A select with multiple() was the browser's <select multiple>: one item
per click, and a saved selection could never be cleared because an empty
selection submits nothing. It now renders real checkboxes (a chip picker
with search above six options) and always sends an empty marker. The
picker is plain JavaScript over the checkboxes, so keyboard use and the
stored value are unchanged. display() chooses checkboxes, tokens or
native. See ADR-0027.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
One build per project runs at a time (OS file lock; waitForLock() or
--wait to queue). Zips and directories are written beside their target
and moved into place, so a failed or stopped build leaves the previous
output whole. Ctrl+C stops the running command and what it started and
removes staging; abandoned staging is swept. Steps end with their
duration, and an interactive terminal gets a spinner that never reaches
logs. The runner polls a file the shell redirects into, because a
descriptor handed to proc_open lost output in long test runs. See
ADR-0026.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@codad5
codad5 merged commit 23cab99 into main Oct 12, 2026
19 checks passed
@codad5
codad5 deleted the feat/build-robustness branch October 12, 2026 13:56
@codad5 codad5 mentioned this pull request Oct 12, 2026
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