@unson/brainbase-mcp 0.15.1 → 0.15.3
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/canonical-task-service.d.ts +11 -7
- package/dist/canonical-task-service.js +49 -21
- package/dist/cli.js +39 -3
- package/dist/graph-web-http.d.ts +7 -0
- package/dist/graph-web-http.js +14 -0
- package/dist/graph-web.d.ts +36 -6
- package/dist/graph-web.js +45 -4
- package/dist/judgment-value-proof-review.d.ts +24 -0
- package/dist/judgment-value-proof-review.js +120 -9
- package/dist/local-web-host.d.ts +36 -0
- package/dist/local-web-host.js +26 -7
- package/dist/organization-graph-web.d.ts +80 -0
- package/dist/organization-graph-web.js +309 -0
- package/dist/value-proof-review-http.d.ts +5 -0
- package/dist/value-proof-review-http.js +19 -7
- package/dist/world-extension.d.ts +183 -0
- package/dist/world-extension.js +361 -0
- package/package.json +9 -3
- package/ui/graph-projects-view.js +11 -4
- package/ui/graph-registry-view.js +14 -4
- package/ui/graph-view-shared.css +2 -0
- package/ui/graph-view-shared.js +58 -3
- package/ui/local-web-shell.js +74 -12
- package/ui/value-proof-review.js +67 -7
- package/ui/world/world-placement.js +45 -0
- package/ui/world/world-vendor.LICENSE.txt +23 -0
- package/ui/world/world-vendor.js +4220 -0
- package/ui/world/world-view.css +234 -0
- package/ui/world/world-view.js +1092 -0
package/ui/local-web-shell.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createObjectiveEditorController } from './objective-editor.js';
|
|
|
21
21
|
import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
|
|
22
22
|
import { createValueProofReviewUI } from './value-proof-review.js';
|
|
23
23
|
import { createWorldModelView } from './world-model-view.js';
|
|
24
|
-
import { createGraphClient } from './graph-view-shared.js';
|
|
24
|
+
import { createGraphClient, organizationRecordAnchor, organizationRecordLink, organizationWebOrigin } from './graph-view-shared.js';
|
|
25
25
|
|
|
26
26
|
export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
|
|
27
27
|
|
|
@@ -43,6 +43,8 @@ const NAV_ICON_PATHS = Object.freeze({
|
|
|
43
43
|
objectives: ['M12 4 3 20h18z', 'M12 9v5m0 3h.01'],
|
|
44
44
|
projects: ['M3 6.5h7l2 2h9v10H3z', 'M3 6.5v-2h7l2 2'],
|
|
45
45
|
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'],
|
|
46
|
+
// World view (extension id `world`, ledger P17).
|
|
47
|
+
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'],
|
|
46
48
|
});
|
|
47
49
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
48
50
|
|
|
@@ -88,9 +90,15 @@ export function parseLocalWebTarget(target) {
|
|
|
88
90
|
if (!screenId) return { screenId: null, entityId: null };
|
|
89
91
|
const query = separator < 0 ? '' : raw.slice(separator + 1);
|
|
90
92
|
const params = new URLSearchParams(query);
|
|
93
|
+
// 今日 accepts one judgment to open (`decision` = decision_attempt_id) and
|
|
94
|
+
// プロジェクトと関係者 one project (`project` = Graph project id); other screens ignore them.
|
|
95
|
+
const decisionId = screenId === 'today' ? params.get('decision')?.trim() || null : null;
|
|
96
|
+
const projectId = screenId === 'projects' ? params.get('project')?.trim() || null : null;
|
|
91
97
|
return {
|
|
92
98
|
screenId,
|
|
93
99
|
entityId: screenId === 'graph' ? params.get('entity_id')?.trim() || null : null,
|
|
100
|
+
...(decisionId ? { decisionId } : {}),
|
|
101
|
+
...(projectId ? { projectId } : {}),
|
|
94
102
|
};
|
|
95
103
|
}
|
|
96
104
|
|
|
@@ -195,11 +203,58 @@ function mountObjectives(container, context) {
|
|
|
195
203
|
return { editor, worldModel };
|
|
196
204
|
}
|
|
197
205
|
|
|
198
|
-
|
|
206
|
+
const ORGANIZATION_READ_ONLY_NOTE = '組織のGraphを読み取り専用で表示しています。登録の訂正は組織版で行ってください。';
|
|
207
|
+
// What the owner needs before leaving for the organization web (Inspector UX-20261004-01): a link to this
|
|
208
|
+
// very record there, who may correct it, and what happens after saving.
|
|
209
|
+
export { organizationRecordLink };
|
|
210
|
+
const organizationReadOnlyNoteLinked = (doc, origin, screenId) => (record) => {
|
|
211
|
+
const target = organizationRecordLink(origin, screenId, record);
|
|
212
|
+
if (!target) return ORGANIZATION_READ_ONLY_NOTE;
|
|
213
|
+
const note = makeElement(doc, 'span');
|
|
214
|
+
note.append(
|
|
215
|
+
makeElement(doc, 'span', { text: [
|
|
216
|
+
'組織のGraphを読み取り専用で表示しています。ここでは直せません。訂正の手順:',
|
|
217
|
+
`① 下のリンク(上の「出典」にも同じリンクがあります)から、組織版の${target.label}でこの記録を開きます。ログインしていなければSlackでログインすると、そのままこの記録に戻ります。`,
|
|
218
|
+
'② 直せるのは、組織版での役割がgmかceoで、このプロジェクトが属する事業(事業そのものなら、その事業)を許可されている人です。直せるのはプロジェクトの記録(名前・別名・要約・期間・目的・状態)だけで、人物の記録と、関係者の追加・終了は組織版でもまだ直せません。',
|
|
219
|
+
'③ 保存すると、理由といっしょに組織のGraphへ書き込まれ、組織版がその場で読み戻して表示します。この画面には、1分ほどたってから再読み込みすると反映されます。',
|
|
220
|
+
'',
|
|
221
|
+
].join('\n') }),
|
|
222
|
+
organizationRecordAnchor(doc, target),
|
|
223
|
+
);
|
|
224
|
+
return note;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The 出典 notice of a screen reading the organization Graph. Once a record is selected its link opens
|
|
229
|
+
* that record in the organization web; before that, the screen there.
|
|
230
|
+
*/
|
|
231
|
+
const organizationSourceText = (doc, origin, screenId, sourceText) => (record) => {
|
|
232
|
+
const target = record ? organizationRecordLink(origin, screenId, record) : null;
|
|
233
|
+
const link = target
|
|
234
|
+
? organizationRecordAnchor(doc, target)
|
|
235
|
+
: makeElement(doc, 'a', {
|
|
236
|
+
text: '組織版で開いて訂正する',
|
|
237
|
+
attrs: { href: `${origin}/?screen=${encodeURIComponent(screenId)}`, target: '_blank', rel: 'noopener noreferrer' },
|
|
238
|
+
});
|
|
239
|
+
const source = makeElement(doc, 'span');
|
|
240
|
+
source.append(makeElement(doc, 'span', { text: `${sourceText} ` }), link);
|
|
241
|
+
return source;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
function mountGraphScreen(screenId, createView) {
|
|
199
245
|
return (container, context) => {
|
|
200
246
|
const viewRoot = makeElement(context.document, 'div');
|
|
201
247
|
makePage(container, context).append(viewRoot);
|
|
248
|
+
// Ledger C1: when the host reads the organization Graph, nothing here can be corrected.
|
|
249
|
+
const readOnly = context.organizationGraph?.status === 'connected';
|
|
250
|
+
const origin = readOnly ? organizationWebOrigin(context.organizationGraph) : null;
|
|
251
|
+
const sourceText = `組織のGraph(${context.organizationGraph?.server ?? '接続先不明'})を読み取り専用で表示しています。このMacのGraphではありません。`;
|
|
202
252
|
return createView({
|
|
253
|
+
...(readOnly ? {
|
|
254
|
+
canCorrect: false,
|
|
255
|
+
readOnlyNote: origin ? organizationReadOnlyNoteLinked(context.document, origin, screenId) : ORGANIZATION_READ_ONLY_NOTE,
|
|
256
|
+
sourceNotice: { label: '出典', text: origin ? organizationSourceText(context.document, origin, screenId, sourceText) : sourceText },
|
|
257
|
+
} : {}),
|
|
203
258
|
root: viewRoot,
|
|
204
259
|
rail: context.rail,
|
|
205
260
|
page: context.page,
|
|
@@ -214,8 +269,8 @@ function mountGraphScreen(createView) {
|
|
|
214
269
|
export const LOCAL_WEB_SCREENS = Object.freeze([
|
|
215
270
|
Object.freeze({ id: 'today', label: '今日', usesGraph: false, rail: true, source: '判断journal', mount: mountToday }),
|
|
216
271
|
Object.freeze({ id: 'objectives', label: '目的と現状', usesGraph: true, rail: true, source: '手元のGraph', mount: mountObjectives }),
|
|
217
|
-
Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen(createGraphProjectsView) }),
|
|
218
|
-
Object.freeze({ id: 'graph', label: '情報と関係', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen(createGraphRegistryView) }),
|
|
272
|
+
Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('projects', createGraphProjectsView) }),
|
|
273
|
+
Object.freeze({ id: 'graph', label: '情報と関係', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('graph', createGraphRegistryView) }),
|
|
219
274
|
]);
|
|
220
275
|
|
|
221
276
|
function renderGraphGate(doc, slot, status, onRecheck, label) {
|
|
@@ -279,7 +334,7 @@ function renderSource(doc, bar, status, onRecheck) {
|
|
|
279
334
|
bar.append(
|
|
280
335
|
fact('データ', data.data_dir),
|
|
281
336
|
fact('Graph', graphLabel, data.graph.status === 'ready' ? '' : 'is-warning'),
|
|
282
|
-
fact('組織のGraph', data.organization_graph?.status === 'connected' ? '
|
|
337
|
+
fact('組織のGraph', data.organization_graph?.status === 'connected' ? '読んでいます(読み取りのみ)' : '読んでいません'),
|
|
283
338
|
);
|
|
284
339
|
}
|
|
285
340
|
|
|
@@ -377,10 +432,10 @@ export function createLocalWebShell({
|
|
|
377
432
|
|
|
378
433
|
function ensureMounted(screen, entityId = pendingEntityIds.get(screen.id) ?? null) {
|
|
379
434
|
if (mounted.has(screen.id)) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
435
|
+
const view = mounted.get(screen.id);
|
|
436
|
+
if (entityId && screen.id === 'graph' && typeof view?.openEntity === 'function') void view.openEntity(entityId);
|
|
437
|
+
if (entityId && screen.id === 'today' && typeof view?.openDecision === 'function') view.openDecision(entityId);
|
|
438
|
+
if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);
|
|
384
439
|
return;
|
|
385
440
|
}
|
|
386
441
|
const slot = slots.get(screen.id);
|
|
@@ -391,8 +446,13 @@ export function createLocalWebShell({
|
|
|
391
446
|
slot.replaceChildren();
|
|
392
447
|
const rail = rails.get(screen.id) ?? null;
|
|
393
448
|
rail?.replaceChildren();
|
|
394
|
-
const
|
|
395
|
-
|
|
449
|
+
const organizationGraph = status.phase === 'ready' ? status.data.organization_graph ?? null : null;
|
|
450
|
+
const readsOrganization = organizationGraph?.status === 'connected' && (screen.id === 'projects' || screen.id === 'graph');
|
|
451
|
+
const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: readsOrganization ? '組織のGraph(読み取りのみ)' : screen.source ?? null });
|
|
452
|
+
const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId, organizationGraph })) ?? true;
|
|
453
|
+
mounted.set(screen.id, view);
|
|
454
|
+
if (entityId && screen.id === 'today' && typeof view?.openDecision === 'function') view.openDecision(entityId);
|
|
455
|
+
if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);
|
|
396
456
|
updateRail();
|
|
397
457
|
}
|
|
398
458
|
|
|
@@ -403,7 +463,9 @@ export function createLocalWebShell({
|
|
|
403
463
|
show(target) {
|
|
404
464
|
const parsed = parseLocalWebTarget(target);
|
|
405
465
|
const screen = screens.find((candidate) => candidate.id === parsed.screenId) ?? screens[0];
|
|
406
|
-
pendingEntityIds.set(screen.id, screen.id === 'graph' ? parsed.entityId
|
|
466
|
+
pendingEntityIds.set(screen.id, screen.id === 'graph' ? parsed.entityId
|
|
467
|
+
: screen.id === 'today' ? parsed.decisionId ?? null
|
|
468
|
+
: screen.id === 'projects' ? parsed.projectId ?? null : null);
|
|
407
469
|
active = screen.id;
|
|
408
470
|
for (const [screenId, slot] of slots) slot.hidden = screenId !== screen.id;
|
|
409
471
|
for (const [screenId, link] of links) {
|
package/ui/value-proof-review.js
CHANGED
|
@@ -87,6 +87,7 @@ const ANSWER_FINAL_NOTE = '記録した回答は、この画面では変更・
|
|
|
87
87
|
const ANSWER_RESUME_NOTE = '止まった作業は自動では再開しません。続きを進めるときは、AIにこの回答を伝えて頼み直してください。';
|
|
88
88
|
const ANSWER_REJECT_LABEL = 'どれも選ばない';
|
|
89
89
|
const ANSWER_KINDS = Object.freeze(['select', 'reject']);
|
|
90
|
+
const MOVED_ON_LABEL = '会話で先に進んだ';
|
|
90
91
|
const FEEDBACK_PURPOSE_NOTE = '評価は、実行後の振り返りとして記録します。人に戻した判断への回答や承認ではありません。';
|
|
91
92
|
const CONSULT_COPY_NOTE = '相談文をコピーするだけで、この画面からCodexへは送信しません。';
|
|
92
93
|
/** Layers the owner can pick for a correction. `delegation` comes from 「次回は聞く」. */
|
|
@@ -159,9 +160,26 @@ function normalizeAnswer(answer) {
|
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
/**
|
|
163
|
+
/** When the conversation moved on after the question, or null. */
|
|
164
|
+
function normalizeMovedOnAt(value) {
|
|
165
|
+
const at = text(value);
|
|
166
|
+
return at && !Number.isNaN(Date.parse(at)) ? at : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A judgment returned to the owner that still has no recorded answer and whose conversation is still at the question. */
|
|
163
170
|
function awaitsOwner(item) {
|
|
164
|
-
return item.section === 'needs_human' && !item.answer;
|
|
171
|
+
return item.section === 'needs_human' && !item.answer && !item.conversationMovedOnAt;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The section label, or how the owner's side of a returned judgment ended. */
|
|
175
|
+
function itemStateLabel(item) {
|
|
176
|
+
if (item.answer) return '回答済み';
|
|
177
|
+
return item.conversationMovedOnAt ? MOVED_ON_LABEL : SECTION_LABELS[item.section];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Shown instead of the answer controls: the owner answered in that conversation or it moved on. */
|
|
181
|
+
function movedOnNote(at) {
|
|
182
|
+
return `この問いのあと、同じ会話で次のやり取りがありました(${formatDate(at)})。会話の中で答えたか、話が先に進んだと考えられるので、この画面では回答を受け付けません。続きが必要なら、元の会話で頼んでください。`;
|
|
165
183
|
}
|
|
166
184
|
|
|
167
185
|
/** Normalize the host response. Anything unexpected becomes `invalid`, never an empty success. */
|
|
@@ -184,6 +202,7 @@ export function normalizeValueProofReviewHome(payload) {
|
|
|
184
202
|
proof: item.proof,
|
|
185
203
|
feedbackHistory: Array.isArray(item.feedback_history) ? item.feedback_history.filter(isRecord) : [],
|
|
186
204
|
answer: normalizeAnswer(item.answer),
|
|
205
|
+
conversationMovedOnAt: normalizeMovedOnAt(item.conversation_moved_on_at),
|
|
187
206
|
}));
|
|
188
207
|
}
|
|
189
208
|
const itemsByKey = new Map(SECTION_ORDER.flatMap((section) => sections[section].map((item) => [itemKey(item.proof), item])));
|
|
@@ -297,6 +316,15 @@ function rowOfItem(state, key) {
|
|
|
297
316
|
return state.home?.delegationMap?.rows.find((row) => row.itemKeys.includes(key)) ?? null;
|
|
298
317
|
}
|
|
299
318
|
|
|
319
|
+
function findItemByDecision(state, decisionAttemptId) {
|
|
320
|
+
if (state.home?.status !== 'available' || !decisionAttemptId) return null;
|
|
321
|
+
for (const section of [...SECTION_ORDER, 'other']) {
|
|
322
|
+
const found = state.home.sections[section].find((item) => item.proof.decision_attempt_id === decisionAttemptId);
|
|
323
|
+
if (found) return found;
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
300
328
|
function findItem(state, key) {
|
|
301
329
|
if (state.home?.status !== 'available' || !key) return null;
|
|
302
330
|
for (const section of [...SECTION_ORDER, 'other']) {
|
|
@@ -420,12 +448,16 @@ function renderFilters(doc, home, state, callbacks) {
|
|
|
420
448
|
|
|
421
449
|
function renderMetrics(doc, home) {
|
|
422
450
|
const map = home.delegationMap;
|
|
451
|
+
const excluded = [
|
|
452
|
+
home.answersRecordable && '回答済み',
|
|
453
|
+
home.sections.needs_human.some((item) => item.conversationMovedOnAt) && '会話で先に進んだもの',
|
|
454
|
+
].filter(Boolean);
|
|
423
455
|
return workspaceMetrics(doc, [
|
|
424
456
|
{ label: '保存済み', value: home.coverage.saved, note: '判断journalの記録' },
|
|
425
457
|
{
|
|
426
458
|
label: 'あなたの判断が必要',
|
|
427
459
|
value: home.sections.needs_human.filter(awaitsOwner).length,
|
|
428
|
-
note:
|
|
460
|
+
note: excluded.length > 0 ? `あなたに戻した判断(${excluded.join('と、')}を除く)` : 'あなたに戻した判断',
|
|
429
461
|
},
|
|
430
462
|
{ label: '聞かずに進めた', value: home.sections.continued.length, note: '聞かずに続行した判断' },
|
|
431
463
|
{ label: '評価済み', value: map.rated, note: `判断 ${map.judged}件のうち` },
|
|
@@ -701,7 +733,7 @@ function renderItemButton(doc, state, item, callbacks) {
|
|
|
701
733
|
makeElement(doc, 'span', {
|
|
702
734
|
className: 'bb-vpr-item-meta',
|
|
703
735
|
text: [
|
|
704
|
-
item
|
|
736
|
+
itemStateLabel(item),
|
|
705
737
|
formatDate(item.proof.recorded_at),
|
|
706
738
|
OUTCOME_LABELS[item.proof.outcome.status] ?? '成果不明',
|
|
707
739
|
FEEDBACK_LABELS[item.proof.feedback.status] ?? '評価不明',
|
|
@@ -884,15 +916,20 @@ function renderJudgmentDetail(doc, state, item, callbacks) {
|
|
|
884
916
|
const { proof } = item;
|
|
885
917
|
const detail = [];
|
|
886
918
|
const row = rowOfItem(state, itemKey(proof));
|
|
919
|
+
const proofKind = kindLabel(proof);
|
|
920
|
+
// Keep a judgment-kind detail aligned with the row that groups it.
|
|
921
|
+
const displayedKind = row?.source === 'judgment_kind'
|
|
922
|
+
? rowName(row)
|
|
923
|
+
: proofKind ? proofKind : row ? rowName(row) : null;
|
|
887
924
|
if (row) {
|
|
888
925
|
const back = workspaceButton(doc, { text: `← ${rowName(row)}`, variant: 'quiet', onClick: () => callbacks.onShowRow?.() });
|
|
889
926
|
back.className = `${back.className} bb-vpr-back`;
|
|
890
927
|
detail.push(back);
|
|
891
928
|
}
|
|
892
929
|
const head = workspaceRailHead(doc, {
|
|
893
|
-
kicker: item
|
|
930
|
+
kicker: itemStateLabel(item),
|
|
894
931
|
title: itemTitle(proof),
|
|
895
|
-
sub:
|
|
932
|
+
sub: displayedKind ? `判断の種類: ${displayedKind}` : null,
|
|
896
933
|
});
|
|
897
934
|
const heading = Array.from(head.children ?? []).find((child) => child.tagName === 'H2');
|
|
898
935
|
heading?.setAttribute('tabindex', '-1');
|
|
@@ -943,7 +980,8 @@ function renderJudgmentDetail(doc, state, item, callbacks) {
|
|
|
943
980
|
for (const option of options) list.append(makeElement(doc, 'li', { text: `${text(option.label) ?? option.id}: ${text(option.impact) ?? '影響の記録なし'}` }));
|
|
944
981
|
content.push(makeElement(doc, 'h4', { text: '選択肢と影響' }), list);
|
|
945
982
|
}
|
|
946
|
-
if (
|
|
983
|
+
if (item.conversationMovedOnAt && !item.answer) content.push(notice(doc, 'is-muted', movedOnNote(item.conversationMovedOnAt)));
|
|
984
|
+
else if (state.home?.answersRecordable && item.section === 'needs_human') content.push(renderAnswer(doc, item, state, callbacks));
|
|
947
985
|
else content.push(makeElement(doc, 'p', { className: 'bb-vpr-hint', text: HUMAN_DECISION_BOUNDARY_NOTE }));
|
|
948
986
|
detail.push(workspaceRailBlock(doc, { title: 'あなたに戻した理由', className: 'bb-vpr-human-decision', content }));
|
|
949
987
|
}
|
|
@@ -1367,8 +1405,25 @@ export function createValueProofReviewUI({
|
|
|
1367
1405
|
},
|
|
1368
1406
|
};
|
|
1369
1407
|
|
|
1408
|
+
/** Selects a judgment by decision_attempt_id; false when the loaded home does not have it. */
|
|
1409
|
+
const selectDecision = (decisionAttemptId) => {
|
|
1410
|
+
const item = findItemByDecision(state, decisionAttemptId);
|
|
1411
|
+
if (!item) return false;
|
|
1412
|
+
callbacks.onSelect(itemKey(item.proof));
|
|
1413
|
+
return true;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1370
1416
|
const controller = {
|
|
1371
1417
|
get state() { return state; },
|
|
1418
|
+
/**
|
|
1419
|
+
* Opens one judgment, as a link from another screen does. Before the home
|
|
1420
|
+
* has loaded, the request waits for the load; an unknown id selects nothing.
|
|
1421
|
+
*/
|
|
1422
|
+
openDecision(decisionAttemptId) {
|
|
1423
|
+
if (state.phase === 'ready') return selectDecision(decisionAttemptId);
|
|
1424
|
+
state.pendingDecisionId = decisionAttemptId;
|
|
1425
|
+
return false;
|
|
1426
|
+
},
|
|
1372
1427
|
render() {
|
|
1373
1428
|
renderValueProofReview(root, state, callbacks, { document: doc, unavailableNotice, rail, page });
|
|
1374
1429
|
return controller;
|
|
@@ -1393,6 +1448,11 @@ export function createValueProofReviewUI({
|
|
|
1393
1448
|
state.error = error instanceof Error ? error.message : 'request_failed';
|
|
1394
1449
|
}
|
|
1395
1450
|
controller.render();
|
|
1451
|
+
if (state.phase === 'ready' && state.pendingDecisionId) {
|
|
1452
|
+
const pending = state.pendingDecisionId;
|
|
1453
|
+
state.pendingDecisionId = null;
|
|
1454
|
+
selectDecision(pending);
|
|
1455
|
+
}
|
|
1396
1456
|
return state.home;
|
|
1397
1457
|
},
|
|
1398
1458
|
callbacks,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Where a judgment stands in the world (ledger D6 placement).
|
|
3
|
+
*
|
|
4
|
+
* The judgment Host records the repository a turn worked in (`project_code`,
|
|
5
|
+
* the session's repository name). A judgment is placed in a business only by
|
|
6
|
+
* a relation the organization Graph already states:
|
|
7
|
+
* 1. the business registers that repository in `repository_roots`, or
|
|
8
|
+
* 2. the business code equals the repository name.
|
|
9
|
+
* Anything else stays in the plaza with the reason. The place is where the
|
|
10
|
+
* work happened, which is not always what the judgment was about.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const WORLD_PLACEMENT_CONTRACT_VERSION = 'brainbase.world-placement.v0';
|
|
14
|
+
|
|
15
|
+
export const UNPLACED_REASON_TEXT = Object.freeze({
|
|
16
|
+
workspace_unrecorded: '作業した場所の記録が無い',
|
|
17
|
+
workspace_unregistered: '作業した場所が、どの事業にも登録されていない',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** @returns {{ business: object, basis: 'repository' | 'code' } | { business: null, reason: keyof UNPLACED_REASON_TEXT }} */
|
|
21
|
+
export function placeJudgment(workspace, businesses) {
|
|
22
|
+
if (typeof workspace !== 'string' || !workspace.trim()) return { business: null, reason: 'workspace_unrecorded' };
|
|
23
|
+
const name = workspace.trim();
|
|
24
|
+
const byRepository = businesses.find((business) => Array.isArray(business.repositories) && business.repositories.includes(name));
|
|
25
|
+
if (byRepository) return { business: byRepository, basis: 'repository' };
|
|
26
|
+
const byCode = businesses.find((business) => business.code === name);
|
|
27
|
+
if (byCode) return { business: byCode, basis: 'code' };
|
|
28
|
+
return { business: null, reason: 'workspace_unregistered' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Groups judgment places by business code; unplaced ones are listed with their reason. */
|
|
32
|
+
export function groupJudgmentPlaces(places, businesses) {
|
|
33
|
+
const byBusiness = new Map();
|
|
34
|
+
const unplaced = [];
|
|
35
|
+
for (const place of places) {
|
|
36
|
+
const result = placeJudgment(place.workspace, businesses);
|
|
37
|
+
if (result.business) {
|
|
38
|
+
if (!byBusiness.has(result.business.code)) byBusiness.set(result.business.code, []);
|
|
39
|
+
byBusiness.get(result.business.code).push({ ...place, basis: result.basis });
|
|
40
|
+
} else {
|
|
41
|
+
unplaced.push({ ...place, reason: result.reason });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { byBusiness, unplaced };
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
three 0.186.1
|
|
2
|
+
|
|
3
|
+
The MIT License
|
|
4
|
+
|
|
5
|
+
Copyright © 2010-2026 three.js authors
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
THE SOFTWARE.
|