@unson/brainbase-mcp 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -496,5 +496,5 @@
496
496
  "engines": {
497
497
  "node": ">=20"
498
498
  },
499
- "gitHead": "a8166d088899a27bb8267af9e6dd99d3550c7b63"
499
+ "gitHead": "a695d9dedc1c2bbe6a5ae43c23280c21771387d5"
500
500
  }
@@ -698,3 +698,125 @@
698
698
  .bb-minutes-connection-note.is-unavailable {
699
699
  color: var(--bb-color-warning);
700
700
  }
701
+
702
+ /* 参加者 (story-meeting-participants-row-v1): linked names, 未確認 badges and the link panel. */
703
+ .bb-minutes-participants {
704
+ display: grid;
705
+ gap: 4px;
706
+ margin: 8px 0 4px;
707
+ padding: 8px 12px;
708
+ border: 1px solid var(--bb-color-border);
709
+ border-radius: var(--bb-radius-md, 6px);
710
+ background: var(--bb-color-surface);
711
+ }
712
+
713
+ .bb-minutes-participants[hidden] {
714
+ display: none;
715
+ }
716
+
717
+ .bb-minutes-participants-row {
718
+ display: grid;
719
+ grid-template-columns: 7em minmax(0, 1fr);
720
+ gap: 8px;
721
+ align-items: baseline;
722
+ font-size: 13px;
723
+ line-height: 1.5;
724
+ }
725
+
726
+ .bb-minutes-participant-items {
727
+ display: flex;
728
+ flex-wrap: wrap;
729
+ gap: 4px 14px;
730
+ margin: 0;
731
+ padding: 0;
732
+ list-style: none;
733
+ min-width: 0;
734
+ }
735
+
736
+ .bb-minutes-participant-items li {
737
+ display: inline-flex;
738
+ align-items: baseline;
739
+ gap: 4px;
740
+ min-width: 0;
741
+ overflow-wrap: anywhere;
742
+ }
743
+
744
+ .bb-minutes-participant-badge {
745
+ padding: 0 8px;
746
+ border: 1px dashed var(--bb-color-warning);
747
+ border-radius: 999px;
748
+ background: transparent;
749
+ color: var(--bb-color-ink);
750
+ font: inherit;
751
+ cursor: pointer;
752
+ }
753
+
754
+ .bb-minutes-participant-badge[aria-expanded="true"] {
755
+ border-style: solid;
756
+ background: var(--bb-color-warning-soft);
757
+ }
758
+
759
+ .bb-minutes-participant-undo {
760
+ padding: 0;
761
+ border: 0;
762
+ background: transparent;
763
+ color: var(--bb-color-muted);
764
+ font-size: 12px;
765
+ text-decoration: underline;
766
+ cursor: pointer;
767
+ }
768
+
769
+ .bb-minutes-participant-notice {
770
+ margin: 0;
771
+ font-size: 13px;
772
+ }
773
+
774
+ .bb-minutes-participant-panel {
775
+ display: grid;
776
+ gap: 8px;
777
+ margin-top: 4px;
778
+ padding: 10px 12px;
779
+ border: 1px solid var(--bb-color-border);
780
+ border-radius: var(--bb-radius-md, 6px);
781
+ background: var(--bb-color-paper);
782
+ font-size: 13px;
783
+ }
784
+
785
+ .bb-minutes-participant-panel-title {
786
+ margin: 0;
787
+ font-weight: 700;
788
+ }
789
+
790
+ .bb-minutes-participant-others {
791
+ margin: 0;
792
+ color: var(--bb-color-muted);
793
+ }
794
+
795
+ .bb-minutes-participant-candidates {
796
+ display: grid;
797
+ gap: 6px;
798
+ margin: 0;
799
+ padding: 0;
800
+ list-style: none;
801
+ }
802
+
803
+ .bb-minutes-participant-candidate {
804
+ display: flex;
805
+ flex-wrap: wrap;
806
+ align-items: center;
807
+ justify-content: space-between;
808
+ gap: 4px 12px;
809
+ }
810
+
811
+ .bb-minutes-participant-candidate > div {
812
+ display: grid;
813
+ min-width: 0;
814
+ }
815
+
816
+ .bb-minutes-participant-new,
817
+ .bb-minutes-participant-not-person {
818
+ display: flex;
819
+ flex-wrap: wrap;
820
+ align-items: flex-end;
821
+ gap: 6px 8px;
822
+ }
@@ -208,6 +208,52 @@ function normalizeConnections(value) {
208
208
  ]));
