npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@woodylab/payload

v0.0.222

Published

UIMS — User Interface Management System per Payload CMS 3 + Next.js: separa il contenuto dalla presentazione (ViewMode), temi/palette/stili configurabili da admin.

Readme

@woodylab/payload

UIMS — User Interface Management System per Payload CMS 3 + Next.js (App Router).

Il come si vede l'interfaccia (ViewMode, stili, temi, variabili) è gestito nello Studio e persistito in file db.*.json versionati; il contenuto e il dato (pagine, chrome, menu, relazioni) restano in Payload. Il pacchetto risolve il tutto a runtime in componenti React dumb (senza fetch né business logic) che renderizzano solo il descrittore prodotto dal motore.

Cos'è UIMS

Separa il contenuto (i dati) dal come viene mostrato (la presentazione). Il "come" è il ViewMode: lega un dato a un componente React, a classi Tailwind, a un tag HTML e a degli slot (content-projection).

Principi:

  • Colori sempre semantici: i ViewMode usano token (bg-foreground, text-primary, bg-secondary, text-accent, border-secondary…), il cui valore viene dalla palette del tema attivo. Mai classi-colore raw.
  • Un tema = un insieme di variabili + stili: la base (db.base-vars.json) contiene la totalità delle variabili modificabili (colori light/dark, font, scala/tipografia, motion, componenti); un tema (db.themes.json) è un override della stessa lista. Il tema attivo è la costante ACTIVE_THEME (code-owned). Un tema ridefinisce solo ciò che vuole diverso; il resto eredita dalla base.
  • Fonte di verità = gli store dello Studio (i db.*.json, versionati). Si editano dallo Studio (/admin/studio), non dal pannello Payload. In dev la scrittura è live (fs); in prod sono read-only (si aggiornano via commit + redeploy).

Pipeline: DatoViewMode (da db.json, risolto per NOME nel tema attivo→uims fallback) → contesto UIMS (registry, dati iniettati) → componenterenderer. Le classi le genera Tailwind via @source sugli store (db.json/db.styles.json) — niente safelist né validazione.

Lo STUDIO — /admin/studio

La superficie di gestione di tutta la forma dell'interfaccia. È una admin-view Payload custom (gated dall'auth admin), registrata zero-config dal plugin, con UI propria (fuori dalla chrome Payload). Gestisce quattro entità (una rotta per lista, raggruppamento e dettaglio client-side) + i font:

| Tab | Store | Cosa gestisci | | --- | --- | --- | | ViewModes | db.json | raggruppati per component. Per ognuno: component, tag, className, styles[] (riferimenti per nome), slots e themeStyles (override per-tema strutturato: className/styles/slots per ogni tema a sistema). | | Styles | db.styles.json | raggruppati per categoria. { name, categoria, css?, classes? }. | | Themes | db.themes.json | i temi (palette con toggle light/dark, font con anteprima live, vars di override). | | Base Vars | db.base-vars.json | la base completa: palette (light/dark) + font + scala/tipografia + motion + componenti; il tema le sovrascrive. | | Fonts | — (endpoint) | lista Google Fonts live (/api/studio/fonts): Webfonts API se GOOGLE_FONTS_API_KEY, altrimenti il metadata endpoint pubblico; cachata, con fallback. Selettore con anteprima. |

