privateboard 0.1.36 → 0.1.38

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.
@@ -64,7 +64,7 @@
64
64
  flex-direction: column;
65
65
  gap: 4px;
66
66
  font-family: var(--sans, system-ui), sans-serif;
67
- font-size: 13px;
67
+ font-size: 14px;
68
68
  color: var(--text);
69
69
  }
70
70
  .mention-picker[hidden] { display: none; }
@@ -304,7 +304,7 @@
304
304
  }
305
305
  .na-avatar-regen .icon {
306
306
  font-family: var(--mono);
307
- font-size: 13px;
307
+ font-size: 14px;
308
308
  font-weight: 400;
309
309
  line-height: 1;
310
310
  }
@@ -362,7 +362,7 @@
362
362
  color: var(--lime);
363
363
  }
364
364
  .na-model-trigger .trigger-l .name {
365
- font-size: 13px;
365
+ font-size: 14px;
366
366
  font-weight: 700;
367
367
  color: var(--text);
368
368
  letter-spacing: -0.003em;
@@ -779,7 +779,7 @@
779
779
  border: none;
780
780
  background: transparent;
781
781
  font-family: var(--font-human);
782
- font-size: 13px;
782
+ font-size: 14px;
783
783
  line-height: 1.5;
784
784
  color: var(--text);
785
785
  outline: none;
@@ -951,7 +951,7 @@
951
951
  margin-top: 6px;
952
952
  align-self: flex-start;
953
953
  }
954
- .new-agent-overlay .na-rule-add .plus { color: var(--lime); font-size: 13px; line-height: 1; }
954
+ .new-agent-overlay .na-rule-add .plus { color: var(--lime); font-size: 14px; line-height: 1; }
955
955
  .new-agent-overlay .na-rule-add:hover { border-color: var(--lime); color: var(--lime); border-style: solid; }
956
956
  .new-agent-overlay .na-rule-add[disabled],
957
957
  .new-agent-overlay .na-rule-add.at-cap {
@@ -1119,7 +1119,7 @@
1119
1119
  border: 0.5px solid var(--line-bright);
1120
1120
  color: var(--text-faint);
1121
1121
  font-family: var(--mono);
1122
- font-size: 13px;
1122
+ font-size: 14px;
1123
1123
  line-height: 1;
1124
1124
  cursor: pointer;
1125
1125
  display: flex;
@@ -1352,7 +1352,7 @@
1352
1352
  .new-agent-overlay .na-textarea-wrap.tall textarea {
1353
1353
  min-height: 220px;
1354
1354
  font-family: var(--mono);
1355
- font-size: 13px;
1355
+ font-size: 14px;
1356
1356
  line-height: 1.5;
1357
1357
  resize: vertical;
1358
1358
  overflow-y: auto;
@@ -1382,7 +1382,7 @@
1382
1382
  font-weight: 700;
1383
1383
  letter-spacing: -0.005em;
1384
1384
  font-family: var(--font-human);
1385
- font-size: 13px;
1385
+ font-size: 14px;
1386
1386
  }
1387
1387
  .na-summary-handle { color: var(--lime); }
1388
1388
  .na-foot-sep { color: var(--text-faint); }
@@ -109,7 +109,7 @@
109
109
  }
110
110
  .onb-deck {
111
111
  font-family: var(--font-human, var(--mono));
112
- font-size: 13px;
112
+ font-size: 14px;
113
113
  color: var(--text-soft);
114
114
  line-height: 1.55;
115
115
  margin-top: 8px;
@@ -179,7 +179,7 @@
179
179
  content: ">";
180
180
  color: var(--lime);
181
181
  font-weight: 700;
182
- font-size: 13px;
182
+ font-size: 14px;
183
183
  font-family: var(--mono);
184
184
  padding: 9px 0 0 11px;
185
185
  align-self: flex-start;
@@ -332,7 +332,7 @@
332
332
  }
333
333
  .onb-voice-pitch {
334
334
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
335
- font-size: 13px;
335
+ font-size: 14px;
336
336
  line-height: 1.6;
337
337
  color: var(--text-soft);
338
338
  margin: 0;
@@ -393,7 +393,7 @@
393
393
  }
394
394
  .onb-cast-next-arrow {
395
395
  font-family: var(--mono);
396
- font-size: 13px;
396
+ font-size: 14px;
397
397
  font-weight: 700;
398
398
  color: var(--text-faint);
399
399
  align-self: center;
@@ -592,7 +592,7 @@
592
592
  color: var(--text-dim);
593
593
  cursor: pointer;
594
594
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
595
- font-size: 13px;
595
+ font-size: 14px;
596
596
  font-weight: 500;
597
597
  letter-spacing: -0.005em;
598
598
  line-height: 1.2;
@@ -80,9 +80,10 @@
80
80
  white-space: nowrap;
81
81
  }
