privateboard 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/version.d.ts CHANGED
@@ -12,6 +12,6 @@
12
12
  * number ends up surfaced in the user-facing footer or banner. Keep
13
13
  * this file as the canonical source — every callsite reads from here.
14
14
  */
15
- declare const VERSION = "0.1.31";
15
+ declare const VERSION = "0.1.32";
16
16
 
17
17
  export { VERSION };
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var VERSION = "0.1.31";
4
+ var VERSION = "0.1.32";
5
5
  export {
6
6
  VERSION
7
7
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * Single source of truth for the app version.\n *\n * Imported by `cli.ts` (CLI banner / `--version`), `server.ts` (the\n * `/health` payload + the `/api/version` endpoint), and bundled into\n * the frontend via the version endpoint. Bump alongside `package.json`\n * on every release — the existing `npm version <patch|minor|major>`\n * + commit pattern updates package.json automatically; this file\n * needs the matching manual bump.\n *\n * If two strings drift (bumped one but not the other), the wrong\n * number ends up surfaced in the user-facing footer or banner. Keep\n * this file as the canonical source — every callsite reads from here.\n */\nexport const VERSION = \"0.1.31\";\n"],"mappings":";;;AAcO,IAAM,UAAU;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * Single source of truth for the app version.\n *\n * Imported by `cli.ts` (CLI banner / `--version`), `server.ts` (the\n * `/health` payload + the `/api/version` endpoint), and bundled into\n * the frontend via the version endpoint. Bump alongside `package.json`\n * on every release — the existing `npm version <patch|minor|major>`\n * + commit pattern updates package.json automatically; this file\n * needs the matching manual bump.\n *\n * If two strings drift (bumped one but not the other), the wrong\n * number ends up surfaced in the user-facing footer or banner. Keep\n * this file as the canonical source — every callsite reads from here.\n */\nexport const VERSION = \"0.1.32\";\n"],"mappings":";;;AAcO,IAAM,UAAU;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "privateboard",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "PrivateBoard · your private board meeting, on call. Local-first, multi-agent thinking amplifier.",
5
5
  "type": "module",
6
6
  "main": "electron-entry.cjs",
@@ -57,6 +57,7 @@
57
57
  "commander": "^12.1.0",
58
58
  "electron-updater": "^6.8.3",
59
59
  "hono": "^4.6.14",
60
+ "mp4-muxer": "^5.2.2",
60
61
  "open": "^10.1.0",
61
62
  "three": "^0.184.0",
62
63
  "tiny-pinyin": "^1.3.2",
package/public/app.js CHANGED
@@ -362,7 +362,6 @@
362
362
  document.addEventListener("boardroom:locale", () => {
363
363
  this.renderSidebarRooms();
364
364
  this.renderSidebarAgents();
365
- this.renderSidebarCounts();
366
365
  this.renderUserBlock();
367
366
  if (this.currentRoomId) this.renderRoom();
368
367
  else this.renderEmptyState();
@@ -546,14 +545,7 @@
546
545
  document.querySelector("[data-sidebar-collapse]")?.click();
547
546
  });
548
547
 
549
- // Sidebar count badges · refresh on boot.
550
- // Notes: also refreshed on every note:created / note:deleted
551
- // (handlers below). Reports: refreshed when a brief lands or
552
- // is deleted (see SSE brief-final / brief-deleted handlers).
553
- this.refreshNotesCount();
554
- this.refreshReportsCount();
555
548
  document.addEventListener("note:created", (e) => {
556
- this.refreshNotesCount();
557
549
  // Live-update currentNotes for the active room so the new
558
550
  // span gets its highlight without waiting for a navigation.
559
551
  const note = e && e.detail && e.detail.note;
@@ -566,7 +558,6 @@
566
558
  }
567
559
  });
568
560
  document.addEventListener("note:deleted", (e) => {
569
- this.refreshNotesCount();
570
561
  const detail = e && e.detail;
571
562
  if (detail && detail.noteId && this.currentNotes) {
572
563
  for (const [mid, arr] of this.currentNotes) {
@@ -755,7 +746,6 @@
755
746
  if (j.chair) this.currentChair = j.chair;
756
747
  if (this.currentChair) this.agentsById[this.currentChair.id] = this.currentChair;
757
748
  this.renderSidebarAgents();
758
- this.renderSidebarCounts();
759
749
  } catch (e) { /* ignore */ }
760
750
  },
761
751
 
@@ -2337,8 +2327,6 @@
2337
2327
  this.renderBrief();
2338
2328
  }
2339
2329
  this.renderHeader();
2340
- // A new brief just landed → bump the All Reports badge.
2341
- this.refreshReportsCount();
2342
2330
  // Refresh the FULL brief list so the tab strip picks up the
2343
2331
  // newly filed brief (including any "add a perspective"
2344
2332
  // regenerations). Active brief = the just-finalised one.
@@ -5134,7 +5122,6 @@
5134
5122
  this.currentBrief = this.currentBriefs[0] || null;
5135
5123
  }
