@guidelytech/guide-runtime 0.5.0 → 0.6.2

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/dist/messenger.js CHANGED
@@ -1,7 +1,104 @@
1
1
  import {
2
2
  createHelpView,
3
- el
4
- } from "./chunk-SGFC66YD.js";
3
+ el,
4
+ listItem
5
+ } from "./chunk-4FKYF6OB.js";
6
+ import "./chunk-7DOGZKWD.js";
7
+
8
+ // src/messenger.ts
9
+ import { foldPathIds, pageLabel } from "@guidelytech/shared/runtime";
10
+
11
+ // src/howto-view.ts
12
+ var pct = (v) => `${(v * 100).toFixed(3)}%`;
13
+ var place = (node, r) => Object.assign(node.style, {
14
+ left: pct(r.x),
15
+ top: pct(r.y),
16
+ width: pct(r.width),
17
+ height: pct(r.height)
18
+ });
19
+ function paragraphs(doc, text, cls) {
20
+ const parts = text.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean);
21
+ if (!parts.length) return null;
22
+ const wrap = el(doc, "div", cls);
23
+ for (const p of parts) wrap.appendChild(el(doc, "p", "", p));
24
+ return wrap;
25
+ }
26
+ function screenshot(doc, image, alt) {
27
+ if (!/^https?:\/\//i.test(image.url)) return null;
28
+ const frame = el(doc, "div", "g-howto-shot");
29
+ const img = (cls) => {
30
+ const i = el(doc, "img", cls);
31
+ i.src = image.url;
32
+ i.alt = alt;
33
+ i.loading = "lazy";
34
+ i.decoding = "async";
35
+ return i;
36
+ };
37
+ const crop = image.crop;
38
+ if (crop && crop.width > 0 && crop.height > 0) {
39
+ const ghost = img("g-howto-ghost");
40
+ ghost.alt = "";
41
+ ghost.setAttribute("aria-hidden", "true");
42
+ const zoomed = img("g-howto-zoomed");
43
+ Object.assign(zoomed.style, {
44
+ width: pct(1 / crop.width),
45
+ left: pct(-crop.x / crop.width),
46
+ top: pct(-crop.y / crop.height)
47
+ });
48
+ frame.append(ghost, zoomed);
49
+ } else frame.appendChild(img(""));
50
+ if (image.highlight) {
51
+ const mark = el(doc, "div", "g-howto-mark");
52
+ mark.setAttribute("aria-hidden", "true");
53
+ place(mark, image.highlight);
54
+ frame.appendChild(mark);
55
+ }
56
+ return frame;
57
+ }
58
+ function renderHowTo(doc, howTo, onShowMe) {
59
+ const wrap = el(doc, "div", "g-howto");
60
+ wrap.appendChild(el(doc, "h3", "g-title", howTo.title));
61
+ const description = paragraphs(doc, howTo.description, "g-howto-desc");
62
+ if (description) wrap.appendChild(description);
63
+ const guideKey = howTo.guideKey;
64
+ if (guideKey && onShowMe) {
65
+ const show = el(doc, "button", "g-btn g-btn-primary g-howto-show", "\u25B6 Show me in the app");
66
+ show.type = "button";
67
+ show.addEventListener("click", () => onShowMe(guideKey));
68
+ wrap.appendChild(show);
69
+ }
70
+ const list = el(doc, "ol", "g-howto-steps");
71
+ let n = 0;
72
+ for (const s of howTo.steps) {
73
+ const li = el(doc, "li", `g-howto-step g-howto-${s.kind}`);
74
+ if (s.kind === "heading") li.appendChild(el(doc, "h4", "", s.title));
75
+ else if (s.kind === "tip" || s.kind === "alert") {
76
+ li.appendChild(el(doc, "strong", "", s.title || (s.kind === "tip" ? "Tip" : "Important")));
77
+ const body = paragraphs(doc, s.body, "");
78
+ if (body) li.appendChild(body);
79
+ } else if (s.kind === "text") {
80
+ if (s.title) li.appendChild(el(doc, "strong", "", s.title));
81
+ const body = paragraphs(doc, s.body, "g-howto-note");
82
+ if (body) li.appendChild(body);
83
+ } else {
84
+ const row = el(doc, "div", "g-howto-row");
85
+ const num = el(doc, "span", "g-howto-num", String(++n));
86
+ const text = el(doc, "div", "g-howto-text");
87
+ text.appendChild(el(doc, "p", "g-howto-title", s.title));
88
+ const body = paragraphs(doc, s.body, "g-howto-note");
89
+ if (body) text.appendChild(body);
90
+ row.append(num, text);
91
+ li.appendChild(row);
92
+ const shot = s.image ? screenshot(doc, s.image, s.title) : null;
93
+ if (shot) li.appendChild(shot);
94
+ }
95
+ list.appendChild(li);
96
+ }
97
+ if (!list.childNodes.length)
98
+ wrap.appendChild(el(doc, "div", "g-rc-empty", "This how-to has no steps yet."));
99
+ else wrap.appendChild(list);
100
+ return wrap;
101
+ }
5
102
 
6
103
  // src/messenger-styles.ts
7
104
  var MESSENGER_STYLES = `
@@ -17,17 +114,16 @@ var MESSENGER_STYLES = `
17
114
  .g-m-head .g-close{color:inherit;top:50%;transform:translateY(-50%)}
18
115
  .g-m-head .g-close:hover,.g-m-head .g-close:focus-visible{background:rgba(255,255,255,.18);color:inherit}
19
116
  .g-m-head h2{margin:0;font-size:16px;font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20
- .g-m-back{border:0;background:transparent;color:inherit;font:inherit;font-size:18px;cursor:pointer;width:32px;height:32px;border-radius:999px;margin-left:-8px}
117
+ .g-m-back{border:0;background:transparent;color:inherit;font:inherit;cursor:pointer;width:32px;height:32px;border-radius:999px;margin-left:-8px;display:inline-flex;align-items:center;justify-content:center;padding:0}
21
118
  .g-m-back:hover,.g-m-back:focus-visible{background:rgba(255,255,255,.18);outline:none}
22
119
  .g-m-main{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}
23
120
  .g-m-scroll{flex:1;overflow:auto;padding:16px}
24
- .g-m-hero{font-size:22px;font-weight:700;margin:4px 0 4px}
25
- .g-m-greet{color:var(--g-muted);margin:0 0 16px}
26
121
  .g-m-card{display:block;width:100%;text-align:left;border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);border-radius:calc(var(--g-radius) * .8);padding:14px 16px;margin:0 0 12px;font:inherit;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.04)}
27
122
  .g-m-card:hover{background:var(--g-subtle)}
28
123
  .g-m-card:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
