@helpai/elements 0.42.2 → 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
@@ -6225,7 +6225,7 @@ function ArticleRow({ article, nav }) {
6225
6225
  }
6226
6226
  );
6227
6227
  }
6228
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6228
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6229
6229
  const tags = config.contentTags;
6230
6230
  const [state, setState] = useState8("loading");
6231
6231
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6237,8 +6237,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6237
6237
  setState("loading");
6238
6238
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6239
6239
  if (cancelled) return;
6240
- setItems(res.items ?? []);
6240
+ const rows = res.items ?? [];
6241
+ setItems(rows);
6241
6242
  setState("loaded");
6243
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6242
6244
  }).catch((err) => {
6243
6245
  if (cancelled) return;
6244
6246
  log13.warn("listContent (help) failed", { err });
@@ -6248,8 +6250,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6248
6250
  return () => {
6249
6251
  cancelled = true;
6250
6252
  };
6251
- }, [transport, tags, reloadKey]);
6253
+ }, [transport, tags, reloadKey, bus]);
6252
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]);
6253
6261
  function renderBody() {
6254
6262
  if (query.trim().length > 0) {
6255
6263
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6304,7 +6312,7 @@ function resolveGreeting(props2) {
6304
6312
  }
6305
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 });
6306
6314
  function HomeRoot(props2) {
6307
- const { transport, strings, config, nav, panelProps } = props2;
6315
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6308
6316
  const [recent, setRecent] = useState9(null);
6309
6317
  const [content, setContent] = useState9([]);
6310
6318
  const tagsKey = config.contentTags?.join(",");
@@ -6319,11 +6327,16 @@ function HomeRoot(props2) {
6319
6327
  useEffect12(() => {
6320
6328
  if (!config.contentTags?.length) return;
6321
6329
  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 }));
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 }));
6323
6336
  return () => {
6324
6337
  cancelled = true;
6325
6338
  };
6326
- }, [transport, tagsKey]);
6339
+ }, [transport, tagsKey, bus]);
6327
6340
  const greeting = resolveGreeting(props2);
6328
6341
  const avatars = config.userAvatars ?? [];
6329
6342
  const status = config.status;
@@ -6395,7 +6408,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6395
6408
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6396
6409
  var p26 = BRAND.cssPrefix;
6397
6410
  var log15 = logger.scope("news");
6398
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6411
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6399
6412
  const tags = config.contentTags;
6400
6413
  const [state, setState] = useState10("loading");
6401
6414
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6406,8 +6419,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6406
6419
  setState("loading");
6407
6420
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6408
6421
  if (cancelled) return;
6409
- setItems(res.items ?? []);
6422
+ const rows = res.items ?? [];
6423
+ setItems(rows);
6410
6424
  setState("loaded");
