# StyloBot Release Series: Behaviour-Aware TypeScript UI

*La détection des bottes ne doit pas se terminer par permettre/block. Ce post montre comment le résultat de la classification du StyloBot ' devient une logique d’application dans TypeScriptM SK3 middleware pour les serveurs Express et FastifyMSC4 template helpers for HandlebarsMST5 NunjucksMSL6 and EJSMSP7 and browser web components MST8 so your UI can shape the experience instead of bolting friction on after the factMSV9*

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

> **L'article 5 de la Loi sur l'immigration et le statut des réfugiés `@stylobot/core` et `@stylobot/node` Les paquets npm seront publiés prochainement.** Les fragments ci-dessous décrivent la surface qu’ils exposeront.

> **StyloBot Release Series**
> 
> 1. [**Comportement, Non identité**](/blog/stylobot-fingerprint): pourquoi StyloBot modele les clients de façon comportementale
> 2. [**Behaviour-Aware ASP.NET UI**](/blog/behaviour-aware-ux): le serveur- la surface rendue pour les applications .NET
> 3. [**Trouver et rétablir une croissance sans limites dans les services longs-Running .NET**](/blog/stylobot-release-reliability): la discipline de fiabilité qui maintient le moteur borne dans la production
> 4. **Behaviour-Aware TypeScript UI**: cet article
> 5. [**L'architecture de la Sidecar**](/blog/sidecar-architecture): comment le moteur de détection se connecte aux piles NET non -.
> 6. [**Apprentissage pour devenir plus rapide**](/blog/stylobot-release-learning): le système d'apprentissage adaptatif , quatre- mémoire de niveauM SK3 et cache des verdicts
> 7. [**Étudier ce qui ne va pas se tenir**](/blog/stylobot-release-nondeterministic-testing): la discipline de vérification : un fichier BDF conduit à la régressionM SK2 charge, et à l'étalonnage
> 8. [**StyloExtract - un convertisseur HTML d'apprentissage local à Markdown**](/blog/stylobot-release-styloextract): la couche de marquage HTML→ qui se joigne au détecteur , le bug lucidVIEW du fauteuil roulant qui a capté l’image

StyloBot's moteur de détection est écrit dans ASPM SK1NET Core: un cadre de performance élevée - avec d'excellents primitives asynciques M SK4 sous-- millisecondes chaudes - latence des cheminements MSC7 et vingt ans de pedigree de production derrière elle MST8 Le TypeScript SDK est la surface qui permet à l'engineMNK9 de transférer sa sortie en NodeMST10js applications et navigateurs sans qu'il soit nécessaire de savoir quoi s'est passé au-dessous de cette dernière MSS11 MSS12La latence du cheminement chaudeMSSS13 provient du système d'apprentissage adaptatif couvert par [Apprentissage pour devenir plus rapide](/blog/stylobot-release-learning): une fois qu’une empreinte digitale a été vue avec suffisamment de confiance , le cache du verdict dépasse la conduite complète du détecteur et répond en microsecondes

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

# Introduction

La plupart des détecteurs de robots disponibles dans l’écosystème TypeScript vous donnent une des deux choses suivantes : : une empreinte digitale du navigateur qui vous indique si le *client* apparaît automatisé, ou un regex au-dessus de l'utilisateur-Streinte d'agent qui ne capte que les bots qui s'annoncent eux-mêmes

Aucun des deux n'est utile à la couche d'application.

[Ã‰tats-Unis](https://github.com/fingerprintjs/BotD) fonctionne dans le navigateur et n’a pas de vue des titres de demande, empreintes digitales TLSM SK1 comportement de session , ou réputation IP. Un Chromium sans tête et un Googlebot semblent identiques à celui-ci`is-bot`, `isbot`) saisir uniquement ce qui opte pour être captéM SK1 Réseau - produits de la couche d’application M SK3 Gestion des bots Cloudflare , DataDome MSC5 bloque à l’extrémité mais ne peut pas personnaliser au niveau de l’application. Vous apprenez qu’une demande a été bloquée

Ce que StyloBot donne à votre application TypeScript, c’est un verdict: un résultat type avec une cote de probabilité continue du botM SK1 une classification (`AiBot`, `Scraper`, `GoodBot`, `MaliciousBot`, ...), une bande de risqueM SK2 une action recommandée, et un score de menaceMSC4 Ce que vous faites avec ce verdict est à votre discrétion

Cet article passe par un échantillon de storefront et montre comment ce verdict devient le comportement d’une application à chaque couche.

[TOC]

# Le verdict

Avant tout autre, c’est ce qui arrive.

```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 point final de contrôle peut tourner `Medium` risque au lieu de le bloquer. Une API peut exclure `AiBot` -limiter les quotas sans le compter par rapport aux utilisateurs réels . Un formulaire d’inscription peut ajouter la preuve `Elevated` risque sans toucher l'expérience pour `Low` n’est pas possible si le résultat de détection est un boolean à la couche réseau.

Bandes de risque en ordre: `VeryLow`, `Low`, `Elevated`, `Medium`, `High`, `VeryHigh`, `Verified`.

# Installation

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

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

`@stylobot/core` a des dépendances de temps d'exécution zéro. Il contient les définitions de type , le client REST M SK2 le parteur de titres , et ( optionnellement MSC5 le clients gRPC MST6 Il fonctionne dans Node MSL7 Deno MLSC8 et Bun sans modification MSSC9

`@stylobot/node` fournit Express middleware, un plugin Fastify, des utilités d'aide à la template , et l'injecteur de verdictM SK3 Il dépend `@stylobot/core` et rien d’autre à l’heure de démarrage.

# La surface du serveur

## Comment le verdict atteint votre logiciel de médiation

Le middleware a trois modes. Tous les trois produisent la même `req.stylobot` forme. Le type d’appareil que vous utilisez dépend de l’endroit où se déroule la détection de votre infrastructure.

```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 détection a été effectuée en amont à l’entrée ; le middleware lit les titres injectés sans appel de réseau

**`grpc` mode**: le middleware appele la sidecar directement par HTTP /2. La latence la plus faible `@grpc/grpc-js`. Les détails de l’interface gRPC et du déploiement des véhicules secondaires figurent dans le [Architecture Sidecar article](/blog/sidecar-architecture).

**`api` mode**: appels `POST /api/v1/detect` sur HTTP/1.1. Retourne également `reasons` (parM SK1contributions des détecteurs ) et `signals` (l'état complet du tableaux de bord ) que les autres modes omettent

Le middleware faille toujours à ouvrir. Si l'automobile n'est pas accessible ou s'éloigne de la route `req.stylobot` est fixé à un verdict vide permissif et la demande se poursuit.

## Établissement d'un réseau

**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' });
```

Ajouter une déclaration de type si vous voulez un accès type sur les demandes Fastify:

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

# L'emplacement du magasin

Le même échantillon d’entrepôt de la [Article ASP.NET](/blog/behaviour-aware-ux), page de produit , sortie d'échantillonnage, loginM SK3 bulletin d'informationMSC4 mais dans TypeScriptMNK5 le résultat de détection est le mêmeMMK6 ce qui change c'est la façon dont la surface l'exprimeMRK7

## Page 1: La page du produit

Les acheteurs de prix commencent ici. Ils veulent les noms des produitsM SK1 les prix, et les niveaux de stocks . Vous voulez que le catalogue soit indexable par les moteurs de recherche et utile aux humains sans remettre les signaux commerciaux à une récolte systématiqueMSC4

*Scénario: un affaiblissement des prix frappe `/products/:id`. Il n'y a pas de code d'actualisation , il n'existe pas d'addition - à M SK3 bouton de cartouche MSC4 et aucun signal d'achat valable à l'action sur.*

Le gestionnaire de la route:

```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',
  });
});
```

Le modèle (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}}
```