5136
5124
  this.renderBrief();
5137
- this.refreshReportsCount();
5138
5125
  },
5139
5126
 
5140
5127
  /** Retry handler for the orphaned-brief recovery UI. Drops the
@@ -6606,7 +6593,6 @@
6606
6593
 
6607
6594
  // ── Rendering · sidebar rooms ─────────────────────────────
6608
6595
  renderSidebarRooms() {
6609
- this.renderSidebarCounts();
6610
6596
  const list = document.querySelector("[data-rooms-list]");
6611
6597
  if (!list) return;
6612
6598
  const live = this.rooms.filter((r) => r.status === "live");
@@ -6614,38 +6600,17 @@
6614
6600
  const adj = this.rooms.filter((r) => r.status === "adjourned");
6615
6601
 
6616
6602
  const renderRow = (r) => {
6617
- const pausedLbl = this._t("sidebar_paused");
6618
- const status =
6619
- r.status === "paused"
6620
- ? `<span class="row-status paused">❚❚ ${this.escape(pausedLbl)}</span>`
6621
- : "";
6622
- const time =
6623
- r.status === "paused"
6624
- ? this.relTime(r.pausedAt || r.createdAt)
6625
- : r.status === "adjourned"
6626
- ? this.relTime(r.adjournedAt || r.createdAt)
6627
- : this.relTime(r.createdAt);
6628
6603
  const fullTitle = r.name || r.subject || "";
6629
6604
  // Layout: a wrapper holds the anchor + the delete button as
6630
6605
  // siblings. Putting the <button> inside the <a> is invalid HTML
6631
6606
  // and some browsers route the click to the link, swallowing the
6632
6607
  // delete action — moving it out fixes that.
6633
- // No `title` attr · the native browser tooltip popping the full
6634
- // subject on hover competes with the row's own subtitle line
6635
- // and felt redundant. The subtitle already shows the subject;
6636
- // truncated names are rare and the user can click in to see
6637
- // the full thing if needed.
6638
6608
  return `
6639
6609
  <div class="session-row-shell" data-room-id="${this.escape(r.id)}" data-status="${this.escape(r.status)}">
6640
6610
  <a href="#/r/${this.escape(r.id)}" class="session-row">
6641
- <div class="row-content">
6642
- <div class="row-top-line">
6643
- <span class="row-title">${this.escape(fullTitle)}</span>
6644
- </div>
6645
- <div class="row-subtitle">${status}${this.escape(r.subject || "")}</div>
6646
- </div>
6611
+ <span class="row-title">${this.escape(fullTitle)}</span>
6647
6612
  </a>
6648
- <button type="button" class="row-delete" data-room-delete title="${this.escape(this._t("sidebar_delete_room"))}">✕</button>
6613
+ <button type="button" class="row-delete" data-room-delete title="${this.escape(this._t("sidebar_delete_room"))}" aria-label="${this.escape(this._t("sidebar_delete_room"))}"></button>
6649
6614
  </div>
6650
6615
  `;
6651
6616
  };
@@ -6655,7 +6620,6 @@
6655
6620
  <div class="section-header live">
6656
6621
  <span>${this.escape(this._t("sidebar_section_live"))}</span>
6657
6622
  <span class="line"></span>
6658
- <span class="badge">${live.length}</span>
6659
6623
  </div>
6660
6624
  ${live.map(renderRow).join("")}
6661
6625
  ` : ""}
@@ -6664,7 +6628,6 @@
6664
6628
  <div class="section-header paused">
6665
6629
  <span>${this.escape(this._t("sidebar_section_paused"))}</span>
6666
6630
  <span class="line"></span>
6667
- <span class="badge">${paused.length}</span>
6668
6631
  </div>
6669
6632
  ${paused.map(renderRow).join("")}
6670
6633
  ` : ""}
@@ -6672,7 +6635,6 @@
6672
6635
  <div class="section-header adjourned">
6673
6636
  <span>${this.escape(this._t("sidebar_section_adjourned"))}</span>
6674
6637
  <span class="line"></span>
6675
- <span class="badge" data-adjourned-count>${adj.length}</span>
6676
6638
  </div>
6677
6639
  <div class="adjourned-list" data-adjourned-list>
6678
6640
  ${adj.map(renderRow).join("")}
@@ -6898,7 +6860,7 @@
6898
6860
  </a>
6899
6861
  `;
6900
6862
 
6901
- const sectionHeader = (label, count, kind) => {
6863
+ const sectionHeader = (label, kind) => {
6902
6864
  const pinned = kind === "pinned";
6903
6865
  const chair = kind === "chair";
6904
6866
  const glyph = pinned
@@ -6910,7 +6872,6 @@
6910
6872
  ${glyph}
6911
6873
  <span>${this.escape(label)}</span>
6912
6874
  <span class="line"></span>
6913
- <span class="badge">${count}</span>
6914
6875
  </div>
6915
6876
  `;
6916
6877
  };
@@ -6921,7 +6882,7 @@
6921
6882
  // user immediately sees the orchestrator, and so it can't get
6922
6883
  // grouped with directors they pin or create.
6923
6884
  if (this.currentChair) {
6924
- parts.push(sectionHeader(this._t("sidebar_sec_chair"), 1, "chair"));
6885
+ parts.push(sectionHeader(this._t("sidebar_sec_chair"), "chair"));
6925
6886
  parts.push(renderChairRow(this.currentChair));
6926
6887
  }
6927
6888
  // Building section · placeholder row for the in-flight Full
@@ -6940,19 +6901,19 @@
6940
6901
  // right now". The earlier i18n key fallback to "Building"
6941
6902
  // tested as confusing.
6942
6903
  const headerLabel = job.status === "done" ? "Ready to save" : "Generating";
6943
- parts.push(`<div class="agents-section-header building"><span>${this.escape(headerLabel)}</span><span class="line"></span><span class="badge">1</span></div>`);
6904
+ parts.push(`<div class="agents-section-header building"><span>${this.escape(headerLabel)}</span><span class="line"></span></div>`);
6944
6905
  parts.push(this._renderPersonaBuildingRow(job));
6945
6906
  }
6946
6907
  if (pinned.length) {
6947
- parts.push(sectionHeader(this._t("sidebar_sec_pinned"), pinned.length, "pinned"));
6908
+ parts.push(sectionHeader(this._t("sidebar_sec_pinned"), "pinned"));
6948
6909
  parts.push(pinned.map((a) => renderRow(a)).join(""));
6949
6910
  }
6950
6911
  if (custom.length) {
6951
- parts.push(sectionHeader(this._t("sidebar_sec_custom"), custom.length));
6912
+ parts.push(sectionHeader(this._t("sidebar_sec_custom")));
6952
6913
  parts.push(custom.map((a) => renderRow(a)).join(""));
6953
6914
  }
6954
6915
  if (core.length) {
6955
- parts.push(sectionHeader(this._t("sidebar_sec_core"), core.length));
6916
+ parts.push(sectionHeader(this._t("sidebar_sec_core")));
6956
6917
  parts.push(core.map((a) => renderRow(a)).join(""));
6957
6918
  }
6958
6919
  // Same idempotency guard as renderSidebarRooms · skip the
@@ -7058,16 +7019,6 @@
7058
7019
  if (mt) mt.textContent = meta;
7059
7020
  },
7060
7021
 
7061
- renderSidebarCounts() {
7062
- const roomsCount = this.rooms.length;
7063
- const agentsCount = this.agents.length;
7064
-
7065
- const r = document.querySelector('[data-sidebar-tab-count="rooms"]');
7066
- if (r) r.textContent = String(roomsCount);
7067
- const a = document.querySelector('[data-sidebar-tab-count="agents"]');
7068
- if (a) a.textContent = String(agentsCount);
7069
- },
7070
-
7071
7022
  // ── Rendering · main view ─────────────────────────────────
7072
7023
  renderRoom() {
7073
7024
  this.renderHeader();
@@ -9665,61 +9616,11 @@
9665
9616
  return;
9666
9617
  }
9667
9618
  // Patch local cache so the immediate re-render reflects the
9668
- // delete without a refetch round-trip. The sidebar count badge
9669
- // re-fetches via the /count endpoint.
9619
+ // delete without a refetch round-trip.
9670
9620
  if (Array.isArray(this._notesCache)) {
9671
9621
  this._notesCache = this._notesCache.filter((n) => n && n.id !== id);
9672
9622
  this.renderNotesPage(this._notesCache);
9673
9623
  }
9674
- this.refreshNotesCount();
9675
- },
9676
-
9677
- /** Refresh the sidebar count badge · called on boot, after
9678
- * every successful save (note:created event), and after a
9679
- * delete. Hits /api/notes/count which is cheap (one COUNT query),
9680
- * so we don't need to debounce.
9681
- *
9682
- * When count is 0 the badge is hidden (the `hidden` attr stays
9683
- * on); otherwise the count renders. The CSS bumps the colour to
9684
- * lime on hover/active so the badge tracks the link's cascade. */
9685
- async refreshNotesCount() {
9686
- try {
9687
- const r = await fetch("/api/notes/count");
9688
- if (!r.ok) return;
9689
- const j = await r.json();
9690
- const total = typeof j.total === "number" ? j.total : 0;
9691
- const badge = document.querySelector("[data-notes-count]");
9692
- if (!badge) return;
9693
- if (total > 0) {
9694
- badge.textContent = String(total);
9695
- badge.removeAttribute("hidden");
9696
- } else {
9697
- badge.textContent = "";
9698
- badge.setAttribute("hidden", "");
9699
- }
9700
- } catch { /* fail closed — leave badge as-is */ }
9701
- },
9702
-
9703
- /** Mirror of refreshNotesCount for the All Reports sidebar badge.
9704
- * Hits /api/briefs/count (cheap COUNT, excludes empty placeholder
9705
- * rows). Called on boot and whenever a brief is filed / deleted
9706
- * so the badge stays in sync with the All Reports list. */
9707
- async refreshReportsCount() {
9708
- try {
9709
- const r = await fetch("/api/briefs/count");
9710
- if (!r.ok) return;
9711
- const j = await r.json();
9712
- const total = typeof j.total === "number" ? j.total : 0;
9713
- const badge = document.querySelector("[data-reports-count]");
9714
- if (!badge) return;
9715
- if (total > 0) {
9716
- badge.textContent = String(total);
9717
- badge.removeAttribute("hidden");
9718
- } else {
9719
- badge.textContent = "";
9720
- badge.setAttribute("hidden", "");
9721
- }
9722
- } catch { /* fail closed */ }
9723
9624
  },
