@unson/brainbase-mcp 0.37.0 → 0.39.0
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/graph-corrections.d.ts +16 -1
- package/dist/graph-corrections.js +81 -2
- package/dist/graph-maintenance-engine.js +20 -3
- package/dist/graph-revision-reader.js +1 -0
- package/dist/graph-web-http.d.ts +1 -1
- package/dist/graph-web-http.js +7 -2
- package/dist/graph-web.d.ts +73 -0
- package/dist/graph-web.js +112 -2
- package/dist/local-web-host.js +3 -1
- package/dist/relation-registry.js +8 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/templates.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -2
- package/ui/graph-view-shared.js +87 -7
- package/ui/local-web-shell.css +13 -0
- package/ui/local-web-shell.js +61 -10
- package/ui/people-and-companies-view.css +121 -0
- package/ui/people-and-companies-view.js +596 -0
package/ui/graph-view-shared.js
CHANGED
|
@@ -56,6 +56,7 @@ export const GRAPH_RELATION_LABELS = Object.freeze({
|
|
|
56
56
|
participates_in: Object.freeze({ label: '参加', meaning: '人物がプロジェクトに参加している', outgoing: 'に参加', incoming: 'が参加' }),
|
|
57
57
|
accountable_for: Object.freeze({ label: '責任', meaning: '人物がプロジェクトの成果や判断に責任を持つ', outgoing: 'に責任を持つ', incoming: 'が責任を持つ' }),
|
|
58
58
|
owned_by: Object.freeze({ label: '所有', meaning: 'プロジェクトを組織が所有している', outgoing: 'が所有', incoming: 'を所有' }),
|
|
59
|
+
related_to: Object.freeze({ label: '関わる', meaning: '会社が顧客・協業先などとしてプロジェクトに関わっている', outgoing: 'に関わる', incoming: 'が関わる' }),
|
|
59
60
|
governs: Object.freeze({ label: '方針', meaning: '判断や原則がプロジェクトの進め方を決める', outgoing: 'の進め方を決める', incoming: 'が進め方を決める' }),
|
|
60
61
|
supersedes: Object.freeze({ label: '置き換え', meaning: '判断が以前の判断を置き換える', outgoing: 'を置き換える', incoming: 'に置き換えられた' }),
|
|
61
62
|
});
|
|
@@ -101,6 +102,7 @@ const CORRECTION_KIND_LABELS = Object.freeze({
|
|
|
101
102
|
update_entity: '記録を直した',
|
|
102
103
|
update_edge: '関係を直した',
|
|
103
104
|
create_edge: '関係を加えた',
|
|
105
|
+
create_entity: '記録を加えた',
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
/** Inputs of the correction form. `date` inputs hold a local day (YYYY-MM-DD). */
|
|
@@ -127,6 +129,9 @@ export const GRAPH_CORRECTION_ERROR_MESSAGES = Object.freeze({
|
|
|
127
129
|
correction_relation_change_not_allowed: '関係の種類は変えられません。今の関係を終えてから、新しい関係を加えてください。',
|
|
128
130
|
correction_endpoint_change_not_allowed: '関係の相手は変えられません。今の関係を終えてから、新しい関係を加えてください。',
|
|
129
131
|
correction_endpoint_type_invalid: 'この組み合わせの関係は加えられません。',
|
|
132
|
+
correction_entity_type_not_allowed: 'ここで作れるのは人と会社だけです。',
|
|
133
|
+
correction_name_required: '名前を書いてください。',
|
|
134
|
+
entity_already_exists: '同じ名前の記録がすでにあります。今の記録を開いて確かめてください。',
|
|
130
135
|
correction_field_not_allowed: 'この項目は、ここでは直せません。',
|
|
131
136
|
correction_changes_required: '変更がありません。直したい項目を書き換えてください。',
|
|
132
137
|
correction_scope_not_allowed: 'この画面では、この記録や関係は直せません。',
|
|
@@ -442,6 +447,7 @@ export function readOnlyGraphClient(client) {
|
|
|
442
447
|
* `GRAPH_CORRECTION_FIELDS` such as `name` or `goal` (default: every field).
|
|
443
448
|
* - `edges: false`: relations cannot be corrected or ended.
|
|
444
449
|
* - `createEdges: false`: relations cannot be added.
|
|
450
|
+
* - `createEntities: false`: people and companies cannot be created.
|
|
445
451
|
*/
|
|
446
452
|
export function graphCorrectionScope(scope) {
|
|
447
453
|
const given = isRecord(scope) ? scope : {};
|
|
@@ -451,6 +457,7 @@ export function graphCorrectionScope(scope) {
|
|
|
451
457
|
return Object.freeze({
|
|
452
458
|
edges: given.edges !== false,
|
|
453
459
|
createEdges: given.createEdges !== false,
|
|
460
|
+
createEntities: given.createEntities !== false,
|
|
454
461
|
/** Whether a record of this kind may be corrected. */
|
|
455
462
|
entity: (type) => types === null || types.has(type),
|
|
456
463
|
/** The given record fields that may be corrected, in their order. */
|
|
@@ -466,6 +473,7 @@ export function graphEntityCorrectionFields(type) {
|
|
|
466
473
|
/** Whether a built correction request stays inside the scope (`recordType` is the corrected entity's kind). */
|
|
467
474
|
function withinCorrectionScope(scope, body, recordType) {
|
|
468
475
|
if (body.kind === 'create_edge') return scope.createEdges;
|
|
476
|
+
if (body.kind === 'create_entity') return scope.createEntities && scope.entity(body.entity?.type);
|
|
469
477
|
if (body.kind === 'update_edge') return scope.edges;
|
|
470
478
|
const changed = Object.keys(body.changes ?? {});
|
|
471
479
|
return scope.entity(recordType) && scope.fields(changed).length === changed.length;
|
|
@@ -820,6 +828,24 @@ export function buildCorrectionRequest(form) {
|
|
|
820
828
|
return { ok: true, body: { kind: 'create_edge', reason, edge } };
|
|
821
829
|
}
|
|
822
830
|
|
|
831
|
+
if (form.kind === 'create_entity') {
|
|
832
|
+
const type = String(form.draft.type ?? '');
|
|
833
|
+
if (!form.entityTypes.includes(type)) return { ok: false, message: '作る記録の種類を選んでください。' };
|
|
834
|
+
const name = String(form.draft.name ?? '').trim();
|
|
835
|
+
if (!name) return { ok: false, message: GRAPH_CORRECTION_ERROR_MESSAGES.correction_name_required };
|
|
836
|
+
const entity = { type, name };
|
|
837
|
+
const aliases = splitAliases(form.draft.aliases ?? '').filter((alias) => alias !== name);
|
|
838
|
+
const summary = String(form.draft.summary ?? '').trim();
|
|
839
|
+
if (aliases.length > 0) entity.aliases = aliases;
|
|
840
|
+
if (summary) entity.summary = summary;
|
|
841
|
+
if (form.draft.validFrom) {
|
|
842
|
+
const validFrom = dayToRfc3339(form.draft.validFrom);
|
|
843
|
+
if (!validFrom) return { ok: false, message: '開始日の形式が正しくありません。' };
|
|
844
|
+
entity.validFrom = validFrom;
|
|
845
|
+
}
|
|
846
|
+
return { ok: true, body: { kind: 'create_entity', reason, entity } };
|
|
847
|
+
}
|
|
848
|
+
|
|
823
849
|
const changes = {};
|
|
824
850
|
for (const field of form.fields) {
|
|
825
851
|
if (field === 'icon') {
|
|
@@ -860,11 +886,17 @@ export function buildCorrectionRequest(form) {
|
|
|
860
886
|
|
|
861
887
|
function readBackFields(form, payload) {
|
|
862
888
|
if (form.kind === 'create_edge') return ['role', 'context', 'validFrom', 'validTo'];
|
|
889
|
+
if (form.kind === 'create_entity') return ['name', 'aliases', 'summary', 'validFrom'];
|
|
863
890
|
const changed = Array.isArray(payload?.correction?.changedFields) ? payload.correction.changedFields : [];
|
|
864
891
|
const fields = changed.map((field) => field.replace(/^metadata\./u, '')).filter((field) => GRAPH_CORRECTION_FIELDS[field]);
|
|
865
892
|
return fields.length > 0 ? fields : form.fields;
|
|
866
893
|
}
|
|
867
894
|
|
|
895
|
+
/** What a new record is, as rows shown before its values. */
|
|
896
|
+
function createdEntityRows(form, record) {
|
|
897
|
+
return [['種類', form.typeNames[record?.type] ?? typeLabel(record?.type)]];
|
|
898
|
+
}
|
|
899
|
+
|
|
868
900
|
/** What a new relation connects, as rows shown before its values. */
|
|
869
901
|
function createdRows(form, record) {
|
|
870
902
|
const counterpartId = record?.fromId === form.anchor.id ? record?.toId : record?.fromId;
|
|
@@ -1015,6 +1047,22 @@ function renderCreateFields(doc, form, callbacks) {
|
|
|
1015
1047
|
return group;
|
|
1016
1048
|
}
|
|
1017
1049
|
|
|
1050
|
+
function renderCreateEntityFields(doc, form, callbacks) {
|
|
1051
|
+
const group = makeElement(doc, 'div', { className: 'bb-graph-fields' });
|
|
1052
|
+
if (form.entityTypes.length > 1) {
|
|
1053
|
+
group.append(selectControl(doc, {
|
|
1054
|
+
id: `${form.formId}-type`,
|
|
1055
|
+
name: 'type',
|
|
1056
|
+
label: '種類',
|
|
1057
|
+
value: form.draft.type,
|
|
1058
|
+
options: form.entityTypes.map((type) => ({ value: type, label: form.typeNames[type] ?? typeLabel(type) })),
|
|
1059
|
+
onChange: (value) => callbacks.onInput('type', value),
|
|
1060
|
+
}));
|
|
1061
|
+
}
|
|
1062
|
+
for (const field of form.fields) group.append(fieldControl(doc, form, field, callbacks));
|
|
1063
|
+
return group;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1018
1066
|
/**
|
|
1019
1067
|
* The correction panel. `form` is the state held by `createGraphCorrection`;
|
|
1020
1068
|
* callbacks are the controller's handlers.
|
|
@@ -1035,7 +1083,9 @@ export function renderCorrectionPanel(doc, form, callbacks) {
|
|
|
1035
1083
|
const readBack = makeElement(doc, 'div', { className: 'bb-graph-readback', attrs: { 'aria-label': '読み直した内容' } });
|
|
1036
1084
|
readBack.append(
|
|
1037
1085
|
makeElement(doc, 'strong', { text: '読み直した内容' }),
|
|
1038
|
-
renderValues(doc, payload.record, readBackFields(form, payload), form.kind === 'create_edge'
|
|
1086
|
+
renderValues(doc, payload.record, readBackFields(form, payload), form.kind === 'create_edge'
|
|
1087
|
+
? createdRows(form, payload.record)
|
|
1088
|
+
: form.kind === 'create_entity' ? createdEntityRows(form, payload.record) : []),
|
|
1039
1089
|
);
|
|
1040
1090
|
panel.append(readBack, notice(doc, 'accent', mcpNote(payload)));
|
|
1041
1091
|
panel.append(recordDetails(doc, [['記録ID', payload.record?.id], ['保存した内容のdigest', payload.digest], ['訂正ID', payload.correction?.id]]));
|
|
@@ -1045,12 +1095,17 @@ export function renderCorrectionPanel(doc, form, callbacks) {
|
|
|
1045
1095
|
|
|
1046
1096
|
if (form.phase === 'conflict') {
|
|
1047
1097
|
const exists = form.conflictCode === 'edge_already_exists';
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1098
|
+
const entityExists = form.conflictCode === 'entity_already_exists';
|
|
1099
|
+
panel.append(notice(doc, 'warning', entityExists
|
|
1100
|
+
? '同じ名前の記録がすでにあるため、作りませんでした。今の記録を下に示します。同じ人や会社なら、今の記録を使ってください(終わっている記録は、終了日を消すと戻せます)。入力は残しています。'
|
|
1101
|
+
: exists
|
|
1102
|
+
? 'この関わりはすでに登録されているため、加えませんでした。今の内容を下に示します。終わった関わりをもう一度始めるときは、その関わりの終了日を消してください。入力は残しています。'
|
|
1103
|
+
: 'ほかの保存で内容が変わっていたため、保存しませんでした。今の内容を下に示します。入力は残しています。今の内容を確かめてから、もう一度保存してください。', 'alert'));
|
|
1051
1104
|
const current = makeElement(doc, 'div', { className: 'bb-graph-current', attrs: { 'aria-label': '今の内容' } });
|
|
1052
|
-
const fields = exists ? ['role', 'context', 'validFrom', 'validTo'] : form.fields;
|
|
1053
|
-
current.append(makeElement(doc, 'strong', { text: '今の内容' }), renderValues(doc, form.current.record, fields
|
|
1105
|
+
const fields = exists ? ['role', 'context', 'validFrom', 'validTo'] : entityExists ? ['name', 'aliases', 'summary', 'validFrom', 'validTo'] : form.fields;
|
|
1106
|
+
current.append(makeElement(doc, 'strong', { text: '今の内容' }), renderValues(doc, form.current.record, fields, entityExists
|
|
1107
|
+
? [['種類', form.typeNames[form.current.record?.type] ?? typeLabel(form.current.record?.type)]]
|
|
1108
|
+
: []));
|
|
1054
1109
|
current.append(recordDetails(doc, [['記録ID', form.current.record?.id], ['今の内容のdigest', form.current.digest]]));
|
|
1055
1110
|
panel.append(current);
|
|
1056
1111
|
} else if (form.message) {
|
|
@@ -1065,6 +1120,8 @@ export function renderCorrectionPanel(doc, form, callbacks) {
|
|
|
1065
1120
|
});
|
|
1066
1121
|
if (form.kind === 'create_edge') {
|
|
1067
1122
|
formElement.append(renderCreateFields(doc, form, callbacks));
|
|
1123
|
+
} else if (form.kind === 'create_entity') {
|
|
1124
|
+
formElement.append(renderCreateEntityFields(doc, form, callbacks));
|
|
1068
1125
|
} else {
|
|
1069
1126
|
const group = makeElement(doc, 'div', { className: 'bb-graph-fields' });
|
|
1070
1127
|
for (const field of form.fields) group.append(fieldControl(doc, form, field, callbacks));
|
|
@@ -1298,6 +1355,29 @@ export function createGraphCorrection({ client, rerender, onSaved, onConflict, n
|
|
|
1298
1355
|
void searchCandidates('');
|
|
1299
1356
|
return form;
|
|
1300
1357
|
},
|
|
1358
|
+
/**
|
|
1359
|
+
* Create a person or a company. `types` lists the kinds offered (only
|
|
1360
|
+
* `person` and `org` are created; others are dropped), `typeNames` their
|
|
1361
|
+
* labels. Null outside the scope.
|
|
1362
|
+
*/
|
|
1363
|
+
openCreateEntity({ types = ['person', 'org'], type, typeNames = {}, title = '記録を加える', subject } = {}) {
|
|
1364
|
+
const entityTypes = allowed.createEntities
|
|
1365
|
+
? types.filter((candidate) => (candidate === 'person' || candidate === 'org') && allowed.entity(candidate))
|
|
1366
|
+
: [];
|
|
1367
|
+
if (entityTypes.length === 0) return null;
|
|
1368
|
+
const fields = ['name', 'aliases', 'summary', 'validFrom'];
|
|
1369
|
+
form = baseForm('create_entity', {
|
|
1370
|
+
title,
|
|
1371
|
+
subject: subject ?? null,
|
|
1372
|
+
entityTypes,
|
|
1373
|
+
typeNames,
|
|
1374
|
+
fields,
|
|
1375
|
+
original: Object.fromEntries(fields.map((field) => [field, ''])),
|
|
1376
|
+
draft: { type: entityTypes.includes(type) ? type : entityTypes[0], name: '', aliases: '', summary: '', validFrom: '' },
|
|
1377
|
+
});
|
|
1378
|
+
redraw();
|
|
1379
|
+
return form;
|
|
1380
|
+
},
|
|
1301
1381
|
close() {
|
|
1302
1382
|
iconReadGeneration += 1;
|
|
1303
1383
|
form = null;
|
|
@@ -1353,7 +1433,7 @@ export function createGraphCorrection({ client, rerender, onSaved, onConflict, n
|
|
|
1353
1433
|
// The owner now sees the current record; the next save is based on it
|
|
1354
1434
|
// and still sends only the fields the owner changed. Untouched fields
|
|
1355
1435
|
// show the current values so the form never suggests reverting them.
|
|
1356
|
-
if (target.kind !== 'create_edge' && typeof result.current.digest === 'string') {
|
|
1436
|
+
if (target.kind !== 'create_edge' && target.kind !== 'create_entity' && typeof result.current.digest === 'string') {
|
|
1357
1437
|
target.expectedDigest = result.current.digest;
|
|
1358
1438
|
const currentValues = graphRecordValues(result.current.record);
|
|
1359
1439
|
for (const field of target.fields) {
|
package/ui/local-web-shell.css
CHANGED
|
@@ -70,6 +70,19 @@ body {
|
|
|
70
70
|
gap: 2px;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
.bb-shell-nav-group {
|
|
74
|
+
display: grid;
|
|
75
|
+
gap: 2px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.bb-shell-nav-group-head {
|
|
79
|
+
margin: 14px 10px 4px;
|
|
80
|
+
color: var(--bb-color-rail-muted);
|
|
81
|
+
font-size: 11px;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: .04em;
|
|
84
|
+
}
|
|
85
|
+
|
|
73
86
|
.bb-shell-nav-link {
|
|
74
87
|
min-height: 38px;
|
|
75
88
|
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 { createPeopleAndCompaniesView } from './people-and-companies-view.js';
|
|
20
21
|
import { createObjectiveEditorController } from './objective-editor.js';
|
|
21
22
|
import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
|
|
22
23
|
import { createJudgmentHistoryUI } from './judgment-history.js';
|
|
@@ -42,18 +43,52 @@ const UNREADABLE_LABELS = Object.freeze({
|
|
|
42
43
|
|
|
43
44
|
// Stroke icons from the organization edition's sprite (apps/web/public/index.html).
|
|
44
45
|
const NAV_ICON_PATHS = Object.freeze({
|
|
45
|
-
'value-proofs': ['
|
|
46
|
+
'value-proofs': ['M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8', 'M3 3v5h5', 'M12 7v5l4 2'],
|
|
46
47
|
objectives: ['M12 4 3 20h18z', 'M12 9v5m0 3h.01'],
|
|
47
48
|
projects: ['M3 6.5h7l2 2h9v10H3z', 'M3 6.5v-2h7l2 2'],
|
|
48
49
|
graph: ['M10 13a5 5 0 0 0 7.5.5l2-2a5 5 0 0 0-7-7l-1.2 1.2', 'M14 11a5 5 0 0 0-7.5-.5l-2 2a5 5 0 0 0 7 7l1.2-1.2'],
|
|
49
50
|
minutes: ['M6 3.5h9l3 3V20.5H6z', 'M15 3.5v4h3', 'M9 12h6m-6 3h6'],
|
|
51
|
+
// Two people (the organization edition's 人と会社 uses the same shape).
|
|
52
|
+
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'],
|
|
50
53
|
// World view (extension id `world`, ledger P17).
|
|
51
54
|
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'],
|
|
52
55
|
});
|
|
56
|
+
// Screens without their own icon (for example a private product's extension).
|
|
57
|
+
const DEFAULT_NAV_ICON_PATHS = Object.freeze(['M5 20V10m7 10V4m7 16v-7']);
|
|
53
58
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
54
59
|
|
|
60
|
+
// The menu's areas (brainbase-project ledger D10): the same order and headings
|
|
61
|
+
// as the organization edition. 目的と現状 is the only item of its area, so it
|
|
62
|
+
// has no heading. Inside an area, `order` lists the screens first in that order.
|
|
63
|
+
const NAV_AREAS = Object.freeze([
|
|
64
|
+
Object.freeze({ id: 'purpose', label: null, order: ['objectives'] }),
|
|
65
|
+
Object.freeze({ id: 'work', label: '仕事', order: ['projects', 'minutes'] }),
|
|
66
|
+
Object.freeze({ id: 'outcomes', label: '成果', order: ['value-proofs'] }),
|
|
67
|
+
Object.freeze({ id: 'information', label: '情報と関係', order: ['graph', 'people'] }),
|
|
68
|
+
Object.freeze({ id: 'settings', label: '設定', order: [] }),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Menu order only: the screens' own order (and so the first screen) stays as given.
|
|
72
|
+
// Screens without an area keep their place — those before the first area-bound
|
|
73
|
+
// screen come first (a leading extension such as the world), the rest come last.
|
|
74
|
+
function navLayout(screens) {
|
|
75
|
+
const firstInArea = screens.findIndex((screen) => NAV_AREAS.some((area) => area.id === screen.area));
|
|
76
|
+
if (firstInArea === -1) return { leading: [...screens], areas: [], trailing: [] };
|
|
77
|
+
const leading = screens.slice(0, firstInArea);
|
|
78
|
+
const trailing = screens.slice(firstInArea).filter((screen) => !NAV_AREAS.some((area) => area.id === screen.area));
|
|
79
|
+
const areas = NAV_AREAS.map((area) => {
|
|
80
|
+
const members = screens.filter((screen) => screen.area === area.id);
|
|
81
|
+
const rank = (screen) => {
|
|
82
|
+
const index = area.order.indexOf(screen.id);
|
|
83
|
+
return index === -1 ? area.order.length + members.indexOf(screen) : index;
|
|
84
|
+
};
|
|
85
|
+
return { area, screens: [...members].sort((a, b) => rank(a) - rank(b)) };
|
|
86
|
+
}).filter((entry) => entry.screens.length > 0);
|
|
87
|
+
return { leading, areas, trailing };
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
function makeIcon(doc, id) {
|
|
56
|
-
const paths = NAV_ICON_PATHS[id] ??
|
|
91
|
+
const paths = NAV_ICON_PATHS[id] ?? DEFAULT_NAV_ICON_PATHS;
|
|
57
92
|
const create = (tag) => (typeof doc.createElementNS === 'function' ? doc.createElementNS(SVG_NS, tag) : doc.createElement(tag));
|
|
58
93
|
const svg = create('svg');
|
|
59
94
|
svg.setAttribute('class', 'bb-shell-icon');
|
|
@@ -235,7 +270,7 @@ function mountObjectives(container, context) {
|
|
|
235
270
|
return { editor, worldModel };
|
|
236
271
|
}
|
|
237
272
|
|
|
238
|
-
function mountGraphScreen(screenId, createView) {
|
|
273
|
+
function mountGraphScreen(screenId, createView, extra = {}) {
|
|
239
274
|
return (container, context) => {
|
|
240
275
|
const viewRoot = makeElement(context.document, 'div');
|
|
241
276
|
makePage(container, context).append(viewRoot);
|
|
@@ -247,16 +282,18 @@ function mountGraphScreen(screenId, createView) {
|
|
|
247
282
|
fetcher: context.fetcher,
|
|
248
283
|
token: context.token,
|
|
249
284
|
initialEntityId: context.initialEntityId,
|
|
285
|
+
...extra,
|
|
250
286
|
});
|
|
251
287
|
};
|
|
252
288
|
}
|
|
253
289
|
|
|
254
290
|
export const LOCAL_WEB_SCREENS = Object.freeze([
|
|
255
|
-
Object.freeze({ id: 'value-proofs', label: '判断の履歴', usesGraph: false, rail: true, source: '判断の記録', mount: mountToday }),
|
|
256
|
-
Object.freeze({ id: 'minutes', label: '議事録', usesGraph: false, rail: true, source: '正本の議事録', mount: mountMeetingMinutes }),
|
|
257
|
-
Object.freeze({ id: 'objectives', label: '目的と現状', usesGraph: true, rail: true, source: '手元のGraph', mount: mountObjectives }),
|
|
258
|
-
Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('projects', createGraphProjectsView) }),
|
|
259
|
-
Object.freeze({ id: 'graph', label: '情報と関係', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('graph', createGraphRegistryView) }),
|
|
291
|
+
Object.freeze({ id: 'value-proofs', label: '判断の履歴', area: 'outcomes', usesGraph: false, rail: true, source: '判断の記録', mount: mountToday }),
|
|
292
|
+
Object.freeze({ id: 'minutes', label: '議事録', area: 'work', usesGraph: false, rail: true, source: '正本の議事録', mount: mountMeetingMinutes }),
|
|
293
|
+
Object.freeze({ id: 'objectives', label: '目的と現状', area: 'purpose', usesGraph: true, rail: true, source: '手元のGraph', mount: mountObjectives }),
|
|
294
|
+
Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', area: 'work', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('projects', createGraphProjectsView) }),
|
|
295
|
+
Object.freeze({ id: 'graph', label: '情報と関係', area: 'information', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('graph', createGraphRegistryView) }),
|
|
296
|
+
Object.freeze({ id: 'people', label: '人と会社', area: 'information', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('people', createPeopleAndCompaniesView, { canCorrect: true }) }),
|
|
260
297
|
]);
|
|
261
298
|
|
|
262
299
|
function renderGraphGate(doc, slot, status, onRecheck, label) {
|
|
@@ -373,7 +410,7 @@ export function createLocalWebShell({
|
|
|
373
410
|
};
|
|
374
411
|
menuButton.addEventListener('click', () => setMenuOpen(!sidebar.className.includes('is-open')));
|
|
375
412
|
backdrop.addEventListener('click', () => setMenuOpen(false));
|
|
376
|
-
|
|
413
|
+
const navLink = (screen) => {
|
|
377
414
|
const link = makeElement(doc, 'a', { className: 'bb-shell-nav-link', text: screen.label, attrs: { href: `#${screen.id}` } });
|
|
378
415
|
// The label stays the link's own text; the icon goes in front where the DOM allows it.
|
|
379
416
|
if (typeof link.prepend === 'function') link.prepend(makeIcon(doc, screen.id));
|
|
@@ -382,8 +419,22 @@ export function createLocalWebShell({
|
|
|
382
419
|
controller.show(screen.id);
|
|
383
420
|
});
|
|
384
421
|
links.set(screen.id, link);
|
|
385
|
-
|
|
422
|
+
return link;
|
|
423
|
+
};
|
|
424
|
+
const layout = navLayout(screens);
|
|
425
|
+
for (const screen of layout.leading) nav.append(navLink(screen));
|
|
426
|
+
for (const { area, screens: members } of layout.areas) {
|
|
427
|
+
if (!area.label) {
|
|
428
|
+
for (const screen of members) nav.append(navLink(screen));
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const headId = `bb-shell-nav-area-${area.id}`;
|
|
432
|
+
const group = makeElement(doc, 'div', { className: 'bb-shell-nav-group', attrs: { role: 'group', 'aria-labelledby': headId } });
|
|
433
|
+
group.append(makeElement(doc, 'div', { className: 'bb-shell-nav-group-head', text: area.label, attrs: { id: headId } }));
|
|
434
|
+
for (const screen of members) group.append(navLink(screen));
|
|
435
|
+
nav.append(group);
|
|
386
436
|
}
|
|
437
|
+
for (const screen of layout.trailing) nav.append(navLink(screen));
|
|
387
438
|
const source = makeElement(doc, 'section', { className: 'bb-shell-source', attrs: { 'aria-label': '出典' } });
|
|
388
439
|
sidebar.append(brand, nav, source);
|
|
389
440
|
const workspace = makeElement(doc, 'div', { className: 'bb-shell-workspace' });
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 「人と会社」. Load after workspace-kit.css and graph-view-shared.css.
|
|
3
|
+
* Column templates of the people and company ledgers, the group titles,
|
|
4
|
+
* the write buttons, the recorded company note and the relations in the
|
|
5
|
+
* right rail, with a 760px variant.
|
|
6
|
+
* Uses only the shared tokens in brainbase-tokens.css.
|
|
7
|
+
*/
|
|
8
|
+
.bb-pc-people,
|
|
9
|
+
.bb-pc-companies {
|
|
10
|
+
margin-bottom: 30px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.bb-graph .bb-pc-group-title {
|
|
14
|
+
margin: 22px 0 10px;
|
|
15
|
+
color: var(--bb-color-ink);
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.bb-graph .bb-pc-company-title {
|
|
20
|
+
margin: 14px 0 8px;
|
|
21
|
+
color: var(--bb-color-muted);
|
|
22
|
+
font-size: 12px;
|
|
23
|
+
font-weight: 650;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.bb-graph .bb-pc-empty,
|
|
27
|
+
.bb-graph .bb-pc-ended,
|
|
28
|
+
.bb-graph-rail .bb-pc-empty {
|
|
29
|
+
margin: 0 0 10px;
|
|
30
|
+
color: var(--bb-color-muted);
|
|
31
|
+
font-size: 12px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.bb-pc-people-ledger .bb-ws-ledger-row {
|
|
35
|
+
grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.bb-pc-company-ledger .bb-ws-ledger-row {
|
|
39
|
+
grid-template-columns: minmax(0, 1.4fr) minmax(0, .8fr) 84px minmax(0, 1fr) minmax(0, 1fr);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.bb-pc-relation-list {
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 0;
|
|
45
|
+
list-style: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.bb-pc-relation {
|
|
49
|
+
display: grid;
|
|
50
|
+
gap: 4px;
|
|
51
|
+
padding: 8px 0;
|
|
52
|
+
border-bottom: 1px solid var(--bb-color-border);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.bb-pc-relation.is-ended {
|
|
56
|
+
opacity: .62;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.bb-pc-relation-head {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 8px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.bb-pc-relation-meta {
|
|
66
|
+
color: var(--bb-color-muted);
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bb-graph-rail .bb-pc-fix {
|
|
71
|
+
display: grid;
|
|
72
|
+
gap: 6px;
|
|
73
|
+
margin: 16px 0 0;
|
|
74
|
+
color: var(--bb-color-muted);
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.bb-pc-actions,
|
|
79
|
+
.bb-pc-relation-actions {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
gap: 8px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.bb-pc-actions {
|
|
86
|
+
margin: 0 0 12px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.bb-pc-company-cell {
|
|
90
|
+
display: grid;
|
|
91
|
+
gap: 2px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* A note recorded as text on the person, never a membership: muted and dashed. */
|
|
95
|
+
.bb-pc-org-note {
|
|
96
|
+
justify-self: start;
|
|
97
|
+
padding: 1px 6px;
|
|
98
|
+
border: 1px dashed var(--bb-color-border);
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
color: var(--bb-color-muted);
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (max-width: 760px) {
|
|
105
|
+
.bb-pc-people-ledger .bb-ws-ledger-row {
|
|
106
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 84px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.bb-pc-people-ledger .bb-ws-ledger-row > :nth-child(4) {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.bb-pc-company-ledger .bb-ws-ledger-row {
|
|
114
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, .7fr) 64px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.bb-pc-company-ledger .bb-ws-ledger-row > :nth-child(4),
|
|
118
|
+
.bb-pc-company-ledger .bb-ws-ledger-row > :nth-child(5) {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
}
|