82
82
  /* Adjourned-room state · Probe / Second hide (they post to a
83
- closed room), but Save stays — bookmarking from a finished
84
- session is a primary use case. */
85
- .qcta.qcta-readonly .qcta-btn:not(.qcta-btn-save) { display: none; }
83
+ closed room), but Save and Thread stay — bookmarking and opening a
84
+ private 1:1 follow-up thread are both primary review-mode uses
85
+ (a thread spawns its own live room, independent of the parent). */
86
+ .qcta.qcta-readonly .qcta-btn:not(.qcta-btn-save):not(.qcta-btn-thread) { display: none; }
86
87
  .qcta.qcta-readonly .qcta-hint { display: inline-flex; align-items: center; }
87
88
 
88
89
  /* ─── Save toast ─────────────────────────────────────────────
@@ -220,7 +221,7 @@
220
221
  content: ">";
221
222
  color: var(--lime);
222
223
  font-weight: 700;
223
- font-size: 13px;
224
+ font-size: 14px;
224
225
  font-family: var(--mono, "Inter", system-ui, sans-serif);
225
226
  padding: 9px 0 0 11px;
226
227
  align-self: flex-start;
@@ -160,8 +160,8 @@
160
160
  cta.className = "qcta";
161
161
  cta.setAttribute("role", "toolbar");
162
162
  const t = lang() === "zh"
163
- ? { ask: "追问", love: "附议", save: "收藏" }
164
- : { ask: "Probe", love: "Second", save: "Save" };
163
+ ? { ask: "追问", love: "附议", save: "收藏", thread: "私聊" }
164
+ : { ask: "Probe", love: "Second", save: "Save", thread: "Thread" };
165
165
  // Inline chat-bubble SVG · uses currentColor so it inherits the
166
166
  // hover lime / base text colour like the ★ glyph does.
167
167
  const askIcon = `
@@ -176,6 +176,17 @@
176
176
  <path d="M3.5 1.5 H10.5 V12.5 L7 9.5 L3.5 12.5 Z"/>
177
177
  </svg>
178
178
  `;
179
+ // Reply curve · matches `_threadTriggerIconSvg` in app.js so the
180
+ // selection bar's Thread button reads as the same action as the
181
+ // per-bubble msg-toolbar reply icon. Both surfaces route into
182
+ // `openThreadWith` — selection bar additionally seeds the
183
+ // composer with a quote of the selected text.
184
+ const threadIcon = `
185
+ <svg viewBox="0 0 14 14" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
186
+ <path d="M5 10 L2 7 L5 4"/>
187
+ <path d="M2 7 H10 a2 2 0 0 1 2 2 V11"/>
188
+ </svg>
189
+ `;
179
190
  cta.innerHTML = `
180
191
  <button type="button" class="qcta-btn" data-qcta="ask">
181
192
  <span class="ico">${askIcon}</span><span>${t.ask}</span>
@@ -183,6 +194,9 @@
183
194
  <button type="button" class="qcta-btn" data-qcta="second">
184
195
  <span class="ico">★</span><span>${t.love}</span>
185
196
  </button>
197
+ <button type="button" class="qcta-btn qcta-btn-thread" data-qcta="thread" title="${t.thread}">
198
+ <span class="ico">${threadIcon}</span><span>${t.thread}</span>
199
+ </button>
186
200
  <button type="button" class="qcta-btn qcta-btn-save" data-qcta="save" title="Save to Notes · S">
187
201
  <span class="ico">${saveIcon}</span><span>${t.save}</span>
188
202
  </button>
@@ -197,15 +211,18 @@
197
211
  if (!btn) return;
198
212
  const action = btn.getAttribute("data-qcta");
199
213
  // Read-only state (adjourned rooms) blocks Probe / Second since
200
- // they post messages to a closed room. Save is exempt — the
201
- // user is bookmarking for personal review, not interacting.
202
- if (cta.classList.contains("qcta-readonly") && action !== "save") return;
214
+ // they post messages to the closed parent room. Save and Thread
215
+ // are exempt — Save is a personal bookmark, and a Thread spawns
216
+ // its own live 1:1 room (server doesn't gate thread-create on the
217
+ // parent's status), so review-mode follow-ups stay possible.
218
+ if (cta.classList.contains("qcta-readonly") && action !== "save" && action !== "thread") return;
203
219
  const sel = lastSelection;
204
220
  hideCTA();
205
221
  if (!sel || !sel.text) return;
206
222
  if (action === "ask") openAskOverlay(sel);
207
223
  else if (action === "second") submitSecond(sel);
208
224
  else if (action === "save") submitSave(sel);
225
+ else if (action === "thread") openSelectionThread(sel);
209
226
  });
210
227
  document.body.appendChild(cta);
211
228
  return cta;
@@ -434,6 +451,34 @@
434
451
  routeSend(body, mentions);
435
452
  }
436
453
 
454
+ // ── Selection → private thread ──────────────────────────────
455
+ // Opens (or restores) a 1:1 thread window with the quoted director
456
+ // and pre-seeds the thread's composer textarea with the markdown
457
+ // blockquote of the selected text + a blank line. The user then
458
+ // types their follow-up question and sends; the director's reply
459
+ // sees the quote inline as the user's first thread message.
460
+ // Threads stay private to the user + this one director — the rest
461
+ // of the room never sees what was selected or discussed below.
462
+ async function openSelectionThread(sel) {
463
+ const app = window.app;
464
+ if (!app || typeof app.openThreadWith !== "function") return;
465
+ if (!sel.directorId) return;
466
+ // Stash the selected quote so the thread window mount can find
467
+ // it AFTER the async POST + DOM build settle. Keyed by director
468
+ // id so concurrent clicks on different bubbles don't overwrite
469
+ // each other. Consumed (and cleared) by mountThreadWindow.
470
+ app._threadPendingQuote = app._threadPendingQuote || {};
471
+ app._threadPendingQuote[sel.directorId] = {
472
+ text: sel.text,
473
+ directorName: sel.directorName || "",
474
+ };
475
+ try {
476
+ await app.openThreadWith(sel.directorId);
477
+ } catch (e) {
478
+ try { console.warn("[qcta-thread] open failed:", e); } catch (_) {}
479
+ }
480
+ }
481
+
437
482
  // ── Save to Notes ─────────────────────────────────────────────
438
483
  // POST /api/notes with quote + sentence-based context + char
439
484
  // offsets. No room interaction — this is a personal bookmark.
@@ -5041,19 +5041,20 @@
5041
5041
  const intro = typeof parsed.intro === "string" ? parsed.intro.trim() : "";
5042
5042
  const chairSynthesis = typeof parsed.chairSynthesis === "string" ? parsed.chairSynthesis.trim() : "";
5043
5043
 
5044
- // Resolve director id → display name from the rendered member
5045
- // list above (the brief renderer attaches `data-director-id` to
5046
- // each cover-byline author chip; fall back to the id when no
5047
- // mapping is found).
5044
+ // Resolve director id → display name from the global stash that
5045
+ // `load()` populates from state.members + historicalMembers +
5046
+ // chair. Falls back to scanning `[data-director-id]` chips for
5047
+ // legacy / forward-compat (none currently rendered, but cheap),
5048
+ // and finally to the id itself when nothing matches.
5048
5049
  const directorNameById = (() => {
5049
- const out = {};
5050
+ const out = Object.assign({}, window.__reportAgentNames || {});
5050
5051
  try {
5051
5052
  const chips = document.querySelectorAll("[data-director-id]");
5052
5053
  chips.forEach((el) => {
5053
5054
  const id = el.getAttribute("data-director-id");
5054
- if (id && el.textContent) out[id] = el.textContent.trim();
5055
+ if (id && el.textContent && !out[id]) out[id] = el.textContent.trim();
5055
5056
  });
5056
- } catch (_) { /* during pre-paint, no chips yet fall through */ }
5057
+ } catch (_) { /* defensive · DOM may not be ready */ }
5057
5058
  return out;
