@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.
- package/dist/foundation-http.d.ts +12 -2
- package/dist/foundation-http.js +183 -8
- package/dist/graph-web.d.ts +2 -0
- package/dist/graph-web.js +10 -1
- package/dist/judgment-history.js +3 -1
- package/dist/local-web-host.js +1 -1
- package/dist/portable-graph.d.ts +2 -2
- package/dist/portable-graph.js +4 -3
- package/dist/types.d.ts +32 -1
- package/package.json +2 -2
- package/ui/foundation-overview.js +17 -1
- package/ui/graph-own-share.js +4 -2
- package/ui/graph-projects-view.js +167 -11
- package/ui/graph-registry-view.js +135 -5
- package/ui/graph-view-shared.js +6 -1
- package/ui/judgment-history.js +5 -1
- package/ui/meeting-minutes.css +19 -0
- package/ui/meeting-minutes.js +124 -6
- package/ui/objective-editor-http-port.js +21 -8
- package/ui/objective-editor.js +186 -24
- package/ui/objective-readback-contract.js +177 -0
- package/ui/outcome-knowledge.css +18 -1
- package/ui/outcome-knowledge.js +523 -55
- package/ui/project-workspace.js +177 -4
- package/ui/world/world-canvas-ui.css +32 -0
- package/ui/world/world-canvas-ui.js +30 -2
- package/ui/world/world-view.js +5 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
598
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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: `定義の版 ${
|
|
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, [
|
|
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
|
-
|
|
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));
|
package/ui/graph-view-shared.js
CHANGED
|
@@ -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(
|
|
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
|
|
package/ui/judgment-history.js
CHANGED
|
@@ -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: [
|
|
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
|
});
|
package/ui/meeting-minutes.css
CHANGED
|
@@ -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,
|