Trois modèles utilisés ici. Différenciation Crawler: `SearchEngine` et `VerifiedBot` Obtenir des métadonnées structurées sans signaux commerciaux, ce qu’ils ont réellement besoin de ce type de donnéesM SK1 ciblage à rabais: code de loyauté montré seulement à faible valeurMSC3 le trafic à risque est moins susceptible d’aboutir à un forum sur les bons de commandes `Challenge` est un avertissement, non un blocM SK1 Les séances suspectes peuvent encore acheter.

## Page 2: Dépôt

Automatisation de la fraude, testers de cartes, brute de bonsM SK2 forcers , et retraits scriptés tous convergent à l’étape du débarquementMSC4 L’objectif est une défense en profondeur sans CAPTCHA

*Scénario: un bon d'achatM SK1 coupures de bots testés `/checkout`. Il voit un message mort - message d’achèvement , soumet une poêle de miel MSC3 formulaire rempli M SK4 et reçoit une confirmation silencieuse sans incitation à essayer de nouveau*

```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 route:

```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);
});
```

Le modèle:

```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}}
```

L’élimination de la pomme d’herbe est la technique clé à cet égard. La rétroaction des erreurs instructe les attaquants à iterer ; une confirmation fausse silencieuse enfouie leur temps , et la page de confirmation apparaît identique si l’ordonnance était réelle ou discardée M SK3 le versement express comme un avantage de confiance `<div>` emballer dans une barre de manœuvre partagée partielle (`{{> honeypot}}`) plutôt que de répéter le bloc de style en ligne sur chaque formulaire protégé .

## Page 3: Login

L’adhésion est crédible-territoire d’approvisionnementM SK1Le commerce-la sortie diffère de l’expéditionMSC3un faux positif à la sortie peut perdre une vente

*Scénario: un certificat de crédibilité - attaques d’un script `/login`. Il voit un message de dissuasion , remplit le récipient d’herbe M SK2 et est renversé `/login/denied` avant que l’authentification ne se déroule.*

```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>
```

Le robot voit le dissuasif avant de le soumettre. S’il le soumette en tout état de cause , la poêle d’herbe l’a capturéeM SK2 s’il évite quelque peu la poôle d'herbe, `verdict.isBot` est le serveur final- vérification des côtés avant que toute logique d'authentification ne soit exécutée

## Page 4: Bulletin

Les crawleurs de l’AI sont un autre problème.

*Scénario: GPTBot frappe `/newsletter`. Il voit un message de licence de données au lieu d’un pitch d’abonnementM SK1 Sa soumission est discardée silencieusement .*

```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` n’est pas seulement un autre étiquette hostile, il s’agit d’une classification qui permet une réponse commerciale.

# Modèles d'aide

## Injection du verdict dans les modèles

`sbVerdictInjector` fonctionne après le middleware de détection et met le verdict sur `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` est disponible dans chaque modèle. `res.locals.sbVerdictScript` est une ligne en ligne `<script>` de l'étiquette qui fixe `window.__sb` pour les composants du navigateur.

## Les barres de manutention

Définir `RISK_ORDER` une fois à l’échelle du module ( les Nunjucks et les Aideurs EJS ci-dessous utilisent la même carte ):

```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);
    },
  },
});
```

Dans les modèles:

```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 n’a pas d’aide-bloc, mais les fonctions locales couvrent la plupart des cas de façon propre:

```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 surface du navigateur

## Injection du verdict dans le navigateur

Ajouter `{{{sbVerdictScript}}}` à votre tête de planification. Il se traduit comme

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

Puis chargez le paquet d'éléments:

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

## `<sb-gate>`: montrer ou dissimuler le contenu par bande de risque

```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>`: choisissez le premier cas correspondant

```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>` Choisissez le premier `<sb-case>` dont `max-risk` fit et cache le reste. Le dernier `<sb-case>` avec aucun `max-risk` est la prise-tousM SK1Bois `<sb-gate>` et `<sb-adapt>` écouter le `sb:verdict` événement et re-évaluer si le verdict change après la première peinture

## `<sb-widget>`: serveur-fragments liquides remis

`<sb-gate>` hides content client-side. Si vous avez besoin d'un bot pour recevoir un marquage différent , non camouflage cachéM SK3 vous aurez besoin du fragment qui sera rendu sur le serveur avec le contexte de détection disponible dans la templateMSC4

`<sb-widget>` lire un modèle liquide d'une ligne en ligne `<template>` element, batches toutes les demandes de widget sur la page en une seule rondeM SK1trip, et remplace elle-même par le HTML retourné

```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>
```

Modèles de modèle: `isBot`, `botProbability`, `confidence`, `botType`, `botName`, `riskBand`, `recommendedAction`, `threatScore`, `threatBand`. Plus tout `vars` vous passez de côté du serveur- lors d'un appel `RenderWidget` direct.

Tous `<sb-widget>` les éléments qui se connectent dans le même tick de microtask sont battus en une seule demande. Une page avec cinq widgets fait une ronde

```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
```

# Tableau de synthèse

| Component | Layer S| Ce qu'il fait M|
|---|---|---|
| `styloBotMiddleware` | Express | Annexes `req.stylobot` via les titres / gRPC / REST |
| `styloBotPlugin` | Favoriser | Le même , que le plugin Fastify avec `decorateRequest` |
| `sbVerdictInjector` | Express | Populations `res.locals.sbVerdict` et `sbVerdictScript` |
| Outils d'aide aux barres de manœuvre | Modèles de serveur | `sbGate`, `sbIsBot`, `sbBotType`, `sbMinRisk` aides à bloquer |
Modèles de serveur | `{% sbgate "Low" %}...{% endsbgate %}` |
| Locals EJS | Modèles de serveur | `sbBelowRisk()`, `sbAboveRisk()`, `sbIsBot()`, `sbBotIs()` |
| `<sb-gate>` | Browser | Afficher/ dissimuler le contenu par bande de risqueM SK3 écouter pour `sb:verdict` |
| `<sb-adapt>` | Explorateur | Choisissez le premier cas correspondant d’une liste
| `<sb-widget>` | Browser | Serveur d'acheminement- fragment liquide remisM SK3 battu par tick |
| `StyloBotGrpcClient` | Node / Bun / Deno S| Client gRPC direct pour non--utilisation de logiciels M|

Suivant dans la série de sortie: [**L'architecture de la Sidecar**](/blog/sidecar-architecture), la façon dont le moteur de détection se connecte à des piles NET autres que -. , le Go SDK, , le Caddy plugin et l’interface gRPC qui les relie ensemble

Si vous êtes arrivé ici à partir du côté .NET [Behaviour-Aware ASP.NET UI](/blog/behaviour-aware-ux) couvre les mêmes modèles de storefront avec des aides-étiquettes et des filtres d’action; le réaménagement de la fiabilité qui maintient la mémoire du moteur - stable sous un trafic soutenu est en [Trouver et rétablir une croissance sans limites dans les services longs-Running .NET](/blog/stylobot-release-reliability). moteur en direct , tableaux de bord M SK2 et contrôles commerciaux à [stylobot.net](https://www.stylobot.net).