CRUD completo (create/update via Salva, Elimina per viewMode/style/tema). Gli endpoint /api/studio/* sono scaffoldati dall'init e gated (ruoli ADMIN/WEBADMIN/DESIGNER); la logica sta in studioRoute(entity, { config, data }) del pacchetto.

L'ADMINBAR — l'editor IN PAGINA

Lo Studio gestisce la forma (viewMode, stili, temi) dall'area admin. L'AdminBar gestisce le istanze — questa sezione, questo testo, questa classe — stando sulla pagina, con la pagina come anteprima. Si vede solo a utente autenticato: per un visitatore non rende nulla di proprio e il markup resta identico a com'era senza di lei.

Tre slot, e la loro composizione è un GLOBAL (AdminBar, gruppo UIMS in Payload):

| Slot | Default | Cosa ci puoi mettere | | --- | --- | --- | | trigger | la matita (in basso a sinistra) | qualunque blocco: di norma un action che dichiara admin:toggle nei suoi uiState.events | | bar | l'albero degli elementi della pagina | adminSectionTree, o i tuoi blocchi | | sectionDrawer | la gestione dell'elemento selezionato | adminSection, o i tuoi blocchi |

Slot vuoto = default: finchè non componi niente, la barra funziona come descritto qui. Comporre è opt-in e si fa solo da Payload — la barra non edita sè stessa, o sarebbe l'unico modo di rendersi inutilizzabile senza avere più l'interfaccia per rimediare.

Il form viene dallo SCHEMA, non dal dato: i campi si leggono dalla definizione Payload del blocco (tab Section · Container · Content, e dentro ognuno UI · UIManager), quindi compare anche ciò che non è mai stato valorizzato. Cliccando un elemento nella pagina il pannello apre da solo la catena di tab e evidenzia la card giusta.

Mentre modifichi, la pagina riflette (testo e classi d'istanza, a qualunque profondità): non è editing in-line — il campo sta nel drawer — ed è reversibile: Annulla o chiusura riportano tutto com'era. Cio' che cambia il componente (viewMode, struttura) si vede al salvataggio, quando è il server a renderlo.

Salvare è un evento: il bottone chiede admin:save, chi ha i canali scrive, e l'esito torna come admin:saved / admin:saveFailed; sul successo la pagina si rigenera dal server.

⚠️ admin: è un namespace RISERVATO. Gli eventi admin:* vengono inoltrati dal kernel del sito allo store dell'editor: se chiami così un tuo evento, piloti l'AdminBar senza volerlo. Per i tuoi usa un namespace tuo (shop:, app:, il nome del progetto).

⚠️ I viewMode del tema admin devono puntare a componenti CLIENT. Gli slot dell'editor si rendono nel browser: un componente server in quel sottoalbero non attraversa il confine e lo slot arriva vuoto, in silenzio. Per questo il pacchetto fornisce i gemelli client (AdminSlot, AdminText, AdminAction, AdminSectionTree, AdminSection).

Cosa scaffolda l'init (tutto cablato, niente da montare a mano):

| File | Ruolo | | --- | --- | | src/adminBridge.tsx | come si legge e si scrive il dato (quale entità, quale rotta): è tuo, non del pacchetto | | src/uimsAdminContext.ts | il contesto UIMS del tema admin (stesso motore, altro tema) + resolveAdminSlots | | src/app/api/uims/schemas/route.ts | la forma dei blocchi per il form (~190 KB, chiesti una volta; auth-gated) | | layout.tsx | <UIMSEventsWrapper> sopra <AdminBridge> — l'ordine conta: gli eventi dei blocchi admin passano dal kernel |

Cosa NON fa (ancora): aggiungere o togliere blocchi figlio; modificare richText, immagini e relazioni (restano in Payload); header/footer (si modificano dai rispettivi global); i campi condizionali si vedono sempre (la condition di Payload è una funzione, e non sopravvive al descrittore).

Stato iniziale (dopo init + primo build) — LEGGERE prima di sviluppare

Gli store db.*.json li crea npx woodylab-payload init (a scaffold-time, dai default del pacchetto: sono import statici, devono esistere prima del compile). Il contenuto/chrome lo seedi tu nel tuo onInit chiamando i seeder di @woodylab/payload/seeders (il plugin non seeda nulla). Quindi NON parti da un progetto vuoto: parti da un sito già funzionante e stilato col tema uims. Trovi già pronto:

  • ViewMode del tema uims: completi — ogni componente (Card, Section, Container, Header, Footer, Figure, Button*, Accordion, Swiper, …) ha già className/styles/slots. Non vanno ricreati né ri-stilati da zero: si editano dallo Studio.
  • Tema uims attivo (ACTIVE_THEME): palette/font in db.base-vars.json + db.themes.json; stili riusabili in db.styles.json.
  • Chrome (header/footer con siteName + logo), menu, eventi/effetti baseline — questi restano in Payload (contenuto/dominio).
  • Pagine demo + la superficie protetta /woodylab (hub + live-preview dei ViewMode).

Regola CARDINALE: editi dallo Studio, non ricrei da zero

La forma (viewMode/stili/tema/variabili) si modifica nello Studio; il codice (i default del pacchetto) è la base da cui gli store nascono. Quando sviluppi:

| Vuoi… | Fai | NON fare | | --- | --- | --- | | Cambiare l'aspetto di un componente | Edita il suo ViewMode nello Studio (o aggiungi un override per-tema in themeStyles) | ri-stilare da zero / duplicare | | Cambiare colori/font/scala del tema | Base Vars (default condivisi) o Themes (override del tema) nello Studio | hardcodare valori nei ViewMode | | Aggiungere una pagina | Componi blocchi che referenziano ViewMode esistenti per nome (in Payload Pages) | inventare stili nuovi inline | | Aggiungere un componente/variante tua | uiComponents (component custom) + domainViewModesEstendere | toccare il pacchetto o i ViewMode uims | | Dati di dominio | Tua collection + blocco views/mappedReference | hardcodare dati nei ViewMode |

Un ViewMode/blocco si visualizza risolvendo il suo ViewMode (UIMS.resolve), non ridisegnandolo: se un componente "sembra vuoto", il problema è il contenuto/slot o il ViewMode giusto per nome, non uno stile mancante.

Stili riusabili — db.styles.json

Gli stili nominati (design system: shadow, bgConic, borderPrimary, …) vivono in db.styles.json (tab Styles), richiamati dai ViewMode per NOME (campo styles, o token us-<nome> in una className). Ogni stile è { name, categoria, css?, classes? } con due nature:

  • classes → utility Tailwind (es. rounded-2xl border shadow-lg): generate da Tailwind che fa @source su db.styles.json; il resolver le espande nel className del ViewMode/slot/istanza.
  • css → CSS grezzo (es. gradienti con var(--color-*)): genera l'utility .us-<name> emessa a runtime da generateCssVars. Usa i var(--color-*) → si ri-stila da sé al cambio tema.

Responsabilità: MOTORE (package) vs STUDIO vs PROGETTO (consumer)

| Ambito | Package @woodylab/payload | Studio (db.*.json) | Consumer (il tuo progetto) | | --- | --- | --- | --- | | ViewMode / stili / temi / base-vars | ✅ default (seed if-missing) | ✅ fonte di verità (editi qui) | scegli ACTIVE_THEME; estendi via domainViewModes | | Rendering del tema | ✅ helper @woodylab/payload/theme | i valori | il layout consuma l'helper (@/uimsTheme) | | Contenuto Payload: pagine, chrome, menu, eventi, effetti, relazioni | ✅ engine + seed baseline | — | pagine e dati tuoi | | Rotte Next (layout / pagine / preview / studio) | scaffold (init) | — | ne possiedi i file | | Infrastruttura: DB, storage media, hosting, env, ACL | — | — | ✅ tua |

Esempio: comporre una pagina

Costruire una pagina significa comporre blocchi, ognuno associato a un ViewMode esistente scelto da un menu a tendina (in Payload Pages). Non si scrive CSS: lo stile lo porta il ViewMode.

[
  {
    "blockType": "sections",
    "section":   { "viewMode": "Section",   "content": [] },
    "container": { "viewMode": "Container", "content": [] },
    "content": [
      {
        "blockType": "content",           // resolver: contenitore
        "viewMode": "Card",               // aspetto: già stilato per il tema attivo
        "slot": "content",                // dove va nel Container
        "content": [
          { "blockType": "text", "viewMode": "Text", "slot": "header",  "text": "Chi siamo" },
          { "blockType": "text", "viewMode": "Text", "slot": "content", "text": "Studio di design con base a Milano." },
          {
            "blockType": "action",
            "viewMode": "ButtonPill",
            "slot": "footer",
            "action": { "actionType": "link", "actionTarget": "/contatti", "target": "_self" },
            "content": [ { "blockType": "text", "viewMode": "ButtonLabel", "text": "Contattaci" } ]
          }
        ]
      }
    ]
  }
]

Ogni blocco porta blockType (quale resolver lo interpreta), viewMode (quale aspetto, per nome), slot (in quale fessura del genitore va) e il contenuto. Il motore risolve ogni viewMode per nome → componente + classi + slot; il renderer disegna l'albero. In tutto il JSON non compare una classe CSS: cambiare aspetto = scegliere un altro ViewMode. Lo stesso contenuto, cambiando ACTIVE_THEME (o editando un tema nello Studio), si ri-stila da solo.

Requisiti

Pacchetto ESM-only, subpath-only (nessun export root ., per separare codice server e client RSC).

| Peer dependency | Versione | | --- | --- | | payload | ^3.54.0 | | @payloadcms/plugin-form-builder · @payloadcms/richtext-lexical · @payloadcms/live-preview-react | ^3.54.0 | | next | ^15.2.3 | | react / react-dom | ^19 |

Installazione

npm install @woodylab/payload
npx woodylab-payload init   # scaffold: rotte Next + glue consumer (uimsContext, uimsTheme, studio, styles.css)

init copia i template una volta (salta i file esistenti, --force per sovrascrivere). Se non basta, i file da creare a mano sono in Rotte da costruire.

Entrypoint

| Subpath | Contenuto | Lato | | --- | --- | --- | | @woodylab/payload/plugin | uimsPlugin(options) — collection + global + form-builder + onInit/seed + admin-view Studio | Server | | @woodylab/payload/config | Collections, globals, blocks (factory makeBlockX), fields, hooks | Server | | @woodylab/payload/uims | createUIMSContext(…, viewModesDb, stylesDb, activeTheme), uimsResolvers, resolver, mapper | Server | | @woodylab/payload/theme | buildActiveTheme({ baseVars, themes, styles, activeTheme }){ active, varsCss, stylesCss, themeVarsMap } + buildThemeVars | Server | | @woodylab/payload/react | Componenti, uimsComponents, LayoutRenderer, BlockRenderer, provider eventi | Client | | @woodylab/payload/utils | getCachedGlobal, iconsList, generateCssVars/generateStyleRulesserver-only (tira next/cache) | Server | | @woodylab/payload/utils/generateCssVars | solo le funzioni PURE (generateCssVars, generateStyleRules): importabile anche da un componente client | Client-safe | | @woodylab/payload/studio/next · /server · /adminView · /StudioNavLink | studioRoute(entity, opts), handler, la admin-view | Server/Client | | @woodylab/payload/admin | AdminBar (editor in pagina): AdminBar e i suoi pezzi (AdminSectionTree, AdminSection), store ed eventi (ADMIN_EVENTS), describeBlocks (schema dei blocchi), applyDraft | Client | | @woodylab/payload/migrations | trasformazioni una tantum dei dati già salvati (pure, dato -> dato: a leggere e scrivere ci pensa un tuo script) | Server | | @woodylab/payload/seeders | seeder di CONTENUTO da chiamare nel tuo onInit (seedSiteChrome, seedEvents, seedEffects, seedDocPages…) + i tipi ViewModeSeed/EffectSeed | Server | | @woodylab/payload/themes | registro temi del pacchetto (themes, themeByName) | Server | | @woodylab/payload/uims-viewModes · /uims-styles · /uims-effects · /uims-base-vars · /uims-theme-palettes | i cataloghi BASELINE da cui init genera gli store dello Studio | Server | | @woodylab/payload/dal · /icons | accesso dati cachato · catalogo icone | Server |

I percorsi dist/... sono incapsulati da exports: non usarli.

Setup (5 passi)

  1. Plugin in payload.config.ts (il tema vive nello Studio, non in Payload — nessuna opzione da passare):

    import { uimsPlugin } from '@woodylab/payload/plugin'
    export default buildConfig({
      collections: [Users],
      plugins: [uimsPlugin({ collections: { pages: true, media: true } })],
    })

    Opzioni: collections:{pages?,media?} · viewModeNames · domainViewModes · themes · activeTheme (inerte) · relationTargets · effects · eventHandlers · siteName · formBuilder:false · disabled.

    Payload non gestisce il tema: viewMode, stili, temi e variabili vivono negli store dello Studio (db.*.json). Nel pacchetto non esistono collection view-modes/styles né global theme/theme-vars.

  2. Env: DATABASE_URI (Mongo), PAYLOAD_SECRET (≥32 char), NEXT_PUBLIC_SERVER_URL (o su Vercel VERCEL_PROJECT_PRODUCTION_URL). Opzionale: GOOGLE_FONTS_API_KEY (fonte ufficiale font; senza, funziona il metadata endpoint pubblico).

  3. Tema attivo & renderingACTIVE_THEME in src/themes.ts (un tema per progetto, code-owned: si cambia da codice/Studio); il layout consuma @/uimsTheme (thin wrapper su buildActiveTheme): importa i db.*.json e ne ricava activeVarsCss() (var del tema attivo) e stylesCss() (regole .us-*).

  4. Stili — in styles.css (Tailwind v4): @import "tailwindcss" + @source verso gli store dello Studio (generati da init), così Tailwind genera esattamente le classi in uso (minimale in prod, niente safelist né validazione):

    @source "../../../db.json";
    @source "../../../db.styles.json";

    Le var del tema e le .us-* sono iniettate in layout.tsx da @/uimsTheme in un <style id="uims-theme-vars">.

  5. Seed & deploy — il seed gira a BUILD-TIME, poi è no-op a runtime (cold-start snelli):

    • onInit (consumer) e seed del plugin sono gated dietro RUN_SEED: a runtime (env assente) no-op.
    • script build-seed + build = build:seed && next build: il seed crea/allinea gli db.*.json e il contenuto una volta; se fallisce, && aborta → prod resta sul deploy buono.
    • ⚠️ su Vercel RUN_SEED solo nello step di build, MAI a runtime.
    • primo npm run dev (imposta RUN_SEED) → seed → login su /admin = sito pronto col tema uims; gestisci la forma su /admin/studio.

In produzione (checklist cardinale)

  1. PluginuimsPlugin({ collections:{pages:true} }). (package: engine + seed contenuto; il tema è negli store.)
  2. Env di runtimeDATABASE_URI, PAYLOAD_SECRET (≥32), URL pubblico. ⚠️ RUN_SEED MAI a runtime. (consumer.)
  3. DB — istanza tua (es. Mongo Atlas) + Network Access che ammetta il provider. (consumer.)
  4. Build = seed + next build — il seed gira una volta a build (crea gli db.*.json se assenti, allinea il contenuto). (package fornisce lo script; tu lo colleghi.)
  5. Store del tema — i db.*.json sono versionati: in prod sono read-only, si aggiornano editando nello Studio in dev e ri-deployando (commit → build). (consumer.)
  6. Storage media — disco effimero → adapter S3/Blob; dominio media nei remotePatterns di next.config. (consumer.)
  7. Cache / ISR — gestita dal pacchetto (pagine statiche, invalidazione on-change via hook). (package.)
  8. Sicurezza / ACL — restringi ciò che è permissivo (allowlist DB, ruoli di /woodylab e dello Studio). (consumer.)

Rotte da costruire (se init non basta)

Un plugin Payload non può iniettare rotte Next: init le copia una volta. Idea comune: getPayloadUIMS.resolve(block) → renderer.

Glue (una volta)

  • src/uimsContext.tscreateUIMSContext(payload, uiComponents, resolvers, viewModesDb, stylesDb, ACTIVE_THEME). Inietta db.json (viewMode) e db.styles.json (stili) come moduli (bundlati, zero fs nel render). Punto d'aggancio per componenti/resolver custom.
  • src/uimsTheme.ts — thin wrapper su buildActiveTheme (importa db.base-vars.json / db.themes.json / db.styles.json + ACTIVE_THEME); espone activeVarsCss/stylesCss/themeVarsMap.
  • src/uimsEventsWrapper.tsx, src/app-events.ts, src/utilities/deployId.ts.

app/(frontend)/layout.tsx — legge header/footer (CACHATI: unstable_cache + DEPLOY_ID + tag global_<slug>); emette `${activeVarsCss()}\n${stylesCss()}` in <style id="uims-theme-vars">; risolve header/footer con UIMS.resolve + BlockRenderer; avvolge children in UIMSEventsWrapper. Non legge più global theme/theme-vars.

app/(frontend)/page.tsx e [slug]/page.tsxISR on-demand (niente force-dynamic/revalidate a tempo): pagine statiche, rigenerate solo al cambio di un oggetto in pagina (query cachata, tag pages_<slug>); layout via LayoutRenderer, meta via generateMeta. Invalidazione dagli hook del pacchetto.

app/(protected)/ — gate unico sul layout (payload.auth → ruoli fieldRoles). Ospita /woodylab (hub) e /woodylab/preview/[slug] (anteprima di un ViewMode per nome, dal registry iniettato — non da collection). Base grafica dagli store del tema (via @/uimsTheme).

app/api/studio/*/route.ts — le 5 rotte dello Studio (viewmodes/styles/themes/base-vars/fonts), thin: export const { GET, PUT, DELETE } = studioRoute('<entity>', { config, data }) (dove data = lo store importato come modulo; fonts è GET-only, no store).

