privateboard 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "privateboard",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "PrivateBoard · your private board meeting, on call. Local-first, multi-agent thinking amplifier.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  * a key isn't in the table (registry updates lag this map). */
17
17
  const MODEL_LABELS = {
18
18
  "opus-4-7": { name: "Claude Opus 4.7", provider: "Anthropic" },
19
+ "opus-4-6": { name: "Claude Opus 4.6", provider: "Anthropic" },
19
20
  "sonnet-4-6": { name: "Claude Sonnet 4.6", provider: "Anthropic" },
20
21
  "opus-4-6": { name: "Claude Opus 4.6", provider: "Anthropic" },
21
22
  "opus-4-6-fast": { name: "Claude Opus 4.6 Fast", provider: "Anthropic" },
@@ -30,9 +31,9 @@
30
31
  "gemini-3-1-flash": { name: "Gemini 3.1 Flash Lite", provider: "Google" },
31
32
  "grok-4-3": { name: "Grok 4.3", provider: "xAI" },
32
33
  "grok-4-1-fast": { name: "Grok 4.1 Fast", provider: "xAI" },
33
- "grok-4-3": { name: "Grok 4.3", provider: "xAI" },
34
34
  "grok-4-20": { name: "Grok 4.20", provider: "xAI" },
35
35
  "deepseek-v4-pro": { name: "DeepSeek V4 Pro", provider: "DeepSeek" },
36
+ "deepseek-v4-flash": { name: "DeepSeek Lite", provider: "DeepSeek" },
36
37
  };
37
38
 
38
39
  const AGENT_CATALOG = {
@@ -182,12 +183,22 @@
182
183
  }
183
184
  };
184
185
 