209
209
  }
210
210
 
211
+ // 参加者の行 (story-meeting-participants-row-v1). The host decides who is linked,
212
+ // unconfirmed or not a person; anything malformed is unreadable, never a person (P9).
213
+ const PARTICIPANT_STATES = new Set(['linked', 'unconfirmed', 'not_person']);
214
+
215
+ function normalizeParticipant(value) {
216
+ if (!isRecord(value) || !PARTICIPANT_STATES.has(value.state)) return null;
217
+ const displayName = text(value.display_name);
218
+ if (!displayName) return null;
219
+ const others = Number(value.other_meetings);
220
+ const participant = {
221
+ displayName,
222
+ state: value.state,
223
+ removable: value.removable === true,
224
+ otherMeetings: Number.isInteger(others) && others > 0 ? others : 0,
225
+ };
226
+ if (value.state === 'linked') {
227
+ const name = text(value.name);
228
+ const entityId = text(value.entity_id);
229
+ if (!name || !entityId) return null;
230
+ participant.name = name;
231
+ participant.entityId = entityId;
232
+ participant.href = connectionHref(value.href);
233
+ }
234
+ return participant;
235
+ }
236
+
237
+ function normalizeParticipants(value) {
238
+ if (!isRecord(value) || !['recorded', 'none', 'unavailable'].includes(value.status)) return { status: 'unavailable' };
239
+ if (value.status !== 'recorded') return { status: value.status };
240
+ const participants = Array.isArray(value.participants) ? value.participants.map(normalizeParticipant) : [];
241
+ if (participants.some((participant) => participant === null)) return { status: 'unavailable' };
242
+ if (!participants.length) return { status: 'none' };
243
+ return { status: 'recorded', participants, redrafting: value.redrafting === true };
244
+ }
245
+
246
+ function normalizeCandidateList(values, idField) {
247
+ if (!Array.isArray(values)) return [];
248
+ return values.flatMap((value) => {
249
+ const id = text(value?.[idField]);
250
+ const name = text(value?.name);
251
+ if (!id || !name) return [];
252
+ const evidence = Array.isArray(value.evidence) ? value.evidence.map((item) => text(item)).filter(Boolean) : [];
253
+ return [{ id, name, company: text(value.company), evidence }];
254
+ });
255
+ }
256
+
211
257
  function firstDate(values) {
212
258
  for (const value of values) {
213
259
  const date = parseDate(value);
@@ -636,6 +682,7 @@ export function createMeetingMinutesUI({
636
682
  versionActionExtensions = [],
637
683
  meetingLabel,
638
684
  meetingConnections,
685
+ meetingParticipants,
639
686
  canCreateMeeting,
640
687
  createUnavailableReason,
641
688
  canEditMinutes,
@@ -666,11 +713,14 @@ export function createMeetingMinutesUI({
666
713
  withdrawingMinutesId: null,
667
714
  // { meetingId, rows } where rows is null while the host is answering.
668
715
  connections: null,
716
+ // { meetingId, value, openName } for the 参加者 row (story-meeting-participants-row-v1).
717
+ participants: null,
669
718
  };
670
719
  let listRequestGeneration = 0;
671
720
  let detailRequestGeneration = 0;
672
721
  let createReasonSequence = 0;
673
722
  let connectionsSlot = null;
723
+ let participantsSlot = null;
674
724
  let railProjectSlot = null;
675
725
  let railProjectMeeting = null;
676
726
 
@@ -1052,6 +1102,260 @@ export function createMeetingMinutesUI({
1052
1102
  if (railProjectSlot && railProjectMeeting?.meeting_id === meetingId) fillRailProject(railProjectSlot, railProjectMeeting);
1053
1103
  }
1054
1104
 
1105
+ function hasParticipants() {
1106
+ return isRecord(meetingParticipants) && typeof meetingParticipants.read === 'function';
1107
+ }
1108
+
1109
+ function currentParticipants(meeting) {
1110
+ const value = state.participants;
1111
+ return value && value.meetingId === meeting?.meeting_id ? value : null;
1112
+ }
1113
+
1114
+ function participantError(error, fallback) {
1115
+ const reason = text(error?.message);
1116
+ return reason && !/^[a-z_]+$/u.test(reason) ? reason : fallback;
1117
+ }
1118
+
1119
+ function fillParticipants(slot, meeting) {
1120
+ const current = currentParticipants(meeting);
1121
+ const value = current?.value ?? null;
1122
+ slot.hidden = value?.status === 'none';
1123
+ if (value?.status === 'none') {
1124
+ slot.replaceChildren();
1125
+ return;
1126
+ }
1127
+ const row = makeElement(doc, 'div', { className: 'bb-minutes-participants-row' });
1128
+ row.append(makeElement(doc, 'span', { className: 'bb-minutes-connection-label', textContent: '参加者' }));
1129
+ if (!value) {
1130
+ row.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note', textContent: '読み込んでいます' }));
1131
+ slot.replaceChildren(row);
1132
+ return;
1133
+ }
1134
+ if (value.status !== 'recorded') {
1135
+ row.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note is-unavailable', textContent: '参加者を読み込めませんでした' }));
1136
+ slot.replaceChildren(row);
1137
+ return;
1138
+ }
1139
+ const list = makeElement(doc, 'ul', { className: 'bb-minutes-participant-items' });
1140
+ for (const participant of value.participants) {
1141
+ const entry = makeElement(doc, 'li', { attrs: { 'data-participant-state': participant.state } });
1142
+ if (participant.state === 'linked') {
1143
+ entry.append(connectionLink({ label: participant.name, href: participant.href }, 'bb-minutes-connection-link'));
1144
+ if (participant.name !== participant.displayName) {
1145
+ entry.append(makeElement(doc, 'span', { className: 'bb-minutes-connection-detail', textContent: `(${participant.displayName})` }));
1146
+ }
1147
+ } else if (participant.state === 'unconfirmed') {
1148
+ const open = current.openName === participant.displayName;
1149
+ const badge = button(doc, `${participant.displayName}(未確認)`, () => toggleParticipantPanel(meeting, participant.displayName), {
1150
+ className: 'bb-minutes-participant-badge',
1151
+ attrs: { 'aria-expanded': open ? 'true' : 'false' },
1152
+ });
1153
+ entry.append(badge);
1154
+ } else {
1155
+ entry.append(makeElement(doc, 'span', { textContent: participant.displayName }));
1156
+ entry.append(makeElement(doc, 'span', { className: 'bb-minutes-connection-detail', textContent: '(人ではない)' }));
1157
+ }
1158
+ if (participant.removable && typeof meetingParticipants.unlink === 'function') {
1159
+ entry.append(button(doc, '取り消す', () => void unlinkParticipant(meeting, participant), { className: 'bb-minutes-participant-undo' }));
1160
+ }
1161
+ list.append(entry);
1162
+ }
1163
+ row.append(list);
1164
+ const children = [row];
1165
+ if (value.redrafting) {
1166
+ children.push(makeElement(doc, 'p', { className: 'bb-minutes-connection-note', textContent: '名前を結んだので、この議事録を作り直しています。' }));
1167
+ }
1168
+ if (current.notice) {
1169
+ children.push(makeElement(doc, 'p', { className: 'bb-minutes-participant-notice', textContent: current.notice, attrs: { role: 'status' } }));
1170
+ }
1171
+ if (current.openName && value.participants.some((participant) => participant.state === 'unconfirmed' && participant.displayName === current.openName)) {
1172
+ children.push(renderParticipantPanel(meeting, current));
1173
+ }
1174
+ slot.replaceChildren(...children);
1175
+ }
1176
+
1177
+ function refreshParticipants(meeting) {
1178
+ if (participantsSlot && state.detail?.meeting?.meeting_id === meeting.meeting_id) fillParticipants(participantsSlot, meeting);
1179
+ }
1180
+
1181
+ async function loadParticipants(detail, isCurrentRequest, { notice = '' } = {}) {
1182
+ const meetingId = detail.meeting.meeting_id;
1183
+ const previous = currentParticipants(detail.meeting);
1184
+ state.participants = { meetingId, value: previous?.value ?? null, openName: null, notice };
1185
+ let value;
1186
+ try {
1187
+ value = normalizeParticipants(await meetingParticipants.read({
1188
+ meeting: detail.meeting,
1189
+ minutes: detail.minutes,
1190
+ versions: detail.versions,
1191
+ }));
1192
+ } catch {
1193
+ value = { status: 'unavailable' };
1194
+ }
1195
+ if (!isCurrentRequest() || state.participants?.meetingId !== meetingId) return;
1196
+ state.participants = { ...state.participants, value };
1197
+ refreshParticipants(detail.meeting);
1198
+ }
1199
+
1200
+ function reloadParticipants(meeting, notice) {
1201
+ const detail = state.detail;
1202
+ if (!detail || detail.meeting?.meeting_id !== meeting.meeting_id) return Promise.resolve();
1203
+ return loadParticipants(detail, () => state.detail?.meeting?.meeting_id === meeting.meeting_id, { notice });
1204
+ }
1205
+
1206
+ function toggleParticipantPanel(meeting, displayName) {
1207
+ const current = currentParticipants(meeting);
1208
+ if (!current) return;
1209
+ const closing = current.openName === displayName;
1210
+ state.participants = { ...current, openName: closing ? null : displayName, panel: closing ? null : { status: 'loading' }, notice: '' };
1211
+ refreshParticipants(meeting);
1212
+ if (!closing) void loadCandidates(meeting, displayName);
1213
+ }
1214
+
1215
+ async function loadCandidates(meeting, displayName) {
1216
+ let panel;
1217
+ try {
1218
+ if (typeof meetingParticipants.candidates !== 'function') throw new Error('候補を読み込めませんでした');
1219
+ const value = await meetingParticipants.candidates({ meeting }, displayName);
1220
+ if (!isRecord(value)) throw new Error('候補を読み込めませんでした');
1221
+ const others = Number(value.other_meetings);
1222
+ panel = {
1223
+ status: 'ready',
1224
+ people: normalizeCandidateList(value.people, 'entity_id'),
1225
+ contacts: normalizeCandidateList(value.contacts, 'contact_id'),
1226
+ companies: normalizeCandidateList(value.companies, 'entity_id'),
1227
+ otherMeetings: Number.isInteger(others) && others > 0 ? others : 0,
1228
+ };
1229
+ } catch (error) {
1230
+ panel = { status: 'error', message: participantError(error, '候補を読み込めませんでした') };
1231
+ }
1232
+ const current = currentParticipants(meeting);
1233
+ if (!current || current.openName !== displayName) return;
1234
+ state.participants = { ...current, panel: { ...panel, name: current.panel?.name ?? displayName } };
1235
+ refreshParticipants(meeting);
1236
+ }
1237
+
1238
+ function candidateRow(name, evidence, actionLabel, onClick, busy) {
1239
+ const item = makeElement(doc, 'li', { className: 'bb-minutes-participant-candidate' });
1240
+ const body = makeElement(doc, 'div');
1241
+ body.append(makeElement(doc, 'strong', { textContent: name }));
1242
+ if (evidence.length) body.append(makeElement(doc, 'span', { className: 'bb-minutes-connection-detail', textContent: evidence.join('・') }));
1243
+ item.append(body, button(doc, actionLabel, onClick, { disabled: busy }));
1244
+ return item;
1245
+ }
1246
+
1247
+ function renderParticipantPanel(meeting, current) {
1248
+ const displayName = current.openName;
1249
+ const panelState = current.panel ?? { status: 'loading' };
1250
+ const busy = panelState.busy === true;
1251
+ const panel = makeElement(doc, 'div', {
1252
+ className: 'bb-minutes-participant-panel',
1253
+ attrs: { role: 'group', 'aria-label': `${displayName}を人物に結ぶ` },
1254
+ });
1255
+ panel.append(makeElement(doc, 'p', { className: 'bb-minutes-participant-panel-title', textContent: `「${displayName}」は誰ですか` }));
1256
+ if (panelState.status === 'loading') {
1257
+ panel.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note', textContent: '候補を読み込んでいます' }));
1258
+ } else if (panelState.status === 'error' && !panelState.people) {
1259
+ panel.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note is-unavailable', textContent: panelState.message, attrs: { role: 'alert' } }));
1260
+ }
1261
+ if (panelState.people) {
1262
+ if (panelState.otherMeetings > 0) {
1263
+ panel.append(makeElement(doc, 'p', {
1264
+ className: 'bb-minutes-participant-others',
1265
+ textContent: `同じ表示名の議事録があと${panelState.otherMeetings}件あります。結ぶとそれらも作り直します。`,
1266
+ }));
1267
+ }
1268
+ const list = makeElement(doc, 'ul', { className: 'bb-minutes-participant-candidates' });
1269
+ for (const person of panelState.people) {
1270
+ list.append(candidateRow(person.name, person.evidence, 'この人', () => void linkParticipant(meeting, displayName, { action: 'person', entity_id: person.id }), busy));
1271
+ }
1272
+ for (const contact of panelState.contacts) {
1273
+ const evidence = contact.evidence.length ? contact.evidence : contact.company ? [`名刺:${contact.company}`] : ['名刺'];
1274
+ list.append(candidateRow(contact.name, evidence, 'この名刺の名前で登録', () => void linkParticipant(meeting, displayName, { action: 'new_person', name: contact.name, contact_id: contact.id }), busy));
1275
+ }
1276
+ if (!panelState.people.length && !panelState.contacts.length) {
1277
+ panel.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note', textContent: '人物と名刺に、それらしい候補はありませんでした。' }));
1278
+ } else {
1279
+ panel.append(list);
1280
+ }
1281
+ const form = makeElement(doc, 'div', { className: 'bb-minutes-participant-new' });
1282
+ const field = labelInput(doc, '新しい人として登録する名前', { value: panelState.name ?? displayName, name: 'participant_name' });
1283
+ field.input.addEventListener('input', () => {
1284
+ const latest = currentParticipants(meeting);
1285
+ if (latest?.panel) latest.panel.name = field.input.value;
1286
+ });
1287
+ form.append(field.wrapper, button(doc, '新しい人として登録', () => void linkParticipant(meeting, displayName, {
1288
+ action: 'new_person',
1289
+ name: text(field.input.value, displayName),
1290
+ }), { disabled: busy, className: 'bb-minutes-button is-primary' }));
1291
+ panel.append(form);
1292
+ const notPerson = makeElement(doc, 'div', { className: 'bb-minutes-participant-not-person' });
1293
+ notPerson.append(makeElement(doc, 'span', { className: 'bb-minutes-connection-label', textContent: '人ではない' }));
1294
+ for (const company of panelState.companies) {
1295
+ notPerson.append(button(doc, `${company.name}(会社)に結ぶ`, () => void linkParticipant(meeting, displayName, { action: 'company', entity_id: company.id }), { disabled: busy }));
1296
+ }
1297
+ notPerson.append(button(doc, '結ばない', () => void linkParticipant(meeting, displayName, { action: 'not_person' }), { disabled: busy }));
1298
+ panel.append(notPerson);
1299
+ if (panelState.message) {
1300
+ panel.append(makeElement(doc, 'p', { className: 'bb-minutes-connection-note is-unavailable', textContent: panelState.message, attrs: { role: 'alert' } }));
1301
+ }
1302
+ if (panelState.existing) {
1303
+ panel.append(candidateRow(panelState.existing.name, ['同じ名前の人がいます'], 'この人', () => void linkParticipant(meeting, displayName, { action: 'person', entity_id: panelState.existing.id }), busy));
1304
+ }
1305
+ }
1306
+ panel.append(button(doc, 'あとで', () => toggleParticipantPanel(meeting, displayName), { className: 'bb-minutes-button is-quiet' }));
1307
+ return panel;
1308
+ }
1309
+
1310
+ function setPanel(meeting, displayName, patch) {
1311
+ const current = currentParticipants(meeting);
1312
+ if (!current || current.openName !== displayName) return false;
1313
+ state.participants = { ...current, panel: { ...current.panel, ...patch } };
1314
+ refreshParticipants(meeting);
1315
+ return true;
1316
+ }
1317
+
1318
+ function redraftNotice(result, done) {
1319
+ const count = Number(result?.redraft?.meetings);
1320
+ return Number.isInteger(count) && count > 0
1321
+ ? `${done}議事録${count}件を作り直します(数分かかります)。`
1322
+ : `${done}`;
1323
+ }
1324
+
1325
+ async function linkParticipant(meeting, displayName, input) {
1326
+ if (typeof meetingParticipants.link !== 'function') return;
1327
+ if (!setPanel(meeting, displayName, { busy: true, message: '', existing: null })) return;
1328
+ try {
1329
+ const result = await meetingParticipants.link({ meeting }, { display_name: displayName, ...input });
1330
+ const linkedName = text(result?.linked?.name);
1331
+ await reloadParticipants(meeting, redraftNotice(result, linkedName ? `「${displayName}」を${linkedName}に結びました。` : `「${displayName}」を記録しました。`));
1332
+ } catch (error) {
1333
+ const existing = isRecord(error?.candidate) && text(error.candidate.entity_id) && text(error.candidate.name)
1334
+ ? { id: text(error.candidate.entity_id), name: text(error.candidate.name) }
1335
+ : null;
1336
+ setPanel(meeting, displayName, {
1337
+ busy: false,
1338
+ existing,
1339
+ message: existing ? '同じ名前の人がいます。この人で良ければ選んでください。' : participantError(error, '結べませんでした。もう一度試してください。'),
1340
+ });
1341
+ }
1342
+ }
1343
+
1344
+ async function unlinkParticipant(meeting, participant) {
1345
+ try {
1346
+ const result = await meetingParticipants.unlink({ meeting }, {
1347
+ display_name: participant.displayName,
1348
+ ...(participant.entityId ? { entity_id: participant.entityId } : {}),
1349
+ });
1350
+ await reloadParticipants(meeting, redraftNotice(result, `「${participant.displayName}」の結び付きを取り消しました。`));
1351
+ } catch (error) {
1352
+ const current = currentParticipants(meeting);
1353
+ if (!current) return;
1354
+ state.participants = { ...current, notice: participantError(error, '取り消せませんでした。') };
1355
+ refreshParticipants(meeting);
1356
+ }
1357
+ }
1358
+
1055
1359
  function appendLoadError(parent, message, retry) {
1056
1360
  const notice = makeElement(doc, 'div', {
1057
1361
  className: 'bb-minutes-load-error',
@@ -1448,6 +1752,7 @@ export function createMeetingMinutesUI({
1448
1752
  function renderDetail(detail, documentRecord = null, selectedVersion = null) {
1449
1753
  clear(detailPane);
1450
1754
  connectionsSlot = null;
1755
+ participantsSlot = null;
1451
1756
  if (state.detailError) {
1452
1757
  const retry = state.meetingId === null ? loadMeetings : () => loadDetail(state.meetingId);
1453
1758
  appendLoadError(detailPane, state.detailError.startsWith('会議詳細を読み込めませんでした。')
@@ -1480,6 +1785,15 @@ export function createMeetingMinutesUI({
1480
1785
  textContent: participants ? `${when} / ${participants}` : when,
1481
1786
  }));
1482
1787
  detailPane.append(header);
1788
+ participantsSlot = null;
1789
+ if (hasParticipants()) {
1790
+ participantsSlot = makeElement(doc, 'section', {
1791
+ className: 'bb-minutes-participants',
1792
+ attrs: { 'aria-label': '参加者' },
1793
+ });
1794
+ fillParticipants(participantsSlot, meeting);
1795
+ detailPane.append(participantsSlot);
1796
+ }
1483
1797
  if (hasConnections()) {
1484
1798
  connectionsSlot = makeElement(doc, 'section', {
1485
1799
  className: 'bb-minutes-connections',
@@ -1631,9 +1945,11 @@ export function createMeetingMinutesUI({
1631
1945
  state.detail = detail;
1632
1946
  state.detailError = null;
1633
1947
  if (hasConnections()) state.connections = { meetingId, rows: null };
1948
+ if (hasParticipants() && state.participants?.meetingId !== meetingId) state.participants = null;
1634
1949
  renderDetail(detail);
1635
1950
  setStatus('会議詳細を読み込みました。');
1636
1951
  if (hasConnections()) void loadConnections(detail, isCurrentRequest);
1952
+ if (hasParticipants()) void loadParticipants(detail, isCurrentRequest);
1637
1953
  } catch (error) {
1638
1954
  if (!isCurrentRequest()) return state.detail;
1639
1955
  const preserved = preserveLoadedStateOnFailure(error) && state.detail?.meeting?.meeting_id === meetingId