9724
9625
 
9725
9626
  // ── In-room note highlight overlay ────────────────────────
package/public/home.html CHANGED
@@ -1622,7 +1622,7 @@
1622
1622
 
1623
1623
  <div class="hero-actions">
1624
1624
  <a href="#install" class="big-btn primary">[ ◆ Convene a Room ]</a>
1625
- <a href="https://github.com/kaysaith1900/privateboard/releases/download/v0.1.30/PrivateBoard-0.1.30-arm64.dmg"
1625
+ <a href="https://github.com/kaysaith1900/privateboard/releases/download/v0.1.31/PrivateBoard-0.1.31-arm64.dmg"
1626
1626
  class="big-btn secondary"
1627
1627
  download
1628
1628
  aria-label="Download PrivateBoard for macOS (Apple Silicon)"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 3v12"/><polyline points="6 11 12 17 18 11"/><path d="M5 21h14"/></svg> Download for Mac</a>
package/public/i18n.js CHANGED
@@ -531,7 +531,7 @@
531
531
  // Meeting recording · head button + REC pill + exit modal.
532
532
  head_record_tip: "Record meeting",
533
533
  head_record_label: "Record meeting",
534
- head_record_stop_tip: "Stop recording · save .webm",
534
+ head_record_stop_tip: "Stop recording · save .mp4",
535
535
  head_record_stop_label: "Stop recording",