app/(frontend)/styles.css — Tailwind v4: @import "tailwindcss" + @source "../../../db.json" / "../../../db.styles.json" + @custom-variant dark + @theme (mappa i token) + @import CSS terze parti.

Tema & variabili (base-vars ⊕ themes)

  • db.base-vars.json = la base completa di tutte le variabili modificabili, mappa flat: colori (primaryColor/primaryColorDark/…), font (headingFont/textFont), scala (radius*/text*/leading*/tracking*/spacing), motion (duration*/ease*), componenti (swiperThemeColor/accordion*).
  • db.themes.json = i temi; ognuno ha palette + fonts + vars = override della base (stessa lista di chiavi). A render: buildThemeVars fonde base ⊕ palette ⊕ fonts ⊕ varsil tema vince. Il file base non viene toccato dallo switch: è una fusione a strati.
  • Tema attivo = ACTIVE_THEME (costante, un tema per progetto): scelta code-owned (si cambia da codice/Studio). A render il layout emette le var del tema attivo nel <head>; non c'è switch di tema esposto al client.
  • className per-istanza: ogni blocco compositivo ha, accanto al viewMode, un campo className fuso (twMerge) col ViewMode → l'istanza vince. Ammette Tailwind (generate via @source sugli store), valori arbitrari [...] e i token us-<nome> (riferimento a uno stile per nome → espanso a render).

