@unson/brainbase-mcp 0.41.11 → 0.41.13
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/README.md +1 -1
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/local-web-host.js +19 -2
- package/package.json +6 -2
- package/ui/area-nav.css +79 -0
- package/ui/area-nav.js +249 -0
- package/ui/foundation-overview.css +9 -0
- package/ui/foundation-overview.js +75 -5
- package/ui/graph-ontology-view.css +88 -0
- package/ui/graph-ontology-view.js +248 -0
- package/ui/graph-own-share.js +167 -0
- package/ui/graph-registry-view.css +272 -50
- package/ui/graph-registry-view.js +539 -456
- package/ui/graph-view-shared.css +32 -0
- package/ui/graph-view-shared.js +191 -12
- package/ui/local-web-shell.css +1 -13
- package/ui/local-web-shell.js +64 -40
- package/ui/outcome-knowledge.js +6 -2
- package/ui/people-and-companies-view.css +166 -15
- package/ui/people-and-companies-view.js +307 -93
- package/ui/world/world-canvas-notes.js +3 -2
- package/ui/world/world-view.css +19 -0
- package/ui/world/world-view.js +52 -10
- package/ui/world/world-work-rail.js +114 -8
package/ui/graph-view-shared.css
CHANGED
|
@@ -364,6 +364,38 @@
|
|
|
364
364
|
margin-top: 14px;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/* 根拠と履歴: one fold per record, after its relations (ledger D14). */
|
|
368
|
+
.bb-graph-rail > .bb-graph-evidence {
|
|
369
|
+
border-bottom: 1px solid var(--bb-color-border);
|
|
370
|
+
padding-block: 18px;
|
|
371
|
+
font-size: 13px;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.bb-graph-evidence > summary {
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: center;
|
|
377
|
+
gap: var(--bb-space-2);
|
|
378
|
+
color: var(--bb-color-ink);
|
|
379
|
+
font-weight: 690;
|
|
380
|
+
cursor: pointer;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.bb-graph-evidence-part {
|
|
384
|
+
margin-top: var(--bb-space-3);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.bb-graph-evidence-part > h4 {
|
|
388
|
+
margin: 0 0 var(--bb-space-1);
|
|
389
|
+
color: var(--bb-color-muted);
|
|
390
|
+
font-size: 12px;
|
|
391
|
+
font-weight: 690;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.bb-graph-evidence-part > p {
|
|
395
|
+
margin: 0 0 var(--bb-space-1);
|
|
396
|
+
overflow-wrap: anywhere;
|
|
397
|
+
}
|
|
398
|
+
|
|
367
399
|
.bb-graph-rail > .bb-graph-history {
|
|
368
400
|
border-bottom: 1px solid var(--bb-color-border);
|
|
369
401
|
padding-block: 18px;
|
package/ui/graph-view-shared.js
CHANGED
|
@@ -38,6 +38,45 @@ export const GRAPH_ENTITY_TYPE_LABELS = Object.freeze({
|
|
|
38
38
|
org: '組織',
|
|
39
39
|
project: 'プロジェクト',
|
|
40
40
|
decision: '判断',
|
|
41
|
+
// The kinds an organization's Graph defines on top of the local four, so the
|
|
42
|
+
// screens never show an internal type id (ledger D14).
|
|
43
|
+
app: 'アプリ',
|
|
44
|
+
product: 'プロダクト',
|
|
45
|
+
brand: 'ブランド',
|
|
46
|
+
customer: '顧客',
|
|
47
|
+
partner: '協業先',
|
|
48
|
+
contact: '名刺の連絡先',
|
|
49
|
+
raci: '責任分担表',
|
|
50
|
+
raci_assignment: '責任の割り当て',
|
|
51
|
+
role_assignment: '役割',
|
|
52
|
+
glossary_term: '用語',
|
|
53
|
+
document: '文書',
|
|
54
|
+
frame: '考え方の枠組み',
|
|
55
|
+
story: 'ストーリー',
|
|
56
|
+
infrastructure_environment: '稼働環境',
|
|
57
|
+
speaking: '登壇',
|
|
58
|
+
media_appearance: 'メディア出演',
|
|
59
|
+
publication: '出版物',
|
|
60
|
+
press_mention: '報道',
|
|
61
|
+
initiative: '施策',
|
|
62
|
+
ai_query: 'AIへの問い合わせ',
|
|
63
|
+
ai_decision: 'AIを使った判断',
|
|
64
|
+
philosophy: '哲学',
|
|
65
|
+
capital: '資本',
|
|
66
|
+
contract: '契約',
|
|
67
|
+
database: 'データベース',
|
|
68
|
+
database_cluster: 'データベースのクラスタ',
|
|
69
|
+
diagnosis: '診断',
|
|
70
|
+
finance: '財務',
|
|
71
|
+
finance_account: '財務の口座',
|
|
72
|
+
org_alias: '組織の別名',
|
|
73
|
+
organization: '組織(旧い形式)',
|
|
74
|
+
person_alias: '人物の別名',
|
|
75
|
+
push_case: '推進案件',
|
|
76
|
+
objective: '目的',
|
|
77
|
+
variable: '変数',
|
|
78
|
+
model: '世界モデル',
|
|
79
|
+
constraint: '制約',
|
|
41
80
|
});
|
|
42
81
|
|
|
43
82
|
export const GRAPH_ENTITY_TYPE_MEANINGS = Object.freeze({
|
|
@@ -51,14 +90,72 @@ export const GRAPH_ENTITY_TYPE_MEANINGS = Object.freeze({
|
|
|
51
90
|
* `outgoing` follows the counterpart name when this record is the start of the
|
|
52
91
|
* relation; `incoming` follows it when this record is the end.
|
|
53
92
|
*/
|
|
93
|
+
const relation = (label, meaning, outgoing, incoming) => Object.freeze({ label, meaning, outgoing, incoming });
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Every relation of the local Graph and of an organization's Graph has a plain
|
|
97
|
+
* Japanese name (ledger D14); a relation id is shown only in the details.
|
|
98
|
+
* Relations that mean the same to a reader share one name (所有 for owns and
|
|
99
|
+
* owned_by, 所属 for member_of and belongs_to), and a list of relation kinds
|
|
100
|
+
* shows them as one row (`groupRelationKinds`).
|
|
101
|
+
*/
|
|
54
102
|
export const GRAPH_RELATION_LABELS = Object.freeze({
|
|
55
|
-
member_of:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
103
|
+
member_of: relation('所属', '人物が組織やプロジェクトに所属している', 'に所属', 'が所属'),
|
|
104
|
+
belongs_to: relation('所属', '記録が組織・プロダクト・ブランド・プロジェクトに含まれる', 'に所属', 'が所属'),
|
|
105
|
+
belongs_to_project: relation('所属', '記録がプロジェクトに含まれる', 'に所属', 'が所属'),
|
|
106
|
+
participates_in: relation('参加', '人物がプロジェクトに参加している', 'に参加', 'が参加'),
|
|
107
|
+
accountable_for: relation('責任', '人物や組織が成果や判断に最終的な責任を持つ', 'に責任を持つ', 'が責任を持つ'),
|
|
108
|
+
owned_by: relation('所有', 'プロジェクトなどを組織や人が所有している', 'が所有', 'を所有'),
|
|
109
|
+
owns: relation('所有', '組織がアプリ・プロダクト・ブランド・プロジェクトを所有している', 'を所有', 'が所有'),
|
|
110
|
+
owns_app: relation('所有', 'プロジェクトがアプリを持っている(旧い形式)', 'を所有', 'が所有'),
|
|
111
|
+
related_to: relation('関わる', '会社が顧客・協業先などとしてプロジェクトに関わっている', 'に関わる', 'が関わる'),
|
|
112
|
+
governs: relation('進め方を決める', '判断や原則が、対象の進め方を決める', 'の進め方を決める', 'が進め方を決める'),
|
|
113
|
+
governed_by: relation('従う原則', '推進案件が従う原則', 'に従う', 'が従う'),
|
|
114
|
+
supersedes: relation('置き換え', '判断が以前の判断を置き換える', 'を置き換える', 'に置き換えられた'),
|
|
115
|
+
superseded_by: relation('同一人物に統合', '古い人物の記録を、正しい人物の記録へまとめた', 'へ統合', 'を統合'),
|
|
116
|
+
derived_from: relation('由来', '記録が別の記録をもとにしている', 'に由来', 'が由来とする'),
|
|
117
|
+
depends_on: relation('依存', '稼働や成果が別の記録に依存している', 'に依存', 'が依存'),
|
|
118
|
+
assigned_to: relation('責任の割り当て', '責任分担で、人や組織に責任を割り当てている', 'に割り当て', 'で割り当て'),
|
|
119
|
+
requested_by: relation('問い合わせた人', 'AIへの問い合わせをした人', 'が問い合わせた', 'を問い合わせた'),
|
|
120
|
+
made_by: relation('判断した人', '判断をした人', 'が判断した', 'を判断した'),
|
|
121
|
+
references: relation('根拠として参照', '判断や問い合わせが、根拠として別の記録を参照している', 'を参照', 'が参照'),
|
|
122
|
+
evidenced_by: relation('根拠の文書', '判断の根拠になった文書', 'を根拠にする', 'が根拠にする'),
|
|
123
|
+
informs: relation('判断材料', '文書がプロジェクトの判断材料になる', 'の判断材料', 'が判断材料'),
|
|
124
|
+
mentions: relation('言及', '判断が協業先やプロダクトに言及している', 'に言及', 'が言及'),
|
|
125
|
+
alias_of: relation('別名', '人や組織の別の呼び名', 'の別名', 'が別名'),
|
|
126
|
+
applies_to: relation('適用先', '文書が適用される組織', 'に適用', 'が適用'),
|
|
127
|
+
describes: relation('説明', '文書が組織を説明している', 'を説明', 'が説明'),
|
|
128
|
+
brand_of: relation('ブランドの対象', 'ブランドが表す組織やプロジェクト', 'のブランド', 'がブランド'),
|
|
129
|
+
sub_brand_of: relation('上位ブランド', 'ブランドの上位にあるブランド', 'の下位ブランド', 'が下位ブランド'),
|
|
130
|
+
operates: relation('運営', '組織が運営している', 'を運営', 'が運営'),
|
|
131
|
+
operated_by: relation('運営', '組織が運営している', 'が運営', 'を運営'),
|
|
132
|
+
covers_engagement: relation('責任分担の対象', '責任分担表が対象とする組織や案件', 'の責任分担', 'が責任分担を示す'),
|
|
133
|
+
describes_org: relation('責任分担の対象', '責任分担表が対象とする組織や案件', 'の責任分担', 'が責任分担を示す'),
|
|
134
|
+
has_raci: relation('責任分担表', '組織やプロジェクトが持つ責任分担表', 'を持つ', 'が持つ'),
|
|
135
|
+
has_project: relation('含む', 'プロジェクトが子プロジェクト・案件・施策を含む', 'を含む', 'に含まれる'),
|
|
136
|
+
has_engagement: relation('含む', 'プロジェクトが子プロジェクト・案件・施策を含む', 'を含む', 'に含まれる'),
|
|
137
|
+
has_initiative: relation('含む', 'プロジェクトが子プロジェクト・案件・施策を含む', 'を含む', 'に含まれる'),
|
|
138
|
+
relates_to_project: relation('関連', 'プロジェクトどうしが関連している', 'と関連', 'と関連'),
|
|
139
|
+
serves_customer: relation('価値を届ける顧客', 'プロジェクトが価値を届ける顧客', 'に価値を届ける', 'が価値を届ける'),
|
|
140
|
+
org_unit_of: relation('上位の顧客', '部署が属する顧客の会社', 'の部署', 'が部署'),
|
|
141
|
+
supports_project: relation('支援', '協業先がプロジェクトを運営する組織を支援している', 'を支援', 'が支援'),
|
|
142
|
+
key_contact_for: relation('主な窓口', 'ブランドや推進案件の主な窓口', 'の主な窓口', 'が主な窓口'),
|
|
143
|
+
primary_contact_for: relation('主な窓口', 'ブランドや推進案件の主な窓口', 'の主な窓口', 'が主な窓口'),
|
|
144
|
+
has_push_case: relation('推進案件', '顧客について進めている推進案件', 'を推進案件に持つ', 'の推進案件'),
|
|
145
|
+
has_diagnosis: relation('診断', '推進案件についての診断', 'を持つ', 'が持つ'),
|
|
146
|
+
role_at: relation('役割', '人物の役割と、その役割の所属先', 'での役割', 'がある'),
|
|
147
|
+
holds_role: relation('役割', '人物の役割と、その役割の所属先', 'を持つ', 'が持つ'),
|
|
148
|
+
uses_app: relation('利用', 'プロジェクトやアプリが、アプリやデータベースを使う', 'を使う', 'が使う'),
|
|
149
|
+
uses_database: relation('利用', 'プロジェクトやアプリが、アプリやデータベースを使う', 'を使う', 'が使う'),
|
|
150
|
+
has_environment: relation('稼働環境', 'プロジェクトが稼働環境として持つアプリ', 'を稼働環境に持つ', 'の稼働環境'),
|
|
151
|
+
hosted_on: relation('置き場所', 'データベースが置かれているクラスタ', 'に置かれている', 'が置かれている'),
|
|
152
|
+
uses_secret_store: relation('秘密情報の保管先', 'アプリが使う(または使うべき)秘密情報の保管先', 'を保管先に使う', 'が保管先に使う'),
|
|
153
|
+
should_use_secret_store: relation('秘密情報の保管先', 'アプリが使う(または使うべき)秘密情報の保管先', 'を保管先に使うべき', 'が保管先に使うべき'),
|
|
154
|
+
created: relation('生み出した', '人物がプロダクトを生み出した', 'を生み出した', 'が生み出した'),
|
|
155
|
+
authored: relation('著作', '人物が出版物を著した', 'を著した', 'が著した'),
|
|
156
|
+
spoke_at: relation('登壇', '人物が登壇した', 'で登壇', 'が登壇'),
|
|
157
|
+
appeared_in: relation('出演', '人物がメディアに出演した', 'に出演', 'が出演'),
|
|
158
|
+
mentioned_in: relation('掲載', '人物が報道に掲載された', 'に掲載', 'が掲載'),
|
|
62
159
|
});
|
|
63
160
|
|
|
64
161
|
/** Relations a correction may add (the host refuses any other). */
|
|
@@ -213,9 +310,9 @@ export function facts(doc, rows, className = 'bb-graph-facts') {
|
|
|
213
310
|
}
|
|
214
311
|
|
|
215
312
|
/** Ids and digests stay in this disclosure, never in the visible copy. */
|
|
216
|
-
export function recordDetails(doc, rows) {
|
|
313
|
+
export function recordDetails(doc, rows, { title = '記録の詳細' } = {}) {
|
|
217
314
|
const details = makeElement(doc, 'details', { className: 'bb-graph-audit' });
|
|
218
|
-
details.append(makeElement(doc, 'summary', { text:
|
|
315
|
+
details.append(makeElement(doc, 'summary', { text: title }), facts(doc, rows, 'bb-graph-facts is-mono'));
|
|
219
316
|
return details;
|
|
220
317
|
}
|
|
221
318
|
|
|
@@ -307,9 +404,33 @@ export function ownLabel(table, key) {
|
|
|
307
404
|
return typeof key === 'string' && Object.hasOwn(table, key) ? table[key] : undefined;
|
|
308
405
|
}
|
|
309
406
|
|
|
310
|
-
/**
|
|
311
|
-
|
|
312
|
-
|
|
407
|
+
/**
|
|
408
|
+
* A relation's plain label. An unknown relation shows the meaning its host
|
|
409
|
+
* gave (when there is one) rather than its raw id.
|
|
410
|
+
*/
|
|
411
|
+
export function relationLabel(relation, meaning) {
|
|
412
|
+
return ownLabel(GRAPH_RELATION_LABELS, relation)?.label ?? textOrNull(meaning) ?? relation;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* The relation kinds of an ontology answer as rows a reader can tell apart:
|
|
417
|
+
* kinds with the same plain name are one row (所有 for owns, owned_by and
|
|
418
|
+
* owns_app), and each row keeps every kind with its start and end kinds. Rows
|
|
419
|
+
* keep the order in which their first kind appears.
|
|
420
|
+
*/
|
|
421
|
+
export function groupRelationKinds(relations) {
|
|
422
|
+
const rows = new Map();
|
|
423
|
+
for (const item of Array.isArray(relations) ? relations : []) {
|
|
424
|
+
if (!isRecord(item) || typeof item.id !== 'string') continue;
|
|
425
|
+
const known = ownLabel(GRAPH_RELATION_LABELS, item.id);
|
|
426
|
+
const label = relationLabel(item.id, item.meaning);
|
|
427
|
+
const row = rows.get(label) ?? { label, meanings: [], kinds: [] };
|
|
428
|
+
const meaning = known?.meaning ?? textOrNull(item.meaning);
|
|
429
|
+
if (meaning && !row.meanings.includes(meaning)) row.meanings.push(meaning);
|
|
430
|
+
row.kinds.push(item);
|
|
431
|
+
rows.set(label, row);
|
|
432
|
+
}
|
|
433
|
+
return [...rows.values()];
|
|
313
434
|
}
|
|
314
435
|
|
|
315
436
|
/** A type's plain label; an unknown type (from another host's Graph) shows its raw id. */
|
|
@@ -620,6 +741,59 @@ export function renderGraphIssues(doc, issues) {
|
|
|
620
741
|
});
|
|
621
742
|
}
|
|
622
743
|
|
|
744
|
+
/**
|
|
745
|
+
* 根拠と履歴 of one record, folded: where the record came from (the host's 出典
|
|
746
|
+
* notice, which may carry its authorization), the source or correction records
|
|
747
|
+
* that could not be read, and the corrections made. None of these is a notice
|
|
748
|
+
* for the whole screen (ledger D14); records that could not be read are counted
|
|
749
|
+
* on the fold itself so they are not hidden.
|
|
750
|
+
*
|
|
751
|
+
* @param {Document} doc
|
|
752
|
+
* @param {{ source?: string | Element | null, issues?: unknown[], history?: unknown[] }} parts
|
|
753
|
+
*/
|
|
754
|
+
export function renderEvidenceAndHistory(doc, { source = null, issues = [], history = [] } = {}) {
|
|
755
|
+
const unreadable = (Array.isArray(issues) ? issues : []).filter(isRecord);
|
|
756
|
+
const corrections = (Array.isArray(history) ? history : []).filter(isRecord);
|
|
757
|
+
const details = makeElement(doc, 'details', { className: 'bb-graph-evidence' });
|
|
758
|
+
const summary = makeElement(doc, 'summary', { text: '根拠と履歴' });
|
|
759
|
+
if (unreadable.length > 0) summary.append(badge(doc, `読めない記録 ${unreadable.length}件`, 'warning'));
|
|
760
|
+
details.append(summary);
|
|
761
|
+
const section = (title, ...children) => {
|
|
762
|
+
const block = makeElement(doc, 'section', { className: 'bb-graph-evidence-part' });
|
|
763
|
+
block.append(makeElement(doc, 'h4', { text: title }), ...children.filter(Boolean));
|
|
764
|
+
details.append(block);
|
|
765
|
+
};
|
|
766
|
+
if (source) {
|
|
767
|
+
const body = typeof source === 'object' && typeof source.tagName === 'string' ? source : makeElement(doc, 'p', { text: String(source) });
|
|
768
|
+
section('出どころ', body);
|
|
769
|
+
}
|
|
770
|
+
if (unreadable.length > 0) {
|
|
771
|
+
const list = makeElement(doc, 'ul', { className: 'bb-graph-issue-list' });
|
|
772
|
+
for (const issue of unreadable) {
|
|
773
|
+
const where = `${String(issue.file ?? '不明')}${Number.isInteger(issue.line) ? `(${issue.line}行目)` : ''}`;
|
|
774
|
+
list.append(makeElement(doc, 'li', { text: `${where}: ${String(issue.reason ?? '理由不明')}` }));
|
|
775
|
+
}
|
|
776
|
+
section(
|
|
777
|
+
'読めない記録',
|
|
778
|
+
makeElement(doc, 'p', { text: `この記録の出典や訂正の履歴のうち、壊れていて読めない記録が${unreadable.length}件あります。表示している内容は読めた記録だけから作っているため、最後の訂正や出典が抜けているかもしれません。` }),
|
|
779
|
+
list,
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
if (corrections.length > 0) {
|
|
783
|
+
const list = makeElement(doc, 'ol', { className: 'bb-graph-history-list' });
|
|
784
|
+
for (const record of corrections) {
|
|
785
|
+
const fields = Array.isArray(record.changedFields) ? record.changedFields.map((field) => FIELD_LABELS[field] ?? field).join('・') : '';
|
|
786
|
+
list.append(makeElement(doc, 'li', {
|
|
787
|
+
text: `${formatDateTime(record.at)} ${CORRECTION_KIND_LABELS[record.kind] ?? '直した'}${fields ? `(${fields})` : ''}。理由: ${String(record.reason ?? '')}`,
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
section(`訂正の履歴(${corrections.length}件)`, list);
|
|
791
|
+
} else if (Array.isArray(history)) {
|
|
792
|
+
section('訂正の履歴', makeElement(doc, 'p', { className: 'bb-graph-empty', text: 'この記録は、まだ訂正されていません。' }));
|
|
793
|
+
}
|
|
794
|
+
return details;
|
|
795
|
+
}
|
|
796
|
+
|
|
623
797
|
// ---------------------------------------------------------------------------
|
|
624
798
|
// Layout
|
|
625
799
|
|
|
@@ -662,6 +836,11 @@ export function createGraphLayout(doc, { root, rail, label, className, contractV
|
|
|
662
836
|
surface.append(...workspaceChildren.filter(Boolean));
|
|
663
837
|
const detail = makeElement(doc, 'div', { className: `bb-graph-rail ${className}-rail` });
|
|
664
838
|
detail.append(...railChildren.filter(Boolean));
|
|
839
|
+
// A view with nothing for the rail (its detail is inside the workspace) draws no inline rail.
|
|
840
|
+
if (!rail && !railChildren.some(Boolean)) {
|
|
841
|
+
root.replaceChildren(surface);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
665
844
|
if (rail) {
|
|
666
845
|
root.replaceChildren(surface);
|
|
667
846
|
rail.replaceChildren(detail);
|
package/ui/local-web-shell.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@import './native-scrollbars.css';
|
|
2
|
+
@import './area-nav.css';
|
|
2
3
|
/*
|
|
3
4
|
* Shell of the local Web host, in the organization edition's layout: a white
|
|
4
5
|
* left rail (brand, navigation, source) and a paper workspace. Uses only the
|
|
@@ -71,19 +72,6 @@ body {
|
|
|
71
72
|
gap: 2px;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
.bb-shell-nav-group {
|
|
75
|
-
display: grid;
|
|
76
|
-
gap: 2px;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.bb-shell-nav-group-head {
|
|
80
|
-
margin: 14px 10px 4px;
|
|
81
|
-
color: var(--bb-color-rail-muted);
|
|
82
|
-
font-size: 11px;
|
|
83
|
-
font-weight: 700;
|
|
84
|
-
letter-spacing: .04em;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
75
|
.bb-shell-nav-link {
|
|
88
76
|
min-height: 38px;
|
|
89
77
|
display: flex;
|
package/ui/local-web-shell.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { createGraphProjectsView } from './graph-projects-view.js';
|
|
19
19
|
import { createGraphRegistryView } from './graph-registry-view.js';
|
|
20
|
+
import { createGraphOntologyView } from './graph-ontology-view.js';
|
|
20
21
|
import { createPeopleAndCompaniesView } from './people-and-companies-view.js';
|
|
21
22
|
import { createObjectiveEditorController } from './objective-editor.js';
|
|
22
23
|
import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
|
|
@@ -27,6 +28,7 @@ import { createMeetingMinutesLineageExtension } from './meeting-minutes-lineage-
|
|
|
27
28
|
import { createWorldModelView } from './world-model-view.js';
|
|
28
29
|
import { createGraphClient } from './graph-view-shared.js';
|
|
29
30
|
import { installNativeScrollbars } from './native-scrollbars.js';
|
|
31
|
+
import { browserAreaStorage, createAreaMemory, createAreaNav } from './area-nav.js';
|
|
30
32
|
|
|
31
33
|
export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
|
|
32
34
|
|
|
@@ -51,22 +53,30 @@ const NAV_ICON_PATHS = Object.freeze({
|
|
|
51
53
|
minutes: ['M6 3.5h9l3 3V20.5H6z', 'M15 3.5v4h3', 'M9 12h6m-6 3h6'],
|
|
52
54
|
// Two people (the organization edition's 人と会社 uses the same shape).
|
|
53
55
|
people: ['M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z', 'M2.5 20a6.5 6.5 0 0 1 13 0', 'M16 4.5a3.5 3.5 0 0 1 0 6.5', 'M18 14a6.5 6.5 0 0 1 3.5 6'],
|
|
56
|
+
// Kinds of information and how they connect (settings, ledger D14): three linked boxes.
|
|
57
|
+
ontology: ['M4 4h6v6H4z', 'M14 14h6v6h-6z', 'M14 4h6v6h-6z', 'M10 7h4', 'M17 10v4'],
|
|
54
58
|
// World view (extension id `world`, ledger P17).
|
|
55
59
|
world: ['M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z', 'M3 12h18', 'M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3z'],
|
|
60
|
+
// Areas whose screens have their own icons (ledger D10, stage 2): a briefcase, and
|
|
61
|
+
// the organization edition's gear.
|
|
62
|
+
'area:work': ['M4 8h16v11H4z', 'M9 8V5h6v3', 'M4 13h16'],
|
|
63
|
+
'area:settings': ['M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915', 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z'],
|
|
56
64
|
});
|
|
57
65
|
// Screens without their own icon (for example a private product's extension).
|
|
58
66
|
const DEFAULT_NAV_ICON_PATHS = Object.freeze(['M5 20V10m7 10V4m7 16v-7']);
|
|
59
67
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
60
68
|
|
|
61
|
-
// The menu's areas (brainbase-project ledger D10): the same order and
|
|
62
|
-
//
|
|
63
|
-
//
|
|
69
|
+
// The menu's areas (brainbase-project ledger D10): the same order and names as
|
|
70
|
+
// the organization edition. The left side lists only the areas; the screens of
|
|
71
|
+
// the current area are tabs above the page (stage 2). 目的と現状 is the only
|
|
72
|
+
// screen of its area and keeps its own name. Inside an area, `order` lists the
|
|
73
|
+
// screens first in that order; `defaultScreen` opens when nothing is remembered.
|
|
64
74
|
const NAV_AREAS = Object.freeze([
|
|
65
|
-
Object.freeze({ id: 'purpose', label: null, order: ['objectives'] }),
|
|
66
|
-
Object.freeze({ id: 'work', label: '仕事', order: ['projects', 'minutes'] }),
|
|
67
|
-
Object.freeze({ id: 'outcomes', label: '成果', order: ['value-proofs'] }),
|
|
68
|
-
Object.freeze({ id: 'information', label: '情報と関係', order: ['graph', 'people'] }),
|
|
69
|
-
Object.freeze({ id: 'settings', label: '設定', order: [] }),
|
|
75
|
+
Object.freeze({ id: 'purpose', label: null, order: ['objectives'], icon: 'objectives' }),
|
|
76
|
+
Object.freeze({ id: 'work', label: '仕事', order: ['projects', 'minutes'], defaultScreen: 'projects', icon: 'area:work' }),
|
|
77
|
+
Object.freeze({ id: 'outcomes', label: '成果', order: ['value-proofs'], icon: 'value-proofs' }),
|
|
78
|
+
Object.freeze({ id: 'information', label: '情報と関係', order: ['graph', 'people'], defaultScreen: 'graph', icon: 'graph' }),
|
|
79
|
+
Object.freeze({ id: 'settings', label: '設定', order: ['ontology'], defaultScreen: 'ontology', icon: 'area:settings' }),
|
|
70
80
|
]);
|
|
71
81
|
|
|
72
82
|
// Menu order only: the screens' own order (and so the first screen) stays as given.
|
|
@@ -88,6 +98,23 @@ function navLayout(screens) {
|
|
|
88
98
|
return { leading, areas, trailing };
|
|
89
99
|
}
|
|
90
100
|
|
|
101
|
+
// Every menu line is an area: a screen without an area is an area of its own.
|
|
102
|
+
function navAreas(screens) {
|
|
103
|
+
const layout = navLayout(screens);
|
|
104
|
+
const own = (screen) => ({ id: screen.id, label: null, screenIds: [screen.id], icon: screen.id });
|
|
105
|
+
return [
|
|
106
|
+
...layout.leading.map(own),
|
|
107
|
+
...layout.areas.map(({ area, screens: members }) => ({
|
|
108
|
+
id: area.id,
|
|
109
|
+
label: area.label,
|
|
110
|
+
defaultScreen: area.defaultScreen,
|
|
111
|
+
screenIds: members.map((screen) => screen.id),
|
|
112
|
+
icon: area.icon,
|
|
113
|
+
})),
|
|
114
|
+
...layout.trailing.map(own),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
function makeIcon(doc, id) {
|
|
92
119
|
const paths = NAV_ICON_PATHS[id] ?? DEFAULT_NAV_ICON_PATHS;
|
|
93
120
|
const create = (tag) => (typeof doc.createElementNS === 'function' ? doc.createElementNS(SVG_NS, tag) : doc.createElement(tag));
|
|
@@ -299,7 +326,7 @@ function mountGraphScreen(screenId, createView, extra = {}) {
|
|
|
299
326
|
fetcher: context.fetcher,
|
|
300
327
|
token: context.token,
|
|
301
328
|
initialEntityId: context.initialEntityId,
|
|
302
|
-
...extra,
|
|
329
|
+
...(typeof extra === 'function' ? extra(context) : extra),
|
|
303
330
|
});
|
|
304
331
|
};
|
|
305
332
|
}
|
|
@@ -309,8 +336,13 @@ export const LOCAL_WEB_SCREENS = Object.freeze([
|
|
|
309
336
|
Object.freeze({ id: 'minutes', label: '議事録', area: 'work', usesGraph: false, rail: true, source: '正本の議事録', mount: mountMeetingMinutes }),
|
|
310
337
|
Object.freeze({ id: 'objectives', label: '目的と現状', area: 'purpose', usesGraph: true, rail: true, source: '手元のGraph', mount: mountObjectives }),
|
|
311
338
|
Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', area: 'work', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('projects', createGraphProjectsView) }),
|
|
312
|
-
|
|
339
|
+
// A record of 情報と関係 opens as its own screen at `#graph?entity_id=…`; the list is `#graph`.
|
|
340
|
+
Object.freeze({ id: 'graph', label: '情報と関係', area: 'information', usesGraph: true, rail: false, source: '手元のGraph', mount: mountGraphScreen('graph', createGraphRegistryView, (context) => ({
|
|
341
|
+
onRouteChange: (entityId, options) => context.navigate?.(entityId ? `graph?entity_id=${encodeURIComponent(entityId)}` : 'graph', options),
|
|
342
|
+
})) }),
|
|
313
343
|
Object.freeze({ id: 'people', label: '人と会社', area: 'information', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('people', createPeopleAndCompaniesView, { canCorrect: true }) }),
|
|
344
|
+
// The definitions behind 情報と関係: infrequent management work, so under settings (ledger D14).
|
|
345
|
+
Object.freeze({ id: 'ontology', label: '情報の種類とつながり方', area: 'settings', usesGraph: true, rail: false, source: '手元のGraph', mount: mountGraphScreen('ontology', createGraphOntologyView, { canCorrect: true }) }),
|
|
314
346
|
]);
|
|
315
347
|
|
|
316
348
|
function renderGraphGate(doc, slot, status, onRecheck, label) {
|
|
@@ -394,6 +426,8 @@ export function createLocalWebShell({
|
|
|
394
426
|
initialScreen,
|
|
395
427
|
meetingMinutesStorage = null,
|
|
396
428
|
meetingMinutesLineage = null,
|
|
429
|
+
areaMemory = createAreaMemory(browserAreaStorage()),
|
|
430
|
+
onNavigate,
|
|
397
431
|
} = {}) {
|
|
398
432
|
if (!root) throw new TypeError('root is required');
|
|
399
433
|
const doc = explicitDocument ?? (typeof document === 'undefined' ? null : document);
|
|
@@ -401,12 +435,13 @@ export function createLocalWebShell({
|
|
|
401
435
|
const request = typeof fetcher === 'function'
|
|
402
436
|
? fetcher
|
|
403
437
|
: typeof globalThis.fetch === 'function' ? (path, init) => globalThis.fetch(path, init) : null;
|
|
404
|
-
|
|
438
|
+
// `navigate(target, { back })` lets a screen give its own state an address (the host's hash).
|
|
439
|
+
const navigate = typeof onNavigate === 'function' ? (target, options = {}) => onNavigate(target, options) : null;
|
|
440
|
+
const context = Object.freeze({ document: doc, fetcher: request, token, meetingMinutesStorage, meetingMinutesLineage, navigate });
|
|
405
441
|
|
|
406
442
|
const status = { phase: 'loading', data: null, error: null };
|
|
407
443
|
const mounted = new Map();
|
|
408
444
|
const slots = new Map();
|
|
409
|
-
const links = new Map();
|
|
410
445
|
const pendingEntityIds = new Map();
|
|
411
446
|
let active = null;
|
|
412
447
|
|
|
@@ -427,37 +462,27 @@ export function createLocalWebShell({
|
|
|
427
462
|
};
|
|
428
463
|
menuButton.addEventListener('click', () => setMenuOpen(!sidebar.className.includes('is-open')));
|
|
429
464
|
backdrop.addEventListener('click', () => setMenuOpen(false));
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
465
|
+
const areaList = navAreas(screens);
|
|
466
|
+
const areaNav = createAreaNav({
|
|
467
|
+
document: doc,
|
|
468
|
+
areas: areaList,
|
|
469
|
+
screens: screens.map((screen) => ({ id: screen.id, label: screen.label })),
|
|
470
|
+
memory: areaMemory,
|
|
471
|
+
linkClassName: 'bb-shell-nav-link',
|
|
472
|
+
iconFor: (area) => makeIcon(doc, areaList.find((entry) => entry.id === area.id)?.icon ?? area.id),
|
|
473
|
+
onSelect: (screenId) => {
|
|
435
474
|
setMenuOpen(false);
|
|
436
|
-
controller.show(
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
};
|
|
441
|
-
const layout = navLayout(screens);
|
|
442
|
-
for (const screen of layout.leading) nav.append(navLink(screen));
|
|
443
|
-
for (const { area, screens: members } of layout.areas) {
|
|
444
|
-
if (!area.label) {
|
|
445
|
-
for (const screen of members) nav.append(navLink(screen));
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
const headId = `bb-shell-nav-area-${area.id}`;
|
|
449
|
-
const group = makeElement(doc, 'div', { className: 'bb-shell-nav-group', attrs: { role: 'group', 'aria-labelledby': headId } });
|
|
450
|
-
group.append(makeElement(doc, 'div', { className: 'bb-shell-nav-group-head', text: area.label, attrs: { id: headId } }));
|
|
451
|
-
for (const screen of members) group.append(navLink(screen));
|
|
452
|
-
nav.append(group);
|
|
453
|
-
}
|
|
454
|
-
for (const screen of layout.trailing) nav.append(navLink(screen));
|
|
475
|
+
controller.show(screenId);
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
nav.append(areaNav.nav);
|
|
455
479
|
const source = makeElement(doc, 'section', { className: 'bb-shell-source', attrs: { 'aria-label': '出典' } });
|
|
456
480
|
sidebar.append(brand, nav, source);
|
|
457
481
|
const workspace = makeElement(doc, 'div', { className: 'bb-shell-workspace' });
|
|
458
482
|
const mobileBar = makeElement(doc, 'div', { className: 'bb-shell-mobile-bar' });
|
|
459
483
|
mobileBar.append(makeElement(doc, 'strong', { text: 'Brainbase' }), menuButton);
|
|
460
484
|
const main = makeElement(doc, 'main', { className: 'bb-shell-main' });
|
|
485
|
+
main.append(areaNav.tabs);
|
|
461
486
|
for (const screen of screens) {
|
|
462
487
|
const slot = makeElement(doc, 'section', { className: 'bb-shell-screen', attrs: { 'data-screen': screen.id, 'aria-label': screen.label } });
|
|
463
488
|
slot.hidden = true;
|
|
@@ -493,6 +518,8 @@ export function createLocalWebShell({
|
|
|
493
518
|
if (mounted.has(screen.id)) {
|
|
494
519
|
const view = mounted.get(screen.id);
|
|
495
520
|
if (entityId && screen.id === 'graph' && typeof view?.openEntity === 'function') void view.openEntity(entityId);
|
|
521
|
+
// `#graph` without a record is the list: a record still open there closes (the browser's back).
|
|
522
|
+
if (!entityId && screen.id === 'graph' && view?.state?.detail && typeof view.closeEntity === 'function') view.closeEntity();
|
|
496
523
|
if (entityId && screen.id === 'value-proofs' && typeof view?.openDecision === 'function') view.openDecision(entityId);
|
|
497
524
|
if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);
|
|
498
525
|
return;
|
|
@@ -526,10 +553,7 @@ export function createLocalWebShell({
|
|
|
526
553
|
: screen.id === 'projects' ? parsed.projectId ?? null : null);
|
|
527
554
|
active = screen.id;
|
|
528
555
|
for (const [screenId, slot] of slots) slot.hidden = screenId !== screen.id;
|
|
529
|
-
|
|
530
|
-
if (screenId === screen.id) link.setAttribute('aria-current', 'page');
|
|
531
|
-
else link.removeAttribute('aria-current');
|
|
532
|
-
}
|
|
556
|
+
areaNav.show(screen.id);
|
|
533
557
|
ensureMounted(screen, pendingEntityIds.get(screen.id));
|
|
534
558
|
updateRail();
|
|
535
559
|
return controller;
|
package/ui/outcome-knowledge.js
CHANGED
|
@@ -84,6 +84,9 @@ const SCOPE_LABELS = Object.freeze({
|
|
|
84
84
|
|
|
85
85
|
const LIFECYCLE_STATUS_LABELS = Object.freeze({
|
|
86
86
|
active: '有効',
|
|
87
|
+
decided: '決定済み',
|
|
88
|
+
active_reference: '参照用',
|
|
89
|
+
unknown: UNKNOWN,
|
|
87
90
|
inactive: '無効',
|
|
88
91
|
retired: '失効',
|
|
89
92
|
superseded: '置換済み',
|
|
@@ -1480,7 +1483,8 @@ export function createKnowledgeOverviewController(options = {}) {
|
|
|
1480
1483
|
const { root, api, onSelectProject } = options;
|
|
1481
1484
|
if (!root || typeof root.replaceChildren !== 'function') throw new Error('knowledge_root_unavailable');
|
|
1482
1485
|
const labels = resolveKnowledgeUiLabels(options.labels);
|
|
1483
|
-
|
|
1486
|
+
// A host path may already carry conditions (for example `?limit=100`); keep them and add the filters.
|
|
1487
|
+
const [itemsBase, itemsBaseQuery = ''] = (optionalText(options.itemsPath).trim() || `/api/knowledge/${KNOWLEDGE_ROUTES.items}`).split(/\?(.*)/s);
|
|
1484
1488
|
const state = {
|
|
1485
1489
|
filters: { q: '', scope: 'all', status: 'active' },
|
|
1486
1490
|
list: { state: 'loading', records: null, absence_confirmed: false, searched_scope: null },
|
|
@@ -1491,7 +1495,7 @@ export function createKnowledgeOverviewController(options = {}) {
|
|
|
1491
1495
|
const projectName = (code) => projectOptions(options.projects).find((option) => option.code === code)?.name ?? null;
|
|
1492
1496
|
|
|
1493
1497
|
function itemsPath() {
|
|
1494
|
-
const query = new URLSearchParams();
|
|
1498
|
+
const query = new URLSearchParams(itemsBaseQuery);
|
|
1495
1499
|
if (state.filters.q) query.set('q', state.filters.q);
|
|
1496
1500
|
if (state.filters.scope) query.set('scope', state.filters.scope);
|
|
1497
1501
|
if (state.filters.status) query.set('status', state.filters.status);
|