# StyloBot Release Series: Behavior-Aware TypeScript UI

*La rilevazione del bot non dovrebbe fermarsi a permettere/block. Questo post mostra come il risultato della classificazione di StyloBotM SK2 diventi la logica dell'applicazione nel TypeScript : middleware del server per Express e FastifyMSC4 helpers dei template per le barre di comandosMST5 NunjucksMSL6 e EJSMLS7 e componenti web del browserMSSK8 così che l'interfaccia utente possa modellare l'esperienza invece di infilare la frizione dopo il fattoMSS9*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **L'informazione figura nella parte dispositiva. `@stylobot/core` e `@stylobot/node` I pacchetti npm verranno pubblicati tra poco.** I frammenti sotto descrivono la superficie che esponeranno.

> **StyloBot Release Series**
> 
> 1. [**Comportamento, Non identità**](/blog/stylobot-fingerprint): perché StyloBot modella i clienti comportamentalmente
> 2. [**Comportamento-Aware ASP.NET UI**](/blog/behaviour-aware-ux): il server- la superficie restituita per le applicazioni .NET
> 3. [**Trovare e riparare la crescita senza limiti nei servizi NET Long-Running .**](/blog/stylobot-release-reliability): la disciplina di affidabilità che mantiene il motore noioso nella produzione.
> 4. **Comportamento-Conoscere l'interfaccia grafica del tipoScript**: questo articolo
> 5. [**L'architettura della Sidecar**](/blog/sidecar-architecture): come il motore di rilevazione si collega a stack non -.NET
> 6. [**Imparare a diventare più veloce**](/blog/stylobot-release-learning): il sistema di apprendimento adattativo, quattroM SK2 memoria a livello di livello+, e la cassa del verdetto
> 7. [**Provare a fare ciò che non si ferma.**](/blog/stylobot-release-nondeterministic-testing): la disciplina di verificazioneM SK1 un file BDF guida la regressione , carico, e calibrazione
> 8. [**StyloExtract - un convertitore locale di HTML per Markdown**](/blog/stylobot-release-styloextract): l'HTML→Layere Markdown che si combina con il rilevatore , il walkeder bug lucidVIEW catturatoM SK3 e la catena di cibo per cani che lo rende onesto

Il motore di rilevamento del StyloBot' è scritto in ASP.NET CoreM SK2 un sistema ad alta performance - con eccellenti primitivi asincentiMSC4 sotto-- millisecondi di latenza del percorso MNK6 e 20 anni di pedigree produttivo dietro di essoMRK8 Il TypeScript SDK è la superficie che porta il motoreMST9 la fuoriuscita in NodeMst10js applicazioni e browser senza bisogno di sapere nulla su ciò che è successo sotto l'aspetto SST11 (La latenza di percorso \MST13\ viene dal sistema di apprendimento adattabile coperto [Imparare a diventare più veloce](/blog/stylobot-release-learning): una volta che l'impronta digitale è stata vista con abbastanza sicurezza, il cache del verdetto supera la catena di rilevamento completa e risponde in microsecondi .)

<!--category-- TypeScript, Node.js, StyloBot, Bot Detection, Security -->
<datetime class="hidden">2026-06-01T10:30</datetime>

# Introduzione

La maggior parte della rilevazione dei robot disponibile nell'ecosistema di TypeScript ti dà una delle due cose: un'impronta digitale del browser che ti dice se il *cliente* Sembra automatizzato, o un regex sull'User-String Agent che cattura solo i bot che si annuncianoM SK2

Nessuno dei due è utile nella stratagemma dell'applicazione.

[Si trattava di un sistema che si basava su una serie di fattori, come il tempo, l'energia e la capacità.](https://github.com/fingerprintjs/BotD) funziona nel browser e non ha una visuale dei titoli di richiesta, impronte digitali TLSM SK1 comportamento della sessione, o reputazione IP . Un Chromium senza testa e un Googlebot sembrano identici a luiMSC4 Pacchetti di compatibilità Pure UA Mスク5`is-bot`, `isbot`) cattura solo quello che si opta per essere catturatoM SK1 Network-Producti di strata (Cloudflare Bot Management+, DataDome+) blocca al limite ma non può personalizzarlo nella strata dell'applicazione+M SK6 Avete imparato che una richiesta è stata bloccata=, mai che una domanda che ha raggiunto la vostra app abbia avuto una probabilità di bot e una fascia di rischio medio=M SK9 così la vostra pagina di pagamento avrebbe potuto mostrare un passo di frizione e le vostre analisi l'avrebbero potuto escludere+MSC10 tutto senza un codice d'errore*.

Quello che StyloBot dà alla vostra applicazione TypeScript è un verdetto: un risultato digitato con un punteggio di probabilità continua del bot, una classifica M SK2`AiBot`, `Scraper`, `GoodBot`, `MaliciousBot`, ...), una fascia di rischioM SK2 un'azione consigliata, e un punteggio per la minacciaMSC4 Quello che fate con quel giudizio dipende da voi.

Questo articolo passa attraverso un esempio di storefront e mostra come quel giudizio diventi il comportamento dell'applicazione a ogni strato: middleware del server, helpers dei templateM SK2 e componenti del browserMSC3

[TOC]

# Il verdetto

Prima di ogni altra cosa, questo è ciò che arriva:

```ts
// @stylobot/core
interface Verdict {
  isBot: boolean;
  botProbability: number;        // 0.0–1.0 - not binary, a continuous score
  confidence: number;            // certainty in that score
  botType: BotType | null;       // 'AiBot' | 'Scraper' | 'GoodBot' | 'MaliciousBot' | ...
  botName: string | null;        // 'GPTBot', 'Googlebot', 'curl/8.9.1', ...
  riskBand: RiskBand;            // 'VeryLow' | 'Low' | 'Elevated' | 'Medium' | 'High' | 'VeryHigh'
  recommendedAction: RecommendedAction;  // 'Allow' | 'Throttle' | 'Challenge' | 'Block'
  threatScore: number;           // elevated when CVE probes or credential stuffing detected
  threatBand: ThreatBand;        // 'None' | 'Low' | 'Elevated' | 'High' | 'Critical'
}
```

Un punto di fine del checkout può gonfiare. `Medium` Rischio invece di bloccarlo. Un API può escludere `AiBot` traffic from rate-limit quotas without counting it against real users `Elevated` Rischio senza toccare l'esperienza per `Low` Visitori a rischio. Niente di questo è possibile se il risultato della rilevazione è un boolean alla strata di rete.

Bande di rischio in ordine: `VeryLow`, `Low`, `Elevated`, `Medium`, `High`, `VeryHigh`, `Verified`.

# L'installazione

```bash
npm install @stylobot/core @stylobot/node

# only needed for grpc mode
npm install @grpc/grpc-js @grpc/proto-loader
```

`@stylobot/core` Ha zero dipendenze del tempo di esecuzione. contiene le definizioni di tipo, il cliente RESTM SK2 il parsatore di header , e MSC4 opzionealmenteMST5 il client gRPCMSV6 Funziona in NodeMSL7 DenoMsl8 e Bun senza modificazioniMSR9

`@stylobot/node` fornisce Express middleware, un plugin Fastify, utilità per l'aiuto di schemiM SK2 e l'injectore del verdetto . dipende da `@stylobot/core` e niente altro al runtime.

# La superficie del server.

## Come il verdetto raggiunge la vostra middleware

Il middleware ha tre modi. Tutti i tre producono lo stesso. `req.stylobot` forma. Quella che usate dipende da dove funziona la vostra detezione dell'infrastruttura

```mermaid
graph LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px

    subgraph H ["headers mode - zero added latency"]
        GW[Gateway / Caddy / YARP]:::input -->|"X-StyloBot-* headers"| A[Express / Fastify]:::good
    end

    subgraph G ["grpc mode - HTTP/2, no JSON overhead"]
        B[Express / Fastify]:::good -->|"gRPC Detect RPC"| SC[StyloBot Sidecar :5090]:::async
    end

    subgraph R ["api mode - REST, full signal set"]
        C[Express / Fastify]:::good -->|"POST /api/v1/detect"| SC2[StyloBot Sidecar :5091]:::async
    end
```

**`headers` Mode**: la detezione è andata in arrivo alla porta ; il middleware legge i titoli inseriti senza chiamare la rete МSK2 Questo è il modello di produzione quando una porta Caddy o YARP si trova davanti a noi

**`grpc` Mode**: il middleware chiama la sidecar direttamente su HTTP/2. La latenza più bassaM SK2 non c'è JSON overhead `@grpc/grpc-js`. L'interfaccia del gRPC e i dettagli di deployment della sidecar sono in [articolo sull'architettura della carrozza laterale](/blog/sidecar-architecture).

**`api` Mode**Le chiamate : `POST /api/v1/detect` su HTTP/1.1. Ritorna anche `reasons` (perM SK1contributioni dei rilevatori) e `signals` (lo stato completo della lavagna) che gli altri modi omitanoM SK2

Il middleware continua a fallire quando si apre. Se l'autostrana è inaccessibile o non funziona. `req.stylobot` è stabilito ad un verdetto vuoto permisivo e la richiesta continua.

## Costruirlo.

**Express:**

```ts
import express from 'express';
import { styloBotMiddleware } from '@stylobot/node';

const app = express();
app.use(styloBotMiddleware({ mode: 'headers' }));
// or:
app.use(styloBotMiddleware({ mode: 'grpc', endpoint: 'localhost:5090', timeout: 100 }));
```

**Fastify:**

```ts
import Fastify from 'fastify';
import { styloBotPlugin } from '@stylobot/node';

const fastify = Fastify();
await fastify.register(styloBotPlugin, { mode: 'headers' });
```

Add a type declaration if you want typed access on Fastify requests:

```ts
// types.d.ts
import type { StyloBotResult } from '@stylobot/node';
declare module 'fastify' {
  interface FastifyRequest { stylobot: StyloBotResult; }
}
```

# La vitrina.

Lo stesso campione di magazzino di [Article ASP.NET](/blog/behaviour-aware-ux), la pagina del prodottoM SK1 il pagamento, l'inscrittaMST3 il newsletterM ST4 ma in TypeScriptMst5 Il risultato della rilevazione è lo stessoMSST6 quello che cambia è come la superficie lo esponeMSS7

## La pagina 1: La pagina del prodotto

I tiratori di prezzi iniziano qui. Vogliono i nomi dei prodotti, i prezziM SK2 e i livelli di quotaMSC3 Vogliate il catalogo indicebile da motori di ricerca ed utile per gli umani senza dare i segnali commerciali a una raccolta sistematica .

*Scenario: un scafo di prezzo colpisce `/products/:id`. Non vede un codice di sconto, nessun aggiungimentoM SK2a\- bottone di carto , e nessun segnale d'acquisto che valga la pena di agire suMSC5*

L'operatore del percorso:

```ts
app.get('/products/:id', (req, res) => {
  const { verdict } = req.stylobot;
  const product = getProduct(req.params.id);

  res.render('product', {
    product,
    sbVerdict: verdict,
    showDiscount:   verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showBuyButton:  !verdict.isBot,
    showCartWarning: verdict.recommendedAction === 'Challenge',
    isSearchBot:    verdict.botType === 'SearchEngine' || verdict.botType === 'VerifiedBot',
  });
});
```

La template (Handlebars):

```handlebars
{{! verified crawlers get structured metadata instead of commercial UI }}
{{#if isSearchBot}}
  <meta name="description" content="{{product.name}} -{{product.category}}." />
  <p>{{product.description}}</p>
{{else}}

  {{! discount only for low-risk human visitors }}
  {{#if showDiscount}}
    <div class="alert alert-success">
      Member price: use code <strong>LOYAL10</strong> for 10% off.
    </div>
  {{/if}}

  {{! add-to-cart only for non-bot traffic }}
  {{#if showBuyButton}}
    <form method="post" action="/cart/add">
      <input type="hidden" name="productId" value="{{product.id}}" />
      {{#if showCartWarning}}
        <p class="text-warning">Additional verification may be required at checkout.</p>
      {{/if}}
      <button type="submit" class="btn btn-primary">Add to Cart</button>
    </form>
  {{else}}
    <p class="text-muted">Purchase available to human visitors.</p>
  {{/if}}

{{/if}}
```

Tre modelli in uso qui. Differenzazione Crawler: `SearchEngine` e `VerifiedBot` ottenere metadati strutturati senza segnali commerciali, che è quello di cui hanno veramente bisogno. Targeting discountM SK2 un codice di lealtà mostrato solo a basso livelloMSC3 il traffico a rischio è meno probabile di finire su un forum per i voucher . Frizione progressivaMST5 `Challenge` è un avvertimento, non è un blocco. Le sessioni sospette possono ancora comprarsi

## La pagina 2: Checkout

Automatizzazione della frode, testatori di carte, voucher bruteM SK2 forscheriMSC3 e retreazioni scritte tutte convergono al checkout . L'obiettivo è la difesa in profondità senza CAPTCHAMST5la prima esperienza per tuttiMst6

*Scenario: un voucher-test bot colpisce `/checkout`. Vede un mortoM SK1 messaggio finale, invia una vasca da miele - modulo completatoMSC4 e riceve una conferma silenziosa senza incoraggiamento a riprovare.*

```mermaid
flowchart TD
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    A([Visitor reaches /checkout]):::input --> B{bot?}
    B -->|yes| C[Dead-end message - no form]:::problem
    B -->|no| D{risk band}
    D -->|Low| E[Express checkout CTA]:::good
    D -->|Elevated| F[Standard checkout CTA]:::good
    D -->|High+| G[Phone number - no online path]:::problem
    E & F --> H[POST /checkout/order]
    H --> I{honeypot triggered?}
    I -->|yes| J[Silent fake confirmation]:::problem
    I -->|no| K[Process real order]:::good
```

La strada:

```ts
app.get('/checkout', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('checkout', {
    sbVerdict: verdict,
    showForm:     !verdict.isBot,
    showExpress:  verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showStandard: ['VeryLow','Low','Elevated'].includes(verdict.riskBand),
    showPhone:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/checkout/order', (req, res) => {
  // honeypot fields are invisible to humans; bots fill everything
  if (req.body.hp_name || req.body.hp_email) {
    return res.redirect('/checkout/confirmed');  // silent fake - no retry incentive
  }
  return processRealOrder(req, res);
});
```

La template:

```handlebars
{{#if showForm}}
  <form method="post" action="/checkout/order">

    {{! invisible honeypot trap - humans leave blank, bots fill everything }}
    <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
      <input type="text" name="hp_name" tabindex="-1" autocomplete="off" />
      <input type="email" name="hp_email" tabindex="-1" autocomplete="off" />
    </div>

    {{#if showExpress}}
      <button type="submit" name="express" value="true" class="btn btn-success btn-lg">
        Express Checkout
      </button>
    {{/if}}

    {{#if showStandard}}
      <button type="submit" class="btn btn-primary">Proceed to Payment</button>
    {{/if}}

    {{#if showPhone}}
      <p>Please call us to complete your order: <strong>0800 123 456</strong></p>
    {{/if}}

  </form>
{{else}}
  <p class="text-muted">Checkout is available to human visitors only.</p>
{{/if}}
```

La discardazione del vaso di miele è la tecnica chiave qui. Il feedback sull'errore insegna agli attacchi a ripetere; una falsa conferma silenziosa spreca il loro tempo , e la pagina di conferma sembra identica se l'ordine era reale o discardatoM SK3 Il checkout Express come beneficio di fiducia MSC4 non è un predefinitoMST5 significa che solo le sessioni che lo hanno guadagnato ottengono il percorso più breveM ST6 Nella produzioneMst7 il fattore del vaso `<div>` un wrapper in una maniglia condivisa (`{{> honeypot}}`) invece di ripetere il blocco dello stile inline su ogni forma protetta.

## Page 3: Login

L'ingresso è affidabile-territorio di affichageM SK1 Il commercio-l'affetto è diverso dal checkout : un falso positivo al checkout può perdere una venditaMSC4 un affidabile mancanteMST5 l'attacco di affari perde un accountMst6 L'applicazione può essere più rigorosaMSSK7

*Scenario: credenzale- battito dello script `/login`. Vede un messaggio di dissuasione , riempie il vaso con il miele, e viene spinto verso `/login/denied` prima di qualsiasi authenticazione funziona.*

```ts
app.get('/login', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('login', {
    sbVerdict: verdict,
    showWarning: verdict.isBot,
    showRisk:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/login', async (req, res) => {
  if (req.body.hp_user || req.body.hp_pass) {
    return res.redirect('/login/denied');
  }
  if (req.stylobot.verdict.isBot) {
    return res.redirect('/login/denied');
  }
  return authenticate(req, res);
});
```

```handlebars
{{#if showRisk}}
  <div class="alert alert-danger">
    High-risk signals detected. Login attempts are logged.
  </div>
{{/if}}

{{#if showWarning}}
  <div class="alert alert-warning">
    Automated login attempts are detected and blocked.
  </div>
{{/if}}

<form method="post" action="/login">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="text" name="hp_user" tabindex="-1" autocomplete="off" />
    <input type="password" name="hp_pass" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" id="email" name="email" autocomplete="email" />
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <input type="password" id="password" name="password" autocomplete="current-password" />
  </div>
  <button type="submit" class="btn btn-primary">Sign In</button>
</form>
```

Il bot vede il contenitore prima di presentarlo. Se lo presenta comunqueM SK1 il vaso d'almonde lo cattura. Se in qualche modo evita il vaso di almonde , `verdict.isBot` è il server finale-controllo laterale prima che una logica di autenticazione funzioni. Tre stratiM SK2 tutti piccoli .

## La pagina 4: Newsletter

I crawler artificiali sono un problema diverso. Non stanno cercando di brutalizzare- forzare le account o i prezzi delle raccolteM SK2 stanno ingaggiando contenuti per la formazione dei modelli . Sono spesso onesti riguardo all'identitàMSC4 il che significa che la risposta giusta è commerciale piuttosto che avversariaMST5

*scenario: GPTBot colpisce `/newsletter`. Vede un messaggio di licenzione dei dati invece che un'affidazione di sottoscrizione. La sua ricevuta è discesa silenziosamenteM SK2*

```ts
app.get('/newsletter', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('newsletter', {
    sbVerdict:     verdict,
    isHuman:       !verdict.isBot,
    isAiBot:       verdict.botType === 'AiBot',
    isOtherBot:    verdict.isBot && verdict.botType !== 'AiBot',
  });
});

app.post('/newsletter/subscribe', (req, res) => {
  if (req.body.hp_email2 || req.stylobot.verdict.isBot) {
    return res.redirect('/newsletter/thanks');  // silent discard
  }
  mailingList.subscribe(req.body.email);
  return res.redirect('/newsletter/thanks');
});
```

```handlebars
{{#if isHuman}}
  <p>Get exclusive deals delivered to your inbox. Unsubscribe any time.</p>
{{/if}}

{{#if isAiBot}}
  <div class="alert alert-info">
    This subscription endpoint is for human readers.
    For data licensing enquiries, <a href="/contact">contact us directly</a>.
  </div>
{{/if}}

{{#if isOtherBot}}
  <div class="alert alert-warning">
    Automated subscription attempts are discarded.
  </div>
{{/if}}

<form method="post" action="/newsletter/subscribe">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="email" name="hp_email2" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <input type="email" name="email" placeholder="your@email.com" autocomplete="email" />
  </div>
  <button type="submit" class="btn btn-success">Subscribe</button>
</form>
```

`AiBot` Non è solo un'altra etichetta ostile.

# Aiutitori per i moduli

## Inserire il verdetto in schemi.

`sbVerdictInjector` va avanti dopo il middleware di rilevazione e mette il verdetto. `res.locals`:

```ts
import { sbVerdictInjector } from '@stylobot/node';

app.use(styloBotMiddleware({ mode: 'headers' }));
app.use(sbVerdictInjector({ mode: 'gateway' }));
// or for direct sidecar calls (REST port, not the gRPC port):
app.use(sbVerdictInjector({ mode: 'sidecar', endpoint: 'http://localhost:5091' }));
```

`res.locals.sbVerdict` è disponibile in ogni modello. `res.locals.sbVerdictScript` è un'inline. `<script>` tag che stabilisce `window.__sb` per i componenti del browser.

## Le barre di mano

Definire `RISK_ORDER` una volta nella scala di moduli ( i Nunjucks e gli assistenti EJS sotto usano la stessa mappa):

```ts
import type { Verdict, RiskBand } from '@stylobot/core';

const RISK_ORDER: Record<string, number> = {
  Unknown: 0, VeryLow: 1, Low: 2, Elevated: 3, Medium: 4, High: 5, VeryHigh: 6, Verified: 7,
};

// register on your Handlebars engine instance
hbs.engine({
  helpers: {
    // {{#sbGate sbVerdict "Low"}}...{{else}}...{{/sbGate}}
    sbGate(verdict: Verdict | null, maxRisk: string, options: any) {
      if (!verdict) return options.fn(this);
      return RISK_ORDER[verdict.riskBand] <= RISK_ORDER[maxRisk]
        ? options.fn(this)
        : options.inverse(this);
    },

    // {{#sbIsBot sbVerdict}}...{{else}}...{{/sbIsBot}}
    sbIsBot(verdict: Verdict | null, options: any) {
      return verdict?.isBot ? options.fn(this) : options.inverse(this);
    },

    // {{#sbBotType sbVerdict "AiBot"}}...{{/sbBotType}}
    sbBotType(verdict: Verdict | null, type: string, options: any) {
      return verdict?.botType === type ? options.fn(this) : options.inverse(this);
    },

    // {{#sbMinRisk sbVerdict "High"}}...{{/sbMinRisk}}
    sbMinRisk(verdict: Verdict | null, minRisk: string, options: any) {
      if (!verdict) return options.inverse(this);
      return RISK_ORDER[verdict.riskBand] >= RISK_ORDER[minRisk]
        ? options.fn(this)
        : options.inverse(this);
    },
  },
});
```

Nelle schemi:

```handlebars
{{#sbGate sbVerdict "Low"}}
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
{{else}}
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
{{/sbGate}}

{{#sbBotType sbVerdict "AiBot"}}
  <div class="alert alert-info">AI crawler detected. Data licensing info above.</div>
{{/sbBotType}}

{{#sbMinRisk sbVerdict "High"}}
  <div class="alert alert-danger">High-risk session. Some features restricted.</div>
{{/sbMinRisk}}
```

## Nunjucks

```ts
import nunjucks from 'nunjucks';

// RISK_ORDER - same map defined in the Handlebars section above

class SbGateExtension {
  tags = ['sbgate'];
  parse(parser: any, nodes: any, lexer: any) {
    const tok = parser.nextToken();
    const args = parser.parseSignature(null, true);
    parser.advanceAfterBlockEnd(tok.value);
    const body = parser.parseUntilBlocks('else', 'endsbgate');
    let elseBody = null;
    if (parser.skipSymbol('else')) {
      parser.skip(lexer.TOKEN_BLOCK_END);
      elseBody = parser.parseUntilBlocks('endsbgate');
    }
    parser.advanceAfterBlockEnd();
    return new nodes.CallExtension(this, 'run', args, [body, elseBody]);
  }
  run(context: any, maxRisk: string, body: any, elseBody: any) {
    const v = context.ctx.sbVerdict;
    const fits = !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[maxRisk];
    return fits ? body() : (elseBody?.() ?? '');
  }
}
env.addExtension('SbGateExtension', new SbGateExtension());
```

```nunjucks
{% sbgate "Low" %}
  <a href="/checkout/express">Express checkout</a>
{% else %}
  <a href="/checkout">Checkout</a>
{% endsbgate %}
```

## EJS

EJS non ha degli aiuti a blocchi, ma le funzioni locali coprono la maggior parte dei casi in modo pulito:

```ts
// RISK_ORDER - same map defined in the Handlebars section above

app.use((req, res, next) => {
  const v = res.locals.sbVerdict ?? null;
  res.locals.sbBelowRisk = (max: string) =>
    !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[max];
  res.locals.sbAboveRisk = (min: string) =>
    v && RISK_ORDER[v.riskBand] >= RISK_ORDER[min];
  res.locals.sbIsBot  = () => v?.isBot ?? false;
  res.locals.sbBotIs  = (type: string) => v?.botType === type;
  next();
});
```

```ejs
<% if (sbBelowRisk('Low')) { %>
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
<% } else { %>
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
<% } %>

<% if (sbBotIs('AiBot')) { %>
  <div class="alert alert-info">AI crawler. Data licensing enquiries via /contact.</div>
<% } %>
```

# La superficie del browser

## Introduzione del verdetto nel browser

Aggiungere `{{{sbVerdictScript}}}` alla vostra testa di layout. Rende come:

```html
<script>window.__sb = {"isBot":false,"botProbability":0.12,"riskBand":"Low","recommendedAction":"Allow",...}</script>
```

Poi caricate il pacchetto di elementi:

```handlebars
<head>
  {{{sbVerdictScript}}}
  <script type="module" src="/js/sb-elements.js"></script>
</head>
```

## `<sb-gate>`: mostrare o nascondere il contenuto secondo la fascia di rischio

```html
<!-- only shown when riskBand <= 'low' -->
<sb-gate max-risk="low">
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
</sb-gate>

<!-- only shown when riskBand >= 'elevated' -->
<sb-gate min-risk="elevated">
  <div class="alert alert-warning">Unusual activity detected on your session.</div>
</sb-gate>
```

## `<sb-adapt>`: scegliere il primo caso corrispondente

```html
<sb-adapt>
  <sb-case max-risk="low">
    Standard checkout - enter your card details below.
  </sb-case>
  <sb-case max-risk="medium">
    Additional verification is required before we can process your order.
  </sb-case>
  <sb-case>
    Automated checkout access is not permitted.
  </sb-case>
</sb-adapt>
```

`<sb-adapt>` Prende il primo. `<sb-case>` I quali `max-risk` Si adatta e nasconde il resto. Il finale `<sb-case>` Non c'è. `max-risk` è il taglio-toccoM SK1 Entrambi `<sb-gate>` e `<sb-adapt>` Ascoltate. `sb:verdict` evento e ri-evalutare se il verdetto cambia dopo la prima pitturaM SK1

## `<sb-widget>`: server-fragmenti liquidi consegnati

`<sb-gate>` nasconde il cliente del contenuto-lato. Se avete bisogno di un bot per ricevere un diverso marchoM SK2 non un marcho nascosto , dovete rendere il frammento sul server con il contesto di rilevazione disponibile alla templateMSC4

`<sb-widget>` legge il foglio Liquido da un'inline. `<template>` element, batchi tutte le richieste di widget sulla pagina in una singola ronda -trip, e si sostituisce con il HTML riferitoM SK3

```html
<sb-widget data-sb-widget="checkout-cta">
  <template>
    {% if recommendedAction == 'Block' %}
      <p class="text-danger">Automated checkout is blocked.</p>
    {% elsif botType == 'AiBot' %}
      <p>AI crawlers cannot complete purchases. <a href="/contact">Data licensing enquiries</a>.</p>
    {% elsif isBot %}
      <p class="text-muted">Checkout is available to human visitors.</p>
    {% else %}
      <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
    {% endif %}
  </template>
</sb-widget>
```

Variabili del template: `isBot`, `botProbability`, `confidence`, `botType`, `botName`, `riskBand`, `recommendedAction`, `threatScore`, `threatBand`. In più qualsiasi `vars` Si passa dal server-side quando si chiama. `RenderWidget` direttamente.

Tutti. `<sb-widget>` Elementi che si collegano nello stesso ticolo di microtask sono battuti in una sola richiesta. Una pagina con cinque widget fa una volta rotonda

```mermaid
sequenceDiagram
    participant B as Browser
    participant C as Coordinator
    participant S as /_stylobot/partials/render
    participant L as Fluid / Liquid

    B->>C: sb-widget#checkout-cta registers
    B->>C: sb-widget#promo-banner registers
    B->>C: sb-widget#nav-links registers
    Note over C: queueMicrotask - one batch
    C->>S: POST { widgets: { checkout-cta: "...", promo-banner: "...", nav-links: "..." } }
    S->>L: render each template with verdict context
    L-->>S: rendered HTML fragments
    S-->>C: <div data-sb-widget="checkout-cta">...</div> ...
    C-->>B: each sb-widget replaced in DOM
```

# Il grafico sintetico.

| Componente
|---|---|---|
| `styloBotMiddleware` | Express | Attaches `req.stylobot` attraverso i titoli / gRPC / REST |
| `styloBotPlugin` | Fastify | Lo stesso, come plugin Fastify con `decorateRequest` |
| `sbVerdictInjector` | Express | Populazioni `res.locals.sbVerdict` e `sbVerdictScript` |
| Aiuti alla barra di comandos | Template del server | `sbGate`, `sbIsBot`, `sbBotType`, `sbMinRisk` Aiutatori di blocco |
| Espansione di Nunjucks | Immagini del server | `{% sbgate "Low" %}...{% endsbgate %}` |
| Locali EJS | Servetori templates | `sbBelowRisk()`, `sbAboveRisk()`, `sbIsBot()`, `sbBotIs()` |
| `<sb-gate>` | Browser | mostrare/ nascondere il contenuto secondo la banda di rischioM SK3 ascolta per `sb:verdict` |
| `<sb-adapt>` | Browser | Prendete il primo caso corrispondente da una lista SSK2
| `<sb-widget>` | Browser | Server di attivazione- Fragmento liquido consegnatoM SK3 battuto per taglio SSK4
| `StyloBotGrpcClient` | Node / Bun

Il prossimo nella serie di release: [**L'architettura della Sidecar**](/blog/sidecar-architecture), come il motore di rilevazione si collega a non--. stacks NETM SK2 Go SDK, la plugin Caddy , e l'interfaccia gRPC che lo collega insiemeMSC5

Se siete arrivati qui dal lato .NET, [Comportamento-Aware ASP.NET UI](/blog/behaviour-aware-ux) copre gli stessi schemi frontali con tag helper e filtri di azione; il ritocco di affidabilità che mantiene la memoria del motore- stabile sotto un traffico sostenuto è in [Trovare e riparare la crescita senza limiti nei servizi NET Long-Running .](/blog/stylobot-release-reliability). Motore viventeM SK1 Armatura dashboard, e controlli commerciali a [stylobot.net](https://www.stylobot.net).