@guidelytech/guide-runtime 0.10.0 → 0.11.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/dist/messenger.js CHANGED
@@ -3,11 +3,11 @@ import {
3
3
  el,
4
4
  resultRow,
5
5
  startableGuide
6
- } from "./chunk-KJRXCITD.js";
7
- import "./chunk-PG7CMBHD.js";
6
+ } from "./chunk-H3CROHPR.js";
7
+ import "./chunk-VJSQM23R.js";
8
8
 
9
9
  // src/messenger.ts
10
- import { foldPathIds, matchesHelpQuery, pageLabel } from "@guidelytech/shared/runtime";
10
+ import { foldPathIds, matchesHelpQuery, orderedModules, pageLabel } from "@guidelytech/shared/runtime";
11
11
 
12
12
  // src/howto-view.ts
13
13
  var pct = (v) => `${(v * 100).toFixed(3)}%`;
@@ -379,6 +379,874 @@ async function readScreenshot(doc, file) {
379
379
  return { fileName: scaled ? `${name.replace(/\.[a-z0-9]+$/i, "")}.jpg` : name, data, previewUrl: dataUrl };
380
380
  }
381
381
 
382
+ // src/ideas-view.ts
383
+ import { FEEDBACK_PRIORITIES, FEEDBACK_PRIORITY_LABEL, FEEDBACK_STATUS_LABEL, MAX_FEEDBACK_COMMENT, MAX_FEEDBACK_DESCRIPTION, MAX_FEEDBACK_NOTE, MAX_FEEDBACK_TITLE, feedbackReference } from "@guidelytech/shared";
384
+
385
+ // src/render-safe.ts
386
+ var CITATION = /\s*\[\[article:[^\]]*\]\]/g;
387
+ var INLINE = /\*\*([^*\n]+)\*\*|`([^`\n]+)`|(!?)\[([^\]\n]{1,200})\]\((https?:\/\/[^\s)]+)\)|(https?:\/\/[^\s<>()"']+[^\s<>().,;:!?'"])/g;
388
+ function stripCitationMarkers(text) {
389
+ return text.replace(CITATION, "");
390
+ }
391
+ function safeHttpUrl(raw) {
392
+ try {
393
+ const u = new URL(raw);
394
+ return u.protocol === "http:" || u.protocol === "https:" ? u.href : null;
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
399
+ function link(doc, label, href) {
400
+ const url = safeHttpUrl(href);
401
+ if (!url) return doc.createTextNode(label);
402
+ const a = doc.createElement("a");
403
+ a.href = url;
404
+ a.target = "_blank";
405
+ a.rel = "noopener noreferrer nofollow";
406
+ a.textContent = label;
407
+ return a;
408
+ }
409
+ function appendInline(doc, parent, text) {
410
+ let last = 0;
411
+ for (const m of text.matchAll(INLINE)) {
412
+ const start = m.index ?? 0;
413
+ if (start > last) parent.appendChild(doc.createTextNode(text.slice(last, start)));
414
+ if (m[1] !== void 0) {
415
+ const b = doc.createElement("strong");
416
+ b.textContent = m[1];
417
+ parent.appendChild(b);
418
+ } else if (m[2] !== void 0) {
419
+ const c = doc.createElement("code");
420
+ c.textContent = m[2];
421
+ parent.appendChild(c);
422
+ } else if (m[4] !== void 0 && m[5] !== void 0) {
423
+ parent.appendChild(m[3] ? doc.createTextNode(m[4]) : link(doc, m[4], m[5]));
424
+ } else if (m[6] !== void 0) {
425
+ parent.appendChild(link(doc, m[6], m[6]));
426
+ }
427
+ last = start + m[0].length;
428
+ }
429
+ if (last < text.length) parent.appendChild(doc.createTextNode(text.slice(last)));
430
+ }
431
+ var BULLET = /^\s*[-*•]\s+/;
432
+ var NUMBERED = /^\s*\d{1,3}[.)]\s+/;
433
+ function renderSafeText(doc, text) {
434
+ const frag = doc.createDocumentFragment();
435
+ const blocks = stripCitationMarkers(text).replace(/\r\n?/g, "\n").trim().split(/\n{2,}/);
436
+ for (const block of blocks) {
437
+ if (!block.trim()) continue;
438
+ const lines = block.split("\n");
439
+ if (lines.every((l) => BULLET.test(l)) || lines.every((l) => NUMBERED.test(l))) {
440
+ const ordered = NUMBERED.test(lines[0]);
441
+ const list = doc.createElement(ordered ? "ol" : "ul");
442
+ for (const line of lines) {
443
+ const li = doc.createElement("li");
444
+ appendInline(doc, li, line.replace(ordered ? NUMBERED : BULLET, ""));
445
+ list.appendChild(li);
446
+ }
447
+ frag.appendChild(list);
448
+ continue;
449
+ }
450
+ const p = doc.createElement("p");
451
+ lines.forEach((line, i) => {
452
+ if (i) p.appendChild(doc.createElement("br"));
453
+ appendInline(doc, p, line);
454
+ });
455
+ frag.appendChild(p);
456
+ }
457
+ return frag;
458
+ }
459
+
460
+ // src/ideas-view.ts
461
+ var SVG_NS = "http://www.w3.org/2000/svg";
462
+ var IDEA_ICON = ["M9 18h6", "M10 21h4", "M12 3a6 6 0 0 0-3.6 10.8c.7.5 1.1 1.3 1.1 2.1V16h5v-.1c0-.8.4-1.6 1.1-2.1A6 6 0 0 0 12 3z"];
463
+ var UP_ICON = ["m6 15 6-6 6 6"];
464
+ function svgIcon(doc, paths, cls = "g-m-ico") {
465
+ const svg = doc.createElementNS(SVG_NS, "svg");
466
+ svg.setAttribute("viewBox", "0 0 24 24");
467
+ svg.setAttribute("aria-hidden", "true");
468
+ svg.setAttribute("class", cls);
469
+ for (const d of paths) {
470
+ const p = doc.createElementNS(SVG_NS, "path");
471
+ p.setAttribute("d", d);
472
+ svg.appendChild(p);
473
+ }
474
+ return svg;
475
+ }
476
+ var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
477
+ function shortDate(iso) {
478
+ const d = new Date(iso);
479
+ if (Number.isNaN(d.getTime())) return "";
480
+ try {
481
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
482
+ } catch {
483
+ return d.toISOString().slice(0, 10);
484
+ }
485
+ }
486
+ var errorText = (e, fallback) => (e instanceof Error && e.message ? e.message : fallback).replace(/^[\w.]+: /, "");
487
+ function ideaStatusPill(doc, status) {
488
+ return el(doc, "span", `g-m-pill g-m-idea-pill g-m-idea-${status}`, FEEDBACK_STATUS_LABEL[status] ?? status);
489
+ }
490
+ function voteControl(doc, idea, o) {
491
+ const count = el(doc, "span", "g-m-vote-count", String(idea.votes));
492
+ if (!o.canVote) {
493
+ const box = el(doc, "span", "g-m-vote g-m-vote-static");
494
+ box.setAttribute("aria-label", plural(idea.votes, "vote"));
495
+ box.append(svgIcon(doc, UP_ICON, "g-m-ico g-m-vote-ico"), count);
496
+ return box;
497
+ }
498
+ const b = el(doc, "button", `g-m-vote${idea.myVote ? " g-m-voted" : ""}`);
499
+ b.type = "button";
500
+ b.setAttribute("aria-pressed", String(!!idea.myVote));
501
+ b.setAttribute("aria-label", `${idea.myVote ? "Remove your vote for" : "Vote for"} ${idea.title} (${plural(idea.votes, "vote")})`);
502
+ b.disabled = !!o.busy;
503
+ b.append(svgIcon(doc, UP_ICON, "g-m-ico g-m-vote-ico"), count);
504
+ b.addEventListener("click", (e) => {
505
+ e.stopPropagation();
506
+ o.onToggle?.();
507
+ });
508
+ return b;
509
+ }
510
+ function ideaRow(doc, idea, o) {
511
+ const row = el(doc, "div", "g-m-idea");
512
+ row.setAttribute("role", "listitem");
513
+ row.appendChild(voteControl(doc, idea, { canVote: o.canVote, busy: o.busy, onToggle: o.onVote }));
514
+ const open = el(doc, "button", "g-m-idea-open");
515
+ open.type = "button";
516
+ open.appendChild(el(doc, "span", "g-m-idea-title", idea.title));
517
+ const meta = el(doc, "span", "g-m-idea-meta");
518
+ meta.appendChild(ideaStatusPill(doc, idea.status));
519
+ if (idea.comments) meta.appendChild(el(doc, "span", "g-m-time", plural(idea.comments, "comment")));
520
+ if (idea.productArea) meta.appendChild(el(doc, "span", "g-m-time", idea.productArea));
521
+ if (idea.mine) meta.appendChild(el(doc, "span", "g-m-time", "Yours"));
522
+ open.appendChild(meta);
523
+ open.addEventListener("click", o.onOpen);
524
+ row.appendChild(open);
525
+ return row;
526
+ }
527
+ var FILTERS = [
528
+ ["open", "Open"],
529
+ ["all", "All"],
530
+ ["mine", "Mine"],
531
+ ["following", "Following"]
532
+ ];
533
+ var SORTS = [
534
+ ["top", "Top"],
535
+ ["new", "New"],
536
+ ["trending", "Trending"]
537
+ ];
538
+ function filterChips(doc, current, onPick) {
539
+ const wrap = el(doc, "div", "g-m-filters");
540
+ wrap.setAttribute("role", "group");
541
+ wrap.setAttribute("aria-label", "Show");
542
+ for (const [f, label] of FILTERS) {
543
+ const b = el(doc, "button", "g-m-filter", label);
544
+ b.type = "button";
545
+ b.dataset.filter = f;
546
+ b.setAttribute("aria-pressed", String(f === current));
547
+ b.addEventListener("click", () => {
548
+ wrap.querySelectorAll("button").forEach((x) => x.setAttribute("aria-pressed", String(x === b)));
549
+ onPick(f);
550
+ });
551
+ wrap.appendChild(b);
552
+ }
553
+ return wrap;
554
+ }
555
+ function sortSelect(doc, current, onChange) {
556
+ const select = el(doc, "select", "g-input g-m-sort");
557
+ select.setAttribute("aria-label", "Sort ideas");
558
+ for (const [s, label] of SORTS) {
559
+ const opt = el(doc, "option", "", label);
560
+ opt.value = s;
561
+ select.appendChild(opt);
562
+ }
563
+ select.value = current;
564
+ select.addEventListener("change", () => onChange(select.value));
565
+ return select;
566
+ }
567
+ function priorityChoice(doc, current, onPick, label = "How important is this to you?") {
568
+ const wrap = el(doc, "div", "g-m-field");
569
+ const id = `g-m-prio${++fieldSeq2}`;
570
+ const l = el(doc, "span", "g-m-label", label);
571
+ l.id = id;
572
+ const group = el(doc, "div", "g-m-seg");
573
+ group.setAttribute("role", "radiogroup");
574
+ group.setAttribute("aria-labelledby", id);
575
+ for (const p of FEEDBACK_PRIORITIES) {
576
+ const b = el(doc, "button", "g-m-seg-btn", FEEDBACK_PRIORITY_LABEL[p]);
577
+ b.type = "button";
578
+ b.dataset.priority = p;
579
+ b.setAttribute("role", "radio");
580
+ b.setAttribute("aria-checked", String(p === current));
581
+ b.addEventListener("click", () => {
582
+ group.querySelectorAll("button").forEach((x) => x.setAttribute("aria-checked", String(x === b)));
583
+ onPick(p);
584
+ });
585
+ group.appendChild(b);
586
+ }
587
+ wrap.append(l, group);
588
+ return wrap;
589
+ }
590
+ var fieldSeq2 = 0;
591
+ function field2(doc, label, control, opts = {}) {
592
+ const id = `g-m-i${++fieldSeq2}`;
593
+ control.id = id;
594
+ const wrap = el(doc, "div", "g-m-field");
595
+ const l = el(doc, "label", "g-m-label", label);
596
+ l.htmlFor = id;
597
+ if (opts.optional) l.appendChild(el(doc, "span", "g-m-optional", " (optional)"));
598
+ if (opts.invalid) control.setAttribute("aria-invalid", "true");
599
+ wrap.append(l, control);
600
+ return wrap;
601
+ }
602
+ function errorLine(doc, text) {
603
+ const p = el(doc, "p", "g-m-error", text);
604
+ p.setAttribute("role", "alert");
605
+ return p;
606
+ }
607
+ function button(doc, cls, text, onClick) {
608
+ const b = el(doc, "button", cls, text);
609
+ b.type = "button";
610
+ b.addEventListener("click", onClick);
611
+ return b;
612
+ }
613
+ function writeNotice(doc, reason, onPortal) {
614
+ const box = el(doc, "div", "g-m-idea-notice");
615
+ box.setAttribute("role", "note");
616
+ box.appendChild(el(doc, "span", "", reason));
617
+ if (onPortal) box.appendChild(button(doc, "g-m-text-btn", "Open the feedback portal", onPortal));
618
+ return box;
619
+ }
620
+ function comment(doc, c) {
621
+ const li = el(doc, "li", `g-m-comment${c.authorType === "team" ? " g-m-comment-team" : ""}`);
622
+ const who = el(doc, "div", "g-m-row g-m-center g-m-comment-meta");
623
+ who.appendChild(el(doc, "span", "g-m-comment-author", c.authorName || (c.authorType === "team" ? "Team" : "Someone")));
624
+ if (c.authorType === "team") who.appendChild(el(doc, "span", "g-m-ai", "Team"));
625
+ who.appendChild(el(doc, "span", "g-m-time", shortDate(c.createdAt)));
626
+ const body = el(doc, "div", "g-m-comment-body");
627
+ body.appendChild(renderSafeText(doc, c.body));
628
+ li.append(who, body);
629
+ return li;
630
+ }
631
+ function ideaDetail(doc, o) {
632
+ const { idea } = o;
633
+ const wrap = el(doc, "div", "g-m-form g-m-idea-detail");
634
+ const meta = el(doc, "div", "g-m-row g-m-center g-m-idea-meta");
635
+ meta.append(el(doc, "span", "g-m-time", feedbackReference(idea.number)), ideaStatusPill(doc, idea.status));
636
+ if (idea.productArea) meta.appendChild(el(doc, "span", "g-m-time", idea.productArea));
637
+ wrap.append(meta, el(doc, "h3", "g-m-form-title", idea.title));
638
+ if (idea.description.trim()) {
639
+ const d = el(doc, "div", "g-m-idea-desc");
640
+ d.appendChild(renderSafeText(doc, idea.description));
641
+ wrap.appendChild(d);
642
+ }
643
+ if (idea.statusNote.trim()) {
644
+ const note = el(doc, "div", "g-m-idea-note");
645
+ note.appendChild(el(doc, "span", "g-m-card-label", `${FEEDBACK_STATUS_LABEL[idea.status] ?? idea.status} \xB7 from the team`));
646
+ note.appendChild(renderSafeText(doc, idea.statusNote));
647
+ wrap.appendChild(note);
648
+ }
649
+ const panel = el(doc, "div", "g-m-card g-m-static g-m-idea-vote");
650
+ const top = el(doc, "div", "g-m-row g-m-center");
651
+ top.appendChild(voteControl(doc, idea, { canVote: false }));
652
+ const said = el(doc, "span", "g-m-grow");
653
+ said.appendChild(el(doc, "span", "g-m-card-title", plural(idea.votes, "vote")));
654
+ if (idea.myVote) said.appendChild(el(doc, "span", "g-m-card-sub", `You voted \xB7 ${FEEDBACK_PRIORITY_LABEL[idea.myVote.priority]}`));
655
+ top.appendChild(said);
656
+ if (!o.blocked) {
657
+ const follow = button(doc, `g-m-follow${idea.following ? " g-m-following" : ""}`, idea.following ? "Following" : "Follow", () => o.onFollow(!idea.following));
658
+ follow.setAttribute("aria-pressed", String(idea.following));
659
+ follow.disabled = o.busy;
660
+ follow.title = idea.following ? "Stop getting updates about this idea" : "Get updates here when it changes";
661
+ top.appendChild(follow);
662
+ }
663
+ panel.appendChild(top);
664
+ if (o.error) panel.appendChild(errorLine(doc, o.error));
665
+ if (o.blocked) panel.appendChild(writeNotice(doc, o.blocked, o.onPortal));
666
+ else if (idea.myVote) {
667
+ panel.appendChild(priorityChoice(doc, idea.myVote.priority, (p) => p !== idea.myVote?.priority && o.onVote(p, ""), "How much do you need it?"));
668
+ const remove = button(doc, "g-m-text-btn g-m-unvote", "Remove my vote", o.onUnvote);
669
+ remove.disabled = o.busy;
670
+ panel.appendChild(remove);
671
+ } else {
672
+ const vote = o.vote;
673
+ panel.appendChild(priorityChoice(doc, vote.priority, (p) => vote.priority = p));
674
+ const note = el(doc, "textarea", "g-input g-m-idea-why");
675
+ note.rows = 2;
676
+ note.maxLength = MAX_FEEDBACK_NOTE;
677
+ note.value = vote.note;
678
+ note.placeholder = "What would it let you do?";
679
+ note.addEventListener("input", () => vote.note = note.value);
680
+ panel.appendChild(field2(doc, "Why it matters", note, { optional: true }));
681
+ const go = button(doc, "g-btn g-btn-primary g-m-wide g-m-vote-submit", o.busy ? "Voting\u2026" : "Vote for this idea", () => o.onVote(vote.priority, vote.note.trim()));
682
+ go.disabled = o.busy;
683
+ panel.appendChild(go);
684
+ }
685
+ wrap.appendChild(panel);
686
+ const comments = el(doc, "div", "g-m-comments");
687
+ comments.appendChild(el(doc, "h4", "g-m-comments-title", idea.thread.length ? `Comments (${idea.thread.length})` : "Comments"));
688
+ if (idea.thread.length) {
689
+ const list = el(doc, "ul", "g-m-comment-list");
690
+ idea.thread.forEach((c) => list.appendChild(comment(doc, c)));
691
+ comments.appendChild(list);
692
+ } else comments.appendChild(el(doc, "p", "g-m-hint", "No comments yet."));
693
+ if (!o.blocked) {
694
+ const form = el(doc, "form", "g-m-comment-form");
695
+ form.noValidate = true;
696
+ const box = el(doc, "textarea", "g-input");
697
+ box.rows = 2;
698
+ box.maxLength = MAX_FEEDBACK_COMMENT;
699
+ box.placeholder = "Add a comment\u2026";
700
+ box.setAttribute("aria-label", "Comment");
701
+ box.value = o.commentDraft;
702
+ const post = el(doc, "button", "g-btn g-btn-secondary", "Post");
703
+ post.type = "submit";
704
+ post.disabled = o.busy || !o.commentDraft.trim();
705
+ box.addEventListener("input", () => {
706
+ o.onCommentInput(box.value);
707
+ post.disabled = o.busy || !box.value.trim();
708
+ });
709
+ form.addEventListener("submit", (e) => {
710
+ e.preventDefault();
711
+ if (box.value.trim() && !o.busy) o.onComment();
712
+ });
713
+ form.append(box, post);
714
+ comments.appendChild(form);
715
+ }
716
+ wrap.appendChild(comments);
717
+ return wrap;
718
+ }
719
+ var emptyIdeaDraft = (over = {}) => ({ title: "", description: "", productArea: "", priority: "nice_to_have", conversationId: null, similar: null, similarFor: "", error: null, sending: false, checked: false, ...over });
720
+ function ideaProblem(draft) {
721
+ return draft.title.trim().length < 3 ? "Give your idea a short title." : null;
722
+ }
723
+ function ideaForm(doc, o) {
724
+ const { draft } = o;
725
+ const form = el(doc, "form", "g-m-form g-m-idea-form");
726
+ form.noValidate = true;
727
+ const title = el(doc, "input", "g-input");
728
+ title.type = "text";
729
+ title.maxLength = MAX_FEEDBACK_TITLE;
730
+ title.required = true;
731
+ title.value = draft.title;
732
+ title.placeholder = "Your idea in a few words";
733
+ title.addEventListener("input", () => {
734
+ draft.title = title.value;
735
+ o.onTitleInput();
736
+ });
737
+ form.appendChild(field2(doc, "Idea", title, { invalid: draft.checked && !!ideaProblem(draft) }));
738
+ const similar = el(doc, "div", "g-m-similar");
739
+ similar.setAttribute("aria-live", "polite");
740
+ form.appendChild(similar);
741
+ const desc = el(doc, "textarea", "g-input g-m-textarea");
742
+ desc.rows = 4;
743
+ desc.maxLength = MAX_FEEDBACK_DESCRIPTION;
744
+ desc.value = draft.description;
745
+ desc.placeholder = "What are you trying to do, and what gets in the way?";
746
+ desc.addEventListener("input", () => draft.description = desc.value);
747
+ form.appendChild(field2(doc, "Details", desc, { optional: true }));
748
+ if (o.productAreas.length) {
749
+ const select = el(doc, "select", "g-input");
750
+ const opt = (label, value) => {
751
+ const x = el(doc, "option", "", label);
752
+ x.value = value;
753
+ return x;
754
+ };
755
+ select.appendChild(opt("Choose one (optional)", ""));
756
+ o.productAreas.forEach((a) => select.appendChild(opt(a, a)));
757
+ select.value = o.productAreas.includes(draft.productArea) ? draft.productArea : "";
758
+ select.addEventListener("change", () => draft.productArea = select.value);
759
+ form.appendChild(field2(doc, "Product area", select, { optional: true }));
760
+ }
761
+ form.appendChild(priorityChoice(doc, draft.priority, (p) => draft.priority = p));
762
+ const foot = el(doc, "div", "g-m-form-foot");
763
+ if (draft.error) foot.appendChild(errorLine(doc, draft.error));
764
+ const submit = el(doc, "button", "g-btn g-btn-primary g-m-wide", draft.sending ? "Sending\u2026" : "Submit idea");
765
+ submit.type = "submit";
766
+ submit.disabled = draft.sending;
767
+ foot.appendChild(submit);
768
+ form.appendChild(foot);
769
+ form.addEventListener("submit", (e) => {
770
+ e.preventDefault();
771
+ if (!draft.sending) o.onSubmit();
772
+ });
773
+ return { form, similar };
774
+ }
775
+ function similarIdeas(doc, ideas, row) {
776
+ if (!ideas.length) return null;
777
+ const box = el(doc, "div", "g-m-similar-box");
778
+ box.appendChild(el(doc, "div", "g-m-card-label", "Similar ideas \u2014 vote instead?"));
779
+ const list = el(doc, "div", "g-m-idea-list");
780
+ list.setAttribute("role", "list");
781
+ ideas.slice(0, 5).forEach((i) => list.appendChild(row(i)));
782
+ box.appendChild(list);
783
+ return box;
784
+ }
785
+ function ideaSent(doc, o) {
786
+ const wrap = el(doc, "div", "g-m-form g-m-sent");
787
+ const top = el(doc, "div", "g-m-sent-top");
788
+ const tick = el(doc, "span", "g-m-sent-tick", "\u2713");
789
+ tick.setAttribute("aria-hidden", "true");
790
+ const p = el(doc, "p", "g-m-card-sub");
791
+ p.append(el(doc, "strong", "", `${feedbackReference(o.idea.number)} \xB7 ${o.idea.title}`), o.idea.following ? ". You're following it: updates show up here when its status changes." : ". Others can now vote for it.");
792
+ top.append(tick, el(doc, "h3", "g-m-form-title", "Thanks for your idea"), p);
793
+ wrap.append(top, button(doc, "g-btn g-btn-primary g-m-wide", "View your idea", o.onView), button(doc, "g-btn g-btn-secondary g-m-wide", "Back to ideas", o.onBack));
794
+ return wrap;
795
+ }
796
+ function updatesList(doc, updates, onOpen) {
797
+ const wrap = el(doc, "div", "g-m-idea-updates");
798
+ if (!updates.length) {
799
+ wrap.appendChild(el(doc, "div", "g-m-empty", "No updates yet. Follow or vote for an idea to hear when it changes."));
800
+ return wrap;
801
+ }
802
+ for (const u of updates) {
803
+ const card = el(doc, "button", "g-m-card g-m-update");
804
+ card.type = "button";
805
+ card.disabled = !u.requestId;
806
+ const row = el(doc, "span", "g-m-row");
807
+ if (!u.read) row.appendChild(el(doc, "span", "g-m-dot"));
808
+ const text = el(doc, "span", "g-m-grow");
809
+ text.appendChild(el(doc, "span", "g-m-card-title", u.title));
810
+ if (u.body) text.appendChild(el(doc, "span", "g-m-card-sub g-m-clamp", u.body));
811
+ row.append(text, el(doc, "span", "g-m-time", shortDate(u.createdAt)));
812
+ card.appendChild(row);
813
+ card.setAttribute("aria-label", `${u.title}${u.read ? "" : ", new"}`);
814
+ card.addEventListener("click", () => onOpen(u));
815
+ wrap.appendChild(card);
816
+ }
817
+ return wrap;
818
+ }
819
+ var LIST_TTL_MS = 15e3;
820
+ var asDetail = (d) => ({ ...d, title: d.title ?? "", description: d.description ?? "", statusNote: d.statusNote ?? "", votes: d.votes ?? 0, thread: Array.isArray(d.thread) ? d.thread : [] });
821
+ function createIdeas(o) {
822
+ const { doc, api } = o;
823
+ let query = { sort: "top", filter: "open", page: 1 };
824
+ let list = null;
825
+ let hasMore = false;
826
+ let listAt = 0;
827
+ let listLoading = false;
828
+ let listError = null;
829
+ let listSeq = 0;
830
+ let listActionError = null;
831
+ const details = /* @__PURE__ */ new Map();
832
+ const detailErrors = /* @__PURE__ */ new Map();
833
+ const actionErrors = /* @__PURE__ */ new Map();
834
+ const voteDrafts = /* @__PURE__ */ new Map();
835
+ const commentDrafts = /* @__PURE__ */ new Map();
836
+ const busy = /* @__PURE__ */ new Set();
837
+ const offers = /* @__PURE__ */ new Map();
838
+ let updates = null;
839
+ let updatesAt = 0;
840
+ let updatesLoading = false;
841
+ let updatesSeq = 0;
842
+ let draft = emptyIdeaDraft();
843
+ let similarTimer = null;
844
+ let similarSeq = 0;
845
+ let searchTimer = null;
846
+ let who = o.identified();
847
+ let mounted = null;
848
+ const mount = (root, paint) => {
849
+ mounted = { root, paint };
850
+ paint();
851
+ };
852
+ const repaint = () => {
853
+ if (mounted?.root.isConnected) mounted.paint();
854
+ };
855
+ const showing = (root) => mounted?.root === root && root.isConnected;
856
+ const cfg = () => o.config() ?? { title: "Ideas", anonymous: false, readOnly: true, productAreas: [], portal: false };
857
+ function blocked() {
858
+ const c = cfg();
859
+ if (c.readOnly || !api.voteIdea) return c.portal ? "Voting and new ideas happen in our feedback portal." : "You can browse ideas here. Voting isn't open in the app.";
860
+ if (!c.anonymous && !o.identified()) return "Log in to vote, comment or share an idea.";
861
+ return null;
862
+ }
863
+ const canPortal = () => cfg().portal && !!api.ideaPortalLink;
864
+ function openPortal() {
865
+ if (!api.ideaPortalLink) return;
866
+ const win = doc.defaultView;
867
+ let tab = null;
868
+ try {
869
+ tab = win?.open("about:blank", "_blank") ?? null;
870
+ if (tab) tab.opener = null;
871
+ } catch {
872
+ tab = null;
873
+ }
874
+ api.ideaPortalLink().then((raw) => {
875
+ const url = safeHttpUrl(raw);
876
+ if (!url) throw new Error("The portal link isn't available.");
877
+ if (tab) tab.location.href = url;
878
+ else win?.open(url, "_blank", "noopener");
879
+ }).catch((e) => {
880
+ tab?.close();
881
+ listActionError = errorText(e, "The feedback portal couldn't be opened.");
882
+ repaint();
883
+ });
884
+ }
885
+ const portal = () => canPortal() ? openPortal : void 0;
886
+ function patch(idea) {
887
+ if (list) list = list.map((x) => x.id === idea.id ? { ...x, ...idea } : x);
888
+ const d = details.get(idea.id);
889
+ if (d) details.set(idea.id, { ...d, ...idea, thread: "thread" in idea ? idea.thread : d.thread });
890
+ if (draft.similar) draft.similar = draft.similar.map((x) => x.id === idea.id ? { ...x, ...idea } : x);
891
+ }
892
+ const find = (id) => details.get(id) ?? list?.find((x) => x.id === id) ?? draft.similar?.find((x) => x.id === id);
893
+ function toggleVote(idea) {
894
+ if (busy.has(idea.id) || blocked() || !api.voteIdea || idea.myVote && !api.unvoteIdea) return;
895
+ busy.add(idea.id);
896
+ listActionError = null;
897
+ const voted = !!idea.myVote;
898
+ patch({ ...idea, votes: Math.max(0, idea.votes + (voted ? -1 : 1)), myVote: voted ? null : { priority: "nice_to_have" } });
899
+ repaint();
900
+ (voted ? api.unvoteIdea(idea.id) : api.voteIdea(idea.id, { priority: "nice_to_have" })).then(patch).catch((e) => {
901
+ patch(idea);
902
+ listActionError = errorText(e, "Your vote couldn't be saved.");
903
+ }).finally(() => {
904
+ busy.delete(idea.id);
905
+ repaint();
906
+ });
907
+ }
908
+ function loadList(append = false) {
909
+ if (!api.listIdeas) return;
910
+ const seq = ++listSeq;
911
+ listLoading = true;
912
+ listError = null;
913
+ const q = { ...query };
914
+ api.listIdeas(q).then((res) => {
915
+ if (seq !== listSeq) return;
916
+ const got = Array.isArray(res?.requests) ? res.requests : [];
917
+ list = append && list ? [...list, ...got.filter((r) => !list.some((x) => x.id === r.id))] : got;
918
+ hasMore = !!res?.hasMore;
919
+ listAt = Date.now();
920
+ }).catch((e) => {
921
+ if (seq === listSeq) listError = errorText(e, "Ideas couldn't be loaded.");
922
+ }).finally(() => {
923
+ if (seq !== listSeq) return;
924
+ listLoading = false;
925
+ repaint();
926
+ });
927
+ }
928
+ const requery = (change) => {
929
+ query = { ...query, ...change, page: 1 };
930
+ loadList();
931
+ repaint();
932
+ };
933
+ function listScreen() {
934
+ const root = el(doc, "div", "g-m-scroll g-m-ideas");
935
+ const top = el(doc, "div", "g-m-ideas-top");
936
+ const why = blocked();
937
+ if (why) top.appendChild(writeNotice(doc, why, portal()));
938
+ else top.appendChild(button(doc, "g-btn g-btn-primary g-m-wide", "Suggest an idea", o.openForm));
939
+ const updatesLink = el(doc, "div");
940
+ top.appendChild(updatesLink);
941
+ const search = el(doc, "input", "g-input g-m-idea-search");
942
+ search.type = "search";
943
+ search.placeholder = "Search ideas";
944
+ search.setAttribute("aria-label", "Search ideas");
945
+ search.maxLength = 200;
946
+ search.value = query.q ?? "";
947
+ search.addEventListener("input", () => {
948
+ if (searchTimer) clearTimeout(searchTimer);
949
+ searchTimer = setTimeout(() => requery({ q: search.value.trim() || void 0 }), 300);
950
+ });
951
+ const tools = el(doc, "div", "g-m-row g-m-center g-m-idea-tools");
952
+ tools.append(filterChips(doc, query.filter, (filter) => requery({ filter })), sortSelect(doc, query.sort, (sort) => requery({ sort })));
953
+ top.append(search, tools);
954
+ const results = el(doc, "div", "g-m-idea-results");
955
+ root.append(top, results);
956
+ mount(root, () => {
957
+ updatesLink.textContent = "";
958
+ const n = updates?.unread ?? 0;
959
+ if (updates?.updates.length) updatesLink.appendChild(button(doc, "g-m-text-btn g-m-updates-link", n ? `Updates on your ideas (${n} new)` : "Updates on your ideas", o.openUpdates));
960
+ results.textContent = "";
961
+ if (listActionError) results.appendChild(errorLine(doc, listActionError));
962
+ if (!list) {
963
+ if (listError) {
964
+ results.append(errorLine(doc, listError), button(doc, "g-m-text-btn", "Try again", () => loadList()));
965
+ } else results.appendChild(el(doc, "div", "g-m-empty", "Loading ideas\u2026"));
966
+ return;
967
+ }
968
+ if (!list.length) {
969
+ const empty = query.q ? `No ideas match \u201C${query.q}\u201D.` : query.filter === "mine" ? "You haven't suggested any ideas yet." : query.filter === "following" ? "You aren't following any ideas yet." : "No ideas yet. Be the first to suggest one.";
970
+ results.appendChild(el(doc, "div", "g-m-empty", empty));
971
+ } else {
972
+ const rows = el(doc, "div", "g-m-idea-list");
973
+ rows.setAttribute("role", "list");
974
+ rows.setAttribute("aria-busy", String(listLoading));
975
+ const canVote = !blocked();
976
+ list.forEach((idea) => rows.appendChild(ideaRow(doc, idea, { canVote, busy: busy.has(idea.id), onVote: () => toggleVote(idea), onOpen: () => o.openIdea(idea.id) })));
977
+ results.appendChild(rows);
978
+ }
979
+ if (listError) results.appendChild(errorLine(doc, listError));
980
+ if (hasMore) {
981
+ const more = button(doc, "g-btn g-btn-secondary g-m-wide", listLoading ? "Loading\u2026" : "Show more", () => {
982
+ query = { ...query, page: query.page + 1 };
983
+ loadList(true);
984
+ repaint();
985
+ });
986
+ more.disabled = listLoading;
987
+ results.appendChild(more);
988
+ }
989
+ if (!why && canPortal()) results.appendChild(button(doc, "g-m-text-btn g-m-portal-link", "Open the feedback portal", openPortal));
990
+ });
991
+ if (!list || Date.now() - listAt > LIST_TTL_MS || query.page > 1) {
992
+ query = { ...query, page: 1 };
993
+ loadList();
994
+ }
995
+ return root;
996
+ }
997
+ function act(id, call, fallback, done) {
998
+ if (busy.has(id)) return;
999
+ const p = call();
1000
+ if (!p) return;
1001
+ busy.add(id);
1002
+ actionErrors.delete(id);
1003
+ repaint();
1004
+ p.then((res) => {
1005
+ patch(res);
1006
+ done?.();
1007
+ }).catch((e) => actionErrors.set(id, errorText(e, fallback))).finally(() => {
1008
+ busy.delete(id);
1009
+ repaint();
1010
+ });
1011
+ }
1012
+ function detailScreen(id) {
1013
+ const root = el(doc, "div", "g-m-scroll");
1014
+ mount(root, () => {
1015
+ root.textContent = "";
1016
+ const idea = details.get(id);
1017
+ if (!idea) {
1018
+ const err = detailErrors.get(id);
1019
+ root.appendChild(err ? errorLine(doc, err) : el(doc, "div", "g-m-empty", "Loading\u2026"));
1020
+ return;
1021
+ }
1022
+ if (!voteDrafts.has(id)) voteDrafts.set(id, { priority: "nice_to_have", note: "" });
1023
+ root.appendChild(
1024
+ ideaDetail(doc, {
1025
+ idea,
1026
+ blocked: blocked(),
1027
+ onPortal: portal(),
1028
+ busy: busy.has(id),
1029
+ error: actionErrors.get(id) ?? null,
1030
+ vote: voteDrafts.get(id),
1031
+ commentDraft: commentDrafts.get(id) ?? "",
1032
+ onVote: (priority, note) => act(id, () => api.voteIdea?.(id, { priority, ...note ? { note } : {} }), "Your vote couldn't be saved.", () => voteDrafts.delete(id)),
1033
+ onUnvote: () => act(id, () => api.unvoteIdea?.(id), "Your vote couldn't be removed."),
1034
+ onFollow: (follow) => act(id, () => api.followIdea?.(id, follow), "That didn't work. Please try again."),
1035
+ onCommentInput: (text) => commentDrafts.set(id, text),
1036
+ onComment: () => act(id, () => api.commentIdea?.(id, (commentDrafts.get(id) ?? "").trim()), "Your comment couldn't be posted.", () => commentDrafts.delete(id))
1037
+ })
1038
+ );
1039
+ });
1040
+ if (api.getIdea) {
1041
+ detailErrors.delete(id);
1042
+ api.getIdea(id).then((d) => {
1043
+ details.set(id, asDetail(d));
1044
+ patch(d);
1045
+ }).catch((e) => {
1046
+ if (!details.has(id)) detailErrors.set(id, errorText(e, "This idea couldn't be loaded."));
1047
+ }).finally(() => {
1048
+ if (showing(root)) mounted.paint();
1049
+ });
1050
+ }
1051
+ return root;
1052
+ }
1053
+ function lookUpSimilar(paint) {
1054
+ const title = draft.title.trim();
1055
+ if (title.length < 3 || !api.similarIdeas) {
1056
+ draft.similar = null;
1057
+ draft.similarFor = "";
1058
+ return paint();
1059
+ }
1060
+ if (title === draft.similarFor) return;
1061
+ const seq = ++similarSeq;
1062
+ api.similarIdeas(title).then((res) => {
1063
+ if (seq !== similarSeq) return;
1064
+ draft.similar = res;
1065
+ draft.similarFor = title;
1066
+ paint();
1067
+ }).catch(() => {
1068
+ });
1069
+ }
1070
+ function formScreen() {
1071
+ const root = el(doc, "div", "g-m-scroll");
1072
+ let similarBox = null;
1073
+ const paintSimilar = () => {
1074
+ if (!similarBox) return;
1075
+ similarBox.textContent = "";
1076
+ const canVote = !blocked();
1077
+ const box = similarIdeas(doc, draft.similar ?? [], (idea) => ideaRow(doc, idea, { canVote, busy: busy.has(idea.id), onVote: () => toggleVote(idea), onOpen: () => o.openIdea(idea.id) }));
1078
+ if (box) similarBox.appendChild(box);
1079
+ if (listActionError) similarBox.appendChild(errorLine(doc, listActionError));
1080
+ };
1081
+ const paintAll = () => {
1082
+ root.textContent = "";
1083
+ const why = blocked();
1084
+ if (why) {
1085
+ root.appendChild(writeNotice(doc, why, portal()));
1086
+ similarBox = null;
1087
+ return;
1088
+ }
1089
+ const { form, similar } = ideaForm(doc, {
1090
+ draft,
1091
+ productAreas: cfg().productAreas ?? [],
1092
+ onTitleInput: () => {
1093
+ if (similarTimer) clearTimeout(similarTimer);
1094
+ similarTimer = setTimeout(() => {
1095
+ if (showing(root)) lookUpSimilar(paintSimilar);
1096
+ }, 300);
1097
+ },
1098
+ onSubmit: () => submit(root, paintAll)
1099
+ });
1100
+ root.appendChild(form);
1101
+ similarBox = similar;
1102
+ paintSimilar();
1103
+ };
1104
+ mounted = { root, paint: paintSimilar };
1105
+ paintAll();
1106
+ if (draft.title.trim() && draft.title.trim() !== draft.similarFor) lookUpSimilar(paintSimilar);
1107
+ return root;
1108
+ }
1109
+ function submit(root, paintAll) {
1110
+ const d = draft;
1111
+ d.checked = true;
1112
+ d.error = ideaProblem(d);
1113
+ if (d.error || !api.submitIdea) return paintAll();
1114
+ d.sending = true;
1115
+ paintAll();
1116
+ api.submitIdea({ title: d.title.trim(), description: d.description.trim(), productArea: d.productArea || null, priority: d.priority, conversationId: d.conversationId }).then((idea) => {
1117
+ details.set(idea.id, asDetail(idea));
1118
+ listAt = 0;
1119
+ if (draft === d) draft = emptyIdeaDraft();
1120
+ if (showing(root)) o.openSent(idea);
1121
+ }).catch((e) => {
1122
+ d.sending = false;
1123
+ d.error = errorText(e, "Your idea couldn't be sent.");
1124
+ if (showing(root)) paintAll();
1125
+ });
1126
+ }
1127
+ function updatesScreen() {
1128
+ const root = el(doc, "div", "g-m-scroll g-m-idea-updates-screen");
1129
+ mount(root, () => {
1130
+ root.textContent = "";
1131
+ if (!updates) {
1132
+ root.appendChild(el(doc, "div", "g-m-empty", updatesLoading || api.ideaUpdates ? "Loading\u2026" : "No updates yet."));
1133
+ return;
1134
+ }
1135
+ root.appendChild(updatesList(doc, updates.updates, (u) => u.requestId && o.openIdea(u.requestId)));
1136
+ });
1137
+ if (!updates) loadUpdates();
1138
+ else markRead();
1139
+ return root;
1140
+ }
1141
+ function markRead() {
1142
+ const ids = updates?.updates.filter((u) => !u.read).map((u) => u.id) ?? [];
1143
+ if (!ids.length || !api.markIdeaUpdatesRead) return;
1144
+ const seq = updatesSeq;
1145
+ api.markIdeaUpdatesRead(ids).then((res) => {
1146
+ if (seq !== updatesSeq || !updates) return;
1147
+ updates = { updates: updates.updates, unread: res.unread };
1148
+ o.onUnreadChange();
1149
+ }).catch(() => {
1150
+ });
1151
+ }
1152
+ function loadUpdates(maxAgeMs = 0) {
1153
+ if (!api.ideaUpdates || updatesLoading || Date.now() - updatesAt < maxAgeMs) return;
1154
+ updatesLoading = true;
1155
+ const seq = ++updatesSeq;
1156
+ api.ideaUpdates().then((res) => {
1157
+ if (seq !== updatesSeq) return;
1158
+ const before = updates?.unread ?? 0;
1159
+ updates = { updates: Array.isArray(res?.updates) ? res.updates : [], unread: Number(res?.unread) || 0 };
1160
+ updatesAt = Date.now();
1161
+ if (updates.unread !== before) o.onUnreadChange();
1162
+ if (mounted?.root.isConnected) {
1163
+ mounted.paint();
1164
+ if (mounted.root.classList.contains("g-m-idea-updates-screen")) markRead();
1165
+ }
1166
+ }).catch(() => {
1167
+ }).finally(() => {
1168
+ if (seq === updatesSeq) updatesLoading = false;
1169
+ });
1170
+ }
1171
+ function offerChip(offer, conversationId, lead) {
1172
+ const canWrite = !blocked();
1173
+ const b = el(doc, "button", "g-m-chip g-m-guide g-m-offer g-m-idea-offer");
1174
+ b.type = "button";
1175
+ const label = el(doc, "span");
1176
+ b.append(lead(), label);
1177
+ const id = offer.requestId;
1178
+ if (!id) {
1179
+ if (!canWrite || !api.submitIdea) return null;
1180
+ label.textContent = "Suggest this idea";
1181
+ b.addEventListener("click", () => {
1182
+ startDraft({ title: offer.title, description: offer.description, conversationId });
1183
+ o.openForm();
1184
+ });
1185
+ return b;
1186
+ }
1187
+ const paint = () => {
1188
+ const state = offers.get(id) ?? (find(id)?.myVote ? "voted" : void 0);
1189
+ b.disabled = state === "voting";
1190
+ label.textContent = !canWrite ? `View \u201C${offer.title}\u201D` : state === "voting" ? "Voting\u2026" : state === "voted" ? "Voted \xB7 View the idea" : state === "failed" ? "Couldn't vote \xB7 View the idea" : `Vote for \u201C${offer.title}\u201D`;
1191
+ return state;
1192
+ };
1193
+ paint();
1194
+ b.addEventListener("click", () => {
1195
+ const state = paint();
1196
+ if (!canWrite || state === "voted" || state === "failed" || !api.voteIdea) return o.openIdea(id);
1197
+ if (state === "voting") return;
1198
+ offers.set(id, "voting");
1199
+ paint();
1200
+ api.voteIdea(id, { priority: "nice_to_have", conversationId }).then((idea) => {
1201
+ patch(idea);
1202
+ offers.set(id, "voted");
1203
+ }).catch(() => offers.set(id, "failed")).finally(paint);
1204
+ });
1205
+ return b;
1206
+ }
1207
+ function startDraft(prefill = {}) {
1208
+ draft = emptyIdeaDraft({ title: (prefill.title ?? "").slice(0, MAX_FEEDBACK_TITLE), description: (prefill.description ?? "").slice(0, MAX_FEEDBACK_DESCRIPTION), conversationId: prefill.conversationId ?? null });
1209
+ }
1210
+ return {
1211
+ blocked,
1212
+ unread: () => updates?.unread ?? 0,
1213
+ listScreen,
1214
+ detailScreen,
1215
+ formScreen,
1216
+ sentScreen: (idea) => {
1217
+ mounted = null;
1218
+ const root = el(doc, "div", "g-m-scroll");
1219
+ root.appendChild(ideaSent(doc, { idea, onView: () => o.openIdea(idea.id), onBack: o.openList }));
1220
+ return root;
1221
+ },
1222
+ updatesScreen,
1223
+ startDraft,
1224
+ offerChip,
1225
+ loadUpdates,
1226
+ sync(open) {
1227
+ const now = o.identified();
1228
+ if (now !== who) {
1229
+ who = now;
1230
+ list = null;
1231
+ listAt = 0;
1232
+ details.clear();
1233
+ offers.clear();
1234
+ updates = null;
1235
+ updatesAt = 0;
1236
+ updatesSeq++;
1237
+ updatesLoading = false;
1238
+ o.onUnreadChange();
1239
+ }
1240
+ if (open) loadUpdates(3e4);
1241
+ },
1242
+ destroy() {
1243
+ if (similarTimer) clearTimeout(similarTimer);
1244
+ if (searchTimer) clearTimeout(searchTimer);
1245
+ mounted = null;
1246
+ }
1247
+ };
1248
+ }
1249
+
382
1250
  // src/messenger-styles.ts
383
1251
  var MESSENGER_STYLES = `
