@unson/brainbase-mcp 0.41.4 → 0.41.7
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/brainbase-tokens.css +6 -0
- package/ui/foundation-overview.js +30 -11
- package/ui/judgment-history.css +34 -0
- package/ui/judgment-history.js +154 -66
- package/ui/local-web-shell.js +4 -1
- package/ui/meeting-minutes.css +293 -60
- package/ui/meeting-minutes.js +555 -201
- package/ui/people-and-companies-view.css +143 -0
- package/ui/people-and-companies-view.js +87 -11
- package/ui/workspace-kit.css +43 -0
- package/ui/workspace-kit.js +131 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.7",
|
|
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",
|
|
@@ -483,5 +483,5 @@
|
|
|
483
483
|
"engines": {
|
|
484
484
|
"node": ">=20"
|
|
485
485
|
},
|
|
486
|
-
"gitHead": "
|
|
486
|
+
"gitHead": "218515bb85c16915c1e509f608f83aa06af3a5d1"
|
|
487
487
|
}
|
package/ui/brainbase-tokens.css
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
--bb-color-accent: #5b4bd8;
|
|
27
27
|
--bb-color-accent-soft: #efedff;
|
|
28
28
|
--bb-color-accent-strong: #4939b8;
|
|
29
|
+
/* Minutes screen remaps its accent to this indigo. Shared accent stays purple. */
|
|
30
|
+
--bb-color-brand-indigo: #0F2A43;
|
|
29
31
|
--bb-color-action: #5b4bd8;
|
|
30
32
|
--bb-color-action-hover: #4939b8;
|
|
31
33
|
--bb-color-action-text: #ffffff;
|
|
@@ -62,6 +64,10 @@
|
|
|
62
64
|
--bb-color-notice-control-border: #ffffff45;
|
|
63
65
|
--bb-color-inline-error: #9b3d3d;
|
|
64
66
|
--bb-color-record-missing: #80601c;
|
|
67
|
+
--bb-color-drawer-overlay: rgb(13 25 46 / 6%);
|
|
68
|
+
--bb-color-drawer-overlay-clear: rgb(13 25 46 / 0%);
|
|
69
|
+
--bb-shadow-drawer: -18px 0 48px rgb(13 25 46 / 10%);
|
|
70
|
+
--bb-shadow-popover: 0 8px 28px rgb(13 25 46 / 14%);
|
|
65
71
|
--bb-color-dialog-overlay: #0d192e66;
|
|
66
72
|
--bb-color-scrollbar-rgb: 36,50,75;
|
|
67
73
|
--bb-color-scrollbar-dark-rgb: 203,212,225;
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
workspaceNotice,
|
|
15
15
|
workspacePageHeader,
|
|
16
16
|
workspaceSectionTitle,
|
|
17
|
-
workspaceHelp, workspaceDialog, workspaceDisclosure, workspaceErrorCard,
|
|
17
|
+
workspaceHelp, workspaceDialog, workspaceDisclosure, workspaceErrorCard, workspaceLoadingCard, workspaceDismissNotification,
|
|
18
18
|
} from './workspace-kit.js';
|
|
19
19
|
|
|
20
20
|
export const FOUNDATION_OVERVIEW_CONTRACT_VERSION = 'foundation-overview.v1';
|
|
@@ -660,10 +660,25 @@ function renderModels(doc, catalog, viewState, callbacks, recordContexts) {
|
|
|
660
660
|
return section;
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
663
|
+
/** Keep one loading card across redraws so completion can leave visibly. */
|
|
664
|
+
function renderLoadingState(doc, root, viewState, loading, onLoading) {
|
|
665
|
+
if (onLoading) { onLoading(loading); return; }
|
|
666
|
+
if (loading) {
|
|
667
|
+
viewState.cancelLoadingDismiss?.();
|
|
668
|
+
viewState.cancelLoadingDismiss = null;
|
|
669
|
+
viewState.loadingCard ??= workspaceLoadingCard(doc);
|
|
670
|
+
}
|
|
671
|
+
const card = viewState.loadingCard;
|
|
672
|
+
if (!card) return;
|
|
673
|
+
const stack = viewState.loadingStack ??= makeElement(doc, 'aside', { className: 'bb-ws-error-stack', attrs: { 'aria-label': '読み込み状況' } });
|
|
674
|
+
stack.append(card); root.append(stack);
|
|
675
|
+
if (!loading && !viewState.cancelLoadingDismiss) {
|
|
676
|
+
const cancel = workspaceDismissNotification(card, () => {
|
|
677
|
+
if (viewState.loadingCard !== card) return;
|
|
678
|
+
viewState.loadingCard = null; viewState.cancelLoadingDismiss = null; viewState.loadingStack = null; stack.remove();
|
|
679
|
+
});
|
|
680
|
+
if (viewState.loadingCard === card) viewState.cancelLoadingDismiss = cancel;
|
|
681
|
+
}
|
|
667
682
|
}
|
|
668
683
|
|
|
669
684
|
function renderFailure(doc, state, callbacks) {
|
|
@@ -684,6 +699,7 @@ export function renderFoundationOverview(root, state, {
|
|
|
684
699
|
recordContexts,
|
|
685
700
|
partial = false,
|
|
686
701
|
onNotice,
|
|
702
|
+
onLoading,
|
|
687
703
|
} = {}) {
|
|
688
704
|
if (!root) throw new TypeError('root is required');
|
|
689
705
|
const doc = getDocument(explicitDocument);
|
|
@@ -705,14 +721,13 @@ export function renderFoundationOverview(root, state, {
|
|
|
705
721
|
else surface.append(workspaceErrorCard(doc, notice));
|
|
706
722
|
};
|
|
707
723
|
if (state.status === 'loading') {
|
|
708
|
-
|
|
724
|
+
root.append(surface); renderLoadingState(doc, root, viewState, true, onLoading); return surface;
|
|
709
725
|
}
|
|
710
726
|
if (state.status !== 'ready') {
|
|
711
727
|
notify({ title: '取得失敗', message: `${state.error?.message ?? '目的・哲学・世界モデルを確認できません。'} 登録なし・0件として表示していません。`, onRetry: callbacks.load });
|
|
712
|
-
root.append(surface); return surface;
|
|
728
|
+
root.append(surface); renderLoadingState(doc, root, viewState, false, onLoading); return surface;
|
|
713
729
|
}
|
|
714
730
|
const catalog = state.catalog;
|
|
715
|
-
if (partial) notify({ title: '取得済みの範囲', tone: 'warning', message: '以下の登録なし・検索結果は取得できた記録だけが対象です。全体は未確認です。' });
|
|
716
731
|
surface.append(renderObjectives(doc, catalog, viewState, callbacks, recordContexts));
|
|
717
732
|
const fold = (title, help, content, initialOpen) => {
|
|
718
733
|
const key = title === '哲学' ? 'philosophyOpen' : 'modelsOpen';
|
|
@@ -721,6 +736,7 @@ export function renderFoundationOverview(root, state, {
|
|
|
721
736
|
surface.append(fold('哲学', '判断の前提となる考え方です。登録された本文と、判断への適用範囲を分けて表示します。', renderPhilosophies(doc, catalog, recordContexts), true));
|
|
722
737
|
surface.append(fold('世界モデル', '何をすると結果が変わるか、という仮説。未検証・許可用途を分けて確認します。', renderModels(doc, catalog, viewState, callbacks, recordContexts), false));
|
|
723
738
|
root.append(surface);
|
|
739
|
+
renderLoadingState(doc, root, viewState, false, onLoading);
|
|
724
740
|
return surface;
|
|
725
741
|
}
|
|
726
742
|
|
|
@@ -731,7 +747,7 @@ function appendRecordSource(doc, card, record, contexts) {
|
|
|
731
747
|
|
|
732
748
|
/** Compose independently validated catalogs by topic, preserving each reference's source context. */
|
|
733
749
|
export function renderFoundationCollectionOverview(root, sources, {
|
|
734
|
-
document: explicitDocument, viewState = { query: '', page: 0 }, callbacks = {}, scopeLabel = '全体', onNotice,
|
|
750
|
+
document: explicitDocument, viewState = { query: '', page: 0 }, callbacks = {}, scopeLabel = '全体', onNotice, onLoading,
|
|
735
751
|
} = {}) {
|
|
736
752
|
const doc = getDocument(explicitDocument);
|
|
737
753
|
root.replaceChildren();
|
|
@@ -742,7 +758,7 @@ export function renderFoundationCollectionOverview(root, sources, {
|
|
|
742
758
|
for (const source of sources) {
|
|
743
759
|
if (source.state.status !== 'ready') {
|
|
744
760
|
if (source.state.status === 'loading') {
|
|
745
|
-
|
|
761
|
+
// A single shared loading card is rendered after the available catalogs.
|
|
746
762
|
} else {
|
|
747
763
|
const notice = { title: source.label, message: `取得失敗: ${source.state.error?.message ?? 'この対象は未確認です。0件ではありません。'}`, onRetry: callbacks.load };
|
|
748
764
|
if (onNotice) onNotice(notice); else root.append(workspaceErrorCard(doc, notice));
|
|
@@ -778,10 +794,11 @@ export function renderFoundationCollectionOverview(root, sources, {
|
|
|
778
794
|
const mount = makeElement(doc, 'div');
|
|
779
795
|
renderFoundationOverview(mount, { status: 'ready', catalog }, {
|
|
780
796
|
document: doc, scopeId: scopeLabel, showHeader: false, viewState, callbacks,
|
|
781
|
-
recordContexts: contexts, partial: ready !== sources.length, onNotice,
|
|
797
|
+
recordContexts: contexts, partial: ready !== sources.length, onNotice, onLoading: () => {},
|
|
782
798
|
});
|
|
783
799
|
root.append(mount);
|
|
784
800
|
}
|
|
801
|
+
renderLoadingState(doc, root, viewState, sources.some(source => source.state.status === 'loading'), onLoading);
|
|
785
802
|
return root;
|
|
786
803
|
}
|
|
787
804
|
|
|
@@ -841,6 +858,8 @@ export function createFoundationOverview({ root, port, scopeId, document: explic
|
|
|
841
858
|
destroyed = true;
|
|
842
859
|
generation += 1;
|
|
843
860
|
viewState.dialog?.close();
|
|
861
|
+
viewState.cancelLoadingDismiss?.();
|
|
862
|
+
viewState.loadingCard = null;
|
|
844
863
|
root.replaceChildren();
|
|
845
864
|
},
|
|
846
865
|
};
|
package/ui/judgment-history.css
CHANGED
|
@@ -583,3 +583,37 @@
|
|
|
583
583
|
.bb-jh-ledger .bb-ws-ledger-row > span:nth-child(3)::before { content: '使った参照'; }
|
|
584
584
|
.bb-jh-ledger .bb-ws-ledger-row > span:nth-child(4)::before { content: '入口・状態'; }
|
|
585
585
|
}
|
|
586
|
+
|
|
587
|
+
/* Compact workspace header, search controls and shared floating notifications. */
|
|
588
|
+
.bb-jh { position: relative; }
|
|
589
|
+
.bb-jh > .bb-ws-page { margin-bottom: var(--bb-space-6, 32px); }
|
|
590
|
+
.bb-jh .bb-ws-page-head h1 { font-size: 24px; line-height: 1.4; font-weight: 600; letter-spacing: .02em; }
|
|
591
|
+
.bb-jh-controls { margin-bottom: var(--bb-space-4, 16px); }
|
|
592
|
+
.bb-jh-checked { font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
593
|
+
.bb-jh-filter-fields { display: grid; gap: 16px; }
|
|
594
|
+
.bb-jh-filter-fields .bb-jh-periods { flex-wrap: wrap; gap: 6px; }
|
|
595
|
+
.bb-jh-filter-fields .bb-jh-normal-filters { display: grid; flex: none; gap: 16px; }
|
|
596
|
+
.bb-jh-filter-fields .bb-jh-filter { display: grid; gap: 6px; }
|
|
597
|
+
.bb-jh-filter-fields select { width: 100%; }
|
|
598
|
+
.bb-jh-refresh { display: grid; place-items: center; flex: none; width: 32px; height: 32px; padding: 6px; }
|
|
599
|
+
.bb-jh-refresh-mark { display: block; width: 16px; height: 16px; }
|
|
600
|
+
.bb-jh-notifications { display: grid; gap: 8px; }
|
|
601
|
+
.bb-jh-notifications .bb-jh-banner { margin: 0; }
|
|
602
|
+
.bb-jh-notifications .bb-jh-banner-sources { display: grid; gap: 4px; margin-top: 8px; }
|
|
603
|
+
.bb-jh-notifications .bb-jh-source-detail { margin: 8px 0 0; }
|
|
604
|
+
.bb-jh-notifications .bb-jh-source-detail summary { color: inherit; }
|
|
605
|
+
.bb-jh-notifications .bb-jh-source-detail-body { position: static; max-width: none; background: transparent; border-color: var(--bb-color-notice-border); color: inherit; }
|
|
606
|
+
.bb-jh-detail .bb-ws-rail-head { margin-top: 0; }
|
|
607
|
+
@media (max-width: 760px) {
|
|
608
|
+
.bb-jh-controls { flex-direction: row; flex-wrap: wrap; align-items: center; }
|
|
609
|
+
.bb-jh-search { flex: 1 1 150px; }
|
|
610
|
+
.bb-jh-toolbar-status { margin-left: auto; }
|
|
611
|
+
}
|
|
612
|
+
.bb-jh-notifications .bb-jh-banner {
|
|
613
|
+
border-color: var(--bb-color-notice-border);
|
|
614
|
+
background: var(--bb-color-notice-background);
|
|
615
|
+
color: var(--bb-color-notice-text);
|
|
616
|
+
border-left-color: var(--bb-color-notice-warning);
|
|
617
|
+
}
|
|
618
|
+
.bb-jh-notifications .bb-jh-banner.is-danger { border-left-color: var(--bb-color-notice-danger); }
|
|
619
|
+
.bb-jh-notifications .bb-jh-banner-sources > span { white-space: normal; }
|
package/ui/judgment-history.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
import {
|
|
12
12
|
makeWorkspaceElement,
|
|
13
13
|
workspaceButton,
|
|
14
|
+
workspaceDialog,
|
|
15
|
+
workspaceFilterPopover,
|
|
16
|
+
workspaceErrorCard,
|
|
17
|
+
workspaceLoadingCard,
|
|
18
|
+
workspaceDismissNotification,
|
|
14
19
|
workspaceDefinition,
|
|
15
20
|
workspaceDetailEmpty,
|
|
16
21
|
workspaceLedger,
|
|
@@ -1278,14 +1283,16 @@ function sourceChips(doc, sources) {
|
|
|
1278
1283
|
}
|
|
1279
1284
|
|
|
1280
1285
|
function historyBanner(doc, { label, tone, role, sources, home, note, reload }) {
|
|
1281
|
-
const banner =
|
|
1282
|
-
|
|
1283
|
-
|
|
1286
|
+
const banner = workspaceErrorCard(doc, {
|
|
1287
|
+
title: label,
|
|
1288
|
+
tone,
|
|
1289
|
+
message: note || '取得できた記録だけを表示しています。全体の取得状態は通知の詳細で確認できます。',
|
|
1284
1290
|
});
|
|
1285
|
-
banner.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if (
|
|
1291
|
+
banner.className += ` bb-jh-banner is-${tone}`;
|
|
1292
|
+
banner.setAttribute('role', role ?? 'status');
|
|
1293
|
+
const details = findFirst(banner, node => node.tagName === 'DETAILS');
|
|
1294
|
+
if (sources?.length) details.append(sourceChips(doc, sources));
|
|
1295
|
+
if (home) details.append(sourceDetail(doc, home));
|
|
1289
1296
|
if (reload) banner.append(reload);
|
|
1290
1297
|
return banner;
|
|
1291
1298
|
}
|
|
@@ -1315,30 +1322,7 @@ function statusNotice(doc, phase, error, home, onRetry, { loading = false, slow
|
|
|
1315
1322
|
});
|
|
1316
1323
|
}
|
|
1317
1324
|
if (loading) {
|
|
1318
|
-
|
|
1319
|
-
const counts = new Map();
|
|
1320
|
-
for (const record of progressHome?.records ?? []) {
|
|
1321
|
-
const key = normalEntrypoint(record.entrypoint);
|
|
1322
|
-
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1323
|
-
}
|
|
1324
|
-
const known = Array.isArray(progressHome?.coverage?.sources) && progressHome.coverage.sources.length
|
|
1325
|
-
? progressHome.coverage.sources
|
|
1326
|
-
: NORMAL_ENTRYPOINTS.filter((entrypoint) => entrypoint !== 'unknown').map((entrypoint) => ({ entrypoint, status: 'pending', reason: null }));
|
|
1327
|
-
const sources = known.map((source) => {
|
|
1328
|
-
const count = counts.get(normalEntrypoint(source.entrypoint)) ?? 0;
|
|
1329
|
-
if (count > 0) return { entrypoint: source.entrypoint, status: 'partial', retrieved: count, reason: null };
|
|
1330
|
-
if (source.status === 'unavailable') return { entrypoint: source.entrypoint, status: 'unavailable', reason: source.reason ?? null };
|
|
1331
|
-
return { entrypoint: source.entrypoint, status: 'pending', reason: null };
|
|
1332
|
-
});
|
|
1333
|
-
return historyBanner(doc, {
|
|
1334
|
-
label: '読み込み中',
|
|
1335
|
-
tone: 'info',
|
|
1336
|
-
role: 'status',
|
|
1337
|
-
sources,
|
|
1338
|
-
note: slow
|
|
1339
|
-
? '時間がかかっています。届いた分だけ先に見られます。'
|
|
1340
|
-
: '取得元ごとに届いた順に表示します',
|
|
1341
|
-
});
|
|
1325
|
+
return workspaceLoadingCard(doc, { title: 'ロード中' });
|
|
1342
1326
|
}
|
|
1343
1327
|
if (home?.status === 'partial') {
|
|
1344
1328
|
return historyBanner(doc, {
|
|
@@ -1522,12 +1506,6 @@ function summaryLine(doc, aggregate, { loading = false } = {}) {
|
|
|
1522
1506
|
line.append(makeWorkspaceElement(doc, 'span', { className: 'bb-jh-skeleton is-inline', attrs: { 'aria-hidden': 'true' } }));
|
|
1523
1507
|
line.append(makeWorkspaceElement(doc, 'span', { text: ' 取得済み' }));
|
|
1524
1508
|
}
|
|
1525
|
-
if (!complete) {
|
|
1526
|
-
line.append(makeWorkspaceElement(doc, 'span', {
|
|
1527
|
-
className: 'bb-jh-summary-note',
|
|
1528
|
-
text: loading ? '(読み込み中・全件数は未確認)' : '(全件数は未確認)',
|
|
1529
|
-
}));
|
|
1530
|
-
}
|
|
1531
1509
|
const references = loading ? null : (aggregate?.stats?.referenceCount ?? aggregate?.stats?.references);
|
|
1532
1510
|
const confirmed = loading ? null : (aggregate?.stats?.confirmedOutcomes ?? aggregate?.stats?.outcomes);
|
|
1533
1511
|
line.append(makeWorkspaceElement(doc, 'span', { text: ' ・ 使った参照 ' }));
|
|
@@ -1578,6 +1556,7 @@ export function createJudgmentHistoryUI({
|
|
|
1578
1556
|
page,
|
|
1579
1557
|
autoLoad = true,
|
|
1580
1558
|
pageHeader = true,
|
|
1559
|
+
notificationRoot = null,
|
|
1581
1560
|
now = new Date(),
|
|
1582
1561
|
skeletonDelayMs = 300,
|
|
1583
1562
|
slowDelayMs = 10000,
|
|
@@ -1621,6 +1600,52 @@ export function createJudgmentHistoryUI({
|
|
|
1621
1600
|
slowTimer: null,
|
|
1622
1601
|
};
|
|
1623
1602
|
|
|
1603
|
+
let filterPopover = null;
|
|
1604
|
+
let filterExpanded = false;
|
|
1605
|
+
let drawer = null;
|
|
1606
|
+
let drawerContent = null;
|
|
1607
|
+
let drawerKey = null;
|
|
1608
|
+
let suppressDrawerClose = false;
|
|
1609
|
+
function disposeDrawer() {
|
|
1610
|
+
suppressDrawerClose = true;
|
|
1611
|
+
drawer?.dispose();
|
|
1612
|
+
drawer = null; drawerContent = null; drawerKey = null;
|
|
1613
|
+
suppressDrawerClose = false;
|
|
1614
|
+
}
|
|
1615
|
+
const nativeDrawer = Boolean(doc.body && typeof doc.createElement('dialog').showModal === 'function');
|
|
1616
|
+
const previousRailHidden = rail?.hidden;
|
|
1617
|
+
if (nativeDrawer && rail) rail.hidden = true;
|
|
1618
|
+
const notifications = makeWorkspaceElement(doc, 'div', {
|
|
1619
|
+
className: 'bb-ws-error-stack bb-jh-notifications', attrs: { 'aria-label': '判断履歴の通知' },
|
|
1620
|
+
});
|
|
1621
|
+
if (notificationRoot) notificationRoot.append(notifications);
|
|
1622
|
+
let loadingCard = null;
|
|
1623
|
+
let dismissLoading = null;
|
|
1624
|
+
let statusCard = null;
|
|
1625
|
+
let statusKey = null;
|
|
1626
|
+
|
|
1627
|
+
function updateNotifications(loading) {
|
|
1628
|
+
if (loading) {
|
|
1629
|
+
dismissLoading?.(); dismissLoading = null;
|
|
1630
|
+
if (!loadingCard) loadingCard = workspaceLoadingCard(doc, { title: 'ロード中' });
|
|
1631
|
+
if (loadingCard.parentNode !== notifications) notifications.append(loadingCard);
|
|
1632
|
+
} else if (loadingCard && !dismissLoading) {
|
|
1633
|
+
dismissLoading = workspaceDismissNotification(loadingCard);
|
|
1634
|
+
}
|
|
1635
|
+
const home = state.home;
|
|
1636
|
+
const key = loading ? null : JSON.stringify([state.phase, state.error, home?.status, home?.coverage]);
|
|
1637
|
+
if (statusKey !== key) {
|
|
1638
|
+
statusCard?.remove?.();
|
|
1639
|
+
// Small host DOM adapters may not implement Element.remove().
|
|
1640
|
+
if (statusCard?.parentNode === notifications && typeof statusCard.remove !== 'function') {
|
|
1641
|
+
notifications.replaceChildren(...notifications.children.filter(node => node !== statusCard));
|
|
1642
|
+
}
|
|
1643
|
+
statusKey = key;
|
|
1644
|
+
statusCard = loading ? null : statusNotice(doc, state.phase, state.error, home, () => void load());
|
|
1645
|
+
if (statusCard) notifications.append(statusCard);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1624
1649
|
function clearLoadTimers() {
|
|
1625
1650
|
if (state.skeletonTimer !== null) {
|
|
1626
1651
|
clearTimeout(state.skeletonTimer);
|
|
@@ -1664,12 +1689,13 @@ export function createJudgmentHistoryUI({
|
|
|
1664
1689
|
state.selectedKey = null;
|
|
1665
1690
|
state.detailPhase = 'idle';
|
|
1666
1691
|
state.detailError = null;
|
|
1692
|
+
if (drawer) drawer.close();
|
|
1667
1693
|
render();
|
|
1668
1694
|
return true;
|
|
1669
1695
|
}
|
|
1670
1696
|
|
|
1671
1697
|
function handleKeyDown(event) {
|
|
1672
|
-
if (event?.key === 'Escape') closeSelected();
|
|
1698
|
+
if (event?.key === 'Escape' && !drawer) closeSelected();
|
|
1673
1699
|
}
|
|
1674
1700
|
|
|
1675
1701
|
root.addEventListener?.('keydown', handleKeyDown);
|
|
@@ -1908,7 +1934,18 @@ export function createJudgmentHistoryUI({
|
|
|
1908
1934
|
|
|
1909
1935
|
function render() {
|
|
1910
1936
|
if (state.disposed) return;
|
|
1911
|
-
|
|
1937
|
+
updateNotifications(state.phase === 'loading');
|
|
1938
|
+
if (state.phase === 'loading' && !state.showSkeleton && !state.arrived) {
|
|
1939
|
+
if (!notificationRoot && notifications.parentNode !== root) root.append(notifications);
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
const focusedFilter = filterExpanded && filterPopover
|
|
1943
|
+
? findFirst(filterPopover.panel, node => node === doc.activeElement) : null;
|
|
1944
|
+
const filterFocus = focusedFilter && {
|
|
1945
|
+
label: attributeValue(focusedFilter, 'aria-label'), period: attributeValue(focusedFilter, 'data-period'),
|
|
1946
|
+
};
|
|
1947
|
+
filterPopover?.destroy();
|
|
1948
|
+
filterPopover = null;
|
|
1912
1949
|
const previousSearchInput = findFirst(root, (node) => node.tagName === 'INPUT');
|
|
1913
1950
|
const previousSearchSelection = previousSearchInput && Number.isInteger(previousSearchInput.selectionStart)
|
|
1914
1951
|
? { start: previousSearchInput.selectionStart, end: previousSearchInput.selectionEnd ?? previousSearchInput.selectionStart }
|
|
@@ -1936,21 +1973,18 @@ export function createJudgmentHistoryUI({
|
|
|
1936
1973
|
source: context.source,
|
|
1937
1974
|
}));
|
|
1938
1975
|
}
|
|
1939
|
-
|
|
1940
|
-
loading,
|
|
1941
|
-
slow: state.slow,
|
|
1942
|
-
arrived: state.arrived,
|
|
1943
|
-
});
|
|
1944
|
-
if (notice) wrapper.append(notice);
|
|
1976
|
+
if (!notificationRoot) wrapper.append(notifications);
|
|
1945
1977
|
const failed = state.phase === 'error' || state.home?.status === 'unavailable' || state.home?.status === 'error' || state.home?.status === 'invalid';
|
|
1946
1978
|
if (failed) {
|
|
1947
1979
|
wrapper.append(workspaceDetailEmpty(doc, { mark: '…', title: '判断履歴を確認できません', text: '再試行すると最新の状態を確認します。' }));
|
|
1948
1980
|
root.replaceChildren(wrapper);
|
|
1949
|
-
|
|
1981
|
+
if (drawer) disposeDrawer();
|
|
1982
|
+
if (!nativeDrawer) renderRail(doc, rail, null, null);
|
|
1950
1983
|
return;
|
|
1951
1984
|
}
|
|
1952
1985
|
const controls = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-controls' });
|
|
1953
|
-
|
|
1986
|
+
const filterFields = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-filter-fields' });
|
|
1987
|
+
filterFields.append(periodButtons(doc, state.period, (period) => {
|
|
1954
1988
|
state.period = period;
|
|
1955
1989
|
state.selectedKey = null;
|
|
1956
1990
|
state.cursor = null;
|
|
@@ -1961,7 +1995,7 @@ export function createJudgmentHistoryUI({
|
|
|
1961
1995
|
}
|
|
1962
1996
|
}));
|
|
1963
1997
|
const showNormalFilters = loading ? sourceHome?.mode !== 'legacy' : normalMode();
|
|
1964
|
-
if (showNormalFilters)
|
|
1998
|
+
if (showNormalFilters) filterFields.append(normalFilterControls(doc, sourceHome ?? state.home, state, handleNormalFilterChange));
|
|
1965
1999
|
controls.append(createSearch(doc, state.query, (query) => {
|
|
1966
2000
|
state.query = String(query);
|
|
1967
2001
|
state.focusSearch = true;
|
|
@@ -1970,28 +2004,41 @@ export function createJudgmentHistoryUI({
|
|
|
1970
2004
|
if (searchable) state.aggregate = aggregateJudgmentHistory(searchable, { now: readNow(), period: state.period, query: state.query });
|
|
1971
2005
|
render();
|
|
1972
2006
|
}));
|
|
2007
|
+
filterPopover = workspaceFilterPopover(doc, {
|
|
2008
|
+
content: filterFields, open: filterExpanded,
|
|
2009
|
+
onToggle: value => { filterExpanded = value; },
|
|
2010
|
+
});
|
|
2011
|
+
controls.append(filterPopover.root);
|
|
1973
2012
|
const toolbarStatus = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-toolbar-status' });
|
|
1974
|
-
if (
|
|
1975
|
-
toolbarStatus.append(makeWorkspaceElement(doc, 'span', {
|
|
1976
|
-
className: 'bb-jh-checked',
|
|
1977
|
-
text: '確認しています…',
|
|
1978
|
-
attrs: { role: 'status' },
|
|
1979
|
-
}));
|
|
1980
|
-
} else if (state.checkedAt) {
|
|
2013
|
+
if (state.checkedAt) {
|
|
1981
2014
|
const stamp = formatCheckedAt(state.checkedAt);
|
|
1982
|
-
if (stamp) toolbarStatus.append(makeWorkspaceElement(doc, 'span', {
|
|
2015
|
+
if (stamp) toolbarStatus.append(makeWorkspaceElement(doc, 'span', {
|
|
2016
|
+
className: 'bb-jh-checked', text: stamp, attrs: { 'aria-label': `${stamp}に確認` },
|
|
2017
|
+
}));
|
|
1983
2018
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2019
|
+
const reloadButton = workspaceButton(doc, {
|
|
2020
|
+
variant: 'quiet', disabled: loading, onClick: () => void load(),
|
|
2021
|
+
attrs: { 'aria-label': '判断履歴を再読み込み', title: '再読み込み', 'data-action': 'reload' },
|
|
2022
|
+
});
|
|
2023
|
+
reloadButton.className += ' bb-jh-refresh';
|
|
2024
|
+
// Text-independent vector keeps the refresh control available in standalone hosts.
|
|
2025
|
+
const refreshMark = doc.createElementNS?.('http://www.w3.org/2000/svg', 'svg')
|
|
2026
|
+
?? makeWorkspaceElement(doc, 'span', { text: '↻' });
|
|
2027
|
+
refreshMark.setAttribute('class', 'bb-jh-refresh-mark'); refreshMark.setAttribute('aria-hidden', 'true');
|
|
2028
|
+
if (doc.createElementNS) {
|
|
2029
|
+
refreshMark.setAttribute('viewBox', '0 0 24 24'); refreshMark.setAttribute('fill', 'none');
|
|
2030
|
+
refreshMark.setAttribute('stroke', 'currentColor'); refreshMark.setAttribute('stroke-width', '1.7');
|
|
2031
|
+
refreshMark.setAttribute('stroke-linecap', 'round'); refreshMark.setAttribute('stroke-linejoin', 'round');
|
|
2032
|
+
const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
2033
|
+
path.setAttribute('d', 'M20 7v5h-5M4 17v-5h5M6.1 6.1a8 8 0 0 1 13.2 3.2M4.7 14.7a8 8 0 0 0 13.2 3.2');
|
|
2034
|
+
refreshMark.append(path);
|
|
2035
|
+
}
|
|
2036
|
+
reloadButton.append(refreshMark);
|
|
2037
|
+
toolbarStatus.append(reloadButton);
|
|
1991
2038
|
controls.append(toolbarStatus);
|
|
1992
2039
|
wrapper.append(controls);
|
|
1993
2040
|
wrapper.append(summaryLine(doc, aggregate, { loading }));
|
|
1994
|
-
const emptyText = state.home?.status === 'partial' || (normalMode() && !aggregate.countComplete)
|
|
2041
|
+
const emptyText = loading ? '' : state.home?.status === 'partial' || (normalMode() && !aggregate.countComplete)
|
|
1995
2042
|
? 'この期間に取得できた判断履歴はありません。全件数が未確認のため0件とは確認できません。'
|
|
1996
2043
|
: 'この期間の判断履歴はありません。';
|
|
1997
2044
|
const dataRows = makeRows(doc, aggregate, state.selectedKey, (key) => {
|
|
@@ -2023,6 +2070,12 @@ export function createJudgmentHistoryUI({
|
|
|
2023
2070
|
replaceNode(nextSearchInput, previousSearchInput);
|
|
2024
2071
|
}
|
|
2025
2072
|
root.replaceChildren(wrapper);
|
|
2073
|
+
if (filterExpanded && filterFocus) {
|
|
2074
|
+
const field = findFirst(filterPopover.panel, node => filterFocus.label
|
|
2075
|
+
? attributeValue(node, 'aria-label') === filterFocus.label
|
|
2076
|
+
: filterFocus.period && attributeValue(node, 'data-period') === filterFocus.period);
|
|
2077
|
+
field?.focus?.();
|
|
2078
|
+
}
|
|
2026
2079
|
if (state.focusSearch || previousSearchFocused) {
|
|
2027
2080
|
const input = findFirst(root, (node) => node.tagName === 'INPUT');
|
|
2028
2081
|
input?.focus?.();
|
|
@@ -2048,8 +2101,38 @@ export function createJudgmentHistoryUI({
|
|
|
2048
2101
|
onCopyReference: (payload) => void copyReference(payload),
|
|
2049
2102
|
}
|
|
2050
2103
|
: {};
|
|
2051
|
-
|
|
2052
|
-
|
|
2104
|
+
if (nativeDrawer) {
|
|
2105
|
+
if (selected) {
|
|
2106
|
+
if (!drawer || drawerKey !== selected.key) {
|
|
2107
|
+
if (drawer) disposeDrawer();
|
|
2108
|
+
drawerKey = selected.key;
|
|
2109
|
+
drawerContent = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-detail' });
|
|
2110
|
+
const selectedKey = selected.key;
|
|
2111
|
+
drawer = workspaceDialog(doc, {
|
|
2112
|
+
title: '判断の詳細', content: drawerContent, presentation: 'drawer',
|
|
2113
|
+
opener: () => findFirst(root, node => attributeValue(node, 'data-key') === selectedKey),
|
|
2114
|
+
onClose: () => {
|
|
2115
|
+
if (suppressDrawerClose) return;
|
|
2116
|
+
drawer = null; drawerContent = null; drawerKey = null;
|
|
2117
|
+
if (state.disposed) return;
|
|
2118
|
+
state.selectedKey = null; state.detailPhase = 'idle'; state.detailError = null;
|
|
2119
|
+
state.lastFocusedKey = selectedKey; render();
|
|
2120
|
+
},
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
const detailFocus = findFirst(drawerContent, node => node === doc.activeElement);
|
|
2124
|
+
const detailLabel = attributeValue(detailFocus, 'aria-label');
|
|
2125
|
+
const detailSelection = Number.isInteger(detailFocus?.selectionStart)
|
|
2126
|
+
? [detailFocus.selectionStart, detailFocus.selectionEnd] : null;
|
|
2127
|
+
renderRail(doc, drawerContent, selected, selectedDetail, null, { ...railOptions, loading });
|
|
2128
|
+
if (detailLabel) {
|
|
2129
|
+
const field = findFirst(drawerContent, node => attributeValue(node, 'aria-label') === detailLabel);
|
|
2130
|
+
field?.focus?.();
|
|
2131
|
+
if (detailSelection) field?.setSelectionRange?.(...detailSelection);
|
|
2132
|
+
}
|
|
2133
|
+
} else if (drawer) drawer.close();
|
|
2134
|
+
} else renderRail(doc, rail, selected, selectedDetail, selected ? closeSelected : null, { ...railOptions, loading });
|
|
2135
|
+
if (state.lastFocusedKey && !drawer) {
|
|
2053
2136
|
const row = findFirst(root, (node) => attributeValue(node, 'data-key') === state.lastFocusedKey);
|
|
2054
2137
|
row?.focus?.();
|
|
2055
2138
|
state.lastFocusedKey = null;
|
|
@@ -2169,10 +2252,15 @@ export function createJudgmentHistoryUI({
|
|
|
2169
2252
|
state.disposed = true;
|
|
2170
2253
|
state.generation += 1;
|
|
2171
2254
|
clearLoadTimers();
|
|
2255
|
+
filterPopover?.destroy();
|
|
2256
|
+
dismissLoading?.();
|
|
2257
|
+
notifications.remove?.();
|
|
2258
|
+
disposeDrawer();
|
|
2172
2259
|
root.removeEventListener?.('keydown', handleKeyDown);
|
|
2173
2260
|
rail?.removeEventListener?.('keydown', handleKeyDown);
|
|
2174
2261
|
root.replaceChildren();
|
|
2175
2262
|
rail?.replaceChildren();
|
|
2263
|
+
if (nativeDrawer && rail) rail.hidden = previousRailHidden;
|
|
2176
2264
|
}
|
|
2177
2265
|
|
|
2178
2266
|
const controller = { load, render, dispose, openDecision, get state() { return { ...state }; } };
|
package/ui/local-web-shell.js
CHANGED
|
@@ -451,8 +451,11 @@ export function createLocalWebShell({
|
|
|
451
451
|
workspace.append(mobileBar, main);
|
|
452
452
|
const railAside = makeElement(doc, 'aside', { className: 'bb-shell-rail', attrs: { 'aria-label': '選択中の項目' } });
|
|
453
453
|
const rails = new Map();
|
|
454
|
+
const nativeDrawers = Boolean(doc.body && typeof doc.createElement('dialog').showModal === 'function');
|
|
454
455
|
for (const screen of screens) {
|
|
455
|
-
|
|
456
|
+
// The built-in history uses a modal drawer in browsers, with a rail fallback
|
|
457
|
+
// for minimal hosts. Do not reserve an empty desktop column for the drawer.
|
|
458
|
+
if (!screen.rail || (nativeDrawers && screen.mount === mountToday)) continue;
|
|
456
459
|
const railSlot = makeElement(doc, 'div', { className: 'bb-shell-rail-slot', attrs: { 'data-rail': screen.id } });
|
|
457
460
|
railSlot.hidden = true;
|
|
458
461
|
rails.set(screen.id, railSlot);
|