@unson/brainbase-mcp 0.40.0 → 0.41.1

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.
@@ -88,6 +88,82 @@ export const GRAPH_PROJECT_LEDGER_COLUMNS = Object.freeze(['プロジェクト',
88
88
 
89
89
  const INVALID = Object.freeze({ state: 'invalid', reason: '応答の形式が不正です' });
90
90
 
91
+ const PARTICIPANT_STATUS_VALUES = new Set(['available', 'empty', 'partial', 'unavailable', 'unknown', 'stale', 'failed']);
92
+ const PARTICIPANT_FRESHNESS_VALUES = new Set(['fresh', 'stale', 'unknown']);
93
+
94
+ /**
95
+ * Detail adapters may expose either wire-style snake_case or JS-style camelCase.
96
+ * `ready` is the existing host spelling for an available read. Conflicting
97
+ * aliases and unknown values are invalid instead of silently becoming empty.
98
+ */
99
+ function normalizeParticipantStatus(payload) {
100
+ const raw = [payload?.participants_status, payload?.participantsStatus].filter((value) => value !== undefined);
101
+ if (raw.length === 0) return { value: null, explicit: false };
102
+ const values = raw.map((value) => value === 'ready' ? 'available' : value);
103
+ if (values.some((value) => typeof value !== 'string' || !PARTICIPANT_STATUS_VALUES.has(value)) || new Set(values).size !== 1) {
104
+ return { invalid: true };
105
+ }
106
+ return { value: values[0], explicit: true };
107
+ }
108
+
109
+ function normalizeParticipantFreshness(payload) {
110
+ const raw = [payload?.participants_freshness, payload?.participantsFreshness].filter((value) => value !== undefined);
111
+ if (raw.length === 0) return { value: null, explicit: false };
112
+ if (raw.some((value) => typeof value !== 'string' || !PARTICIPANT_FRESHNESS_VALUES.has(value)) || new Set(raw).size !== 1) {
113
+ return { invalid: true };
114
+ }
115
+ return { value: raw[0], explicit: true };
116
+ }
117
+
118
+ function normalizeParticipantAbsence(payload) {
119
+ const raw = [payload?.participants_absence_confirmed, payload?.participantsAbsenceConfirmed]
120
+ .filter((value) => value !== undefined);
121
+ if (raw.length === 0) return { confirmed: false, explicit: false };
122
+ if (raw.some((value) => typeof value !== 'boolean') || new Set(raw).size !== 1) return { invalid: true };
123
+ return { confirmed: raw[0] === true, explicit: true };
124
+ }
125
+
126
+ function confirmsParticipantAbsence(payload) {
127
+ // `absenceConfirmed` describes a project-list miss, not participant absence;
128
+ // `participants_status=empty` is only a label. The native server owns the
129
+ // same-Graph strict-validation gate before setting this dedicated flag.
130
+ return payload.participantAbsenceConfirmed === true;
131
+ }
132
+
133
+ function participantReason(payload) {
134
+ const explicit = textOrNull(payload.participants_unavailable_reason)
135
+ ?? textOrNull(payload.participantsUnavailableReason);
136
+ if (explicit) return explicit;
137
+ if (Array.isArray(payload.warnings)) {
138
+ const warning = payload.warnings
139
+ .map((item) => textOrNull(item) ?? textOrNull(item?.reason) ?? textOrNull(item?.message))
140
+ .find(Boolean);
141
+ if (warning) return warning;
142
+ }
143
+ if (payload.participantStatus === 'partial') return '一部の関係者だけを読み取っています。';
144
+ return '関係者の有無を確認できません。登録なしとは判断できません。';
145
+ }
146
+
147
+ function participantCountConflict(payload) {
148
+ const project = payload.project;
149
+ const declared = [project.participantCount, project.accountableCount]
150
+ .some((value) => Number.isInteger(value) && value > 0);
151
+ const named = Array.isArray(project.people) && project.people.length > 0;
152
+ return payload.participants.length === 0 && (declared || named);
153
+ }
154
+
155
+ function participantCountSummary(payload) {
156
+ const { participantCount, accountableCount } = payload.project;
157
+ const known = [];
158
+ if (Number.isInteger(participantCount) && participantCount > 0) known.push(`関係者${participantCount}人`);
159
+ if (Number.isInteger(accountableCount) && accountableCount > 0) known.push(`責任者${accountableCount}人`);
160
+ return known.length > 0 ? `確認済みの件数: ${known.join('、')}。` : '';
161
+ }
162
+
163
+ function projectCorrectionSubject(name, fields) {
164
+ return `プロジェクト「${name}」の${fields.map((field) => GRAPH_CORRECTION_FIELDS[field].label).join('・')}`;
165
+ }
166
+
91
167
  function isProjectPerson(value) {
92
168
  return isRecord(value) && typeof value.id === 'string' && typeof value.name === 'string' && typeof value.accountable === 'boolean';
93
169
  }
@@ -118,7 +194,24 @@ export function normalizeProjectDetail(result) {
118
194
  if (!isEntityView(project) || project.type !== 'project') return INVALID;
119
195
  if (!Array.isArray(payload.participants) || !payload.participants.every(isEdgeView)) return INVALID;
120
196
  if (payload.relations !== undefined && (!Array.isArray(payload.relations) || !payload.relations.every(isEdgeView))) return INVALID;
121
- return { state: 'ok', payload };
197
+ const participantStatus = normalizeParticipantStatus(payload);
198
+ if (participantStatus.invalid) return INVALID;
199
+ const participantFreshness = normalizeParticipantFreshness(payload);
200
+ if (participantFreshness.invalid) return INVALID;
201
+ const participantAbsence = normalizeParticipantAbsence({ ...payload, project });
202
+ if (participantAbsence.invalid) return INVALID;
203
+ return {
204
+ state: 'ok',
205
+ payload: {
206
+ ...payload,
207
+ participantStatus: participantStatus.value,
208
+ participantStatusExplicit: participantStatus.explicit,
209
+ participantFreshness: participantFreshness.value,
210
+ participantFreshnessExplicit: participantFreshness.explicit,
211
+ participantAbsenceConfirmed: participantAbsence.confirmed,
212
+ participantAbsenceExplicit: participantAbsence.explicit,
213
+ },
214
+ };
122
215
  }
123
216
 
124
217
  /**
@@ -308,7 +401,19 @@ export function createGraphProjectsView({
308
401
  now,
309
402
  scope: correctionScope,
310
403
  rerender: () => controller.render(),
311
- onSaved: async () => {
404
+ onSaved: async (payload, form) => {
405
+ // The save response is the authoritative read-back. A project name
406
+ // may have changed, so refresh the sentence above the saved form before
407
+ // the list/detail reloads redraw the rail. Otherwise the old name
408
+ // remains visible while the read-back values already show the new one.
409
+ if (form?.kind === 'update_entity'
410
+ && form.recordType === 'project'
411
+ && payload?.record?.id === form.targetId
412
+ && typeof payload.record.name === 'string'
413
+ && Array.isArray(form.fields)) {
414
+ form.subject = projectCorrectionSubject(payload.record.name, form.fields);
415
+ controller.render();
416
+ }
312
417
  await Promise.all([
313
418
  state.detail ? controller.loadDetail(state.detail.id, { keep: true }) : null,
314
419
  controller.load({ keep: true }),
@@ -591,11 +696,16 @@ export function createGraphProjectsView({
591
696
  mountGraph: projectGraphMount,
592
697
  readEntity: (id) => graphClient.read(`/entities/${encodeURIComponent(id)}`),
593
698
  onTabChange: setKnowledgeTabChrome,
699
+ onRetryProject: (projectId) => controller.loadDetail(projectId),
594
700
  });
595
701
  children.push(knowledgeWorkspace.element);
596
702
  } else if (state.selectedId) {
597
- children.push(renderProjectKnowledgeLoading(state.selectedId, state.detail ?? { state: 'loading' }, {
598
- onRetry: () => controller.loadDetail(state.selectedId),
703
+ const projectId = state.selectedId;
704
+ children.push(renderProjectKnowledgeLoading(projectId, state.detail ?? { state: 'loading' }, {
705
+ onRetry: () => {
706
+ if (state.selectedId !== projectId) return state.detail;
707
+ return controller.loadDetail(projectId);
708
+ },
599
709
  }));
600
710
  }
601
711
 
@@ -679,16 +789,52 @@ export function createGraphProjectsView({
679
789
  return item;
680
790
  }
681
791
 
682
- function renderParticipants(payload) {
792
+ function renderParticipants(payload, { onRetry } = {}) {
683
793
  const { project } = payload;
684
794
  const content = [makeElement(doc, 'p', { className: 'bb-graph-block-lead', text: '参加している人と、責任を持つ人です。終わった関わりも、終了日つきで残ります。' })];
685
- if (payload.participants.length === 0) {
795
+ const hasParticipants = payload.participants.length > 0;
796
+ const state = payload.participantStatus;
797
+ const unavailable = ['partial', 'unavailable', 'unknown'].includes(state);
798
+ const contradictory = participantCountConflict(payload)
799
+ || (state === 'empty' && hasParticipants)
800
+ || (confirmsParticipantAbsence(payload) && hasParticipants);
801
+ // Retrieval uncertainty has precedence over the empty/contradictory
802
+ // branch so the host's reason is never discarded. A contradiction may
803
+ // still be shown below and known rows are always retained.
804
+ if (unavailable) {
805
+ content.push(workspaceNotice(doc, {
806
+ label: state === 'partial' ? '一部取得' : '未確認',
807
+ tone: 'warning',
808
+ role: 'status',
809
+ text: participantReason(payload),
810
+ }));
811
+ }
812
+ if (contradictory) {
813
+ content.push(workspaceNotice(doc, {
814
+ label: '関係者を確認できません',
815
+ tone: 'warning',
816
+ role: 'status',
817
+ text: `${participantCountSummary(payload)}取得した関係者と件数が一致しません。登録なしとは判断せず、読み直してください。`,
818
+ }));
819
+ }
820
+ if (!hasParticipants && !unavailable && !contradictory && confirmsParticipantAbsence(payload)) {
686
821
  content.push(makeElement(doc, 'p', { className: 'bb-graph-empty', text: 'このプロジェクトには、まだ関係者の登録がありません。' }));
687
- } else {
822
+ } else if (!hasParticipants && !unavailable && !contradictory) {
823
+ content.push(workspaceNotice(doc, {
824
+ label: '関係者を確認できません',
825
+ tone: 'warning',
826
+ role: 'status',
827
+ text: participantReason(payload),
828
+ }));
829
+ }
830
+ if (hasParticipants) {
688
831
  const list = makeElement(doc, 'ul', { className: 'bb-graph-compact-list' });
689
832
  for (const edge of payload.participants) list.append(participantItem(edge, project, payload.asOf));
690
833
  content.push(list);
691
834
  }
835
+ if (onRetry && (contradictory || unavailable || (!hasParticipants && !confirmsParticipantAbsence(payload)))) {
836
+ content.push(workspaceActions(doc, [workspaceButton(doc, { text: '再試行', variant: 'quiet', onClick: onRetry })]));
837
+ }
692
838
  return workspaceRailBlock(doc, { title: '関係者', content });
693
839
  }
694
840
 
@@ -736,14 +882,18 @@ export function createGraphProjectsView({
736
882
  const noteSubject = { id: project.id, type: 'project', metadata: loaded?.project?.metadata ?? {} };
737
883
  const children = [workspaceRailHead(doc, { kicker: 'プロジェクト', title: project.name, sub: project.id, leading: renderProjectIcon(doc, project.icon, project.name) })];
738
884
  if (!loaded) {
885
+ const projectId = state.selectedId;
739
886
  const readState = detail?.id === state.selectedId
740
887
  ? detail
741
888
  : state.list.state === 'ok' ? { state: 'loading' } : state.list;
742
889
  children.push(graphStateNotice(doc, readState, {
743
- onRetry: () => controller.loadDetail(state.selectedId),
890
+ onRetry: () => {
891
+ if (state.selectedId !== projectId) return state.detail;
892
+ return controller.loadDetail(projectId);
893
+ },
744
894
  loadingText: 'このプロジェクトの関係者を読み込んでいます。',
745
895
  }));
746
- children.push(...railExtensions(state.selectedId, null));
896
+ children.push(...railExtensions(projectId, null));
747
897
  return children;
748
898
  }
749
899
  const principles = Array.isArray(project.decisionPrinciples) ? project.decisionPrinciples.filter((item) => typeof item === 'string' && item.trim()) : [];
@@ -766,7 +916,13 @@ export function createGraphProjectsView({
766
916
  recordDetails(doc, [['プロジェクトID', project.id], ['digest', project.digest], ['読み取った時点', loaded.asOf]]),
767
917
  ],
768
918
  }));
769
- children.push(renderParticipants(loaded));
919
+ const projectId = state.selectedId;
920
+ children.push(renderParticipants(loaded, {
921
+ onRetry: () => {
922
+ if (state.selectedId !== projectId) return state.detail;
923
+ return controller.loadDetail(projectId);
924
+ },
925
+ }));
770
926
  if (writable) {
771
927
  const canAdd = scope.createEdges;
772
928
  const projectFields = scope.entity('project') ? scope.fields(['name', 'goal', 'status', 'icon']) : [];
@@ -787,7 +943,7 @@ export function createGraphProjectsView({
787
943
  onClick: () => openCorrection('project', () => correction.openEntity(project, {
788
944
  fields: projectFields,
789
945
  title: 'プロジェクトを直す',
790
- subject: `プロジェクト「${project.name}」の${projectFields.map((field) => GRAPH_CORRECTION_FIELDS[field].label).join('・')}`,
946
+ subject: projectCorrectionSubject(project.name, projectFields),
791
947
  })),
792
948
  }) : null,
793
949
  ].filter(Boolean);
@@ -119,6 +119,114 @@ export function normalizeEntityDetail(result) {
119
119
  return { state: 'ok', payload };
120
120
  }
121
121
 
122
+ const RELATION_UNAVAILABLE_STATES = new Set(['failed', 'incomplete', 'partial', 'stale', 'unknown', 'unavailable']);
123
+ const RELATION_EMPTY_STATES = new Set(['confirmed_empty', 'empty', 'none']);
124
+
125
+ function normalizedRelationState(value) {
126
+ if (typeof value !== 'string') return null;
127
+ const state = value.trim().toLowerCase().replace(/[- ]/gu, '_');
128
+ if (RELATION_UNAVAILABLE_STATES.has(state)) return 'unknown';
129
+ if (RELATION_EMPTY_STATES.has(state)) return 'confirmed_empty';
130
+ if (['available', 'complete', 'ready'].includes(state)) return 'available';
131
+ return null;
132
+ }
133
+
134
+ function firstText(...values) {
135
+ for (const value of values) {
136
+ const text = textOrNull(value);
137
+ if (text) return text;
138
+ }
139
+ return null;
140
+ }
141
+
142
+ function relationStatusFrom(payload, direction) {
143
+ const direct = [
144
+ payload[`${direction}_status`],
145
+ payload[`${direction}Status`],
146
+ payload[`${direction}_availability`],
147
+ payload[`${direction}Availability`],
148
+ ].map(normalizedRelationState).find(Boolean);
149
+ if (direct) return direct;
150
+ return [
151
+ payload.relations_status,
152
+ payload.relationsStatus,
153
+ payload.relation_status,
154
+ payload.relationStatus,
155
+ payload.edges_status,
156
+ payload.edgesStatus,
157
+ ].map(normalizedRelationState).find(Boolean) ?? null;
158
+ }
159
+
160
+ function participantStatusFrom(payload) {
161
+ return [
162
+ payload.participants_status,
163
+ payload.participantsStatus,
164
+ payload.participant_status,
165
+ payload.participantStatus,
166
+ ].map(normalizedRelationState).find(Boolean) ?? null;
167
+ }
168
+
169
+ function knownParticipantCount(payload) {
170
+ const entity = payload.entity ?? {};
171
+ const count = [
172
+ entity.participantCount,
173
+ entity.participant_count,
174
+ entity.accountableCount,
175
+ entity.accountable_count,
176
+ payload.actual_known_count,
177
+ payload.actualKnownCount,
178
+ ].find((value) => Number.isInteger(value) && value > 0);
179
+ if (count) return count;
180
+ if (Array.isArray(entity.people) && entity.people.length > 0) return entity.people.length;
181
+ if (Array.isArray(payload.people) && payload.people.length > 0) return payload.people.length;
182
+ return null;
183
+ }
184
+
185
+ function relationUnavailableReason(payload) {
186
+ const explicit = firstText(
187
+ payload.relations_reason,
188
+ payload.relationsReason,
189
+ payload.relation_reason,
190
+ payload.relationReason,
191
+ payload.edges_reason,
192
+ payload.edgesReason,
193
+ payload.participants_reason,
194
+ payload.participantsReason,
195
+ payload.participants_unavailable_reason,
196
+ payload.participantsUnavailableReason,
197
+ payload.reason,
198
+ payload.source?.reason,
199
+ payload.source?.message,
200
+ );
201
+ if (explicit) return `${explicit} 関係の有無は確認できません。登録なしとは判断できません。`;
202
+ const count = knownParticipantCount(payload);
203
+ const known = count ? `関係者の件数(${count}人)は確認できていますが、` : '';
204
+ return `${known}関係の有無を確認できません。関係の詳細が未取得のため、登録なしとは判断できません。`;
205
+ }
206
+
207
+ /**
208
+ * Empty edge arrays are a confirmed empty result only when the host says so.
209
+ * A project can carry a known participant count while its relation edges are
210
+ * unavailable; rendering the empty copy in that case turns unknown data into
211
+ * a product assertion.
212
+ */
213
+ function relationAvailability(payload, direction) {
214
+ if (payload[direction].length > 0) return { state: 'available' };
215
+ const direct = relationStatusFrom(payload, direction);
216
+ if (direct === 'unknown') return { state: 'unknown', reason: relationUnavailableReason(payload) };
217
+ // A host may accidentally include a stale confirmed-empty status alongside
218
+ // an incomplete relation payload. Completeness signals take precedence so
219
+ // the UI cannot turn a known participant count into a false "none" claim.
220
+ const edgesNotProvided = payload.source?.edges_not_provided === true || payload.source?.edgesNotProvided === true;
221
+ const participantsUnknown = participantStatusFrom(payload) === 'unknown';
222
+ const knownCount = knownParticipantCount(payload);
223
+ if (edgesNotProvided || participantsUnknown || knownCount) {
224
+ return { state: 'unknown', reason: relationUnavailableReason(payload) };
225
+ }
226
+ if (direct === 'confirmed_empty') return { state: 'confirmed_empty' };
227
+ return { state: 'confirmed_empty' };
228
+ }
229
+
122
230
  // A host that could not finish counting sends null (with countStatus
123
231
  // 'unknown', or 'at_least' plus atLeast) rather than a number it cannot vouch for.
124
232
  function isCount(value) {
@@ -396,11 +504,14 @@ export function createGraphRegistryView({
396
504
  }
397
505
  const { payload } = state.ontology;
398
506
  const ontology = payload.ontology;
507
+ const version = textOrNull(ontology.version);
508
+ const currentVersion = textOrNull(ontology.currentVersion);
509
+ const freshness = ontology.upToDate === false && currentVersion
510
+ ? `最新ではありません。最新は ${currentVersion}`
511
+ : ontology.upToDate === true && version ? '最新' : '最新性は未確認';
399
512
  section.append(makeElement(doc, 'p', {
400
513
  className: 'bb-gr-caption',
401
- text: `定義の版 ${ontology.upToDate === false
402
- ? `${String(ontology.version)}(最新ではありません。最新は ${String(ontology.currentVersion)})`
403
- : `${String(ontology.version)}(最新)`}`,
514
+ text: `定義の版 ${version ?? '未確認'}(${freshness})`,
404
515
  }));
405
516
  section.append(makeElement(doc, 'h3', { className: 'bb-gr-subheading', text: 'つながり方' }));
406
517
  section.append(workspaceLedger(doc, {
@@ -441,7 +552,11 @@ export function createGraphRegistryView({
441
552
  );
442
553
  section.append(list);
443
554
  }
444
- section.append(recordDetails(doc, [['定義ID', ontology.id], ['定義の版', ontology.version], ['release digest', ontology.releaseDigest]]));
555
+ section.append(recordDetails(doc, [
556
+ ['定義ID', textOrNull(ontology.id) ?? '未確認'],
557
+ ['定義の版', version ?? '未確認'],
558
+ ['release digest', textOrNull(ontology.releaseDigest) ?? '未確認'],
559
+ ]));
445
560
  return section;
446
561
  }
447
562
 
@@ -573,7 +688,22 @@ export function createGraphRegistryView({
573
688
  })];
574
689
  const edges = payload[direction];
575
690
  if (edges.length === 0) {
576
- content.push(makeElement(doc, 'p', { className: 'bb-graph-empty', text: outgoing ? 'この記録から出る関係はありません。' : 'この記録へ入る関係はありません。' }));
691
+ const availability = relationAvailability(payload, direction);
692
+ if (availability.state === 'unknown') {
693
+ content.push(workspaceNotice(doc, {
694
+ label: '未確認',
695
+ tone: 'warning',
696
+ role: 'status',
697
+ text: availability.reason,
698
+ }));
699
+ content.push(workspaceActions(doc, [workspaceButton(doc, {
700
+ text: '再試行',
701
+ variant: 'quiet',
702
+ onClick: () => controller.loadEntity(entity.id, { keep: true }),
703
+ })]));
704
+ } else {
705
+ content.push(makeElement(doc, 'p', { className: 'bb-graph-empty', text: outgoing ? 'この記録から出る関係はありません。' : 'この記録へ入る関係はありません。' }));
706
+ }
577
707
  } else {
578
708
  const list = makeElement(doc, 'ul', { className: 'bb-graph-compact-list' });
579
709
  for (const edge of edges) list.append(relationItem(edge, entity, payload.asOf));
@@ -584,7 +584,12 @@ export function graphStateNotice(doc, readState, { onRetry, loadingText = '読
584
584
  return workspaceNotice(doc, {
585
585
  label: '読み取り失敗',
586
586
  tone: 'danger',
587
- text: noticeBody(doc, makeElement(doc, 'p', { text: `読み取れませんでした(${readState.reason ?? '理由不明'})。0件ではありません。` }), retryButton(doc, onRetry)),
587
+ text: noticeBody(
588
+ doc,
589
+ makeElement(doc, 'p', { className: 'bb-graph-error-primary', text: '記録を取得できませんでした。' }),
590
+ makeElement(doc, 'small', { className: 'bb-graph-error-detail', text: `読み取れませんでした。詳細: ${readState.reason ?? '理由不明'}。0件ではありません。` }),
591
+ retryButton(doc, onRetry),
592
+ ),
588
593
  });
589
594
  }
590
595
 
@@ -1194,7 +1194,11 @@ function statusNotice(doc, phase, error, home, onRetry) {
1194
1194
  if (phase === 'loading') return workspaceNotice(doc, { label: '読み込み中', text: '判断履歴を確認しています。', tone: 'info' });
1195
1195
  if (phase === 'error') return workspaceNotice(doc, {
1196
1196
  label: '取得できません',
1197
- text: [`判断の記録を確認できません。${error ? `理由: ${error}` : ''} `, workspaceButton(doc, { text: '再読み込み', variant: 'quiet', onClick: onRetry })],
1197
+ text: [
1198
+ { className: 'bb-jh-error-primary', text: '判断履歴を取得できませんでした。' },
1199
+ error ? { className: 'bb-jh-error-detail', text: `詳細: ${error}` } : null,
1200
+ workspaceButton(doc, { text: '再読み込み', variant: 'quiet', onClick: onRetry }),
1201
+ ],
1198
1202
  tone: 'danger',
1199
1203
  role: 'alert',
1200
1204
  });
@@ -16,12 +16,31 @@
16
16
  .bb-minutes-eyebrow,
17
17
  .bb-minutes-label,
18
18
  .bb-minutes-item-meta,
19
+ .bb-minutes-meeting-label,
19
20
  .bb-minutes-rail-title,
20
21
  .bb-minutes-revision {
21
22
  color: var(--bb-color-muted);
22
23
  font-size: 0.82rem;
23
24
  }
24
25
 
26
+ .bb-minutes-meeting-label {
27
+ margin: 0;
28
+ overflow-wrap: anywhere;
29
+ }
30
+
31
+ .bb-minutes-create-guard {
32
+ flex-basis: 100%;
33
+ margin: 0;
34
+ color: var(--bb-color-danger, var(--bb-color-ink));
35
+ }
36
+
37
+ .bb-minutes-edit-guard {
38
+ display: grid;
39
+ gap: var(--bb-space-2);
40
+ margin: 0;
41
+ color: var(--bb-color-danger, var(--bb-color-ink));
42
+ }
43
+
25
44
  .bb-minutes-heading h1,
26
45
  .bb-minutes-heading p,
27
46
  .bb-minutes-pane-head h2,