5058
5059
  })();
5059
5060
  const dn = (id) => directorNameById[id] || id || "—";
@@ -6420,6 +6421,25 @@
6420
6421
  const j = await stateRes.json();
6421
6422
  room = j.room;
6422
6423
  members = j.members || [];
6424
+ // Author-id → display-name map · drives `renderViewsCompared`'s
6425
+ // director chips ("Every director on the table", alignment
6426
+ // cards, divergence sides). Earlier the helper scraped
6427
+ // `[data-director-id]` chips off the rendered DOM, but no
6428
+ // chip ever carried that attribute — the lookup always fell
6429
+ // through and the chips printed raw agent ids like
6430
+ // `1khezh9da65g`. Stash a real lookup on window from BOTH
6431
+ // active members and historicalMembers (excused directors
6432
+ // still appear in views-compared if they participated) plus
6433
+ // the chair (occasionally referenced too).
6434
+ const nameMap = {};
6435
+ for (const m of (j.members || [])) {
6436
+ if (m && m.id && m.name) nameMap[m.id] = m.name;
6437
+ }
6438
+ for (const m of (j.historicalMembers || [])) {
6439
+ if (m && m.id && m.name && !nameMap[m.id]) nameMap[m.id] = m.name;
6440
+ }
6441
+ if (j.chair && j.chair.id && j.chair.name) nameMap[j.chair.id] = j.chair.name;
6442
+ window.__reportAgentNames = nameMap;
6423
6443
  } else {
6424
6444
  members = [];
6425
6445
  }
