From f5de97c60c5f17030fe2147b8e2337de64db1352 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Mar 2026 14:02:27 -0700 Subject: [PATCH] feat(ux): move comment button to header bar with unresolved badge Move the "Leave Comment" button from the bottom canvas toolbar to the top header bar as , positioned left of the notification bell. Shows a red badge with unresolved comment pin count. Wires canvas via comment-pin-activate/comment-pins-changed custom events. Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 1 + shared/components/rstack-comment-bell.ts | 136 +++++++++++++++++++++++ website/canvas.html | 23 ++-- website/shell.ts | 2 + 4 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 shared/components/rstack-comment-bell.ts diff --git a/server/shell.ts b/server/shell.ts index cb5e75e..0fdde38 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -243,6 +243,7 @@ export function renderShell(opts: ShellOptions): string {
Try Demo + diff --git a/shared/components/rstack-comment-bell.ts b/shared/components/rstack-comment-bell.ts new file mode 100644 index 0000000..e222a4f --- /dev/null +++ b/shared/components/rstack-comment-bell.ts @@ -0,0 +1,136 @@ +/** + * — Comment button with unresolved-count badge. + * + * Shows a chat-bubble icon in the header bar. Badge displays the count + * of unresolved comment pins on the current canvas. Clicking dispatches + * a `comment-pin-activate` event on `window` so canvas.html can enter + * pin-placement mode. + * + * Data source: `window.__communitySync?.doc?.commentPins` + * Listens for `comment-pins-changed` on `window` (re-dispatched by canvas). + * Polls every 5s as fallback (sync may appear after component mounts). + */ + +const POLL_INTERVAL = 5_000; + +export class RStackCommentBell extends HTMLElement { + #shadow: ShadowRoot; + #count = 0; + #pollTimer: ReturnType | null = null; + + constructor() { + super(); + this.#shadow = this.attachShadow({ mode: "open" }); + } + + connectedCallback() { + this.#render(); + this.#refreshCount(); + this.#pollTimer = setInterval(() => this.#refreshCount(), POLL_INTERVAL); + window.addEventListener("comment-pins-changed", this.#onPinsChanged); + } + + disconnectedCallback() { + if (this.#pollTimer) clearInterval(this.#pollTimer); + window.removeEventListener("comment-pins-changed", this.#onPinsChanged); + } + + #onPinsChanged = () => { + this.#refreshCount(); + }; + + #refreshCount() { + const sync = (window as any).__communitySync; + const pins = sync?.doc?.commentPins; + if (!pins) { + if (this.#count !== 0) { + this.#count = 0; + this.#render(); + } + return; + } + const newCount = Object.values(pins).filter( + (p: any) => !p.resolved + ).length; + if (newCount !== this.#count) { + this.#count = newCount; + this.#render(); + } + } + + #render() { + const badge = + this.#count > 0 + ? `${this.#count > 99 ? "99+" : this.#count}` + : ""; + + this.#shadow.innerHTML = ` + + + `; + + this.#shadow + .querySelector(".comment-btn") + ?.addEventListener("click", (e) => { + e.stopPropagation(); + window.dispatchEvent(new CustomEvent("comment-pin-activate")); + }); + } + + static define(tag = "rstack-comment-bell") { + if (!customElements.get(tag)) customElements.define(tag, RStackCommentBell); + } +} + +const STYLES = ` +:host { + display: inline-flex; + align-items: center; +} + +.comment-btn { + position: relative; + background: none; + border: none; + color: var(--rs-text-muted, #94a3b8); + cursor: pointer; + padding: 6px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.15s, background 0.15s; +} +.comment-btn:hover { + color: var(--rs-text-primary, #e2e8f0); + background: var(--rs-bg-hover, rgba(255,255,255,0.05)); +} + +.badge { + position: absolute; + top: 2px; + right: 2px; + min-width: 16px; + height: 16px; + border-radius: 8px; + background: #ef4444; + color: white; + font-size: 10px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + padding: 0 4px; + line-height: 1; + pointer-events: none; +} + +@media (max-width: 640px) { + .comment-btn { display: none; } +} +`; diff --git a/website/canvas.html b/website/canvas.html index b1b8bda..a703ec8 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2198,9 +2198,6 @@ -