Fractal Personalizzazioni Tecniche

Fractal — personalizzazioni tecniche

Note di manutenzione per chi lavora sul repository RTDS. Per una panoramica generale su cos’è Fractal e sulle personalizzazioni visibili nella UI, vedi Fractal e personalizzazioni.

I sei punti seguenti hanno in comune una caratteristica pericolosa: quasi nessuno produce un errore o un warning quando qualcosa va storto — il comportamento sbagliato si manifesta come output silenziosamente diverso da quello atteso (l’eccezione è il punto 6, che in un caso specifico manda in errore l’intera pagina invece di sbagliare in silenzio).

1. Risoluzione varianti per nome file

Fractal cerca un file component--variantname.njk per ogni variante dichiarata nel .config.yml di un componente. Se il file non esiste, ricade silenziosamente sul template base component.njk, senza segnalare nulla.

Esempio reale — components/02-atoms/spinner/:

spinner.njk                       ← template base
spinner--with-label.njk           ← variante con label sr-only
spinner--with-label-heading.njk   ← variante con label visibile <h2>

Come evitarlo: quando si aggiunge al .config.yml una variante che richiede markup diverso dal default, creare sempre il file component--variantname.njk corrispondente. Impostare solo parametri di contesto non basta se il template base non li usa nel modo corretto — e non c’è alcun avviso in fase di build se il file manca.

2. resources è un overwrite completo, non un merge

fractal.components.set('resources', ...) sostituisce interamente la configurazione di default del resource browser, non la integra. Il default nativo di Fractal è { assets: { match: ['**/*'] } } (mostra tutti i file del componente).

RTDS lo personalizza per nascondere i file di predisposizione Storybook, ma usando un pattern di negazione nello stesso gruppo match, non una whitelist di estensioni:

// fractal.config.js:38-40 e fractal.servizi.config.js:38-40 (identico in entrambi)
fractal.components.set('resources', {
    assets: {
        match: ['**/*', '!**/*.stories.js'],
        label: 'Assets',
    },
});

Perché è pericoloso: sostituire questo blocco con una whitelist (es. { css: { match: '**/*.css' } }) nasconde silenziosamente ogni file non esplicitamente elencato — inclusi i .js reali dei componenti, non solo quelli che si intendeva escludere. anymatch (il matcher usato internamente da Fractal) valuta i pattern ! prima di quelli positivi, quindi il modo corretto di esprimere “tutto tranne X” è ['**/*', '!X'], non “solo Y”.

Come evitarlo: qualunque modifica a questo blocco va pensata in termini di “tutto tranne X”, e va applicata a entrambi i config (fractal.config.js e fractal.servizi.config.js condividono lo stesso blocco, non è sufficiente correggerne uno solo).

3. Nascondere una pagina docs la esclude anche dalla build statica

Impostare item._isHidden = true su una pagina di documentazione (fractal.docs.items()) la esclude anche dalla build statica in dist/, non solo dalla sidebar — a differenza delle pagine componente (fractal.components.items()), dove nascondere dalla nav non ne impedisce l’esportazione in build.

Le liste attuali:

// fractal.config.js:124 — CORE_DOCS_HIDE
['servizi', 'superpowers', 'ai-work-tracker']

// fractal.servizi.config.js:100 — SERVIZI_DOCS_HIDE
['core', 'superpowers', 'ai-work-tracker']

Perché è pericoloso: il builder statico di Mandelbrot filtra esplicitamente !isHidden sulle pagine docs, ma non applica lo stesso filtro alle pagine componente — è facile assumere (sbagliando) che i due comportamenti siano simmetrici.

Come evitarlo: prima di nascondere una nuova pagina docs dal nav, non assumere che resti comunque raggiungibile in build statica via URL diretto — verificarlo esplicitamente rigenerando dist/. Se serve che una pagina resti sia nascosta dal nav sia presente in build, l’unica strada è spostarla fuori da docs/ a monte, non affidarsi a _isHidden.

4. Il global path() di Mandelbrot restituisce il file, non la directory

Il global Nunjucks path() esposto dal tema @frctl/mandelbrot (usato per calcolare link relativi robusti a qualunque profondità di pagina) restituisce il percorso relativo al file index.html della root dell’istanza, non alla directory radice.