29
- .g-m-card-title{font-weight:600}
30
- .g-m-card-sub{color:var(--g-muted);font-size:13px;margin-top:2px}
124
+ .g-m-card-title{display:block;font-weight:600}
125
+ .g-m-card-sub{display:block;color:var(--g-muted);font-size:13px;margin-top:2px}
126
+ .g-m-card-label{display:block;font-size:12px;color:var(--g-muted);font-weight:500}
31
127
  .g-m-row{display:flex;align-items:flex-start;gap:10px}
32
128
  .g-m-row .g-m-grow{flex:1;min-width:0}
33
129
  .g-m-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -37,7 +133,9 @@ var MESSENGER_STYLES = `
37
133
  .g-m-tab{flex:1;border:0;background:transparent;color:var(--g-muted);font:inherit;font-size:12px;font-weight:600;padding:10px 4px 12px;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:2px}
38
134
  .g-m-tab[aria-selected="true"]{color:var(--g-primary)}
39
135
  .g-m-tab:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px}
40
- .g-m-tab-icon{font-size:18px;line-height:1}
136
+ .g-m-tab-icon{position:relative;display:inline-flex;line-height:1}
137
+ .g-m-tab-icon .g-m-ico{width:22px;height:22px}
138
+ .g-m-tab-dot{position:absolute;top:-2px;right:-4px;width:8px;height:8px;border-radius:999px;background:#e11d48;border:2px solid var(--g-bg);box-sizing:content-box}
41
139
  .g-m-log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px}
42
140
  .g-m-msg{max-width:85%;display:flex;flex-direction:column;gap:4px}
43
141
  .g-m-msg.g-from-user{align-self:flex-end;align-items:flex-end}
@@ -66,7 +164,8 @@ var MESSENGER_STYLES = `
66
164
  .g-m-compose{display:flex;align-items:flex-end;gap:8px}
67
165
  .g-m-compose textarea{flex:1;resize:none;min-height:40px;max-height:120px;border:1px solid var(--g-border);border-radius:10px;padding:9px 10px;font:inherit;font-size:14px;color:var(--g-text);background:var(--g-bg)}
68
166
  .g-m-compose textarea:focus-visible{outline:2px solid var(--g-primary);outline-offset:1px}
69
- .g-m-send{width:40px;height:40px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);cursor:pointer;font-size:18px;flex:none}
167
+ .g-m-send{width:40px;height:40px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;padding:0}
168
+ .g-m-send .g-m-ico{width:18px;height:18px}
70
169
  .g-m-send:disabled{opacity:.45;cursor:not-allowed}
71
170
  .g-m-send:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
72
171
  .g-m-human{border:0;background:transparent;color:var(--g-muted);font:inherit;font-size:12px;cursor:pointer;padding:6px 2px 0;text-decoration:underline}
@@ -76,6 +175,79 @@ var MESSENGER_STYLES = `
76
175
  .g-m-rate button:hover,.g-m-rate button:focus-visible{background:var(--g-subtle);outline:none}
77
176
  .g-m-note{color:var(--g-muted);font-size:12px;margin-top:6px}
78
177
  .g-m-empty{color:var(--g-muted);text-align:center;padding:24px 8px}
178
+ .g-howto .g-title{margin:0 0 6px}
179
+ .g-howto-desc,.g-howto-note{color:var(--g-muted);font-size:13px}
180
+ .g-howto p{margin:0 0 6px;white-space:pre-line}
181
+ .g-howto-show{width:100%;margin:6px 0 4px}
182
+ .g-howto-steps{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:16px}
183
+ .g-howto-step h4{margin:4px 0 0;font-size:15px}
184
+ .g-howto-tip,.g-howto-alert{border-left:3px solid var(--g-primary);background:var(--g-subtle);border-radius:6px;padding:8px 10px;font-size:13px}
185
+ .g-howto-alert{border-left-color:#d97706}
186
+ .g-howto-row{display:flex;gap:10px;align-items:flex-start}
187
+ .g-howto-num{flex:none;width:24px;height:24px;border-radius:999px;background:var(--g-primary);color:var(--g-primary-text);font-size:12px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}
188
+ .g-howto-text{flex:1;min-width:0;padding-top:2px}
189
+ .g-howto-title{font-weight:600}
190
+ .g-howto-shot{position:relative;overflow:hidden;margin-top:8px;border:1px solid var(--g-border);border-radius:8px;background:var(--g-subtle)}
191
+ .g-howto-shot img{display:block;width:100%;height:auto}
192
+ .g-howto-shot .g-howto-ghost{visibility:hidden}
193
+ .g-howto-shot .g-howto-zoomed{position:absolute;max-width:none}
194
+ .g-howto-mark{position:absolute;pointer-events:none;border:3px solid var(--g-primary);border-radius:6px;box-shadow:0 0 0 3px color-mix(in srgb,var(--g-primary) 25%,transparent)}
195
+ .g-m-ico{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
196
+ .g-close .g-m-ico,.g-m-back .g-m-ico{width:18px;height:18px}
197
+ .g-m-row.g-m-center{align-items:center}
198
+ .g-m-row.g-m-baseline{align-items:baseline}
199
+ .g-m-static{cursor:default}
200
+ .g-m-static:hover{background:var(--g-bg)}
201
+ .g-m-wide{width:100%}
202
+ .g-m-home{position:relative;padding:0}
203
+ .g-m-hero-bg{position:absolute;top:0;left:0;right:0;height:300px;pointer-events:none;background:linear-gradient(180deg,var(--g-primary) 0%,var(--g-primary) 52%,color-mix(in srgb,var(--g-primary) 30%,var(--g-bg)) 84%,var(--g-bg) 100%)}
204
+ .g-m-home-inner{position:relative;padding:16px;display:flex;flex-direction:column;gap:12px}
205
+ .g-m-home .g-m-card{margin:0;box-shadow:0 1px 2px rgba(0,0,0,.04),0 4px 14px rgba(17,24,39,.06)}
206
+ .g-m-hero-top{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:40px;padding:2px 4px 0}
207
+ .g-m-logo{width:36px;height:36px;border-radius:9px;background:var(--g-bg);color:var(--g-primary);display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;overflow:hidden;flex:none}
208
+ .g-m-logo img{width:100%;height:100%;object-fit:contain}
209
+ .g-m-team{display:flex}
210
+ .g-m-avatar{width:32px;height:32px;border-radius:999px;border:2px solid var(--g-primary);display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;overflow:hidden;margin-left:-8px;background:#fde7c7;color:#7a4a0a}
211
+ .g-m-avatar:first-child{margin-left:0}
212
+ .g-m-avatar-1{background:#d9f0e3;color:#1f5e3b}
213
+ .g-m-avatar-2{background:#e3e1fa;color:#3b3486}
214
+ .g-m-avatar img{width:100%;height:100%;object-fit:cover}
215
+ .g-m-hero-close{width:36px;height:36px;border:0;border-radius:999px;background:rgba(255,255,255,.16);color:var(--g-primary-text);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0;margin-left:10px}
216
+ .g-m-hero-close .g-m-ico{width:18px;height:18px}
217
+ .g-m-hero-close:hover{background:rgba(255,255,255,.28)}
218
+ .g-m-hero-close:focus-visible{outline:2px solid var(--g-primary-text);outline-offset:2px}
219
+ .g-m-hello{margin:36px 4px 6px;color:var(--g-primary-text);font-size:26px;font-weight:700;line-height:1.2;letter-spacing:-.01em;display:flex;flex-direction:column}
220
+ .g-m-hello-1{opacity:.8}
221
+ .g-m-trail{color:var(--g-primary)}
222
+ .g-m-lead{color:var(--g-muted)}
223
+ .g-m-status-ico{width:26px;height:26px}
224
+ .g-m-status-operational .g-m-status-ico{color:#15803d}
225
+ .g-m-status-degraded .g-m-status-ico{color:#b45309}
226
+ .g-m-status-outage .g-m-status-ico{color:#b91c1c}
227
+ .g-m-status-maintenance .g-m-status-ico{color:#1d4ed8}
228
+ .g-m-bar{display:block;height:6px;border-radius:999px;background:var(--g-border);margin-top:10px;overflow:hidden}
229
+ .g-m-bar-fill{display:block;height:100%;background:var(--g-primary);border-radius:999px}
230
+ .g-m-link{color:var(--g-primary);font-weight:600;font-size:13px;flex:none}
231
+ .g-m-badge-ico{width:32px;height:32px;border-radius:8px;background:var(--g-subtle);color:color-mix(in srgb,var(--g-text) 78%,transparent);display:inline-flex;align-items:center;justify-content:center;flex:none}
232
+ .g-m-badge-ico .g-m-ico{width:16px;height:16px}
233
+ .g-m-badge-ico.g-m-accent{background:color-mix(in srgb,var(--g-primary) 12%,var(--g-bg));color:var(--g-primary)}
234
+ .g-m-suggest{padding-bottom:4px}
235
+ .g-m-items{display:flex;flex-direction:column;margin-top:8px}
236
+ .g-m-item{display:flex;align-items:center;gap:12px;width:100%;text-align:left;border:0;border-top:1px solid var(--g-border);background:transparent;color:var(--g-text);padding:10px 0;font:inherit;cursor:pointer;min-height:44px}
237
+ .g-m-item:hover .g-m-item-title{color:var(--g-primary)}
238
+ .g-m-item:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px;border-radius:4px}
239
+ .g-m-item .g-m-grow{flex:1;min-width:0}
240
+ .g-m-item-title{display:block;font-weight:600;overflow-wrap:anywhere}
241
+ .g-m-news{padding:0;overflow:hidden}
242
+ .g-m-news-img{display:block;width:100%;height:150px;object-fit:cover;background:var(--g-subtle)}
243
+ .g-m-news-body{display:block;padding:14px 16px}
244
+ .g-m-news-meta{gap:8px;margin-bottom:4px}
245
+ .g-m-tag{font-size:11px;font-weight:700;color:var(--g-primary-text);background:var(--g-primary);border-radius:4px;padding:1px 6px;text-transform:uppercase;letter-spacing:.03em}
246
+ .g-m-clamp{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
247
+ .g-m-context{align-self:center;font-size:12px;color:var(--g-muted);background:var(--g-subtle);border-radius:999px;padding:3px 10px}
248
+ .g-m-asks{align-items:flex-start}
249
+ .g-m-chip.g-m-ask{border-color:var(--g-primary);color:var(--g-primary);font-weight:600;border-radius:999px;padding:8px 14px;min-height:36px}
250
+ .g-m-chip.g-m-ask:disabled{opacity:.5;cursor:not-allowed}
79
251
  `;