Estendere (consumer-side)

Punti d'aggancio: uimsContext.ts (componenti + resolver), app-events.ts (eventi), l'onInit del consumer (preservato dal plugin).

Componente

import { UIMSPrimitive } from '@woodylab/payload/react'
export const Badge = ({ ui, data, ...rest }: any) =>
  <UIMSPrimitive ui={ui} {...rest}>{data?.text ?? 'Badge'}</UIMSPrimitive>
// uimsContext.ts:  const uiComponents = { ...uimsComponents, Badge }

Regola: se renderizza <Slot> (nodi server) non marcarlo 'use client'.

Resolver (logica a resolve-time; chiave = blockType)

import { resolveViewMode } from '@woodylab/payload/uims'
export async function greetingResolver(block, context) {
  const r = await resolveViewMode(block, context)
  r.data = { ...(r.data || {}), text: `Ciao ${block?.name || 'mondo'}!` }
  return r
}
// uimsContext.ts:  const resolvers = { greeting: greetingResolver }

Evento (kernel) — app-events.ts: export const customHandlers = { 'cart:add': async (e) => ({ status:'ok' }) }. Dispatch: useUIMSEvents().dispatch({ type:'cart:add', payload }); da admin: Action actionType:event.

ViewMode di dominio — passali al plugin (theme consumer), seedati nello store col resto:

