@unson/brainbase-mcp 0.22.3 → 0.22.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 +102 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.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",
|
|
@@ -407,5 +407,5 @@
|
|
|
407
407
|
"engines": {
|
|
408
408
|
"node": ">=20"
|
|
409
409
|
},
|
|
410
|
-
"gitHead": "
|
|
410
|
+
"gitHead": "8c472153bac9ae0d439f8275a2c640f46ae03699"
|
|
411
411
|
}
|
|
@@ -298,6 +298,9 @@ export function createGraphProjectsView({
|
|
|
298
298
|
let contextRequest = 0;
|
|
299
299
|
let detailRequest = 0;
|
|
300
300
|
let hostListRequest = 0;
|
|
301
|
+
// An explicitly selected project may be read in parallel with the host
|
|
302
|
+
// list, but the list remains authoritative for whether its detail is shown.
|
|
303
|
+
let initialSelectionGate = null;
|
|
301
304
|
let viewDestroyed = false;
|
|
302
305
|
|
|
303
306
|
const correction = createGraphCorrection({
|
|
@@ -415,7 +418,7 @@ export function createGraphProjectsView({
|
|
|
415
418
|
}
|
|
416
419
|
|
|
417
420
|
/** Reads supplementary sections independently of the Graph project detail. */
|
|
418
|
-
async function loadProjectContextFor(id, { keep = false } = {}) {
|
|
421
|
+
async function loadProjectContextFor(id, { keep = false, deferUntilHostList = null } = {}) {
|
|
419
422
|
if (viewDestroyed) return state.context;
|
|
420
423
|
if (typeof loadProjectContext !== 'function') {
|
|
421
424
|
if (state.context?.projectId !== id) state.context = null;
|
|
@@ -425,9 +428,16 @@ export function createGraphProjectsView({
|
|
|
425
428
|
const request = ++contextRequest;
|
|
426
429
|
state.context = normalizeProjectContext(id, { state: 'loading' });
|
|
427
430
|
controller.render();
|
|
428
|
-
const applyContext = (snapshot) => {
|
|
431
|
+
const applyContext = (snapshot, { flushDeferred = false } = {}) => {
|
|
429
432
|
const normalized = normalizeProjectContext(id, snapshot);
|
|
430
433
|
if (viewDestroyed || request !== contextRequest || state.selectedId !== id) return false;
|
|
434
|
+
if (deferUntilHostList && !deferUntilHostList.confirmed && !flushDeferred) {
|
|
435
|
+
if (!deferUntilHostList.cancelled && initialSelectionGate === deferUntilHostList) {
|
|
436
|
+
deferUntilHostList.pendingContext = normalized;
|
|
437
|
+
deferUntilHostList.flushContext = () => applyContext(normalized, { flushDeferred: true });
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
431
441
|
state.context = normalized;
|
|
432
442
|
// Context progress must not rebuild the workspace: rebuilding destroys
|
|
433
443
|
// Sigma's viewport and resets the user's active tab/selection. Detail
|
|
@@ -796,6 +806,37 @@ export function createGraphProjectsView({
|
|
|
796
806
|
return children;
|
|
797
807
|
}
|
|
798
808
|
|
|
809
|
+
function cancelInitialSelectionGate(gate, { reset = true } = {}) {
|
|
810
|
+
if (!gate || gate.cancelled || gate.confirmed) return;
|
|
811
|
+
gate.cancelled = true;
|
|
812
|
+
gate.pendingContext = null;
|
|
813
|
+
gate.pendingDetail = null;
|
|
814
|
+
gate.flushContext = null;
|
|
815
|
+
gate.flushDetail = null;
|
|
816
|
+
if (initialSelectionGate === gate) initialSelectionGate = null;
|
|
817
|
+
// A caller may have selected another project while this gate was pending.
|
|
818
|
+
// Do not invalidate that newer project's reads in that case.
|
|
819
|
+
if (!reset || state.selectedId !== gate.id) return;
|
|
820
|
+
detailRequest += 1;
|
|
821
|
+
contextRequest += 1;
|
|
822
|
+
if (state.detail?.id === gate.id && state.detail.state === 'loading') state.detail = null;
|
|
823
|
+
if (state.context?.projectId === gate.id && state.context.state === 'loading') state.context = null;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function confirmInitialSelectionGate(gate) {
|
|
827
|
+
if (!gate || gate.cancelled) return;
|
|
828
|
+
gate.confirmed = true;
|
|
829
|
+
if (initialSelectionGate === gate) initialSelectionGate = null;
|
|
830
|
+
// Responses which finished before the list are safe to expose now. A
|
|
831
|
+
// response still in flight will apply through its normal completion path.
|
|
832
|
+
gate.flushDetail?.();
|
|
833
|
+
gate.flushContext?.();
|
|
834
|
+
gate.pendingDetail = null;
|
|
835
|
+
gate.pendingContext = null;
|
|
836
|
+
gate.flushDetail = null;
|
|
837
|
+
gate.flushContext = null;
|
|
838
|
+
}
|
|
839
|
+
|
|
799
840
|
/** Reads the host's project list (see `load`). */
|
|
800
841
|
async function loadHostList({ keep = false } = {}) {
|
|
801
842
|
const request = ++hostListRequest;
|
|
@@ -804,14 +845,45 @@ export function createGraphProjectsView({
|
|
|
804
845
|
state.list = { state: 'loading' };
|
|
805
846
|
controller.render();
|
|
806
847
|
}
|
|
848
|
+
let selectionGate = null;
|
|
849
|
+
let selectionDetailReading = null;
|
|
850
|
+
const selectedBeforeList = state.selectedId;
|
|
851
|
+
if (selectedBeforeList) {
|
|
852
|
+
if (initialSelectionGate?.id === selectedBeforeList && !initialSelectionGate.cancelled && !initialSelectionGate.confirmed) {
|
|
853
|
+
selectionGate = initialSelectionGate;
|
|
854
|
+
selectionDetailReading = selectionGate.detailReading;
|
|
855
|
+
} else if (state.detail === null) {
|
|
856
|
+
selectionGate = {
|
|
857
|
+
id: selectedBeforeList,
|
|
858
|
+
confirmed: false,
|
|
859
|
+
cancelled: false,
|
|
860
|
+
pendingContext: null,
|
|
861
|
+
pendingDetail: null,
|
|
862
|
+
flushContext: null,
|
|
863
|
+
flushDetail: null,
|
|
864
|
+
detailReading: null,
|
|
865
|
+
};
|
|
866
|
+
initialSelectionGate = selectionGate;
|
|
867
|
+
selectionDetailReading = controller.loadDetail(selectedBeforeList, {
|
|
868
|
+
keep,
|
|
869
|
+
deferUntilHostList: selectionGate,
|
|
870
|
+
});
|
|
871
|
+
selectionGate.detailReading = selectionDetailReading;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
807
874
|
const result = await client.read('/projects');
|
|
808
875
|
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
809
876
|
state.list = normalizeProjectList(result);
|
|
810
877
|
if (state.list.state !== 'ok') {
|
|
878
|
+
cancelInitialSelectionGate(selectionGate);
|
|
811
879
|
controller.render();
|
|
812
880
|
return state.list;
|
|
813
881
|
}
|
|
814
882
|
const { projects } = state.list.payload;
|
|
883
|
+
if (selectionGate && (selectionGate.cancelled || state.selectedId !== selectionGate.id)) {
|
|
884
|
+
cancelInitialSelectionGate(selectionGate, { reset: false });
|
|
885
|
+
selectionGate = null;
|
|
886
|
+
}
|
|
815
887
|
if (!state.selectedId) {
|
|
816
888
|
if (correction.form) correction.close();
|
|
817
889
|
state.panelAt = null;
|
|
@@ -832,6 +904,7 @@ export function createGraphProjectsView({
|
|
|
832
904
|
// never be replaced with the first row when the refreshed list no
|
|
833
905
|
// longer contains it. Keeping the id makes the missing target visible
|
|
834
906
|
// and lets the host decide whether its identity or access changed.
|
|
907
|
+
cancelInitialSelectionGate(selectionGate);
|
|
835
908
|
if (correction.form) correction.close();
|
|
836
909
|
detailRequest += 1;
|
|
837
910
|
state.panelAt = null;
|
|
@@ -842,6 +915,11 @@ export function createGraphProjectsView({
|
|
|
842
915
|
};
|
|
843
916
|
state.context = null;
|
|
844
917
|
contextRequest += 1;
|
|
918
|
+
} else if (selectionGate && selectionGate.id === state.selectedId) {
|
|
919
|
+
confirmInitialSelectionGate(selectionGate);
|
|
920
|
+
if (selectionDetailReading) await selectionDetailReading;
|
|
921
|
+
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
922
|
+
return state.list;
|
|
845
923
|
} else if (state.detail?.id !== state.selectedId || state.detail?.state === 'not_found') {
|
|
846
924
|
// The project the host asked for is listed; read it for the rail.
|
|
847
925
|
if (viewDestroyed || request !== hostListRequest) return state.list;
|
|
@@ -876,6 +954,7 @@ export function createGraphProjectsView({
|
|
|
876
954
|
hostListRequest += 1;
|
|
877
955
|
detailRequest += 1;
|
|
878
956
|
contextRequest += 1;
|
|
957
|
+
initialSelectionGate = null;
|
|
879
958
|
knowledgeWorkspace?.destroy();
|
|
880
959
|
knowledgeWorkspace = null;
|
|
881
960
|
correction.close();
|
|
@@ -925,7 +1004,7 @@ export function createGraphProjectsView({
|
|
|
925
1004
|
return state.own.detail;
|
|
926
1005
|
},
|
|
927
1006
|
/** `keep` shows the current detail until the new one has been read. */
|
|
928
|
-
async loadDetail(id, { keep = false } = {}) {
|
|
1007
|
+
async loadDetail(id, { keep = false, deferUntilHostList = null } = {}) {
|
|
929
1008
|
if (viewDestroyed) return state.detail;
|
|
930
1009
|
const request = ++detailRequest;
|
|
931
1010
|
const previous = state.detail;
|
|
@@ -933,13 +1012,26 @@ export function createGraphProjectsView({
|
|
|
933
1012
|
state.detail = { id, state: 'loading' };
|
|
934
1013
|
controller.render();
|
|
935
1014
|
}
|
|
936
|
-
const contextReading = loadProjectContextFor(id, { keep });
|
|
1015
|
+
const contextReading = loadProjectContextFor(id, { keep, deferUntilHostList });
|
|
937
1016
|
const result = await client.read(`/projects/${encodeURIComponent(id)}`);
|
|
938
1017
|
if (viewDestroyed || request !== detailRequest || state.selectedId !== id) return state.detail;
|
|
939
|
-
|
|
1018
|
+
const detail = result.state === 'error' && result.status === 404
|
|
940
1019
|
? { id, state: 'not_found', reason: 'このプロジェクトは見つかりません。一覧を読み直してください。' }
|
|
941
1020
|
: { id, ...normalizeProjectDetail(result) };
|
|
942
|
-
|
|
1021
|
+
const applyDetail = () => {
|
|
1022
|
+
if (viewDestroyed || request !== detailRequest || state.selectedId !== id) return false;
|
|
1023
|
+
state.detail = detail;
|
|
1024
|
+
controller.render();
|
|
1025
|
+
return true;
|
|
1026
|
+
};
|
|
1027
|
+
if (deferUntilHostList && !deferUntilHostList.confirmed) {
|
|
1028
|
+
if (!deferUntilHostList.cancelled && initialSelectionGate === deferUntilHostList) {
|
|
1029
|
+
deferUntilHostList.pendingDetail = detail;
|
|
1030
|
+
deferUntilHostList.flushDetail = applyDetail;
|
|
1031
|
+
}
|
|
1032
|
+
return state.detail;
|
|
1033
|
+
}
|
|
1034
|
+
applyDetail();
|
|
943
1035
|
// The context is intentionally independent. Its completion may trigger
|
|
944
1036
|
// one more render, but never changes the Graph detail result.
|
|
945
1037
|
void contextReading;
|
|
@@ -947,6 +1039,10 @@ export function createGraphProjectsView({
|
|
|
947
1039
|
},
|
|
948
1040
|
/** Selects a row: the rail shows that project, and an open correction is closed. */
|
|
949
1041
|
async selectProject(id) {
|
|
1042
|
+
// A host selection supersedes a prefetched initial detail. Cancel the
|
|
1043
|
+
// gate before changing the id so A -> B -> A cannot make a later list
|
|
1044
|
+
// response wait for the first A read.
|
|
1045
|
+
cancelInitialSelectionGate(initialSelectionGate, { reset: false });
|
|
950
1046
|
if (correction.form) correction.close();
|
|
951
1047
|
state.panelAt = null;
|
|
952
1048
|
state.selectedId = id;
|