186
+ function escapeHtml(s) {
187
+ return String(s).replace(/[&<>"']/g, (c) => ({
188
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
189
+ }[c]));
190
+ }
191
+
192
+ function ovT(key, vars) {
193
+ return (window.I18n && window.I18n.t(key, vars)) || key;
194
+ }
195
+
185
196
  const OVERLAY_HTML = `
186
197
  <div class="agent-overlay" id="agent-overlay" role="dialog" aria-modal="true" aria-hidden="true">
187
198
  <div class="agent-card" role="document">
188
199
  <div class="agent-classification">
189
- <span><span class="dot">●</span> agent · personnel file</span>
190
- <span class="right">// classified</span>
200
+ <span><span class="dot">●</span> <span data-i18n="ao_personnel_kicker">agent · personnel file</span></span>
201
+ <span class="right" data-i18n="ao_classified_mark">// classified</span>
191
202
  </div>
192
203
  <header class="agent-card-head">
193
204
  <img class="agent-card-avatar" src="" alt="">
@@ -196,17 +207,17 @@
196
207
  <div class="role"></div>
197
208
  <div class="handle"></div>
198
209
  </div>
199
- <button type="button" class="agent-card-close" aria-label="Close">✕</button>
210
+ <button type="button" class="agent-card-close" data-i18n-aria="us_close" aria-label="Close">✕</button>
200
211
  </header>
201
212
  <div class="agent-card-body">
202
213
 
203
214
  <div class="agent-block">
204
- <div class="agent-block-label">Lens</div>
215
+ <div class="agent-block-label" data-i18n="ao_lens">Lens</div>
205
216
  <p class="agent-lens"></p>
206
217
  </div>
207
218
 
208
219
  <div class="agent-block agent-model-block">
209
- <div class="agent-block-label">Model</div>
220
+ <div class="agent-block-label" data-i18n="ao_model">Model</div>
210
221
  <div class="agent-model-display">
211
222
  <span class="agent-model-name"></span>
212
223
  <span class="agent-model-provider"></span>
@@ -214,32 +225,31 @@
214
225
  </div>
215
226
 
216
227
  <div class="agent-block">
217
- <div class="agent-block-label">Style</div>
228
+ <div class="agent-block-label" data-i18n="ao_style">Style</div>
218
229
  <div class="agent-traits"></div>
219
230
  </div>
220
231
 
221
232
  <div class="agent-block private-only">
222
233
  <div class="agent-block-label">
223
- In-Room Memory
224
- <span class="badge">this room</span>
234
+ <span data-i18n="ao_memory_room">In-Room Memory</span>
235
+ <span class="badge" data-i18n="ao_badge_this_room">this room</span>
225
236
  </div>
226
237
  <div class="agent-memory-list" data-agent-room-notes></div>
227
238
  </div>
228
239
 
229
240
  <div class="agent-block private-only">
230
- <div class="agent-block-label">Track Record</div>
241
+ <div class="agent-block-label" data-i18n="ap_track_record">Track Record</div>
231
242
  <div class="agent-stats"></div>
232
243
  </div>
233
244
 
234
245
  <div class="agent-block public-only">
235
246
  <div class="agent-block-label">
236
- In-Room Memory
237
- <span class="badge locked-badge">⊘ classified</span>
247
+ <span data-i18n="ao_memory_room">In-Room Memory</span>
248
+ <span class="badge locked-badge" data-i18n="ao_badge_classified">⊘ classified</span>
238
249
  </div>
239
250
  <div class="agent-locked">
240
251
  <div class="lock-icon">▰</div>
241
- <div class="lock-text">
242
- in-room notes are private to each thinker.
252
+ <div class="lock-text" data-i18n-html="ao_lock_blurb_html">in-room notes are private to each thinker.
243
253
  <a href="/" class="lock-link">sign in →</a>
244
254
  to see what they have said and where their stance shifted.
245
255
  </div>
@@ -248,10 +258,10 @@
248
258
 
249
259
  </div>
250
260
  <footer class="agent-card-foot">
251
- <div class="meta private-only">tenure · <span class="lime agent-tenure"></span></div>
252
- <div class="meta public-only">first room · <span class="lime">free</span></div>
253
- <a href="/#convene" class="agent-card-cta private-only">[ ◆ Convene with them ]</a>
254
- <a href="/#convene" class="agent-card-cta public-only">[ → Sign in to convene ]</a>
261
+ <div class="meta private-only"><span data-i18n="ao_tenure_meta">tenure ·</span> <span class="lime agent-tenure"></span></div>
262
+ <div class="meta public-only"><span data-i18n="ao_first_room_meta">first room ·</span> <span class="lime" data-i18n="ao_free">free</span></div>
263
+ <a href="/#convene" class="agent-card-cta private-only" data-i18n="ao_convene_cta">[ ◆ Convene with them ]</a>
264
+ <a href="/#convene" class="agent-card-cta public-only" data-i18n="ao_signin_cta">[ → Sign in to convene ]</a>
255
265
  </footer>
256
266
  </div>
257
267
  </div>
@@ -285,17 +295,21 @@
285
295
  // Privacy mode: pages can opt-in via <body data-agent-mode="public">,
286
296
  // which hides personal memory/stats and swaps the CTA to a sign-in.
287
297
  const isPublic = document.body.dataset.agentMode === "public";
288
- const overlayEl = document.getElementById("agent-overlay");
289
- if (isPublic) overlayEl.classList.add("public");
298
+ const overlay = document.getElementById("agent-overlay");
299
+ if (isPublic) overlay.classList.add("public");
300
+ if (window.I18n && typeof window.I18n.applyDom === "function") {
301
+ window.I18n.applyDom(overlay);
302
+ }
290
303
 
291
304
  autoTagAvatars();
292
305
  // Re-run after short delay in case other scripts mutate the DOM
293
306
  setTimeout(autoTagAvatars, 50);
294
307
 
295
- const overlay = document.getElementById("agent-overlay");
296
308
  const card = overlay.querySelector(".agent-card");
297
309
  const closeBtn = overlay.querySelector(".agent-card-close");
298
310
 
311
+ let overlayOpenSlug = null;
312
+
299
313
  /** Auto-hide scrollbar · adds `.is-scrolling` to a scroll container
300
314
  * for ~700ms after each scroll event. The CSS uses that class
301
315
  * alongside :hover to show the thumb only while the user is
@@ -322,7 +336,7 @@
322
336
  function buildLiveAgentCard(live) {
323
337
  return {
324
338
  name: live.name,
325
- role: live.roleTag || "Director",
339
+ role: live.roleTag || ovT("ap_live_agent_director"),
326
340
  handle: live.handle || ("/" + live.id),
327
341
  avatar: live.avatarPath || "",
328
342
  lens: live.bio || "",
@@ -345,6 +359,7 @@
345
359
  if (live) a = buildLiveAgentCard(live);
346
360
  }
347
361
  if (!a) return;
362
+ overlayOpenSlug = slug;
348
363
  // Avatar source-of-truth · the live agent record's avatarPath
349
364
  // (same field the agent profile renders). For seeds this is an
350
365
  // absolute path "/avatars/<slug>.svg"; for customs it's a data:
@@ -356,7 +371,11 @@
356
371
  av.src = (live && live.avatarPath) ? live.avatarPath : a.avatar;
357
372
  av.alt = a.name;
358
373
  card.querySelector(".agent-card-id .name").textContent = a.name;
359
- card.querySelector(".agent-card-id .role").textContent = a.role;
374
+ let roleDisp = a.role;
375
+ if (live && live.roleKind === "moderator" && String(roleDisp).toLowerCase() === "moderator") {
376
+ roleDisp = ovT("agent_role_tag_moderator");
377
+ }
378
+ card.querySelector(".agent-card-id .role").textContent = roleDisp;
360
379
  card.querySelector(".agent-card-id .handle").textContent = a.handle;
361
380
  card.querySelector(".agent-lens").textContent = a.lens;
362
381
 
@@ -473,7 +492,7 @@
473
492
  list.innerHTML = `