6425
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6411
6426
  }).catch((err) => {
6412
6427
  if (cancelled) return;
6413
6428
  log15.warn("listContent (news) failed", { err });
@@ -6417,7 +6432,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6417
6432
  return () => {
6418
6433
  cancelled = true;
6419
6434
  };
6420
- }, [transport, tags, reloadKey]);
6435
+ }, [transport, tags, reloadKey, bus]);
6421
6436
  function renderBody() {
6422
6437
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6423
6438
  if (state === "error") {
@@ -6525,7 +6540,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6525
6540
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6526
6541
  var p29 = BRAND.cssPrefix;
6527
6542
  var log16 = logger.scope("content");
6528
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6543
+ var READ_DWELL_MS = 5e3;
6544
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6529
6545
  const [item, setItem] = useState11(null);
6530
6546
  const [failed, setFailed] = useState11(false);
6531
6547
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6539,8 +6555,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6539
6555
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6540
6556
  if (cancelled) return;
6541
6557
  const found = res.items[0];
6542
- if (found) setItem(found);
6543
- 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);
6544
6562
  }).catch((err) => {
6545
6563
  if (cancelled) return;
6546
6564
  log16.warn("listContent (reader) failed", { err });
@@ -6549,11 +6567,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6549
6567
  return () => {
6550
6568
  cancelled = true;
6551
6569
  };
6552
- }, [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
+ );
6553
6584
  function renderBody() {
6554
6585
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6555
6586
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6556
- 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: [
6557
6588
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6558
6589
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6559
6590
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6581,6 +6612,7 @@ function MessengerHome({
6581
6612
  panelProps,
6582
6613
  enabledModules,
6583
6614
  nav,
6615
+ bus,
6584
6616
  unreadCount,
6585
6617
  onSelectConversation,
6586
6618
  onStartNewConversation,
@@ -6642,6 +6674,7 @@ function MessengerHome({
6642
6674
  config: module,
6643
6675
  visitorId: panelProps.visitorId,
6644
6676
  nav: moduleNav,
6677
+ bus,
6645
6678
  panelProps
6646
6679
  });
6647
6680
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6656,6 +6689,7 @@ function MessengerHome({
6656
6689
  title: top.title,
6657
6690
  transport: panelProps.transport,
6658
6691
  strings,
6692
+ bus,
6659
6693
  onBack: nav.pop,
6660
6694
  actions: plainActions
6661
6695
  }
@@ -7688,6 +7722,7 @@ function App({ options, hostElement, bus }) {
7688
7722
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7689
7723
  enabledModules,
7690
7724
  nav: homeNav,
7725
+ bus,
7691
7726
  unreadCount: unreadCountSig.value,
7692
7727
  onSelectConversation,
7693
7728
  onStartNewConversation: handleNewChat,
@@ -7814,6 +7849,12 @@ var TRACKED = {
7814
7849
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7815
7850
  toolResult: () => void 0,
7816
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 }),
7817
7858
  // Composer / attachments / voice.
7818
7859
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7819
7860
  voiceStart: () => void 0,
@@ -8032,6 +8073,12 @@ var EVENT_NAMES = [
8032
8073
  "formSubmit",
8033
8074
  "toolResult",
8034
8075
  "toolDecision",
8076
+ // Content (news / help / home articles)
8077
+ "contentView",
8078
+ "contentOpen",
8079
+ "contentSearch",
8080
+ "contentRead",
8081
+ "contentLinkClick",
8035
8082
  // Composer / attachments / voice
8036
8083
  "attach",
8037
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.2"
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
@@ -6184,7 +6184,7 @@ function ArticleRow({ article, nav }) {
6184
6184
  }
6185
6185
  );
6186
6186
  }
6187
- function HelpRoot({ transport, strings, config, nav, panelProps }) {
6187
+ function HelpRoot({ transport, strings, config, nav, bus, panelProps }) {
6188
6188
  const tags = config.contentTags;
6189
6189
  const [state, setState] = useState8("loading");
6190
6190
  const [errorMsg, setErrorMsg] = useState8(strings.errorGeneric);
@@ -6196,8 +6196,10 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6196
6196
  setState("loading");
6197
6197
  transport.listContent({ tags, sortBy: "order", limit: 100 }).then((res) => {
6198
6198
  if (cancelled) return;
6199
- setItems(res.items ?? []);
6199
+ const rows = res.items ?? [];
6200
+ setItems(rows);
6200
6201
  setState("loaded");
6202
+ bus.emit("contentView", { section: "help", tags: tags?.join(","), count: rows.length });
6201
6203
  }).catch((err) => {
6202
6204
  if (cancelled) return;
6203
6205
  log12.warn("listContent (help) failed", { err });
@@ -6207,8 +6209,14 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
6207
6209
  return () => {
6208
6210
  cancelled = true;
6209
6211
  };
6210
- }, [transport, tags, reloadKey]);
6212
+ }, [transport, tags, reloadKey, bus]);
6211
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]);
6212
6220
  function renderBody() {
6213
6221
  if (query.trim().length > 0) {
6214
6222
  if (results.length === 0) return /* @__PURE__ */ jsx27(ModuleState, { message: strings.helpSearchEmpty, strings });
@@ -6263,7 +6271,7 @@ function resolveGreeting(props2) {
6263
6271
  }
6264
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 });
6265
6273
  function HomeRoot(props2) {
6266
- const { transport, strings, config, nav, panelProps } = props2;
6274
+ const { transport, strings, config, nav, bus, panelProps } = props2;
6267
6275
  const [recent, setRecent] = useState9(null);
6268
6276
  const [content, setContent] = useState9([]);
6269
6277
  const tagsKey = config.contentTags?.join(",");
@@ -6278,11 +6286,16 @@ function HomeRoot(props2) {
6278
6286
  useEffect12(() => {
6279
6287
  if (!config.contentTags?.length) return;
6280
6288
  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 }));
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 }));
6282
6295
  return () => {
6283
6296
  cancelled = true;
6284
6297
  };
6285
- }, [transport, tagsKey]);
6298
+ }, [transport, tagsKey, bus]);
6286
6299
  const greeting = resolveGreeting(props2);
6287
6300
  const avatars = config.userAvatars ?? [];
6288
6301
  const status = config.status;
@@ -6354,7 +6367,7 @@ import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
6354
6367
  import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
6355
6368
  var p26 = BRAND.cssPrefix;
6356
6369
  var log14 = logger.scope("news");
