Guida Architettura Frontend
Ultimo aggiornamento: 2026-04-27
Obiettivo
Definire lo standard operativo del frontend modulare basato su AppBootstrap, con priorita su gameplay (app/views/app) e view leggere.
Principi
- componente shared per comportamento condiviso
- feature/module per logica di dominio
- doppia validazione: client + backend
- fallback espliciti solo dove necessari
- nessuna dipendenza da alias globali legacy
Architettura runtime
Core
assets/js/app/core/Context.jsassets/js/app/core/ModuleRegistry.jsassets/js/app/core/AppBootstrap.jsassets/js/app/core/RuntimeBootstrap.jsassets/js/app/core/game.*.jsassets/js/app/core/system.dialogs.js
Layer applicativo
assets/js/app/features/game/*:
controller/factory di pagina (Game*Page).
assets/js/app/modules/game/*:
servizi modulo per list/create/update/delete e operazioni specializzate.
assets/js/app/App.js:
facade minima (get, mount, call).
Contratti tecnici
Facade
App().get(name)App().mount(name, options)App().call(name, method, payload)
Runtime API pubblica
RuntimeBootstrap.boot(config)RuntimeBootstrap.start(config)RuntimeBootstrap.stop(config)RuntimeBootstrap.resolveAppModule(moduleName)RuntimeBootstrap.restartGameRuntime(options)
Nota: metodi interni non necessari non vengono esposti come API pubblica.
Moduli
Ogni modulo registrato deve esporre API coerente:
list(payload)create(payload)update(payload)delete(payload)
Quando necessario:
get(payload)- azioni verticali (
assign,promote,setPrimary, ecc.)
Bootstrap per pagina
Game (app/views/app)
- bootstrap da
assets/js/app/core/bootstrap.game.js - mount moduli in base a
pageKey - binder UI da
game.ui/game.page
Admin (app/views/admin)
- bootstrap da
assets/js/app/core/bootstrap.admin.js - runtime modulare operativo con registry come fonte autoritativa
Registry admin (pattern obbligatorio per nuove pagine admin)
Il routing pagina→modulo passa per admin.registry.js, non per admin.runtime.js.RuntimeBootstrap.boot() chiama applyRegistryToPageConfig() che sovrascrive la mappa modules del runtime con il risultato di AdminRegistry.getPageConfig().
Ogni nuova pagina admin richiede tre punti di registrazione:
MODULE_FACTORY_MAPinadmin.registry.js— risoluzione factory del modulogetPageModules()inadmin.registry.js— fonte autoritativa per routing pagina→modulomodulesmap inadmin.runtime.js— necessario ma secondario (sovrascritto dal registry)
Omettere il punto 2 causa mancato mount del modulo anche se il punto 3 è presente.
Regole di implementazione
- nessun JS inline nelle view
- nessun CSS inline nelle view
- script caricati da layout con ordine core -> features -> modules -> bootstrap
- filtri/datagrid in GET dove richiesto dal flusso pagina
- fallback legacy solo se documentato e temporaneo
- le view pubbliche che includono login devono ricevere
google_auth.enableddal backend per mostrare/nascondere il pulsante OAuth
Flusso login multi-personaggio
POST /signinpuo rispondere conerror_character_select.- In quel caso la UI apre la modale selezione personaggio.
- Conferma selezione via
POST /signin/character/select. - Solo il backend finalizza sessione character e redirect a
/game.
Guide complementari
- Guida contributori (moduli/pagine/funzionalita):
docs/guida-contributori.md
- UI authz dichiarativa (
data-requires-*, refresh suauthz:changed):
docs/guida-permessi-ui-attributi.md
- Riferimento componenti frontend:
docs/riferimento-componenti-frontend.md
- Usare gli attributi
data-requires-*per downgrade/refresh runtime dei permessi lato client. - Mantenere comunque il gating server-side (Twig/PHP) e l'autorizzazione backend.
Guardrail runtime
Script di controllo:
scripts/smoke-runtime-guardrails.mjsscripts/guardrails/check-runtime-deprecations.mjsscripts/guardrails/check-selectiongroup-integration.mjs
Vincoli:
- bloccare token/alias deprecati
- bloccare include a file rimossi
- mantenere integrazione
SelectionGroup/RadioGroupcoerente
Disaccoppiamento core / moduli opzionali
Il core non deve importare o richiamare direttamente codice appartenente a moduli opzionali. Il pattern corretto e basato su CustomEvent DOM neutrali:
- Il core (es.
LocationSidebarPage.js) emette eventi documentati:
location:sceneLauncher.init—{ detail: { location_id, character_id } }location:sceneLauncher.refresh—{ detail: { location_id } }location:characters.loaded—{ detail: { characters } }
- I moduli opzionali si agganciano con
document.addEventListener(...). - Il modulo opzionale non e mai referenziato nel core; puo essere aggiunto o rimosso senza toccare il core.
Questo pattern si applica ogni volta che un modulo opzionale ha bisogno di reagire a eventi di pagine core.
Asset JS dei moduli opzionali
I moduli che hanno JS (dichiarato in module.json sotto assets.admin.js / assets.game.js) vengono caricati dai layout Twig tramite {{ module_assets('admin')|raw }} e {{ module_assets('game')|raw }} — implementato da ModuleRuntime::renderAssetTags().
Il modulo si auto-registra nel proprio assets/js/index.admin.js o index.game.js:
// modules/logeon.archetypes/assets/js/index.admin.js
import './admin/ArchetypesModule.js';
if (window.AdminRegistry) {
window.AdminRegistry.registerModule('admin.archetypes', 'AdminArchetypesModuleFactory');
window.AdminRegistry.extendPage('archetypes', ['admin.archetypes']);
}
if (window.AdminFeatureLoader) {
window.AdminFeatureLoader.registerPageScripts('archetypes', [
'/modules/logeon.archetypes/dist/admin.js'
]);
}
Il build script (scripts/build/frontend-pilot.mjs) ha discoverModuleTargets() — rileva automaticamente tutti i moduli con index.admin.js / index.game.js e produce dist/admin.js / dist/game.js separati per ciascuno.
Regola: il core (admin.registry.esm.js, game.registry.esm.js, entry files) non deve contenere riferimenti a moduli opzionali. Ogni modulo gestisce la propria registrazione dal proprio index.*.js.
Bundler e moduli ESM
- Tree shaking completato (Step 0-4): tutti i nuovi file JS usano ESM (
import/export). - Step 5 completato: JS dei moduli opzionali isolato nei rispettivi
modules/<id>/assets/js/. Registry ESM puliti. Build con module discovery automatica. - Steps 6-8 (conversione IIFE→ESM dei feature files, rimozione IIFE core, ESM nativo): non eseguiti — file IIFE preesistenti mantenuti; evoluzione post-ESM completata su binari separati (decommission layout legacy, TipTap, split interfacce weather).
- Bundler: esbuild attivo.
scripts/build/window-globals-registry.json: fonte autoritativa per i simboli esposti come global. Ogni nuovo simbolo che deve essere raggiungibile dawindowva registrato qui.- Non usare IIFE ne assegnamenti
window.*nei nuovi file.
Stato consolidato
- Runtime gameplay operativo in modalita module-first.
- Runtime admin operativo con registry modulare.
- Helper legacy globali consolidati nel core.
App.jsridotto a facade runtime.- Componenti shared in fase continua di hardening, senza dipendenze nascoste.
- Tree shaking completato; bundler ESM operativo.
- Editor rich-text: Summernote rimosso;
TipTapEditoroperativo (auto-init su.summernote, .richtext-editor); bridge jQuery ($.fn.summernote) attivo per retrocompatibilita callsite esistenti.
Pattern Datagrid admin — note operative
- Usare
grid.loadData(payload, limit, page, orderBy)per pagine con filtri liberi. - Usare
grid.setFilters(filters)per pagine log. Non chiamaregrid.load()doposetFilters():setFilters()chiama giàload()internamente. Una doppia chiamata manda una seconda richiesta con criteri vuoti che può sovrascrivere il risultato filtrato.
Flusso consigliato per nuovi sviluppi
- Definire contratto modulo (API + permessi backend).
- Implementare modulo in
modules/game. - Implementare controller di pagina in
features/game. - Collegare bootstrap
pageKey -> module/factory. - Validare con guardrail + smoke manuale mirato.
- Documentare delta in
docs/riferimento-componenti-frontend.mdo doc tecnica dedicata.