80
252
 
81
253
  // src/render-safe.ts
@@ -164,6 +336,73 @@ function chatIcon(doc) {
164
336
  svg.appendChild(path);
165
337
  return svg;
166
338
  }
339
+ var ICONS = {
340
+ home: ["M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"],
341
+ messages: ["M4 5h16v11H8l-4 4z"],
342
+ help: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z", "M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .8-1 1.5V14", "M12 17h.01"],
343
+ news: ["M3 11v2a1 1 0 0 0 1 1h2l5 4V6L6 10H4a1 1 0 0 0-1 1z", "M15 9a4 4 0 0 1 0 6", "M18 6a8 8 0 0 1 0 12"],
344
+ arrow: ["M5 12h14", "M13 6l6 6-6 6"],
345
+ close: ["M6 6l12 12", "M18 6 6 18"],
346
+ back: ["M15 6l-6 6 6 6"],
347
+ chevron: ["m9 6 6 6-6 6"],
348
+ check: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z", "m8 12 3 3 5-6"],
349
+ alert: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z", "M12 8v4", "M12 16h.01"],
350
+ wrench: ["M14.7 6.3a4 4 0 0 0-5.4 5.4L4 17l3 3 5.3-5.3a4 4 0 0 0 5.4-5.4l-2.5 2.5-2.4-.6-.6-2.4z"],
351
+ play: ["M8 5v14l11-7z"],
352
+ steps: ["M9 6h11", "M9 12h11", "M9 18h11", "M4 6h.01", "M4 12h.01", "M4 18h.01"],
353
+ article: ["M6 3h9l4 4v14H6z", "M9 12h6", "M9 16h6"],
354
+ ask: ["M4 5h16v11H8l-4 4z", "M9 9.5h6", "M9 12.5h4"],
355
+ search: ["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z", "m20 20-3.5-3.5"],
356
+ circle: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z"]
357
+ };
358
+ function icon(doc, name, cls = "g-m-ico") {
359
+ const svg = doc.createElementNS(SVG_NS, "svg");
360
+ svg.setAttribute("viewBox", "0 0 24 24");
361
+ svg.setAttribute("aria-hidden", "true");
362
+ svg.setAttribute("class", cls);
363
+ for (const d of ICONS[name] ?? []) {
364
+ const path = doc.createElementNS(SVG_NS, "path");
365
+ path.setAttribute("d", d);
366
+ svg.appendChild(path);
367
+ }
368
+ return svg;
369
+ }
370
+ var REPLY_TIME_TEXT = {
371
+ minutes: "typically replies in a few minutes",
372
+ hour: "typically replies within an hour",
373
+ hours: "typically replies in a few hours",
374
+ day: "typically replies within a day"
375
+ };
376
+ var STATUS_TEXT = {
377
+ operational: { title: "All systems operational", icon: "check" },
378
+ degraded: { title: "Degraded performance", icon: "alert" },
379
+ outage: { title: "Service outage", icon: "alert" },
380
+ maintenance: { title: "Scheduled maintenance", icon: "wrench" }
381
+ };
382
+ function shortDateTime(iso) {
383
+ const d = new Date(iso);
384
+ if (Number.isNaN(d.getTime())) return "";
385
+ try {
386
+ return d.toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
387
+ } catch {
388
+ return d.toISOString().slice(0, 16).replace("T", " ");
389
+ }
390
+ }
391
+ function shortDate(iso) {
392
+ const d = new Date(iso);
393
+ if (Number.isNaN(d.getTime())) return "";
394
+ try {
395
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
396
+ } catch {
397
+ return d.toISOString().slice(0, 10);
398
+ }
399
+ }
400
+ var initialOf = (name) => (name.trim().match(/\p{L}/u)?.[0] ?? "?").toUpperCase();
401
+ function personalGreeting(greeting, firstName) {
402
+ if (!firstName) return greeting;
403
+ const rest = greeting.replace(/^\s*(hi|hello|hey)( there)?\s*[!,.]?\s*/i, "");
404
+ return rest ? `Hi ${firstName}! ${rest}` : `Hi ${firstName}!`;
405
+ }
167
406
  function relativeTime(iso, now = Date.now()) {
168
407
  const s = Math.max(0, Math.round((now - Date.parse(iso)) / 1e3));
169
408
  if (!Number.isFinite(s) || s < 60) return "now";
@@ -199,6 +438,25 @@ function renderMessenger(options) {
199
438
  let threadTimer = null;
200
439
  let backgroundTimer = null;
201
440
  let threadSeq = 0;
441
+ let howTos = null;
442
+ let howTosSeq = 0;
443
+ const howToCache = /* @__PURE__ */ new Map();
444
+ let page = null;
445
+ let pageKey = null;
446
+ let pageAt = 0;
447
+ let pageSeq = 0;
448
+ let pageLoading = false;
449
+ const PAGE_TTL_MS = 12e4;
450
+ const cards = { suggestions: config.home?.suggestions !== false, progress: config.home?.progress !== false, news: config.home?.news !== false };
451
+ const visitor = () => {
452
+ try {
453
+ const v = options.visitor?.();
454
+ if (v) return v;
455
+ } catch {
456
+ }
457
+ return { firstName: null, path: null, pageGuides: [], resumeGuide: null, checklist: null, news: [] };
458
+ };
459
+ const news = () => cards.news ? visitor().news : [];
202
460
  const pos = `g-pos-${config.launcher.position}`;
203
461
  const rootNode = scope.getRootNode();
204
462
  const shadowHost = "host" in rootNode ? rootNode.host : null;
@@ -230,6 +488,7 @@ function renderMessenger(options) {
230
488
  data: () => ({
231
489
  collections: articles?.collections ?? options.resourceCenter?.collections ?? [],
232
490
  articles: articles?.articles ?? options.resourceCenter?.articles ?? [],
491
+ suggested: helpSuggestions(),
233
492
  recommended: rcExtras(options.resourceCenter?.recommendedGuideIds),
234
493
  checklists: rcExtras(options.resourceCenter?.checklistGuideIds),
235
494
  faqs: options.resourceCenter?.faqs,
@@ -246,7 +505,8 @@ function renderMessenger(options) {
246
505
  setOpen(false);
247
506
  options.onOpenChecklist?.(id);
248
507
  },
249
- onArticleView: options.onArticleView
508
+ onArticleView: options.onArticleView,
509
+ extra: (q) => howToItems(q)
250
510
  });
251
511
  const unreadTotal = () => conversations.reduce((n, c) => n + (c.unread > 0 ? 1 : 0), 0);
252
512
  const updateBadge = () => {
@@ -276,6 +536,32 @@ function renderMessenger(options) {
276
536
  }).catch(() => {
277
537
  }).finally(() => articlesLoading = false);
278
538
  };
539
+ const loadHowTos = () => {
540
+ if (!api.listHowTos) return;
541
+ const seq = ++howTosSeq;
542
+ api.listHowTos().then((res) => {
543
+ if (seq !== howTosSeq || destroyed) return;
544
+ howTos = res;
545
+ if (open && view.kind === "tab" && view.tab === "home") render();
546
+ else if (open && view.kind === "tab" && view.tab === "help") help.render();
547
+ }).catch(() => {
548
+ });
549
+ };
550
+ const pageHowTos = () => (howTos?.forPage ?? []).map((id) => howTos?.howTos.find((h) => h.id === id)).filter((h) => !!h);
551
+ const stepsLabel = (h) => `${h.stepCount} ${h.stepCount === 1 ? "step" : "steps"}`;
552
+ const openHowTo = (id) => go({ kind: "howto", id, from: view });
553
+ function howToItems(q) {
554
+ const list = q ? (howTos?.howTos ?? []).filter((h) => h.title.toLowerCase().includes(q) || h.description.toLowerCase().includes(q)).slice(0, 10) : [];
555
+ if (!list.length) return [];
556
+ return [
557
+ el(doc, "div", "g-rc-section", `How-tos (${list.length})`),
558
+ ...list.map((h) => {
559
+ const b = listItem(doc, "\u{1F4CB}", h.title, h.description || stepsLabel(h));
560
+ b.addEventListener("click", () => openHowTo(h.id));
561
+ return b;
562
+ })
563
+ ];
564
+ }
279
565
  const refreshConversations = () => api.listConversations().then((list) => {
280
566
  conversations = [...list].sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
281
567
  updateBadge();
@@ -284,92 +570,379 @@ function renderMessenger(options) {
284
570
  });
285
571
  function renderHead(title, back) {
286
572
  head.textContent = "";
573
+ head.hidden = false;
287
574
  if (back) {
288
- const b = el(doc, "button", "g-m-back", "\u2190");
575
+ const b = el(doc, "button", "g-m-back");
289
576
  b.type = "button";
290
577
  b.setAttribute("aria-label", "Back");
578
+ b.appendChild(icon(doc, "back"));
291
579
  b.addEventListener("click", back);
292
580
  head.appendChild(b);
293
581
  }
294
582
  head.appendChild(el(doc, "h2", "", title));
295
- const close = el(doc, "button", "g-close", "\xD7");
583
+ head.appendChild(closeButton("g-close"));
584
+ }
585
+ function closeButton(cls) {
586
+ const close = el(doc, "button", cls);
296
587
  close.type = "button";
297
588
  close.setAttribute("aria-label", "Close");
589
+ close.appendChild(icon(doc, "close"));
298
590
  close.addEventListener("click", () => setOpen(false));
299
- head.appendChild(close);
591
+ return close;
300
592
  }
301
593
  function renderTabs() {
302
594
  tabs.textContent = "";
303
- tabs.hidden = view.kind === "thread";
595
+ tabs.hidden = view.kind !== "tab";
304
596
  const items = [
305
- ["home", "\u2302", "Home"],
306
- ["messages", "\u{1F4AC}", "Messages"],
307
- ["help", "?", "Help"]
597
+ ["home", "home", "Home"],
598
+ ["messages", "messages", "Messages"],
599
+ ["help", "help", "Help"]
308
600
  ];
309
- for (const [tab, icon, label] of items) {
601
+ const latest = news();
602
+ if (latest.length) items.push(["news", "news", "News"]);
603
+ for (const [tab, ico, label] of items) {
310
604
  const b = el(doc, "button", "g-m-tab");
311
605
  b.type = "button";
312
606
  b.setAttribute("role", "tab");
313
607
  b.setAttribute("aria-selected", String(view.kind === "tab" && view.tab === tab));
314
- const i = el(doc, "span", "g-m-tab-icon", icon);
315
- i.setAttribute("aria-hidden", "true");
608
+ const i = el(doc, "span", "g-m-tab-icon");
609
+ i.appendChild(icon(doc, ico));
316
610
  b.append(i, el(doc, "span", "", label));
611
+ if (tab === "news" && latest.some((n) => n.unread)) {
612
+ const dot = el(doc, "span", "g-m-tab-dot");
613
+ dot.setAttribute("aria-label", "New");
614
+ i.appendChild(dot);
615
+ }
317
616
  b.addEventListener("click", () => go({ kind: "tab", tab }));
318
617
  tabs.appendChild(b);
319
618
  }
320
619
  }
321
- function conversationCard(c) {
620
+ function conversationCard(c, label) {
322
621
  const card = el(doc, "button", "g-m-card");
323
622
  card.type = "button";
324
623
  const row = el(doc, "div", "g-m-row");
325
624
  if (c.unread > 0) row.appendChild(el(doc, "span", "g-m-dot"));
326
625
  const text = el(doc, "div", "g-m-grow");
626
+ if (label) text.appendChild(el(doc, "div", "g-m-card-label", label));
327
627
  text.append(el(doc, "div", "g-m-card-title g-m-ellipsis", c.subject || "Conversation"), el(doc, "div", "g-m-card-sub g-m-ellipsis", c.lastMessagePreview));
328
628
  row.append(text, el(doc, "span", "g-m-time", relativeTime(c.lastMessageAt)));
329
629
  card.appendChild(row);
330
- card.setAttribute("aria-label", `${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`);
630
+ card.setAttribute("aria-label", `${label ? `${label}: ` : ""}${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`);
331
631
  card.addEventListener("click", () => go({ kind: "thread", conversationId: c.id }));
332
632
  return card;
333
633
  }
634
+ function actionCard(opts) {
635
+ const card = el(doc, "button", `g-m-card g-m-row g-m-center${opts.cls ? ` ${opts.cls}` : ""}`);
636
+ card.type = "button";
637
+ if (opts.lead) card.appendChild(opts.lead);
638
+ const text = el(doc, "span", "g-m-grow");
639
+ text.appendChild(el(doc, "span", "g-m-card-title", opts.title));
640
+ if (opts.sub) text.appendChild(el(doc, "span", "g-m-card-sub", opts.sub));
641
+ card.appendChild(text);
642
+ if (opts.trail) card.appendChild(icon(doc, "arrow", "g-m-ico g-m-trail"));
643
+ card.addEventListener("click", opts.onClick);
644
+ return card;
645
+ }
646
+ function progressBar(done, total) {
647
+ const bar = el(doc, "span", "g-m-bar");
648
+ bar.setAttribute("role", "progressbar");
649
+ bar.setAttribute("aria-valuemin", "0");
650
+ bar.setAttribute("aria-valuemax", String(total));
651
+ bar.setAttribute("aria-valuenow", String(done));
652
+ const fill = el(doc, "span", "g-m-bar-fill");
653
+ fill.style.width = `${total ? Math.round(Math.min(done, total) / total * 100) : 0}%`;
654
+ bar.appendChild(fill);
655
+ return bar;
656
+ }
657
+ function teamAvatars() {
658
+ const team = (config.team ?? []).slice(0, 3);
659
+ if (!team.length) return null;
660
+ const wrap = el(doc, "div", "g-m-team");
661
+ wrap.setAttribute("role", "img");
662
+ wrap.setAttribute("aria-label", `The team: ${team.map((t) => t.name).join(", ")}`);
663
+ team.forEach((t, i) => {
664
+ const a = el(doc, "span", `g-m-avatar g-m-avatar-${i % 3}`);
665
+ if (t.avatarUrl) {
666
+ const img = el(doc, "img");
667
+ img.src = t.avatarUrl;
668
+ img.alt = "";
669
+ img.referrerPolicy = "no-referrer";
670
+ img.addEventListener("error", () => {
671
+ img.remove();
672
+ a.textContent = initialOf(t.name);
673
+ });
674
+ a.appendChild(img);
675
+ } else a.textContent = initialOf(t.name);
676
+ wrap.appendChild(a);
677
+ });
678
+ return wrap;
679
+ }
680
+ function brandMark() {
681
+ const name = options.brand?.name || config.name;
682
+ const mark = el(doc, "span", "g-m-logo");
683
+ const url = options.brand?.logoUrl;
684
+ if (url && /^(https?:)?\/\//.test(url)) {
685
+ const img = el(doc, "img");
686
+ img.src = url;
687
+ img.alt = name;
688
+ img.addEventListener("error", () => {
689
+ img.remove();
690
+ mark.textContent = initialOf(name);
691
+ });
692
+ mark.appendChild(img);
693
+ } else {
694
+ mark.textContent = initialOf(name);
695
+ mark.setAttribute("aria-label", name);
696
+ mark.setAttribute("role", "img");
697
+ }
698
+ return mark;
699
+ }
700
+ function messageSub() {
701
+ const reply = config.replyTime ? REPLY_TIME_TEXT[config.replyTime] : null;
702
+ if (config.aiAvailable) return reply ? `${config.name} answers right away. The team ${reply}.` : `${config.name} answers right away, and the team can help too.`;
703
+ return reply ? `The team ${reply}.` : "The team will reply here.";
704
+ }
705
+ function statusCard(status) {
706
+ const meta = STATUS_TEXT[status.level] ?? STATUS_TEXT.operational;
707
+ const card = el(doc, "div", `g-m-card g-m-static g-m-row g-m-center g-m-status g-m-status-${status.level}`);
708
+ card.setAttribute("role", "status");
709
+ card.appendChild(icon(doc, meta.icon, "g-m-ico g-m-status-ico"));
710
+ const text = el(doc, "span", "g-m-grow");
711
+ text.appendChild(el(doc, "span", "g-m-card-title", status.message || meta.title));
712
+ const when = shortDateTime(status.updatedAt);
713
+ if (when) text.appendChild(el(doc, "span", "g-m-card-sub", `Updated ${when}`));
714
+ card.appendChild(text);
715
+ return card;
716
+ }
717
+ function progressCards(v) {
718
+ const out = [];
719
+ if (v.resumeGuide) {
720
+ const g = v.resumeGuide;
721
+ const card = el(doc, "button", "g-m-card");
722
+ card.type = "button";
723
+ const row = el(doc, "span", "g-m-row g-m-center");
724
+ row.appendChild(iconTile("play", true));
725
+ const text = el(doc, "span", "g-m-grow");
726
+ text.append(el(doc, "span", "g-m-card-label", "Pick up where you left off"), el(doc, "span", "g-m-card-title g-m-ellipsis", g.name), el(doc, "span", "g-m-card-sub", `Step ${g.stepIndex + 1} of ${g.stepCount}`));
727
+ row.append(text, el(doc, "span", "g-m-link", "Resume"));
728
+ card.append(row, progressBar(g.stepIndex, g.stepCount));
729
+ card.addEventListener("click", () => {
730
+ setOpen(false);
731
+ options.onResumeGuide?.(g.id, g.stepId);
732
+ });
733
+ out.push(card);
734
+ }
735
+ if (v.checklist && v.checklist.total > 0) {
736
+ const c = v.checklist;
737
+ const card = el(doc, "button", "g-m-card");
738
+ card.type = "button";
739
+ const top = el(doc, "span", "g-m-row g-m-baseline");
740
+ top.append(el(doc, "span", "g-m-card-title g-m-grow g-m-ellipsis", c.name), el(doc, "span", "g-m-time", `${c.done} of ${c.total} done`));
741
+ card.append(top, progressBar(c.done, c.total));
742
+ if (c.nextTask) card.appendChild(el(doc, "span", "g-m-card-sub", `Next: ${c.nextTask}`));
743
+ card.addEventListener("click", () => {
744
+ setOpen(false);
745
+ options.onOpenChecklist?.(c.id);
746
+ });
747
+ out.push(card);
748
+ }
749
+ return out;
750
+ }
751
+ function iconTile(name, accent = false) {
752
+ const b = el(doc, "span", `g-m-badge-ico${accent ? " g-m-accent" : ""}`);
753
+ b.appendChild(icon(doc, name));
754
+ return b;
755
+ }
756
+ function pageSuggestions(v, max = 4) {
757
+ if (!cards.suggestions) return [];
758
+ const out = [];
759
+ for (const g of v.pageGuides.slice(0, 2)) {
760
+ out.push({ icon: "play", title: g.name, sub: g.kind === "checklist" ? "Checklist" : "Interactive guide", accent: true, run: () => g.kind === "checklist" ? openChecklist(g.id) : startGuide(g.key || g.id) });
761
+ }
762
+ const inApp = /* @__PURE__ */ new Set();
763
+ for (const h of pageHowTos()) {
764
+ if (out.length >= max) break;
765
+ inApp.add(h.title.toLowerCase());
766
+ out.push({ icon: "steps", title: h.title, sub: `How-to \xB7 ${stepsLabel(h)}`, run: () => openHowTo(h.id) });
767
+ }
768
+ for (const s of page?.suggestions ?? []) {
769
+ if (out.length >= max) break;
770
+ if (s.type === "howto" && inApp.has(s.title.toLowerCase())) continue;
771
+ if (s.type === "howto") out.push({ icon: "steps", title: s.title, sub: `How-to \xB7 ${s.steps} ${s.steps === 1 ? "step" : "steps"}`, run: () => openArticle(s.articleId) });
772
+ else out.push({ icon: "article", title: s.title, sub: s.summary || "Article", run: () => openArticle(s.articleId) });
773
+ }
774
+ return out.slice(0, max);
775
+ }
776
+ function startGuide(keyOrId) {
777
+ setOpen(false);
778
+ options.onStartGuide?.(keyOrId);
779
+ }
780
+ function openChecklist(id) {
781
+ setOpen(false);
782
+ options.onOpenChecklist?.(id);
783
+ }
784
+ function ask(question) {
785
+ go({ kind: "thread", conversationId: null });
786
+ if (!textarea) return;
787
+ textarea.value = question;
788
+ submit();
789
+ }
790
+ function suggestionButton(s) {
791
+ const b = el(doc, "button", "g-m-item");
792
+ b.type = "button";
793
+ b.appendChild(iconTile(s.icon, s.accent));
794
+ const text = el(doc, "span", "g-m-grow");
795
+ text.append(el(doc, "span", "g-m-item-title", s.title), el(doc, "span", "g-m-card-sub g-m-ellipsis", s.sub));
796
+ b.appendChild(text);
797
+ b.addEventListener("click", s.run);
798
+ return b;
799
+ }
800
+ function newsCard(n) {
801
+ const card = el(doc, "button", "g-m-card g-m-news");
802
+ card.type = "button";
803
+ if (n.imageUrl && /^https?:\/\//.test(n.imageUrl)) {
804
+ const img = el(doc, "img", "g-m-news-img");
805
+ img.src = n.imageUrl;
806
+ img.alt = "";
807
+ img.loading = "lazy";
808
+ img.addEventListener("error", () => img.remove());
809
+ card.appendChild(img);
810
+ }
811
+ const body = el(doc, "span", "g-m-news-body");
812
+ const meta = el(doc, "span", "g-m-row g-m-center g-m-news-meta");
813
+ if (n.unread) meta.appendChild(el(doc, "span", "g-m-tag", "New"));
814
+ meta.appendChild(el(doc, "span", "g-m-time", shortDate(n.publishedAt)));
815
+ body.append(meta, el(doc, "span", "g-m-card-title", n.title));
816
+ if (n.body) body.appendChild(el(doc, "span", "g-m-card-sub g-m-clamp", n.body));
817
+ card.appendChild(body);
818
+ card.addEventListener("click", () => startGuide(n.key || n.id));
819
+ return card;
820
+ }
334
821
  function renderHome() {
335
- renderHead(config.name);
336
- const scroll = el(doc, "div", "g-m-scroll");
337
- scroll.append(el(doc, "div", "g-m-hero", "Hi there \u{1F44B}"), el(doc, "p", "g-m-greet", config.greeting));
338
- const ask = el(doc, "button", "g-m-card");
339
- ask.type = "button";
340
- ask.append(el(doc, "div", "g-m-card-title", "Ask a question"), el(doc, "div", "g-m-card-sub", config.aiAvailable ? `${config.name} answers right away, and the team can help too` : "The team will reply here"));
341
- ask.addEventListener("click", () => go({ kind: "thread", conversationId: null }));
342
- scroll.appendChild(ask);
822
+ head.hidden = true;
823
+ const v = visitor();
824
+ loadPage();
825
+ const scroll = el(doc, "div", "g-m-scroll g-m-home");
826
+ const hero = el(doc, "div", "g-m-hero-bg");
827
+ hero.setAttribute("aria-hidden", "true");
828
+ const inner = el(doc, "div", "g-m-home-inner");
829
+ const top = el(doc, "div", "g-m-hero-top");
830
+ const right = el(doc, "div", "g-m-row g-m-center");
831
+ const team = teamAvatars();
832
+ if (team) right.appendChild(team);
833
+ right.appendChild(closeButton("g-m-hero-close"));
834
+ top.append(brandMark(), right);
835
+ const hello = el(doc, "h2", "g-m-hello");
836
+ hello.append(el(doc, "span", "g-m-hello-1", v.firstName ? `Hi ${v.firstName}.` : "Hi there."), el(doc, "span", "g-m-hello-2", "How can we help?"));
837
+ inner.append(top, hello);
838
+ inner.appendChild(actionCard({ title: "Send us a message", sub: messageSub(), trail: true, onClick: () => go({ kind: "thread", conversationId: null }) }));
343
839
  const recent = conversations[0];
344
- if (recent) {
345
- scroll.appendChild(el(doc, "div", "g-rc-section", "Recent conversation"));
346
- scroll.appendChild(conversationCard(recent));
840
+ if (recent) inner.appendChild(conversationCard(recent, recent.status === "closed" && !recent.unread ? "Recent conversation" : "Continue the conversation"));
841
+ if (config.status) inner.appendChild(statusCard(config.status));
842
+ if (cards.progress) progressCards(v).forEach((c) => inner.appendChild(c));
843
+ const suggestions = pageSuggestions(v);
844
+ const questions = cards.suggestions ? (page?.questions ?? []).slice(0, 2) : [];
845
+ const label = v.path ? pageLabel(v.path) : null;
846
+ const fallback = !suggestions.length && !questions.length ? rcExtras(options.resourceCenter?.recommendedGuideIds).slice(0, 3) : [];
847
+ if (suggestions.length || questions.length || fallback.length) {
848
+ const box = el(doc, "div", "g-m-card g-m-static g-m-suggest");
849
+ box.appendChild(el(doc, "div", "g-m-card-title", fallback.length ? "Recommended" : "Suggested for you"));
850
+ if (!fallback.length && label) box.appendChild(el(doc, "div", "g-m-card-sub", `For the ${label} page`));
851
+ const list = el(doc, "div", "g-m-items");
852
+ suggestions.forEach((s) => list.appendChild(suggestionButton(s)));
853
+ questions.forEach((q) => list.appendChild(suggestionButton({ icon: "ask", title: q, sub: config.aiAvailable ? `Ask ${config.name}` : "Ask the team", run: () => ask(q) })));
854
+ fallback.forEach((g) => list.appendChild(suggestionButton({ icon: "play", title: g.name, sub: "Interactive guide", accent: true, run: () => startGuide(g.key || g.id) })));
855
+ box.appendChild(list);
856
+ inner.appendChild(box);
347
857
  }
348
- const search = el(doc, "button", "g-m-card");
349
- search.type = "button";
350
- search.append(el(doc, "div", "g-m-card-title", "Search for help"), el(doc, "div", "g-m-card-sub", "Browse articles and guides"));
351
- search.addEventListener("click", () => go({ kind: "tab", tab: "help" }));
352
- scroll.appendChild(search);
858
+ const latest = news()[0];
859
+ if (latest) inner.appendChild(newsCard(latest));
860
+ inner.appendChild(actionCard({ title: "Search for help", sub: "Articles, guides and how-tos", lead: icon(doc, "search", "g-m-ico g-m-lead"), onClick: () => go({ kind: "tab", tab: "help" }) }));
861
+ scroll.append(hero, inner);
862
+ main.appendChild(scroll);
863
+ }
864
+ function renderHowToView(id, from) {
865
+ const cached = howToCache.get(id);
866
+ renderHead(cached?.title ?? "How-to", () => go(from));
867
+ const scroll = el(doc, "div", "g-m-scroll");
868
+ if (cached) {
869
+ scroll.appendChild(
870
+ renderHowTo(doc, cached, (key) => {
871
+ setOpen(false);
872
+ options.onStartGuide?.(key);
873
+ })
874
+ );
875
+ } else scroll.appendChild(el(doc, "div", "g-m-empty", cached === null ? "This how-to isn't available any more." : "Loading\u2026"));
353
876
  main.appendChild(scroll);
877
+ if (cached !== void 0 || !api.getHowTo) return;
878
+ const seq = threadSeq;
879
+ api.getHowTo(id).catch(() => null).then((h) => {
880
+ howToCache.set(id, h);
881
+ if (seq === threadSeq && view.kind === "howto" && view.id === id) render();
882
+ });
354
883
  }
355
884
  function renderMessagesTab() {
356
885
  renderHead("Messages");
357
886
  const scroll = el(doc, "div", "g-m-scroll");
358
887
  if (!conversations.length) scroll.appendChild(el(doc, "div", "g-m-empty", "No conversations yet."));
359
888
  conversations.forEach((c) => scroll.appendChild(conversationCard(c)));
360
- const start = el(doc, "button", "g-btn g-btn-primary", "Send us a message");
889
+ const start = el(doc, "button", "g-btn g-btn-primary g-m-wide", "Send us a message");
361
890
  start.type = "button";
362
- start.style.width = "100%";
363
891
  start.addEventListener("click", () => go({ kind: "thread", conversationId: null }));
364
892
  scroll.appendChild(start);
365
893
  main.appendChild(scroll);
366
894
  }
895
+ function helpSuggestions() {
896
+ const v = visitor();
897
+ const items = pageSuggestions(v, 5).map((s) => ({ kind: s.icon === "play" ? "guide" : s.icon === "steps" ? "howto" : "article", title: s.title, summary: s.sub, run: s.run }));
898
+ if (!items.length) return void 0;
899
+ const label = v.path ? pageLabel(v.path) : null;
900
+ return { title: label ? `For the ${label} page` : "Suggested for you", items };
901
+ }
367
902
  function renderHelpTab() {
368
903
  renderHead("Help");
369
904
  loadArticles();
905
+ loadPage();
370
906
  help.render();
371
907
  main.appendChild(help.el);
372
908
  }
909
+ function renderNewsTab() {
910
+ renderHead("News");
911
+ const scroll = el(doc, "div", "g-m-scroll");
912
+ const items = news();
913
+ if (!items.length) scroll.appendChild(el(doc, "div", "g-m-empty", "No news yet."));
914
+ items.forEach((n) => scroll.appendChild(newsCard(n)));
915
+ main.appendChild(scroll);
916
+ const unread = items.filter((n) => n.unread).map((n) => n.id);
917
+ if (unread.length) options.onNewsSeen?.(unread);
918
+ }
919
+ function loadPage(force = false) {
920
+ const path = visitor().path;
921
+ if (!path || !api.pageContent || !cards.suggestions) {
922
+ page = null;
923
+ pageKey = null;
924
+ return;
925
+ }
926
+ const key = foldPathIds(path).toLowerCase();
927
+ if (!force && key === pageKey && (pageLoading || Date.now() - pageAt < PAGE_TTL_MS)) return;
928
+ if (key !== pageKey) page = null;
929
+ pageKey = key;
930
+ pageLoading = true;
931
+ const seq = ++pageSeq;
932
+ api.pageContent(path).then((res) => {
933
+ if (seq !== pageSeq) return;
934
+ page = res;
935
+ pageAt = Date.now();
936
+ if (!open) return;
937
+ if (view.kind === "tab" && view.tab === "home") render();
938
+ else if (view.kind === "tab" && view.tab === "help") help.render();
939
+ else if (view.kind === "thread" && !messages.length) renderLog();
940
+ }).catch(() => {
941
+ if (seq === pageSeq) pageAt = Date.now();
942
+ }).finally(() => {
943
+ if (seq === pageSeq) pageLoading = false;
944
+ });
945
+ }
373
946
  let log = null;
374
947
  let foot = null;
375
948
  let textarea = null;
@@ -400,7 +973,7 @@ function renderMessenger(options) {
400
973
  if (m.status === "pending") bubble.setAttribute("aria-busy", "true");
401
974
  bubble.dataset.messageId = m.id;
402
975
  wrap.appendChild(bubble);
403
- if (m.citations.length || m.suggestedGuides.length) {
976
+ if (m.citations.length || m.suggestedGuides.length || m.howTos?.length) {
404
977
  const chips = el(doc, "div", "g-m-chips");
405
978
  for (const g of m.suggestedGuides) {
406
979
  const b = el(doc, "button", "g-m-chip g-m-guide", `\u25B6 Show me: ${g.name}`);
@@ -417,6 +990,12 @@ function renderMessenger(options) {
417
990
  b.addEventListener("click", () => openArticle(c.articleId));
418
991
  chips.appendChild(b);
419
992
  }
993
+ for (const h of m.howTos ?? []) {
994
+ const b = el(doc, "button", "g-m-chip", `\u{1F4CB} ${h.title}`);
995
+ b.type = "button";
996
+ b.addEventListener("click", () => openHowTo(h.howToId));
997
+ chips.appendChild(b);
998
+ }
420
999
  wrap.appendChild(chips);
421
1000
  }
422
1001
  return wrap;
@@ -426,8 +1005,27 @@ function renderMessenger(options) {
426
1005
  const atBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 40;
427
1006
  log.textContent = "";
428
1007
  if (!messages.length) {
429
- const intro = { id: "intro", author: config.aiAvailable ? "scout" : "agent", authorName: "Support team", body: config.greeting, citations: [], suggestedGuides: [], status: "complete", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
1008
+ const v = visitor();
1009
+ const label = v.path ? pageLabel(v.path) : null;
1010
+ if (label && config.aiAvailable && !current) log.appendChild(el(doc, "div", "g-m-context", `Asking from the ${label} page`));
1011
+ const intro = { id: "intro", author: config.aiAvailable ? "scout" : "agent", authorName: "Support team", body: personalGreeting(config.greeting, v.firstName), citations: [], suggestedGuides: [], status: "complete", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
430
1012
  log.appendChild(messageNode(intro));
1013
+ const questions = !current && cards.suggestions ? page?.questions ?? [] : [];
1014
+ if (questions.length && !sending) {
1015
+ const chips = el(doc, "div", "g-m-chips g-m-asks");
1016
+ chips.setAttribute("aria-label", "Common questions on this page");
1017
+ for (const q of questions) {
1018
+ const b = el(doc, "button", "g-m-chip g-m-ask", q);
1019
+ b.type = "button";
1020
+ b.addEventListener("click", () => {
1021
+ if (!textarea || sending) return;
1022
+ textarea.value = q;
1023
+ submit();
1024
+ });
1025
+ chips.appendChild(b);
1026
+ }
1027
+ log.appendChild(chips);
1028
+ }
431
1029
  }
432
1030
  messages.forEach((m) => log.appendChild(messageNode(m)));
433
1031
  if (atBottom || sending) log.scrollTop = log.scrollHeight;
@@ -493,7 +1091,8 @@ function renderMessenger(options) {
493
1091
  textarea.placeholder = current?.status === "open" ? "Reply to the team\u2026" : "Ask a question\u2026";
494
1092
  textarea.setAttribute("aria-label", "Message");
495
1093
  textarea.value = draft;
496
- sendButton = el(doc, "button", "g-m-send", "\u2191");
1094
+ sendButton = el(doc, "button", "g-m-send");
1095
+ sendButton.appendChild(icon(doc, "arrow"));
497
1096
  sendButton.type = "submit";
498
1097
  sendButton.setAttribute("aria-label", "Send");
499
1098
  compose.append(textarea, sendButton);
@@ -523,9 +1122,14 @@ function renderMessenger(options) {
523
1122
  textarea = null;
524
1123
  sendButton = null;
525
1124
  if (view.kind === "thread") renderThread(view.conversationId);
1125
+ else if (view.kind === "howto") renderHowToView(view.id, view.from);
526
1126
  else if (view.tab === "home") renderHome();
527
1127
  else if (view.tab === "messages") renderMessagesTab();
528
- else renderHelpTab();
1128
+ else if (view.tab === "news" && news().length) renderNewsTab();
1129
+ else if (view.tab === "news") {
1130
+ view = { kind: "tab", tab: "home" };
1131
+ renderHome();
1132
+ } else renderHelpTab();
529
1133
  renderTabs();
530
1134
  }
531
1135
  const handlers = (seq) => ({
@@ -622,13 +1226,14 @@ function renderMessenger(options) {
622
1226
  threadSeq++;
623
1227
  if (threadTimer) clearTimeout(threadTimer);
624
1228
  threadTimer = null;
625
- if (view.kind === "tab" && view.tab === "help" && !(next.kind === "tab" && next.tab === "help")) help.reset();
1229
+ if (view.kind === "tab" && view.tab === "help" && !(next.kind === "tab" && next.tab === "help") && next.kind !== "howto") help.reset();
626
1230
  view = next;
627
1231
  if (next.kind === "thread") {
628
1232
  messages = [];
629
1233
  streaming.clear();
630
1234
  sending = false;
631
1235
  current = next.conversationId ? conversations.find((c) => c.id === next.conversationId) ?? null : null;
1236
+ if (!next.conversationId) loadPage();
632
1237
  render();
633
1238
  if (next.conversationId) {
634
1239
  const seq = threadSeq;
@@ -667,6 +1272,7 @@ function renderMessenger(options) {
667
1272
  if (v) {
668
1273
  render();
669
1274
  void refreshConversations();
1275
+ loadHowTos();
670
1276
  if (view.kind === "thread") scheduleThreadPoll();
671
1277
  options.onOpen?.();
672
1278
  } else {
@@ -709,6 +1315,16 @@ function renderMessenger(options) {
709
1315
  setOpen(true);
710
1316
  openArticle(id);
711
1317
  },
1318
+ refresh() {
1319
+ if (destroyed) return;
1320
+ if (!open) {
1321
+ pageKey = null;
1322
+ return;
1323
+ }
1324
+ loadPage();
1325
+ if (view.kind === "tab") render();
1326
+ else if (!messages.length) renderLog();
1327
+ },
712
1328
  destroy() {
713
1329
  destroyed = true;
714
1330
  if (threadTimer) clearTimeout(threadTimer);
@@ -721,6 +1337,7 @@ function renderMessenger(options) {
721
1337
  };
722
1338
  }
723
1339
  export {
1340
+ personalGreeting,
724
1341
  relativeTime,
725
1342
  renderMessenger
726
1343
  };