@helpai/elements 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,643 @@
1
+ # Framework integrations
2
+
3
+ Copy-paste recipes for every major frontend framework + CMS.
4
+
5
+ ---
6
+
7
+ ## Contents
8
+
9
+ - [Next.js](#nextjs)
10
+ - [React (Vite / CRA)](#react)
11
+ - [Vue 3](#vue-3)
12
+ - [Nuxt 3](#nuxt-3)
13
+ - [Svelte / SvelteKit](#svelte--sveltekit)
14
+ - [Astro](#astro)
15
+ - [Remix](#remix)
16
+ - [SolidJS](#solidjs)
17
+ - [Angular](#angular)
18
+ - [WordPress](#wordpress)
19
+ - [Shopify](#shopify)
20
+ - [Webflow](#webflow)
21
+ - [Squarespace / Wix](#squarespace--wix)
22
+
23
+ ---
24
+
25
+ ## Next.js
26
+
27
+ The React kit, `@helpai/elements/react`, is server-render safe and already marked `'use client'`, so it drops straight
28
+ into a layout. Nothing touches the page until it runs in the browser.
29
+
30
+ **App Router** (13+):
31
+
32
+ ```tsx
33
+ // app/layout.tsx
34
+ import { HelpAI } from '@helpai/elements/react';
35
+
36
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
37
+ return (
38
+ <html lang='en'>
39
+ <body>
40
+ {children}
41
+ <HelpAI publicKey={process.env.NEXT_PUBLIC_HELPAI_KEY!} aiAgentDeploymentId={process.env.NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID!} theme='host' />
42
+ </body>
43
+ </html>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ```bash
49
+ # .env.local
50
+ NEXT_PUBLIC_HELPAI_KEY=agent_pk_abc123...
51
+ NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID=01900000-0000-7000-8000-000000000000
52
+ ```
53
+
54
+ A server component can only pass serializable props, so callbacks (`onOpen`, `onMessage`, …) go on a `<HelpAI />`
55
+ rendered from a `'use client'` component of your own. `useHelpAI()` (below) works in any client component.
56
+
57
+ **Pages Router**: render `<HelpAI />` in `pages/_app.tsx` the same way; no `dynamic(..., { ssr: false })` needed.
58
+
59
+ ---
60
+
61
+ ## React
62
+
63
+ The React kit (React 18+) mounts the widget with `<HelpAI />` and drives it from any component with `useHelpAI()`:
64
+
65
+ ```tsx
66
+ import { HelpAI, useHelpAI } from '@helpai/elements/react';
67
+
68
+ export function Root() {
69
+ return (
70
+ <>
71
+ <main>{/* your app */}</main>
72
+ <HelpAI publicKey={import.meta.env.VITE_HELPAI_KEY} aiAgentDeploymentId={import.meta.env.VITE_HELPAI_DEPLOYMENT_ID} theme='host' />
73
+ </>
74
+ );
75
+ }
76
+
77
+ function PricingHelp() {
78
+ const { ask, open, state } = useHelpAI();
79
+ return (
80
+ <>
81
+ <button onClick={() => ask('Which plan fits a team of 10?')}>Ask about plans</button>
82
+ <button onClick={() => open({ to: 'help' })}>Help center {state.unread > 0 && `(${state.unread})`}</button>
83
+ </>
84
+ );
85
+ }
86
+ ```
87
+
88
+ - **`<HelpAI {...options} />`** takes every [widget option](./configuration.md) as a prop; `aiAgentDeploymentId` is
89
+ required. It renders nothing itself: the widget mounts once the page is in the browser, a prop change re-configures
90
+ the live widget without a remount, and unmounting removes it. Callback props (`onOpen`, `onMessage`, …) can be new
91
+ functions on every render; the widget always calls the latest one.
92
+ - **`useHelpAI(widgetId?)`** returns `{ state, ask, open, close, reset }`. `state` is
93
+ [`{ open, ready, unread, conversationId? }`](./api-reference.md#state-and-ready) and re-renders the component when it changes;
94
+ the server render sees the initial state. The commands are stable across renders and do nothing until the widget
95
+ has mounted (`state.ready`). `ask(text)` fills the composer; `ask(text, { send: true })` sends it. Call `reset()`
96
+ when your user logs out.
97
+ - **One import path.** The kit carries the whole widget API (`getInstance`, `hook`, the types), so import everything
98
+ from `@helpai/elements/react`; the package's other entries are separate copies with their own widgets.
99
+ - **Two widgets** on one page: give each `<HelpAI widgetId='support' … />` and read it with `useHelpAI('support')`.
100
+ - **Signed-in users:** pass `userToken`, a function that fetches the token your server signs
101
+ ([Verified users](./security-privacy.md#verified-users-usertoken)). Keep it stable for one user (a module function, or
102
+ `useCallback` keyed on your user id): a different function signs in afresh, and dropping it signs out.
103
+
104
+ The kit bundles the widget and imports only React from your app, so you don't need Preact installed.
105
+
106
+ ### React 19 — use the web component directly
107
+
108
+ React 19+ supports custom elements natively:
109
+
110
+ ```tsx
111
+ // No JS bundle import needed — add a <script> tag or use the web-component sub-entry
112
+ import '@helpai/elements/web-component';
113
+
114
+ function Chat() {
115
+ return <web-ai-chat public-key={import.meta.env.VITE_HELPAI_KEY} ai-agent-deployment-id={import.meta.env.VITE_HELPAI_DEPLOYMENT_ID} theme='auto' />;
116
+ }
117
+ ```
118
+
119
+ ### TypeScript typings
120
+
121
+ `import type {} from '@helpai/elements/jsx/react';` once (in your entry file or a `.d.ts`) registers `<web-ai-chat>`
122
+ in React 19's JSX: every attribute the element reads autocompletes, `ai-agent-deployment-id` is required, and a `ref`
123
+ is the typed element with `ask`, `open`, `state`, `ready` and `reset`. Vue and Svelte have `@helpai/elements/jsx/vue`
124
+ and `@helpai/elements/jsx/svelte` ([JSX typings](./api-reference.md#jsx-typings)).
125
+
126
+ ```tsx
127
+ import { useRef } from 'react';
128
+
129
+ import type {} from '@helpai/elements/jsx/react';
130
+
131
+ function Chat() {
132
+ const chat = useRef<HTMLElementTagNameMap['web-ai-chat']>(null);
133
+ return (
134
+ <>
135
+ <button onClick={() => chat.current?.ask('Which plan fits a team of 10?')}>Ask about plans</button>
136
+ <web-ai-chat ref={chat} public-key='agent_pk_...' ai-agent-deployment-id='...' theme='host' />
137
+ </>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Vue 3
145
+
146
+ Composition API:
147
+
148
+ ```vue
149
+ <!-- components/Chat.vue -->
150
+ <script setup lang="ts">
151
+ import { onMounted, onBeforeUnmount } from 'vue';
152
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
153
+
154
+ let instance: ChatWidgetInstance | null = null;
155
+
156
+ onMounted(() => {
157
+ instance = init({
158
+ publicKey: import.meta.env.VITE_HELPAI_KEY,
159
+ aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID,
160
+ });
161
+ });
162
+
163
+ onBeforeUnmount(() => {
164
+ instance?.destroy();
165
+ });
166
+ </script>
167
+
168
+ <template>
169
+ <!-- Nothing to render — the widget mounts its own DOM -->
170
+ </template>
171
+ ```
172
+
173
+ ```vue
174
+ <!-- App.vue -->
175
+ <script setup lang="ts">
176
+ import Chat from './components/Chat.vue';
177
+ </script>
178
+
179
+ <template>
180
+ <main>
181
+ <!-- your app -->
182
+ </main>
183
+ <Chat />
184
+ </template>
185
+ ```
186
+
187
+ ### Web component approach
188
+
189
+ Vue 3 supports custom elements natively. Register the web-component
190
+ sub-entry, then use the tag:
191
+
192
+ ```ts
193
+ // main.ts
194
+ import { createApp } from 'vue';
195
+ import '@helpai/elements/web-component';
196
+
197
+ import App from './App.vue';
198
+
199
+ const app = createApp(App);
200
+ // Tell Vue to treat `web-ai-chat` as a custom element (not a Vue component)
201
+ app.config.compilerOptions.isCustomElement = (tag) => tag === 'web-ai-chat';
202
+ app.mount('#app');
203
+ ```
204
+
205
+ ```vue
206
+ <template>
207
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto" />
208
+ </template>
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Nuxt 3
214
+
215
+ ```ts
216
+ // plugins/web-ai-chat.client.ts
217
+ import { init } from '@helpai/elements';
218
+
219
+ export default defineNuxtPlugin((nuxtApp) => {
220
+ const chat = init({
221
+ publicKey: useRuntimeConfig().public.helpaiKey,
222
+ aiAgentDeploymentId: useRuntimeConfig().public.helpaiDeploymentId,
223
+ });
224
+
225
+ nuxtApp.hook('app:beforeMount', () => {
226
+ // Widget already mounted by init(). This hook is just a reference.
227
+ });
228
+
229
+ // Clean up on app tear-down (rarely triggered in SPA navigation)
230
+ nuxtApp.hook('app:suspense:resolve', () => {
231
+ // No-op — widget stays across navigations
232
+ });
233
+
234
+ return {
235
+ provide: {
236
+ chat,
237
+ },
238
+ };
239
+ });
240
+ ```
241
+
242
+ ```ts
243
+ // nuxt.config.ts
244
+ export default defineNuxtConfig({
245
+ runtimeConfig: {
246
+ public: {
247
+ helpaiKey: process.env.NUXT_PUBLIC_HELPAI_KEY,
248
+ helpaiDeploymentId: process.env.NUXT_PUBLIC_HELPAI_DEPLOYMENT_ID,
249
+ },
250
+ },
251
+ });
252
+ ```
253
+
254
+ Note the `.client.ts` suffix — ensures the plugin only runs in the
255
+ browser.
256
+
257
+ ---
258
+
259
+ ## Svelte / SvelteKit
260
+
261
+ **SvelteKit**:
262
+
263
+ ```svelte
264
+ <!-- src/routes/+layout.svelte -->
265
+ <script lang="ts">
266
+ import { onMount, onDestroy } from 'svelte';
267
+ import { browser } from '$app/environment';
268
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
269
+ import { PUBLIC_HELPAI_DEPLOYMENT_ID, PUBLIC_HELPAI_KEY } from '$env/static/public';
270
+
271
+ let instance: ChatWidgetInstance | null = null;
272
+
273
+ onMount(() => {
274
+ if (!browser) return;
275
+ instance = init({ publicKey: PUBLIC_HELPAI_KEY, aiAgentDeploymentId: PUBLIC_HELPAI_DEPLOYMENT_ID });
276
+ });
277
+
278
+ onDestroy(() => {
279
+ instance?.destroy();
280
+ });
281
+ </script>
282
+
283
+ <slot />
284
+ ```
285
+
286
+ **Plain Svelte** (no SvelteKit):
287
+
288
+ ```svelte
289
+ <!-- App.svelte -->
290
+ <script lang="ts">
291
+ import { onMount, onDestroy } from 'svelte';
292
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
293
+
294
+ let instance: ChatWidgetInstance | null = null;
295
+
296
+ onMount(() => {
297
+ instance = init({ publicKey: import.meta.env.VITE_HELPAI_KEY, aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID });
298
+ });
299
+
300
+ onDestroy(() => {
301
+ instance?.destroy();
302
+ });
303
+ </script>
304
+
305
+ <main>
306
+ <!-- your app -->
307
+ </main>
308
+ ```
309
+
310
+ ### Web component in Svelte
311
+
312
+ Svelte handles custom elements natively:
313
+
314
+ ```svelte
315
+ <script lang="ts">
316
+ import '@helpai/elements/web-component';
317
+ </script>
318
+
319
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto" />
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Astro
325
+
326
+ Astro is SSR-first; wrap the widget in a client directive:
327
+
328
+ ```astro
329
+ ---
330
+ // src/components/Chat.astro
331
+ const publicKey = import.meta.env.PUBLIC_HELPAI_KEY;
332
+ const aiAgentDeploymentId = import.meta.env.PUBLIC_HELPAI_DEPLOYMENT_ID;
333
+ ---
334
+
335
+ <script define:vars={{ publicKey, aiAgentDeploymentId }}>
336
+ import('@helpai/elements').then(({ init }) => {
337
+ init({ publicKey, aiAgentDeploymentId });
338
+ });
339
+ </script>
340
+ ```
341
+
342
+ ```astro
343
+ ---
344
+ // src/layouts/Layout.astro
345
+ import Chat from '../components/Chat.astro';
346
+ ---
347
+ <html>
348
+ <body>
349
+ <slot />
350
+ <Chat />
351
+ </body>
352
+ </html>
353
+ ```
354
+
355
+ Or use the web component (even simpler — no JS needed):
356
+
357
+ ```astro
358
+ ---
359
+ import '@helpai/elements/web-component';
360
+ ---
361
+
362
+ <web-ai-chat public-key={Astro.env.PUBLIC_HELPAI_KEY} ai-agent-deployment-id={Astro.env.PUBLIC_HELPAI_DEPLOYMENT_ID} />
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Remix
368
+
369
+ Remix (and React Router 7) render on the server, which the [React kit](#react) handles: pass the keys from the loader.
370
+
371
+ ```tsx
372
+ // app/root.tsx
373
+ import { HelpAI } from '@helpai/elements/react';
374
+ import { Outlet, useLoaderData } from '@remix-run/react';
375
+
376
+ export async function loader() {
377
+ return {
378
+ helpaiKey: process.env.HELPAI_PUBLIC_KEY ?? '',
379
+ helpaiDeploymentId: process.env.HELPAI_DEPLOYMENT_ID ?? '',
380
+ };
381
+ }
382
+
383
+ export default function App() {
384
+ const { helpaiKey, helpaiDeploymentId } = useLoaderData<typeof loader>();
385
+
386
+ return (
387
+ <html>
388
+ <body>
389
+ <Outlet />
390
+ {helpaiKey && helpaiDeploymentId && <HelpAI publicKey={helpaiKey} aiAgentDeploymentId={helpaiDeploymentId} />}
391
+ </body>
392
+ </html>
393
+ );
394
+ }
395
+ ```
396
+
397
+ ---
398
+
399
+ ## SolidJS
400
+
401
+ ```tsx
402
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
403
+ import { onMount, onCleanup } from 'solid-js';
404
+
405
+ export function Chat() {
406
+ let instance: ChatWidgetInstance | null = null;
407
+
408
+ onMount(() => {
409
+ instance = init({ publicKey: import.meta.env.VITE_HELPAI_KEY, aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID });
410
+ });
411
+
412
+ onCleanup(() => {
413
+ instance?.destroy();
414
+ });
415
+
416
+ return null;
417
+ }
418
+ ```
419
+
420
+ Solid also supports custom elements natively:
421
+
422
+ ```tsx
423
+ import '@helpai/elements/web-component';
424
+
425
+ export default function App() {
426
+ return <web-ai-chat public-key='agent_pk_...' ai-agent-deployment-id='01900000-0000-7000-8000-000000000000' />;
427
+ }
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Angular
433
+
434
+ Angular needs `CUSTOM_ELEMENTS_SCHEMA` to accept the custom tag:
435
+
436
+ ```ts
437
+ // src/app/app.module.ts
438
+ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
439
+ import '@helpai/elements/web-component';
440
+
441
+ @NgModule({
442
+ // ...
443
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
444
+ })
445
+ export class AppModule {}
446
+ ```
447
+
448
+ ```html
449
+ <!-- app.component.html -->
450
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto"></web-ai-chat>
451
+ ```
452
+
453
+ For programmatic control:
454
+
455
+ ```ts
456
+ // chat.service.ts
457
+ import { Injectable } from '@angular/core';
458
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
459
+
460
+ import { environment } from '../environments/environment';
461
+
462
+ @Injectable({ providedIn: 'root' })
463
+ export class ChatService {
464
+ private instance: ChatWidgetInstance | null = null;
465
+
466
+ mount() {
467
+ if (!this.instance) {
468
+ this.instance = init({ publicKey: environment.helpaiKey, aiAgentDeploymentId: environment.helpaiDeploymentId });
469
+ }
470
+ }
471
+
472
+ destroy() {
473
+ this.instance?.destroy();
474
+ this.instance = null;
475
+ }
476
+
477
+ open() {
478
+ this.instance?.open();
479
+ }
480
+ }
481
+ ```
482
+
483
+ ---
484
+
485
+ ## WordPress
486
+
487
+ Add the snippet via the Theme Header/Footer plugin OR directly in
488
+ your theme's `header.php`:
489
+
490
+ ```html
491
+ <!-- Before </head> -->
492
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_abc123..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
493
+ ```
494
+
495
+ ### Via a plugin
496
+
497
+ Any "Insert Headers and Footers" plugin works:
498
+
499
+ 1. Install the plugin (e.g., "Insert Headers and Footers" by WPBeginner).
500
+ 2. Settings → Insert Headers and Footers.
501
+ 3. Paste the script tag into the "Scripts in Header" box.
502
+ 4. Save.
503
+
504
+ ### Via functions.php
505
+
506
+ ```php
507
+ <?php
508
+ // Add to your theme's functions.php
509
+ function helpai_enqueue_chat() {
510
+ ?>
511
+ <script
512
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
513
+ data-public-key="agent_pk_abc123..."
514
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
515
+ defer
516
+ ></script>
517
+ <?php
518
+ }
519
+ add_action('wp_head', 'helpai_enqueue_chat');
520
+ ```
521
+
522
+ ### Personalization for logged-in users
523
+
524
+ ```php
525
+ <?php
526
+ function helpai_enqueue_chat_personalized() {
527
+ $user = wp_get_current_user();
528
+ $attrs = 'data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"';
529
+ if ($user->ID) {
530
+ $attrs .= ' data-user-id="' . esc_attr($user->ID) . '"';
531
+ $attrs .= ' data-user-email="' . esc_attr($user->user_email) . '"';
532
+ $attrs .= ' data-user-name="' . esc_attr($user->display_name) . '"';
533
+ $attrs .= ' data-user-context=\'' . esc_attr(json_encode(array("plan" => "pro"))) . '\'';
534
+ }
535
+ ?>
536
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" <?php echo $attrs; ?> defer></script>
537
+ <?php
538
+ }
539
+ add_action('wp_head', 'helpai_enqueue_chat_personalized');
540
+ ```
541
+
542
+ > Note: `data-user-*` / `data-page-*` are host-asserted context for the
543
+ > AI, never an auth credential — the browser deployment is selected by
544
+ > `data-public-key` + `data-ai-agent-deployment-id`. Explicit
545
+ > scalar attributes win over keys in the `data-user-context` /
546
+ > `data-page-context` JSON.
547
+
548
+ ---
549
+
550
+ ## Shopify
551
+
552
+ Shopify has a "custom pixels" and "additional scripts" workflow.
553
+ Easiest path:
554
+
555
+ 1. In your admin: **Online Store → Themes → Edit code**.
556
+ 2. Open `theme.liquid`.
557
+ 3. Paste before `</head>`:
558
+
559
+ ```html
560
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
561
+ ```
562
+
563
+ ### With Shopify customer data
564
+
565
+ ```html
566
+ {% if customer %}
567
+ <script
568
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
569
+ data-public-key="agent_pk_..."
570
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
571
+ data-user-id="{{ customer.id }}"
572
+ data-user-email="{{ customer.email }}"
573
+ data-user-name="{{ customer.first_name }} {{ customer.last_name }}"
574
+ data-page-area="orders"
575
+ defer
576
+ ></script>
577
+ {% else %}
578
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
579
+ {% endif %}
580
+ ```
581
+
582
+ ---
583
+
584
+ ## Webflow
585
+
586
+ Webflow lets you add site-wide custom code:
587
+
588
+ 1. Site settings → **Custom code → Head code**.
589
+ 2. Paste:
590
+
591
+ ```html
592
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
593
+ ```
594
+
595
+ 3. Save + publish.
596
+
597
+ Page-specific customization: open the page → **Page settings →
598
+ Inside `<head>` tag** and paste a page-specific snippet.
599
+
600
+ ---
601
+
602
+ ## Squarespace / Wix
603
+
604
+ **Squarespace:**
605
+
606
+ 1. Settings → **Advanced → Code Injection**.
607
+ 2. Paste the script tag into the **Header** section.
608
+
609
+ **Wix:**
610
+
611
+ 1. Settings → **Custom Code**.
612
+ 2. Add new snippet.
613
+ 3. Paste + choose "All pages" + "Head" placement.
614
+
615
+ ---
616
+
617
+ ## Framework-agnostic troubleshooting
618
+
619
+ **The widget doesn't appear in SSR previews** — that's expected. The
620
+ widget is client-only. It appears after hydration / mount.
621
+
622
+ **The widget flashes open then closes on SPA navigation** — you're
623
+ probably calling `destroy()` then `init()` on every route change. Use
624
+ `init()` alone — it's idempotent. If there's an existing instance at
625
+ the same `widgetId`, it merges the new options in.
626
+
627
+ **Hot module replacement breaks the widget** — Next.js / Vite HMR
628
+ sometimes mounts duplicate instances. Always pair your `useEffect` /
629
+ `onMounted` with a cleanup that calls `destroy()`.
630
+
631
+ **CSP blocks the widget** — the widget inlines styles inside a shadow
632
+ root (requires `style-src 'unsafe-inline'` or a nonce pattern). See
633
+ [Security & privacy § CSP](./security-privacy.md#content-security-policy).
634
+
635
+ More: [Troubleshooting](./troubleshooting.md).
636
+
637
+ ---
638
+
639
+ ## Next
640
+
641
+ - [Recipes](./recipes.md) — programmatic control patterns.
642
+ - [Events & hooks](./events-and-hooks.md) — analytics integration.
643
+ - [Configuration](./configuration.md) — every option in detail.