@@ -218,7 +218,7 @@ html.is-electron-mac .room-settings-overlay * {
218
218
  }
219
219
  .rs-config-row-name {
220
220
  font-family: var(--font-human);
221
- font-size: 13px;
221
+ font-size: 14px;
222
222
  font-weight: 600;
223
223
  color: var(--text);
224
224
  letter-spacing: -0.005em;
@@ -1096,12 +1096,11 @@ html.is-electron-mac .room-settings-overlay * {
1096
1096
  0%, 100% { opacity: 1; }
1097
1097
  50% { opacity: 0.4; }
1098
1098
  }
1099
- /* Generate-Report variant · adjourned room with no brief yet. The
1100
- primary CTA-ness moves to the icon's color tone (lime) same
1101
- tactic as the sidebar's `.new-btn.active` lime state. */
1102
- .head-view-report.is-generate {
1103
- color: var(--lime);
1104
- }
1099
+ /* Generate-Report variant · adjourned room with no brief yet. No
1100
+ colour accent the icon rests at the shared `.head-icon-btn`
1101
+ text-soft tone (lime only on hover, like every other head icon)
1102
+ so it doesn't read as a yellow/gold highlight (`--lime` is the
1103
+ warm gold #C9A46B in the default theme). */
1105
1104
  /* Count chip for multi-brief popover trigger · pinned to the icon's
1106
1105
  top-right corner so the user sees "there's more than one report"
1107
1106
  without taking horizontal space. Mono pill, same micro-tag register
@@ -1133,10 +1132,21 @@ html.is-electron-mac .room-settings-overlay * {
1133
1132
  /* Edit cast · Lucide UserPlus. Sits between the cast avatars and the
1134
1133
  primary state action so the user reads it as a direct affordance on
1135
1134
  the cast strip ("add a member to this group"). Hidden on adjourned
1136
- rooms (the cast is frozen once the brief is filed). */
1135
+ rooms (the cast is frozen once the brief is filed).
1136
+ The glyph (user silhouette + plus) sits in opposite quadrants of
1137
+ the 24×24 viewBox · at the shared 16×16 mask size the symbol reads
1138
+ visually smaller than dense siblings like `.head-divergence`. Bump
1139
+ the mask + ::before box to 18×18 so it sits at the same perceived
1140
+ weight as the cast avatars (26px) next to it. */
1137
1141
  .head-add-cast {
1138
1142
  --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/><circle cx='9' cy='7' r='4'/><line x1='19' x2='19' y1='8' y2='14'/><line x1='22' x2='16' y1='11' y2='11'/></svg>");
1139
1143
  }
1144
+ .head-add-cast::before {
1145
+ width: 18px;
1146
+ height: 18px;
1147
+ -webkit-mask-size: 18px 18px;
1148
+ mask-size: 18px 18px;
1149
+ }
1140
1150
  html[data-status="adjourned"] .head-add-cast { display: none; }
1141
1151
  /* Adjourn · identical glyph to the input-bar's `.ib-adjourn` (Lucide
1142
1152
  log-out: door + arrow exit). The head and input-bar icons share the
@@ -1195,7 +1205,12 @@ html[data-status="adjourned"] .head-add-cast { display: none; }
1195
1205
  .rec-countdown-overlay {
1196
1206
  position: absolute;
1197
1207
  inset: 0;
1198
- z-index: 10;
1208
+ /* Sit above the live caption · `.rt-subtitle` is z-index: 12 (see
1209
+ index.html line ~9554), so the countdown overlay must be
1210
+ higher or else the subtitle bar punches through "3 / 2 / 1 /
1211
+ READY" and ruins the movie-trailer intro. 30 leaves headroom
1212
+ for any future stage chrome we want to slip between. */
1213
+ z-index: 30;
1199
1214
  display: flex;
1200
1215
  align-items: center;
1201
1216
  justify-content: center;