php artisan boost:add-skill wm/wm-package
Save this content to: .cursor/rules/nova.md
# Trappole: Nova
Si applica quando tocchi Resource, campi, action o card Nova del package.
- Sui campi Media si usa **sempre `->singleMediaRules()`, mai `->rules()`**: quest'ultimo valida
l'intero array della collection e blocca **ogni** salvataggio del form, anche senza toccare il
campo (oc:8247).
- `Images::make()` (Ebess) impone `['image']` non sovrascrivibile, e con Laravel 12 quella regola
esclude gli SVG: per un SVG serve `Files::make()` (oc:8272).
- Nel callback `->store()` di un campo `File`, `return null` **distrugge il valore esistente** a
ogni update senza file: si torna `true` per non toccarlo. E il callback gira prima del `save()`,
quindi in creazione `$model->id` è `null` (oc:8175).
- `Multiselect::options()` accetta **un solo argomento**: il secondo è ignorato in silenzio
(oc:7953).
- Il titolo di una Resource **non può essere una colonna enum** (500 in pagina): serve un metodo
`title()`. E i modelli devono dichiarare nullable anche le colonne obbligatorie, perché Nova
costruisce i campi su un'istanza vuota (oc:8489).
- Una classe Tailwind che Nova non usa nella propria UI **viene eliminata dal purge e non ha alcun
effetto**: si usano solo classi verificate nel CSS di Nova, o CSS scoped (oc:7546).
- `<Teleport to="body">` è obbligatorio per un modale dentro i tab di Nova, altrimenti è
disallineato (oc:7546).
- Il toast 422 di Nova mostra sempre una stringa fissa: un messaggio di validazione custom non è
mostrabile senza patchare il vendor (oc:8247).
- I link costruiti a mano in un field usano `Nova::path()`: un path hardcoded perde il prefisso
`/nova` (oc:8089).
- **Il package registra una sola policy** (`App`). Un consumer che monta la Resource `EcTrack`
senza registrare `EcTrackPolicy` lascia Nova autorizzare chiunque — verificalo quando aggiungi
una Resource EC a un consumer (oc:8181).
- Nei campi di un'Action, `$request->resourceId` è vuoto nella richiesta che risolve un
`dependsOn()`: la tendina dipendente resta vuota — Nova manda `resources`, una lista di id,
nella `PATCH` gestita da `ActionRequest`, quindi vanno letti entrambi (oc:8569).
- Un'Action istanziata a mano in un test ha `runCallback` nullo e risulta **sempre**
autorizzata: il `canRun()` va preso dall'istanza che la Resource restituisce, altrimenti il
test non verifica nulla (oc:8569).
- `->sole()` vincola un'azione a una singola risorsa **solo lato UI Nova**: il server
(`DispatchAction::forModels()`) non lo applica, e una richiesta con più risorse selezionate
arriva comunque a `handle()` con più elementi in `$models` — un'azione che assume
`$models->first()` processa solo il primo mostrando comunque successo per tutti. `->standalone()`
ha un significato opposto (azione eseguibile senza nessun modello selezionato): non sono
intercambiabili nonostante il nome suggerisca il contrario (oc:8543).
- In una Nova Action, `Bus::dispatch()`/`Job::dispatch()` "nudo" può far leggere a un worker un
dato non ancora committato: Nova avvolge `handle()` in una transazione DB, e le connessioni di
coda hanno `after_commit => false` in `config/queue.php` — un dispatch senza `->afterCommit()`
pusha il job in coda prima del commit (oc:8543).
- `Bus::chain($jobs)->dispatch()->afterCommit()` fallisce (`Call to a member function afterCommit()
on null`): a differenza di `Job::dispatch()`, `PendingChain::dispatch()` non è fluente e non ha
un metodo `afterCommit()` proprio — dispatcha subito e ritorna `null`. Solo il primo job di una
catena viene effettivamente accodato (gli altri partono in base al suo esito), quindi va marcato
lui: `$chain[0]->afterCommit(); Bus::chain($chain)->dispatch();` (oc:8543).
- I Field dentro un `Tab` finiscono anche sull'index se non hanno restrizioni di visibilità: il tab
DEM porta sull'index `round_trip` e le durate bici/escursionismo. Una Resource che usa
`getDemTabFields()` dichiara `fieldsForIndex()` (oc:8571).
- `Boolean::resolveDefaultValue()` restituisce il default solo in una richiesta di Action o di
creazione: in un test su `fields()` di un'Action serve un `ActionRequest` reale, altrimenti torna
`null` (oc:8543).
- L'`help()` di un campo è reso con `v-html`: un valore letto dal DB va passato da `e()`, altrimenti
il markup arriva nel browser dell'utente (oc:8543).
- Il `MultiSelect` nativo di Nova è una `<select multiple>` del browser (si sceglie una voce sola
solo con un clic senza modificatori, le altre con ⌘/Ctrl): per le select multiple si usa il
`Multiselect` di Outl1ne, come nel resto del package (oc:8588).
- `Multiselect` di Outl1ne su un attributo `properties->chiave` salva una **stringa** JSON dentro
`properties` se non si chiama `->saveAsJSON()`: decide guardando il cast della chiave con la
freccia, che non esiste (oc:8588).
- Dopo un'Action Nova **ricrea** il dettaglio: `getResource()` in
`vendor/laravel/nova/resources/js/views/Detail.vue` azzera `panels`, i campi si smontano e si
rimontano. Lo stato di un componente — la vista di una mappa, dati caricati — si perde: ciò che
deve sopravvivere va tenuto fuori dal componente (oc:8662).
- Un campo che tiene altri campi come oggetti nel proprio meta (come `FlexibleTranslatable` con i
sotto-campi per lingua), messo in un layout Flexible, condivide quegli oggetti con gli altri
gruppi e con il template del layout (`Layout::cloneField()` fa un clone superficiale): nell'edit
i valori arrivano vuoti, perché la risposta di Nova serializza il campo due volte (`fields` e
`panels`) dopo il `resolve(true)` del template, oppure presi da un altro gruppo — i sotto-campi
vanno serializzati subito nel `jsonSerialize()` del campo (oc:8675).
- **Nova traduce da sé `$confirmButtonText`, `$cancelButtonText` e `$confirmText` delle Action**
(`Nova::__()`): una stringa italiana lì è una chiave di traduzione italiana, anche senza `__()`.
`Filter::name()` invece non traduce `$name`: per un nome traducibile si ridefinisce `name()` con
`__()` (oc:8672).
Si applica quando tocchi Resource, campi, action o card Nova del package.
->singleMediaRules(), mai ->rules(): quest'ultimo valida
l'intero array della collection e blocca ogni salvataggio del form, anche senza toccare il
campo (oc:8247).Images::make() (Ebess) impone ['image'] non sovrascrivibile, e con Laravel 12 quella regola
esclude gli SVG: per un SVG serve Files::make() (oc:8272).->store() di un campo File, return null distrugge il valore esistente a
ogni update senza file: si torna true per non toccarlo. E il callback gira prima del save(),
quindi in creazione $model->id è null (oc:8175).Multiselect::options() accetta un solo argomento: il secondo è ignorato in silenzio
(oc:7953).title(). E i modelli devono dichiarare nullable anche le colonne obbligatorie, perché Nova
costruisce i campi su un'istanza vuota (oc:8489).<Teleport to="body"> è obbligatorio per un modale dentro i tab di Nova, altrimenti è
disallineato (oc:7546).Nova::path(): un path hardcoded perde il prefisso
/nova (oc:8089).App). Un consumer che monta la Resource EcTrack
senza registrare EcTrackPolicy lascia Nova autorizzare chiunque — verificalo quando aggiungi
una Resource EC a un consumer (oc:8181).$request->resourceId è vuoto nella richiesta che risolve un
dependsOn(): la tendina dipendente resta vuota — Nova manda resources, una lista di id,
nella PATCH gestita da ActionRequest, quindi vanno letti entrambi (oc:8569).runCallback nullo e risulta sempre
autorizzata: il canRun() va preso dall'istanza che la Resource restituisce, altrimenti il
test non verifica nulla (oc:8569).->sole() vincola un'azione a una singola risorsa solo lato UI Nova: il server
(DispatchAction::forModels()) non lo applica, e una richiesta con più risorse selezionate
arriva comunque a handle() con più elementi in $models — un'azione che assume
$models->first() processa solo il primo mostrando comunque successo per tutti. ->standalone()
ha un significato opposto (azione eseguibile senza nessun modello selezionato): non sono
intercambiabili nonostante il nome suggerisca il contrario (oc:8543).Bus::dispatch()/Job::dispatch() "nudo" può far leggere a un worker un
dato non ancora committato: Nova avvolge handle() in una transazione DB, e le connessioni di
coda hanno after_commit => false in config/queue.php — un dispatch senza ->afterCommit()
pusha il job in coda prima del commit (oc:8543).Bus::chain($jobs)->dispatch()->afterCommit() fallisce (Call to a member function afterCommit() on null): a differenza di Job::dispatch(), PendingChain::dispatch() non è fluente e non ha
un metodo afterCommit() proprio — dispatcha subito e ritorna null. Solo il primo job di una
catena viene effettivamente accodato (gli altri partono in base al suo esito), quindi va marcato
lui: $chain[0]->afterCommit(); Bus::chain($chain)->dispatch(); (oc:8543).Tab finiscono anche sull'index se non hanno restrizioni di visibilità: il tab
DEM porta sull'index round_trip e le durate bici/escursionismo. Una Resource che usa
getDemTabFields() dichiara fieldsForIndex() (oc:8571).Boolean::resolveDefaultValue() restituisce il default solo in una richiesta di Action o di
creazione: in un test su fields() di un'Action serve un ActionRequest reale, altrimenti torna
null (oc:8543).help() di un campo è reso con v-html: un valore letto dal DB va passato da e(), altrimenti
il markup arriva nel browser dell'utente (oc:8543).MultiSelect nativo di Nova è una <select multiple> del browser (si sceglie una voce sola
solo con un clic senza modificatori, le altre con ⌘/Ctrl): per le select multiple si usa il
Multiselect di Outl1ne, come nel resto del package (oc:8588).Multiselect di Outl1ne su un attributo properties->chiave salva una stringa JSON dentro
properties se non si chiama ->saveAsJSON(): decide guardando il cast della chiave con la
freccia, che non esiste (oc:8588).getResource() in
vendor/laravel/nova/resources/js/views/Detail.vue azzera panels, i campi si smontano e si
rimontano. Lo stato di un componente — la vista di una mappa, dati caricati — si perde: ciò che
deve sopravvivere va tenuto fuori dal componente (oc:8662).FlexibleTranslatable con i
sotto-campi per lingua), messo in un layout Flexible, condivide quegli oggetti con gli altri
gruppi e con il template del layout (Layout::cloneField() fa un clone superficiale): nell'edit
i valori arrivano vuoti, perché la risposta di Nova serializza il campo due volte (fields e
panels) dopo il resolve(true) del template, oppure presi da un altro gruppo — i sotto-campi
vanno serializzati subito nel jsonSerialize() del campo (oc:8675).$confirmButtonText, $cancelButtonText e $confirmText delle Action
(Nova::__()): una stringa italiana lì è una chiave di traduzione italiana, anche senza __().
Filter::name() invece non traduce $name: per un nome traducibile si ridefinisce name() con
__() (oc:8672).How can I help you explore Laravel packages today?