@helpai/elements 0.42.1 → 0.43.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.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-CB6xjMr0.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-paIqjCiE.js';
2
2
  import 'zod';
3
3
 
4
4
  /**
@@ -1371,6 +1371,30 @@ interface EventMap {
1371
1371
  toolCallId: string;
1372
1372
  approved: boolean;
1373
1373
  };
1374
+ /** A content list (news / help / home block) rendered. */
1375
+ contentView: {
1376
+ section: string;
1377
+ tags?: string;
1378
+ count: number;
1379
+ };
1380
+ /** An article / content item was opened. */
1381
+ contentOpen: {
1382
+ contentId: string;
1383
+ tags?: string;
1384
+ };
1385
+ /** The help search ran. Length + hit count only — never the query text. */
1386
+ contentSearch: {
1387
+ queryLength: number;
1388
+ hitCount: number;
1389
+ };
1390
+ /** An opened article was read (dwell threshold passed). Fires once per open. */
1391
+ contentRead: {
1392
+ contentId: string;
1393
+ };
1394
+ /** A link inside an opened article was followed. */
1395
+ contentLinkClick: {
1396
+ contentId: string;
1397
+ };
1374
1398
  /** Files attached (drag-drop, file picker, paste). */
1375
1399
  attach: {
1376
1400
  count: number;
package/index.mjs CHANGED
@@ -5268,6 +5268,7 @@ function MessageList({
5268
5268
  if (!el) return;
5269
5269
  const last = messages.value.at(-1);
5270
5270
  if (!last) return;
5271
+ if (last.status !== "streaming" && !messages.value.some((m) => m.role === "user")) return;
5271
5272
  const pinIfNear = () => {
5272
5273
  if (detachedRef.current || inInteractionGrace()) return;
5273
5274
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
@@ -6224,7 +6225,7 @@ function ArticleRow({ article, nav }) {
6224
6225
  }
6225
6226
  );
6226
6227
  }
6227
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6228
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6228
6229
  const tags = config.contentTags;
6229
6230
  const [state, setState] = useState8("loading");
6230
6231
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6236,8 +6237,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6236
6237
  setState("loading");
6237
6238
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6238
6239
  if (cancelled) return;
6239
- setItems(res.items ?? []);
6240
+ const rows = res.items ?? [];
6241
+ setItems(rows);
6240
6242
  setState("loaded");
6243
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6241
6244
  }).catch((err) => {
6242
6245
  if (cancelled) return;
6243
6246
  log13.warn("listContent (help) failed", { err });
@@ -6247,8 +6250,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6247
6250
  return () => {
6248
6251
  cancelled = true;
6249
6252
  };
6250
- }, [transport, tags, reloadKey]);
6253
+ }, [transport, tags, reloadKey, bus]);
6251
6254
  const results = useMemo2(() => fuzzySearch(items, query), [items, query]);
