@helpai/elements 0.42.2 → 0.43.1

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
@@ -5259,6 +5259,7 @@ function MessageList({
5259
5259
  pinBottom(el);
5260
5260
  return;
5261
5261
  }
5262
+ if (!list.some((m) => m.role === "user")) return;
5262
5263
  if (detachedRef.current || inInteractionGrace()) return;
5263
5264
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
5264
5265
  if (distanceFromBottom < STICK_THRESHOLD) pinBottom(el);
@@ -6225,7 +6226,7 @@ function ArticleRow({ article, nav }) {
6225
6226
  }
6226
6227
  );
6227
6228
  }
6228
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6229
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6229
6230
  const tags = config.contentTags;
6230
6231
  const [state, setState] = useState8("loading");
6231
6232
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6237,8 +6238,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6237
6238
  setState("loading");
6238
6239
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6239
6240
  if (cancelled) return;
6240
- setItems(res.items ?? []);
6241
+ const rows = res.items ?? [];
6242
+ setItems(rows);
6241
6243
  setState("loaded");
6244
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6242
6245
  }).catch((err) => {
6243
6246
  if (cancelled) return;
6244
6247
  log13.warn("listContent (help) failed", { err });
@@ -6248,8 +6251,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6248
6251
  return () => {
6249
6252
  cancelled = true;
6250
6253
  };
6251
- }, [transport, tags, reloadKey]);
6254
+ }, [transport, tags, reloadKey, bus]);
6252
6255
  const results = useMemo2(() => fuzzySearch(items, query), [items, query]);
6256
+ useEffect11(() => {
6257
+ const q = query.trim();
6258
+ if (!q) return;
6259
+ const timer = setTimeout(() => bus.emit("contentSearch", { queryLength: q.length, hitCount: results.length }), 600);
6260
+ return () => clearTimeout(timer);
6261
+ }, [query, results, bus]);
6253
6262
  function renderBody() {
6254
6263
  if (query.trim().length > 0) {
6255
6264
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6304,7 +6313,7 @@ function resolveGreeting(props2) {
6304
6313
  }
6305
6314
  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 });
6306
6315
  function HomeRoot(props2) {
6307
- const { transport, strings, config, nav, panelProps } = props2;
6316
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6308
6317
  const [recent, setRecent] = useState9(null);
6309
6318
  const [content, setContent] = useState9([]);
6310
6319
  const tagsKey = config.contentTags?.join(",");
@@ -6319,11 +6328,16 @@ function HomeRoot(props2) {
6319
6328
  useEffect12(() => {
6320
6329
  if (!config.contentTags?.length) return;
6321
6330
  let cancelled = false;
6322
- transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
6331
+ transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => {
6332
+ if (cancelled) return;
6333
+ const rows = res.items ?? [];
6334
+ setContent(rows);
6335
+ bus.emit("contentView", { section: "home", tags: tagsKey, count: rows.length });
6336
+ }).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
6323
6337
  return () => {
6324
6338
  cancelled = true;
6325
6339
  };
6326
- }, [transport, tagsKey]);
6340
+ }, [transport, tagsKey, bus]);
6327
6341
  const greeting = resolveGreeting(props2);
6328
6342
  const avatars = config.userAvatars ?? [];
6329
6343
  const status = config.status;
@@ -6395,7 +6409,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6395
6409
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6396
6410
  var p26 = BRAND.cssPrefix;
6397
6411
  var log15 = logger.scope("news");
6398
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6412
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6399
6413
  const tags = config.contentTags;
6400
6414
  const [state, setState] = useState10("loading");
6401
6415
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6406,8 +6420,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6406
6420
  setState("loading");
6407
6421
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6408
6422
  if (cancelled) return;
6409
- setItems(res.items ?? []);
6423
+ const rows = res.items ?? [];
6424
+ setItems(rows);
6410
6425
  setState("loaded");
