@unson/brainbase-mcp 0.10.0 → 0.10.2
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 +2 -2
- package/ui/graph-projects-view.js +80 -3
- package/ui/project-graph-entry.js +36 -33
- package/ui/project-graph.js +100 -5
- package/ui/project-workspace.js +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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",
|
|
@@ -336,5 +336,5 @@
|
|
|
336
336
|
"engines": {
|
|
337
337
|
"node": ">=20"
|
|
338
338
|
},
|
|
339
|
-
"gitHead": "
|
|
339
|
+
"gitHead": "d8334b5e94cdc643f5de90ccfc883a5e67fdd9af"
|
|
340
340
|
}
|
|
@@ -148,6 +148,19 @@ function failureText(error) {
|
|
|
148
148
|
return textOrNull(error instanceof Error ? error.message : typeof error === 'string' ? error : null) ?? '理由不明';
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* A host may know the selected project's identity before its Graph list is
|
|
153
|
+
* available. Keep this boundary deliberately small: the early shell only
|
|
154
|
+
* accepts an id and display name, and never treats a host summary as Graph
|
|
155
|
+
* data such as goal, status or participant counts.
|
|
156
|
+
*/
|
|
157
|
+
function normalizeProjectSummary(value) {
|
|
158
|
+
if (!isRecord(value)) return null;
|
|
159
|
+
const id = textOrNull(value.id ?? value.code);
|
|
160
|
+
if (!id) return null;
|
|
161
|
+
return { id, name: textOrNull(value.name) ?? id };
|
|
162
|
+
}
|
|
163
|
+
|
|
151
164
|
/**
|
|
152
165
|
* Mounts 「プロジェクトと関係者」.
|
|
153
166
|
*
|
|
@@ -201,6 +214,9 @@ function failureText(error) {
|
|
|
201
214
|
* @param {(projectId: string) => Promise<object>} [options.loadProjectContext]
|
|
202
215
|
* Optional read-only supplementary sections (tasks/knowledge). A failed
|
|
203
216
|
* context read is kept separate from the Graph detail read.
|
|
217
|
+
* @param {Array<{ id: string, name?: string }> | (() => Array<{ id: string, name?: string }>)} [options.projectSummaries]
|
|
218
|
+
* Optional host-known project identities. These are used only to draw the
|
|
219
|
+
* selected project's early loading shell before the Graph list is read.
|
|
204
220
|
* @param {(container: Element, options: object) => { destroy?: Function }} [options.mountProjectGraph]
|
|
205
221
|
* Host-injected Sigma.js graph mount. When omitted, the shared browser
|
|
206
222
|
* entry is used. The workspace keeps an accessible relation list when
|
|
@@ -232,6 +248,7 @@ export function createGraphProjectsView({
|
|
|
232
248
|
onSelect,
|
|
233
249
|
ownShare,
|
|
234
250
|
loadProjectContext,
|
|
251
|
+
projectSummaries,
|
|
235
252
|
mountProjectGraph,
|
|
236
253
|
} = {}) {
|
|
237
254
|
if (!root) throw new TypeError('root is required');
|
|
@@ -267,6 +284,7 @@ export function createGraphProjectsView({
|
|
|
267
284
|
let knowledgeWorkspace = null;
|
|
268
285
|
let contextRequest = 0;
|
|
269
286
|
let detailRequest = 0;
|
|
287
|
+
let hostListRequest = 0;
|
|
270
288
|
let viewDestroyed = false;
|
|
271
289
|
|
|
272
290
|
const correction = createGraphCorrection({
|
|
@@ -289,6 +307,46 @@ export function createGraphProjectsView({
|
|
|
289
307
|
return state.list.state === 'ok' ? state.list.payload.projects.find((project) => project.id === id) ?? null : null;
|
|
290
308
|
}
|
|
291
309
|
|
|
310
|
+
function projectSummaryFor(id) {
|
|
311
|
+
const target = textOrNull(id);
|
|
312
|
+
if (!target) return null;
|
|
313
|
+
let summaries = projectSummaries;
|
|
314
|
+
try {
|
|
315
|
+
if (typeof summaries === 'function') summaries = summaries();
|
|
316
|
+
} catch {
|
|
317
|
+
summaries = null;
|
|
318
|
+
}
|
|
319
|
+
if (Array.isArray(summaries)) {
|
|
320
|
+
const summary = summaries.map(normalizeProjectSummary).find((item) => item?.id === target);
|
|
321
|
+
if (summary) return summary;
|
|
322
|
+
}
|
|
323
|
+
const listed = listItem(target);
|
|
324
|
+
return listed ? { id: listed.id, name: listed.name } : { id: target, name: target };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function renderProjectKnowledgeLoading(id, readState, { onRetry } = {}) {
|
|
328
|
+
const target = textOrNull(id);
|
|
329
|
+
if (!target) return null;
|
|
330
|
+
const project = projectSummaryFor(target);
|
|
331
|
+
const loading = makeElement(doc, 'section', {
|
|
332
|
+
className: 'bb-pkw bb-pkw-loading',
|
|
333
|
+
attrs: { 'aria-label': 'プロジェクトの知識', 'data-project-id': target },
|
|
334
|
+
});
|
|
335
|
+
loading.append(makeElement(doc, 'div', { className: 'bb-pkw-kicker', text: 'プロジェクトの記録' }));
|
|
336
|
+
const header = makeElement(doc, 'header', { className: 'bb-pkw-header' });
|
|
337
|
+
header.append(
|
|
338
|
+
makeElement(doc, 'h2', { text: project.name }),
|
|
339
|
+
makeElement(doc, 'p', { className: 'bb-pkw-lead', text: '目的・関係・判断の根拠を、実際に記録された情報からたどれます。' }),
|
|
340
|
+
);
|
|
341
|
+
loading.append(header);
|
|
342
|
+
const notice = graphStateNotice(doc, readState, {
|
|
343
|
+
onRetry,
|
|
344
|
+
loadingText: 'プロジェクトの記録を読み込んでいます。',
|
|
345
|
+
});
|
|
346
|
+
if (notice) loading.append(notice);
|
|
347
|
+
return loading;
|
|
348
|
+
}
|
|
349
|
+
|
|
292
350
|
function openCorrection(at, open) {
|
|
293
351
|
state.panelAt = at;
|
|
294
352
|
open();
|
|
@@ -456,6 +514,9 @@ export function createGraphProjectsView({
|
|
|
456
514
|
const stateNotice = graphStateNotice(doc, state.list, { onRetry: () => controller.load(), loadingText: 'プロジェクトを読み込んでいます。' });
|
|
457
515
|
if (stateNotice) {
|
|
458
516
|
children.push(stateNotice);
|
|
517
|
+
if (state.selectedId) {
|
|
518
|
+
children.push(renderProjectKnowledgeLoading(state.selectedId, state.list, { onRetry: () => controller.load() }));
|
|
519
|
+
}
|
|
459
520
|
return children;
|
|
460
521
|
}
|
|
461
522
|
const { payload } = state.list;
|
|
@@ -486,6 +547,10 @@ export function createGraphProjectsView({
|
|
|
486
547
|
onTabChange: setKnowledgeTabChrome,
|
|
487
548
|
});
|
|
488
549
|
children.push(knowledgeWorkspace.element);
|
|
550
|
+
} else if (state.selectedId) {
|
|
551
|
+
children.push(renderProjectKnowledgeLoading(state.selectedId, state.detail ?? { state: 'loading' }, {
|
|
552
|
+
onRetry: () => controller.loadDetail(state.selectedId),
|
|
553
|
+
}));
|
|
489
554
|
}
|
|
490
555
|
|
|
491
556
|
if (payload.projects.length === 0) {
|
|
@@ -623,7 +688,10 @@ export function createGraphProjectsView({
|
|
|
623
688
|
const project = loaded?.project ?? listItem(state.selectedId) ?? { id: state.selectedId, name: state.selectedId };
|
|
624
689
|
const children = [workspaceRailHead(doc, { kicker: 'プロジェクト', title: project.name, sub: project.id })];
|
|
625
690
|
if (!loaded) {
|
|
626
|
-
|
|
691
|
+
const readState = detail?.id === state.selectedId
|
|
692
|
+
? detail
|
|
693
|
+
: state.list.state === 'ok' ? { state: 'loading' } : state.list;
|
|
694
|
+
children.push(graphStateNotice(doc, readState, {
|
|
627
695
|
onRetry: () => controller.loadDetail(state.selectedId),
|
|
628
696
|
loadingText: 'このプロジェクトの関係者を読み込んでいます。',
|
|
629
697
|
}));
|
|
@@ -692,13 +760,16 @@ export function createGraphProjectsView({
|
|
|
692
760
|
|
|
693
761
|
/** Reads the host's project list (see `load`). */
|
|
694
762
|
async function loadHostList({ keep = false } = {}) {
|
|
763
|
+
const request = ++hostListRequest;
|
|
764
|
+
if (viewDestroyed) return state.list;
|
|
695
765
|
if (!(keep && state.list.state === 'ok')) {
|
|
696
766
|
state.list = { state: 'loading' };
|
|
697
767
|
controller.render();
|
|
698
768
|
}
|
|
699
|
-
|
|
769
|
+
const result = await client.read('/projects');
|
|
770
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
771
|
+
state.list = normalizeProjectList(result);
|
|
700
772
|
if (state.list.state !== 'ok') {
|
|
701
|
-
if (!keep) state.selectedId = null;
|
|
702
773
|
controller.render();
|
|
703
774
|
return state.list;
|
|
704
775
|
}
|
|
@@ -711,16 +782,21 @@ export function createGraphProjectsView({
|
|
|
711
782
|
contextRequest += 1;
|
|
712
783
|
state.selectedId = projects[0]?.id ?? null;
|
|
713
784
|
if (state.selectedId) {
|
|
785
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
714
786
|
const reading = controller.loadDetail(state.selectedId);
|
|
715
787
|
notifySelect(state.selectedId);
|
|
716
788
|
await reading;
|
|
789
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
717
790
|
return state.list;
|
|
718
791
|
}
|
|
719
792
|
} else if (state.detail?.id !== state.selectedId) {
|
|
720
793
|
// The project the host asked for is listed; read it for the rail.
|
|
794
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
721
795
|
await controller.loadDetail(state.selectedId);
|
|
796
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
722
797
|
return state.list;
|
|
723
798
|
}
|
|
799
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
724
800
|
controller.render();
|
|
725
801
|
return state.list;
|
|
726
802
|
}
|
|
@@ -744,6 +820,7 @@ export function createGraphProjectsView({
|
|
|
744
820
|
destroy() {
|
|
745
821
|
if (viewDestroyed) return;
|
|
746
822
|
viewDestroyed = true;
|
|
823
|
+
hostListRequest += 1;
|
|
747
824
|
detailRequest += 1;
|
|
748
825
|
contextRequest += 1;
|
|
749
826
|
knowledgeWorkspace?.destroy();
|
|
@@ -10,7 +10,9 @@ import { MultiDirectedGraph, Sigma } from './project-graph-vendor.js';
|
|
|
10
10
|
import {
|
|
11
11
|
PROJECT_GRAPH_TYPE_COLORS,
|
|
12
12
|
createProjectGraphModel,
|
|
13
|
+
projectGraphEdgePresentation,
|
|
13
14
|
projectGraphNeighborIds,
|
|
15
|
+
projectGraphNodePresentation,
|
|
14
16
|
} from './project-graph.js';
|
|
15
17
|
|
|
16
18
|
const DEFAULT_EDGE_COLOR = '#cbd5e1';
|
|
@@ -85,12 +87,6 @@ function makeGraph(model) {
|
|
|
85
87
|
return graph;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
function edgeTouches(edge, nodeId, neighborIds) {
|
|
89
|
-
if (!nodeId) return false;
|
|
90
|
-
return edge.source === nodeId || edge.target === nodeId ||
|
|
91
|
-
(neighborIds.has(edge.source) && neighborIds.has(edge.target));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
90
|
/**
|
|
95
91
|
* Mounts one project graph and returns an idempotent resource handle.
|
|
96
92
|
*
|
|
@@ -153,46 +149,53 @@ export function mountProjectGraph(container, options = {}) {
|
|
|
153
149
|
const base = { ...data };
|
|
154
150
|
const neighbors = getNeighbors();
|
|
155
151
|
if (!selectedId) return base;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
...base,
|
|
168
|
-
color: base.baseColor || PROJECT_GRAPH_TYPE_COLORS.unknown,
|
|
169
|
-
size: Math.max(9, (base.size || 9) + 1),
|
|
170
|
-
highlighted: true,
|
|
171
|
-
forceLabel: true,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
152
|
+
const presentation = projectGraphNodePresentation(
|
|
153
|
+
{ id: nodeId, size: base.size },
|
|
154
|
+
{ selectedId, neighborIds: neighbors },
|
|
155
|
+
);
|
|
156
|
+
const { state, ...visuals } = presentation;
|
|
157
|
+
if (state === 'selected') return { ...base, ...visuals, color: SELECTED_COLOR };
|
|
158
|
+
if (state === 'neighbor') return {
|
|
159
|
+
...base,
|
|
160
|
+
...visuals,
|
|
161
|
+
color: base.baseColor || PROJECT_GRAPH_TYPE_COLORS.unknown,
|
|
162
|
+
};
|
|
174
163
|
return {
|
|
175
164
|
...base,
|
|
165
|
+
...visuals,
|
|
176
166
|
color: UNRELATED_COLOR,
|
|
177
|
-
size: Math.max(5, (base.size || 9) * 0.72),
|
|
178
|
-
label: null,
|
|
179
|
-
forceLabel: false,
|
|
180
|
-
highlighted: false,
|
|
181
167
|
};
|
|
182
168
|
};
|
|
183
169
|
|
|
184
170
|
const edgeReducer = (edgeId, data) => {
|
|
185
171
|
const edge = edgeById.get(edgeId);
|
|
186
172
|
const base = { ...data };
|
|
187
|
-
if (!selectedId || !edge)
|
|
173
|
+
if (!selectedId || !edge) {
|
|
174
|
+
return {
|
|
175
|
+
...base,
|
|
176
|
+
label: null,
|
|
177
|
+
forceLabel: false,
|
|
178
|
+
color: base.baseColor || DEFAULT_EDGE_COLOR,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
188
181
|
const neighbors = getNeighbors();
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
const presentation = projectGraphEdgePresentation(edge, {
|
|
183
|
+
selectedId,
|
|
184
|
+
neighborIds: neighbors,
|
|
185
|
+
});
|
|
186
|
+
if (!presentation.relevant) {
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
label: null,
|
|
190
|
+
forceLabel: false,
|
|
191
|
+
color: UNRELATED_EDGE_COLOR,
|
|
192
|
+
size: 0.5,
|
|
193
|
+
};
|
|
191
194
|
}
|
|
192
195
|
return {
|
|
193
196
|
...base,
|
|
194
|
-
label: edge.label
|
|
195
|
-
forceLabel:
|
|
197
|
+
label: presentation.showLabel ? edge.label : null,
|
|
198
|
+
forceLabel: presentation.forceLabel,
|
|
196
199
|
color: edge.color || DEFAULT_EDGE_COLOR,
|
|
197
200
|
size: 1.5,
|
|
198
201
|
};
|
package/ui/project-graph.js
CHANGED
|
@@ -24,6 +24,9 @@ export const PROJECT_GRAPH_TYPE_COLORS = Object.freeze({
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
const EMPTY_ARRAY = Object.freeze([]);
|
|
27
|
+
const MAX_SELECTED_NODE_SIZE = 18;
|
|
28
|
+
const MAX_NEIGHBOR_NODE_SIZE = 12;
|
|
29
|
+
const MAX_UNRELATED_NODE_SIZE = 10;
|
|
27
30
|
|
|
28
31
|
function isRecord(value) {
|
|
29
32
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -168,6 +171,81 @@ export function projectGraphNeighborIds(nodes, edges, selectedId) {
|
|
|
168
171
|
return neighbors;
|
|
169
172
|
}
|
|
170
173
|
|
|
174
|
+
function toIdSet(value) {
|
|
175
|
+
return value instanceof Set
|
|
176
|
+
? value
|
|
177
|
+
: new Set(Array.isArray(value) ? value.filter(Boolean) : EMPTY_ARRAY);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Returns the visual state for one node in a selected graph neighborhood.
|
|
182
|
+
*
|
|
183
|
+
* A selected node is the only node that bypasses Sigma's label collision
|
|
184
|
+
* avoidance. Neighbors keep their labels available to Sigma's normal label
|
|
185
|
+
* grid, while their size and hover state stay bounded in dense neighborhoods.
|
|
186
|
+
*/
|
|
187
|
+
export function projectGraphNodePresentation(node, { selectedId = null, neighborIds = EMPTY_ARRAY } = {}) {
|
|
188
|
+
const id = stringValue(node?.id);
|
|
189
|
+
const neighbors = toIdSet(neighborIds);
|
|
190
|
+
const state = id && id === selectedId
|
|
191
|
+
? 'selected'
|
|
192
|
+
: id && neighbors.has(id)
|
|
193
|
+
? 'neighbor'
|
|
194
|
+
: 'unrelated';
|
|
195
|
+
const baseSize = Math.min(finitePositive(node?.size, 9), MAX_SELECTED_NODE_SIZE);
|
|
196
|
+
|
|
197
|
+
if (state === 'selected') {
|
|
198
|
+
return {
|
|
199
|
+
state,
|
|
200
|
+
size: Math.min(MAX_SELECTED_NODE_SIZE, Math.max(10, baseSize + 3)),
|
|
201
|
+
forceLabel: true,
|
|
202
|
+
highlighted: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state === 'neighbor') {
|
|
207
|
+
return {
|
|
208
|
+
state,
|
|
209
|
+
size: Math.min(MAX_NEIGHBOR_NODE_SIZE, Math.max(6, baseSize)),
|
|
210
|
+
forceLabel: false,
|
|
211
|
+
highlighted: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
state,
|
|
217
|
+
size: Math.min(MAX_UNRELATED_NODE_SIZE, Math.max(4, baseSize * 0.72)),
|
|
218
|
+
label: null,
|
|
219
|
+
forceLabel: false,
|
|
220
|
+
highlighted: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Returns the label policy for one edge in a selected graph neighborhood.
|
|
226
|
+
* Dense neighborhoods omit edge labels; relation details remain available
|
|
227
|
+
* in the selected record panel. Small neighborhoods show relevant labels.
|
|
228
|
+
*/
|
|
229
|
+
export function projectGraphEdgePresentation(edge, { selectedId = null, neighborIds = EMPTY_ARRAY } = {}) {
|
|
230
|
+
const neighbors = toIdSet(neighborIds);
|
|
231
|
+
const relevant = Boolean(
|
|
232
|
+
selectedId && edge && (
|
|
233
|
+
edge.source === selectedId ||
|
|
234
|
+
edge.target === selectedId ||
|
|
235
|
+
(neighbors.has(edge.source) && neighbors.has(edge.target))
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
// Sigma's edge-label renderer does not apply the same collision grid as
|
|
239
|
+
// node labels. Hide labels for a dense neighborhood and leave the relation
|
|
240
|
+
// available through selection/details instead of painting a radial stack.
|
|
241
|
+
const denseNeighborhood = neighbors.size > 12;
|
|
242
|
+
return {
|
|
243
|
+
relevant,
|
|
244
|
+
showLabel: relevant && !denseNeighborhood && Boolean(stringValue(edge?.label)),
|
|
245
|
+
forceLabel: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
171
249
|
/**
|
|
172
250
|
* Computes a stable clustered layout. The selected node is the anchor when
|
|
173
251
|
* it exists; its direct neighbours form the first ring and the remaining
|
|
@@ -188,11 +266,28 @@ export function computeProjectGraphLayout(nodes, edges = EMPTY_ARRAY, { selected
|
|
|
188
266
|
const neighbors = list
|
|
189
267
|
.filter((node) => neighborIds.has(node.id))
|
|
190
268
|
.sort(compareNodes);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
269
|
+
// Keep high-degree selections readable by spreading neighbors over stable
|
|
270
|
+
// concentric rings. A single ring turns a person with dozens of relations
|
|
271
|
+
// into a dense necklace even when Sigma's label grid is working correctly.
|
|
272
|
+
const neighborRingCount = neighbors.length ? Math.ceil(neighbors.length / 24) : 0;
|
|
273
|
+
const neighborRingRadius = 2.6;
|
|
274
|
+
const neighborRingGap = 1.8;
|
|
275
|
+
let neighborOffset = 0;
|
|
276
|
+
for (let ringIndex = 0; ringIndex < neighborRingCount; ringIndex += 1) {
|
|
277
|
+
const remaining = neighbors.length - neighborOffset;
|
|
278
|
+
const ringsLeft = neighborRingCount - ringIndex;
|
|
279
|
+
const ringSize = Math.ceil(remaining / ringsLeft);
|
|
280
|
+
const radius = neighborRingRadius + ringIndex * neighborRingGap;
|
|
281
|
+
for (let index = 0; index < ringSize; index += 1) {
|
|
282
|
+
const angle = -Math.PI / 2 + (2 * Math.PI * index) / Math.max(1, ringSize) + ringIndex * 0.19;
|
|
283
|
+
const node = neighbors[neighborOffset + index];
|
|
284
|
+
positions[node.id] = { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius };
|
|
285
|
+
}
|
|
286
|
+
neighborOffset += ringSize;
|
|
287
|
+
}
|
|
288
|
+
const neighborRadius = neighborRingCount
|
|
289
|
+
? neighborRingRadius + (neighborRingCount - 1) * neighborRingGap
|
|
290
|
+
: 1.5;
|
|
196
291
|
|
|
197
292
|
const remaining = list.filter((node) => !positions[node.id]);
|
|
198
293
|
const groups = new Map();
|
package/ui/project-workspace.js
CHANGED
|
@@ -445,8 +445,13 @@ export function createProjectKnowledgeWorkspace({
|
|
|
445
445
|
const payload = projectDetailPayload(detail);
|
|
446
446
|
const project = payload?.project;
|
|
447
447
|
if (!project) throw new TypeError('project detail is required');
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
// Keep the project-bound projection separate from the mutable exploration
|
|
449
|
+
// graph. Reading a related entity may reveal arbitrary neighboring records,
|
|
450
|
+
// but those records are not evidence that they belong to this project.
|
|
451
|
+
const overviewEntities = graphEntityMap(payload);
|
|
452
|
+
const overviewEdges = graphEvidenceEdges(payload);
|
|
453
|
+
const entities = new Map(overviewEntities);
|
|
454
|
+
const edges = [...overviewEdges];
|
|
450
455
|
let destroyed = false;
|
|
451
456
|
let selectedId = project.id;
|
|
452
457
|
let activeTab = 'overview';
|
|
@@ -576,7 +581,7 @@ export function createProjectKnowledgeWorkspace({
|
|
|
576
581
|
['目的', textOrNull(selectedEntity.goal)],
|
|
577
582
|
['有効期間', validityText(selectedEntity.validFrom, selectedEntity.validTo)],
|
|
578
583
|
]));
|
|
579
|
-
panel.append(makeElement(doc, 'h4', { text: 'このプロジェクトとの関係' }), relationEvidence(doc,
|
|
584
|
+
panel.append(makeElement(doc, 'h4', { text: 'このプロジェクトとの関係' }), relationEvidence(doc, overviewEdges, selectedId, overviewEntities, payload.asOf, selectAndExplore));
|
|
580
585
|
if (selectedId !== project.id && !fetched) {
|
|
581
586
|
const failed = read?.state === 'failed';
|
|
582
587
|
panel.append(workspaceNotice(doc, {
|
|
@@ -699,7 +704,7 @@ export function createProjectKnowledgeWorkspace({
|
|
|
699
704
|
teardownGraph();
|
|
700
705
|
tabs.replaceChildren(tabButton('概要', 'overview'), tabButton('情報を探す', 'graph'));
|
|
701
706
|
panels.replaceChildren();
|
|
702
|
-
const overview = renderOverview(doc, payload, context,
|
|
707
|
+
const overview = renderOverview(doc, payload, context, overviewEdges, overviewEntities, selectAndExplore);
|
|
703
708
|
overview.id = 'bb-pkw-panel-overview';
|
|
704
709
|
overview.hidden = activeTab !== 'overview';
|
|
705
710
|
panels.append(overview);
|