536
536
  rec_pill_label: "REC",
537
537
  rec_error_toast: "Couldn't start the meeting recording. Check screen-capture permission and try again.",
@@ -541,7 +541,7 @@
541
541
  rec_exit_title: "Stop the recording before you leave?",
542
542
  rec_exit_deck: "The meeting is still being captured. Stop now to save the file; otherwise the in-flight chunks are discarded.",
543
543
  rec_exit_stop_mark: "▍ Stop & save",
544
- rec_exit_stop_deck: "End the recording, download the .webm, then continue.",
544
+ rec_exit_stop_deck: "End the recording, download the .mp4, then continue.",
545
545
  rec_exit_cancel_mark: "↩ Cancel",
546
546
  rec_exit_cancel_deck: "Stay here · the recording keeps running.",
547
547
  // Stop-recording choice modal · how to handle the live room.
@@ -1752,7 +1752,7 @@ When the room ___, raise an objection.`,
1752
1752
  // 会议录制 · header 按钮 + REC 提示牌 + 退出弹窗
1753
1753
  head_record_tip: "录制会议",
1754
1754
  head_record_label: "录制会议",
1755
- head_record_stop_tip: "停止录制 · 保存 .webm",
1755
+ head_record_stop_tip: "停止录制 · 保存 .mp4",
1756
1756
  head_record_stop_label: "停止录制",
1757
1757
  rec_pill_label: "录制中",
1758
1758
  rec_error_toast: "无法开始会议录制。请检查屏幕录制权限后重试。",
@@ -1762,7 +1762,7 @@ When the room ___, raise an objection.`,
1762
1762
  rec_exit_title: "离开前先停止录制?",