6357
- function NewsRoot({ transport, strings, config, nav, panelProps }) {
6370
+ function NewsRoot({ transport, strings, config, nav, bus, panelProps }) {
6358
6371
  const tags = config.contentTags;
6359
6372
  const [state, setState] = useState10("loading");
6360
6373
  const [errorMsg, setErrorMsg] = useState10(strings.errorGeneric);
@@ -6365,8 +6378,10 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6365
6378
  setState("loading");
6366
6379
  transport.listContent({ tags, sortBy: "publishedAt", sortOrder: "desc" }).then((res) => {
6367
6380
  if (cancelled) return;
6368
- setItems(res.items ?? []);
6381
+ const rows = res.items ?? [];
6382
+ setItems(rows);
6369
6383
  setState("loaded");
6384
+ bus.emit("contentView", { section: "news", tags: tags?.join(","), count: rows.length });
6370
6385
  }).catch((err) => {
6371
6386
  if (cancelled) return;
6372
6387
  log14.warn("listContent (news) failed", { err });
@@ -6376,7 +6391,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
6376
6391
  return () => {
6377
6392
  cancelled = true;
6378
6393
  };
6379
- }, [transport, tags, reloadKey]);
6394
+ }, [transport, tags, reloadKey, bus]);
6380
6395
  function renderBody() {
6381
6396
  if (state === "loading") return /* @__PURE__ */ jsx30(ModuleState, { message: strings.newsLoading, strings });
6382
6397
  if (state === "error") {
@@ -6484,7 +6499,8 @@ import { useCallback as useCallback3, useEffect as useEffect14, useState as useS
6484
6499
  import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
6485
6500
  var p29 = BRAND.cssPrefix;
6486
6501
  var log15 = logger.scope("content");
6487
- function ContentView({ id, title, transport, strings, onBack, actions }) {
6502
+ var READ_DWELL_MS = 5e3;
6503
+ function ContentView({ id, title, transport, strings, bus, onBack, actions }) {
6488
6504
  const [item, setItem] = useState11(null);
6489
6505
  const [failed, setFailed] = useState11(false);
6490
6506
  const [reloadKey, setReloadKey] = useState11(0);
@@ -6498,8 +6514,10 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6498
6514
  transport.listContent({ ids: [id], limit: 1 }).then((res) => {
6499
6515
  if (cancelled) return;
6500
6516
  const found = res.items[0];
6501
- if (found) setItem(found);
6502
- 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);
6503
6521
  }).catch((err) => {
6504
6522
  if (cancelled) return;
6505
6523
  log15.warn("listContent (reader) failed", { err });
@@ -6508,11 +6526,24 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6508
6526
  return () => {
6509
6527
  cancelled = true;
6510
6528
  };
6511
- }, [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
+ );
6512
6543
  function renderBody() {
6513
6544
  if (failed) return /* @__PURE__ */ jsx33(ModuleState, { tone: "error", message: strings.errorGeneric, onRetry: retry, strings });
6514
6545
  if (item === null) return /* @__PURE__ */ jsx33(ModuleState, { message: strings.contentLoading, strings });
6515
- 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: [
6516
6547
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6517
6548
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6518
6549
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
@@ -6540,6 +6571,7 @@ function MessengerHome({
6540
6571
  panelProps,
6541
6572
  enabledModules,
6542
6573
  nav,
6574
+ bus,
6543
6575
  unreadCount,
6544
6576
  onSelectConversation,
6545
6577
  onStartNewConversation,
@@ -6601,6 +6633,7 @@ function MessengerHome({
6601
6633
  config: module,
6602
6634
  visitorId: panelProps.visitorId,
6603
6635
  nav: moduleNav,
6636
+ bus,
6604
6637
  panelProps
6605
6638
  });
6606
6639
  const plainActions = /* @__PURE__ */ jsx34(HeaderActions, { panelProps, variant: "plain" });
@@ -6615,6 +6648,7 @@ function MessengerHome({
6615
6648
  title: top.title,
6616
6649
  transport: panelProps.transport,
6617
6650
  strings,
6651
+ bus,
6618
6652
  onBack: nav.pop,
6619
6653
  actions: plainActions
6620
6654
  }
@@ -7647,6 +7681,7 @@ function App({ options, hostElement, bus }) {
7647
7681
  panelProps: { ...panelProps, panelSize: size, onClose: closeable ? handleClose : void 0 },
7648
7682
  enabledModules,
7649
7683
  nav: homeNav,
7684
+ bus,
7650
7685
  unreadCount: unreadCountSig.value,
7651
7686
  onSelectConversation,
7652
7687
  onStartNewConversation: handleNewChat,
@@ -7773,6 +7808,12 @@ var TRACKED = {
7773
7808
  formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7774
7809
  toolResult: () => void 0,
7775
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 }),
7776
7817
  // Composer / attachments / voice.
7777
7818
  attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7778
7819
  voiceStart: () => void 0,