Skip to content

Panel form slices on the create pages and settings forms (spec 0088) - #2742

Open
glennjacobs wants to merge 3 commits into
spec/0086-panel-draft-slicesfrom
spec/0088-panel-form-slices
Open

glennjacobs wants to merge 3 commits into
spec/0086-panel-draft-slicesfrom
spec/0088-panel-form-slices

Conversation

@glennjacobs

@glennjacobs glennjacobs commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #2741 (spec 0086). Implements spec 0088, included in this PR: the panel's plain forms compose the same FormSlice contract the drafted edit pages do, so one slice class serves both. This is the answer to the review on #2741: no second contract.

The stance. An add-on adds fields under a namespace it owns, on any first-party form's save, and never touches the form's own fields. The form kind decides the plumbing, not the contract.

What differs from a drafted page. A plain form carries only what it posts, so a slice takes part only when a component bound it:

  • Binding claims the namespace. useFormSlice on a plain page seeds that namespace's keys from formSliceValues into the form's data and defaults. A page nobody extends posts exactly what it did before; product creation does not suddenly carry the product's channel rows.
  • Only submitted namespaces commit, with the slice's current values overlaid by the submitted ones.
  • Slice rules are composed under sometimes, so an unbound namespace cannot fail validation; within a bound namespace the slice's rules apply in full.
  • Create pages validate and seed against a fresh instance; commit() receives the created record inside the store's transaction.

Changes

  • Forms\SliceSet: the prefixing composer, extracted from ComposedDraftResource and shared.
  • Forms\FormSlices: rules(), values(), commit(), and save(input, action) which runs the action and the commit in one transaction.
  • Http\Requests\Concerns\ValidatesFormSlices: merges slice rules into a request's own (one validation round), keeps slice keys out of validated() so requests that pass validated input to actions wholesale never leak them, and exposes them via sliceInput(). Applied to the six create requests and thirteen settings requests.
  • Controllers: the six create and thirteen settings update pairs go through FormSlices::save(); create controllers pass formSliceValues for a fresh record. The shared prop now resolves slices for any model with slices, not only draftable ones.
  • usePanelForm: wraps Inertia's useForm, returns it unchanged, and provides a SliceForm with claim-on-bind. All nineteen plain-form pages swap to it. Exported on @lunarphp/panel.
  • ProductAttributeSlice tolerates an unsaved product (no type, no mapping).
  • Example add-on: the loyalty card also sits on customers.create:main:after, same slice, same component. README section extended.

Out of scope, by design: the settings index pages' inline create dialogs (no slot zone to bind in), the roles form (a Spatie model), auth and account forms, and the order view.

Related

  • Documentation: lunarphp/docs#57 covers both form kinds on one Form Slices page, including the differences a plain form introduces (claim-on-bind, sometimes rules, commit for submitted namespaces only). Merge it after this stack lands.
  • Stack: top of stack #2743, on #2741 (spec 0086), which introduces the contract and the drafted-page composition.

Verification

  • vendor/bin/pest --testsuite panel --parallel: 967 passed, including new Feature/Forms/FormSlicesTest (settings update, create, one-round validation under the prefixed key, unbound namespace untouched, rollback on a failing slice commit) and the example add-on's create-page test
  • vendor/bin/phpstan analyse: no errors; vendor/bin/pint: clean
  • npm test (352 passed), npm run type-check, npm run build; example add-on builds through the workspace

🤖 Generated with Claude Code

glennjacobs and others added 2 commits September 16, 2026 21:43
… 0088)

Composes a model's registered form slices into the panel's plain forms, so
the same FormSlice class an add-on wrote for a drafted edit page also serves
the create pages and the settings edit forms. A plain form carries only what
it posts, so a slice takes part only when a component bound it: its keys join
the form on bind, its rules apply when its keys are present, and its commit
runs for the namespaces the input holds, after the form's action inside one
transaction.

- SliceSet extracted from ComposedDraftResource and shared by both composers.
- FormSlices service: rules() under `sometimes`, values() for seeding,
  commit(), and save() wrapping an action and the commit in a transaction.
- ValidatesFormSlices request trait merges slice rules into the six create
  and thirteen settings requests, keeps slice keys out of validated(), and
  hands them over through sliceInput().
- Create controllers pass formSliceValues for a fresh record; the shared prop
  now resolves slices for any model, not only draftable ones.
- usePanelForm wraps Inertia's useForm and provides a SliceForm with
  claim-on-bind; every create page and settings edit page swaps to it.
- ProductAttributeSlice tolerates an unsaved product.
- The example add-on's loyalty card also sits on the customer create page,
  with an end-to-end test through the plain store route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

medium needs-triage Base branch is not a recognised release line

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant