overplay 4.5.0 → 4.7.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.
@@ -107,6 +107,7 @@ function serviceOrigin(origin) {
107
107
  }
108
108
  class HostedAdapter {
109
109
  constructor(serverUrl, projectId, page, sessionChanged) {
110
+ /** The Overplay service address, such as https://overplay.app. */
110
111
  __publicField(this, "base");
111
112
  __publicField(this, "key");
112
113
  __publicField(this, "accessToken", "");
@@ -474,7 +475,7 @@ function newActivity(comments, seen, selfId) {
474
475
  function sameComments(a, b) {
475
476
  return a.length === b.length && JSON.stringify(a) === JSON.stringify(b);
476
477
  }
477
- const version = "4.5.0";
478
+ const version = "4.7.0";
478
479
  const VERSION = version;
479
480
  const PACKAGE = "overplay";
480
481
  function updatePrompt(current, latest) {
@@ -546,7 +547,7 @@ const CHOICES = {
546
547
  buttonColor: ["default", "site", "indigo", "violet", "blue", "teal", "amber", "orange", "red", "pink"],
547
548
  resolvedPins: ["green", "gray"],
548
549
  pinSize: ["regular", "small"],
549
- pinLabel: ["number", "initials"],
550
+ pinLabel: ["number", "initials", "photo"],
550
551
  pushContent: [true, false],
551
552
  notifications: [true, false],
552
553
  shortcut: [true, false]
@@ -597,6 +598,7 @@ const paths = {
597
598
  trash: "M3 6h18M9 6V4h6v2M5 6l1 14h12l1-14M10 10v6m4-6v6",
598
599
  pin: "M12 21s7-6 7-13A7 7 0 0 0 5 8c0 7 7 13 7 13ZM12 6v4M10 8h4",
599
600
  search: "m21 21-5-5M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z",
601
+ filter: "M4 6h16M7 12h10M10 18h4",
600
602
  eye: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12ZM12 14.8a2.8 2.8 0 1 0 0-5.6 2.8 2.8 0 0 0 0 5.6Z",
601
603
  eyeOff: "M3 3l18 18M10.6 5.6A9 9 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a16 16 0 0 1-2.9 3.6M6.5 6.9A16 16 0 0 0 2.5 12S6 18.5 12 18.5a9 9 0 0 0 4.6-1.3M10 10a2.8 2.8 0 0 0 4 4",
602
604
  unlink: "M4.5 4.5h15v15h-15zM9.5 9.5l5 5M14.5 9.5l-5 5",
@@ -622,6 +624,18 @@ function icon(name) {
622
624
  svg.append(path);
623
625
  return svg;
624
626
  }
627
+ function logo() {
628
+ const ns = "http://www.w3.org/2000/svg", svg = document.createElementNS(ns, "svg");
629
+ svg.setAttribute("viewBox", "0 0 48 48");
630
+ svg.setAttribute("aria-hidden", "true");
631
+ const shape = document.createElementNS(ns, "path");
632
+ shape.setAttribute("d", "M24 4a20 20 0 1 1 0 40H9a5 5 0 0 1-5-5V24A20 20 0 0 1 24 4Z");
633
+ shape.setAttribute("fill", "#0068f9");
634
+ const ring = document.createElementNS(ns, "circle");
635
+ for (const [k, v] of [["cx", "24"], ["cy", "24"], ["r", "8"], ["fill", "none"], ["stroke", "#fff"], ["stroke-width", "5"]]) ring.setAttribute(k, v);
636
+ svg.append(shape, ring);
637
+ return svg;
638
+ }
625
639
  function button(label, className, onClick, name) {
626
640
  const el = element("button", className);
627
641
  el.type = "button";
@@ -751,16 +765,16 @@ const styles = `
751
765
  @media(prefers-color-scheme:dark){:host([data-theme=system]){${tokenDeclarations(darkTokens)}color-scheme:dark}}
752
766
  :host>div{font:16px/1.5 var(--cw-font);color:var(--cw-ink);letter-spacing:normal;text-align:left}*,*::before,*::after{box-sizing:border-box}button,input,textarea,select{font:inherit}button{cursor:pointer}button:disabled{cursor:default;opacity:.45}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:3px solid var(--cw-accent);outline-offset:3px}button{color:inherit}svg{width:18px;height:18px;flex-shrink:0}button{touch-action:manipulation}img{max-width:100%}[hidden]{display:none!important}
753
767
  .launcher{position:fixed;bottom:24px;right:24px;display:flex;align-items:center;justify-content:center;width:52px;height:52px;border:1px solid #ffffff24;background:var(--cw-inverse);color:var(--cw-on-inverse);padding:0;border-radius:50%;box-shadow:0 6px 24px #12172233;font-weight:600;font-size:16px;pointer-events:auto;touch-action:none;user-select:none;-webkit-user-select:none;cursor:grab;transition:transform .16s,box-shadow .16s}.launcher:hover{transform:translateY(-2px);box-shadow:0 8px 28px #20243733}.launcher.dragging{cursor:grabbing;transition:none;transform:scale(1.06);box-shadow:0 12px 32px #2024374d}.launcher[aria-expanded=true]{background:var(--cw-accent)}.launcher>svg{width:23px;height:23px}.badge{position:absolute;top:-5px;right:-5px;font-weight:650;font-size:12px;line-height:18px;min-width:22px;height:22px;border:2px solid var(--cw-surface);border-radius:999px;background:var(--cw-accent);color:#fff;text-align:center;padding:0 4px}.launcher[aria-expanded=true] .badge{background:var(--cw-inverse)}
754
- .panel{position:fixed;top:0;right:0;bottom:0;width:400px;max-width:100vw;display:flex;flex-direction:column;background:var(--cw-surface);border-left:1px solid var(--cw-line);box-shadow:var(--cw-shadow);pointer-events:auto;overflow:hidden}.panel.entering{animation:slide-in .2s ease-out}.panel.left{right:auto;left:0;border-left:0;border-right:1px solid var(--cw-line);--cw-slide:-24px}.detail{display:flex;flex-direction:column;flex:1;min-height:0}.detail.entering{animation:enter .16s ease-out}.detail:focus{outline:0}.head{display:flex;align-items:center;gap:10px;padding:22px 20px 16px}.brand-icon{width:34px;height:34px;display:grid;place-items:center;background:var(--cw-accent-soft);color:var(--cw-accent);border-radius:50%}.heading{flex:1;min-width:0}.heading h2{font-size:19px;line-height:1.3;letter-spacing:-.4px;margin:0;font-weight:650}.eyebrow{font-size:12px;font-weight:700;letter-spacing:1.4px;color:var(--cw-muted);text-transform:uppercase;margin:0 0 4px}.subtext{color:var(--cw-muted);font-size:14px;margin:4px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button{width:32px;height:32px;display:inline-grid;place-items:center;background:transparent;border:0;border-radius:50%;flex-shrink:0}.icon-button:hover{background:var(--cw-surface-soft)}.icon-button.danger{color:var(--cw-danger)}.icon-button svg{width:16px;height:16px}.icon-button.outlined{width:42px;height:42px;border:1px solid var(--cw-line-strong);border-radius:50%;color:var(--cw-ink-soft)}.icon-button.outlined svg{width:18px;height:18px}.icon-button.busy:disabled{opacity:1;color:var(--cw-accent)}.icon-button.busy svg{animation:spin .9s linear infinite}.toolbar{padding:0 20px 16px;display:flex;gap:10px;align-items:center}.primary{border:0;border-radius:var(--cw-radius-pill);background:var(--cw-accent);color:#fff;display:inline-flex;gap:7px;align-items:center;justify-content:center;padding:10px 16px;font-size:14px;font-weight:550;white-space:nowrap;box-shadow:inset 0 -1px 0 #12172238;transition:filter .16s}.primary:hover{filter:brightness(.94)}.toolbar .primary{flex:1}.quiet{border:1px solid var(--cw-line-strong);background:var(--cw-surface);color:var(--cw-ink);border-radius:var(--cw-radius-pill);padding:9px 16px;font-size:14px}.quiet:hover{background:var(--cw-canvas)}.quiet.update{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;color:var(--cw-accent);border-color:var(--cw-accent-line);background:var(--cw-accent-soft);font-weight:600}.quiet.update:hover{border-color:var(--cw-accent)}.quiet.update svg{width:14px;height:14px}.scope{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:0 20px 8px;padding:4px;background:var(--cw-surface-soft);border-radius:var(--cw-radius-pill)}.scope-option{display:flex;gap:7px;align-items:center;justify-content:center;border:0;background:transparent;border-radius:var(--cw-radius-pill);padding:8px 6px;font-size:14px;font-weight:550;color:var(--cw-muted);white-space:nowrap}.scope-option:hover{color:var(--cw-ink)}.scope-option[aria-pressed=true]{background:var(--cw-surface);color:var(--cw-ink);box-shadow:0 1px 3px #2024371f}.scope-option[aria-pressed=true] .count{background:var(--cw-accent-soft);color:var(--cw-accent)}.tabs{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin:0 20px 8px;padding:4px;border:1px solid var(--cw-line);border-radius:var(--cw-radius-pill)}.tab{display:flex;gap:7px;align-items:center;justify-content:center;border:0;border-radius:var(--cw-radius-pill);background:transparent;padding:7px 6px;color:var(--cw-muted);font-size:14px;font-weight:550;white-space:nowrap}.tab[aria-pressed=true]{color:var(--cw-on-inverse);background:var(--cw-inverse)}.tab[aria-pressed=true] .count{background:#ffffff2e;color:var(--cw-on-inverse)}.count{background:var(--cw-surface-soft);padding:0 6px;border-radius:5px;font-size:12px;line-height:18px}.search{position:relative;margin:4px 20px 8px}.search>svg{position:absolute;left:11px;top:11px;width:15px;height:15px;color:var(--cw-subtle);pointer-events:none}.search input{border:1px solid var(--cw-line);border-radius:var(--cw-radius);background:var(--cw-canvas);width:100%;padding:9px 38px 9px 34px;color:var(--cw-ink);font-size:14px}.search input::placeholder{color:var(--cw-subtle)}.search input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.search-clear{position:absolute;right:5px;top:50%;transform:translateY(-50%);width:28px;height:28px;color:var(--cw-muted)}.search-clear svg{width:14px;height:14px}.people{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 20px 6px}.filter-chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cw-line);background:var(--cw-surface);color:var(--cw-ink-soft);border-radius:999px;padding:4px 6px 4px 11px;font-size:13px;font-weight:600;white-space:nowrap}.filter-chip:hover{background:var(--cw-canvas)}.filter-chip[aria-pressed=true]{background:var(--cw-accent-soft);border-color:var(--cw-accent-line);color:var(--cw-accent)}.filter-chip[aria-pressed=true] .count{background:var(--cw-surface);color:var(--cw-accent)}.select{position:relative;display:inline-flex;min-width:0;max-width:220px;color:var(--cw-muted)}.select::after{content:'';position:absolute;right:12px;top:50%;width:6px;height:6px;border:solid currentColor;border-width:0 1.5px 1.5px 0;transform:translateY(-70%) rotate(45deg);pointer-events:none}.select select{appearance:none;-webkit-appearance:none;border:1px solid var(--cw-line);background:var(--cw-surface);color:var(--cw-ink-soft);border-radius:999px;padding:5px 30px 5px 11px;font-size:13px;font-weight:600;line-height:18px;max-width:100%;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.select select:hover{background:var(--cw-canvas)}.select.chosen{color:var(--cw-accent)}.select.chosen select{background:var(--cw-accent-soft);border-color:var(--cw-accent-line);color:var(--cw-accent)}.select option{color:var(--cw-ink);background:var(--cw-surface)}.link-button{margin-left:auto;border:0;background:transparent;color:var(--cw-accent);font-size:13px;font-weight:600;padding:4px 2px;white-space:nowrap}.link-button:hover{text-decoration:underline}mark.hit{background:color-mix(in srgb,var(--cw-accent) 22%,transparent);color:inherit;border-radius:3px;padding:0 1px;-webkit-box-decoration-break:clone;box-decoration-break:clone}.note-match{display:flex;align-items:center;gap:5px;margin:4px 0 0 31px;font-size:12.5px;line-height:1.5;color:var(--cw-muted);min-width:0}.note-match svg{width:12px;height:12px}.note-match-label{font-weight:600;color:var(--cw-ink-soft);white-space:nowrap;flex-shrink:0;max-width:45%;overflow:hidden;text-overflow:ellipsis}.note-match-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.list{flex:1;overflow:auto;overscroll-behavior:contain;padding:8px 12px 16px;min-height:0}.list-title{display:flex;align-items:center;gap:8px;padding:12px 8px 6px;color:var(--cw-muted);font-size:12px;letter-spacing:1.15px;text-transform:uppercase;font-weight:600}.page-path{text-transform:none;letter-spacing:0;font-size:13px;color:var(--cw-ink-soft);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.page-name{text-transform:none;letter-spacing:0;font-size:13px;font-weight:600;color:var(--cw-ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.here{flex-shrink:0;padding:0 6px;border-radius:5px;background:var(--cw-accent-soft);color:var(--cw-accent);font-size:11px;letter-spacing:.4px;line-height:18px}.go{margin-left:auto;display:inline-flex;align-items:center;gap:4px;color:var(--cw-accent);font-weight:600}.note.current{background:var(--cw-accent-soft);border-color:var(--cw-accent-line)}.note{display:block;text-align:left;width:100%;border:1px solid transparent;background:var(--cw-surface);color:var(--cw-ink);border-radius:10px;padding:9px 10px;margin-bottom:2px}.note .note-header .via{color:var(--cw-muted);font-size:10px;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.note-header{margin-bottom:4px;gap:7px}.note .avatar{width:24px;height:24px;font-size:11px}.note .name{font-size:13px}.note .time{margin-left:0}.note-meta{margin-left:auto;display:inline-flex;align-items:center;gap:8px;font-size:12px;color:var(--cw-muted);white-space:nowrap;flex-shrink:0}.note-meta svg{width:12px;height:12px}.note:hover{background:var(--cw-canvas);border-color:var(--cw-line)}.note-header{display:flex;align-items:center;gap:8px;margin-bottom:9px}.avatar{display:inline-grid;place-items:center;width:29px;height:29px;border-radius:50%;background:var(--cw-avatar-bg);color:var(--cw-avatar-ink);font-size:12px;font-weight:700;flex-shrink:0;overflow:hidden}.avatar img{height:100%;width:100%;object-fit:cover}.name{font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.time{font-size:12px;color:var(--cw-muted);margin-left:auto;white-space:nowrap}.note-text{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:0 0 0 31px;line-height:1.5;font-size:14px;color:var(--cw-ink-soft);overflow-wrap:anywhere}.note-footer{display:flex;gap:6px;align-items:center;margin:6px 0 0 31px;color:var(--cw-muted);font-size:12px}.note-footer svg{width:12px;height:12px}.note-number{color:var(--cw-accent);font-weight:700}.status{display:inline-flex;align-items:center;gap:4px}.status.resolved{color:var(--cw-resolved-text)}.status-dot{width:5px;height:5px;background:var(--cw-accent);border-radius:50%;display:inline-block}.empty{height:100%;min-height:230px;display:flex;align-items:center;justify-content:center;text-align:center;padding:32px 16px}.empty-icon{position:relative;display:grid;place-items:center;margin:0 auto 18px;width:64px;height:64px;border:1px solid var(--cw-line);border-radius:21px;transform:rotate(-7deg);background:var(--cw-canvas);color:var(--cw-accent)}.empty-icon svg{width:27px;height:27px}.empty h3{font-size:17px;letter-spacing:-.25px;margin:0 0 8px}.empty p{font-size:14px;color:var(--cw-muted);line-height:1.7;margin:0 auto 18px;max-width:230px}.footer{display:flex;align-items:center;gap:9px;border-top:1px solid var(--cw-line);padding:14px 20px;background:var(--cw-canvas)}.footer .avatar{width:25px;height:25px;font-size:11px}.identity{border:0;background:transparent;text-align:left;padding:0;min-width:0;flex:1}.identity strong{display:block;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.identity small{font-size:12px;color:var(--cw-muted)}.storage-label{font-size:11px;color:var(--cw-muted);text-align:right;line-height:1.5;max-width:105px}.storage-dot{display:inline-block;width:5px;height:5px;background:var(--cw-resolved);border-radius:50%;margin-right:4px}.error{padding:12px 16px;margin:12px 20px;border:1px solid var(--cw-danger-line);background:var(--cw-danger-soft);color:var(--cw-danger);border-radius:10px;font-size:14px;overflow-wrap:anywhere}.error button{display:block;margin-top:8px}.notice{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:1;background:var(--cw-inverse);color:var(--cw-on-inverse);border:1px solid #ffffff20;border-radius:12px;padding:12px 15px;display:flex;align-items:center;gap:12px;font-size:14px;box-shadow:0 6px 30px #0002;pointer-events:auto;max-width:calc(100vw - 32px)}.notice kbd{font-size:12px;color:var(--cw-inverse-muted);border:1px solid #ffffff30;border-radius:4px;padding:2px 5px}.notice button{color:var(--cw-on-inverse)}.notice.beside-panel{left:calc(50% - 200px)}.notice.beside-panel.left{left:calc(50% + 200px)}.pins{position:fixed;inset:0;pointer-events:none}.pin{position:absolute;width:32px;height:36px;transform:translate(-50%,-100%);border:2px solid #fff;border-radius:15px 15px 15px 4px;background:var(--cw-accent);color:#fff;box-shadow:var(--cw-shadow-sm);display:grid;place-items:center;font-size:13px;font-weight:650;pointer-events:auto;transition:scale .15s}.pin:hover,.pin[aria-pressed=true]{scale:1.12}.pin.resolved{background:var(--cw-resolved)}.pin.highlight{animation:pulse 1s ease-out 2}.pin.hovered{animation:pulse 1s ease-out infinite;scale:1.12;opacity:1!important}.focus-ring.hover-ring{animation:none;border-style:dashed}.focus-ring{position:absolute;border:2px solid var(--cw-accent);border-radius:var(--cw-radius);background:color-mix(in srgb,var(--cw-accent) 6%,transparent);pointer-events:none;animation:enter .16s ease-out}.pick-ring{border-style:dashed;animation:none;transition:left .08s,top .08s,width .08s,height .08s}.pin:focus-visible{outline-offset:3px}.pin.preview{pointer-events:none;opacity:.7}.pin.movable{cursor:grab;touch-action:none}.pins.focused .pin:not([aria-pressed=true]):not(.dragging){opacity:.3;transition:opacity .15s,scale .15s}.pins.focused .pin:hover,.pins.focused .pin:focus-visible{opacity:1}.pin.dragging{cursor:grabbing;scale:1.15;pointer-events:none;box-shadow:var(--cw-shadow);transition:none;z-index:1}.detail-hint{margin:16px 20px 12px;color:var(--cw-muted);font-size:14px;line-height:1.6}.compose{padding:0 20px 20px}.compose textarea,.reply-bar textarea{scrollbar-width:none}
768
+ .panel{position:fixed;top:0;right:0;bottom:0;width:400px;max-width:100vw;display:flex;flex-direction:column;background:var(--cw-surface);border-left:1px solid var(--cw-line);box-shadow:var(--cw-shadow);pointer-events:auto;overflow:hidden}.panel.entering{animation:slide-in .2s ease-out}.panel.left{right:auto;left:0;border-left:0;border-right:1px solid var(--cw-line);--cw-slide:-24px}.detail{display:flex;flex-direction:column;flex:1;min-height:0}.detail.entering{animation:enter .16s ease-out}.detail:focus{outline:0}.head{display:flex;align-items:center;gap:10px;padding:22px 20px 16px}.brand-icon{width:34px;height:34px;display:grid;place-items:center;flex-shrink:0}.brand-icon svg{width:34px;height:34px}.heading{flex:1;min-width:0}.heading h2{font-size:19px;line-height:1.3;letter-spacing:-.4px;margin:0;font-weight:650}.eyebrow{font-size:12px;font-weight:700;letter-spacing:1.4px;color:var(--cw-muted);text-transform:uppercase;margin:0 0 4px}.subtext{color:var(--cw-muted);font-size:14px;margin:4px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button{width:32px;height:32px;display:inline-grid;place-items:center;background:transparent;border:0;border-radius:50%;flex-shrink:0}.icon-button:hover{background:var(--cw-surface-soft)}.icon-button.danger{color:var(--cw-danger)}.icon-button svg{width:16px;height:16px}.icon-button.outlined{width:42px;height:42px;border:1px solid var(--cw-line-strong);border-radius:50%;color:var(--cw-ink-soft)}.icon-button.outlined svg{width:18px;height:18px}.icon-button.busy:disabled{opacity:1;color:var(--cw-accent)}.icon-button.busy svg{animation:spin .9s linear infinite}.toolbar{padding:0 20px 16px;display:flex;gap:10px;align-items:center}.primary{border:0;border-radius:var(--cw-radius-pill);background:var(--cw-accent);color:#fff;display:inline-flex;gap:7px;align-items:center;justify-content:center;padding:10px 16px;font-size:14px;font-weight:550;white-space:nowrap;box-shadow:inset 0 -1px 0 #12172238;transition:filter .16s}.primary:hover{filter:brightness(.94)}.toolbar .primary{flex:1}.quiet{border:1px solid var(--cw-line-strong);background:var(--cw-surface);color:var(--cw-ink);border-radius:var(--cw-radius-pill);padding:9px 16px;font-size:14px}.quiet:hover{background:var(--cw-canvas)}.quiet.update{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0;color:var(--cw-accent);border-color:var(--cw-accent-line);background:var(--cw-accent-soft);font-weight:600}.quiet.update:hover{border-color:var(--cw-accent)}.quiet.update svg{width:14px;height:14px}.scope{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:0 20px 8px;padding:4px;background:var(--cw-surface-soft);border-radius:var(--cw-radius-pill)}.scope-option{display:flex;gap:7px;align-items:center;justify-content:center;border:0;background:transparent;border-radius:var(--cw-radius-pill);padding:8px 6px;font-size:14px;font-weight:550;color:var(--cw-muted);white-space:nowrap}.scope-option:hover{color:var(--cw-ink)}.scope-option[aria-pressed=true]{background:var(--cw-surface);color:var(--cw-ink);box-shadow:0 1px 3px #2024371f}.scope-option[aria-pressed=true] .count{background:var(--cw-accent-soft);color:var(--cw-accent)}.tabs{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin:0 20px 8px;padding:4px;border:1px solid var(--cw-line);border-radius:var(--cw-radius-pill)}.tab{display:flex;gap:7px;align-items:center;justify-content:center;border:0;border-radius:var(--cw-radius-pill);background:transparent;padding:7px 6px;color:var(--cw-muted);font-size:14px;font-weight:550;white-space:nowrap}.tab[aria-pressed=true]{color:var(--cw-on-inverse);background:var(--cw-inverse)}.tab[aria-pressed=true] .count{background:#ffffff2e;color:var(--cw-on-inverse)}.count{display:inline-block;background:var(--cw-surface-soft);min-width:20px;height:20px;padding:0 6px;border-radius:999px;font-size:12px;line-height:20px;text-align:center}.search{position:relative;flex:1;min-width:0}.search>svg{position:absolute;left:15px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--cw-subtle);pointer-events:none}.search input{border:1px solid var(--cw-line);border-radius:var(--cw-radius-pill);background:var(--cw-canvas);width:100%;height:42px;padding:0 40px 0 40px;color:var(--cw-ink);font-size:14px}.search input::placeholder{color:var(--cw-subtle)}.search input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.search-clear{position:absolute;right:7px;top:50%;transform:translateY(-50%);width:28px;height:28px;color:var(--cw-muted)}.search-clear svg{width:14px;height:14px}.people{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 20px 10px}.scope-row,.search-row{display:flex;align-items:center;gap:10px;margin:0 20px 10px}.scope-row .scope{flex:1;margin:0}.filter-toggle{position:relative}.filter-toggle.on{border-color:var(--cw-accent-line);color:var(--cw-accent);background:var(--cw-accent-soft)}.filter-toggle .count{position:absolute;top:-5px;right:-5px;min-width:18px;height:18px;padding:0 5px;line-height:18px;font-size:11px;background:var(--cw-accent);color:#fff;box-shadow:0 0 0 2px var(--cw-surface)}.filter-menu{margin:0 20px 10px;padding:14px;border:1px solid var(--cw-line);border-radius:20px;background:var(--cw-surface);box-shadow:var(--cw-shadow-sm)}.filter-group+.filter-group{margin-top:14px}.filter-group h3{margin:0 0 8px;font-size:11px;font-weight:650;letter-spacing:.06em;text-transform:uppercase;color:var(--cw-muted)}.filter-options{display:flex;flex-wrap:wrap;gap:6px}.option-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;border:1px solid var(--cw-line);background:var(--cw-surface);color:var(--cw-ink-soft);border-radius:999px;padding:3px 3px 3px 11px;font-size:13px;font-weight:600;white-space:nowrap}.option-chip>span:first-child{overflow:hidden;text-overflow:ellipsis}.option-chip:hover{background:var(--cw-canvas)}.option-chip[aria-pressed=true]{background:var(--cw-accent-soft);border-color:var(--cw-accent-line);color:var(--cw-accent)}.option-chip[aria-pressed=true] .count{background:var(--cw-surface);color:var(--cw-accent)}.chip-label{overflow:hidden;text-overflow:ellipsis;max-width:160px}.chip-remove{width:22px;height:22px;border-radius:50%;color:var(--cw-accent)}.chip-remove svg{width:12px;height:12px}.chip-remove:hover{background:var(--cw-surface)}.copy-field{display:flex;align-items:center;gap:6px;border:1px solid var(--cw-line);border-radius:var(--cw-radius-pill);padding:4px 4px 4px 14px;background:var(--cw-canvas)}.copy-field code{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--cw-ink)}.copy-button{width:32px;height:32px;border-radius:50%;color:var(--cw-muted)}.copy-button svg{width:15px;height:15px}.copy-button.done{color:var(--cw-resolved)}.mcp{display:flex;flex-direction:column;gap:18px}.mcp-intro{display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid var(--cw-accent-line);border-radius:20px;background:var(--cw-accent-soft)}.mcp-badge{display:grid;place-items:center;flex-shrink:0;width:36px;height:36px;border-radius:50%;background:var(--cw-accent);color:#fff}.mcp-badge svg{width:18px;height:18px}.mcp-intro h3{margin:0 0 3px;font-size:15px;font-weight:650;color:var(--cw-ink)}.mcp-intro p{margin:0;font-size:13px;line-height:1.5;color:var(--cw-ink-soft)}.mcp-block h4{margin:0 0 8px;font-size:11px;font-weight:650;letter-spacing:.06em;text-transform:uppercase;color:var(--cw-muted)}.mcp-steps{list-style:none;counter-reset:step;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}.mcp-steps li{counter-increment:step;display:flex;align-items:center;gap:10px;font-size:13.5px;line-height:1.45;color:var(--cw-ink-soft)}.mcp-steps li::before{content:counter(step);display:grid;place-items:center;flex-shrink:0;width:24px;height:24px;border-radius:50%;background:var(--cw-surface-soft);color:var(--cw-ink);font-size:12px;font-weight:650}.mcp-prompts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.mcp-prompts li{padding:9px 14px;border:1px solid var(--cw-line);border-radius:14px;background:var(--cw-canvas);font-size:13px;line-height:1.45;color:var(--cw-ink-soft)}.mcp-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding-top:2px}.mcp-foot p{margin:0;font-size:12px;color:var(--cw-muted)}.mcp-link{display:inline-flex;align-items:center;gap:6px;text-decoration:none;font-weight:600;padding:8px 14px;font-size:13px}.mcp-link svg{width:14px;height:14px}.filter-chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cw-accent-line);background:var(--cw-accent-soft);color:var(--cw-accent);border-radius:999px;padding:3px 3px 3px 11px;font-size:13px;font-weight:600;white-space:nowrap;max-width:100%}.filter-chip .count{background:var(--cw-surface);color:var(--cw-accent)}.filter-chip:hover{background:var(--cw-canvas)}.filter-chip[aria-pressed=true]{background:var(--cw-accent-soft);border-color:var(--cw-accent-line);color:var(--cw-accent)}.filter-chip[aria-pressed=true] .count{background:var(--cw-surface);color:var(--cw-accent)}.select{position:relative;display:inline-flex;min-width:0;max-width:220px;color:var(--cw-muted)}.select::after{content:'';position:absolute;right:12px;top:50%;width:6px;height:6px;border:solid currentColor;border-width:0 1.5px 1.5px 0;transform:translateY(-70%) rotate(45deg);pointer-events:none}.select select{appearance:none;-webkit-appearance:none;border:1px solid var(--cw-line);background:var(--cw-surface);color:var(--cw-ink-soft);border-radius:999px;padding:5px 30px 5px 11px;font-size:13px;font-weight:600;line-height:18px;max-width:100%;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.select select:hover{background:var(--cw-canvas)}.select.chosen{color:var(--cw-accent)}.select.chosen select{background:var(--cw-accent-soft);border-color:var(--cw-accent-line);color:var(--cw-accent)}.select option{color:var(--cw-ink);background:var(--cw-surface)}.link-button{margin-left:auto;border:0;background:transparent;color:var(--cw-accent);font-size:13px;font-weight:600;padding:4px 2px;white-space:nowrap}.link-button:hover{text-decoration:underline}mark.hit{background:color-mix(in srgb,var(--cw-accent) 22%,transparent);color:inherit;border-radius:3px;padding:0 1px;-webkit-box-decoration-break:clone;box-decoration-break:clone}.note-match{display:flex;align-items:center;gap:5px;margin:4px 0 0 31px;font-size:12.5px;line-height:1.5;color:var(--cw-muted);min-width:0}.note-match svg{width:12px;height:12px}.note-match-label{font-weight:600;color:var(--cw-ink-soft);white-space:nowrap;flex-shrink:0;max-width:45%;overflow:hidden;text-overflow:ellipsis}.note-match-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.list{flex:1;overflow:auto;overscroll-behavior:contain;padding:8px 12px 16px;min-height:0}.list-title{display:flex;align-items:center;gap:8px;padding:12px 8px 6px;color:var(--cw-muted);font-size:12px;letter-spacing:1.15px;text-transform:uppercase;font-weight:600}.page-path{text-transform:none;letter-spacing:0;font-size:13px;color:var(--cw-ink-soft);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.page-name{text-transform:none;letter-spacing:0;font-size:13px;font-weight:600;color:var(--cw-ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.here{flex-shrink:0;padding:0 6px;border-radius:5px;background:var(--cw-accent-soft);color:var(--cw-accent);font-size:11px;letter-spacing:.4px;line-height:18px}.go{margin-left:auto;display:inline-flex;align-items:center;gap:4px;color:var(--cw-accent);font-weight:600}.note.current{background:var(--cw-accent-soft);border-color:var(--cw-accent-line)}.note{display:block;text-align:left;width:100%;border:1px solid transparent;background:var(--cw-surface);color:var(--cw-ink);border-radius:10px;padding:9px 10px;margin-bottom:2px}.note .note-header .via{color:var(--cw-muted);font-size:10px;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.note-header{margin-bottom:4px;gap:7px}.note .avatar{width:24px;height:24px;font-size:11px}.note .name{font-size:13px}.note .time{margin-left:0}.note-meta{margin-left:auto;display:inline-flex;align-items:center;gap:8px;font-size:12px;color:var(--cw-muted);white-space:nowrap;flex-shrink:0}.note-meta svg{width:12px;height:12px}.note:hover{background:var(--cw-canvas);border-color:var(--cw-line)}.note-header{display:flex;align-items:center;gap:8px;margin-bottom:9px}.avatar{display:inline-grid;place-items:center;width:29px;height:29px;border-radius:50%;background:var(--cw-avatar-bg);color:var(--cw-avatar-ink);font-size:12px;font-weight:700;flex-shrink:0;overflow:hidden}.avatar img{height:100%;width:100%;object-fit:cover}.name{font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.time{font-size:12px;color:var(--cw-muted);margin-left:auto;white-space:nowrap}.note-text{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:0 0 0 31px;line-height:1.5;font-size:14px;color:var(--cw-ink-soft);overflow-wrap:anywhere}.note-footer{display:flex;gap:6px;align-items:center;margin:6px 0 0 31px;color:var(--cw-muted);font-size:12px}.note-footer svg{width:12px;height:12px}.note-number{color:var(--cw-accent);font-weight:700}.status{display:inline-flex;align-items:center;gap:4px}.status.resolved{color:var(--cw-resolved-text)}.status-dot{width:5px;height:5px;background:var(--cw-accent);border-radius:50%;display:inline-block}.empty{height:100%;min-height:230px;display:flex;align-items:center;justify-content:center;text-align:center;padding:32px 16px}.empty-icon{position:relative;display:grid;place-items:center;margin:0 auto 18px;width:64px;height:64px;border:1px solid var(--cw-line);border-radius:21px;transform:rotate(-7deg);background:var(--cw-canvas);color:var(--cw-accent)}.empty-icon svg{width:27px;height:27px}.empty h3{font-size:17px;letter-spacing:-.25px;margin:0 0 8px}.empty p{font-size:14px;color:var(--cw-muted);line-height:1.7;margin:0 auto 18px;max-width:230px}.footer{display:flex;align-items:center;gap:9px;border-top:1px solid var(--cw-line);padding:14px 20px;background:var(--cw-canvas)}.footer .avatar{width:25px;height:25px;font-size:11px}.identity{border:0;background:transparent;text-align:left;padding:0;min-width:0;flex:1}.identity strong{display:block;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.identity small{font-size:12px;color:var(--cw-muted)}.storage-label{font-size:11px;color:var(--cw-muted);text-align:right;line-height:1.5;max-width:105px}.storage-dot{display:inline-block;width:5px;height:5px;background:var(--cw-resolved);border-radius:50%;margin-right:4px}.error{padding:12px 16px;margin:12px 20px;border:1px solid var(--cw-danger-line);background:var(--cw-danger-soft);color:var(--cw-danger);border-radius:10px;font-size:14px;overflow-wrap:anywhere}.error button{display:block;margin-top:8px}.notice{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:1;background:var(--cw-inverse);color:var(--cw-on-inverse);border:1px solid #ffffff20;border-radius:12px;padding:12px 15px;display:flex;align-items:center;gap:12px;font-size:14px;box-shadow:0 6px 30px #0002;pointer-events:auto;max-width:calc(100vw - 32px)}.notice kbd{font-size:12px;color:var(--cw-inverse-muted);border:1px solid #ffffff30;border-radius:4px;padding:2px 5px}.notice button{color:var(--cw-on-inverse)}.notice.beside-panel{left:calc(50% - 200px)}.notice.beside-panel.left{left:calc(50% + 200px)}.pins{position:fixed;inset:0;pointer-events:none}.pin{position:absolute;width:32px;height:36px;transform:translate(-50%,-100%);border:2px solid #fff;border-radius:15px 15px 15px 4px;background:var(--cw-accent);color:#fff;box-shadow:var(--cw-shadow-sm);display:grid;place-items:center;font-size:13px;font-weight:650;pointer-events:auto;transition:scale .15s}.pin:hover,.pin[aria-pressed=true]{scale:1.12}.pin.resolved{background:var(--cw-resolved)}.pin .pin-photo{width:100%;height:100%;border-radius:inherit;background:transparent;color:inherit;font-size:inherit;box-shadow:none}.pin.resolved .pin-photo img{filter:grayscale(1);opacity:.6}.pin.highlight{animation:pulse 1s ease-out 2}.pin.hovered{animation:pulse 1s ease-out infinite;scale:1.12;opacity:1!important}.focus-ring.hover-ring{animation:none;border-style:dashed}.focus-ring{position:absolute;border:2px solid var(--cw-accent);border-radius:var(--cw-radius);background:color-mix(in srgb,var(--cw-accent) 6%,transparent);pointer-events:none;animation:enter .16s ease-out}.pick-ring{border-style:dashed;animation:none;transition:left .08s,top .08s,width .08s,height .08s}.pin:focus-visible{outline-offset:3px}.pin.preview{pointer-events:none;opacity:.7}.pin.movable{cursor:grab;touch-action:none}.pins.focused .pin:not([aria-pressed=true]):not(.dragging){opacity:.3;transition:opacity .15s,scale .15s}.pins.focused .pin:hover,.pins.focused .pin:focus-visible{opacity:1}.pin.dragging{cursor:grabbing;scale:1.15;pointer-events:none;box-shadow:var(--cw-shadow);transition:none;z-index:1}.detail-hint{margin:16px 20px 12px;color:var(--cw-muted);font-size:14px;line-height:1.6}.compose{padding:0 20px 20px}.compose textarea,.reply-bar textarea{scrollbar-width:none}
755
769
  .compose textarea::-webkit-scrollbar,.reply-bar textarea::-webkit-scrollbar{display:none}
756
770
  .compose textarea{width:100%;display:block;resize:vertical;min-height:120px;max-height:40vh;border:1px solid var(--cw-line);border-radius:var(--cw-radius);padding:12px;color:var(--cw-ink);background:var(--cw-canvas);font-size:16px;line-height:1.6}.compose textarea::placeholder{color:var(--cw-subtle)}.compose-bar{display:flex;align-items:center;gap:10px;justify-content:space-between;margin-top:12px}.compose-hint{font-size:11px;color:var(--cw-muted)}.thread-body{overflow:auto;overscroll-behavior:contain;flex:1;min-height:0;padding:18px 20px 4px}.message{padding:16px 0;border-bottom:1px solid var(--cw-line)}.message:first-child{padding-top:0}.message:last-child{border:0}.message-text{font-size:16px;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;margin:10px 0 0 37px}.thread-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 20px;border-top:1px solid var(--cw-line)}.resolve{display:inline-flex;align-items:center;gap:7px;white-space:nowrap;font-weight:600}.resolve svg{width:16px;height:16px;color:var(--cw-resolved)}.resolve:hover{border-color:var(--cw-resolved);background:var(--cw-resolved-soft);color:var(--cw-resolved-text)}.resolve.done svg{color:var(--cw-muted)}.resolve.done:hover{border-color:var(--cw-line-strong);background:var(--cw-canvas);color:var(--cw-ink)}.confirm-delete{display:flex;align-items:center;gap:8px}.danger-button{display:inline-flex;align-items:center;gap:6px;border:0;border-radius:var(--cw-radius-pill);background:var(--cw-danger);color:#fff;padding:10px 12px;font-size:14px;font-weight:600;white-space:nowrap}.danger-button svg{width:15px;height:15px}.detail-head{padding:12px 20px 16px;border-bottom:1px solid var(--cw-line)}.detail-bar{display:flex;align-items:center;justify-content:space-between;margin:0 -8px 12px -10px}.back-link{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;border-radius:var(--cw-radius-sm);padding:6px 10px;font-size:14px;font-weight:600;color:var(--cw-muted)}.back-link:hover{background:var(--cw-surface-soft);color:var(--cw-ink)}.back-link svg{width:16px;height:16px}.detail-title{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.detail-title h2{font-size:20px;line-height:1.3;letter-spacing:-.4px;margin:0;font-weight:650}.detail-head .subtext{white-space:normal}.chip{display:inline-flex;align-items:center;gap:6px;padding:1px 9px;border-radius:999px;background:var(--cw-accent-soft);color:var(--cw-accent);font-size:12px;font-weight:600;line-height:20px}.chip svg{width:12px;height:12px}.chip.resolved{background:var(--cw-resolved-soft);color:var(--cw-resolved-text)}.thread .compose{padding-top:12px;border-top:1px solid var(--cw-line)}.thread textarea{min-height:88px}.anchor-status{margin:0 20px 12px;padding:12px;background:var(--cw-canvas);border:1px solid var(--cw-line);border-radius:10px;color:var(--cw-muted);font-size:13px;line-height:1.55}.anchor-head{display:flex;align-items:flex-start;gap:10px;margin-bottom:8px}.anchor-icon{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border-radius:8px;background:var(--cw-warning-soft);color:var(--cw-warning)}.anchor-status.removed .anchor-icon{background:var(--cw-danger-soft);color:var(--cw-danger)}.anchor-icon svg{width:16px;height:16px}.anchor-titles{display:flex;flex-direction:column;min-width:0}.anchor-titles strong{color:var(--cw-ink);font-size:13px;font-weight:650;line-height:1.35}.anchor-target{color:var(--cw-ink-soft);overflow-wrap:anywhere}.identity-form{padding:0 20px 20px}.identity-form label{font-size:14px;font-weight:600;display:block;margin-bottom:7px}.identity-form input{width:100%;border:1px solid var(--cw-line);background:var(--cw-canvas);color:var(--cw-ink);padding:10px;border-radius:var(--cw-radius);margin-bottom:14px}.identity-form p{font-size:13px;color:var(--cw-muted);line-height:1.7;margin:0 0 14px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
757
771
  @keyframes enter{from{opacity:0;translate:0 5px}to{opacity:1;translate:0 0}}@keyframes slide-in{from{opacity:0;translate:var(--cw-slide,24px) 0}to{opacity:1;translate:0 0}}@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--cw-accent) 55%,transparent)}100%{box-shadow:0 0 0 16px transparent}}@keyframes spin{to{rotate:360deg}}@keyframes toast-out{to{opacity:0;translate:0 6px}}
758
772
  .toasts{position:fixed;right:24px;bottom:88px;width:320px;max-width:calc(100vw - 32px);display:flex;flex-direction:column;gap:8px;pointer-events:none}.toasts.left{right:auto;left:24px}.toasts.top{bottom:auto;top:88px;flex-direction:column-reverse}.toasts.beside-panel{right:424px;bottom:24px;max-width:calc(100vw - 448px)}.toasts.beside-panel.left{right:auto;left:424px}.toasts.beside-panel.top{top:24px;bottom:auto}.toast{display:flex;gap:10px;align-items:flex-start;background:var(--cw-inverse);color:var(--cw-on-inverse);border:1px solid #ffffff20;border-radius:var(--cw-radius-md,12px);padding:12px 14px;box-shadow:var(--cw-shadow,var(--cw-shadow));animation:enter .18s ease-out}.toast.leaving{animation:toast-out .2s ease-in forwards}.toast-icon{background:var(--cw-accent-soft,var(--cw-accent-soft));color:var(--cw-accent)}.toast-icon svg{width:15px;height:15px}.toast-body{min-width:0;flex:1}.toast-title{margin:0;font-size:14px;line-height:1.4;color:var(--cw-inverse-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast-title strong{color:var(--cw-on-inverse);font-weight:600}.toast-number{margin-left:6px;padding:0 6px;border-radius:5px;background:var(--cw-accent-soft,var(--cw-accent-soft));color:var(--cw-accent);font-size:12px;font-weight:650;line-height:18px;display:inline-block}.toast-text{margin:3px 0 0;font-size:14px;line-height:1.5;color:#fff;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
759
773
  .popover{position:fixed;left:0;top:0;z-index:1;width:320px;max-height:min(560px,calc(100vh - 24px));display:flex;flex-direction:column;background:var(--cw-surface);border:1px solid var(--cw-line);border-radius:var(--cw-radius-lg);box-shadow:var(--cw-shadow);pointer-events:auto;color:var(--cw-ink)}.popover>.detail{min-height:0}.popover .detail.entering{animation:pop .18s cubic-bezier(.2,.9,.3,1.15)}@keyframes pop{from{opacity:0;transform:translateY(6px) scale(.97)}}.popover.sheet{left:8px;right:8px;top:auto;bottom:8px;width:auto;max-height:75vh}
760
- .card-head .detail-bar{margin:0;padding:12px 12px 0 10px}.pop-head{position:relative;display:flex;align-items:center;gap:10px;padding:14px 10px 12px 14px}.panel .pop-head{padding:12px 14px 12px 20px}.pop-icon{display:grid;place-items:center;flex-shrink:0;width:30px;height:30px;border-radius:50%;background:var(--cw-accent-soft);color:var(--cw-accent)}.pop-icon svg{width:15px;height:15px}.pop-title{flex:1;min-width:0}.pop-title h2{margin:0;font-size:14px;font-weight:650;letter-spacing:-.2px;line-height:1.35}.pop-sub{margin:0;font-size:12px;line-height:1.5;color:var(--cw-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pop-head .icon-button{width:28px;height:28px;color:var(--cw-muted)}.pop-head .icon-button:hover{background:var(--cw-surface-soft);color:var(--cw-ink)}.pop-head .icon-button svg{width:16px;height:16px}.pop-head .more svg{stroke-width:3.2}
774
+ .card-head .detail-bar{margin:0;padding:12px 12px 0 10px}.pop-head{position:relative;display:flex;align-items:center;gap:10px;padding:14px 10px 12px 14px}.panel .pop-head{padding:12px 14px 12px 20px}.pop-title{flex:1;min-width:0}.pop-title h2{margin:0;font-size:14px;font-weight:650;letter-spacing:-.2px;line-height:1.35}.pop-sub{margin:0;font-size:12px;line-height:1.5;color:var(--cw-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pop-head .icon-button{width:28px;height:28px;color:var(--cw-muted)}.pop-head .icon-button:hover{background:var(--cw-surface-soft);color:var(--cw-ink)}.pop-head .icon-button svg{width:16px;height:16px}.pop-head .more svg{stroke-width:3.2}
761
775
  .pop-menu{position:absolute;top:46px;right:10px;z-index:2;min-width:190px;padding:4px;background:var(--cw-surface);border:1px solid var(--cw-line);border-radius:var(--cw-radius-md);box-shadow:var(--cw-shadow)}.menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border:0;border-radius:8px;background:transparent;font-size:13px;text-align:left;color:var(--cw-ink)}.menu-item:hover{background:var(--cw-surface-soft)}.menu-item svg{width:15px;height:15px;color:var(--cw-muted)}.menu-item.danger,.menu-item.danger svg{color:var(--cw-danger)}
762
- .thread .thread-body{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:0 14px}.panel .thread .thread-body{padding:0 20px}.thread .message{padding:12px 0;border-top:1px solid var(--cw-line);border-bottom:0}.thread .message:first-child{padding-top:12px}.thread .note-header{gap:8px;margin:0}.thread .avatar{width:26px;height:26px;font-size:10px}.thread .name{font-size:14px}.thread .time{font-size:12px}.thread .message-text{margin:4px 0 0 34px;font-size:14px;line-height:1.55;color:var(--cw-ink-soft)}.panel .thread .message-text{font-size:15px}
763
- .thread-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;border-top:1px solid var(--cw-line)}.panel .thread-actions{padding:12px 20px}.reply-bar{flex:1;min-width:0;display:flex;align-items:flex-end;gap:8px}.reply-bar textarea{flex:1;min-width:0;min-height:32px;max-height:120px;resize:none;padding:6px 10px;border:1px solid var(--cw-line-strong);border-radius:var(--cw-radius);background:var(--cw-surface);color:var(--cw-ink);font-size:13px;line-height:1.45;overflow:auto}.reply-bar textarea:focus-visible{outline:0;border-color:var(--cw-accent);box-shadow:0 0 0 3px var(--cw-accent-line)}.reply-bar textarea::placeholder{color:var(--cw-subtle)}.thread-actions .primary{padding:7px 12px;font-size:13px;border-radius:var(--cw-radius-pill);white-space:nowrap}.thread-actions .primary svg{width:14px;height:14px}.thread-actions .primary.resolve,.thread-actions .primary.resolve:hover{border:0;background:var(--cw-accent);color:#fff}.thread-actions .primary.resolve:hover{filter:brightness(.94)}.thread-actions .primary.resolve svg{color:currentColor}.thread-actions .status{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:600;color:var(--cw-resolved-text)}.thread-actions .status svg{width:14px;height:14px}.thread-actions .reopen{padding:6px 12px;font-size:13px;font-weight:500}
776
+ .thread .thread-body{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:0 14px}.panel .thread .thread-body{padding:0 20px}.thread .message{padding:12px 0;border-top:1px solid var(--cw-line);border-bottom:0}.thread .message:first-child{padding-top:12px}.thread .note-header{gap:8px;margin:0}.thread .avatar{width:30px;height:30px;font-size:11px}.thread .name{font-size:14px}.thread .time{font-size:12px}.thread .message-text{margin:4px 0 0 38px;font-size:14px;line-height:1.55;color:var(--cw-ink-soft)}.panel .thread .message-text{font-size:15px}
777
+ .thread-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;border-top:1px solid var(--cw-line)}.panel .thread-actions{padding:12px 20px}.reply-bar{flex:1;min-width:0;display:flex;align-items:flex-end;gap:8px}.reply-bar textarea{flex:1;min-width:0;min-height:32px;max-height:120px;resize:none;padding:6px 10px;border:1px solid var(--cw-line-strong);border-radius:var(--cw-radius);background:var(--cw-surface);color:var(--cw-ink);font-size:13px;line-height:1.45;overflow:auto}.reply-bar textarea:focus-visible{outline:0;border-color:var(--cw-accent);box-shadow:0 0 0 3px var(--cw-accent-line)}.reply-bar textarea::placeholder{color:var(--cw-subtle)}.thread-actions .primary{padding:7px 12px;font-size:13px;border-radius:var(--cw-radius-pill);white-space:nowrap}.thread-actions .primary svg{width:14px;height:14px}.thread-actions .primary.send{width:34px;height:34px;padding:0;border-radius:50%;flex-shrink:0}.thread-actions .primary.send svg{width:16px;height:16px}.thread-actions .primary.resolve{border:0;background:var(--cw-accent);color:#fff;transition:background .16s}.thread-actions .primary.resolve:hover{border:0;background:var(--cw-resolved);color:#fff;filter:none}.thread-actions .primary.resolve svg{color:currentColor}.thread-actions .status{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:600;color:var(--cw-resolved-text)}.thread-actions .status svg{width:14px;height:14px}.thread-actions .reopen{padding:6px 12px;font-size:13px;font-weight:500}
764
778
  .popover .anchor-status,.popover .error{margin:0 14px 10px}.new-comment .compose{margin:0 14px;padding:14px 0;border-top:1px solid var(--cw-line)}.panel .new-comment .compose{margin:0 20px;padding:16px 0 20px}.popover .compose textarea:focus-visible{outline:0;border-color:var(--cw-accent);box-shadow:0 0 0 3px var(--cw-accent-line)}.popover .new-comment .compose textarea{min-height:76px;font-size:14px}.popover .compose-hint{font-size:11px}
765
779
  .pin.below{transform:translate(-50%,0);border-radius:4px 15px 15px 15px}
766
780
  .pick-ring{border-style:solid;background:transparent}.head-actions{display:flex;align-items:center;gap:2px;margin-right:-6px}
@@ -781,7 +795,7 @@ const styles = `
781
795
  .menu-note{display:flex;align-items:center;gap:2px;margin:4px 0 0;padding:8px 10px 4px;border-top:1px solid var(--cw-line);font-size:12px;color:var(--cw-muted)}
782
796
  .footer{justify-content:space-between}
783
797
  .footer-note{font-size:13px;color:var(--cw-muted)}
784
- .panel:focus{outline:0}.settings-tabs{grid-template-columns:1fr 1fr;margin:0 0 12px}.settings-body{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:4px 20px 12px}
798
+ .panel:focus{outline:0}.settings-tabs{grid-template-columns:1fr 1fr;margin:14px 0 0}.settings-body{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:16px 20px 12px}
785
799
  .setting-group{padding:14px 0 6px}
786
800
  .setting-group+.setting-group{border-top:1px solid var(--cw-line)}
787
801
  .setting-group h3{margin:0 0 6px;font-size:12px;font-weight:650;letter-spacing:1.1px;text-transform:uppercase;color:var(--cw-muted)}
@@ -1431,7 +1445,9 @@ function describeElement(el) {
1431
1445
  }
1432
1446
  return info;
1433
1447
  }
1434
- const DEFAULT_FILTERS = Object.freeze({ status: "open", query: "", mine: false, author: "" });
1448
+ const DATE_FILTERS = [["any", "Any Time"], ["today", "Today"], ["yesterday", "Yesterday"], ["week", "Last 7 Days"], ["month", "Last 30 Days"]];
1449
+ const REPLY_FILTERS = [["any", "Any"], ["without", "No Replies"], ["with", "With Replies"]];
1450
+ const DEFAULT_FILTERS = Object.freeze({ status: "open", query: "", mine: false, author: "", date: "any", replies: "any" });
1435
1451
  const MAX_QUERY = 200, MAX_WORDS = 8;
1436
1452
  function foldChar(char) {
1437
1453
  return char.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "").toLowerCase();
@@ -1520,13 +1536,26 @@ function isMine(note, userId) {
1520
1536
  function statusMatches(comment, status) {
1521
1537
  return status === "all" || (status === "resolved" ? comment.resolved : !comment.resolved);
1522
1538
  }
1539
+ function lastActivity(comment) {
1540
+ return Math.max(new Date(comment.timestamp).getTime(), ...comment.replies.map((r) => new Date(r.timestamp).getTime()));
1541
+ }
1542
+ function dateMatches(comment, date2, now = /* @__PURE__ */ new Date()) {
1543
+ if (date2 === "any") return true;
1544
+ const at = lastActivity(comment), midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(), day = 864e5;
1545
+ if (date2 === "today") return at >= midnight;
1546
+ if (date2 === "yesterday") return at >= new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1).getTime() && at < midnight;
1547
+ return at >= now.getTime() - (date2 === "week" ? 7 : 30) * day;
1548
+ }
1549
+ function repliesMatch(comment, replies) {
1550
+ return replies === "any" || replies === "with" === comment.replies.length > 0;
1551
+ }
1523
1552
  function filterNotes(comments, filters, context) {
1524
1553
  const words2 = parseQuery(filters.query);
1525
- return comments.filter((c) => statusMatches(c, filters.status) && (!filters.mine || isMine(c, context.userId)) && (!filters.author || authorKey(c) === filters.author) && !!matchNote(c, words2, context));
1554
+ return comments.filter((c) => statusMatches(c, filters.status) && (!filters.mine || isMine(c, context.userId)) && (!filters.author || authorKey(c) === filters.author) && dateMatches(c, filters.date, context.now) && repliesMatch(c, filters.replies) && !!matchNote(c, words2, context));
1526
1555
  }
1527
1556
  function filterCounts(comments, filters, context) {
1528
1557
  const words2 = parseQuery(filters.query);
1529
- const searched = comments.filter((c) => !!matchNote(c, words2, context));
1558
+ const searched = comments.filter((c) => dateMatches(c, filters.date, context.now) && repliesMatch(c, filters.replies) && !!matchNote(c, words2, context));
1530
1559
  const byPerson = (c) => (!filters.mine || isMine(c, context.userId)) && (!filters.author || authorKey(c) === filters.author);
1531
1560
  const people = searched.filter(byPerson);
1532
1561
  const inStatus = searched.filter((c) => statusMatches(c, filters.status));
@@ -1548,8 +1577,11 @@ function filterCounts(comments, filters, context) {
1548
1577
  authors: [...authors.values()].sort((a, b) => a.name.localeCompare(b.name) || a.key.localeCompare(b.key))
1549
1578
  };
1550
1579
  }
1580
+ function filterCount(filters) {
1581
+ return Number(filters.mine || !!filters.author) + Number(filters.date !== "any") + Number(filters.replies !== "any");
1582
+ }
1551
1583
  function filtersActive(filters) {
1552
- return !!filters.query.trim() || filters.mine || !!filters.author;
1584
+ return !!filters.query.trim() || filterCount(filters) > 0;
1553
1585
  }
1554
1586
  function clearFilters(comments) {
1555
1587
  return { ...DEFAULT_FILTERS, status: comments.some((c) => !c.resolved) || !comments.length ? "open" : "all" };
@@ -1560,7 +1592,9 @@ function readFilters(value) {
1560
1592
  status: v.status === "resolved" || v.status === "all" ? v.status : "open",
1561
1593
  query: typeof v.query === "string" ? v.query.slice(0, MAX_QUERY) : "",
1562
1594
  mine: v.mine === true,
1563
- author: typeof v.author === "string" ? v.author.slice(0, 300) : ""
1595
+ author: typeof v.author === "string" ? v.author.slice(0, 300) : "",
1596
+ date: DATE_FILTERS.some(([d]) => d === v.date) ? v.date : "any",
1597
+ replies: REPLY_FILTERS.some(([r]) => r === v.replies) ? v.replies : "any"
1564
1598
  };
1565
1599
  }
1566
1600
  const THEMES = ["light", "dark", "system"];
@@ -1677,6 +1711,9 @@ class Overlay {
1677
1711
  /** Search, status, Mine and author for the list and the pins. Kept for this browser tab (session storage). */
1678
1712
  __publicField(this, "filters", { ...DEFAULT_FILTERS });
1679
1713
  __publicField(this, "scope", "page");
1714
+ /** Search and filters stay out of the way until the person opens them. */
1715
+ __publicField(this, "searchOpen", false);
1716
+ __publicField(this, "filterMenuOpen", false);
1680
1717
  __publicField(this, "filterKey");
1681
1718
  __publicField(this, "searchTimer");
1682
1719
  __publicField(this, "launcherKey");
@@ -3023,7 +3060,7 @@ ${this.currentPage()}`;
3023
3060
  heading(title, subtitle) {
3024
3061
  const head = element("div", "head");
3025
3062
  const brand = element("span", "brand-icon");
3026
- brand.append(icon("comment"));
3063
+ brand.append(logo());
3027
3064
  const heading = element("div", "heading");
3028
3065
  heading.append(element("h2", "", title));
3029
3066
  if (subtitle) heading.append(element("p", "subtext", subtitle));
@@ -3123,6 +3160,15 @@ ${this.currentPage()}`;
3123
3160
  this.render();
3124
3161
  (_a = this.shadow.querySelector(".detail")) == null ? void 0 : _a.focus({ preventScroll: true });
3125
3162
  }
3163
+ /** What a pin shows. "photo" falls back to initials for an author without a picture. */
3164
+ pinFace(label, name, url, n) {
3165
+ if (label === "photo" && url) {
3166
+ const face = avatar(name, url);
3167
+ face.classList.add("pin-photo");
3168
+ return [face];
3169
+ }
3170
+ return [document.createTextNode(label === "number" ? String(n) : initials(name))];
3171
+ }
3126
3172
  /** Header of a detail view: a back link and close on one bar, then the title. It reads as a step inside the list. */
3127
3173
  detailHeading(title, subtitle, extra) {
3128
3174
  const wrap = element("div", "detail-head");
@@ -3138,7 +3184,7 @@ ${this.currentPage()}`;
3138
3184
  return wrap;
3139
3185
  }
3140
3186
  /** Header of a comment card: icon, title, status line, a menu for less frequent actions, and close. */
3141
- cardHeading(title, subtitle, name, hint = "", remove) {
3187
+ cardHeading(title, subtitle, hint = "", remove) {
3142
3188
  const wrap = element("div", "card-head");
3143
3189
  if (this.docked) {
3144
3190
  const bar = element("div", "detail-bar");
@@ -3149,8 +3195,6 @@ ${this.currentPage()}`;
3149
3195
  }
3150
3196
  const head = element("div", "pop-head");
3151
3197
  if (hint) head.title = hint;
3152
- const tile = element("span", "pop-icon");
3153
- tile.append(icon(name));
3154
3198
  const text = element("div", "pop-title");
3155
3199
  text.append(element("h2", "", title), element("p", "pop-sub", subtitle));
3156
3200
  const menu = element("div", "pop-menu");
@@ -3167,7 +3211,7 @@ ${this.currentPage()}`;
3167
3211
  del.disabled = this.saving;
3168
3212
  menu.append(del);
3169
3213
  }
3170
- head.append(tile, text);
3214
+ head.append(text);
3171
3215
  if (menu.childElementCount) {
3172
3216
  const more = button("More actions", "icon-button more", () => {
3173
3217
  }, "more");
@@ -3212,9 +3256,37 @@ ${this.currentPage()}`;
3212
3256
  refresh.disabled = this.loading || this.saving;
3213
3257
  toolbar.append(add, refresh);
3214
3258
  panel.append(toolbar);
3215
- panel.append(this.scopeBar());
3216
3259
  const comments = this.scopeComments();
3217
- if (comments.length) panel.append(this.searchField(), this.peopleBar());
3260
+ const scopeRow = element("div", "scope-row");
3261
+ scopeRow.append(this.scopeBar());
3262
+ if (comments.length || this.searchOpen) {
3263
+ const toggle = button(
3264
+ this.searchOpen ? "Close search and filters" : "Search and filter",
3265
+ "icon-button outlined search-toggle",
3266
+ () => this.toggleSearch(),
3267
+ this.searchOpen ? "close" : "search"
3268
+ );
3269
+ toggle.setAttribute("aria-expanded", String(this.searchOpen));
3270
+ toggle.setAttribute("aria-keyshortcuts", "/");
3271
+ toggle.dataset.focus = "search-toggle";
3272
+ scopeRow.append(toggle);
3273
+ }
3274
+ panel.append(scopeRow);
3275
+ if (this.searchOpen) {
3276
+ const row = element("div", "search-row");
3277
+ const active = filterCount(this.filters);
3278
+ const filter = button(active ? `Filters, ${active} on` : "Filters", `icon-button outlined filter-toggle${active ? " on" : ""}`, () => {
3279
+ this.filterMenuOpen = !this.filterMenuOpen;
3280
+ this.render();
3281
+ }, "filter");
3282
+ filter.setAttribute("aria-expanded", String(this.filterMenuOpen));
3283
+ filter.dataset.focus = "filter-toggle";
3284
+ if (active) filter.append(element("span", "count", String(active)));
3285
+ row.append(this.searchField(), filter);
3286
+ panel.append(row);
3287
+ if (this.filterMenuOpen) panel.append(this.filterMenu());
3288
+ panel.append(this.peopleBar());
3289
+ }
3218
3290
  panel.append(this.statusTabs());
3219
3291
  if (this.error) panel.append(this.errorBox());
3220
3292
  panel.append(this.commentList(comments));
@@ -3224,12 +3296,15 @@ ${this.currentPage()}`;
3224
3296
  const target = event.composedPath()[0];
3225
3297
  if (event.key !== "/" || event.ctrlKey || event.metaKey || event.altKey || event.isComposing) return;
3226
3298
  if (target instanceof Element && target.closest("input,textarea,select")) return;
3227
- const field = this.shadow.querySelector(".search input");
3228
- if (field) {
3229
- event.preventDefault();
3230
- field.focus();
3231
- field.select();
3299
+ event.preventDefault();
3300
+ if (!this.searchOpen) {
3301
+ this.searchOpen = true;
3302
+ this.saveFilters();
3303
+ this.render();
3232
3304
  }
3305
+ const field = this.shadow.querySelector(".search input");
3306
+ field == null ? void 0 : field.focus();
3307
+ field == null ? void 0 : field.select();
3233
3308
  });
3234
3309
  return panel;
3235
3310
  }
@@ -3243,12 +3318,13 @@ ${this.currentPage()}`;
3243
3318
  if (!saved) return;
3244
3319
  this.filters = readFilters(saved);
3245
3320
  this.scope = saved.scope === "all" ? "all" : "page";
3321
+ this.searchOpen = saved.searchOpen === true || filtersActive(this.filters);
3246
3322
  } catch {
3247
3323
  }
3248
3324
  }
3249
3325
  saveFilters() {
3250
3326
  try {
3251
- sessionStorage.setItem(this.filterKey, JSON.stringify({ ...this.filters, scope: this.scope }));
3327
+ sessionStorage.setItem(this.filterKey, JSON.stringify({ ...this.filters, scope: this.scope, searchOpen: this.searchOpen }));
3252
3328
  } catch {
3253
3329
  }
3254
3330
  }
@@ -3275,6 +3351,7 @@ ${this.currentPage()}`;
3275
3351
  };
3276
3352
  swap(".scope", this.scopeBar());
3277
3353
  swap(".people", this.peopleBar());
3354
+ swap(".filter-menu", this.filterMenu());
3278
3355
  swap(".tabs", this.statusTabs());
3279
3356
  swap(".list", this.commentList(comments));
3280
3357
  this.pins.replaceChildren();
@@ -3345,38 +3422,83 @@ ${this.currentPage()}`;
3345
3422
  search.append(icon("search"), input, clear);
3346
3423
  return search;
3347
3424
  }
3348
- /** Mine, the author list and Clear Filters. */
3425
+ /** Opens search and filters, or closes them and clears what they filtered. The page tabs and status stay. */
3426
+ toggleSearch() {
3427
+ var _a;
3428
+ this.searchOpen = !this.searchOpen;
3429
+ this.filterMenuOpen = false;
3430
+ if (!this.searchOpen) this.filters = { ...DEFAULT_FILTERS, status: this.filters.status };
3431
+ this.saveFilters();
3432
+ this.render();
3433
+ if (!this.searchOpen) this.announceResults();
3434
+ if (this.searchOpen) (_a = this.shadow.querySelector(".search input")) == null ? void 0 : _a.focus();
3435
+ }
3436
+ /** How many notes a filter choice shows with the other filters and the search. */
3437
+ countWith(patch) {
3438
+ return filterNotes(this.scopeComments(), { ...this.filters, ...patch }, this.filterContext()).length;
3439
+ }
3440
+ /** The people a note can be filtered by: Mine for a signed-in person, then each author. */
3441
+ personChoices() {
3442
+ const hosted = this.storage instanceof HostedAdapter ? this.storage : null;
3443
+ const counts = filterCounts(this.scopeComments(), this.filters, this.filterContext());
3444
+ const choices = [{ label: "Anyone", patch: { mine: false, author: "" }, on: !this.filters.mine && !this.filters.author }];
3445
+ const mine = !hosted || hosted.signedIn;
3446
+ if (mine) choices.push({ label: "Mine", patch: { mine: true, author: "" }, on: this.filters.mine });
3447
+ const authors = counts.authors.filter((a) => !mine || a.key !== `id:${this.user.id}` || this.filters.author === a.key);
3448
+ if (this.filters.author && !authors.some((a) => a.key === this.filters.author)) authors.push({ key: this.filters.author, name: "Earlier author", count: 0 });
3449
+ for (const a of authors) choices.push({ label: a.name, patch: { mine: false, author: a.key }, on: this.filters.author === a.key });
3450
+ return choices;
3451
+ }
3452
+ /** The filter menu: person, last activity and replies. Each choice shows how many notes it leaves. */
3453
+ filterMenu() {
3454
+ const menu = element("div", "filter-menu");
3455
+ menu.setAttribute("role", "group");
3456
+ menu.setAttribute("aria-label", "Filters");
3457
+ const group = (title, choices, key2) => {
3458
+ const section = element("div", "filter-group");
3459
+ section.setAttribute("role", "group");
3460
+ section.setAttribute("aria-label", title);
3461
+ section.append(element("h3", "", title));
3462
+ const options = element("div", "filter-options");
3463
+ choices.forEach((choice, i) => {
3464
+ const count = this.countWith(choice.patch);
3465
+ const option = button(choice.label, "option-chip", () => this.setFilters(choice.patch));
3466
+ option.setAttribute("aria-pressed", String(choice.on));
3467
+ option.setAttribute("aria-label", `${title}: ${choice.label}, ${count} ${count === 1 ? "note" : "notes"}`);
3468
+ option.dataset.focus = `filter-${key2}-${i}`;
3469
+ option.append(element("span", "count", String(count)));
3470
+ options.append(option);
3471
+ });
3472
+ section.append(options);
3473
+ menu.append(section);
3474
+ };
3475
+ group("Person", this.personChoices(), "person");
3476
+ group("Last Activity", DATE_FILTERS.map(([date2, label]) => ({ label, patch: { date: date2 }, on: this.filters.date === date2 })), "date");
3477
+ group("Replies", REPLY_FILTERS.map(([replies, label]) => ({ label, patch: { replies }, on: this.filters.replies === replies })), "replies");
3478
+ return menu;
3479
+ }
3480
+ /** The filters in use, each as a chip with its count and a remove button. Hidden when none is set. */
3349
3481
  peopleBar() {
3350
3482
  const bar = element("div", "people");
3351
3483
  bar.setAttribute("role", "group");
3352
- bar.setAttribute("aria-label", "Filter by person");
3353
- const counts = filterCounts(this.scopeComments(), this.filters, this.filterContext());
3354
- const hosted = this.storage instanceof HostedAdapter ? this.storage : null;
3355
- if (!hosted || hosted.signedIn) {
3356
- const mine = button("Mine", "filter-chip", () => this.setFilters({ mine: !this.filters.mine, author: "" }));
3357
- mine.title = "Only notes you started";
3358
- mine.dataset.focus = "filter-mine";
3359
- mine.setAttribute("aria-label", `Mine, ${counts.mine} ${counts.mine === 1 ? "note" : "notes"}`);
3360
- mine.setAttribute("aria-pressed", String(this.filters.mine));
3361
- mine.append(element("span", "count", String(counts.mine)));
3362
- bar.append(mine);
3363
- }
3364
- const authors = counts.authors;
3365
- if (this.filters.author && !authors.some((a) => a.key === this.filters.author)) authors.push({ key: this.filters.author, name: "Earlier author", count: 0 });
3366
- if (authors.length > 1 || this.filters.author) {
3367
- const wrap = element("span", `select${this.filters.author ? " chosen" : ""}`);
3368
- const select = element("select");
3369
- select.setAttribute("aria-label", "Author");
3370
- select.dataset.focus = "filter-author";
3371
- select.append(new Option("All Authors", ""));
3372
- for (const author of authors) select.append(new Option(`${author.name} (${author.count})`, author.key));
3373
- select.value = this.filters.author;
3374
- select.addEventListener("change", () => this.setFilters({ author: select.value, mine: false }));
3375
- wrap.append(select);
3376
- bar.append(wrap);
3377
- }
3378
- if (filtersActive(this.filters)) {
3379
- const clear = button("Clear Filters", "link-button", () => this.clearAllFilters());
3484
+ bar.setAttribute("aria-label", "Filters in use");
3485
+ const none = { mine: false, author: "", date: "any", replies: "any" };
3486
+ const chip = (label, reset, key2) => {
3487
+ const own = Object.fromEntries(Object.entries(none).filter(([k]) => !(k in reset)));
3488
+ const count = this.countWith(own);
3489
+ const item = element("span", "filter-chip");
3490
+ item.append(element("span", "chip-label", label), element("span", "count", String(count)));
3491
+ const remove = button(`Remove filter: ${label}`, "icon-button chip-remove", () => this.setFilters(reset), "close");
3492
+ remove.dataset.focus = `chip-${key2}`;
3493
+ item.append(remove);
3494
+ bar.append(item);
3495
+ };
3496
+ const person = this.personChoices().find((c) => c.on && c.label !== "Anyone");
3497
+ if (person) chip(person.label, { mine: false, author: "" }, "person");
3498
+ if (this.filters.date !== "any") chip(DATE_FILTERS.find(([d]) => d === this.filters.date)[1], { date: "any" }, "date");
3499
+ if (this.filters.replies !== "any") chip(REPLY_FILTERS.find(([r]) => r === this.filters.replies)[1], { replies: "any" }, "replies");
3500
+ if (filterCount(this.filters) > 1) {
3501
+ const clear = button("Clear All", "link-button", () => this.setFilters({ mine: false, author: "", date: "any", replies: "any" }));
3380
3502
  clear.dataset.focus = "filter-clear";
3381
3503
  bar.append(clear);
3382
3504
  }
@@ -3602,7 +3724,7 @@ ${this.currentPage()}`;
3602
3724
  }
3603
3725
  newCommentForm() {
3604
3726
  const view = this.view("New comment", "new-comment");
3605
- view.append(this.cardHeading("New comment", `Commenting as ${this.user.name}`, "plus"));
3727
+ view.append(this.cardHeading("New comment", `Commenting as ${this.user.name}`));
3606
3728
  if (this.error) view.append(this.errorBox());
3607
3729
  view.append(this.composer(false));
3608
3730
  return view;
@@ -3614,7 +3736,6 @@ ${this.currentPage()}`;
3614
3736
  view.append(this.cardHeading(
3615
3737
  `Note #${this.pageComments().indexOf(comment) + 1}`,
3616
3738
  comment.resolved ? "Resolved conversation" : "Open conversation",
3617
- "comment",
3618
3739
  this.canMove(comment) ? "Drag its pin to move it." : "",
3619
3740
  canDelete ? () => this.deleteNote(comment) : void 0
3620
3741
  ));
@@ -3649,7 +3770,7 @@ ${this.currentPage()}`;
3649
3770
  field.value = this.replyText;
3650
3771
  field.dataset.focus = "reply";
3651
3772
  field.disabled = this.saving;
3652
- const send = button(this.saving ? "Saving…" : "Send Reply", "primary send", () => {
3773
+ const send = button(this.saving ? "Saving…" : "Send Reply", "primary send icon-button", () => {
3653
3774
  }, "arrow");
3654
3775
  send.type = "submit";
3655
3776
  const resolve = button("Mark as Resolved", "primary resolve", () => this.toggleResolved(comment), "check");
@@ -3692,13 +3813,19 @@ ${this.currentPage()}`;
3692
3813
  /** Display choices for this project. Each person has their own, saved in this browser. */
3693
3814
  settingsView() {
3694
3815
  const view = this.view("Settings", "settings");
3695
- view.append(this.detailHeading("Settings", "Your choices for this project. Other people keep their own."));
3816
+ const header = this.detailHeading("Settings", "Only for you. Others keep their own.");
3817
+ view.append(header);
3696
3818
  const current = this.settings, body = element("div", "settings-body");
3697
3819
  const tabs = element("div", "tabs settings-tabs");
3698
3820
  tabs.setAttribute("role", "group");
3699
3821
  tabs.setAttribute("aria-label", "Settings section");
3700
- for (const [id, label2] of [["general", "General"], ["appearance", "Appearance"]]) {
3701
- const tab = button(label2, "tab", () => {
3822
+ const hosted = this.storage instanceof HostedAdapter ? this.storage : null;
3823
+ if (!hosted && this.settingsTab === "mcp") this.settingsTab = "general";
3824
+ const sections = [["general", "General"], ["appearance", "Appearance"]];
3825
+ if (hosted) sections.push(["mcp", "MCP"]);
3826
+ tabs.style.gridTemplateColumns = `repeat(${sections.length},1fr)`;
3827
+ for (const [id, label] of sections) {
3828
+ const tab = button(label, "tab", () => {
3702
3829
  this.settingsTab = id;
3703
3830
  this.render();
3704
3831
  });
@@ -3706,26 +3833,26 @@ ${this.currentPage()}`;
3706
3833
  tab.dataset.focus = `settings-tab-${id}`;
3707
3834
  tabs.append(tab);
3708
3835
  }
3709
- body.append(tabs);
3836
+ header.append(tabs);
3710
3837
  const section = (title, tab) => {
3711
3838
  const group = element("section", "setting-group");
3712
3839
  group.append(element("h3", "", title));
3713
3840
  if (tab === this.settingsTab) body.append(group);
3714
3841
  return group;
3715
3842
  };
3716
- const row = (group, label2, hint, control) => {
3843
+ const row = (group, label, hint, control) => {
3717
3844
  const line = element("div", "setting");
3718
3845
  const text = element("div", "setting-text");
3719
- text.append(element("strong", "", label2));
3846
+ text.append(element("strong", "", label));
3720
3847
  if (hint) text.append(element("small", "", hint));
3721
3848
  line.append(text, control);
3722
3849
  group.append(line);
3723
3850
  return line;
3724
3851
  };
3725
- const choice = (name, label2, options) => {
3852
+ const choice = (name, label, options) => {
3726
3853
  const group = element("div", "segmented");
3727
3854
  group.setAttribute("role", "group");
3728
- group.setAttribute("aria-label", label2);
3855
+ group.setAttribute("aria-label", label);
3729
3856
  for (const [value, text] of options) {
3730
3857
  const option = button(text, "segment", () => this.setSetting(name, value));
3731
3858
  option.setAttribute("aria-pressed", String(current[name] === value));
@@ -3734,13 +3861,13 @@ ${this.currentPage()}`;
3734
3861
  }
3735
3862
  return group;
3736
3863
  };
3737
- const toggle = (name, label2, disabled = false) => {
3864
+ const toggle = (name, label, disabled = false) => {
3738
3865
  const on = current[name] === true;
3739
3866
  const control = element("button", "switch");
3740
3867
  control.type = "button";
3741
3868
  control.setAttribute("role", "switch");
3742
3869
  control.setAttribute("aria-checked", String(on));
3743
- control.setAttribute("aria-label", label2);
3870
+ control.setAttribute("aria-label", label);
3744
3871
  control.dataset.focus = `set-${name}`;
3745
3872
  control.disabled = disabled;
3746
3873
  control.addEventListener("click", () => this.setSetting(name, !on));
@@ -3752,8 +3879,12 @@ ${this.currentPage()}`;
3752
3879
  const sample = element("div", `pin-sample${current.pinSize === "small" ? " small" : ""}${current.resolvedPins === "gray" ? " resolved-gray" : ""}`);
3753
3880
  sample.setAttribute("aria-hidden", "true");
3754
3881
  sample.style.setProperty("--cw-accent", this.pinColor());
3755
- const label = (n) => current.pinLabel === "initials" ? initials(this.user.name) : String(n);
3756
- sample.append(element("span", "pin", label(1)), element("span", "pin resolved", label(2)), element("span", "pin-sample-text", "Open and resolved"));
3882
+ const samplePin = (n, cls) => {
3883
+ const pin = element("span", cls);
3884
+ pin.append(...this.pinFace(current.pinLabel, this.user.name, this.user.avatarUrl, n));
3885
+ return pin;
3886
+ };
3887
+ sample.append(samplePin(1, "pin"), samplePin(2, "pin resolved"), element("span", "pin-sample-text", "Open and resolved"));
3757
3888
  pins.append(sample);
3758
3889
  const swatches = element("div", "swatches");
3759
3890
  swatches.setAttribute("role", "group");
@@ -3771,7 +3902,7 @@ ${this.currentPage()}`;
3771
3902
  row(pins, "Color", current.pinColor === "site" ? "The color of this site" : PIN_COLORS[current.pinColor].label, swatches).classList.add("wide");
3772
3903
  row(pins, "Resolved pins", "", choice("resolvedPins", "Resolved pins", [["green", "Green"], ["gray", "Gray"]]));
3773
3904
  row(pins, "Size", "", choice("pinSize", "Pin size", [["regular", "Regular"], ["small", "Small"]]));
3774
- row(pins, "Label", "", choice("pinLabel", "Pin label", [["number", "Number"], ["initials", "Initials"]]));
3905
+ row(pins, "Label", "", choice("pinLabel", "Pin label", [["number", "Number"], ["initials", "Initials"], ["photo", "Photo"]]));
3775
3906
  const floating = section("Comments button", "appearance");
3776
3907
  const buttonSample = element("div", "launcher-sample");
3777
3908
  buttonSample.setAttribute("aria-hidden", "true");
@@ -3812,11 +3943,64 @@ ${this.currentPage()}`;
3812
3943
  row(alerts, "New activity", "Show a short notice when others comment or reply.", toggle("notifications", "Show new activity"));
3813
3944
  const keys = shortcutLabel(this.config.keyboardShortcut ?? "", /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent));
3814
3945
  row(alerts, "Keyboard shortcut", this.shortcut ? `${keys} opens and closes comments.` : "This site has no shortcut.", toggle("shortcut", "Keyboard shortcut", !this.shortcut));
3946
+ if (hosted && this.settingsTab === "mcp") body.append(this.mcpSettings(hosted.base));
3815
3947
  const foot = element("div", "settings-foot");
3948
+ foot.hidden = this.settingsTab === "mcp";
3816
3949
  foot.append(element("span", "", "Saved in this browser"), button("Reset to Defaults", "quiet", () => this.resetSettings()));
3817
3950
  view.append(body, foot);
3818
3951
  return view;
3819
3952
  }
3953
+ /** The MCP tab: what it does, the server address to copy, how to connect, and prompts to try. */
3954
+ mcpSettings(base) {
3955
+ const endpoint = `${base}/mcp`, wrap = element("div", "mcp");
3956
+ const intro = element("div", "mcp-intro"), badge = element("span", "mcp-badge");
3957
+ badge.append(icon("sparkle"));
3958
+ const copyText = element("div");
3959
+ copyText.append(
3960
+ element("h3", "", "Connect an AI agent"),
3961
+ element("p", "", "Claude, Cursor and other agents can read, answer and resolve the comments of this project.")
3962
+ );
3963
+ intro.append(badge, copyText);
3964
+ const block = (title) => {
3965
+ const group = element("section", "mcp-block");
3966
+ group.append(element("h4", "", title));
3967
+ wrap.append(group);
3968
+ return group;
3969
+ };
3970
+ wrap.append(intro);
3971
+ const address = element("div", "copy-field"), code = element("code", "", endpoint);
3972
+ const copy = button("Copy server address", "icon-button copy-button", async () => {
3973
+ var _a;
3974
+ try {
3975
+ await navigator.clipboard.writeText(endpoint);
3976
+ this.announce("Server address copied.");
3977
+ copy.classList.add("done");
3978
+ setTimeout(() => copy.classList.remove("done"), 1500);
3979
+ } catch {
3980
+ (_a = getSelection()) == null ? void 0 : _a.selectAllChildren(code);
3981
+ }
3982
+ }, "copy");
3983
+ copy.dataset.focus = "mcp-copy";
3984
+ address.append(code, copy);
3985
+ block("Server address").append(address);
3986
+ const steps = element("ol", "mcp-steps");
3987
+ for (const step of ["Add the address as a remote MCP server in your agent.", "Sign in and allow access to this project.", "Ask the agent to work on the comments."])
3988
+ steps.append(element("li", "", step));
3989
+ block("How to connect").append(steps);
3990
+ const prompts = element("ul", "mcp-prompts");
3991
+ for (const prompt of ["Fix the open comments on this page.", "Summarize the feedback from this week.", "Reply to each comment when its fix is done."])
3992
+ prompts.append(element("li", "", `“${prompt}”`));
3993
+ block("Try asking").append(prompts);
3994
+ const foot = element("div", "mcp-foot");
3995
+ const more = element("a", "quiet mcp-link");
3996
+ more.append(element("span", "", "Open MCP Settings"), icon("open"));
3997
+ more.href = `${base}/app/mcp`;
3998
+ more.target = "_blank";
3999
+ more.rel = "noopener";
4000
+ foot.append(more, element("p", "", "MCP needs a plan that includes it."));
4001
+ wrap.append(foot);
4002
+ return wrap;
4003
+ }
3820
4004
  /** Colors the floating button with the person's choice. The default button keeps its own dark and open styles. */
3821
4005
  paintLauncher(launcher) {
3822
4006
  const choice = this.settings.buttonColor;
@@ -3891,7 +4075,7 @@ ${this.currentPage()}`;
3891
4075
  pin.title += ". Drag to move.";
3892
4076
  pin.addEventListener("pointerdown", (event) => this.startPinDrag(event, comment));
3893
4077
  }
3894
- pin.replaceChildren(document.createTextNode(settings.pinLabel === "initials" ? initials(comment.author) : String(index + 1)));
4078
+ pin.replaceChildren(...this.pinFace(settings.pinLabel, comment.author, comment.avatarUrl, index + 1));
3895
4079
  pin.dataset.id = comment.id;
3896
4080
  pin.setAttribute("aria-pressed", String(this.activeId === comment.id));
3897
4081
  this.pins.append(pin);