384
1252
  :host([data-g-messenger="bottom-right"]) .g-launcher.g-pos-bottom-right{right:72px}
@@ -582,90 +1450,81 @@ var MESSENGER_STYLES = `
582
1450
  .g-m-step-dot{width:14px;height:14px;border-radius:999px;border:2px solid var(--g-border);background:var(--g-bg);box-sizing:border-box}
583
1451
  .g-m-step-done{color:var(--g-text);font-weight:600}
584
1452
  .g-m-step-done .g-m-step-dot{background:var(--g-primary);border-color:var(--g-primary)}
1453
+ .g-m-ideas-top{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}
1454
+ .g-m-ideas-top:empty{display:none}
1455
+ .g-m-idea-tools{justify-content:space-between;gap:8px;flex-wrap:wrap}
1456
+ .g-m-filters{display:flex;gap:6px;flex-wrap:wrap}
1457
+ .g-m-filter{border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);font:inherit;font-size:12px;font-weight:600;border-radius:999px;padding:5px 11px;cursor:pointer;min-height:30px}
1458
+ .g-m-filter:hover{background:var(--g-subtle)}
1459
+ .g-m-filter[aria-pressed="true"]{background:var(--g-primary);border-color:var(--g-primary);color:var(--g-primary-text)}
1460
+ .g-m-filter:focus-visible,.g-m-seg-btn:focus-visible,.g-m-vote:focus-visible,.g-m-idea-open:focus-visible,.g-m-follow:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
1461
+ .g-m-sort{width:auto;min-width:0;min-height:30px;padding:4px 8px;font-size:12px}
1462
+ .g-m-idea-list{display:flex;flex-direction:column;border:1px solid var(--g-border);border-radius:calc(var(--g-radius) * .8);background:var(--g-bg);overflow:hidden;margin-bottom:12px}
1463
+ .g-m-idea{display:flex;align-items:flex-start;gap:12px;padding:12px 14px;border-top:1px solid var(--g-border)}
1464
+ .g-m-idea:first-child{border-top:0}
1465
+ .g-m-vote{flex:none;width:44px;min-height:48px;border:1px solid var(--g-border);border-radius:10px;background:var(--g-bg);color:var(--g-text);font:inherit;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:0;padding:4px 0;cursor:pointer}
1466
+ button.g-m-vote:hover{border-color:var(--g-primary);color:var(--g-primary)}
1467
+ .g-m-vote:disabled{opacity:.6;cursor:progress}
1468
+ .g-m-vote.g-m-voted{background:color-mix(in srgb,var(--g-primary) 12%,var(--g-bg));border-color:var(--g-primary);color:var(--g-primary)}
1469
+ .g-m-vote-static{cursor:default;background:var(--g-subtle)}
1470
+ .g-m-vote-ico{width:16px;height:16px;stroke-width:2.4}
1471
+ .g-m-vote-count{font-size:13px;font-weight:700;line-height:1.2}
1472
+ .g-m-idea-open{flex:1;min-width:0;border:0;background:transparent;color:var(--g-text);font:inherit;text-align:left;padding:2px 0;cursor:pointer;display:flex;flex-direction:column;gap:6px;border-radius:4px}
1473
+ .g-m-idea-open:hover .g-m-idea-title{color:var(--g-primary)}
1474
+ .g-m-idea-title{font-weight:600;overflow-wrap:anywhere}
1475
+ .g-m-idea-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
1476
+ .g-m-idea-pill.g-m-idea-new{background:var(--g-subtle);color:var(--g-muted)}
1477
+ .g-m-idea-pill.g-m-idea-under_review{background:#e0e7ff;color:#3730a3}
1478
+ .g-m-idea-pill.g-m-idea-planned{background:#dbeafe;color:#1e40af}
1479
+ .g-m-idea-pill.g-m-idea-in_progress{background:#fef3c7;color:#92400e}
1480
+ .g-m-idea-pill.g-m-idea-released{background:#dcfce7;color:#166534}
1481
+ .g-m-idea-pill.g-m-idea-declined{background:#fee2e2;color:#991b1b}
1482
+ .g-m-idea-notice{display:flex;flex-direction:column;align-items:flex-start;gap:2px;border:1px solid var(--g-border);background:var(--g-subtle);border-radius:10px;padding:10px 12px;font-size:13px;color:var(--g-text)}
1483
+ .g-m-idea-notice .g-m-text-btn{padding:4px 0;min-height:0}
1484
+ .g-m-updates-link,.g-m-portal-link{align-self:flex-start;padding-left:0}
1485
+ .g-m-portal-link{display:block;margin:0 auto}
1486
+ .g-m-idea-detail{gap:12px}
1487
+ .g-m-idea-desc,.g-m-idea-note{font-size:14px;overflow-wrap:anywhere}
1488
+ .g-m-idea-desc p,.g-m-idea-note p,.g-m-comment-body p{margin:0 0 8px}
1489
+ .g-m-idea-desc p:last-child,.g-m-idea-note p:last-child,.g-m-comment-body p:last-child{margin:0}
1490
+ .g-m-idea-note{border-left:3px solid var(--g-primary);background:var(--g-subtle);border-radius:6px;padding:8px 10px}
1491
+ .g-m-idea-vote{display:flex;flex-direction:column;gap:12px;margin:0}
1492
+ .g-m-idea-vote .g-m-vote{min-height:44px}
1493
+ .g-m-follow{flex:none;border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);font:inherit;font-size:13px;font-weight:600;border-radius:999px;padding:6px 12px;cursor:pointer;min-height:32px}
1494
+ .g-m-follow:hover{background:var(--g-subtle)}
1495
+ .g-m-follow.g-m-following{border-color:var(--g-primary);color:var(--g-primary)}
1496
+ .g-m-follow:disabled{opacity:.6}
1497
+ .g-m-seg{display:flex;border:1px solid var(--g-border);border-radius:10px;overflow:hidden}
1498
+ .g-m-seg-btn{flex:1;border:0;border-left:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);font:inherit;font-size:12px;font-weight:600;padding:8px 4px;cursor:pointer;min-height:36px}
1499
+ .g-m-seg-btn:first-child{border-left:0}
1500
+ .g-m-seg-btn:hover{background:var(--g-subtle)}
1501
+ .g-m-seg-btn[aria-checked="true"]{background:var(--g-primary);color:var(--g-primary-text)}
1502
+ .g-m-unvote{align-self:flex-start;padding-left:0;color:var(--g-muted)}
1503
+ .g-m-comments{display:flex;flex-direction:column;gap:8px}
1504
+ .g-m-comments-title{margin:4px 0 0;font-size:14px}
1505
+ .g-m-comment-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
1506
+ .g-m-comment{border:1px solid var(--g-border);border-radius:10px;padding:8px 10px;font-size:13px}
1507
+ .g-m-comment-team{background:var(--g-subtle);border-color:color-mix(in srgb,var(--g-primary) 35%,var(--g-border))}
1508
+ .g-m-comment-meta{gap:6px;margin-bottom:4px}
1509
+ .g-m-comment-author{font-weight:600}
1510
+ .g-m-comment-body{overflow-wrap:anywhere}
1511
+ .g-m-comment-form{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
1512
+ .g-m-comment-form textarea{resize:vertical;min-height:56px}
1513
+ .g-m-similar:empty{display:none}
1514
+ .g-m-similar-box{display:flex;flex-direction:column;gap:6px}
1515
+ .g-m-similar-box .g-m-idea-list{margin:0}
1516
+ .g-m-update{display:block}
1517
+ .g-m-update:disabled{cursor:default}
1518
+ .g-m-update:disabled:hover{background:var(--g-bg)}
585
1519
  `;