474
493
  <div class="agent-memory-empty">
475
494
  <div class="lock-icon">○</div>
476
- <div class="lock-text">no live room. open a room to see this director's in-room notes.</div>
495
+ <div class="lock-text">${escapeHtml(ovT("ao_room_notes_empty"))}</div>
477
496
  </div>
478
497
  `;
479
498
  return;
@@ -482,7 +501,7 @@
482
501
  list.innerHTML = `
483
502
  <div class="agent-memory-empty">
484
503
  <div class="lock-icon">○</div>
485
- <div class="lock-text">no turns yet — once they speak, claims and stance shifts land here.</div>
504
+ <div class="lock-text">${escapeHtml(ovT("ao_room_notes_waiting"))}</div>
486
505
  </div>
487
506
  `;
488
507
  return;
@@ -494,10 +513,10 @@
494
513
  : n.tag;
495
514
  return `
496
515
  <div class="agent-note-entry ${cls}">
497
- <div class="agent-note-time">${escape(formatTime(n.ts))}</div>
516
+ <div class="agent-note-time">${escapeHtml(formatTime(n.ts))}</div>
498
517
  <div class="agent-note-body">
499
- <span class="agent-note-tag t-${escape(n.tag)}">${escape(tagLabel)}</span>
500
- ${escape(n.body)}
518
+ <span class="agent-note-tag t-${escapeHtml(n.tag)}">${escapeHtml(tagLabel)}</span>
519
+ ${escapeHtml(n.body)}
501
520
  </div>
502
521
  </div>
503
522
  `;
@@ -510,9 +529,9 @@
510
529
  // Render placeholders immediately, then patch in real values
511
530
  // once the fetch resolves. Keeps the overlay snappy on open.
512
531
  stats.innerHTML = `
513
- <div class="agent-stat"><div class="v" data-stat-v="rooms">—</div><div class="l">rooms</div></div>
514
- <div class="agent-stat"><div class="v" data-stat-v="rounds">—</div><div class="l">rounds</div></div>
515
- <div class="agent-stat"><div class="v" data-stat-v="tokens">—</div><div class="l">tokens</div></div>
532
+ <div class="agent-stat"><div class="v" data-stat-v="rooms">—</div><div class="l">${escapeHtml(ovT("ap_stat_rooms"))}</div></div>
533
+ <div class="agent-stat"><div class="v" data-stat-v="rounds">—</div><div class="l">${escapeHtml(ovT("ap_stat_rounds"))}</div></div>
534
+ <div class="agent-stat"><div class="v" data-stat-v="tokens">—</div><div class="l">${escapeHtml(ovT("ap_stat_tokens"))}</div></div>
516
535
  `;
517
536
  fetch("/api/agents/" + encodeURIComponent(slug) + "/stats")
518
537
  .then((r) => (r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status))))
@@ -544,16 +563,20 @@
544
563
  }
545
564
 
546
565
  function close() {
566
+ overlayOpenSlug = null;
547
567
  overlay.classList.remove("open");
548
568
  overlay.setAttribute("aria-hidden", "true");
549
569
  document.body.style.overflow = "";
550
570
  }
551
571
 
552
- function escape(s) {
553
- return String(s).replace(/[&<>"']/g, (c) => ({
554
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
555
- }[c]));
556
- }
572
+ document.addEventListener("boardroom:locale", () => {
573
+ if (!overlay.classList.contains("open") || !overlayOpenSlug) return;
574
+ if (window.I18n && typeof window.I18n.applyDom === "function") {
575
+ window.I18n.applyDom(overlay);
576
+ }
577
+ renderTrackRecord(overlayOpenSlug);
578
+ renderRoomNotes(overlayOpenSlug);
579
+ });
557
580
 
558
581
  document.addEventListener("click", (e) => {
559
582
  const trigger = e.target.closest("[data-agent]");