const CardPromo = { name:'CardPromo', component:'Card', className:'rounded-xl bg-info p-6',
  styles:['borderPrimary'], slots:[{ name:'content', className:'p-4' }] }
uimsPlugin({ domainViewModes: [CardPromo] })

Relazioni

Blocchi + resolver dedicati: reference (entità-package in un layout), views/mappedReference (liste mappate negli slot), pathBinding (ricorsivo), navigation/sectionRef. Legame dato→campo via dataPathValue ({ "prop": "path.nel.record" }); il record arriva in data.

  • Dati di DOMINIO: mappedReference mappa un record di una tua collection → dichiarala con relationTargets: ['articles', …]. Per listare una collection usa il blocco views.
  • Il wiring viewMode è per NOME (campo text): i resolver risolvono gli slot del wrapper dal registry (db.json), non da relazioni.

Entità nel pannello Payload

Payload gestisce il contenuto/dominio, non il tema (le collection/global del tema non esistono nel pacchetto):

  • Collection: blocks, sections, views, menus, events, animations, pages (+ forms/form-submissions). Global: settings, header, footer.
  • NON più in Payload: view-modes/styles (collection), theme/theme-vars (global) → vivono nello Studio (db.*.json).

Note

  • Studio = DEV-ONLY: in produzione la admin-view /admin/studio non è registrata e gli endpoint /api/studio/* rispondono 404 (tutti i metodi). Si edita in dev e si versionano i db.*.json → commit + redeploy.
  • Superficie protetta /woodylab: hub + anteprima ViewMode a /woodylab/preview/<nome> (gate su ruoli fieldRoles). Il Playground non fa parte del comportamento base ed è escluso dallo scaffold.
  • Estensibilità: il consumer estende UIMS a livello di tema (ACTIVE_THEME + Studio), plugin (opzioni), componenti/resolver/eventi (uimsContext/app-events), ViewMode di dominio (domainViewModes) e collection referenziabili (relationTargets).