586
1520
 
587
- // src/render-safe.ts
588
- var CITATION = /\s*\[\[article:[^\]]*\]\]/g;
589
- var INLINE = /\*\*([^*\n]+)\*\*|`([^`\n]+)`|(!?)\[([^\]\n]{1,200})\]\((https?:\/\/[^\s)]+)\)|(https?:\/\/[^\s<>()"']+[^\s<>().,;:!?'"])/g;
590
- function stripCitationMarkers(text) {
591
- return text.replace(CITATION, "");
592
- }
593
- function safeHttpUrl(raw) {
594
- try {
595
- const u = new URL(raw);
596
- return u.protocol === "http:" || u.protocol === "https:" ? u.href : null;
597
- } catch {
598
- return null;
599
- }
600
- }
601
- function link(doc, label, href) {
602
- const url = safeHttpUrl(href);
603
- if (!url) return doc.createTextNode(label);
604
- const a = doc.createElement("a");
605
- a.href = url;
606
- a.target = "_blank";
607
- a.rel = "noopener noreferrer nofollow";
608
- a.textContent = label;
609
- return a;
610
- }
611
- function appendInline(doc, parent, text) {
612
- let last = 0;
613
- for (const m of text.matchAll(INLINE)) {
614
- const start = m.index ?? 0;
615
- if (start > last) parent.appendChild(doc.createTextNode(text.slice(last, start)));
616
- if (m[1] !== void 0) {
617
- const b = doc.createElement("strong");
618
- b.textContent = m[1];
619
- parent.appendChild(b);
620
- } else if (m[2] !== void 0) {
621
- const c = doc.createElement("code");
622
- c.textContent = m[2];
623
- parent.appendChild(c);
624
- } else if (m[4] !== void 0 && m[5] !== void 0) {
625
- parent.appendChild(m[3] ? doc.createTextNode(m[4]) : link(doc, m[4], m[5]));
626
- } else if (m[6] !== void 0) {
627
- parent.appendChild(link(doc, m[6], m[6]));
628
- }
629
- last = start + m[0].length;
630
- }
631
- if (last < text.length) parent.appendChild(doc.createTextNode(text.slice(last)));
632
- }
633
- var BULLET = /^\s*[-*•]\s+/;
634
- var NUMBERED = /^\s*\d{1,3}[.)]\s+/;
635
- function renderSafeText(doc, text) {
636
- const frag = doc.createDocumentFragment();
637
- const blocks = stripCitationMarkers(text).replace(/\r\n?/g, "\n").trim().split(/\n{2,}/);
638
- for (const block of blocks) {
639
- if (!block.trim()) continue;
640
- const lines = block.split("\n");
641
- if (lines.every((l) => BULLET.test(l)) || lines.every((l) => NUMBERED.test(l))) {
642
- const ordered = NUMBERED.test(lines[0]);
643
- const list = doc.createElement(ordered ? "ol" : "ul");
644
- for (const line of lines) {
645
- const li = doc.createElement("li");
646
- appendInline(doc, li, line.replace(ordered ? NUMBERED : BULLET, ""));
647
- list.appendChild(li);
648
- }
649
- frag.appendChild(list);
650
- continue;
651
- }
652
- const p = doc.createElement("p");
653
- lines.forEach((line, i) => {
654
- if (i) p.appendChild(doc.createElement("br"));
655
- appendInline(doc, p, line);
656
- });
657
- frag.appendChild(p);
658
- }
659
- return frag;
660
- }
661
-
662
1521
  // src/messenger.ts
663
- var SVG_NS = "http://www.w3.org/2000/svg";
1522
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
664
1523
  function chatIcon(doc) {
665
- const svg = doc.createElementNS(SVG_NS, "svg");
1524
+ const svg = doc.createElementNS(SVG_NS2, "svg");
666
1525
  svg.setAttribute("viewBox", "0 0 24 24");
667
1526
  svg.setAttribute("aria-hidden", "true");
668
- const path = doc.createElementNS(SVG_NS, "path");
1527
+ const path = doc.createElementNS(SVG_NS2, "path");
669
1528
  path.setAttribute("d", "M12 3C6.5 3 2 6.8 2 11.5c0 2.4 1.2 4.6 3.1 6.1L4.5 21l3.9-2c1.1.3 2.3.5 3.6.5 5.5 0 10-3.8 10-8.5S17.5 3 12 3z");
670
1529
  svg.appendChild(path);
671
1530
  return svg;
@@ -690,15 +1549,16 @@ var ICONS = {
690
1549
  circle: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z"],
691
1550
  expand: ["M15 3h6v6", "M21 3l-7 7", "M9 21H3v-6", "M3 21l7-7"],
692
1551
  flag: ["M5 21V4", "M5 4h12l-2.5 4L17 12H5"],
693
- collapse: ["M4 14h6v6", "M3 21l7-7", "M20 10h-6V4", "M21 3l-7 7"]
1552
+ collapse: ["M4 14h6v6", "M3 21l7-7", "M20 10h-6V4", "M21 3l-7 7"],
1553
+ idea: IDEA_ICON
694
1554
  };
695
1555
  function icon(doc, name, cls = "g-m-ico") {
696
- const svg = doc.createElementNS(SVG_NS, "svg");
1556
+ const svg = doc.createElementNS(SVG_NS2, "svg");
697
1557
  svg.setAttribute("viewBox", "0 0 24 24");
698
1558
  svg.setAttribute("aria-hidden", "true");
699
1559
  svg.setAttribute("class", cls);
700
1560
  for (const d of ICONS[name] ?? []) {
701
- const path = doc.createElementNS(SVG_NS, "path");
1561
+ const path = doc.createElementNS(SVG_NS2, "path");
702
1562
  path.setAttribute("d", d);
703
1563
  svg.appendChild(path);
704
1564
  }
@@ -725,7 +1585,7 @@ function shortDateTime(iso) {
725
1585
  return d.toISOString().slice(0, 16).replace("T", " ");
726
1586
  }
727
1587
  }
728
- function shortDate(iso) {
1588
+ function shortDate2(iso) {
729
1589
  const d = new Date(iso);
730
1590
  if (Number.isNaN(d.getTime())) return "";
731
1591
  try {
@@ -820,17 +1680,24 @@ function renderMessenger(options) {
820
1680
  panel.append(head, main, tabs);
821
1681
  root.append(launcher, panel);
822
1682
  scope.appendChild(root);
1683
+ function withRcText(items, rc, apply) {
1684
+ if (!rc?.length) return items;
1685
+ const byId = new Map(rc.map((x) => [x.id, x]));
1686
+ return items.map((i) => byId.has(i.id) ? apply(i, byId.get(i.id)) : i);
1687
+ }
1688
+ const helpModules = () => options.resourceCenter?.modules?.length ? orderedModules(options.resourceCenter.modules).filter((m) => m.id !== "announcements" && m.enabled && (!("targeting" in m) || !m.targeting || !options.evaluate || options.evaluate(m.targeting))).map((m) => m.id) : void 0;
823
1689
  const rcExtras = (ids = []) => ids.map((id) => options.guides?.find((g) => g.id === id || g.key === id)).filter((g) => !!g);
824
1690
  const help = createHelpView({
825
1691
  doc,
826
1692
  data: () => ({
827
- collections: articles?.collections ?? options.resourceCenter?.collections ?? [],
828
- articles: articles?.articles ?? options.resourceCenter?.articles ?? [],
1693
+ collections: withRcText(articles?.collections ?? options.resourceCenter?.collections ?? [], options.resourceCenter?.collections, (c, t) => ({ ...c, name: t.name, description: t.description })),
1694
+ articles: withRcText(articles?.articles ?? options.resourceCenter?.articles ?? [], options.resourceCenter?.articles, (a, t) => ({ ...a, title: t.title, summary: t.summary })),
829
1695
  suggested: helpSuggestions(),
830
1696
  recommended: rcExtras(options.resourceCenter?.recommendedGuideIds),
831
1697
  checklists: rcExtras(options.resourceCenter?.checklistGuideIds),
832
1698
  faqs: options.resourceCenter?.faqs,
833
- links: options.resourceCenter?.links
1699
+ links: options.resourceCenter?.links,
1700
+ modules: helpModules()
834
1701
  }),
835
1702
  search: (q) => api.searchArticles(q),
836
1703
  audienceIds: options.audienceIds,
@@ -849,6 +1716,24 @@ function renderMessenger(options) {
849
1716
  onShowMe: (key) => startWalkthrough(key),
850
1717
  onArticleChange: () => syncSize()
851
1718
  });
1719
+ const ideasOn = () => !!config.ideas && !!api.listIdeas;
1720
+ const ideasTab = { kind: "tab", tab: "ideas" };
1721
+ const ideas = createIdeas({
1722
+ doc,
1723
+ api,
1724
+ config: () => config.ideas,
1725
+ identified: () => visitor().identified !== false,
1726
+ openIdea: (id) => go({ kind: "idea", id, from: view.kind === "idea" ? view.from : view }),
1727
+ openForm: () => go({ kind: "idea-new", from: view }),
1728
+ openSent: (idea) => go({ kind: "idea-sent", idea }),
1729
+ openList: () => go(ideasTab),
1730
+ openUpdates: () => go({ kind: "idea-updates", from: view }),
1731
+ onUnreadChange: () => {
1732
+ if (!open || destroyed) return;
1733
+ if (view.kind === "tab" && view.tab === "home") render();
1734
+ else renderTabs();
1735
+ }
1736
+ });
852
1737
  let docCollapsed = false;
853
1738
  let sizeButton = null;
854
1739
  const readingDoc = () => open && (view.kind === "howto" || view.kind === "tab" && view.tab === "help" && help.isArticleOpen());
@@ -943,7 +1828,7 @@ function renderMessenger(options) {
943
1828
  const refreshConversations = () => api.listConversations().then((list) => {
944
1829
  conversations = [...list].sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
945
1830
  updateBadge();
946
- if (open && view.kind === "tab" && view.tab !== "help") render();
1831
+ if (open && view.kind === "tab" && view.tab !== "help" && view.tab !== "ideas") render();
947
1832
  }).catch(() => {
948
1833
  });
949
1834
  function renderHead(title, back) {
@@ -977,6 +1862,7 @@ function renderMessenger(options) {
977
1862
  ["messages", "messages", "Messages"],
978
1863
  ["help", "help", "Help"]
979
1864
  ];
1865
+ if (ideasOn()) items.push(["ideas", "idea", config.ideas.title || "Ideas"]);
980
1866
  const latest = news();
981
1867
  if (latest.length) items.push(["news", "news", "News"]);
982
1868
  for (const [tab, ico, label] of items) {
@@ -987,7 +1873,7 @@ function renderMessenger(options) {
987
1873
  const i = el(doc, "span", "g-m-tab-icon");
988
1874
  i.appendChild(icon(doc, ico));
989
1875
  b.append(i, el(doc, "span", "", label));
990
- if (tab === "news" && latest.some((n) => n.unread)) {
1876
+ if (tab === "news" && latest.some((n) => n.unread) || tab === "ideas" && ideas.unread() > 0) {
991
1877
  const dot2 = el(doc, "span", "g-m-tab-dot");
992
1878
  dot2.setAttribute("aria-label", "New");
993
1879
  i.appendChild(dot2);
@@ -1193,11 +2079,16 @@ function renderMessenger(options) {
1193
2079
  const body = el(doc, "span", "g-m-news-body");
1194
2080
  const meta = el(doc, "span", "g-m-row g-m-center g-m-news-meta");
1195
2081
  if (n.unread) meta.appendChild(el(doc, "span", "g-m-tag", "New"));
1196
- meta.appendChild(el(doc, "span", "g-m-time", shortDate(n.publishedAt)));
2082
+ meta.appendChild(el(doc, "span", "g-m-time", shortDate2(n.publishedAt)));
1197
2083
  body.append(meta, el(doc, "span", "g-m-card-title", n.title));
1198
2084
  if (n.body) body.appendChild(el(doc, "span", "g-m-card-sub g-m-clamp", n.body));
1199
2085
  card.appendChild(body);
1200
- card.addEventListener("click", () => startGuide(n.key || n.id));
2086
+ card.addEventListener("click", () => {
2087
+ if (n.key) startGuide(n.key);
2088
+ else if (n.articleId) openArticle(n.articleId);
2089
+ else if (n.url && /^https?:\/\//i.test(n.url)) window.open(n.url, "_blank", "noopener,noreferrer");
2090
+ else startGuide(n.id);
2091
+ });
1201
2092
  return card;
1202
2093
  }
1203
2094
  function renderHome() {
@@ -1223,6 +2114,8 @@ function renderMessenger(options) {
1223
2114
  if (filed) inner.appendChild(conversationCard(filed, "Your complaint"));
1224
2115
  const recent = conversations.find((c) => c.id !== filed?.id);
1225
2116
  if (recent) inner.appendChild(conversationCard(recent, recent.status === "closed" && !recent.unread ? "Recent conversation" : "Continue the conversation"));
2117
+ const ideaUpdates = ideasOn() ? ideas.unread() : 0;
2118
+ if (ideaUpdates) inner.appendChild(actionCard({ title: "Updates on your ideas", sub: ideaUpdates === 1 ? "1 new update" : `${ideaUpdates} new updates`, lead: iconTile("idea", true), trail: true, onClick: () => go({ kind: "idea-updates", from: view }) }));
1226
2119
  if (config.status) inner.appendChild(statusCard(config.status));
1227
2120
  if (cards.progress) progressCards(v).forEach((c) => inner.appendChild(c));
1228
2121
  const suggestions = pageSuggestions(v);
@@ -1242,6 +2135,10 @@ function renderMessenger(options) {
1242
2135
  }
1243
2136
  const latest = news()[0];
1244
2137
  if (latest) inner.appendChild(newsCard(latest));
2138
+ if (ideasOn()) {
2139
+ const canWrite = !ideas.blocked();
2140
+ inner.appendChild(actionCard({ title: canWrite ? "Share an idea" : "Browse ideas", sub: canWrite ? "Suggest a feature or vote for what's next." : "See what people are asking for and what's planned.", lead: iconTile("idea"), onClick: () => go(ideasTab) }));
2141
+ }
1245
2142
  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" }) }));
1246
2143
  scroll.append(hero, inner);
1247
2144
  main.appendChild(scroll);
@@ -1342,6 +2239,25 @@ function renderMessenger(options) {
1342
2239
  scroll.appendChild(complaintSent(doc, { complaint: v.complaint, answers: v.answers, onOpenConversation: () => go({ kind: "thread", conversationId: v.conversationId }), onHome: () => go({ kind: "tab", tab: "home" }) }));
1343
2240
  main.appendChild(scroll);
1344
2241
  }
2242
+ function renderIdeasView(v) {
2243
+ const title = config.ideas?.title || "Ideas";
2244
+ if (v.kind === "idea") {
2245
+ renderHead(title, () => go(v.from));
2246
+ main.appendChild(ideas.detailScreen(v.id));
2247
+ } else if (v.kind === "idea-new") {
2248
+ renderHead("Suggest an idea", () => go(v.from));
2249
+ main.appendChild(ideas.formScreen());
2250
+ } else if (v.kind === "idea-sent") {
2251
+ renderHead(title, () => go(ideasTab));
2252
+ main.appendChild(ideas.sentScreen(v.idea));
2253
+ } else if (v.kind === "idea-updates") {
2254
+ renderHead("Updates on your ideas", () => go(v.from));
2255
+ main.appendChild(ideas.updatesScreen());
2256
+ } else {
2257
+ renderHead(title);
2258
+ main.appendChild(ideas.listScreen());
2259
+ }
2260
+ }
1345
2261
  function renderHowToView(id, from) {
1346
2262
  const cached = howToCache.get(id);
1347
2263
  renderHead(cached?.title ?? "How-to", () => go(from));
@@ -1455,7 +2371,8 @@ function renderMessenger(options) {
1455
2371
  bubble.dataset.messageId = m.id;
1456
2372
  wrap.appendChild(bubble);
1457
2373
  const offer = m.complaintOffer && canComplain() ? complaintCategories().find((c) => c.id === m.complaintOffer.categoryId) : void 0;
1458
- if (m.citations.length || m.suggestedGuides.length || m.howTos?.length || offer) {
2374
+ const ideaChip = m.ideaOffer && ideasOn() ? ideas.offerChip(m.ideaOffer, current?.id ?? null, () => icon(doc, "idea", "g-m-ico g-m-chip-ico")) : null;
2375
+ if (m.citations.length || m.suggestedGuides.length || m.howTos?.length || offer || ideaChip) {
1459
2376
  const chips = el(doc, "div", "g-m-chips");
1460
2377
  if (offer) {
1461
2378
  const b = el(doc, "button", "g-m-chip g-m-guide g-m-offer");
@@ -1468,6 +2385,7 @@ function renderMessenger(options) {
1468
2385
  });
1469
2386
  chips.appendChild(b);
1470
2387
  }
2388
+ if (ideaChip) chips.appendChild(ideaChip);
1471
2389
  for (const g of m.suggestedGuides) {
1472
2390
  const b = el(doc, "button", "g-m-chip g-m-guide", `\u25B6 Show me: ${g.name}`);
1473
2391
  b.type = "button";
@@ -1620,7 +2538,13 @@ function renderMessenger(options) {
1620
2538
  else if (view.kind === "complaint-category") renderComplaintCategories(view);
1621
2539
  else if (view.kind === "complaint-form") renderComplaintForm(view);
1622
2540
  else if (view.kind === "complaint-sent") renderComplaintSent(view);
1623
- else if (view.tab === "home") renderHome();
2541
+ else if (view.kind !== "tab" || view.tab === "ideas") {
2542
+ if (ideasOn()) renderIdeasView(view);
2543
+ else {
2544
+ view = { kind: "tab", tab: "home" };
2545
+ renderHome();
2546
+ }
2547
+ } else if (view.tab === "home") renderHome();
1624
2548
  else if (view.tab === "messages") renderMessagesTab();
1625
2549
  else if (view.tab === "news" && news().length) renderNewsTab();
1626
2550
  else if (view.tab === "news") {
@@ -1771,6 +2695,7 @@ function renderMessenger(options) {
1771
2695
  render();
1772
2696
  void refreshConversations();
1773
2697
  loadHowTos();
2698
+ if (ideasOn()) ideas.loadUpdates();
1774
2699
  if (view.kind === "thread") scheduleThreadPoll();
1775
2700
  options.onOpen?.();
1776
2701
  } else {
@@ -1819,15 +2744,22 @@ function renderMessenger(options) {
1819
2744
  setOpen(true);
1820
2745
  startComplaint(categoryId ?? null);
1821
2746
  },
2747
+ openIdeas(requestId) {
2748
+ if (!ideasOn()) return;
2749
+ setOpen(true);
2750
+ go(requestId ? { kind: "idea", id: requestId, from: ideasTab } : ideasTab);
2751
+ },
1822
2752
  refresh() {
1823
2753
  if (destroyed) return;
2754
+ if (ideasOn()) ideas.sync(open);
1824
2755
  if (!open) {
1825
2756
  pageKey = null;
1826
2757
  return;
1827
2758
  }
1828
2759
  loadPage();
1829
- if (view.kind === "tab") render();
1830
- else if (!messages.length) renderLog();
2760
+ if (view.kind === "tab" && view.tab !== "ideas") render();
2761
+ else if (view.kind === "tab") renderTabs();
2762
+ else if (view.kind === "thread" && !messages.length) renderLog();
1831
2763
  },
1832
2764
  destroy() {
1833
2765
  destroyed = true;
@@ -1835,6 +2767,7 @@ function renderMessenger(options) {
1835
2767
  if (backgroundTimer) clearInterval(backgroundTimer);
1836
2768
  panel.removeEventListener("keydown", onKey);
1837
2769
  help.destroy();
2770
+ ideas.destroy();
1838
2771
  shadowHost?.removeAttribute("data-g-messenger");
1839
2772
  root.remove();
1840
2773
  }