6426
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6411
6427
  }).catch((err) => {
6412
6428
  if (cancelled) return;
6413
6429
  log15.warn("listContent (news) failed", { err });
@@ -6417,7 +6433,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6417
6433
  return () => {
6418
6434
  cancelled = true;
6419
6435
  };
6420
- }, [transport, tags, reloadKey]);
6436
+ }, [transport, tags, reloadKey, bus]);
6421
6437
  function renderBody() {
6422
6438
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6423
6439
  if (state === "error") {
@@ -6525,7 +6541,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6525
6541
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6526
6542
  var p29 = BRAND.cssPrefix;
6527
6543
  var log16 = logger.scope("content");
6528
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6544
+ var READ_DWELL_MS = 5e3;
6545
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6529
6546
  const [item, setItem] = useState11(null);
6530
6547
  const [failed, setFailed] = useState11(false);
6531
6548
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6539,8 +6556,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6539
6556
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6540
6557
  if (cancelled) return;
6541
6558
  const found = res.items[0];
6542
- if (found) setItem(found);
6543
- else setFailed(true);
6559
+ if (found) {
6560
+ setItem(found);
6561
+ bus.emit("contentOpen", { contentId: id, tags: found.tags?.length ? found.tags.join(",") : void 0 });
6562
+ } else setFailed(true);
6544
6563
  }).catch((err) => {
6545
6564
  if (cancelled) return;
6546
6565
  log16.warn("listContent (reader) failed", { err });
@@ -6549,11 +6568,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6549
6568
  return () => {
6550
6569
  cancelled = true;
6551
6570
  };
6552
- }, [transport, id, reloadKey]);
6571
+ }, [transport, id, reloadKey, bus]);
6572
+ useEffect14(() => {
6573
+ if (!item) return;
6574
+ const timer = setTimeout(() => bus.emit("contentRead", { contentId: id }), READ_DWELL_MS);
6575
+ return () => clearTimeout(timer);
6576
+ }, [item, id, bus]);
6577
+ const onArticleClick = useCallback3(
6578
+ (e) => {
6579
+ const anchor = e.target?.closest?.("a");
6580
+ const href = anchor?.getAttribute("href") ?? "";
6581
+ if (href && !href.startsWith("#")) bus.emit("contentLinkClick", { contentId: id });
6582
+ },
6583
+ [bus, id]
6584
+ );
6553
6585
  function renderBody() {
6554
6586
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6555
6587
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6556
- return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6588
+ return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, onClick: onArticleClick, children: [
6557
6589
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6558
6590
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6559
6591
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6581,6 +6613,7 @@ function MessengerHome({
6581
6613
  panelProps,
6582
6614
  enabledModules,
6583
6615
  nav,
6616
+ bus,
6584
6617
  unreadCount,
6585
6618
  onSelectConversation,
6586
6619
  onStartNewConversation,
@@ -6642,6 +6675,7 @@ function MessengerHome({
6642
6675
  config: module,
6643
6676
  visitorId: panelProps.visitorId,
6644
6677
  nav: moduleNav,
6678
+ bus,
6645
6679
  panelProps
6646
6680
  });
6647
6681
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6656,6 +6690,7 @@ function MessengerHome({
6656
6690
  title: top.title,
6657
6691
  transport: panelProps.transport,
6658
6692
  strings,
6693
+ bus,
6659
6694
  onBack: nav.pop,
6660
6695
  actions: plainActions
6661
6696
  }
@@ -7688,6 +7723,7 @@ function App({ options, hostElement, bus }) {
7688
7723
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7689
7724
  enabledModules,
7690
7725
  nav: homeNav,
7726
+ bus,
7691
7727
  unreadCount: unreadCountSig.value,
7692
7728
  onSelectConversation,
7693
7729
  onStartNewConversation: handleNewChat,
@@ -7814,6 +7850,12 @@ var TRACKED = {
7814
7850
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7815
7851
  toolResult: () => void 0,
7816
7852
  toolDecision: (p33) => ({ approved: p33.approved }),
7853
+ // Content — ids, tags + counts only; titles/bodies never ride.
7854
+ contentView: (p33) => ({ section: p33.section, tags: p33.tags, count: p33.count }),
7855
+ contentOpen: (p33) => ({ contentId: p33.contentId, tags: p33.tags }),
7856
+ contentSearch: (p33) => ({ qlen: p33.queryLength, hits: p33.hitCount }),
7857
+ contentRead: (p33) => ({ contentId: p33.contentId }),
7858
+ contentLinkClick: (p33) => ({ contentId: p33.contentId }),
7817
7859
  // Composer / attachments / voice.
7818
7860
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7819
7861
  voiceStart: () => void 0,
@@ -8032,6 +8074,12 @@ var EVENT_NAMES = [
8032
8074
  "formSubmit",
8033
8075
  "toolResult",
8034
8076
  "toolDecision",
8077
+ // Content (news / help / home articles)
8078
+ "contentView",
8079
+ "contentOpen",
8080
+ "contentSearch",
8081
+ "contentRead",
8082
+ "contentLinkClick",
8035
8083
  // Composer / attachments / voice
8036
8084
  "attach",
8037
8085
  "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.2"
83
+ "version": "0.43.1"
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
@@ -5218,6 +5218,7 @@ function MessageList({
5218
5218
  pinBottom(el);
5219
5219
  return;
5220
5220
  }
5221
+ if (!list.some((m) => m.role === "user")) return;
5221
5222
  if (detachedRef.current || inInteractionGrace()) return;
5222
5223
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
5223
5224
  if (distanceFromBottom < STICK_THRESHOLD) pinBottom(el);
@@ -6184,7 +6185,7 @@ function ArticleRow({ article, nav }) {
6184
6185
  }
6185
6186
  );
6186
6187
  }
6187
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6188
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6188
6189
  const tags = config.contentTags;
6189
6190
  const [state, setState] = useState8("loading");
6190
6191
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6196,8 +6197,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6196
6197
  setState("loading");
6197
6198
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6198
6199
  if (cancelled) return;
6199
- setItems(res.items ?? []);
6200
+ const rows = res.items ?? [];
6201
+ setItems(rows);
6200
6202
  setState("loaded");
6203
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6201
6204
  }).catch((err) => {
6202
6205
  if (cancelled) return;
6203
6206
  log12.warn("listContent (help) failed", { err });
@@ -6207,8 +6210,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6207
6210
  return () => {
6208
6211
  cancelled = true;
6209
6212
  };
6210
- }, [transport, tags, reloadKey]);
6213
+ }, [transport, tags, reloadKey, bus]);
6211
6214
  const results = useMemo2(() => fuzzySearch(items, query), [items, query]);
6215
+ useEffect11(() => {
6216
+ const q = query.trim();
6217
+ if (!q) return;
6218
+ const timer = setTimeout(() => bus.emit("contentSearch", { queryLength: q.length, hitCount: results.length }), 600);
6219
+ return () => clearTimeout(timer);
6220
+ }, [query, results, bus]);
6212
6221
  function renderBody() {
6213
6222
  if (query.trim().length > 0) {
6214
6223
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6263,7 +6272,7 @@ function resolveGreeting(props2) {
6263
6272
  }
6264
6273
  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 });
6265
6274
  function HomeRoot(props2) {
6266
- const { transport, strings, config, nav, panelProps } = props2;
6275
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6267
6276
  const [recent, setRecent] = useState9(null);
6268
6277
  const [content, setContent] = useState9([]);
6269
6278
  const tagsKey = config.contentTags?.join(",");
@@ -6278,11 +6287,16 @@ function HomeRoot(props2) {
6278
6287
  useEffect12(() => {
6279
6288
  if (!config.contentTags?.length) return;
6280
6289
  let cancelled = false;
6281
- transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log13.warn("listContent (home) failed", { err }));
6290
+ transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => {
6291
+ if (cancelled) return;
6292
+ const rows = res.items ?? [];
6293
+ setContent(rows);
6294
+ bus.emit("contentView", { section: "home", tags: tagsKey, count: rows.length });
6295
+ }).catch((err) => !cancelled && log13.warn("listContent (home) failed", { err }));
6282
6296
  return () => {
6283
6297
  cancelled = true;
6284
6298
  };
6285
- }, [transport, tagsKey]);
6299
+ }, [transport, tagsKey, bus]);
6286
6300
  const greeting = resolveGreeting(props2);
6287
6301
  const avatars = config.userAvatars ?? [];
6288
6302
  const status = config.status;
@@ -6354,7 +6368,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6354
6368
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6355
6369
  var p26 = BRAND.cssPrefix;
6356
6370
  var log14 = logger.scope("news");
6357
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6371
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6358
6372
  const tags = config.contentTags;
6359
6373
  const [state, setState] = useState10("loading");
6360
6374
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6365,8 +6379,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6365
6379
  setState("loading");
6366
6380
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6367
6381
  if (cancelled) return;
6368
- setItems(res.items ?? []);
6382
+ const rows = res.items ?? [];
6383
+ setItems(rows);
6369
6384
  setState("loaded");
6385
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6370
6386
  }).catch((err) => {
6371
6387
  if (cancelled) return;
6372
6388
  log14.warn("listContent (news) failed", { err });
@@ -6376,7 +6392,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6376
6392
  return () => {
6377
6393
  cancelled = true;
6378
6394
  };
6379
- }, [transport, tags, reloadKey]);
6395
+ }, [transport, tags, reloadKey, bus]);
6380
6396
  function renderBody() {
6381
6397
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6382
6398
  if (state === "error") {
@@ -6484,7 +6500,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6484
6500
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6485
6501
  var p29 = BRAND.cssPrefix;
6486
6502
  var log15 = logger.scope("content");
6487
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6503
+ var READ_DWELL_MS = 5e3;
6504
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6488
6505
  const [item, setItem] = useState11(null);
6489
6506
  const [failed, setFailed] = useState11(false);
6490
6507
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6498,8 +6515,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6498
6515
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6499
6516
  if (cancelled) return;
6500
6517
  const found = res.items[0];
6501
- if (found) setItem(found);
6502
- else setFailed(true);
6518
+ if (found) {
6519
+ setItem(found);
6520
+ bus.emit("contentOpen", { contentId: id, tags: found.tags?.length ? found.tags.join(",") : void 0 });
6521
+ } else setFailed(true);
6503
6522
  }).catch((err) => {
6504
6523
  if (cancelled) return;
6505
6524
  log15.warn("listContent (reader) failed", { err });
@@ -6508,11 +6527,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6508
6527
  return () => {
6509
6528
  cancelled = true;
6510
6529
  };
6511
- }, [transport, id, reloadKey]);
6530
+ }, [transport, id, reloadKey, bus]);
6531
+ useEffect14(() => {
6532
+ if (!item) return;
6533
+ const timer = setTimeout(() => bus.emit("contentRead", { contentId: id }), READ_DWELL_MS);
6534
+ return () => clearTimeout(timer);
6535
+ }, [item, id, bus]);
6536
+ const onArticleClick = useCallback3(
6537
+ (e) => {
6538
+ const anchor = e.target?.closest?.("a");
6539
+ const href = anchor?.getAttribute("href") ?? "";
6540
+ if (href && !href.startsWith("#")) bus.emit("contentLinkClick", { contentId: id });
6541
+ },
6542
+ [bus, id]
6543
+ );
6512
6544
  function renderBody() {
6513
6545
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6514
6546
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6515
- return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6547
+ return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, onClick: onArticleClick, children: [
6516
6548
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6517
6549
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6518
6550
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6540,6 +6572,7 @@ function MessengerHome({
6540
6572
  panelProps,
6541
6573
  enabledModules,
6542
6574
  nav,
6575
+ bus,
6543
6576
  unreadCount,
6544
6577
  onSelectConversation,
6545
6578
  onStartNewConversation,
@@ -6601,6 +6634,7 @@ function MessengerHome({
6601
6634
  config: module,
6602
6635
  visitorId: panelProps.visitorId,
6603
6636
  nav: moduleNav,
6637
+ bus,
6604
6638
  panelProps
6605
6639
  });
6606
6640
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6615,6 +6649,7 @@ function MessengerHome({
6615
6649
  title: top.title,
6616
6650
  transport: panelProps.transport,
6617
6651
  strings,
6652
+ bus,
6618
6653
  onBack: nav.pop,
6619
6654
  actions: plainActions
6620
6655
  }
@@ -7647,6 +7682,7 @@ function App({ options, hostElement, bus }) {
7647
7682
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7648
7683
  enabledModules,
7649
7684
  nav: homeNav,
7685
+ bus,
7650
7686
  unreadCount: unreadCountSig.value,
7651
7687
  onSelectConversation,
7652
7688
  onStartNewConversation: handleNewChat,
@@ -7773,6 +7809,12 @@ var TRACKED = {
7773
7809
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7774
7810
  toolResult: () => void 0,
7775
7811
  toolDecision: (p33) => ({ approved: p33.approved }),
7812
+ // Content — ids, tags + counts only; titles/bodies never ride.
7813
+ contentView: (p33) => ({ section: p33.section, tags: p33.tags, count: p33.count }),
7814
+ contentOpen: (p33) => ({ contentId: p33.contentId, tags: p33.tags }),
7815
+ contentSearch: (p33) => ({ qlen: p33.queryLength, hits: p33.hitCount }),
7816
+ contentRead: (p33) => ({ contentId: p33.contentId }),
7817
+ contentLinkClick: (p33) => ({ contentId: p33.contentId }),
7776
7818
  // Composer / attachments / voice.
7777
7819
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7778
7820
  voiceStart: () => void 0,