@unson/brainbase-mcp 0.10.2 → 0.10.4
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 +37 -10
- package/ui/project-workspace.css +38 -0
- package/ui/project-workspace.js +195 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
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": "84863f09b8274b738b16e15d2550ece19ac51124"
|
|
340
340
|
}
|
|
@@ -54,7 +54,12 @@ import {
|
|
|
54
54
|
graphHostEmptyNotice,
|
|
55
55
|
} from './graph-view-shared.js';
|
|
56
56
|
import { graphOwnShareSource, ownProjectDetail, ownShareSection, ownShareStateNotice } from './graph-own-share.js';
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
createProjectKnowledgeWorkspace,
|
|
59
|
+
normalizeProjectContext,
|
|
60
|
+
renderProjectContext,
|
|
61
|
+
renderProjectKnowledgeSkeleton,
|
|
62
|
+
} from './project-workspace.js';
|
|
58
63
|
import { mountProjectGraph as defaultMountProjectGraph } from './project-graph.js';
|
|
59
64
|
import {
|
|
60
65
|
workspaceActions,
|
|
@@ -211,9 +216,11 @@ function normalizeProjectSummary(value) {
|
|
|
211
216
|
* labelled 「引き継いだ自分の分(組織には未反映)」 unless `label` says otherwise. They are never
|
|
212
217
|
* added to the ledger, the metrics or the rail, and a failed read shows only in that section.
|
|
213
218
|
* Without it nothing of the kind is drawn or read.
|
|
214
|
-
* @param {(projectId: string) => Promise<object>} [options.loadProjectContext]
|
|
215
|
-
* Optional read-only supplementary sections (tasks/knowledge).
|
|
216
|
-
*
|
|
219
|
+
* @param {(projectId: string, options?: { onProgress?: (snapshot: object) => void }) => Promise<object>} [options.loadProjectContext]
|
|
220
|
+
* Optional read-only supplementary sections (tasks/knowledge). The host may
|
|
221
|
+
* call `onProgress` with a complete `{ sections: [...] }` snapshot as each
|
|
222
|
+
* section resolves; a failed context read is kept separate from the Graph
|
|
223
|
+
* detail read.
|
|
217
224
|
* @param {Array<{ id: string, name?: string }> | (() => Array<{ id: string, name?: string }>)} [options.projectSummaries]
|
|
218
225
|
* Optional host-known project identities. These are used only to draw the
|
|
219
226
|
* selected project's early loading shell before the Graph list is read.
|
|
@@ -330,7 +337,10 @@ export function createGraphProjectsView({
|
|
|
330
337
|
const project = projectSummaryFor(target);
|
|
331
338
|
const loading = makeElement(doc, 'section', {
|
|
332
339
|
className: 'bb-pkw bb-pkw-loading',
|
|
333
|
-
attrs: {
|
|
340
|
+
attrs: {
|
|
341
|
+
'aria-label': 'プロジェクトの知識',
|
|
342
|
+
'data-project-id': target,
|
|
343
|
+
},
|
|
334
344
|
});
|
|
335
345
|
loading.append(makeElement(doc, 'div', { className: 'bb-pkw-kicker', text: 'プロジェクトの記録' }));
|
|
336
346
|
const header = makeElement(doc, 'header', { className: 'bb-pkw-header' });
|
|
@@ -344,6 +354,12 @@ export function createGraphProjectsView({
|
|
|
344
354
|
loadingText: 'プロジェクトの記録を読み込んでいます。',
|
|
345
355
|
});
|
|
346
356
|
if (notice) loading.append(notice);
|
|
357
|
+
if (readState?.state === 'loading') {
|
|
358
|
+
loading.append(renderProjectKnowledgeSkeleton(doc, { context: state.context?.projectId === target ? state.context : null }));
|
|
359
|
+
} else if (state.context?.projectId === target) {
|
|
360
|
+
const contextBlock = renderProjectContext(doc, state.context);
|
|
361
|
+
if (contextBlock) loading.append(contextBlock);
|
|
362
|
+
}
|
|
347
363
|
return loading;
|
|
348
364
|
}
|
|
349
365
|
|
|
@@ -403,16 +419,27 @@ export function createGraphProjectsView({
|
|
|
403
419
|
const request = ++contextRequest;
|
|
404
420
|
state.context = normalizeProjectContext(id, { state: 'loading' });
|
|
405
421
|
controller.render();
|
|
422
|
+
const applyContext = (snapshot) => {
|
|
423
|
+
const normalized = normalizeProjectContext(id, snapshot);
|
|
424
|
+
if (viewDestroyed || request !== contextRequest || state.selectedId !== id) return false;
|
|
425
|
+
state.context = normalized;
|
|
426
|
+
// Context progress must not rebuild the workspace: rebuilding destroys
|
|
427
|
+
// Sigma's viewport and resets the user's active tab/selection. Detail
|
|
428
|
+
// loading still uses the shell render so the project name is immediate.
|
|
429
|
+
if (knowledgeWorkspace && state.detail?.id === id && state.detail.state === 'ok') {
|
|
430
|
+
knowledgeWorkspace.setContext?.(normalized);
|
|
431
|
+
} else {
|
|
432
|
+
controller.render();
|
|
433
|
+
}
|
|
434
|
+
return true;
|
|
435
|
+
};
|
|
406
436
|
let result;
|
|
407
437
|
try {
|
|
408
|
-
result = await loadProjectContext(id);
|
|
438
|
+
result = await loadProjectContext(id, { onProgress: (snapshot) => { applyContext(snapshot); } });
|
|
409
439
|
} catch (error) {
|
|
410
440
|
result = { state: 'failed', message: `補足情報を読み取れませんでした(${failureText(error)})。` };
|
|
411
441
|
}
|
|
412
|
-
|
|
413
|
-
if (viewDestroyed || request !== contextRequest || state.selectedId !== id) return state.context;
|
|
414
|
-
state.context = normalized;
|
|
415
|
-
controller.render();
|
|
442
|
+
applyContext(result);
|
|
416
443
|
return state.context;
|
|
417
444
|
}
|
|
418
445
|
|
package/ui/project-workspace.css
CHANGED
|
@@ -267,6 +267,44 @@
|
|
|
267
267
|
padding-top: var(--bb-space-3);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
.bb-pkw-skeleton-section {
|
|
271
|
+
min-height: 118px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.bb-pkw-skeleton-stack {
|
|
275
|
+
display: grid;
|
|
276
|
+
gap: var(--bb-space-2);
|
|
277
|
+
width: min(100%, 360px);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.bb-pkw-skeleton-bar {
|
|
281
|
+
display: block;
|
|
282
|
+
width: 100%;
|
|
283
|
+
height: 10px;
|
|
284
|
+
border-radius: 999px;
|
|
285
|
+
background: color-mix(in srgb, var(--bb-color-border-strong) 65%, var(--bb-color-panel));
|
|
286
|
+
animation: bb-pkw-skeleton-pulse 1.35s ease-in-out infinite;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.bb-pkw-skeleton-bar-1 { width: 92%; }
|
|
290
|
+
.bb-pkw-skeleton-bar-2 { width: 68%; }
|
|
291
|
+
.bb-pkw-skeleton-bar-3 { width: 82%; }
|
|
292
|
+
|
|
293
|
+
.bb-pkw-context-group-loading {
|
|
294
|
+
min-height: 72px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@keyframes bb-pkw-skeleton-pulse {
|
|
298
|
+
0%, 100% { opacity: .48; }
|
|
299
|
+
50% { opacity: 1; }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@media (prefers-reduced-motion: reduce) {
|
|
303
|
+
.bb-pkw-skeleton-bar {
|
|
304
|
+
animation: none;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
270
308
|
.bb-pkw-context-heading,
|
|
271
309
|
.bb-pkw-graph-intro,
|
|
272
310
|
.bb-pkw-selected-head,
|
package/ui/project-workspace.js
CHANGED
|
@@ -95,7 +95,18 @@ export function normalizeProjectContext(projectId, result) {
|
|
|
95
95
|
if (result?.state === 'failed' || result?.state === 'error') {
|
|
96
96
|
return { projectId: id, state: 'failed', message: firstText(result.message, result.reason) ?? '補足情報を読み取れませんでした。', sections: [] };
|
|
97
97
|
}
|
|
98
|
-
if (result?.state === 'loading')
|
|
98
|
+
if (result?.state === 'loading') {
|
|
99
|
+
const partial = result?.payload && isRecord(result.payload) ? result.payload : result;
|
|
100
|
+
const rawSections = Array.isArray(partial.sections) ? partial.sections : [];
|
|
101
|
+
const normalized = {
|
|
102
|
+
projectId: id,
|
|
103
|
+
state: 'loading',
|
|
104
|
+
sections: rawSections.map(normalizeContextSection).filter(Boolean),
|
|
105
|
+
};
|
|
106
|
+
const message = firstText(result.message, result.reason);
|
|
107
|
+
if (message) normalized.message = message;
|
|
108
|
+
return normalized;
|
|
109
|
+
}
|
|
99
110
|
if (result?.state === 'unavailable') {
|
|
100
111
|
return { projectId: id, state: 'unavailable', message: firstText(result.message, result.reason) ?? '補足情報は接続されていません。', sections: [] };
|
|
101
112
|
}
|
|
@@ -107,7 +118,7 @@ export function normalizeProjectContext(projectId, result) {
|
|
|
107
118
|
if (sections.length === 0 && payload.sections.length > 0) {
|
|
108
119
|
return { projectId: id, state: 'unavailable', message: '補足情報の形式を確認できません。', sections: [] };
|
|
109
120
|
}
|
|
110
|
-
return { projectId: id, state: 'ok', sections };
|
|
121
|
+
return { projectId: id, state: sections.some((section) => section.state === 'loading') ? 'loading' : 'ok', sections };
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
function addEntity(map, candidate) {
|
|
@@ -292,49 +303,131 @@ function renderEntityRelations(doc, edges, direction, entity, asOf, onSelect) {
|
|
|
292
303
|
return list.children.length > 0 ? list : null;
|
|
293
304
|
}
|
|
294
305
|
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
function skeletonBars(doc, count = 3) {
|
|
307
|
+
const stack = makeElement(doc, 'div', { className: 'bb-pkw-skeleton-stack', attrs: { 'aria-hidden': 'true' } });
|
|
308
|
+
for (let index = 0; index < count; index += 1) {
|
|
309
|
+
stack.append(makeElement(doc, 'span', {
|
|
310
|
+
className: `bb-pkw-skeleton-bar bb-pkw-skeleton-bar-${(index % 3) + 1}`,
|
|
311
|
+
attrs: { 'aria-hidden': 'true' },
|
|
312
|
+
}));
|
|
302
313
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
314
|
+
return stack;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function contextSectionSkeleton(doc, { title = null, message = null } = {}) {
|
|
318
|
+
const group = makeElement(doc, 'section', {
|
|
319
|
+
className: 'bb-pkw-context-group bb-pkw-context-group-loading',
|
|
320
|
+
attrs: { 'aria-label': title ?? '補足情報', 'aria-busy': 'true' },
|
|
321
|
+
});
|
|
322
|
+
if (title) group.append(makeElement(doc, 'h4', { text: title }));
|
|
323
|
+
if (message) group.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: message }));
|
|
324
|
+
group.append(skeletonBars(doc));
|
|
325
|
+
return group;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function renderProjectContextGroup(doc, item) {
|
|
329
|
+
const group = makeElement(doc, 'section', {
|
|
330
|
+
className: `bb-pkw-context-group${item.state === 'loading' ? ' bb-pkw-context-group-loading' : ''}`,
|
|
331
|
+
attrs: { 'aria-label': item.title, ...(item.state === 'loading' ? { 'aria-busy': 'true' } : {}) },
|
|
332
|
+
});
|
|
333
|
+
const heading = makeElement(doc, 'div', { className: 'bb-pkw-context-heading' });
|
|
334
|
+
heading.append(makeElement(doc, 'h4', { text: item.title }), badge(doc, item.state === 'ok' ? '利用可能' : item.state === 'loading' ? '読み込み中' : item.state === 'failed' ? '読み取り失敗' : '未接続', item.state === 'ok' ? 'success' : item.state === 'failed' ? 'danger' : 'muted'));
|
|
335
|
+
group.append(heading);
|
|
336
|
+
if (item.message && !['failed', 'unavailable'].includes(item.state)) group.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: item.message }));
|
|
337
|
+
if (item.asOf) group.append(makeElement(doc, 'small', { className: 'bb-pkw-muted', text: `読み取った時点: ${formatDateTime(item.asOf)}` }));
|
|
338
|
+
if (item.state === 'loading') {
|
|
339
|
+
group.append(skeletonBars(doc));
|
|
340
|
+
} else if (item.state === 'failed') {
|
|
341
|
+
group.append(workspaceNotice(doc, { label: '読み取り失敗', text: item.message ?? 'このセクションを読み取れませんでした。', tone: 'danger', role: 'alert' }));
|
|
342
|
+
} else if (item.state === 'unavailable') {
|
|
343
|
+
group.append(workspaceNotice(doc, { label: '未接続', text: item.message ?? 'このセクションは接続されていません。', tone: 'warning' }));
|
|
344
|
+
} else if (item.items.length > 0) {
|
|
345
|
+
const list = makeElement(doc, 'ul', { className: 'bb-pkw-context-list' });
|
|
346
|
+
for (const entry of item.items) {
|
|
347
|
+
const row = makeElement(doc, 'li');
|
|
348
|
+
row.append(makeElement(doc, 'strong', { text: entry.title }));
|
|
349
|
+
if (entry.status) row.append(badge(doc, entry.status, 'muted'));
|
|
350
|
+
const facts = [entry.summary, entry.owner ? `担当: ${entry.owner}` : null, entry.dueAt ? `期限: ${entry.dueAt}` : null, entry.updatedAt ? `更新: ${entry.updatedAt}` : null, entry.source ? `出典: ${entry.source}` : null].filter(Boolean);
|
|
351
|
+
if (facts.length > 0) row.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: facts.join(' · ') }));
|
|
352
|
+
list.append(row);
|
|
353
|
+
}
|
|
354
|
+
group.append(list);
|
|
355
|
+
} else if (item.state === 'ok') {
|
|
356
|
+
group.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: 'このセクションに表示できる記録はありません。' }));
|
|
357
|
+
}
|
|
358
|
+
return group;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function projectContextInsertionAnchor(doc) {
|
|
362
|
+
return makeElement(doc, 'span', {
|
|
363
|
+
className: 'bb-pkw-context-anchor',
|
|
364
|
+
attrs: { 'aria-hidden': 'true', hidden: 'true' },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Renders supplementary context while preserving each section's own state. */
|
|
369
|
+
export function renderProjectContext(doc, context, {
|
|
370
|
+
sections: selectedSections = null,
|
|
371
|
+
includeStatus = true,
|
|
372
|
+
includeHeading = true,
|
|
373
|
+
} = {}) {
|
|
374
|
+
if (!context) return null;
|
|
375
|
+
const sections = Array.isArray(selectedSections)
|
|
376
|
+
? selectedSections
|
|
377
|
+
: Array.isArray(context.sections) ? context.sections : [];
|
|
378
|
+
const section = makeElement(doc, 'section', {
|
|
379
|
+
className: 'bb-pkw-section bb-pkw-context',
|
|
380
|
+
attrs: { 'aria-label': '補足情報' },
|
|
381
|
+
});
|
|
382
|
+
if (includeHeading) section.append(makeElement(doc, 'h3', { text: '補足情報' }));
|
|
383
|
+
if (includeStatus && context.state !== 'ok') {
|
|
384
|
+
if (context.state === 'loading') {
|
|
385
|
+
section.append(workspaceNotice(doc, { label: '読み込み中', text: context.message ?? '補足情報を読み込んでいます。', tone: 'info', role: 'status' }));
|
|
386
|
+
if (sections.length === 0) section.append(contextSectionSkeleton(doc));
|
|
387
|
+
} else {
|
|
388
|
+
section.append(workspaceNotice(doc, { label: context.state === 'failed' ? '読み取り失敗' : '未接続', text: context.message ?? '補足情報を確認できません。', tone: context.state === 'failed' ? 'danger' : 'warning' }));
|
|
389
|
+
}
|
|
306
390
|
}
|
|
307
|
-
if (context.sections.length === 0) {
|
|
391
|
+
if (includeStatus && context.state === 'ok' && sections.length === 0) {
|
|
308
392
|
section.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: '補足情報のセクションはありません。' }));
|
|
309
393
|
return section;
|
|
310
394
|
}
|
|
311
|
-
for (const item of
|
|
312
|
-
const group = makeElement(doc, 'section', { className: 'bb-pkw-context-group', attrs: { 'aria-label': item.title } });
|
|
313
|
-
const heading = makeElement(doc, 'div', { className: 'bb-pkw-context-heading' });
|
|
314
|
-
heading.append(makeElement(doc, 'h4', { text: item.title }), badge(doc, item.state === 'ok' ? '利用可能' : item.state === 'loading' ? '読み込み中' : item.state === 'failed' ? '読み取り失敗' : '未接続', item.state === 'ok' ? 'success' : item.state === 'failed' ? 'danger' : 'muted'));
|
|
315
|
-
group.append(heading);
|
|
316
|
-
if (item.message) group.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: item.message }));
|
|
317
|
-
if (item.asOf) group.append(makeElement(doc, 'small', { className: 'bb-pkw-muted', text: `読み取った時点: ${formatDateTime(item.asOf)}` }));
|
|
318
|
-
if (item.items.length > 0) {
|
|
319
|
-
const list = makeElement(doc, 'ul', { className: 'bb-pkw-context-list' });
|
|
320
|
-
for (const entry of item.items) {
|
|
321
|
-
const row = makeElement(doc, 'li');
|
|
322
|
-
row.append(makeElement(doc, 'strong', { text: entry.title }));
|
|
323
|
-
if (entry.status) row.append(badge(doc, entry.status, 'muted'));
|
|
324
|
-
const facts = [entry.summary, entry.owner ? `担当: ${entry.owner}` : null, entry.dueAt ? `期限: ${entry.dueAt}` : null, entry.updatedAt ? `更新: ${entry.updatedAt}` : null, entry.source ? `出典: ${entry.source}` : null].filter(Boolean);
|
|
325
|
-
if (facts.length > 0) row.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: facts.join(' · ') }));
|
|
326
|
-
list.append(row);
|
|
327
|
-
}
|
|
328
|
-
group.append(list);
|
|
329
|
-
} else if (item.state === 'ok') {
|
|
330
|
-
group.append(makeElement(doc, 'p', { className: 'bb-pkw-muted', text: 'このセクションに表示できる記録はありません。' }));
|
|
331
|
-
}
|
|
332
|
-
section.append(group);
|
|
333
|
-
}
|
|
395
|
+
for (const item of sections) section.append(renderProjectContextGroup(doc, item));
|
|
334
396
|
return section;
|
|
335
397
|
}
|
|
336
398
|
|
|
337
|
-
|
|
399
|
+
/** Structural shell used while the Graph detail is loading; it contains no inferred values. */
|
|
400
|
+
export function renderProjectKnowledgeSkeleton(doc, { context = null } = {}) {
|
|
401
|
+
const panel = makeElement(doc, 'div', {
|
|
402
|
+
className: 'bb-pkw-panel bb-pkw-overview bb-pkw-overview-loading',
|
|
403
|
+
attrs: { 'aria-label': '概要' },
|
|
404
|
+
});
|
|
405
|
+
const contextSections = Array.isArray(context?.sections) ? context.sections : [];
|
|
406
|
+
const readySections = contextSections.filter((item) => item?.state === 'ok' && Array.isArray(item.items) && item.items.length > 0);
|
|
407
|
+
const deferredSections = contextSections.filter((item) => !readySections.includes(item));
|
|
408
|
+
if (readySections.length > 0) {
|
|
409
|
+
panel.append(renderProjectContext(doc, context, { sections: readySections, includeStatus: false }));
|
|
410
|
+
}
|
|
411
|
+
const columns = makeElement(doc, 'div', { className: 'bb-pkw-columns' });
|
|
412
|
+
for (const title of ['目的と現在地', '関係者と役割']) {
|
|
413
|
+
const item = makeElement(doc, 'section', { className: 'bb-pkw-section bb-pkw-skeleton-section', attrs: { 'aria-label': title, 'aria-busy': 'true' } });
|
|
414
|
+
item.append(makeElement(doc, 'h3', { text: title }), skeletonBars(doc));
|
|
415
|
+
columns.append(item);
|
|
416
|
+
}
|
|
417
|
+
panel.append(columns);
|
|
418
|
+
for (const title of ['記録された判断', '関連する情報']) {
|
|
419
|
+
const item = makeElement(doc, 'section', { className: 'bb-pkw-section bb-pkw-skeleton-section', attrs: { 'aria-label': title, 'aria-busy': 'true' } });
|
|
420
|
+
item.append(makeElement(doc, 'h3', { text: title }), skeletonBars(doc, 2));
|
|
421
|
+
panel.append(item);
|
|
422
|
+
}
|
|
423
|
+
const contextBlock = readySections.length > 0
|
|
424
|
+
? (deferredSections.length > 0 ? renderProjectContext(doc, context, { sections: deferredSections, includeHeading: false }) : null)
|
|
425
|
+
: renderProjectContext(doc, context ?? { state: 'loading', sections: [] });
|
|
426
|
+
if (contextBlock) panel.append(contextBlock);
|
|
427
|
+
return panel;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function renderOverview(doc, detail, context, edges, entities, onSelect, onContext) {
|
|
338
431
|
const payload = projectDetailPayload(detail);
|
|
339
432
|
const project = payload?.project;
|
|
340
433
|
const panel = makeElement(doc, 'div', { className: 'bb-pkw-panel bb-pkw-overview', attrs: { role: 'tabpanel', 'aria-label': '概要' } });
|
|
@@ -369,6 +462,16 @@ function renderOverview(doc, detail, context, edges, entities, onSelect) {
|
|
|
369
462
|
columns.append(people);
|
|
370
463
|
panel.append(columns);
|
|
371
464
|
|
|
465
|
+
const contextBlock = renderProjectContext(doc, context);
|
|
466
|
+
if (contextBlock) {
|
|
467
|
+
panel.append(contextBlock);
|
|
468
|
+
if (typeof onContext === 'function') onContext(contextBlock, null);
|
|
469
|
+
} else {
|
|
470
|
+
const contextAnchor = projectContextInsertionAnchor(doc);
|
|
471
|
+
panel.append(contextAnchor);
|
|
472
|
+
if (typeof onContext === 'function') onContext(null, contextAnchor);
|
|
473
|
+
}
|
|
474
|
+
|
|
372
475
|
const decisions = makeElement(doc, 'section', { className: 'bb-pkw-section', attrs: { 'aria-label': '記録された判断' } });
|
|
373
476
|
decisions.append(makeElement(doc, 'h3', { text: '記録された判断' }));
|
|
374
477
|
const decisionEdges = edges.filter((edge) => entities.get(edge.source)?.type === 'decision' || entities.get(edge.target)?.type === 'decision');
|
|
@@ -423,8 +526,6 @@ function renderOverview(doc, detail, context, edges, entities, onSelect) {
|
|
|
423
526
|
missing.append(list);
|
|
424
527
|
panel.append(missing);
|
|
425
528
|
}
|
|
426
|
-
const contextBlock = renderContext(doc, context);
|
|
427
|
-
if (contextBlock) panel.append(contextBlock);
|
|
428
529
|
return panel;
|
|
429
530
|
}
|
|
430
531
|
|
|
@@ -463,12 +564,23 @@ export function createProjectKnowledgeWorkspace({
|
|
|
463
564
|
let entityReadRequest = 0;
|
|
464
565
|
let graphSearch = '';
|
|
465
566
|
let graphType = '';
|
|
567
|
+
let currentContext = context;
|
|
466
568
|
// Keep direct references as well as the DOM lookup. The local test harness
|
|
467
569
|
// intentionally has no querySelector implementation, and a host can replace
|
|
468
570
|
// the panel while a detail read is still in flight.
|
|
469
571
|
let selectedPanel = null;
|
|
470
|
-
|
|
471
|
-
|
|
572
|
+
let overviewPanel = null;
|
|
573
|
+
let contextBlock = null;
|
|
574
|
+
let contextAnchor = null;
|
|
575
|
+
|
|
576
|
+
const root = makeElement(doc, 'section', {
|
|
577
|
+
className: 'bb-pkw',
|
|
578
|
+
attrs: {
|
|
579
|
+
'data-contract-version': PROJECT_WORKSPACE_CONTRACT_VERSION,
|
|
580
|
+
'data-project-id': cleanId(project.id),
|
|
581
|
+
'aria-label': 'プロジェクトの知識',
|
|
582
|
+
},
|
|
583
|
+
});
|
|
472
584
|
const body = makeElement(doc, 'div', { className: 'bb-pkw-body' });
|
|
473
585
|
const tabs = makeElement(doc, 'div', { className: 'bb-pkw-tabs', attrs: { role: 'tablist', 'aria-label': 'プロジェクト情報の表示' } });
|
|
474
586
|
const panels = makeElement(doc, 'div', { className: 'bb-pkw-panels' });
|
|
@@ -698,15 +810,55 @@ export function createProjectKnowledgeWorkspace({
|
|
|
698
810
|
graphHandle = null;
|
|
699
811
|
}
|
|
700
812
|
|
|
813
|
+
function replaceContextBlock(nextContext) {
|
|
814
|
+
currentContext = nextContext;
|
|
815
|
+
if (destroyed) return;
|
|
816
|
+
const nextBlock = renderProjectContext(doc, currentContext);
|
|
817
|
+
if (!nextBlock) {
|
|
818
|
+
if (contextBlock?.parentNode && typeof contextBlock.parentNode.replaceChild === 'function') {
|
|
819
|
+
const nextAnchor = projectContextInsertionAnchor(doc);
|
|
820
|
+
contextBlock.parentNode.replaceChild(nextAnchor, contextBlock);
|
|
821
|
+
contextBlock = null;
|
|
822
|
+
contextAnchor = nextAnchor;
|
|
823
|
+
} else if (contextBlock?.parentNode?.removeChild) {
|
|
824
|
+
contextBlock.parentNode.removeChild(contextBlock);
|
|
825
|
+
contextBlock = null;
|
|
826
|
+
}
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (contextBlock?.parentNode && typeof contextBlock.parentNode.replaceChild === 'function') {
|
|
830
|
+
contextBlock.parentNode.replaceChild(nextBlock, contextBlock);
|
|
831
|
+
contextBlock = nextBlock;
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (contextAnchor?.parentNode && typeof contextAnchor.parentNode.replaceChild === 'function') {
|
|
835
|
+
contextAnchor.parentNode.replaceChild(nextBlock, contextAnchor);
|
|
836
|
+
contextBlock = nextBlock;
|
|
837
|
+
contextAnchor = null;
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (contextBlock?.replaceWith) {
|
|
841
|
+
contextBlock.replaceWith(nextBlock);
|
|
842
|
+
contextBlock = nextBlock;
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
701
847
|
function render() {
|
|
702
848
|
if (destroyed) return;
|
|
703
849
|
if (typeof onTabChange === 'function') onTabChange(activeTab);
|
|
704
850
|
teardownGraph();
|
|
705
851
|
tabs.replaceChildren(tabButton('概要', 'overview'), tabButton('情報を探す', 'graph'));
|
|
706
852
|
panels.replaceChildren();
|
|
707
|
-
|
|
853
|
+
contextBlock = null;
|
|
854
|
+
contextAnchor = null;
|
|
855
|
+
const overview = renderOverview(doc, payload, currentContext, overviewEdges, overviewEntities, selectAndExplore, (node, anchor) => {
|
|
856
|
+
contextBlock = node;
|
|
857
|
+
contextAnchor = anchor;
|
|
858
|
+
});
|
|
708
859
|
overview.id = 'bb-pkw-panel-overview';
|
|
709
860
|
overview.hidden = activeTab !== 'overview';
|
|
861
|
+
overviewPanel = overview;
|
|
710
862
|
panels.append(overview);
|
|
711
863
|
const graph = renderGraphPanel();
|
|
712
864
|
graph.hidden = activeTab !== 'graph';
|
|
@@ -721,7 +873,7 @@ export function createProjectKnowledgeWorkspace({
|
|
|
721
873
|
}
|
|
722
874
|
|
|
723
875
|
render();
|
|
724
|
-
return Object.freeze({ element: root, destroy, select, setTab });
|
|
876
|
+
return Object.freeze({ element: root, destroy, select, setTab, setContext: replaceContextBlock });
|
|
725
877
|
}
|
|
726
878
|
|
|
727
879
|
export default createProjectKnowledgeWorkspace;
|