1763
1763
  rec_exit_deck: "会议正在录制中。现在停止可以保存视频文件;否则正在采集的最后片段将被丢弃。",
1764
1764
  rec_exit_stop_mark: "▍ 停止并保存",
1765
- rec_exit_stop_deck: "结束录制,下载 .webm 后再继续。",
1765
+ rec_exit_stop_deck: "结束录制,下载 .mp4 后再继续。",
1766
1766
  rec_exit_cancel_mark: "↩ 取消",
1767
1767
  rec_exit_cancel_deck: "留在这里 · 录制继续。",
1768
1768
  // 停止录制 · 选择如何处理当前 live 房间
@@ -2899,7 +2899,7 @@ When the room ___, raise an objection.`,
2899
2899
  // ミーティング録画 · ヘッダーボタン + REC バッジ + 退出ダイアログ
2900
2900
  head_record_tip: "ミーティングを録画",
2901
2901
  head_record_label: "ミーティングを録画",
2902
- head_record_stop_tip: "録画を停止 · .webm を保存",
2902
+ head_record_stop_tip: "録画を停止 · .mp4 を保存",
2903
2903
  head_record_stop_label: "録画を停止",
2904
2904
  rec_pill_label: "録画中",
2905
2905
  rec_error_toast: "ミーティングの録画を開始できませんでした。画面録画の権限を確認してください。",
@@ -2909,7 +2909,7 @@ When the room ___, raise an objection.`,
2909
2909
  rec_exit_title: "退出前に録画を停止しますか?",
2910
2910
  rec_exit_deck: "ミーティングを録画中です。停止すれば動画ファイルを保存できます。停止しないと録画中のチャンクは破棄されます。",
2911
2911
  rec_exit_stop_mark: "▍ 停止して保存",
2912
- rec_exit_stop_deck: "録画を終了し .webm をダウンロードしてから続行。",
2912
+ rec_exit_stop_deck: "録画を終了し .mp4 をダウンロードしてから続行。",
2913
2913
  rec_exit_cancel_mark: "↩ キャンセル",
2914
2914
  rec_exit_cancel_deck: "ここに留まる · 録画は続行されます。",
2915
2915
  // 録画停止 · ライブルームの扱いを選択
@@ -3806,7 +3806,7 @@ When the room ___, raise an objection.`,
3806
3806
  // Grabación de reunión · botón en cabecera + chip REC + modal de salida
3807
3807
  head_record_tip: "Grabar reunión",
3808
3808
  head_record_label: "Grabar reunión",
3809
- head_record_stop_tip: "Detener grabación · guardar .webm",
3809
+ head_record_stop_tip: "Detener grabación · guardar .mp4",
3810
3810
  head_record_stop_label: "Detener grabación",
3811
3811
  rec_pill_label: "GRAB.",
3812
3812
  rec_error_toast: "No se pudo iniciar la grabación. Revisa el permiso de captura de pantalla e inténtalo de nuevo.",
@@ -3816,7 +3816,7 @@ When the room ___, raise an objection.`,
3816
3816
  rec_exit_title: "¿Detener la grabación antes de salir?",
3817
3817
  rec_exit_deck: "La reunión sigue grabándose. Detén ahora para guardar el archivo; de lo contrario se descartan los fragmentos en curso.",
3818
3818
  rec_exit_stop_mark: "▍ Detener y guardar",
3819
- rec_exit_stop_deck: "Termina la grabación, descarga el .webm y luego continúa.",
3819
+ rec_exit_stop_deck: "Termina la grabación, descarga el .mp4 y luego continúa.",
3820
3820
  rec_exit_cancel_mark: "↩ Cancelar",
3821
3821
  rec_exit_cancel_deck: "Quédate aquí · la grabación sigue corriendo.",
3822
3822
  // Modal · cómo manejar la sala en vivo al detener la grabación