Esempio reale — theme-overrides/views/partials/header.nunj:13-15, usato per il link cross-ambiente Core/Servizi in testata:

{% set rootPath = path('/') %}
{% set rootDir = rootPath.slice(0, rootPath.lastIndexOf('/') + 1) %}
{% set crossEnvHref = rootDir + '../' + frctl.theme.get('crossEnvTarget') + '/' %}

Perché è pericoloso: concatenare direttamente path('/') + '../altra-cartella/' produce un href malformato (il nome del file resta nella stringa, es. ../../index.html../altra-cartella/). L’errore non emerge testando solo la homepage (dove il file è nella stessa directory della root), ma diventa evidente su pagine a profondità maggiore (dettaglio componente, docs annidate).

Come evitarlo: quando si usa path() per costruire un link verso una cartella sorella della root (non verso un’altra pagina interna), estrarre prima la directory come nell’esempio sopra, e verificare sempre l’output a più profondità di pagina — non solo sulla homepage, dove il bug è invisibile.

5. I README dei componenti non passano dal motore Nunjucks dei docs

fractal.docs.engine(nunjucks({ globals: {...} })) registra i global (docExt, serviziUrl/coreUrl, serviziUrlRoot/coreUrlRoot) solo per l’engine che processa i file dentro docs/. I README.md dei singoli componenti (components/*/README.md), mostrati nella tab Notes dell’interfaccia Fractal, sono renderizzati da un percorso diverso (component notes), che non ha questi global disponibili.

Perché è pericoloso: usare {{ docExt }} o un altro global dei docs in un README component non produce un errore — il testo resta letterale nell’output ({{ docExt }} compare così com’è, invece di essere sostituito).

Come evitarlo: nei README dei componenti, per riferimenti incrociati (ad altri README, a pagine docs/*.md, ad altre sezioni della libreria):

  • non usare {{ docExt }} o altri global Nunjucks dei docs — non vengono sostituiti;
  • non calcolare a mano path relativi verso docs/ (la profondità di components/ varia da 2 a 4+ livelli a seconda della categoria, e non c’è comunque garanzia di routing per quel path nella tab Notes);
  • usare invece riferimenti testuali semplici (es. “vedi la pagina documentazione ‘Sprite SVG’ nella sezione Fondamenta”, o il nome del componente in backtick). I link a risorse statiche servite da public/ (es. /css/..., /js/...) restano invece validi con path assoluto, perché Fractal serve public/ alla root sia in dev sia in build statica.

6. Il motore Nunjucks dei docs esegue anche la sintassi mostrata dentro un code fence

Ogni pagina dentro docs/ passa dal motore Nunjucks prima di diventare HTML — questo vale per l’intero contenuto del file, non solo per il testo prosa. Un tag Nunjucks scritto dentro un code fence o un inline code Markdown, con l’intento di mostrarlo come esempio letterale, viene invece eseguito come un vero tag.

Perché è pericoloso: se il tag mostrato come esempio referenzia un global non disponibile in quel contesto (es. path() in un punto dove non è registrato), la pagina smette di renderizzare del tutto, con un errore a runtime tipo “Unable to call path, which is undefined or falsey” al posto del contenuto — o, se il tag ha una sintassi che il parser non riconosce (es. un placeholder generico non richiuso correttamente), un errore di parsing tipo “tag name expected” che blocca la pagina ancora prima del rendering. Se invece il tag referenzia un global che è disponibile (es. quello usato per l’estensione dei link tra pagine, registrato per l’intero engine docs/), non c’è errore: il tag viene silenziosamente valutato e sostituito con il suo valore reale, invece di comparire come testo letterale — l’esempio risulta comunque sbagliato, solo in modo meno visibile.

Come evitarlo: per mostrare sintassi Nunjucks letterale dentro una pagina docs/*.md, avvolgerla sempre nei tag di escape dedicati di Nunjucks (visibili in azione nell’esempio sotto), sia dentro un code fence sia dentro un inline code:

```nunjucks
{% set esempio = path('/') %}
```

Verificare sempre col dev server (npm run start) che la pagina non mostri il banner “Error rendering page” dopo aver aggiunto uno snippet con sintassi Nunjucks — l’assenza di errore da sola non basta se il tag referenzia un global disponibile (vedi sopra), va controllato anche che il testo compaia letterale e non sostituito.