Title: feat(forms): graduate forms toward Stable — progressive-enhancement actions + optimistic updates (#139, #140) by JosunLP · Pull Request #156 · bQuery/bQuery · GitHub
Open Graph Title: feat(forms): graduate forms toward Stable — progressive-enhancement actions + optimistic updates (#139, #140) by JosunLP · Pull Request #156 · bQuery/bQuery
X Title: feat(forms): graduate forms toward Stable — progressive-enhancement actions + optimistic updates (#139, #140) by JosunLP · Pull Request #156 · bQuery/bQuery
Description: Collected PR for both open forms tickets, implemented on one branch. Closes #139 Closes #140 #139 — Promote forms to Stable (freeze the 1.13 surface) The forms surface expanded materially in 1.13.0 and has documented sharp edges. This freezes the surface for one minor cycle and settles those edges into guaranteed, tested contracts: validationStrategy default reviewed + documented. The default stays 'manual' (the least-surprising choice for "validate on submit"). The contract is now explicit: handleSubmit() always runs the full validation pass, regardless of strategy — validationStrategy only gates automatic per-change/per-blur validation. Behaviour is unchanged; the surprise is removed by documentation. SSR serialization boundary is now guaranteed. serializeFormState() deterministically drops functions, File / Blob / FileList, bigint, and symbol via an explicit replacer, instead of relying on incidental JSON.stringify behaviour. Re-attach blobs on the client after hydration. createFieldArray() stable-key contract validated with clear errors. New optional getKey enforces present, unique keys on every structural mutation and throws a descriptive error naming the offending key (e.g. "requires stable, unique item keys, but getKey returned "a" for both index 0 and index 1"). Adds keys() / keyAt(index). Without getKey the array stays positional — no behaviour change. Surface frozen + documented. New Stability section in the Forms guide with the exit-criteria checklist and frozen-surface reference; module header and stability matrices updated to "targeting Stable in 1.15.0". #140 — Progressive-enhancement form actions + optimistic updates The headline React-19-parity feature: forms that work before/without JS and reconcile optimistically. import { formAction, useFormStatus, optimistic } from '@bquery/bquery/forms'; const submit = formAction('/todos', { method: 'POST', csrf: () => csrfToken }); const { pending } = useFormStatus(submit); const list = optimistic(todos, (cur, draft) => [...cur, draft]); submit.enhance(document.querySelector('form')!); // native POST without JS; fetch-enhanced with JS formAction(target, options) → reactive pending / error / result / submitCount / submittedAt; enhance(form) sets the native action/method (+ optional hidden CSRF field) for the no-JS path, then intercepts submit for a fetch-based, optimistic-aware submit; programmatic submit(formData); reset(). target is an endpoint URL or a function. Non-OK responses throw FormActionError (carrying status / response). Composes with the validation pipeline and the server module's csrf() (#132). Native forms only support GET/POST, so PUT/PATCH/DELETE degrade to a native POST (the enhanced fetch keeps the real verb). useFormStatus(action) → read-only readonly() views of the action's signals, mirroring React 19's useFormStatus. optimistic(base, reducer) → an optimistic-update primitive whose reactive value folds pending drafts over the base and reverts automatically. add(draft) → handle with remove(); run(draft, task) applies the overlay around an async task; pending / drafts reactive; clear(). Quality 28 new tests across tests/forms-stable.test.ts (field-array key contract, SSR boundary, validationStrategy timing) and tests/forms-actions.test.ts (formAction function + string targets, CSRF, enhance/PE, optimistic composition, useFormStatus). Full suite 2882 pass / 0 fail; tsc --noEmit, eslint, check-full-bundle (in sync), check-doc-exports (forms 46/46), and build:lib / build:types / build:umd all green. New public exports (formAction, useFormStatus, optimistic, FormActionError, FieldArrayKeyFn + action/optimistic types) are wired into src/full.ts and documented in the guide. 🤖 Generated with Claude Code
Open Graph Description: Collected PR for both open forms tickets, implemented on one branch. Closes #139 Closes #140 #139 — Promote forms to Stable (freeze the 1.13 surface) The forms surface expanded materially in 1.13.0...
X Description: Collected PR for both open forms tickets, implemented on one branch. Closes #139 Closes #140 #139 — Promote forms to Stable (freeze the 1.13 surface) The forms surface expanded materially in 1.13.0...
Opengraph URL: https://github.com/bQuery/bQuery/pull/156
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:d26b2b5f-fb4e-3f06-f249-c6721db4806f |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 9584:1E6125:2EFECF:4283C3:6A4E6AF6 |
| html-safe-nonce | 6659e233ec2521d73242bb7224a41ffe32f5d8035400349c018fdb311c1e1231 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5NTg0OjFFNjEyNToyRUZFQ0Y6NDI4M0MzOjZBNEU2QUY2IiwidmlzaXRvcl9pZCI6IjM0MTYyNjcyNzQ0OTkwMjU2NTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | e026edae42bd6ae684368a0b60286a95e5f8a10916579bff0ad27a413188f2e6 |
| hovercard-subject-tag | pull_request:3951880978 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/bQuery/bQuery/pull/156/files |
| twitter:image | https://avatars.githubusercontent.com/u/20913954?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/20913954?s=400&v=4 |
| og:image:alt | Collected PR for both open forms tickets, implemented on one branch. Closes #139 Closes #140 #139 — Promote forms to Stable (freeze the 1.13 surface) The forms surface expanded materially in 1.13.0... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 41b6ab3ba6d20a71766ac245b5a4a94c6fc672a9cd4da7d44c1b33ab8bf6a21c |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/bQuery/bQuery git https://github.com/bQuery/bQuery.git |
| octolytics-dimension-user_id | 256381806 |
| octolytics-dimension-user_login | bQuery |
| octolytics-dimension-repository_id | 1139284244 |
| octolytics-dimension-repository_nwo | bQuery/bQuery |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1139284244 |
| octolytics-dimension-repository_network_root_nwo | bQuery/bQuery |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | e6a744804e8e70f97b4d5a18a94dcc63db22f97a |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width