@devaccel-labs/comment-service 0.5.2 → 0.6.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/react.js CHANGED
@@ -354,8 +354,8 @@ function describeElement(element) {
354
354
  }
355
355
 
356
356
  // ../react/dist/toolbar.js
357
- import { jsx as _jsx4, jsxs as _jsxs4, Fragment as _Fragment2 } from "react/jsx-runtime";
358
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
357
+ import { jsx as _jsx5, jsxs as _jsxs5, Fragment as _Fragment3 } from "react/jsx-runtime";
358
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
359
359
  import { createPortal as createPortal2 } from "react-dom";
360
360
 
361
361
  // ../react/dist/client.js
@@ -445,8 +445,14 @@ async function listMembers(session, memberToken) {
445
445
  return payload.members;
446
446
  }
447
447
  async function listThreads(session, memberToken) {
448
- const payload = await request(`${session.apiUrl}/v1/threads`, memberToken);
449
- return payload.threads;
448
+ return request(`${session.apiUrl}/v1/threads`, memberToken);
449
+ }
450
+ async function saveSettings(session, memberToken, settings) {
451
+ const payload = await request(`${session.apiUrl}/v1/settings`, memberToken, {
452
+ method: "PUT",
453
+ body: JSON.stringify({ settings })
454
+ });
455
+ return payload.settings;
450
456
  }
451
457
  async function createThread(session, memberToken, anchor, body, extras = {}) {
452
458
  return request(`${session.apiUrl}/v1/threads`, memberToken, {
@@ -467,6 +473,118 @@ async function updateThread(session, memberToken, threadId, patch) {
467
473
  });
468
474
  }
469
475
 
476
+ // ../react/dist/details.js
477
+ import { jsx as _jsx2, jsxs as _jsxs2, Fragment as _Fragment } from "react/jsx-runtime";
478
+ import { useState } from "react";
479
+
480
+ // ../react/dist/icons.js
481
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
482
+ function Icon({ children, size = 16 }) {
483
+ return _jsx("svg", { className: "cs-icon", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
484
+ }
485
+ var AddComment = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }), _jsx("path", { d: "M12 7v6M9 10h6" })] });
486
+ var Inbox = () => _jsxs(Icon, { size: 17, children: [_jsx("polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }), _jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] });
487
+ var Eye = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] });
488
+ var EyeOff = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), _jsx("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }), _jsx("path", { d: "M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }), _jsx("path", { d: "M2 2l20 20" })] });
489
+ var Lock = ({ size = 15 }) => _jsxs(Icon, { size, children: [_jsx("rect", { width: "16", height: "10", x: "4", y: "11", rx: "2" }), _jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })] });
490
+ var Close = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
491
+ var Send = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m5 12 7-7 7 7M12 19V5" }) });
492
+ var Check = () => _jsx(Icon, { size: 14, children: _jsx("polyline", { points: "20 6 9 17 4 12" }) });
493
+ var Target = () => _jsxs(Icon, { size: 14, children: [_jsx("circle", { cx: "12", cy: "12", r: "8" }), _jsx("circle", { cx: "12", cy: "12", r: "2.5" })] });
494
+ var Relink = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8" }) });
495
+ var Undo = () => _jsxs(Icon, { size: 14, children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" })] });
496
+ var Pencil = () => _jsx(Icon, { size: 13, children: _jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
497
+ var Cursor = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "m4 4 7.07 17 2.51-7.39L21 11.07z" }) });
498
+ var Paperclip = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
499
+ var Camera = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" }), _jsx("circle", { cx: "12", cy: "13", r: "3" })] });
500
+ var FileIcon = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }), _jsx("polyline", { points: "14 2 14 8 20 8" })] });
501
+ var Back = () => _jsx(Icon, { children: _jsx("path", { d: "m15 18-6-6 6-6" }) });
502
+
503
+ // ../react/dist/details.js
504
+ var noDetails = { role: null, fields: {} };
505
+ function detailsEnabled(settings) {
506
+ if (!settings)
507
+ return false;
508
+ return settings.roles.enabled && settings.roles.options.length > 0 || settings.fields.enabled && settings.fields.items.length > 0;
509
+ }
510
+ function hasFieldValues(details) {
511
+ return Object.values(details.fields).some((value) => value.trim());
512
+ }
513
+ function detailsPayload(settings, details) {
514
+ if (!settings)
515
+ return {};
516
+ const payload = {};
517
+ if (settings.roles.enabled && details.role)
518
+ payload.role = details.role;
519
+ if (settings.fields.enabled) {
520
+ const fields = Object.fromEntries(Object.entries(details.fields).filter(([, value]) => value.trim()));
521
+ if (Object.keys(fields).length)
522
+ payload.fields = fields;
523
+ }
524
+ return payload;
525
+ }
526
+ function fieldId(label, taken) {
527
+ const base = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "field";
528
+ let id = base;
529
+ for (let n = 2; taken.some((field) => field.id === id); n += 1)
530
+ id = `${base}-${n}`;
531
+ return id;
532
+ }
533
+ function DetailsInput({ settings, value, onChange, onKeyDown }) {
534
+ if (!settings || !detailsEnabled(settings))
535
+ return null;
536
+ const roles = settings.roles.enabled ? settings.roles.options : [];
537
+ const fields = settings.fields.enabled ? settings.fields.items : [];
538
+ return _jsxs2("div", { className: "cs-details-input", "data-cs-ui": "details-input", children: [fields.map((field) => _jsxs2("label", { className: "cs-detail-field", children: [_jsx2("span", { children: field.label }), _jsx2("textarea", { "data-cs-ui": `field-${field.id}`, rows: 1, maxLength: 4e3, value: value.fields[field.id] ?? "", onChange: (event) => onChange({ ...value, fields: { ...value.fields, [field.id]: event.target.value } }), onKeyDown })] }, field.id)), roles.length ? _jsxs2("div", { className: "cs-roles", role: "radiogroup", "aria-label": "Role", children: [_jsx2("span", { className: "cs-roles-label", children: "Role" }), roles.map((role) => _jsx2("button", { type: "button", role: "radio", "data-cs-ui": "role-option", "aria-checked": value.role === role, className: value.role === role ? "cs-chip is-on" : "cs-chip", onClick: () => onChange({ ...value, role: value.role === role ? null : role }), children: role }, role))] }) : null] });
539
+ }
540
+ function RoleBadge({ role }) {
541
+ return role ? _jsx2("span", { className: "cs-role", "data-cs-ui": "role", children: role }) : null;
542
+ }
543
+ function FieldValues({ fields, clamp = false }) {
544
+ if (!fields.length)
545
+ return null;
546
+ return _jsx2("dl", { className: clamp ? "cs-detail-list is-clamped" : "cs-detail-list", "data-cs-ui": "fields", children: fields.map((field) => _jsxs2("div", { children: [_jsx2("dt", { children: field.label }), _jsx2("dd", { children: field.value })] }, field.id)) });
547
+ }
548
+ function DetailsEditor({ settings, thread, onSave, onClose }) {
549
+ const [value, setValue] = useState(() => ({
550
+ role: thread.role,
551
+ fields: Object.fromEntries(thread.fields.map((field) => [field.id, field.value]))
552
+ }));
553
+ const [busy, setBusy] = useState(false);
554
+ return _jsxs2("form", { className: "cs-details-edit", "data-cs-ui": "details-editor", onSubmit: (event) => {
555
+ event.preventDefault();
556
+ const patch = {};
557
+ if (settings.roles.enabled && value.role !== thread.role)
558
+ patch.role = value.role;
559
+ if (settings.fields.enabled) {
560
+ patch.fields = Object.fromEntries(settings.fields.items.map((field) => [field.id, value.fields[field.id] ?? ""]));
561
+ }
562
+ setBusy(true);
563
+ onSave(patch).then(onClose).finally(() => setBusy(false));
564
+ }, children: [_jsx2(DetailsInput, { settings, value, onChange: setValue }), _jsxs2("div", { className: "cs-row-end", children: [_jsx2("button", { className: "cs-button", type: "button", onClick: onClose, children: "Cancel" }), _jsx2("button", { className: "cs-button is-primary", "data-cs-ui": "details-save", type: "submit", disabled: busy, children: "Save" })] })] });
565
+ }
566
+ function AddRow({ label, hook, onAdd }) {
567
+ const [text, setText] = useState("");
568
+ return _jsxs2("form", { className: "cs-add-row", onSubmit: (event) => {
569
+ event.preventDefault();
570
+ const next = text.trim().replace(/\s+/g, " ");
571
+ if (!next)
572
+ return;
573
+ onAdd(next);
574
+ setText("");
575
+ }, children: [_jsx2("input", { "data-cs-ui": hook, "aria-label": label, placeholder: label, value: text, maxLength: 60, onChange: (event) => setText(event.target.value) }), _jsx2("button", { className: "cs-icon-button", type: "submit", "aria-label": label, disabled: !text.trim(), children: _jsx2(Check, {}) })] });
576
+ }
577
+ function SettingsPanel({ settings, onChange, onBack, error }) {
578
+ return _jsxs2("section", { className: "cs-card cs-menu cs-settings cs-hit", "data-cs-ui": "settings", "aria-label": "Comment fields", children: [_jsxs2("header", { className: "cs-settings-head", children: [_jsx2("button", { className: "cs-icon-button", type: "button", "aria-label": "Back", onClick: onBack, children: _jsx2(Back, {}) }), _jsx2("h2", { children: "Comment fields" })] }), !settings ? _jsx2("p", { className: "cs-hint", children: "Loading\u2026" }) : _jsxs2(_Fragment, { children: [_jsxs2("button", { className: "cs-item", "data-cs-ui": "roles-toggle", type: "button", role: "switch", "aria-checked": settings.roles.enabled, onClick: () => onChange({ ...settings, roles: { ...settings.roles, enabled: !settings.roles.enabled } }), children: [_jsx2("span", { children: "Role" }), _jsx2("span", { className: settings.roles.enabled ? "cs-switch is-on" : "cs-switch" })] }), settings.roles.enabled ? _jsxs2("div", { className: "cs-settings-body", children: [settings.roles.options.length ? _jsx2("div", { className: "cs-chips", children: settings.roles.options.map((role) => _jsxs2("span", { className: "cs-chip is-static", "data-cs-ui": "role-chip", children: [role, _jsx2("button", { type: "button", "aria-label": `Remove ${role}`, onClick: () => onChange({ ...settings, roles: { ...settings.roles, options: settings.roles.options.filter((item) => item !== role) } }), children: _jsx2(Close, {}) })] }, role)) }) : null, _jsx2(AddRow, { label: "Add a role", hook: "role-add", onAdd: (role) => {
579
+ if (settings.roles.options.some((item) => item.toLowerCase() === role.toLowerCase()))
580
+ return;
581
+ onChange({ ...settings, roles: { ...settings.roles, options: [...settings.roles.options, role.slice(0, 40)] } });
582
+ } })] }) : null, _jsx2("div", { className: "cs-sep" }), _jsxs2("button", { className: "cs-item", "data-cs-ui": "fields-toggle", type: "button", role: "switch", "aria-checked": settings.fields.enabled, onClick: () => onChange({ ...settings, fields: { ...settings.fields, enabled: !settings.fields.enabled } }), children: [_jsx2("span", { children: "Custom fields" }), _jsx2("span", { className: settings.fields.enabled ? "cs-switch is-on" : "cs-switch" })] }), settings.fields.enabled ? _jsxs2("div", { className: "cs-settings-body", children: [settings.fields.items.map((field) => _jsxs2("div", { className: "cs-field-row", "data-cs-ui": "field-def", children: [_jsx2("span", { children: field.label }), _jsx2("button", { className: "cs-icon-button", type: "button", "aria-label": `Remove ${field.label}`, onClick: () => onChange({ ...settings, fields: { ...settings.fields, items: settings.fields.items.filter((item) => item.id !== field.id) } }), children: _jsx2(Close, {}) })] }, field.id)), _jsx2(AddRow, { label: "Add a field", hook: "field-add", onAdd: (label) => onChange({
583
+ ...settings,
584
+ fields: { ...settings.fields, items: [...settings.fields.items, { id: fieldId(label, settings.fields.items), label }] }
585
+ }) })] }) : null, error ? _jsx2("p", { className: "cs-error cs-pad", children: error }) : null, _jsx2("p", { className: "cs-hint", children: "Shared with everyone here. Turning off or removing only affects new comments." })] })] });
586
+ }
587
+
470
588
  // ../react/dist/format.js
471
589
  var palette = ["#007aff", "#34c759", "#ff9500", "#af52de", "#ff2d55", "#5ac8fa", "#5856d6", "#a2845e"];
472
590
  function initials(name) {
@@ -537,31 +655,9 @@ function deliveryText(delivery) {
537
655
  return parts.length ? parts.join(" \xB7 ") : null;
538
656
  }
539
657
 
540
- // ../react/dist/icons.js
541
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
542
- function Icon({ children, size = 16 }) {
543
- return _jsx("svg", { className: "cs-icon", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
544
- }
545
- var AddComment = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }), _jsx("path", { d: "M12 7v6M9 10h6" })] });
546
- var Inbox = () => _jsxs(Icon, { size: 17, children: [_jsx("polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }), _jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] });
547
- var Eye = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] });
548
- var EyeOff = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), _jsx("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }), _jsx("path", { d: "M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }), _jsx("path", { d: "M2 2l20 20" })] });
549
- var Lock = ({ size = 15 }) => _jsxs(Icon, { size, children: [_jsx("rect", { width: "16", height: "10", x: "4", y: "11", rx: "2" }), _jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })] });
550
- var Close = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
551
- var Send = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m5 12 7-7 7 7M12 19V5" }) });
552
- var Check = () => _jsx(Icon, { size: 14, children: _jsx("polyline", { points: "20 6 9 17 4 12" }) });
553
- var Target = () => _jsxs(Icon, { size: 14, children: [_jsx("circle", { cx: "12", cy: "12", r: "8" }), _jsx("circle", { cx: "12", cy: "12", r: "2.5" })] });
554
- var Relink = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8" }) });
555
- var Undo = () => _jsxs(Icon, { size: 14, children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" })] });
556
- var Pencil = () => _jsx(Icon, { size: 13, children: _jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
557
- var Cursor = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "m4 4 7.07 17 2.51-7.39L21 11.07z" }) });
558
- var Paperclip = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
559
- var Camera = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" }), _jsx("circle", { cx: "12", cy: "13", r: "3" })] });
560
- var FileIcon = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }), _jsx("polyline", { points: "14 2 14 8 20 8" })] });
561
-
562
658
  // ../react/dist/attachments.js
563
- import { jsx as _jsx2, jsxs as _jsxs2 } from "react/jsx-runtime";
564
- import { useCallback, useEffect, useRef, useState } from "react";
659
+ import { jsx as _jsx3, jsxs as _jsxs3 } from "react/jsx-runtime";
660
+ import { useCallback, useEffect, useRef, useState as useState2 } from "react";
565
661
  var MAX_BYTES = 10 * 1024 * 1024;
566
662
  var MAX_FILES = 5;
567
663
  var ACCEPT = "image/png,image/jpeg,image/gif,image/webp,application/pdf,text/plain,text/csv,application/json,application/zip,video/mp4,video/webm,video/quicktime";
@@ -574,7 +670,7 @@ function formatSize(bytes) {
574
670
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
575
671
  }
576
672
  function useAttachments(session, memberToken, onError) {
577
- const [items, setItems] = useState([]);
673
+ const [items, setItems] = useState2([]);
578
674
  const itemsRef = useRef(items);
579
675
  itemsRef.current = items;
580
676
  const patch = (key, next) => setItems((current) => current.map((item) => item.key === key ? { ...item, ...next } : item));
@@ -633,26 +729,26 @@ function filesFromClipboard(event) {
633
729
  function AttachmentTray({ items, onRemove }) {
634
730
  if (!items.length)
635
731
  return null;
636
- return _jsx2("div", { className: "cs-tray", "data-cs-ui": "attachment-tray", children: items.map((item) => _jsxs2("div", { className: `cs-pending is-${item.status}`, "data-cs-ui": "pending-file", "data-status": item.status, title: item.error ?? item.name, children: [item.preview ? _jsx2("img", { src: item.preview, alt: "" }) : _jsx2(FileIcon, {}), _jsx2("span", { className: "cs-pending-name", children: item.name }), _jsx2("button", { type: "button", className: "cs-pending-remove", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx2(Close, {}) })] }, item.key)) });
732
+ return _jsx3("div", { className: "cs-tray", "data-cs-ui": "attachment-tray", children: items.map((item) => _jsxs3("div", { className: `cs-pending is-${item.status}`, "data-cs-ui": "pending-file", "data-status": item.status, title: item.error ?? item.name, children: [item.preview ? _jsx3("img", { src: item.preview, alt: "" }) : _jsx3(FileIcon, {}), _jsx3("span", { className: "cs-pending-name", children: item.name }), _jsx3("button", { type: "button", className: "cs-pending-remove", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx3(Close, {}) })] }, item.key)) });
637
733
  }
638
734
  function AttachButtons({ onFiles, onSnapshot, capturing }) {
639
735
  const input = useRef(null);
640
- return _jsxs2("span", { className: "cs-attach", children: [_jsx2("input", { ref: input, type: "file", multiple: true, accept: ACCEPT, hidden: true, "data-cs-ui": "file-input", onChange: (event) => {
736
+ return _jsxs3("span", { className: "cs-attach", children: [_jsx3("input", { ref: input, type: "file", multiple: true, accept: ACCEPT, hidden: true, "data-cs-ui": "file-input", onChange: (event) => {
641
737
  onFiles([...event.target.files ?? []].map((file) => ({ blob: file, name: file.name })));
642
738
  event.target.value = "";
643
- } }), _jsx2("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "attach", "aria-label": "Attach files", onClick: () => input.current?.click(), children: _jsx2(Paperclip, {}) }), _jsx2("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "snapshot", "aria-label": "Attach a screenshot of the page", disabled: capturing, onClick: onSnapshot, children: _jsx2(Camera, {}) })] });
739
+ } }), _jsx3("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "attach", "aria-label": "Attach files", onClick: () => input.current?.click(), children: _jsx3(Paperclip, {}) }), _jsx3("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "snapshot", "aria-label": "Attach a screenshot of the page", disabled: capturing, onClick: onSnapshot, children: _jsx3(Camera, {}) })] });
644
740
  }
645
741
  function MessageAttachments({ attachments }) {
646
742
  if (!attachments?.length)
647
743
  return null;
648
744
  const images = attachments.filter((file) => file.contentType.startsWith("image/"));
649
745
  const others = attachments.filter((file) => !file.contentType.startsWith("image/"));
650
- return _jsxs2("div", { className: "cs-files", "data-cs-ui": "attachments", children: [images.length ? _jsx2("div", { className: "cs-thumbs", children: images.map((file) => _jsx2("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-thumb", title: file.name, "data-cs-ui": "attachment", children: _jsx2("img", { src: file.url, alt: file.name, loading: "lazy" }) }, file.id)) }) : null, others.map((file) => _jsxs2("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-file", "data-cs-ui": "attachment", children: [_jsx2(FileIcon, {}), _jsx2("span", { className: "cs-file-name", children: file.name }), _jsx2("span", { className: "cs-file-size", children: formatSize(file.size) })] }, file.id))] });
746
+ return _jsxs3("div", { className: "cs-files", "data-cs-ui": "attachments", children: [images.length ? _jsx3("div", { className: "cs-thumbs", children: images.map((file) => _jsx3("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-thumb", title: file.name, "data-cs-ui": "attachment", children: _jsx3("img", { src: file.url, alt: file.name, loading: "lazy" }) }, file.id)) }) : null, others.map((file) => _jsxs3("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-file", "data-cs-ui": "attachment", children: [_jsx3(FileIcon, {}), _jsx3("span", { className: "cs-file-name", children: file.name }), _jsx3("span", { className: "cs-file-size", children: formatSize(file.size) })] }, file.id))] });
651
747
  }
652
748
 
653
749
  // ../react/dist/mentions.js
654
- import { Fragment as _Fragment, jsx as _jsx3, jsxs as _jsxs3 } from "react/jsx-runtime";
655
- import { useMemo, useRef as useRef2, useState as useState2 } from "react";
750
+ import { Fragment as _Fragment2, jsx as _jsx4, jsxs as _jsxs4 } from "react/jsx-runtime";
751
+ import { useMemo, useRef as useRef2, useState as useState3 } from "react";
656
752
  import { createPortal } from "react-dom";
657
753
  function queryAt(text, caret) {
658
754
  const match = text.slice(0, caret).match(/(^|\s)@([^\s@]{0,30})$/);
@@ -666,16 +762,16 @@ function mentionsIn(text, picked) {
666
762
  }
667
763
  function MessageText({ body, mentions }) {
668
764
  if (!mentions?.length)
669
- return _jsx3(_Fragment, { children: body });
765
+ return _jsx4(_Fragment2, { children: body });
670
766
  const pattern = new RegExp(`(@(?:${mentions.map((mention) => escapeRegex(mention.name)).join("|")}))`, "g");
671
- const parts = body.split(pattern).map((part, index) => index % 2 === 1 ? _jsx3("span", { className: "cs-mention", children: part }, index) : part);
672
- return _jsx3(_Fragment, { children: parts });
767
+ const parts = body.split(pattern).map((part, index) => index % 2 === 1 ? _jsx4("span", { className: "cs-mention", children: part }, index) : part);
768
+ return _jsx4(_Fragment2, { children: parts });
673
769
  }
674
770
  function MentionTextarea({ value, onValueChange, members, selfEmail, onMention, onKeyDown, ...rest }) {
675
771
  const ref = useRef2(null);
676
- const [caret, setCaret] = useState2(0);
677
- const [index, setIndex] = useState2(0);
678
- const [dismissed, setDismissed] = useState2(null);
772
+ const [caret, setCaret] = useState3(0);
773
+ const [index, setIndex] = useState3(0);
774
+ const [dismissed, setDismissed] = useState3(null);
679
775
  const active = queryAt(value, caret);
680
776
  const query = active?.query.toLowerCase() ?? null;
681
777
  const matches = useMemo(() => {
@@ -702,7 +798,7 @@ function MentionTextarea({ value, onValueChange, members, selfEmail, onMention,
702
798
  const rect = open ? ref.current?.getBoundingClientRect() : null;
703
799
  const root = open ? ref.current?.closest("#cs-root") : null;
704
800
  const below = rect ? window.innerHeight - rect.bottom > 220 : true;
705
- return _jsxs3(_Fragment, { children: [_jsx3("textarea", { ref, ...rest, value, "aria-autocomplete": "list", "aria-expanded": open, onChange: (event) => {
801
+ return _jsxs4(_Fragment2, { children: [_jsx4("textarea", { ref, ...rest, value, "aria-autocomplete": "list", "aria-expanded": open, onChange: (event) => {
706
802
  onValueChange(event.target.value);
707
803
  setCaret(event.target.selectionStart ?? event.target.value.length);
708
804
  setIndex(0);
@@ -728,13 +824,13 @@ function MentionTextarea({ value, onValueChange, members, selfEmail, onMention,
728
824
  }
729
825
  }
730
826
  onKeyDown?.(event);
731
- } }), open && rect && root ? createPortal(_jsx3("div", { className: "cs-people cs-hit", role: "listbox", "data-cs-ui": "mention-list", style: {
827
+ } }), open && rect && root ? createPortal(_jsx4("div", { className: "cs-people cs-hit", role: "listbox", "data-cs-ui": "mention-list", style: {
732
828
  left: Math.min(rect.left, window.innerWidth - 272),
733
829
  ...below ? { top: rect.bottom + 6 } : { bottom: window.innerHeight - rect.top + 6 }
734
- }, children: matches.map((member) => _jsxs3("div", { role: "option", "aria-selected": member === selected, "data-cs-ui": "mention-option", className: member === selected ? "cs-person is-on" : "cs-person", onMouseDown: (event) => {
830
+ }, children: matches.map((member) => _jsxs4("div", { role: "option", "aria-selected": member === selected, "data-cs-ui": "mention-option", className: member === selected ? "cs-person is-on" : "cs-person", onMouseDown: (event) => {
735
831
  event.preventDefault();
736
832
  insert(member);
737
- }, onMouseEnter: () => setIndex(matches.indexOf(member)), children: [_jsx3("span", { className: "cs-avatar", style: { width: 24, height: 24, fontSize: 10, background: colorFor(member.name) }, children: initials(member.name) }), _jsxs3("span", { className: "cs-person-text", children: [_jsx3("strong", { children: member.name }), _jsx3("small", { children: member.email })] })] }, member.id)) }), root) : null] });
833
+ }, onMouseEnter: () => setIndex(matches.indexOf(member)), children: [_jsx4("span", { className: "cs-avatar", style: { width: 24, height: 24, fontSize: 10, background: colorFor(member.name) }, children: initials(member.name) }), _jsxs4("span", { className: "cs-person-text", children: [_jsx4("strong", { children: member.name }), _jsx4("small", { children: member.email })] })] }, member.id)) }), root) : null] });
738
834
  }
739
835
 
740
836
  // ../../node_modules/modern-screenshot/dist/index.mjs
@@ -2568,6 +2664,50 @@ var toolbarCss = `
2568
2664
  #cs-root .cs-composer-foot { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px 40px; }
2569
2665
  #cs-root .cs-composer-foot .cs-hint { margin-left: auto; }
2570
2666
 
2667
+ /* Roles and fields */
2668
+ #cs-root .cs-details-input { display: grid; gap: 8px; padding: 2px 12px 6px 46px; }
2669
+ #cs-root .cs-details-edit .cs-details-input { padding: 0; }
2670
+ #cs-root .cs-detail-field { display: grid; gap: 3px; }
2671
+ #cs-root .cs-detail-field span, #cs-root .cs-roles-label, #cs-root .cs-detail-list dt {
2672
+ font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.03em;
2673
+ }
2674
+ #cs-root .cs-detail-field textarea { padding: 6px 8px; min-height: 30px; max-height: 120px; field-sizing: content; }
2675
+ #cs-root .cs-thread.is-open .cs-detail-field textarea { background: var(--cs-card); }
2676
+ #cs-root .cs-roles { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
2677
+ #cs-root .cs-roles-label { margin-right: 4px; }
2678
+ #cs-root .cs-chip {
2679
+ display: inline-flex; align-items: center; gap: 2px; height: 24px; padding: 0 9px; border-radius: 999px;
2680
+ background: var(--cs-fill); font-size: 12px; font-weight: 500; color: var(--cs-text-2);
2681
+ }
2682
+ #cs-root button.cs-chip:hover { background: var(--cs-fill-2); }
2683
+ #cs-root .cs-chip.is-on, #cs-root button.cs-chip.is-on:hover { background: var(--cs-accent); color: #fff; }
2684
+ #cs-root .cs-chip.is-static { padding-right: 3px; color: var(--cs-text); }
2685
+ #cs-root .cs-chip.is-static button {
2686
+ width: 18px; height: 18px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);
2687
+ }
2688
+ #cs-root .cs-chip.is-static button:hover { background: var(--cs-fill-2); color: var(--cs-text); }
2689
+ #cs-root .cs-chip.is-static .cs-icon { width: 11px; height: 11px; }
2690
+ #cs-root .cs-chips { display: flex; flex-wrap: wrap; gap: 4px; }
2691
+ #cs-root .cs-role {
2692
+ display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 999px;
2693
+ background: var(--cs-accent-soft); color: var(--cs-accent); font-size: 11px; font-weight: 600;
2694
+ }
2695
+ #cs-root .cs-detail-list { display: grid; gap: 6px; margin: 6px 0 0; }
2696
+ #cs-root .cs-detail-list > div { padding: 6px 8px; border-radius: 8px; background: var(--cs-fill); }
2697
+ #cs-root .cs-thread.is-open .cs-detail-list > div { background: var(--cs-card); }
2698
+ #cs-root .cs-detail-list dd { margin: 1px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
2699
+ #cs-root .cs-detail-list.is-clamped dd { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
2700
+ #cs-root .cs-details-edit { display: grid; gap: 8px; }
2701
+ #cs-root .cs-details-edit .cs-row-end { margin-top: 0; }
2702
+ #cs-root .cs-settings { width: 280px; }
2703
+ #cs-root .cs-settings-head { display: flex; align-items: center; gap: 4px; padding: 4px 4px 6px; }
2704
+ #cs-root .cs-settings-head h2 { font-size: 14px; font-weight: 600; }
2705
+ #cs-root .cs-settings-body { display: grid; gap: 6px; padding: 2px 8px 8px; }
2706
+ #cs-root .cs-field-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-height: 28px; padding-left: 2px; }
2707
+ #cs-root .cs-add-row { display: flex; align-items: center; gap: 4px; }
2708
+ #cs-root .cs-add-row input { padding: 5px 8px; }
2709
+ #cs-root .cs-settings .cs-hint { padding: 6px 8px 4px; }
2710
+
2571
2711
  /* Attachments */
2572
2712
  #cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }
2573
2713
  #cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 12px 2px 46px; }
@@ -2690,6 +2830,12 @@ function placeAgainst(element, host) {
2690
2830
  height: rect.height
2691
2831
  };
2692
2832
  }
2833
+ function pageHref() {
2834
+ const url = new URL(window.location.href);
2835
+ url.searchParams.delete("cs-thread");
2836
+ url.searchParams.delete("cs-invite");
2837
+ return `${url.pathname}${url.search}${url.hash}`;
2838
+ }
2693
2839
  function isTyping(target) {
2694
2840
  if (!(target instanceof HTMLElement))
2695
2841
  return false;
@@ -2702,51 +2848,57 @@ function submitOnCmdEnter(event) {
2702
2848
  }
2703
2849
  }
2704
2850
  async function loadSession(sessionPath) {
2705
- const response = await fetch(sessionPath, { headers: { accept: "application/json" }, cache: "no-store" });
2851
+ const response = await fetch(sessionPath, {
2852
+ headers: { accept: "application/json", "x-comment-service-origin": window.location.origin },
2853
+ cache: "no-store"
2854
+ });
2706
2855
  const payload = await response.json();
2707
2856
  if (!payload.enabled || !payload.apiUrl || !payload.project || !payload.environment || !payload.hostToken)
2708
2857
  return null;
2709
2858
  return { apiUrl: payload.apiUrl, project: payload.project, environment: payload.environment, hostToken: payload.hostToken };
2710
2859
  }
2711
2860
  function Avatar({ name, size = 24 }) {
2712
- return _jsx4("span", { className: "cs-avatar", style: { width: size, height: size, fontSize: size * 0.4, background: colorFor(name) }, children: initials(name) });
2713
- }
2714
- function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs = 4e3, position = "bottom-center", theme = "light" }) {
2715
- const [phase, setPhase] = useState3("boot");
2716
- const [session, setSession] = useState3(null);
2717
- const [member, setMember] = useState3(null);
2718
- const [threads, setThreads] = useState3([]);
2719
- const [route, setRoute] = useState3("/");
2720
- const [surface, setSurface] = useState3("none");
2721
- const [tab, setTab] = useState3("page");
2722
- const [mode, setMode] = useState3("browse");
2723
- const [reattachId, setReattachId] = useState3(null);
2724
- const [activeId, setActiveId] = useState3(null);
2725
- const [peekId, setPeekId] = useState3(null);
2726
- const [draft, setDraft] = useState3(null);
2727
- const [body, setBody] = useState3("");
2728
- const [reply, setReply] = useState3("");
2729
- const [name, setName] = useState3("");
2730
- const [passphrase, setPassphrase] = useState3("");
2731
- const [email, setEmail] = useState3("");
2732
- const [members, setMembers] = useState3([]);
2733
- const [profile, setProfile] = useState3(null);
2734
- const [deliveries, setDeliveries] = useState3({});
2735
- const [invite, setInvite] = useState3(null);
2736
- const [capturing, setCapturing] = useState3(false);
2737
- const [inviteLink, setInviteLink] = useState3(null);
2738
- const [renaming, setRenaming] = useState3(null);
2739
- const [confirmReset, setConfirmReset] = useState3(false);
2740
- const [error, setError] = useState3("");
2741
- const [toast, setToast] = useState3("");
2742
- const [busy, setBusy] = useState3(false);
2743
- const [prefs, setPrefs] = useState3({});
2744
- const [portal, setPortal] = useState3(null);
2745
- const [mounted, setMounted] = useState3(false);
2746
- const [highlight, setHighlight] = useState3(null);
2747
- const [flash, setFlash] = useState3(null);
2748
- const [placed, setPlaced] = useState3([]);
2749
- const [systemDark, setSystemDark] = useState3(false);
2861
+ return _jsx5("span", { className: "cs-avatar", style: { width: size, height: size, fontSize: size * 0.4, background: colorFor(name) }, children: initials(name) });
2862
+ }
2863
+ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs = 4e3, position = "bottom-center", theme = "light", navigate }) {
2864
+ const [phase, setPhase] = useState4("boot");
2865
+ const [session, setSession] = useState4(null);
2866
+ const [member, setMember] = useState4(null);
2867
+ const [threads, setThreads] = useState4([]);
2868
+ const [route, setRoute] = useState4("/");
2869
+ const [surface, setSurface] = useState4("none");
2870
+ const [tab, setTab] = useState4("page");
2871
+ const [mode, setMode] = useState4("browse");
2872
+ const [reattachId, setReattachId] = useState4(null);
2873
+ const [activeId, setActiveId] = useState4(null);
2874
+ const [peekId, setPeekId] = useState4(null);
2875
+ const [draft, setDraft] = useState4(null);
2876
+ const [body, setBody] = useState4("");
2877
+ const [reply, setReply] = useState4("");
2878
+ const [name, setName] = useState4("");
2879
+ const [passphrase, setPassphrase] = useState4("");
2880
+ const [email, setEmail] = useState4("");
2881
+ const [members, setMembers] = useState4([]);
2882
+ const [profile, setProfile] = useState4(null);
2883
+ const [deliveries, setDeliveries] = useState4({});
2884
+ const [invite, setInvite] = useState4(null);
2885
+ const [capturing, setCapturing] = useState4(false);
2886
+ const [inviteLink, setInviteLink] = useState4(null);
2887
+ const [renaming, setRenaming] = useState4(null);
2888
+ const [confirmReset, setConfirmReset] = useState4(false);
2889
+ const [error, setError] = useState4("");
2890
+ const [toast, setToast] = useState4("");
2891
+ const [busy, setBusy] = useState4(false);
2892
+ const [prefs, setPrefs] = useState4({});
2893
+ const [portal, setPortal] = useState4(null);
2894
+ const [mounted, setMounted] = useState4(false);
2895
+ const [highlight, setHighlight] = useState4(null);
2896
+ const [flash, setFlash] = useState4(null);
2897
+ const [placed, setPlaced] = useState4([]);
2898
+ const [systemDark, setSystemDark] = useState4(false);
2899
+ const [settings, setSettings] = useState4(null);
2900
+ const [draftDetails, setDraftDetails] = useState4(noDetails);
2901
+ const [editingId, setEditingId] = useState4(null);
2750
2902
  const threadsRef = useRef3(threads);
2751
2903
  threadsRef.current = threads;
2752
2904
  const modeRef = useRef3(mode);
@@ -2756,6 +2908,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
2756
2908
  const draftShot = useRef3(null);
2757
2909
  const replyShot = useRef3(null);
2758
2910
  const pendingThread = useRef3(null);
2911
+ const arrival = useRef3(null);
2759
2912
  const draftFiles = useAttachments(session, member, setError);
2760
2913
  const replyFiles = useAttachments(session, member, setError);
2761
2914
  const resetReplyFiles = replyFiles.reset;
@@ -2855,7 +3008,9 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
2855
3008
  const refresh = useCallback2(async () => {
2856
3009
  if (!session || !member)
2857
3010
  return;
2858
- setThreads(await listThreads(session, member));
3011
+ const next = await listThreads(session, member);
3012
+ setThreads(next.threads);
3013
+ setSettings(next.settings);
2859
3014
  }, [member, session]);
2860
3015
  useEffect2(() => {
2861
3016
  if (!session || !member)
@@ -2863,8 +3018,10 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
2863
3018
  let stop = false;
2864
3019
  const tick = () => {
2865
3020
  listThreads(session, member).then((next) => {
2866
- if (!stop)
2867
- setThreads(next);
3021
+ if (stop)
3022
+ return;
3023
+ setThreads(next.threads);
3024
+ setSettings(next.settings);
2868
3025
  }).catch((caught) => {
2869
3026
  if (caught instanceof ApiError && caught.status === 401) {
2870
3027
  localStorage.removeItem(storageKey(session));
@@ -3045,7 +3202,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3045
3202
  const box = placeAgainst(element, target);
3046
3203
  if (modeRef.current === "reattach" && reattachId && member) {
3047
3204
  setBusy(true);
3048
- updateThread(session, member, reattachId, { anchor }).then(() => refresh()).then(() => {
3205
+ updateThread(session, member, reattachId, { anchor, url: pageHref() }).then(() => refresh()).then(() => {
3049
3206
  setMode("browse");
3050
3207
  setReattachId(null);
3051
3208
  setSurface("inbox");
@@ -3066,6 +3223,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3066
3223
  draftShot.current = null;
3067
3224
  draftFiles.reset();
3068
3225
  setBody("");
3226
+ setDraftDetails(noDetails);
3069
3227
  setMode("browse");
3070
3228
  setError("");
3071
3229
  };
@@ -3138,13 +3296,63 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3138
3296
  setSurface("inbox");
3139
3297
  if (item.status === "pinned" || item.status === "drifted")
3140
3298
  showOnPage(item.thread);
3299
+ else
3300
+ arrival.current = { id, until: Date.now() + 6e3 };
3141
3301
  }, [member, placed, route, showOnPage]);
3302
+ useEffect2(() => {
3303
+ const pending = arrival.current;
3304
+ if (!pending)
3305
+ return;
3306
+ if (Date.now() > pending.until) {
3307
+ arrival.current = null;
3308
+ return;
3309
+ }
3310
+ const item = placed.find((candidate) => candidate.thread.id === pending.id);
3311
+ if (!item || item.status !== "pinned" && item.status !== "drifted")
3312
+ return;
3313
+ arrival.current = null;
3314
+ showOnPage(item.thread);
3315
+ }, [placed, showOnPage]);
3316
+ const select = useCallback2((item) => {
3317
+ const { thread } = item;
3318
+ if (activeId === thread.id) {
3319
+ setActiveId(null);
3320
+ return;
3321
+ }
3322
+ setActiveId(thread.id);
3323
+ setEditingId(null);
3324
+ const onPage = item.status === "pinned" || item.status === "drifted";
3325
+ const target = thread.url || thread.route;
3326
+ const legacyHere = thread.url === thread.route && thread.route === route;
3327
+ if (target === pageHref() || onPage && legacyHere)
3328
+ return;
3329
+ if (navigate) {
3330
+ arrival.current = { id: thread.id, until: Date.now() + 6e3 };
3331
+ navigate(target);
3332
+ return;
3333
+ }
3334
+ const url = new URL(target, window.location.origin);
3335
+ url.searchParams.set("cs-thread", thread.id);
3336
+ window.location.assign(url.toString());
3337
+ }, [activeId, navigate, route]);
3142
3338
  if (!mounted || phase !== "ready" || !session)
3143
3339
  return null;
3144
3340
  const memberName = member ? readJwt(member)?.name ?? name : "";
3145
3341
  const host = portal ?? document.body;
3146
3342
  const inside = host !== document.body;
3147
3343
  const peek = peekId ? placed.find((item) => item.thread.id === peekId && item.top != null) : void 0;
3344
+ const canPost = Boolean(body.trim() || draftFiles.ids.length || settings?.fields.enabled && hasFieldValues(draftDetails));
3345
+ const changeSettings = (next) => {
3346
+ if (!member)
3347
+ return;
3348
+ const previous = settings;
3349
+ setSettings(next);
3350
+ setError("");
3351
+ saveSettings(session, member, next).then(setSettings).catch((caught) => {
3352
+ setSettings(previous);
3353
+ setError(caught instanceof Error ? caught.message : "Couldn\u2019t save.");
3354
+ });
3355
+ };
3148
3356
  const lock = () => {
3149
3357
  localStorage.removeItem(storageKey(session));
3150
3358
  setMember(null);
@@ -3170,22 +3378,22 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3170
3378
  setDraft(null);
3171
3379
  setSurface("unlock");
3172
3380
  };
3173
- const bar = _jsx4("div", { className: "cs-bar cs-hit", "data-cs-ui": "bar", role: "toolbar", "aria-label": "Comments toolbar", children: !member ? _jsxs4("button", { className: "cs-bar-lock", "data-cs-ui": "launcher", type: "button", "aria-label": "Comments", onClick: () => {
3381
+ const bar = _jsx5("div", { className: "cs-bar cs-hit", "data-cs-ui": "bar", role: "toolbar", "aria-label": "Comments toolbar", children: !member ? _jsxs5("button", { className: "cs-bar-lock", "data-cs-ui": "launcher", type: "button", "aria-label": "Comments", onClick: () => {
3174
3382
  setError("");
3175
3383
  setSurface(surface === "unlock" ? "none" : "unlock");
3176
- }, children: [_jsx4(Lock, {}), _jsx4("span", { children: "Comments" })] }) : _jsxs4(_Fragment2, { children: [_jsx4("button", { className: mode === "select" ? "cs-tool is-on is-accent" : "cs-tool", "data-cs-ui": "comment-mode", "data-tip": "Comment C", type: "button", "aria-label": "Add comment", "aria-pressed": mode === "select", onClick: startComment, children: _jsx4(AddComment, {}) }), _jsxs4("button", { className: surface === "inbox" ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "launcher", "data-tip": "Comments I", type: "button", "aria-label": "Comments", "aria-expanded": surface === "inbox", onClick: () => {
3384
+ }, children: [_jsx5(Lock, {}), _jsx5("span", { children: "Comments" })] }) : _jsxs5(_Fragment3, { children: [_jsx5("button", { className: mode === "select" ? "cs-tool is-on is-accent" : "cs-tool", "data-cs-ui": "comment-mode", "data-tip": "Comment C", type: "button", "aria-label": "Add comment", "aria-pressed": mode === "select", onClick: startComment, children: _jsx5(AddComment, {}) }), _jsxs5("button", { className: surface === "inbox" ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "launcher", "data-tip": "Comments I", type: "button", "aria-label": "Comments", "aria-expanded": surface === "inbox", onClick: () => {
3177
3385
  setError("");
3178
3386
  setMode("browse");
3179
3387
  setSurface(surface === "inbox" ? "none" : "inbox");
3180
- }, children: [_jsx4(Inbox, {}), counts.page > 0 ? _jsx4("span", { className: "cs-badge", children: counts.page }) : null] }), _jsx4("button", { className: pinsHidden ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "pins-toggle", "data-tip": pinsHidden ? "Show pins H" : "Hide pins H", type: "button", "aria-label": pinsHidden ? "Show pins" : "Hide pins", "aria-pressed": pinsHidden, onClick: togglePins, children: pinsHidden ? _jsx4(EyeOff, {}) : _jsx4(Eye, {}) }), _jsx4("span", { className: "cs-divider" }), _jsx4("button", { className: surface === "menu" ? "cs-tool cs-me is-on" : "cs-tool cs-me", "data-cs-ui": "menu-button", "data-tip": memberName, type: "button", "aria-label": "You", "aria-expanded": surface === "menu", onClick: () => {
3388
+ }, children: [_jsx5(Inbox, {}), counts.page > 0 ? _jsx5("span", { className: "cs-badge", children: counts.page }) : null] }), _jsx5("button", { className: pinsHidden ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "pins-toggle", "data-tip": pinsHidden ? "Show pins H" : "Hide pins H", type: "button", "aria-label": pinsHidden ? "Show pins" : "Hide pins", "aria-pressed": pinsHidden, onClick: togglePins, children: pinsHidden ? _jsx5(EyeOff, {}) : _jsx5(Eye, {}) }), _jsx5("span", { className: "cs-divider" }), _jsx5("button", { className: surface === "menu" ? "cs-tool cs-me is-on" : "cs-tool cs-me", "data-cs-ui": "menu-button", "data-tip": memberName, type: "button", "aria-label": "You", "aria-expanded": surface === "menu", onClick: () => {
3181
3389
  setConfirmReset(false);
3182
3390
  setRenaming(null);
3183
3391
  if (surface !== "menu" && member) {
3184
3392
  getProfile(session, member).then(setProfile).catch(() => void 0);
3185
3393
  }
3186
3394
  setSurface(surface === "menu" ? "none" : "menu");
3187
- }, children: _jsx4(Avatar, { name: memberName, size: 24 }) })] }) });
3188
- const unlockCard = _jsxs4("section", { className: "cs-card cs-unlock cs-hit", "data-cs-ui": "panel", "aria-label": "Unlock comments", children: [_jsx4("div", { className: "cs-unlock-icon", children: _jsx4(Lock, { size: 18 }) }), _jsx4("h2", { children: "Comments" }), _jsx4("p", { className: "cs-sub", children: invite ? `${readJwt(invite)?.name ?? "A teammate"} invited you to comment. Add your name and email to join.` : "Enter the team passphrase to see and add comments." }), _jsxs4("form", { onSubmit: (event) => {
3395
+ }, children: _jsx5(Avatar, { name: memberName, size: 24 }) })] }) });
3396
+ const unlockCard = _jsxs5("section", { className: "cs-card cs-unlock cs-hit", "data-cs-ui": "panel", "aria-label": "Unlock comments", children: [_jsx5("div", { className: "cs-unlock-icon", children: _jsx5(Lock, { size: 18 }) }), _jsx5("h2", { children: "Comments" }), _jsx5("p", { className: "cs-sub", children: invite ? `${readJwt(invite)?.name ?? "A teammate"} invited you to comment. Add your name and email to join.` : "Enter the team passphrase to see and add comments." }), _jsxs5("form", { onSubmit: (event) => {
3189
3397
  event.preventDefault();
3190
3398
  setBusy(true);
3191
3399
  setError("");
@@ -3206,8 +3414,8 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3206
3414
  setInvite(null);
3207
3415
  setError(caught instanceof Error ? caught.message : "Couldn\u2019t unlock.");
3208
3416
  }).finally(() => setBusy(false));
3209
- }, children: [_jsxs4("label", { className: "cs-field", children: [_jsx4("span", { children: "Your name" }), _jsx4("input", { "data-cs-ui": "unlock-name", value: name, onChange: (event) => setName(event.target.value), autoComplete: "name", required: true })] }), invite ? null : _jsxs4("label", { className: "cs-field", children: [_jsx4("span", { children: "Passphrase" }), _jsx4("input", { "data-cs-ui": "unlock-passphrase", type: "password", value: passphrase, onChange: (event) => setPassphrase(event.target.value), autoComplete: "current-password", required: true })] }), _jsxs4("label", { className: "cs-field", children: [_jsx4("span", { children: "Email" }), _jsx4("input", { "data-cs-ui": "unlock-email", type: "email", value: email, onChange: (event) => setEmail(event.target.value), autoComplete: "email", required: true }), _jsx4("small", { children: "So teammates can @mention you." })] }), error ? _jsx4("p", { className: "cs-error", "data-cs-ui": "error", children: error }) : null, _jsx4("button", { className: "cs-button is-primary is-wide", "data-cs-ui": "unlock-submit", type: "submit", disabled: busy, children: busy ? "Unlocking\u2026" : invite ? "Join" : "Unlock" })] })] });
3210
- const menuCard = _jsx4("section", { className: "cs-card cs-menu cs-hit", "data-cs-ui": "menu", "aria-label": "Your comment settings", children: confirmReset ? _jsxs4("div", { className: "cs-confirm", "data-cs-ui": "reset-confirm", children: [_jsx4("h2", { children: "Reset on this device?" }), _jsx4("p", { className: "cs-sub", children: "You\u2019ll be signed out and your name and preferences are cleared. Comments aren\u2019t affected." }), _jsxs4("div", { className: "cs-row-end", children: [_jsx4("button", { className: "cs-button", type: "button", onClick: () => setConfirmReset(false), children: "Cancel" }), _jsx4("button", { className: "cs-button is-danger", "data-cs-ui": "reset-submit", type: "button", onClick: reset, children: "Reset" })] })] }) : _jsxs4(_Fragment2, { children: [_jsxs4("div", { className: "cs-me-row", children: [_jsx4(Avatar, { name: renaming ?? memberName, size: 36 }), renaming != null ? _jsxs4("form", { className: "cs-rename", onSubmit: (event) => {
3417
+ }, children: [_jsxs5("label", { className: "cs-field", children: [_jsx5("span", { children: "Your name" }), _jsx5("input", { "data-cs-ui": "unlock-name", value: name, onChange: (event) => setName(event.target.value), autoComplete: "name", required: true })] }), invite ? null : _jsxs5("label", { className: "cs-field", children: [_jsx5("span", { children: "Passphrase" }), _jsx5("input", { "data-cs-ui": "unlock-passphrase", type: "password", value: passphrase, onChange: (event) => setPassphrase(event.target.value), autoComplete: "current-password", required: true })] }), _jsxs5("label", { className: "cs-field", children: [_jsx5("span", { children: "Email" }), _jsx5("input", { "data-cs-ui": "unlock-email", type: "email", value: email, onChange: (event) => setEmail(event.target.value), autoComplete: "email", required: true }), _jsx5("small", { children: "So teammates can @mention you." })] }), error ? _jsx5("p", { className: "cs-error", "data-cs-ui": "error", children: error }) : null, _jsx5("button", { className: "cs-button is-primary is-wide", "data-cs-ui": "unlock-submit", type: "submit", disabled: busy, children: busy ? "Unlocking\u2026" : invite ? "Join" : "Unlock" })] })] });
3418
+ const menuCard = _jsx5("section", { className: "cs-card cs-menu cs-hit", "data-cs-ui": "menu", "aria-label": "Your comment settings", children: confirmReset ? _jsxs5("div", { className: "cs-confirm", "data-cs-ui": "reset-confirm", children: [_jsx5("h2", { children: "Reset on this device?" }), _jsx5("p", { className: "cs-sub", children: "You\u2019ll be signed out and your name and preferences are cleared. Comments aren\u2019t affected." }), _jsxs5("div", { className: "cs-row-end", children: [_jsx5("button", { className: "cs-button", type: "button", onClick: () => setConfirmReset(false), children: "Cancel" }), _jsx5("button", { className: "cs-button is-danger", "data-cs-ui": "reset-submit", type: "button", onClick: reset, children: "Reset" })] })] }) : _jsxs5(_Fragment3, { children: [_jsxs5("div", { className: "cs-me-row", children: [_jsx5(Avatar, { name: renaming ?? memberName, size: 36 }), renaming != null ? _jsxs5("form", { className: "cs-rename", onSubmit: (event) => {
3211
3419
  event.preventDefault();
3212
3420
  const next = renaming.trim();
3213
3421
  if (!next || next === memberName || !member) {
@@ -3223,15 +3431,15 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3223
3431
  setRenaming(null);
3224
3432
  say("Name updated");
3225
3433
  }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t rename.")).finally(() => setBusy(false));
3226
- }, children: [_jsx4("input", { "data-cs-ui": "rename-input", "aria-label": "Your name", value: renaming, maxLength: 40, autoFocus: true, onChange: (event) => setRenaming(event.target.value), onKeyDown: (event) => {
3434
+ }, children: [_jsx5("input", { "data-cs-ui": "rename-input", "aria-label": "Your name", value: renaming, maxLength: 40, autoFocus: true, onChange: (event) => setRenaming(event.target.value), onKeyDown: (event) => {
3227
3435
  if (event.key === "Escape") {
3228
3436
  event.preventDefault();
3229
3437
  setRenaming(null);
3230
3438
  }
3231
- } }), _jsx4("button", { className: "cs-icon-button", type: "submit", "aria-label": "Save name", disabled: busy, children: _jsx4(Check, {}) })] }) : _jsxs4("div", { className: "cs-me-name", children: [_jsxs4("span", { className: "cs-me-lines", children: [_jsx4("strong", { children: memberName }), _jsx4("small", { children: profile?.email ?? readJwt(member ?? "")?.email ?? "" })] }), _jsx4("button", { className: "cs-icon-button", "data-cs-ui": "rename", type: "button", "aria-label": "Change name", onClick: () => {
3439
+ } }), _jsx5("button", { className: "cs-icon-button", type: "submit", "aria-label": "Save name", disabled: busy, children: _jsx5(Check, {}) })] }) : _jsxs5("div", { className: "cs-me-name", children: [_jsxs5("span", { className: "cs-me-lines", children: [_jsx5("strong", { children: memberName }), _jsx5("small", { children: profile?.email ?? readJwt(member ?? "")?.email ?? "" })] }), _jsx5("button", { className: "cs-icon-button", "data-cs-ui": "rename", type: "button", "aria-label": "Change name", onClick: () => {
3232
3440
  setError("");
3233
3441
  setRenaming(memberName);
3234
- }, children: _jsx4(Pencil, {}) })] })] }), error ? _jsx4("p", { className: "cs-error", children: error }) : null, _jsx4("div", { className: "cs-sep" }), _jsxs4("button", { className: "cs-item", type: "button", role: "switch", "aria-checked": !pinsHidden, onClick: togglePins, children: [_jsx4("span", { children: "Show pins" }), _jsx4("span", { className: pinsHidden ? "cs-switch" : "cs-switch is-on" })] }), _jsxs4("button", { className: "cs-item", "data-cs-ui": "mention-emails", type: "button", role: "switch", "aria-checked": profile?.mentionEmails ?? true, disabled: !profile, onClick: () => {
3442
+ }, children: _jsx5(Pencil, {}) })] })] }), error ? _jsx5("p", { className: "cs-error", children: error }) : null, _jsx5("div", { className: "cs-sep" }), _jsxs5("button", { className: "cs-item", type: "button", role: "switch", "aria-checked": !pinsHidden, onClick: togglePins, children: [_jsx5("span", { children: "Show pins" }), _jsx5("span", { className: pinsHidden ? "cs-switch" : "cs-switch is-on" })] }), _jsxs5("button", { className: "cs-item", "data-cs-ui": "mention-emails", type: "button", role: "switch", "aria-checked": profile?.mentionEmails ?? true, disabled: !profile, onClick: () => {
3235
3443
  if (!member || !profile)
3236
3444
  return;
3237
3445
  updateProfile(session, member, { mentionEmails: !profile.mentionEmails }).then((updated) => {
@@ -3240,7 +3448,10 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3240
3448
  setProfile(updated);
3241
3449
  say(updated.mentionEmails ? "Mention emails on" : "Mention emails off");
3242
3450
  }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t update."));
3243
- }, children: [_jsx4("span", { children: "Email me when mentioned" }), _jsx4("span", { className: profile?.mentionEmails ?? true ? "cs-switch is-on" : "cs-switch" })] }), _jsxs4("div", { className: "cs-item is-static", children: [_jsx4("span", { children: "Position" }), _jsx4("span", { className: "cs-segment", role: "radiogroup", "aria-label": "Toolbar position", children: positions.map((option) => _jsx4("button", { type: "button", role: "radio", "aria-checked": barPosition === option.value, className: barPosition === option.value ? "is-on" : "", onClick: () => savePrefs({ ...prefs, position: option.value }), children: option.label }, option.value)) })] }), _jsx4("div", { className: "cs-sep" }), _jsx4("button", { className: "cs-item", "data-cs-ui": "invite", type: "button", disabled: busy, onClick: () => {
3451
+ }, children: [_jsx5("span", { children: "Email me when mentioned" }), _jsx5("span", { className: profile?.mentionEmails ?? true ? "cs-switch is-on" : "cs-switch" })] }), _jsxs5("div", { className: "cs-item is-static", children: [_jsx5("span", { children: "Position" }), _jsx5("span", { className: "cs-segment", role: "radiogroup", "aria-label": "Toolbar position", children: positions.map((option) => _jsx5("button", { type: "button", role: "radio", "aria-checked": barPosition === option.value, className: barPosition === option.value ? "is-on" : "", onClick: () => savePrefs({ ...prefs, position: option.value }), children: option.label }, option.value)) })] }), _jsxs5("button", { className: "cs-item", "data-cs-ui": "settings-open", type: "button", onClick: () => {
3452
+ setError("");
3453
+ setSurface("settings");
3454
+ }, children: [_jsx5("span", { children: "Comment fields" }), _jsx5("span", { className: "cs-hint", children: detailsEnabled(settings) ? "On" : "Off" })] }), _jsx5("div", { className: "cs-sep" }), _jsx5("button", { className: "cs-item", "data-cs-ui": "invite", type: "button", disabled: busy, onClick: () => {
3244
3455
  if (!member)
3245
3456
  return;
3246
3457
  setBusy(true);
@@ -3253,23 +3464,23 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3253
3464
  setInviteLink(link);
3254
3465
  return navigator.clipboard.writeText(link).then(() => say("Invite link copied")).catch(() => say("Invite link ready"));
3255
3466
  }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t create an invite.")).finally(() => setBusy(false));
3256
- }, children: _jsx4("span", { children: "Invite someone" }) }), inviteLink ? _jsxs4("div", { className: "cs-invite", children: [_jsx4("input", { "data-cs-ui": "invite-link", "aria-label": "Invite link", readOnly: true, value: inviteLink, onFocus: (event) => event.currentTarget.select() }), _jsx4("p", { className: "cs-hint", children: "Anyone with this link can join for 7 days. Changing the passphrase cancels it." })] }) : null, _jsx4("button", { className: "cs-item", "data-cs-ui": "lock", type: "button", onClick: lock, children: _jsx4("span", { children: "Lock" }) }), _jsx4("button", { className: "cs-item is-danger", "data-cs-ui": "reset", type: "button", onClick: () => setConfirmReset(true), children: _jsx4("span", { children: "Reset on this device\u2026" }) }), _jsx4("p", { className: "cs-hint", children: "C comment \xB7 I list \xB7 H pins" })] }) });
3257
- const inbox = _jsxs4("section", { className: "cs-card cs-sheet cs-hit", "data-cs-ui": "panel", "aria-label": "Comments", children: [_jsxs4("header", { className: "cs-sheet-head", children: [_jsx4("h2", { children: "Comments" }), _jsx4("button", { className: "cs-icon-button", type: "button", "aria-label": "Close comments", onClick: () => setSurface("none"), children: _jsx4(Close, {}) })] }), _jsx4("div", { className: "cs-tabs", role: "tablist", children: [
3467
+ }, children: _jsx5("span", { children: "Invite someone" }) }), inviteLink ? _jsxs5("div", { className: "cs-invite", children: [_jsx5("input", { "data-cs-ui": "invite-link", "aria-label": "Invite link", readOnly: true, value: inviteLink, onFocus: (event) => event.currentTarget.select() }), _jsx5("p", { className: "cs-hint", children: "Anyone with this link can join for 7 days. Changing the passphrase cancels it." })] }) : null, _jsx5("button", { className: "cs-item", "data-cs-ui": "lock", type: "button", onClick: lock, children: _jsx5("span", { children: "Lock" }) }), _jsx5("button", { className: "cs-item is-danger", "data-cs-ui": "reset", type: "button", onClick: () => setConfirmReset(true), children: _jsx5("span", { children: "Reset on this device\u2026" }) }), _jsx5("p", { className: "cs-hint", children: "C comment \xB7 I list \xB7 H pins" })] }) });
3468
+ const inbox = _jsxs5("section", { className: "cs-card cs-sheet cs-hit", "data-cs-ui": "panel", "aria-label": "Comments", children: [_jsxs5("header", { className: "cs-sheet-head", children: [_jsx5("h2", { children: "Comments" }), _jsx5("button", { className: "cs-icon-button", type: "button", "aria-label": "Close comments", onClick: () => setSurface("none"), children: _jsx5(Close, {}) })] }), _jsx5("div", { className: "cs-tabs", role: "tablist", children: [
3258
3469
  ["page", "This page", counts.page, "scope-page"],
3259
3470
  ["all", "All pages", counts.all, "scope-all"],
3260
3471
  ["resolved", "Resolved", counts.resolved, "scope-resolved"]
3261
- ].map(([value, label, count, hook]) => _jsxs4("button", { type: "button", role: "tab", "aria-selected": tab === value, "data-cs-ui": hook, className: tab === value ? "is-on" : "", onClick: () => setTab(value), children: [label, count > 0 ? _jsx4("span", { className: "cs-count", children: count }) : null] }, value)) }), error ? _jsx4("p", { className: "cs-error cs-pad", "data-cs-ui": "error", children: error }) : null, _jsxs4("div", { className: "cs-list", children: [listed.length === 0 ? _jsxs4("div", { className: "cs-empty", children: [_jsx4("p", { children: tab === "resolved" ? "Nothing resolved yet." : tab === "page" ? "No comments on this page." : "No open comments." }), tab !== "resolved" ? _jsxs4("button", { className: "cs-button", type: "button", onClick: startComment, children: [_jsx4(AddComment, {}), " Add a comment"] }) : null] }) : null, groups.map((group) => _jsxs4("div", { children: [group.route ? _jsx4("div", { className: "cs-group", children: group.route === route ? "This page" : group.route }) : null, group.items.map((item) => {
3472
+ ].map(([value, label, count, hook]) => _jsxs5("button", { type: "button", role: "tab", "aria-selected": tab === value, "data-cs-ui": hook, className: tab === value ? "is-on" : "", onClick: () => setTab(value), children: [label, count > 0 ? _jsx5("span", { className: "cs-count", children: count }) : null] }, value)) }), error ? _jsx5("p", { className: "cs-error cs-pad", "data-cs-ui": "error", children: error }) : null, _jsxs5("div", { className: "cs-list", children: [listed.length === 0 ? _jsxs5("div", { className: "cs-empty", children: [_jsx5("p", { children: tab === "resolved" ? "Nothing resolved yet." : tab === "page" ? "No comments on this page." : "No open comments." }), tab !== "resolved" ? _jsxs5("button", { className: "cs-button", type: "button", onClick: startComment, children: [_jsx5(AddComment, {}), " Add a comment"] }) : null] }) : null, groups.map((group) => _jsxs5("div", { children: [group.route ? _jsx5("div", { className: "cs-group", children: group.route === route ? "This page" : group.route }) : null, group.items.map((item) => {
3262
3473
  const { thread } = item;
3263
3474
  const expanded = activeId === thread.id;
3264
3475
  const first = thread.messages[0];
3265
3476
  const note = statusNote(item.status, thread.anchor, thread.route === route);
3266
3477
  const onPage = item.status === "pinned" || item.status === "drifted";
3267
- return _jsxs4("article", { className: expanded ? "cs-thread is-open" : "cs-thread", "data-cs-ui": "thread", "data-thread-id": thread.id, "data-cs-status": item.status, children: [_jsxs4("div", { className: "cs-thread-main", role: "button", tabIndex: 0, "aria-expanded": expanded, "aria-label": `${thread.createdBy}: ${first?.body ?? ""}`, onClick: () => setActiveId(expanded ? null : thread.id), onKeyDown: (event) => {
3478
+ return _jsxs5("article", { className: expanded ? "cs-thread is-open" : "cs-thread", "data-cs-ui": "thread", "data-thread-id": thread.id, "data-cs-status": item.status, children: [_jsxs5("div", { className: "cs-thread-main", role: "button", tabIndex: 0, "aria-expanded": expanded, "aria-label": `${thread.createdBy}: ${first?.body ?? ""}`, onClick: () => select(item), onKeyDown: (event) => {
3268
3479
  if (event.key === "Enter" || event.key === " ") {
3269
3480
  event.preventDefault();
3270
- setActiveId(expanded ? null : thread.id);
3481
+ select(item);
3271
3482
  }
3272
- }, onMouseEnter: () => setPeekId(null), children: [_jsx4(Avatar, { name: thread.createdBy }), _jsxs4("div", { className: "cs-thread-body", children: [_jsxs4("div", { className: "cs-meta", children: [_jsx4("strong", { children: thread.createdBy }), _jsx4("time", { dateTime: thread.createdAt, children: ago(thread.createdAt) })] }), _jsx4("p", { className: "cs-text", children: _jsx4(MessageText, { body: first?.body ?? "", mentions: first?.mentions }) }), _jsx4(MessageAttachments, { attachments: first?.attachments }), first && deliveryText(deliveries[first.id]) ? _jsx4("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[first.id]) }) : null, _jsxs4("div", { className: "cs-target", children: [_jsx4("span", { title: thread.anchor.component?.file ?? void 0, children: targetName(thread.anchor) }), note ? _jsx4("span", { className: `cs-note is-${note.tone}`, children: note.text }) : null] }), !expanded && thread.messages.length > 1 ? _jsxs4("div", { className: "cs-replies", children: [thread.messages.length - 1, " ", thread.messages.length === 2 ? "reply" : "replies"] }) : null] })] }), expanded ? _jsxs4("div", { className: "cs-thread-more", children: [thread.messages.slice(1).map((message) => _jsxs4("div", { className: "cs-message", children: [_jsx4(Avatar, { name: message.author, size: 20 }), _jsxs4("div", { children: [_jsxs4("div", { className: "cs-meta", children: [_jsx4("strong", { children: message.author }), _jsx4("time", { dateTime: message.createdAt, children: ago(message.createdAt) })] }), _jsx4("p", { className: "cs-text", children: _jsx4(MessageText, { body: message.body, mentions: message.mentions }) }), _jsx4(MessageAttachments, { attachments: message.attachments }), deliveryText(deliveries[message.id]) ? _jsx4("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[message.id]) }) : null] })] }, message.id)), _jsxs4("form", { className: "cs-reply", onSubmit: async (event) => {
3483
+ }, onMouseEnter: () => setPeekId(null), children: [_jsx5(Avatar, { name: thread.createdBy }), _jsxs5("div", { className: "cs-thread-body", children: [_jsxs5("div", { className: "cs-meta", children: [_jsx5("strong", { children: thread.createdBy }), _jsx5("time", { dateTime: thread.createdAt, children: ago(thread.createdAt) }), _jsx5(RoleBadge, { role: thread.role })] }), first?.body ? _jsx5("p", { className: "cs-text", children: _jsx5(MessageText, { body: first.body, mentions: first.mentions }) }) : null, _jsx5(FieldValues, { fields: thread.fields, clamp: !expanded }), _jsx5(MessageAttachments, { attachments: first?.attachments }), first && deliveryText(deliveries[first.id]) ? _jsx5("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[first.id]) }) : null, _jsxs5("div", { className: "cs-target", children: [_jsx5("span", { title: thread.anchor.component?.file ?? void 0, children: targetName(thread.anchor) }), note ? _jsx5("span", { className: `cs-note is-${note.tone}`, children: note.text }) : null] }), !expanded && thread.messages.length > 1 ? _jsxs5("div", { className: "cs-replies", children: [thread.messages.length - 1, " ", thread.messages.length === 2 ? "reply" : "replies"] }) : null] })] }), expanded ? _jsxs5("div", { className: "cs-thread-more", children: [editingId === thread.id && settings ? _jsx5(DetailsEditor, { settings, thread, onClose: () => setEditingId(null), onSave: (patch) => updateThread(session, member, thread.id, patch).then(() => refresh()).then(() => say("Details saved")).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t save.")) }) : null, thread.messages.slice(1).map((message) => _jsxs5("div", { className: "cs-message", children: [_jsx5(Avatar, { name: message.author, size: 20 }), _jsxs5("div", { children: [_jsxs5("div", { className: "cs-meta", children: [_jsx5("strong", { children: message.author }), _jsx5("time", { dateTime: message.createdAt, children: ago(message.createdAt) })] }), _jsx5("p", { className: "cs-text", children: _jsx5(MessageText, { body: message.body, mentions: message.mentions }) }), _jsx5(MessageAttachments, { attachments: message.attachments }), deliveryText(deliveries[message.id]) ? _jsx5("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[message.id]) }) : null] })] }, message.id)), _jsxs5("form", { className: "cs-reply", onSubmit: async (event) => {
3273
3484
  event.preventDefault();
3274
3485
  if (!member || !reply.trim() && !replyFiles.ids.length || replyFiles.uploading)
3275
3486
  return;
@@ -3295,7 +3506,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3295
3506
  }
3296
3507
  await refresh();
3297
3508
  }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t reply.")).finally(() => setBusy(false));
3298
- }, children: [_jsx4(MentionTextarea, { "data-cs-ui": "reply-body", "aria-label": "Reply", placeholder: "Reply\u2026 (@ to mention)", rows: 1, value: reply, onValueChange: setReply, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3509
+ }, children: [_jsx5(MentionTextarea, { "data-cs-ui": "reply-body", "aria-label": "Reply", placeholder: "Reply\u2026 (@ to mention)", rows: 1, value: reply, onValueChange: setReply, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3299
3510
  replyPicked.current.set(person.id, person);
3300
3511
  const element = thread.route === route ? resolveInDocument(thread.anchor, route).element : null;
3301
3512
  if (element && !replyShot.current)
@@ -3306,25 +3517,25 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3306
3517
  event.preventDefault();
3307
3518
  replyFiles.add(files);
3308
3519
  }
3309
- } }), _jsx4(AttachButtons, { onFiles: replyFiles.add, capturing, onSnapshot: () => snapshot(thread.route === route ? resolveInDocument(thread.anchor, route).element : null, replyFiles.add) }), _jsx4("button", { className: "cs-send", "data-cs-ui": "reply-submit", type: "submit", "aria-label": "Post reply", disabled: busy || replyFiles.uploading || !reply.trim() && !replyFiles.ids.length, children: _jsx4(Send, {}) })] }), _jsx4(AttachmentTray, { items: replyFiles.items, onRemove: replyFiles.remove }), _jsxs4("div", { className: "cs-actions", children: [_jsxs4("button", { className: "cs-button", "data-cs-ui": "resolve", type: "button", onClick: () => {
3520
+ } }), _jsx5(AttachButtons, { onFiles: replyFiles.add, capturing, onSnapshot: () => snapshot(thread.route === route ? resolveInDocument(thread.anchor, route).element : null, replyFiles.add) }), _jsx5("button", { className: "cs-send", "data-cs-ui": "reply-submit", type: "submit", "aria-label": "Post reply", disabled: busy || replyFiles.uploading || !reply.trim() && !replyFiles.ids.length, children: _jsx5(Send, {}) })] }), _jsx5(AttachmentTray, { items: replyFiles.items, onRemove: replyFiles.remove }), _jsxs5("div", { className: "cs-actions", children: [_jsxs5("button", { className: "cs-button", "data-cs-ui": "resolve", type: "button", onClick: () => {
3310
3521
  if (!member)
3311
3522
  return;
3312
3523
  const next = thread.status === "resolved" ? "open" : "resolved";
3313
3524
  updateThread(session, member, thread.id, { status: next }).then(() => refresh()).then(() => say(next === "resolved" ? "Resolved" : "Reopened")).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t update."));
3314
- }, children: [thread.status === "resolved" ? _jsx4(Undo, {}) : _jsx4(Check, {}), thread.status === "resolved" ? "Reopen" : "Resolve"] }), onPage && thread.status === "open" ? _jsxs4("button", { className: "cs-button", type: "button", onClick: () => showOnPage(thread), children: [_jsx4(Target, {}), " Show"] }) : null, !onPage && thread.status === "open" && thread.route === route ? _jsxs4("button", { className: "cs-button", "data-cs-ui": "reattach", type: "button", onClick: () => {
3525
+ }, children: [thread.status === "resolved" ? _jsx5(Undo, {}) : _jsx5(Check, {}), thread.status === "resolved" ? "Reopen" : "Resolve"] }), onPage && thread.status === "open" ? _jsxs5("button", { className: "cs-button", type: "button", onClick: () => showOnPage(thread), children: [_jsx5(Target, {}), " Show"] }) : null, detailsEnabled(settings) && editingId !== thread.id ? _jsxs5("button", { className: "cs-button", "data-cs-ui": "details-edit", type: "button", onClick: () => setEditingId(thread.id), children: [_jsx5(Pencil, {}), " Details"] }) : null, !onPage && thread.status === "open" && thread.route === route ? _jsxs5("button", { className: "cs-button", "data-cs-ui": "reattach", type: "button", onClick: () => {
3315
3526
  setReattachId(thread.id);
3316
3527
  setMode("reattach");
3317
3528
  setSurface("none");
3318
- }, children: [_jsx4(Relink, {}), " Reattach"] }) : null] })] }) : null] }, thread.id);
3529
+ }, children: [_jsx5(Relink, {}), " Reattach"] }) : null] })] }) : null] }, thread.id);
3319
3530
  })] }, group.route ?? "all"))] })] });
3320
- const ui = _jsxs4("div", { id: "cs-root", className: [
3531
+ const ui = _jsxs5("div", { id: "cs-root", className: [
3321
3532
  inside ? "cs-inside" : "cs-fixed",
3322
3533
  `cs-at-${barPosition.replace("bottom-", "")}`,
3323
3534
  theme === "dark" || theme === "system" && systemDark ? "cs-dark" : ""
3324
- ].join(" "), children: [highlight ? _jsxs4(_Fragment2, { children: [_jsx4("div", { className: "cs-outline", style: { top: highlight.top, left: highlight.left, width: highlight.width, height: highlight.height } }), _jsx4("div", { className: "cs-outline-label", style: { top: Math.max(4, highlight.top - 26), left: highlight.left }, children: highlight.label })] }) : null, flash ? _jsx4("div", { className: "cs-outline is-flash", style: { top: flash.top, left: flash.left, width: flash.width, height: flash.height } }) : null, mode !== "browse" ? _jsxs4("div", { className: "cs-banner cs-hit", "data-cs-ui": "mode", children: [_jsx4(Cursor, {}), _jsx4("span", { children: mode === "reattach" ? "Click the element this comment belongs to" : "Click anything to comment" }), _jsx4("button", { type: "button", onClick: () => {
3535
+ ].join(" "), children: [highlight ? _jsxs5(_Fragment3, { children: [_jsx5("div", { className: "cs-outline", style: { top: highlight.top, left: highlight.left, width: highlight.width, height: highlight.height } }), _jsx5("div", { className: "cs-outline-label", style: { top: Math.max(4, highlight.top - 26), left: highlight.left }, children: highlight.label })] }) : null, flash ? _jsx5("div", { className: "cs-outline is-flash", style: { top: flash.top, left: flash.left, width: flash.width, height: flash.height } }) : null, mode !== "browse" ? _jsxs5("div", { className: "cs-banner cs-hit", "data-cs-ui": "mode", children: [_jsx5(Cursor, {}), _jsx5("span", { children: mode === "reattach" ? "Click the element this comment belongs to" : "Click anything to comment" }), _jsx5("button", { type: "button", onClick: () => {
3325
3536
  setMode("browse");
3326
3537
  setReattachId(null);
3327
- }, children: "Cancel" })] }) : null, pinsHidden ? null : placed.map((item) => item.top == null || item.left == null || item.status !== "pinned" && item.status !== "drifted" ? null : _jsxs4("button", { type: "button", className: [
3538
+ }, children: "Cancel" })] }) : null, pinsHidden ? null : placed.map((item) => item.top == null || item.left == null || item.status !== "pinned" && item.status !== "drifted" ? null : _jsxs5("button", { type: "button", className: [
3328
3539
  "cs-pin",
3329
3540
  item.status === "drifted" ? "is-drifted" : "",
3330
3541
  activeId === item.thread.id && surface === "inbox" ? "is-active" : ""
@@ -3333,9 +3544,9 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3333
3544
  setTab("page");
3334
3545
  setActiveId(item.thread.id);
3335
3546
  setSurface("inbox");
3336
- }, children: [_jsx4(Avatar, { name: item.thread.createdBy, size: 22 }), item.thread.messages.length > 1 ? _jsx4("span", { className: "cs-pin-count", children: item.thread.messages.length }) : null] }, item.thread.id)), peek && !pinsHidden && peek.top != null && peek.left != null ? _jsxs4("div", { className: "cs-peek", style: { top: peek.top + 18, left: Math.max(8, peek.left - 12) }, children: [_jsxs4("div", { className: "cs-meta", children: [_jsx4(Avatar, { name: peek.thread.createdBy, size: 18 }), _jsx4("strong", { children: peek.thread.createdBy }), _jsx4("time", { children: ago(peek.thread.createdAt) })] }), _jsx4("p", { className: "cs-text", children: _jsx4(MessageText, { body: peek.thread.messages[0]?.body ?? "", mentions: peek.thread.messages[0]?.mentions }) }), peek.thread.messages.length > 1 ? _jsxs4("div", { className: "cs-replies", children: [peek.thread.messages.length - 1, " ", peek.thread.messages.length === 2 ? "reply" : "replies"] }) : null] }) : null, draft ? _jsxs4("form", { className: "cs-card cs-composer cs-hit", "data-cs-ui": "composer", style: { top: draft.top, left: draft.left }, onSubmit: async (event) => {
3547
+ }, children: [_jsx5(Avatar, { name: item.thread.createdBy, size: 22 }), item.thread.messages.length > 1 ? _jsx5("span", { className: "cs-pin-count", children: item.thread.messages.length }) : null] }, item.thread.id)), peek && !pinsHidden && peek.top != null && peek.left != null ? _jsxs5("div", { className: "cs-peek", style: { top: peek.top + 18, left: Math.max(8, peek.left - 12) }, children: [_jsxs5("div", { className: "cs-meta", children: [_jsx5(Avatar, { name: peek.thread.createdBy, size: 18 }), _jsx5("strong", { children: peek.thread.createdBy }), _jsx5("time", { children: ago(peek.thread.createdAt) })] }), _jsx5("p", { className: "cs-text", children: _jsx5(MessageText, { body: peek.thread.messages[0]?.body ?? "", mentions: peek.thread.messages[0]?.mentions }) }), peek.thread.messages.length > 1 ? _jsxs5("div", { className: "cs-replies", children: [peek.thread.messages.length - 1, " ", peek.thread.messages.length === 2 ? "reply" : "replies"] }) : null] }) : null, draft ? _jsxs5("form", { className: "cs-card cs-composer cs-hit", "data-cs-ui": "composer", style: { top: draft.top, left: draft.left }, onSubmit: async (event) => {
3337
3548
  event.preventDefault();
3338
- if (!member || !body.trim() && !draftFiles.ids.length || draftFiles.uploading)
3549
+ if (!member || !canPost || draftFiles.uploading)
3339
3550
  return;
3340
3551
  setBusy(true);
3341
3552
  const picked = mentionsIn(body, draftPicked.current);
@@ -3343,10 +3554,13 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3343
3554
  createThread(session, member, draft.anchor, body, {
3344
3555
  mentions: picked.map((person) => person.id),
3345
3556
  screenshot,
3346
- attachments: draftFiles.ids
3557
+ attachments: draftFiles.ids,
3558
+ url: pageHref(),
3559
+ ...detailsPayload(settings, draftDetails)
3347
3560
  }).then(async ({ thread, delivery }) => {
3348
3561
  setDraft(null);
3349
3562
  setBody("");
3563
+ setDraftDetails(noDetails);
3350
3564
  draftFiles.reset();
3351
3565
  setError("");
3352
3566
  draftPicked.current = /* @__PURE__ */ new Map();
@@ -3364,7 +3578,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3364
3578
  setSurface("inbox");
3365
3579
  await refresh();
3366
3580
  }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t post.")).finally(() => setBusy(false));
3367
- }, children: [_jsxs4("div", { className: "cs-composer-target", children: [_jsx4(Target, {}), _jsx4("span", { className: "cs-composer-name", children: targetName(draft.anchor) }), draft.anchor.component?.file ? _jsx4("code", { children: draft.anchor.component.file }) : null, _jsx4("button", { className: "cs-icon-button", type: "button", "aria-label": "Discard", onClick: () => setDraft(null), children: _jsx4(Close, {}) })] }), _jsxs4("div", { className: "cs-composer-body", children: [_jsx4(Avatar, { name: memberName, size: 24 }), _jsx4(MentionTextarea, { "data-cs-ui": "composer-body", "aria-label": "Comment", placeholder: "Add a comment\u2026 (@ to mention)", value: body, autoFocus: true, onValueChange: setBody, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3581
+ }, children: [_jsxs5("div", { className: "cs-composer-target", children: [_jsx5(Target, {}), _jsx5("span", { className: "cs-composer-name", children: targetName(draft.anchor) }), draft.anchor.component?.file ? _jsx5("code", { children: draft.anchor.component.file }) : null, _jsx5("button", { className: "cs-icon-button", type: "button", "aria-label": "Discard", onClick: () => setDraft(null), children: _jsx5(Close, {}) })] }), _jsxs5("div", { className: "cs-composer-body", children: [_jsx5(Avatar, { name: memberName, size: 24 }), _jsx5(MentionTextarea, { "data-cs-ui": "composer-body", "aria-label": "Comment", placeholder: "Add a comment\u2026 (@ to mention)", value: body, autoFocus: true, onValueChange: setBody, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3368
3582
  draftPicked.current.set(person.id, person);
3369
3583
  if (!draftShot.current)
3370
3584
  draftShot.current = captureAround(draft.element);
@@ -3374,7 +3588,7 @@ function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs =
3374
3588
  event.preventDefault();
3375
3589
  draftFiles.add(files);
3376
3590
  }
3377
- } })] }), _jsx4(AttachmentTray, { items: draftFiles.items, onRemove: draftFiles.remove }), error ? _jsx4("p", { className: "cs-error cs-pad", children: error }) : null, _jsxs4("div", { className: "cs-composer-foot", children: [_jsx4(AttachButtons, { onFiles: draftFiles.add, capturing, onSnapshot: () => snapshot(draft.element, draftFiles.add) }), _jsx4("span", { className: "cs-hint", children: capturing ? "Capturing\u2026" : draftFiles.uploading ? "Uploading\u2026" : busy ? "Posting\u2026" : "\u2318\u21B5 to post" }), _jsx4("button", { className: "cs-send", "data-cs-ui": "composer-submit", type: "submit", "aria-label": "Post comment", disabled: busy || draftFiles.uploading || !body.trim() && !draftFiles.ids.length, children: _jsx4(Send, {}) })] })] }) : null, surface === "unlock" && !member ? unlockCard : null, surface === "menu" && member ? menuCard : null, surface === "inbox" && member ? inbox : null, toast ? _jsx4("div", { className: "cs-toast", role: "status", children: toast }) : null, bar] });
3591
+ } })] }), _jsx5(DetailsInput, { settings, value: draftDetails, onChange: setDraftDetails, onKeyDown: submitOnCmdEnter }), _jsx5(AttachmentTray, { items: draftFiles.items, onRemove: draftFiles.remove }), error ? _jsx5("p", { className: "cs-error cs-pad", children: error }) : null, _jsxs5("div", { className: "cs-composer-foot", children: [_jsx5(AttachButtons, { onFiles: draftFiles.add, capturing, onSnapshot: () => snapshot(draft.element, draftFiles.add) }), _jsx5("span", { className: "cs-hint", children: capturing ? "Capturing\u2026" : draftFiles.uploading ? "Uploading\u2026" : busy ? "Posting\u2026" : "\u2318\u21B5 to post" }), _jsx5("button", { className: "cs-send", "data-cs-ui": "composer-submit", type: "submit", "aria-label": "Post comment", disabled: busy || draftFiles.uploading || !canPost, children: _jsx5(Send, {}) })] })] }) : null, surface === "unlock" && !member ? unlockCard : null, surface === "menu" && member ? menuCard : null, surface === "settings" && member ? _jsx5(SettingsPanel, { settings, onChange: changeSettings, onBack: () => setSurface("menu"), error }) : null, surface === "inbox" && member ? inbox : null, toast ? _jsx5("div", { className: "cs-toast", role: "status", children: toast }) : null, bar] });
3378
3592
  return createPortal2(ui, host);
3379
3593
  }
3380
3594
  export {
package/dist/server.js CHANGED
@@ -8,7 +8,21 @@ function readCommentServiceConfig(env = process.env) {
8
8
  return null;
9
9
  return { url, project, environment, secret };
10
10
  }
11
+ function declaredOrigin(request) {
12
+ const value = request.headers.get("origin") ?? request.headers.get("x-comment-service-origin");
13
+ if (!value)
14
+ return null;
15
+ try {
16
+ const origin = new URL(value).origin;
17
+ return origin === "null" ? null : origin;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
11
22
  function requestOrigin(request) {
23
+ const declared = declaredOrigin(request);
24
+ if (declared)
25
+ return declared;
12
26
  const url = new URL(request.url);
13
27
  const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
14
28
  if (!host || host.includes("/"))
@@ -47,9 +47,35 @@ export type PostExtras = {
47
47
  screenshot?: string | null;
48
48
  attachments?: string[];
49
49
  };
50
+ export type FieldDef = {
51
+ id: string;
52
+ label: string;
53
+ };
54
+ export type FieldValue = FieldDef & {
55
+ value: string;
56
+ };
57
+ export type Settings = {
58
+ roles: {
59
+ enabled: boolean;
60
+ options: string[];
61
+ };
62
+ fields: {
63
+ enabled: boolean;
64
+ items: FieldDef[];
65
+ };
66
+ };
67
+ /** Role and custom field values for a new or edited comment, keyed by field id. */
68
+ export type Details = {
69
+ role: string | null;
70
+ fields: Record<string, string>;
71
+ };
50
72
  export type Thread = {
51
73
  id: string;
52
74
  route: string;
75
+ /** The page the comment was left on, including query and hash. */
76
+ url: string;
77
+ role: string | null;
78
+ fields: FieldValue[];
53
79
  anchor: Anchor;
54
80
  status: "open" | "resolved";
55
81
  createdBy: string;
@@ -97,8 +123,14 @@ export declare function updateProfile(session: Session, memberToken: string, pat
97
123
  }>;
98
124
  export declare function uploadAttachment(session: Session, memberToken: string, file: Blob, name: string): Promise<Attachment>;
99
125
  export declare function listMembers(session: Session, memberToken: string): Promise<Member[]>;
100
- export declare function listThreads(session: Session, memberToken: string): Promise<Thread[]>;
101
- export declare function createThread(session: Session, memberToken: string, anchor: Anchor, body: string, extras?: PostExtras): Promise<{
126
+ export declare function listThreads(session: Session, memberToken: string): Promise<{
127
+ threads: Thread[];
128
+ settings: Settings;
129
+ }>;
130
+ export declare function saveSettings(session: Session, memberToken: string, settings: Settings): Promise<Settings>;
131
+ export declare function createThread(session: Session, memberToken: string, anchor: Anchor, body: string, extras?: PostExtras & Partial<Details> & {
132
+ url?: string;
133
+ }): Promise<{
102
134
  thread: Thread;
103
135
  delivery?: Delivery;
104
136
  }>;
@@ -109,6 +141,7 @@ export declare function replyToThread(session: Session, memberToken: string, thr
109
141
  export declare function updateThread(session: Session, memberToken: string, threadId: string, patch: {
110
142
  status?: "open" | "resolved";
111
143
  anchor?: Anchor;
112
- }): Promise<{
144
+ url?: string;
145
+ } & Partial<Details>): Promise<{
113
146
  id: string;
114
147
  }>;
@@ -0,0 +1,34 @@
1
+ import { type KeyboardEvent as ReactKeyboardEvent } from "react";
2
+ import type { Details, FieldValue, Settings, Thread } from "./client.js";
3
+ export declare const noDetails: Details;
4
+ export declare function detailsEnabled(settings: Settings | null): boolean;
5
+ export declare function hasFieldValues(details: Details): boolean;
6
+ /** Only sends what is turned on, so a disabled feature never rejects a post. */
7
+ export declare function detailsPayload(settings: Settings | null, details: Details): Partial<Details>;
8
+ export declare function DetailsInput({ settings, value, onChange, onKeyDown, }: {
9
+ settings: Settings | null;
10
+ value: Details;
11
+ onChange: (next: Details) => void;
12
+ onKeyDown?: (event: ReactKeyboardEvent<HTMLTextAreaElement>) => void;
13
+ }): import("react").JSX.Element | null;
14
+ export declare function RoleBadge({ role }: {
15
+ role: string | null;
16
+ }): import("react").JSX.Element | null;
17
+ export declare function FieldValues({ fields, clamp }: {
18
+ fields: FieldValue[];
19
+ clamp?: boolean;
20
+ }): import("react").JSX.Element | null;
21
+ /** Inline editor for a posted comment's role and fields. Values for fields that were since removed are kept by the server. */
22
+ export declare function DetailsEditor({ settings, thread, onSave, onClose, }: {
23
+ settings: Settings;
24
+ thread: Thread;
25
+ onSave: (patch: Partial<Details>) => Promise<void>;
26
+ onClose: () => void;
27
+ }): import("react").JSX.Element;
28
+ /** Shared per environment: anyone signed in can change it, and existing comments keep their values. */
29
+ export declare function SettingsPanel({ settings, onChange, onBack, error, }: {
30
+ settings: Settings | null;
31
+ onChange: (next: Settings) => void;
32
+ onBack: () => void;
33
+ error: string;
34
+ }): import("react").JSX.Element;
@@ -16,3 +16,4 @@ export declare const Cursor: () => import("react").JSX.Element;
16
16
  export declare const Paperclip: () => import("react").JSX.Element;
17
17
  export declare const Camera: () => import("react").JSX.Element;
18
18
  export declare const FileIcon: () => import("react").JSX.Element;
19
+ export declare const Back: () => import("react").JSX.Element;
@@ -1 +1 @@
1
- export declare const toolbarCss = "\n#cs-root {\n --cs-text: #1d1d1f;\n --cs-text-2: #6e6e73;\n --cs-text-3: #a1a1a6;\n --cs-glass: rgba(251, 251, 253, 0.8);\n --cs-card: rgba(255, 255, 255, 0.94);\n --cs-fill: rgba(0, 0, 0, 0.05);\n --cs-fill-2: rgba(0, 0, 0, 0.08);\n --cs-line: rgba(0, 0, 0, 0.08);\n --cs-edge: rgba(0, 0, 0, 0.1);\n --cs-accent: #007aff;\n --cs-accent-soft: rgba(0, 122, 255, 0.12);\n --cs-danger: #ff3b30;\n --cs-warn: #ff9500;\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n pointer-events: none;\n margin: 0;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--cs-text);\n font: 13px/1.4 -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\", Inter, system-ui, sans-serif;\n letter-spacing: -0.005em;\n -webkit-font-smoothing: antialiased;\n text-align: left;\n}\n#cs-root.cs-dark {\n --cs-text: #f5f5f7;\n --cs-text-2: #a1a1a6;\n --cs-text-3: #6e6e73;\n --cs-glass: rgba(40, 40, 44, 0.78);\n --cs-card: rgba(36, 36, 40, 0.96);\n --cs-fill: rgba(255, 255, 255, 0.07);\n --cs-fill-2: rgba(255, 255, 255, 0.12);\n --cs-line: rgba(255, 255, 255, 0.08);\n --cs-edge: rgba(255, 255, 255, 0.12);\n --cs-accent: #0a84ff;\n --cs-accent-soft: rgba(10, 132, 255, 0.2);\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.45);\n}\n#cs-root.cs-inside { position: absolute; }\n#cs-root, #cs-root * { box-sizing: border-box; }\n#cs-root h2, #cs-root p { margin: 0; }\n#cs-root button, #cs-root input, #cs-root textarea {\n font: inherit; color: inherit; letter-spacing: inherit; margin: 0;\n}\n#cs-root button { background: none; border: 0; padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }\n#cs-root button:disabled { cursor: default; opacity: 0.45; }\n#cs-root button:focus-visible, #cs-root [role=button]:focus-visible { outline: 2px solid var(--cs-accent); outline-offset: 2px; }\n#cs-root .cs-hit { pointer-events: auto; }\n#cs-root .cs-icon { display: block; flex: none; }\n\n/* Bar */\n#cs-root .cs-bar {\n position: absolute;\n bottom: 16px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n border-radius: 999px;\n background: var(--cs-glass);\n -webkit-backdrop-filter: saturate(180%) blur(20px);\n backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow);\n}\n#cs-root.cs-at-center .cs-bar { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-bar { left: 16px; }\n#cs-root.cs-at-right .cs-bar { right: 16px; }\n#cs-root .cs-tool {\n position: relative;\n width: 34px;\n height: 34px;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--cs-text);\n transition: background-color 0.15s ease, color 0.15s ease;\n}\n#cs-root .cs-tool:hover { background: var(--cs-fill); }\n#cs-root .cs-tool.is-on { background: var(--cs-fill-2); }\n#cs-root .cs-tool.is-accent { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-tool[data-tip]::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 10px);\n left: 50%;\n transform: translate(-50%, 2px);\n white-space: pre;\n padding: 4px 8px;\n border-radius: 7px;\n background: rgba(29, 29, 31, 0.92);\n color: #fff;\n font-size: 11.5px;\n font-weight: 500;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease, transform 0.12s ease;\n}\n#cs-root .cs-tool[data-tip]:hover::after { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.35s; }\n#cs-root .cs-me { width: 34px; }\n#cs-root .cs-divider { width: 1px; height: 18px; background: var(--cs-line); margin: 0 3px; }\n#cs-root .cs-badge {\n position: absolute;\n top: 3px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--cs-accent);\n color: #fff;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n box-shadow: 0 0 0 2px var(--cs-glass);\n}\n#cs-root .cs-bar-lock {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n height: 34px;\n padding: 0 14px 0 12px;\n border-radius: 999px;\n font-weight: 500;\n}\n#cs-root .cs-bar-lock:hover { background: var(--cs-fill); }\n#cs-root .cs-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 999px;\n color: #fff;\n font-weight: 600;\n letter-spacing: 0;\n line-height: 1;\n user-select: none;\n}\n\n/* Cards */\n#cs-root .cs-card {\n position: absolute;\n background: var(--cs-card);\n -webkit-backdrop-filter: saturate(180%) blur(24px);\n backdrop-filter: saturate(180%) blur(24px);\n border-radius: 14px;\n box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-unlock, #cs-root .cs-menu { bottom: 64px; }\n#cs-root.cs-at-center .cs-unlock, #cs-root.cs-at-center .cs-menu { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-unlock, #cs-root.cs-at-left .cs-menu { left: 16px; }\n#cs-root.cs-at-right .cs-unlock, #cs-root.cs-at-right .cs-menu { right: 16px; }\n#cs-root .cs-unlock { width: 280px; padding: 20px; text-align: center; }\n#cs-root .cs-unlock-icon {\n width: 40px; height: 40px; margin: 0 auto 10px; border-radius: 12px;\n display: flex; align-items: center; justify-content: center; background: var(--cs-fill); color: var(--cs-text-2);\n}\n#cs-root .cs-unlock h2 { font-size: 15px; font-weight: 600; }\n#cs-root .cs-sub { color: var(--cs-text-2); margin-top: 4px; }\n#cs-root .cs-unlock form { margin-top: 14px; display: grid; gap: 8px; text-align: left; }\n#cs-root .cs-field { display: grid; gap: 4px; }\n#cs-root .cs-field span { font-size: 12px; color: var(--cs-text-2); padding-left: 2px; }\n#cs-root input, #cs-root textarea {\n width: 100%;\n border: 0;\n border-radius: 9px;\n background: var(--cs-fill);\n padding: 8px 10px;\n outline: none;\n transition: box-shadow 0.15s ease, background-color 0.15s ease;\n}\n#cs-root input:focus, #cs-root textarea:focus { background: transparent; box-shadow: 0 0 0 1px var(--cs-accent), 0 0 0 4px var(--cs-accent-soft); }\n#cs-root textarea { resize: none; }\n#cs-root .cs-error { color: var(--cs-danger); font-size: 12px; }\n#cs-root .cs-pad { padding: 6px 14px 0; }\n#cs-root .cs-hint { color: var(--cs-text-3); font-size: 11.5px; }\n\n/* Buttons */\n#cs-root .cs-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n height: 28px;\n padding: 0 11px;\n border-radius: 8px;\n background: var(--cs-fill);\n font-size: 12.5px;\n font-weight: 500;\n transition: background-color 0.15s ease;\n}\n#cs-root .cs-button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-button.is-primary { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-button.is-primary:hover { filter: brightness(1.06); }\n#cs-root .cs-button.is-danger { background: var(--cs-danger); color: #fff; }\n#cs-root .cs-button.is-wide { width: 100%; height: 32px; margin-top: 4px; }\n#cs-root .cs-icon-button {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-2);\n}\n#cs-root .cs-icon-button:hover { background: var(--cs-fill); color: var(--cs-text); }\n#cs-root .cs-send {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; background: var(--cs-accent); color: #fff;\n}\n#cs-root .cs-send:disabled { background: var(--cs-fill-2); color: var(--cs-text-3); opacity: 1; }\n\n/* Menu */\n#cs-root .cs-menu { width: 264px; padding: 6px; }\n#cs-root .cs-me-row { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }\n#cs-root .cs-me-name { display: flex; align-items: center; gap: 4px; min-width: 0; }\n#cs-root .cs-me-name strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-rename { display: flex; align-items: center; gap: 4px; flex: 1; }\n#cs-root .cs-rename input { padding: 5px 8px; }\n#cs-root .cs-sep { height: 1px; background: var(--cs-line); margin: 4px 8px; }\n#cs-root .cs-item {\n display: flex; align-items: center; justify-content: space-between; gap: 10px;\n width: 100%; min-height: 32px; padding: 0 8px; border-radius: 8px; text-align: left;\n}\n#cs-root button.cs-item:hover { background: var(--cs-fill); }\n#cs-root .cs-item.is-danger { color: var(--cs-danger); }\n#cs-root .cs-switch { position: relative; width: 32px; height: 20px; border-radius: 999px; background: var(--cs-fill-2); transition: background-color 0.2s ease; flex: none; }\n#cs-root .cs-switch::after {\n content: \"\"; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 999px;\n background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); transition: transform 0.2s ease;\n}\n#cs-root .cs-switch.is-on { background: #34c759; }\n#cs-root .cs-switch.is-on::after { transform: translateX(12px); }\n#cs-root .cs-segment { display: inline-flex; padding: 2px; border-radius: 8px; background: var(--cs-fill); }\n#cs-root .cs-segment button { padding: 2px 8px; border-radius: 6px; font-size: 12px; color: var(--cs-text-2); }\n#cs-root .cs-segment button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); font-weight: 500; }\n#cs-root .cs-menu .cs-hint { padding: 6px 8px 4px; }\n#cs-root .cs-invite { padding: 2px 8px 6px; }\n#cs-root .cs-invite input { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; padding: 6px 8px; }\n#cs-root .cs-invite .cs-hint { padding: 4px 0 0; }\n#cs-root .cs-confirm { padding: 10px; }\n#cs-root .cs-confirm h2 { font-size: 14px; font-weight: 600; }\n#cs-root .cs-row-end { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; }\n\n/* Sheet */\n#cs-root .cs-sheet {\n top: 12px; right: 12px; max-height: calc(100% - 88px); width: min(360px, calc(100vw - 24px));\n display: flex; flex-direction: column; overflow: hidden;\n}\n#cs-root .cs-sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 10px 4px 16px; }\n#cs-root .cs-sheet-head h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }\n#cs-root .cs-tabs { display: flex; gap: 2px; margin: 4px 12px 6px; padding: 2px; border-radius: 9px; background: var(--cs-fill); }\n#cs-root .cs-tabs button {\n flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;\n height: 26px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--cs-text-2);\n}\n#cs-root .cs-tabs button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }\n#cs-root .cs-count { font-size: 11px; color: var(--cs-text-3); font-variant-numeric: tabular-nums; }\n#cs-root .cs-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 6px 8px; overscroll-behavior: contain; }\n#cs-root .cs-group { padding: 10px 10px 4px; font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.04em; }\n#cs-root .cs-empty { padding: 36px 16px; text-align: center; color: var(--cs-text-2); display: grid; gap: 12px; justify-items: center; }\n#cs-root .cs-thread { border-radius: 10px; }\n#cs-root .cs-thread + .cs-thread { margin-top: 1px; }\n#cs-root .cs-thread.is-open { background: var(--cs-fill); }\n#cs-root .cs-thread-main { display: flex; gap: 10px; padding: 10px; border-radius: 10px; cursor: pointer; }\n#cs-root .cs-thread:not(.is-open) .cs-thread-main:hover { background: var(--cs-fill); }\n#cs-root .cs-thread-body { min-width: 0; flex: 1; }\n#cs-root .cs-meta { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }\n#cs-root .cs-meta strong { font-weight: 600; }\n#cs-root .cs-meta time { color: var(--cs-text-3); font-size: 12px; }\n#cs-root .cs-text { margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-target { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); }\n#cs-root .cs-note { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }\n#cs-root .cs-note::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: currentColor; }\n#cs-root .cs-note.is-quiet { color: var(--cs-text-2); }\n#cs-root .cs-note.is-warn { color: var(--cs-warn); }\n#cs-root .cs-note.is-bad { color: var(--cs-danger); }\n#cs-root .cs-replies { margin-top: 4px; font-size: 12px; color: var(--cs-accent); font-weight: 500; }\n#cs-root .cs-thread-more { padding: 0 10px 10px 44px; display: grid; gap: 10px; }\n#cs-root .cs-message { display: flex; gap: 8px; }\n#cs-root .cs-reply { display: flex; align-items: flex-end; gap: 6px; padding: 4px 4px 4px 0; border-radius: 10px; }\n#cs-root .cs-reply textarea { min-height: 32px; max-height: 120px; field-sizing: content; }\n#cs-root .cs-thread.is-open .cs-reply textarea { background: var(--cs-card); }\n#cs-root .cs-actions { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thread.is-open .cs-button { background: var(--cs-card); box-shadow: 0 0 0 0.5px var(--cs-edge); }\n#cs-root .cs-thread.is-open .cs-button:hover { background: var(--cs-fill-2); }\n\n/* Mentions */\n#cs-root .cs-mention { color: var(--cs-accent); background: var(--cs-accent-soft); border-radius: 4px; padding: 0 3px; font-weight: 500; }\n#cs-root .cs-delivery { margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); display: flex; align-items: center; gap: 5px; }\n#cs-root .cs-delivery::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: #34c759; }\n#cs-root .cs-people {\n position: fixed; z-index: 1; width: 264px; padding: 5px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: auto;\n}\n#cs-root .cs-person { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px; cursor: pointer; }\n#cs-root .cs-person.is-on { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-person-text { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-person-text strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person-text small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person.is-on small { color: rgba(255, 255, 255, 0.8); }\n#cs-root .cs-field small { font-size: 11.5px; color: var(--cs-text-3); padding-left: 2px; }\n#cs-root .cs-me-lines { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-me-lines small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* Composer */\n#cs-root .cs-composer { width: min(320px, calc(100% - 16px)); }\n#cs-root .cs-composer-target {\n display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 12px;\n border-bottom: 1px solid var(--cs-line); font-size: 12px; color: var(--cs-text-2);\n}\n#cs-root .cs-composer-name { font-weight: 500; color: var(--cs-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n#cs-root .cs-composer-target code {\n margin-left: auto; font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--cs-text-3);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; min-width: 0; max-width: 45%;\n}\n#cs-root .cs-composer-body { display: flex; gap: 10px; padding: 12px 12px 4px; }\n#cs-root .cs-composer-body textarea { background: transparent; padding: 3px 0; min-height: 56px; box-shadow: none; border-radius: 0; }\n#cs-root .cs-composer-foot { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px 40px; }\n#cs-root .cs-composer-foot .cs-hint { margin-left: auto; }\n\n/* Attachments */\n#cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }\n#cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 12px 2px 46px; }\n#cs-root .cs-thread-more .cs-tray { padding: 0; }\n#cs-root .cs-pending {\n position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 170px; height: 34px;\n padding: 0 24px 0 4px; border-radius: 8px; background: var(--cs-fill); font-size: 11.5px; color: var(--cs-text-2);\n}\n#cs-root .cs-pending img { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; flex: none; }\n#cs-root .cs-pending .cs-icon { margin-left: 4px; }\n#cs-root .cs-pending-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-pending.is-uploading { opacity: 0.6; }\n#cs-root .cs-pending.is-failed { box-shadow: inset 0 0 0 1px var(--cs-danger); }\n#cs-root .cs-pending-remove {\n position: absolute; right: 3px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; border-radius: 999px;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);\n}\n#cs-root .cs-pending-remove:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-pending-remove .cs-icon { width: 11px; height: 11px; margin: 0; }\n#cs-root .cs-files { display: grid; gap: 6px; margin-top: 6px; }\n#cs-root .cs-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thumb { display: block; width: 112px; height: 72px; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); }\n#cs-root .cs-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }\n#cs-root .cs-file {\n display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 6px 10px; border-radius: 8px;\n background: var(--cs-fill); color: var(--cs-text); text-decoration: none; font-size: 12px; justify-self: start;\n}\n#cs-root .cs-file:hover { background: var(--cs-fill-2); }\n#cs-root .cs-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-file-size { color: var(--cs-text-3); flex: none; }\n\n/* Page overlays */\n#cs-root .cs-pin {\n position: absolute;\n transform: translate(-50%, -50%);\n padding: 2px;\n border-radius: 999px;\n background: var(--cs-card);\n box-shadow: 0 0 0 0.5px var(--cs-edge), 0 3px 10px rgba(0, 0, 0, 0.18);\n pointer-events: auto;\n transition: transform 0.15s ease;\n}\n#cs-root .cs-pin:hover { transform: translate(-50%, -50%) scale(1.08); }\n#cs-root .cs-pin.is-active { box-shadow: 0 0 0 2px var(--cs-accent), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin.is-drifted { box-shadow: 0 0 0 1.5px var(--cs-warn), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin-count {\n position: absolute; top: -5px; right: -6px; min-width: 15px; height: 15px; padding: 0 4px;\n border-radius: 999px; background: var(--cs-text); color: var(--cs-card);\n font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;\n}\n#cs-root .cs-peek {\n position: absolute; width: 240px; padding: 10px 12px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: none;\n}\n#cs-root .cs-peek .cs-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n#cs-root .cs-outline {\n position: absolute; border-radius: 8px; pointer-events: none;\n box-shadow: 0 0 0 2px var(--cs-accent); background: var(--cs-accent-soft);\n}\n#cs-root .cs-outline.is-flash { animation: cs-flash 1.4s ease forwards; }\n@keyframes cs-flash { 0%, 60% { opacity: 1; } 100% { opacity: 0; } }\n#cs-root .cs-outline-label {\n position: absolute; max-width: 360px; padding: 3px 8px; border-radius: 6px; pointer-events: none;\n background: var(--cs-accent); color: #fff; font-size: 11.5px; font-weight: 500;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n#cs-root .cs-banner {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 8px; padding: 5px 5px 5px 12px; border-radius: 999px;\n background: var(--cs-glass); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow); font-weight: 500; white-space: nowrap;\n}\n#cs-root .cs-banner button { padding: 3px 10px; border-radius: 999px; background: var(--cs-fill); font-size: 12px; }\n#cs-root .cs-banner button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-toast {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%); padding: 6px 12px; border-radius: 999px;\n background: rgba(29, 29, 31, 0.9); color: #fff; font-size: 12px; font-weight: 500;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); pointer-events: none; white-space: nowrap;\n}\n\n/* Docked inside an open <dialog> */\n#cs-root.cs-inside .cs-bar { bottom: 8px; }\n#cs-root.cs-inside.cs-at-left .cs-bar { left: 8px; }\n#cs-root.cs-inside.cs-at-right .cs-bar { right: 8px; }\n#cs-root.cs-inside .cs-sheet { top: 8px; right: 8px; bottom: auto; max-height: 46%; width: min(300px, calc(100% - 16px)); }\n#cs-root.cs-inside .cs-unlock, #cs-root.cs-inside .cs-menu { bottom: 52px; }\n#cs-root.cs-inside .cs-toast { top: 8px; }\n";
1
+ export declare const toolbarCss = "\n#cs-root {\n --cs-text: #1d1d1f;\n --cs-text-2: #6e6e73;\n --cs-text-3: #a1a1a6;\n --cs-glass: rgba(251, 251, 253, 0.8);\n --cs-card: rgba(255, 255, 255, 0.94);\n --cs-fill: rgba(0, 0, 0, 0.05);\n --cs-fill-2: rgba(0, 0, 0, 0.08);\n --cs-line: rgba(0, 0, 0, 0.08);\n --cs-edge: rgba(0, 0, 0, 0.1);\n --cs-accent: #007aff;\n --cs-accent-soft: rgba(0, 122, 255, 0.12);\n --cs-danger: #ff3b30;\n --cs-warn: #ff9500;\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n pointer-events: none;\n margin: 0;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--cs-text);\n font: 13px/1.4 -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\", Inter, system-ui, sans-serif;\n letter-spacing: -0.005em;\n -webkit-font-smoothing: antialiased;\n text-align: left;\n}\n#cs-root.cs-dark {\n --cs-text: #f5f5f7;\n --cs-text-2: #a1a1a6;\n --cs-text-3: #6e6e73;\n --cs-glass: rgba(40, 40, 44, 0.78);\n --cs-card: rgba(36, 36, 40, 0.96);\n --cs-fill: rgba(255, 255, 255, 0.07);\n --cs-fill-2: rgba(255, 255, 255, 0.12);\n --cs-line: rgba(255, 255, 255, 0.08);\n --cs-edge: rgba(255, 255, 255, 0.12);\n --cs-accent: #0a84ff;\n --cs-accent-soft: rgba(10, 132, 255, 0.2);\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.45);\n}\n#cs-root.cs-inside { position: absolute; }\n#cs-root, #cs-root * { box-sizing: border-box; }\n#cs-root h2, #cs-root p { margin: 0; }\n#cs-root button, #cs-root input, #cs-root textarea {\n font: inherit; color: inherit; letter-spacing: inherit; margin: 0;\n}\n#cs-root button { background: none; border: 0; padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }\n#cs-root button:disabled { cursor: default; opacity: 0.45; }\n#cs-root button:focus-visible, #cs-root [role=button]:focus-visible { outline: 2px solid var(--cs-accent); outline-offset: 2px; }\n#cs-root .cs-hit { pointer-events: auto; }\n#cs-root .cs-icon { display: block; flex: none; }\n\n/* Bar */\n#cs-root .cs-bar {\n position: absolute;\n bottom: 16px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n border-radius: 999px;\n background: var(--cs-glass);\n -webkit-backdrop-filter: saturate(180%) blur(20px);\n backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow);\n}\n#cs-root.cs-at-center .cs-bar { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-bar { left: 16px; }\n#cs-root.cs-at-right .cs-bar { right: 16px; }\n#cs-root .cs-tool {\n position: relative;\n width: 34px;\n height: 34px;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--cs-text);\n transition: background-color 0.15s ease, color 0.15s ease;\n}\n#cs-root .cs-tool:hover { background: var(--cs-fill); }\n#cs-root .cs-tool.is-on { background: var(--cs-fill-2); }\n#cs-root .cs-tool.is-accent { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-tool[data-tip]::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 10px);\n left: 50%;\n transform: translate(-50%, 2px);\n white-space: pre;\n padding: 4px 8px;\n border-radius: 7px;\n background: rgba(29, 29, 31, 0.92);\n color: #fff;\n font-size: 11.5px;\n font-weight: 500;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease, transform 0.12s ease;\n}\n#cs-root .cs-tool[data-tip]:hover::after { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.35s; }\n#cs-root .cs-me { width: 34px; }\n#cs-root .cs-divider { width: 1px; height: 18px; background: var(--cs-line); margin: 0 3px; }\n#cs-root .cs-badge {\n position: absolute;\n top: 3px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--cs-accent);\n color: #fff;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n box-shadow: 0 0 0 2px var(--cs-glass);\n}\n#cs-root .cs-bar-lock {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n height: 34px;\n padding: 0 14px 0 12px;\n border-radius: 999px;\n font-weight: 500;\n}\n#cs-root .cs-bar-lock:hover { background: var(--cs-fill); }\n#cs-root .cs-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 999px;\n color: #fff;\n font-weight: 600;\n letter-spacing: 0;\n line-height: 1;\n user-select: none;\n}\n\n/* Cards */\n#cs-root .cs-card {\n position: absolute;\n background: var(--cs-card);\n -webkit-backdrop-filter: saturate(180%) blur(24px);\n backdrop-filter: saturate(180%) blur(24px);\n border-radius: 14px;\n box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-unlock, #cs-root .cs-menu { bottom: 64px; }\n#cs-root.cs-at-center .cs-unlock, #cs-root.cs-at-center .cs-menu { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-unlock, #cs-root.cs-at-left .cs-menu { left: 16px; }\n#cs-root.cs-at-right .cs-unlock, #cs-root.cs-at-right .cs-menu { right: 16px; }\n#cs-root .cs-unlock { width: 280px; padding: 20px; text-align: center; }\n#cs-root .cs-unlock-icon {\n width: 40px; height: 40px; margin: 0 auto 10px; border-radius: 12px;\n display: flex; align-items: center; justify-content: center; background: var(--cs-fill); color: var(--cs-text-2);\n}\n#cs-root .cs-unlock h2 { font-size: 15px; font-weight: 600; }\n#cs-root .cs-sub { color: var(--cs-text-2); margin-top: 4px; }\n#cs-root .cs-unlock form { margin-top: 14px; display: grid; gap: 8px; text-align: left; }\n#cs-root .cs-field { display: grid; gap: 4px; }\n#cs-root .cs-field span { font-size: 12px; color: var(--cs-text-2); padding-left: 2px; }\n#cs-root input, #cs-root textarea {\n width: 100%;\n border: 0;\n border-radius: 9px;\n background: var(--cs-fill);\n padding: 8px 10px;\n outline: none;\n transition: box-shadow 0.15s ease, background-color 0.15s ease;\n}\n#cs-root input:focus, #cs-root textarea:focus { background: transparent; box-shadow: 0 0 0 1px var(--cs-accent), 0 0 0 4px var(--cs-accent-soft); }\n#cs-root textarea { resize: none; }\n#cs-root .cs-error { color: var(--cs-danger); font-size: 12px; }\n#cs-root .cs-pad { padding: 6px 14px 0; }\n#cs-root .cs-hint { color: var(--cs-text-3); font-size: 11.5px; }\n\n/* Buttons */\n#cs-root .cs-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n height: 28px;\n padding: 0 11px;\n border-radius: 8px;\n background: var(--cs-fill);\n font-size: 12.5px;\n font-weight: 500;\n transition: background-color 0.15s ease;\n}\n#cs-root .cs-button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-button.is-primary { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-button.is-primary:hover { filter: brightness(1.06); }\n#cs-root .cs-button.is-danger { background: var(--cs-danger); color: #fff; }\n#cs-root .cs-button.is-wide { width: 100%; height: 32px; margin-top: 4px; }\n#cs-root .cs-icon-button {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-2);\n}\n#cs-root .cs-icon-button:hover { background: var(--cs-fill); color: var(--cs-text); }\n#cs-root .cs-send {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; background: var(--cs-accent); color: #fff;\n}\n#cs-root .cs-send:disabled { background: var(--cs-fill-2); color: var(--cs-text-3); opacity: 1; }\n\n/* Menu */\n#cs-root .cs-menu { width: 264px; padding: 6px; }\n#cs-root .cs-me-row { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }\n#cs-root .cs-me-name { display: flex; align-items: center; gap: 4px; min-width: 0; }\n#cs-root .cs-me-name strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-rename { display: flex; align-items: center; gap: 4px; flex: 1; }\n#cs-root .cs-rename input { padding: 5px 8px; }\n#cs-root .cs-sep { height: 1px; background: var(--cs-line); margin: 4px 8px; }\n#cs-root .cs-item {\n display: flex; align-items: center; justify-content: space-between; gap: 10px;\n width: 100%; min-height: 32px; padding: 0 8px; border-radius: 8px; text-align: left;\n}\n#cs-root button.cs-item:hover { background: var(--cs-fill); }\n#cs-root .cs-item.is-danger { color: var(--cs-danger); }\n#cs-root .cs-switch { position: relative; width: 32px; height: 20px; border-radius: 999px; background: var(--cs-fill-2); transition: background-color 0.2s ease; flex: none; }\n#cs-root .cs-switch::after {\n content: \"\"; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 999px;\n background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); transition: transform 0.2s ease;\n}\n#cs-root .cs-switch.is-on { background: #34c759; }\n#cs-root .cs-switch.is-on::after { transform: translateX(12px); }\n#cs-root .cs-segment { display: inline-flex; padding: 2px; border-radius: 8px; background: var(--cs-fill); }\n#cs-root .cs-segment button { padding: 2px 8px; border-radius: 6px; font-size: 12px; color: var(--cs-text-2); }\n#cs-root .cs-segment button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); font-weight: 500; }\n#cs-root .cs-menu .cs-hint { padding: 6px 8px 4px; }\n#cs-root .cs-invite { padding: 2px 8px 6px; }\n#cs-root .cs-invite input { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; padding: 6px 8px; }\n#cs-root .cs-invite .cs-hint { padding: 4px 0 0; }\n#cs-root .cs-confirm { padding: 10px; }\n#cs-root .cs-confirm h2 { font-size: 14px; font-weight: 600; }\n#cs-root .cs-row-end { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; }\n\n/* Sheet */\n#cs-root .cs-sheet {\n top: 12px; right: 12px; max-height: calc(100% - 88px); width: min(360px, calc(100vw - 24px));\n display: flex; flex-direction: column; overflow: hidden;\n}\n#cs-root .cs-sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 10px 4px 16px; }\n#cs-root .cs-sheet-head h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }\n#cs-root .cs-tabs { display: flex; gap: 2px; margin: 4px 12px 6px; padding: 2px; border-radius: 9px; background: var(--cs-fill); }\n#cs-root .cs-tabs button {\n flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;\n height: 26px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--cs-text-2);\n}\n#cs-root .cs-tabs button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }\n#cs-root .cs-count { font-size: 11px; color: var(--cs-text-3); font-variant-numeric: tabular-nums; }\n#cs-root .cs-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 6px 8px; overscroll-behavior: contain; }\n#cs-root .cs-group { padding: 10px 10px 4px; font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.04em; }\n#cs-root .cs-empty { padding: 36px 16px; text-align: center; color: var(--cs-text-2); display: grid; gap: 12px; justify-items: center; }\n#cs-root .cs-thread { border-radius: 10px; }\n#cs-root .cs-thread + .cs-thread { margin-top: 1px; }\n#cs-root .cs-thread.is-open { background: var(--cs-fill); }\n#cs-root .cs-thread-main { display: flex; gap: 10px; padding: 10px; border-radius: 10px; cursor: pointer; }\n#cs-root .cs-thread:not(.is-open) .cs-thread-main:hover { background: var(--cs-fill); }\n#cs-root .cs-thread-body { min-width: 0; flex: 1; }\n#cs-root .cs-meta { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }\n#cs-root .cs-meta strong { font-weight: 600; }\n#cs-root .cs-meta time { color: var(--cs-text-3); font-size: 12px; }\n#cs-root .cs-text { margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-target { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); }\n#cs-root .cs-note { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }\n#cs-root .cs-note::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: currentColor; }\n#cs-root .cs-note.is-quiet { color: var(--cs-text-2); }\n#cs-root .cs-note.is-warn { color: var(--cs-warn); }\n#cs-root .cs-note.is-bad { color: var(--cs-danger); }\n#cs-root .cs-replies { margin-top: 4px; font-size: 12px; color: var(--cs-accent); font-weight: 500; }\n#cs-root .cs-thread-more { padding: 0 10px 10px 44px; display: grid; gap: 10px; }\n#cs-root .cs-message { display: flex; gap: 8px; }\n#cs-root .cs-reply { display: flex; align-items: flex-end; gap: 6px; padding: 4px 4px 4px 0; border-radius: 10px; }\n#cs-root .cs-reply textarea { min-height: 32px; max-height: 120px; field-sizing: content; }\n#cs-root .cs-thread.is-open .cs-reply textarea { background: var(--cs-card); }\n#cs-root .cs-actions { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thread.is-open .cs-button { background: var(--cs-card); box-shadow: 0 0 0 0.5px var(--cs-edge); }\n#cs-root .cs-thread.is-open .cs-button:hover { background: var(--cs-fill-2); }\n\n/* Mentions */\n#cs-root .cs-mention { color: var(--cs-accent); background: var(--cs-accent-soft); border-radius: 4px; padding: 0 3px; font-weight: 500; }\n#cs-root .cs-delivery { margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); display: flex; align-items: center; gap: 5px; }\n#cs-root .cs-delivery::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: #34c759; }\n#cs-root .cs-people {\n position: fixed; z-index: 1; width: 264px; padding: 5px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: auto;\n}\n#cs-root .cs-person { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px; cursor: pointer; }\n#cs-root .cs-person.is-on { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-person-text { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-person-text strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person-text small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person.is-on small { color: rgba(255, 255, 255, 0.8); }\n#cs-root .cs-field small { font-size: 11.5px; color: var(--cs-text-3); padding-left: 2px; }\n#cs-root .cs-me-lines { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-me-lines small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* Composer */\n#cs-root .cs-composer { width: min(320px, calc(100% - 16px)); }\n#cs-root .cs-composer-target {\n display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 12px;\n border-bottom: 1px solid var(--cs-line); font-size: 12px; color: var(--cs-text-2);\n}\n#cs-root .cs-composer-name { font-weight: 500; color: var(--cs-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n#cs-root .cs-composer-target code {\n margin-left: auto; font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--cs-text-3);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; min-width: 0; max-width: 45%;\n}\n#cs-root .cs-composer-body { display: flex; gap: 10px; padding: 12px 12px 4px; }\n#cs-root .cs-composer-body textarea { background: transparent; padding: 3px 0; min-height: 56px; box-shadow: none; border-radius: 0; }\n#cs-root .cs-composer-foot { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px 40px; }\n#cs-root .cs-composer-foot .cs-hint { margin-left: auto; }\n\n/* Roles and fields */\n#cs-root .cs-details-input { display: grid; gap: 8px; padding: 2px 12px 6px 46px; }\n#cs-root .cs-details-edit .cs-details-input { padding: 0; }\n#cs-root .cs-detail-field { display: grid; gap: 3px; }\n#cs-root .cs-detail-field span, #cs-root .cs-roles-label, #cs-root .cs-detail-list dt {\n font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.03em;\n}\n#cs-root .cs-detail-field textarea { padding: 6px 8px; min-height: 30px; max-height: 120px; field-sizing: content; }\n#cs-root .cs-thread.is-open .cs-detail-field textarea { background: var(--cs-card); }\n#cs-root .cs-roles { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }\n#cs-root .cs-roles-label { margin-right: 4px; }\n#cs-root .cs-chip {\n display: inline-flex; align-items: center; gap: 2px; height: 24px; padding: 0 9px; border-radius: 999px;\n background: var(--cs-fill); font-size: 12px; font-weight: 500; color: var(--cs-text-2);\n}\n#cs-root button.cs-chip:hover { background: var(--cs-fill-2); }\n#cs-root .cs-chip.is-on, #cs-root button.cs-chip.is-on:hover { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-chip.is-static { padding-right: 3px; color: var(--cs-text); }\n#cs-root .cs-chip.is-static button {\n width: 18px; height: 18px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);\n}\n#cs-root .cs-chip.is-static button:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-chip.is-static .cs-icon { width: 11px; height: 11px; }\n#cs-root .cs-chips { display: flex; flex-wrap: wrap; gap: 4px; }\n#cs-root .cs-role {\n display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 999px;\n background: var(--cs-accent-soft); color: var(--cs-accent); font-size: 11px; font-weight: 600;\n}\n#cs-root .cs-detail-list { display: grid; gap: 6px; margin: 6px 0 0; }\n#cs-root .cs-detail-list > div { padding: 6px 8px; border-radius: 8px; background: var(--cs-fill); }\n#cs-root .cs-thread.is-open .cs-detail-list > div { background: var(--cs-card); }\n#cs-root .cs-detail-list dd { margin: 1px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-detail-list.is-clamped dd { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n#cs-root .cs-details-edit { display: grid; gap: 8px; }\n#cs-root .cs-details-edit .cs-row-end { margin-top: 0; }\n#cs-root .cs-settings { width: 280px; }\n#cs-root .cs-settings-head { display: flex; align-items: center; gap: 4px; padding: 4px 4px 6px; }\n#cs-root .cs-settings-head h2 { font-size: 14px; font-weight: 600; }\n#cs-root .cs-settings-body { display: grid; gap: 6px; padding: 2px 8px 8px; }\n#cs-root .cs-field-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-height: 28px; padding-left: 2px; }\n#cs-root .cs-add-row { display: flex; align-items: center; gap: 4px; }\n#cs-root .cs-add-row input { padding: 5px 8px; }\n#cs-root .cs-settings .cs-hint { padding: 6px 8px 4px; }\n\n/* Attachments */\n#cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }\n#cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 12px 2px 46px; }\n#cs-root .cs-thread-more .cs-tray { padding: 0; }\n#cs-root .cs-pending {\n position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 170px; height: 34px;\n padding: 0 24px 0 4px; border-radius: 8px; background: var(--cs-fill); font-size: 11.5px; color: var(--cs-text-2);\n}\n#cs-root .cs-pending img { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; flex: none; }\n#cs-root .cs-pending .cs-icon { margin-left: 4px; }\n#cs-root .cs-pending-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-pending.is-uploading { opacity: 0.6; }\n#cs-root .cs-pending.is-failed { box-shadow: inset 0 0 0 1px var(--cs-danger); }\n#cs-root .cs-pending-remove {\n position: absolute; right: 3px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; border-radius: 999px;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);\n}\n#cs-root .cs-pending-remove:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-pending-remove .cs-icon { width: 11px; height: 11px; margin: 0; }\n#cs-root .cs-files { display: grid; gap: 6px; margin-top: 6px; }\n#cs-root .cs-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thumb { display: block; width: 112px; height: 72px; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); }\n#cs-root .cs-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }\n#cs-root .cs-file {\n display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 6px 10px; border-radius: 8px;\n background: var(--cs-fill); color: var(--cs-text); text-decoration: none; font-size: 12px; justify-self: start;\n}\n#cs-root .cs-file:hover { background: var(--cs-fill-2); }\n#cs-root .cs-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-file-size { color: var(--cs-text-3); flex: none; }\n\n/* Page overlays */\n#cs-root .cs-pin {\n position: absolute;\n transform: translate(-50%, -50%);\n padding: 2px;\n border-radius: 999px;\n background: var(--cs-card);\n box-shadow: 0 0 0 0.5px var(--cs-edge), 0 3px 10px rgba(0, 0, 0, 0.18);\n pointer-events: auto;\n transition: transform 0.15s ease;\n}\n#cs-root .cs-pin:hover { transform: translate(-50%, -50%) scale(1.08); }\n#cs-root .cs-pin.is-active { box-shadow: 0 0 0 2px var(--cs-accent), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin.is-drifted { box-shadow: 0 0 0 1.5px var(--cs-warn), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin-count {\n position: absolute; top: -5px; right: -6px; min-width: 15px; height: 15px; padding: 0 4px;\n border-radius: 999px; background: var(--cs-text); color: var(--cs-card);\n font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;\n}\n#cs-root .cs-peek {\n position: absolute; width: 240px; padding: 10px 12px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: none;\n}\n#cs-root .cs-peek .cs-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n#cs-root .cs-outline {\n position: absolute; border-radius: 8px; pointer-events: none;\n box-shadow: 0 0 0 2px var(--cs-accent); background: var(--cs-accent-soft);\n}\n#cs-root .cs-outline.is-flash { animation: cs-flash 1.4s ease forwards; }\n@keyframes cs-flash { 0%, 60% { opacity: 1; } 100% { opacity: 0; } }\n#cs-root .cs-outline-label {\n position: absolute; max-width: 360px; padding: 3px 8px; border-radius: 6px; pointer-events: none;\n background: var(--cs-accent); color: #fff; font-size: 11.5px; font-weight: 500;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n#cs-root .cs-banner {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 8px; padding: 5px 5px 5px 12px; border-radius: 999px;\n background: var(--cs-glass); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow); font-weight: 500; white-space: nowrap;\n}\n#cs-root .cs-banner button { padding: 3px 10px; border-radius: 999px; background: var(--cs-fill); font-size: 12px; }\n#cs-root .cs-banner button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-toast {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%); padding: 6px 12px; border-radius: 999px;\n background: rgba(29, 29, 31, 0.9); color: #fff; font-size: 12px; font-weight: 500;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); pointer-events: none; white-space: nowrap;\n}\n\n/* Docked inside an open <dialog> */\n#cs-root.cs-inside .cs-bar { bottom: 8px; }\n#cs-root.cs-inside.cs-at-left .cs-bar { left: 8px; }\n#cs-root.cs-inside.cs-at-right .cs-bar { right: 8px; }\n#cs-root.cs-inside .cs-sheet { top: 8px; right: 8px; bottom: auto; max-height: 46%; width: min(300px, calc(100% - 16px)); }\n#cs-root.cs-inside .cs-unlock, #cs-root.cs-inside .cs-menu { bottom: 52px; }\n#cs-root.cs-inside .cs-toast { top: 8px; }\n";
@@ -1,10 +1,15 @@
1
1
  export type ToolbarPosition = "bottom-left" | "bottom-center" | "bottom-right";
2
2
  export type ToolbarTheme = "light" | "dark" | "system";
3
- export declare function CommentToolbar({ sessionPath, pollMs, position, theme, }: {
3
+ export declare function CommentToolbar({ sessionPath, pollMs, position, theme, navigate, }: {
4
4
  sessionPath?: string;
5
5
  pollMs?: number;
6
6
  /** Default bar position. Each person can override it from their menu. */
7
7
  position?: ToolbarPosition;
8
8
  /** "system" follows the OS appearance. Light by default so it matches most host pages. */
9
9
  theme?: ToolbarTheme;
10
+ /**
11
+ * Opens another page of the app, for example `(href) => router.navigate({ href })`.
12
+ * Without it, selecting a comment from another page does a full page load.
13
+ */
14
+ navigate?: (href: string) => void;
10
15
  }): import("react").ReactPortal | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devaccel-labs/comment-service",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "In-UI component comments for TanStack Start on Cloudflare. Anchors survive redeploys.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -43,11 +43,11 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "esbuild": "^0.28.1",
46
- "@devaccel/comment-compiler": "0.1.0",
47
- "@devaccel/comment-react": "0.1.0",
48
46
  "@devaccel/comment-core": "0.1.0",
49
- "@devaccel/comment-server": "0.1.0",
50
- "@devaccel/comment-start": "0.1.0"
47
+ "@devaccel/comment-react": "0.1.0",
48
+ "@devaccel/comment-compiler": "0.1.0",
49
+ "@devaccel/comment-start": "0.1.0",
50
+ "@devaccel/comment-server": "0.1.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "registry": "https://registry.npmjs.org/",