6255
+ useEffect11(() => {
6256
+ const q = query.trim();
6257
+ if (!q) return;
6258
+ const timer = setTimeout(() => bus.emit("contentSearch", { queryLength: q.length, hitCount: results.length }), 600);
6259
+ return () => clearTimeout(timer);
6260
+ }, [query, results, bus]);
6252
6261
  function renderBody() {
6253
6262
  if (query.trim().length > 0) {
6254
6263
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6303,7 +6312,7 @@ function resolveGreeting(props2) {
6303
6312
  }
6304
6313
  var openContent = (nav, item) => item.url ? nav.push({ kind: "iframe", url: item.url, title: item.title }) : nav.push({ kind: "content", id: item.id, title: item.title });
6305
6314
  function HomeRoot(props2) {
6306
- const { transport, strings, config, nav, panelProps } = props2;
6315
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6307
6316
  const [recent, setRecent] = useState9(null);
6308
6317
  const [content, setContent] = useState9([]);
6309
6318
  const tagsKey = config.contentTags?.join(",");
@@ -6318,11 +6327,16 @@ function HomeRoot(props2) {
6318
6327
  useEffect12(() => {
6319
6328
  if (!config.contentTags?.length) return;
6320
6329
  let cancelled = false;
6321
- transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
6330
+ transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => {
6331
+ if (cancelled) return;
6332
+ const rows = res.items ?? [];
6333
+ setContent(rows);
6334
+ bus.emit("contentView", { section: "home", tags: tagsKey, count: rows.length });
6335
+ }).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
6322
6336
  return () => {
6323
6337
  cancelled = true;
6324
6338
  };
6325
- }, [transport, tagsKey]);
6339
+ }, [transport, tagsKey, bus]);
6326
6340
  const greeting = resolveGreeting(props2);
6327
6341
  const avatars = config.userAvatars ?? [];
6328
6342
  const status = config.status;
@@ -6394,7 +6408,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6394
6408
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6395
6409
  var p26 = BRAND.cssPrefix;
6396
6410
  var log15 = logger.scope("news");
6397
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6411
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6398
6412
  const tags = config.contentTags;
6399
6413
  const [state, setState] = useState10("loading");
6400
6414
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6405,8 +6419,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6405
6419
  setState("loading");
6406
6420
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6407
6421
  if (cancelled) return;
6408
- setItems(res.items ?? []);
6422
+ const rows = res.items ?? [];
6423
+ setItems(rows);
6409
6424
  setState("loaded");
6425
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6410
6426
  }).catch((err) => {
6411
6427
  if (cancelled) return;
6412
6428
  log15.warn("listContent (news) failed", { err });
@@ -6416,7 +6432,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6416
6432
  return () => {
6417
6433
  cancelled = true;
6418
6434
  };
6419
- }, [transport, tags, reloadKey]);
6435
+ }, [transport, tags, reloadKey, bus]);
6420
6436
  function renderBody() {
6421
6437
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6422
6438
  if (state === "error") {
@@ -6524,7 +6540,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6524
6540
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6525
6541
  var p29 = BRAND.cssPrefix;
6526
6542
  var log16 = logger.scope("content");
6527
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6543
+ var READ_DWELL_MS = 5e3;
6544
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6528
6545
  const [item, setItem] = useState11(null);
6529
6546
  const [failed, setFailed] = useState11(false);
6530
6547
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6538,8 +6555,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6538
6555
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6539
6556
  if (cancelled) return;
6540
6557
  const found = res.items[0];
6541
- if (found) setItem(found);
6542
- else setFailed(true);
6558
+ if (found) {
6559
+ setItem(found);
6560
+ bus.emit("contentOpen", { contentId: id, tags: found.tags?.length ? found.tags.join(",") : void 0 });
6561
+ } else setFailed(true);
6543
6562
  }).catch((err) => {
6544
6563
  if (cancelled) return;
6545
6564
  log16.warn("listContent (reader) failed", { err });
@@ -6548,11 +6567,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6548
6567
  return () => {
6549
6568
  cancelled = true;
6550
6569
  };
6551
- }, [transport, id, reloadKey]);
6570
+ }, [transport, id, reloadKey, bus]);
6571
+ useEffect14(() => {
6572
+ if (!item) return;
6573
+ const timer = setTimeout(() => bus.emit("contentRead", { contentId: id }), READ_DWELL_MS);
6574
+ return () => clearTimeout(timer);
6575
+ }, [item, id, bus]);
6576
+ const onArticleClick = useCallback3(
6577
+ (e) => {
6578
+ const anchor = e.target?.closest?.("a");
6579
+ const href = anchor?.getAttribute("href") ?? "";
6580
+ if (href && !href.startsWith("#")) bus.emit("contentLinkClick", { contentId: id });
6581
+ },
6582
+ [bus, id]
6583
+ );
6552
6584
  function renderBody() {
6553
6585
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6554
6586
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6555
- return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6587
+ return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, onClick: onArticleClick, children: [
6556
6588
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6557
6589
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6558
6590
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6580,6 +6612,7 @@ function MessengerHome({
6580
6612
  panelProps,
6581
6613
  enabledModules,
6582
6614
  nav,
6615
+ bus,
6583
6616
  unreadCount,
6584
6617
  onSelectConversation,
6585
6618
  onStartNewConversation,
@@ -6641,6 +6674,7 @@ function MessengerHome({
6641
6674
  config: module,
6642
6675
  visitorId: panelProps.visitorId,
6643
6676
  nav: moduleNav,
6677
+ bus,
6644
6678
  panelProps
6645
6679
  });
6646
6680
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6655,6 +6689,7 @@ function MessengerHome({
6655
6689
  title: top.title,
6656
6690
  transport: panelProps.transport,
6657
6691
  strings,
6692
+ bus,
6658
6693
  onBack: nav.pop,
6659
6694
  actions: plainActions
6660
6695
  }
@@ -7687,6 +7722,7 @@ function App({ options, hostElement, bus }) {
7687
7722
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7688
7723
  enabledModules,
7689
7724
  nav: homeNav,
7725
+ bus,
7690
7726
  unreadCount: unreadCountSig.value,
7691
7727
  onSelectConversation,
7692
7728
  onStartNewConversation: handleNewChat,
@@ -7813,6 +7849,12 @@ var TRACKED = {
7813
7849
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7814
7850
  toolResult: () => void 0,
7815
7851
  toolDecision: (p33) => ({ approved: p33.approved }),
7852
+ // Content — ids, tags + counts only; titles/bodies never ride.
7853
+ contentView: (p33) => ({ section: p33.section, tags: p33.tags, count: p33.count }),
7854
+ contentOpen: (p33) => ({ contentId: p33.contentId, tags: p33.tags }),
7855
+ contentSearch: (p33) => ({ qlen: p33.queryLength, hits: p33.hitCount }),
7856
+ contentRead: (p33) => ({ contentId: p33.contentId }),
7857
+ contentLinkClick: (p33) => ({ contentId: p33.contentId }),
7816
7858
  // Composer / attachments / voice.
7817
7859
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7818
7860
  voiceStart: () => void 0,
@@ -8031,6 +8073,12 @@ var EVENT_NAMES = [
8031
8073
  "formSubmit",
8032
8074
  "toolResult",
8033
8075
  "toolDecision",
8076
+ // Content (news / help / home articles)
8077
+ "contentView",
8078
+ "contentOpen",
8079
+ "contentSearch",
8080
+ "contentRead",
8081
+ "contentLinkClick",
8034
8082
  // Composer / attachments / voice
8035
8083
  "attach",
8036
8084
  "removeAttachment",
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  ],
81
81
  "type": "module",
82
82
  "types": "./index.d.ts",
83
- "version": "0.42.1"
83
+ "version": "0.43.0"
84
84
  }
package/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-CB6xjMr0.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-paIqjCiE.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -56,9 +56,9 @@ declare const presentationSchema: z.ZodObject<{
56
56
  inset: z.ZodOptional<z.ZodString>;
57
57
  initialSize: z.ZodDefault<z.ZodEnum<{
58
58
  fullscreen: "fullscreen";
59
- normal: "normal";
60
59
  expanded: "expanded";
61
60
  auto: "auto";
61
+ normal: "normal";
62
62
  }>>;
63
63
  autoSizeBreakpoint: z.ZodDefault<z.ZodNumber>;
64
64
  }, z.core.$loose>>;
@@ -239,9 +239,9 @@ type LauncherOptions = z.infer<typeof launcherOptionsSchema>;
239
239
 
240
240
  declare const initialSizeSchema: z.ZodEnum<{
241
241
  fullscreen: "fullscreen";
242
- normal: "normal";
243
242
  expanded: "expanded";
244
243
  auto: "auto";
244
+ normal: "normal";
245
245
  }>;
246
246
  declare const resizeOptionsSchema: z.ZodObject<{
247
247
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -268,9 +268,9 @@ declare const sizeOptionsSchema: z.ZodObject<{
268
268
  inset: z.ZodOptional<z.ZodString>;
269
269
  initialSize: z.ZodDefault<z.ZodEnum<{
270
270
  fullscreen: "fullscreen";
271
- normal: "normal";
272
271
  expanded: "expanded";
273
272
  auto: "auto";
273
+ normal: "normal";
274
274
  }>>;
275
275
  autoSizeBreakpoint: z.ZodDefault<z.ZodNumber>;
276
276
  }, z.core.$loose>;
@@ -322,40 +322,40 @@ type FeatureFlags = z.infer<typeof featureFlagsSchema>;
322
322
  */
323
323
 
324
324
  declare const actionNameSchema: z.ZodEnum<{
325
+ close: "close";
325
326
  expand: "expand";
326
327
  fullscreen: "fullscreen";
327
- close: "close";
328
- language: "language";
328
+ clear: "clear";
329
329
  theme: "theme";
330
+ language: "language";
330
331
  textSize: "textSize";
331
332
  history: "history";
332
- clear: "clear";
333
333
  sound: "sound";
334
334
  }>;
335
335
  type ActionName = z.infer<typeof actionNameSchema>;
336
336
  declare const headerActionsSchema: z.ZodArray<z.ZodEnum<{
337
+ close: "close";
337
338
  expand: "expand";
338
339
  fullscreen: "fullscreen";
339
- close: "close";
340
- language: "language";
340
+ clear: "clear";
341
341
  theme: "theme";
342
+ language: "language";
342
343
  textSize: "textSize";
343
344
  history: "history";
344
- clear: "clear";
345
345
  sound: "sound";
346
346
  }>>;
347
347
  type HeaderActions = z.infer<typeof headerActionsSchema>;
348
348
  /** Section wrapper — `actions` list wrapped under `header` in the dashboard form. */
349
349
  declare const headerSchema: z.ZodObject<{
350
350
  actions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
351
+ close: "close";
351
352
  expand: "expand";
352
353
  fullscreen: "fullscreen";
353
- close: "close";
354
- language: "language";
354
+ clear: "clear";
355
355
  theme: "theme";
356
+ language: "language";
356
357
  textSize: "textSize";
357
358
  history: "history";
358
- clear: "clear";
359
359
  sound: "sound";
360
360
  }>>>;
361
361
  }, z.core.$loose>;
@@ -371,33 +371,33 @@ type HeaderOptions = z.infer<typeof headerSchema>;
371
371
  */
372
372
 
373
373
  declare const feedbackEventSchema: z.ZodEnum<{
374
+ voiceStart: "voiceStart";
375
+ voiceStop: "voiceStop";
374
376
  error: "error";
375
377
  messageReceived: "messageReceived";
376
378
  messageSent: "messageSent";
377
- voiceStart: "voiceStart";
378
- voiceStop: "voiceStop";
379
379
  }>;
380
380
  type FeedbackEvent = z.infer<typeof feedbackEventSchema>;
381
381
  declare const soundOptionsSchema: z.ZodObject<{
382
382
  enabled: z.ZodDefault<z.ZodBoolean>;
383
383
  volume: z.ZodDefault<z.ZodNumber>;
384
384
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
385
+ voiceStart: "voiceStart";
386
+ voiceStop: "voiceStop";
385
387
  error: "error";
386
388
  messageReceived: "messageReceived";
387
389
  messageSent: "messageSent";
388
- voiceStart: "voiceStart";
389
- voiceStop: "voiceStop";
390
390
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>>;
391
391
  }, z.core.$loose>;
392
392
  type SoundOptions = z.infer<typeof soundOptionsSchema>;
393
393
  declare const hapticsOptionsSchema: z.ZodObject<{
394
394
  enabled: z.ZodDefault<z.ZodBoolean>;
395
395
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
396
+ voiceStart: "voiceStart";
397
+ voiceStop: "voiceStop";
396
398
  error: "error";
397
399
  messageReceived: "messageReceived";
398
400
  messageSent: "messageSent";
399
- voiceStart: "voiceStart";
400
- voiceStop: "voiceStop";
401
401
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodArray<z.ZodNumber>]>>>;
402
402
  }, z.core.$loose>;
403
403
  type HapticsOptions = z.infer<typeof hapticsOptionsSchema>;
@@ -406,21 +406,21 @@ declare const feedbackSchema: z.ZodObject<{
406
406
  enabled: z.ZodDefault<z.ZodBoolean>;
407
407
  volume: z.ZodDefault<z.ZodNumber>;
408
408
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
409
+ voiceStart: "voiceStart";
410
+ voiceStop: "voiceStop";
409
411
  error: "error";
410
412
  messageReceived: "messageReceived";
411
413
  messageSent: "messageSent";
412
- voiceStart: "voiceStart";
413
- voiceStop: "voiceStop";
414
414
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>>;
415
415
  }, z.core.$loose>>;
416
416
  haptics: z.ZodOptional<z.ZodObject<{
417
417
  enabled: z.ZodDefault<z.ZodBoolean>;
418
418
  events: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
419
+ voiceStart: "voiceStart";
420
+ voiceStop: "voiceStop";
419
421
  error: "error";
420
422
  messageReceived: "messageReceived";
421
423
  messageSent: "messageSent";
422
- voiceStart: "voiceStart";
423
- voiceStop: "voiceStop";
424
424
  }> & z.core.$partial, z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodArray<z.ZodNumber>]>>>;
425
425
  }, z.core.$loose>>;
426
426
  }, z.core.$loose>;
@@ -855,18 +855,18 @@ type I18nOptions = z.infer<typeof i18nSchema>;
855
855
  */
856
856
 
857
857
  declare const moduleLayoutSchema: z.ZodEnum<{
858
+ home: "home";
858
859
  chat: "chat";
859
860
  help: "help";
860
- home: "home";
861
861
  news: "news";
862
862
  }>;
863
863
  type ModuleLayout = z.infer<typeof moduleLayoutSchema>;
864
864
  declare const moduleSchema: z.ZodObject<{
865
865
  label: z.ZodString;
866
866
  layout: z.ZodEnum<{
867
+ home: "home";
867
868
  chat: "chat";
868
869
  help: "help";
869
- home: "home";
870
870
  news: "news";
871
871
  }>;
872
872
  contentTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -892,9 +892,9 @@ type ModuleOptions = z.infer<typeof moduleSchema>;
892
892
  declare const modulesSchema: z.ZodArray<z.ZodObject<{
893
893
  label: z.ZodString;
894
894
  layout: z.ZodEnum<{
895
+ home: "home";
895
896
  chat: "chat";
896
897
  help: "help";
897
- home: "home";
898
898
  news: "news";
899
899
  }>;
900
900
  contentTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
package/web-component.mjs CHANGED
@@ -5227,6 +5227,7 @@ function MessageList({
5227
5227
  if (!el) return;
5228
5228
  const last = messages.value.at(-1);
5229
5229
  if (!last) return;
5230
+ if (last.status !== "streaming" && !messages.value.some((m) => m.role === "user")) return;
5230
5231
  const pinIfNear = () => {
5231
5232
  if (detachedRef.current || inInteractionGrace()) return;
5232
5233
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
@@ -6183,7 +6184,7 @@ function ArticleRow({ article, nav }) {
6183
6184
  }
6184
6185
  );
6185
6186
  }
6186
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6187
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6187
6188
  const tags = config.contentTags;
6188
6189
  const [state, setState] = useState8("loading");
6189
6190
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6195,8 +6196,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6195
6196
  setState("loading");
6196
6197
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6197
6198
  if (cancelled) return;
6198
- setItems(res.items ?? []);
6199
+ const rows = res.items ?? [];
6200
+ setItems(rows);
6199
6201
  setState("loaded");
6202
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6200
6203
  }).catch((err) => {
6201
6204
  if (cancelled) return;
6202
6205
  log12.warn("listContent (help) failed", { err });
@@ -6206,8 +6209,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6206
6209
  return () => {
6207
6210
  cancelled = true;
6208
6211
  };
6209
- }, [transport, tags, reloadKey]);
6212
+ }, [transport, tags, reloadKey, bus]);
6210
6213
  const results = useMemo2(() => fuzzySearch(items, query), [items, query]);
6214
+ useEffect11(() => {
6215
+ const q = query.trim();
6216
+ if (!q) return;
6217
+ const timer = setTimeout(() => bus.emit("contentSearch", { queryLength: q.length, hitCount: results.length }), 600);
6218
+ return () => clearTimeout(timer);
6219
+ }, [query, results, bus]);
6211
6220
  function renderBody() {
6212
6221
  if (query.trim().length > 0) {
6213
6222
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6262,7 +6271,7 @@ function resolveGreeting(props2) {
6262
6271
  }
6263
6272
  var openContent = (nav, item) => item.url ? nav.push({ kind: "iframe", url: item.url, title: item.title }) : nav.push({ kind: "content", id: item.id, title: item.title });
6264
6273
  function HomeRoot(props2) {
6265
- const { transport, strings, config, nav, panelProps } = props2;
6274
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6266
6275
  const [recent, setRecent] = useState9(null);
6267
6276
  const [content, setContent] = useState9([]);
6268
6277
  const tagsKey = config.contentTags?.join(",");
@@ -6277,11 +6286,16 @@ function HomeRoot(props2) {
6277
6286
  useEffect12(() => {
6278
6287
  if (!config.contentTags?.length) return;
6279
6288
  let cancelled = false;
6280
- transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log13.warn("listContent (home) failed", { err }));
6289
+ transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => {
6290
+ if (cancelled) return;
6291
+ const rows = res.items ?? [];
6292
+ setContent(rows);
6293
+ bus.emit("contentView", { section: "home", tags: tagsKey, count: rows.length });
6294
+ }).catch((err) => !cancelled && log13.warn("listContent (home) failed", { err }));
6281
6295
  return () => {
6282
6296
  cancelled = true;
6283
6297
  };
6284
- }, [transport, tagsKey]);
6298
+ }, [transport, tagsKey, bus]);
6285
6299
  const greeting = resolveGreeting(props2);
6286
6300
  const avatars = config.userAvatars ?? [];
6287
6301
  const status = config.status;
@@ -6353,7 +6367,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6353
6367
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6354
6368
  var p26 = BRAND.cssPrefix;
6355
6369
  var log14 = logger.scope("news");
6356
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6370
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6357
6371
  const tags = config.contentTags;
6358
6372
  const [state, setState] = useState10("loading");
6359
6373
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6364,8 +6378,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6364
6378
  setState("loading");
6365
6379
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6366
6380
  if (cancelled) return;
6367
- setItems(res.items ?? []);
6381
+ const rows = res.items ?? [];
6382
+ setItems(rows);
6368
6383
  setState("loaded");
6384
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6369
6385
  }).catch((err) => {
6370
6386
  if (cancelled) return;
6371
6387
  log14.warn("listContent (news) failed", { err });
@@ -6375,7 +6391,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6375
6391
  return () => {
6376
6392
  cancelled = true;
6377
6393
  };
6378
- }, [transport, tags, reloadKey]);
6394
+ }, [transport, tags, reloadKey, bus]);
6379
6395
  function renderBody() {
6380
6396
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6381
6397
  if (state === "error") {
@@ -6483,7 +6499,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6483
6499
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6484
6500
  var p29 = BRAND.cssPrefix;
6485
6501
  var log15 = logger.scope("content");
6486
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6502
+ var READ_DWELL_MS = 5e3;
6503
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6487
6504
  const [item, setItem] = useState11(null);
6488
6505
  const [failed, setFailed] = useState11(false);
6489
6506
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6497,8 +6514,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6497
6514
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6498
6515
  if (cancelled) return;
6499
6516
  const found = res.items[0];
6500
- if (found) setItem(found);
6501
- else setFailed(true);
6517
+ if (found) {
6518
+ setItem(found);
6519
+ bus.emit("contentOpen", { contentId: id, tags: found.tags?.length ? found.tags.join(",") : void 0 });
6520
+ } else setFailed(true);
6502
6521
  }).catch((err) => {
6503
6522
  if (cancelled) return;
6504
6523
  log15.warn("listContent (reader) failed", { err });
@@ -6507,11 +6526,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6507
6526
  return () => {
6508
6527
  cancelled = true;
6509
6528
  };
6510
- }, [transport, id, reloadKey]);
6529
+ }, [transport, id, reloadKey, bus]);
6530
+ useEffect14(() => {
6531
+ if (!item) return;
6532
+ const timer = setTimeout(() => bus.emit("contentRead", { contentId: id }), READ_DWELL_MS);
6533
+ return () => clearTimeout(timer);
6534
+ }, [item, id, bus]);
6535
+ const onArticleClick = useCallback3(
6536
+ (e) => {
6537
+ const anchor = e.target?.closest?.("a");
6538
+ const href = anchor?.getAttribute("href") ?? "";
6539
+ if (href && !href.startsWith("#")) bus.emit("contentLinkClick", { contentId: id });
6540
+ },
6541
+ [bus, id]
6542
+ );
6511
6543
  function renderBody() {
6512
6544
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6513
6545
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6514
- return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6546
+ return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, onClick: onArticleClick, children: [
6515
6547
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6516
6548
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6517
6549
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6539,6 +6571,7 @@ function MessengerHome({
6539
6571
  panelProps,
6540
6572
  enabledModules,
6541
6573
  nav,
6574
+ bus,
6542
6575
  unreadCount,
6543
6576
  onSelectConversation,
6544
6577
  onStartNewConversation,
@@ -6600,6 +6633,7 @@ function MessengerHome({
6600
6633
  config: module,
6601
6634
  visitorId: panelProps.visitorId,
6602
6635
  nav: moduleNav,
6636
+ bus,
6603
6637
  panelProps
6604
6638
  });
6605
6639
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6614,6 +6648,7 @@ function MessengerHome({
6614
6648
  title: top.title,
6615
6649
  transport: panelProps.transport,
6616
6650
  strings,
6651
+ bus,
6617
6652
  onBack: nav.pop,
6618
6653
  actions: plainActions
6619
6654
  }
@@ -7646,6 +7681,7 @@ function App({ options, hostElement, bus }) {
7646
7681
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7647
7682
  enabledModules,
7648
7683
  nav: homeNav,
7684
+ bus,
7649
7685
  unreadCount: unreadCountSig.value,
7650
7686
  onSelectConversation,
7651
7687
  onStartNewConversation: handleNewChat,
@@ -7772,6 +7808,12 @@ var TRACKED = {
7772
7808
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7773
7809
  toolResult: () => void 0,
7774
7810
  toolDecision: (p33) => ({ approved: p33.approved }),
7811
+ // Content — ids, tags + counts only; titles/bodies never ride.
7812
+ contentView: (p33) => ({ section: p33.section, tags: p33.tags, count: p33.count }),
7813
+ contentOpen: (p33) => ({ contentId: p33.contentId, tags: p33.tags }),
7814
+ contentSearch: (p33) => ({ qlen: p33.queryLength, hits: p33.hitCount }),
7815
+ contentRead: (p33) => ({ contentId: p33.contentId }),
7816
+ contentLinkClick: (p33) => ({ contentId: p33.contentId }),
7775
7817
  // Composer / attachments / voice.
7776
7818
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7777
7819
  voiceStart: () => void 0,