@unson/brainbase-mcp 0.41.2 → 0.41.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.
@@ -1,3 +1,4 @@
1
+ @import './native-scrollbars.css';
1
2
  /*
2
3
  * Shell of the local Web host, in the organization edition's layout: a white
3
4
  * left rail (brand, navigation, source) and a paper workspace. Uses only the
@@ -26,6 +26,7 @@ import { createMeetingMinutesStorageExtension } from './meeting-minutes-storage.
26
26
  import { createMeetingMinutesLineageExtension } from './meeting-minutes-lineage-http.js';
27
27
  import { createWorldModelView } from './world-model-view.js';
28
28
  import { createGraphClient } from './graph-view-shared.js';
29
+ import { installNativeScrollbars } from './native-scrollbars.js';
29
30
 
30
31
  export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
31
32
 
@@ -459,6 +460,7 @@ export function createLocalWebShell({
459
460
  }
460
461
  shell.append(sidebar, backdrop, workspace, railAside);
461
462
  root.replaceChildren(shell);
463
+ const stopScrollbars = doc.documentElement?.classList && doc.addEventListener ? installNativeScrollbars(doc) : () => {};
462
464
 
463
465
  // The rail column shows only while the active screen is mounted and has one.
464
466
  function updateRail() {
@@ -493,6 +495,7 @@ export function createLocalWebShell({
493
495
  }
494
496
 
495
497
  const controller = {
498
+ destroy() { stopScrollbars(); for (const view of mounted.values()) view?.destroy?.(); root.replaceChildren(); },
496
499
  get state() {
497
500
  return { active, status, mounted: [...mounted.keys()] };
498
501
  },
@@ -0,0 +1,18 @@
1
+
2
+ @property --bb-scrollbar-opacity { syntax: "<number>"; inherits: false; initial-value: 0; }
3
+ :root.bb-native-scrollbars, .bb-native-scrollbars :is(.bb-scrollable, .sidebar, .bb-shell-sidebar, .bb-shell-main, .bb-shell-rail, .bb-ws-dialog, .mutation-dialog) {
4
+ --bb-scrollbar-rgb: var(--bb-color-scrollbar-rgb); --bb-scrollbar-opacity: 0;
5
+ scrollbar-width: thin;
6
+ scrollbar-color: rgba(var(--bb-scrollbar-rgb),var(--bb-scrollbar-opacity)) transparent;
7
+ transition: --bb-scrollbar-opacity 450ms ease;
8
+ }
9
+ .bb-native-scrollbars :is(.sidebar,.bb-shell-sidebar) { --bb-scrollbar-rgb: var(--bb-color-scrollbar-dark-rgb); }
10
+ :root.bb-native-scrollbars.bb-is-scrolling, .bb-native-scrollbars .bb-is-scrolling { --bb-scrollbar-opacity: .28; }
11
+ :root.bb-native-scrollbars::-webkit-scrollbar, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog)::-webkit-scrollbar { width: 6px; height: 6px; }
12
+ :root.bb-native-scrollbars::-webkit-scrollbar-track, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog)::-webkit-scrollbar-track { background: transparent; }
13
+ :root.bb-native-scrollbars::-webkit-scrollbar-thumb, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog)::-webkit-scrollbar-thumb { background: rgba(var(--bb-scrollbar-rgb),var(--bb-scrollbar-opacity)); border-radius: 8px; }
14
+ @media (prefers-reduced-motion: reduce) { :root.bb-native-scrollbars, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog) { transition: none; } }
15
+ @media (forced-colors: active) {
16
+ :root.bb-native-scrollbars, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog) { scrollbar-width: auto; scrollbar-color: auto; }
17
+ :root.bb-native-scrollbars::-webkit-scrollbar-thumb, .bb-native-scrollbars :is(.bb-scrollable,.sidebar,.bb-shell-sidebar,.bb-shell-main,.bb-shell-rail,.bb-ws-dialog,.mutation-dialog)::-webkit-scrollbar-thumb { background: CanvasText; }
18
+ }
@@ -0,0 +1,18 @@
1
+ /** Animate native scrollbars without replacing scrolling or input handling. */
2
+ export function installNativeScrollbars(doc = document) {
3
+ const timers = new Map();
4
+ doc.documentElement.classList.add('bb-native-scrollbars');
5
+ const activate = event => {
6
+ const target = event.target === doc ? doc.scrollingElement : event.target;
7
+ if (!target?.classList) return;
8
+ target.classList.add('bb-scrollable', 'bb-is-scrolling');
9
+ clearTimeout(timers.get(target));
10
+ timers.set(target, setTimeout(() => { target.classList.remove('bb-is-scrolling'); timers.delete(target); }, 800));
11
+ };
12
+ doc.addEventListener('scroll', activate, true);
13
+ return () => {
14
+ doc.removeEventListener('scroll', activate, true);
15
+ for (const [target, timer] of timers) { clearTimeout(timer); target.classList.remove('bb-is-scrolling'); }
16
+ timers.clear(); doc.documentElement.classList.remove('bb-native-scrollbars');
17
+ };
18
+ }
@@ -494,3 +494,39 @@
494
494
  }
495
495
 
496
496
  }
497
+
498
+ /* Shared help, modal, disclosures and persistent notifications. */
499
+ .bb-ws-help { position: relative; display: inline-flex; vertical-align: middle; }
500
+ .bb-ws-help-button { display: grid; place-items: center; width: 16px; height: 16px; padding: 0; border: 1px solid var(--bb-color-muted); border-radius: 50%; background: transparent; color: var(--bb-color-muted); font: italic 11px var(--bb-font-help); cursor: help; }
501
+ .bb-ws-help-bubble { position: absolute; z-index: 70; bottom: calc(100% + 8px); left: 50%; width: min(300px,70vw); padding: 10px 12px; border: 1px solid var(--bb-color-border); border-radius: 8px; background: var(--bb-color-panel); color: var(--bb-color-ink); font-size: 12px; font-weight: normal; line-height: 1.7; box-shadow: var(--bb-shadow-help); opacity: 0; visibility: hidden; transform: translate(-50%,4px); transition: opacity 160ms ease,transform 160ms ease,visibility 160ms; pointer-events: none; }
502
+ .bb-ws-help-bubble::after { content: ''; position: absolute; top: 100%; left: 0; width: 100%; height: 10px; }
503
+ .bb-ws-help:not(.is-dismissed):is(:hover,:focus-within,.is-open) .bb-ws-help-bubble { opacity: 1; visibility: visible; transform: translate(-50%,0); pointer-events: auto; }
504
+ .bb-ws-dialog { width: min(720px,calc(100vw - 36px)); max-height: calc(100dvh - 48px); box-sizing: border-box; padding: 0; overflow: auto; border: 1px solid var(--bb-color-border); border-radius: 12px; background: var(--bb-color-panel); color: var(--bb-color-ink); font: inherit; }
505
+ .bb-ws-dialog::backdrop { background: var(--bb-color-dialog-overlay); }
506
+ .bb-ws-dialog-head { display: flex; justify-content: space-between; align-items: start; gap: 20px; padding: 24px 28px; border-bottom: 1px solid var(--bb-color-border); }
507
+ .bb-ws-dialog-head h2 { margin: 0; font-size: 22px; line-height: 1.5; }
508
+ .bb-ws-dialog-body { padding: 28px; }
509
+ .bb-ws-disclosure { border-top: 1px solid var(--bb-color-border); }
510
+ .bb-ws-disclosure > summary { display: flex; align-items: center; gap: 12px; padding: 16px 0; cursor: pointer; list-style: none; }
511
+ .bb-ws-disclosure > summary::-webkit-details-marker { display: none; }
512
+ .bb-ws-disclosure > summary::before { content: '›'; font-size: 20px; color: var(--bb-color-muted); }
513
+ .bb-ws-disclosure[open] > summary::before { transform: rotate(90deg); }
514
+ .bb-ws-disclosure > summary h2 { margin: 0; font-size: 22px; line-height: 1.4; }
515
+ .bb-ws-disclosure > :not(summary) { margin: 0 0 18px 25px; }
516
+ .bb-ws-disclosure > summary:focus-visible, .bb-ws-help-button:focus-visible { outline: 2px solid var(--bb-color-focus); outline-offset: 3px; }
517
+ .bb-ws-error-stack { position: absolute; z-index: 55; top: 0; right: 0; width: min(300px,100%); max-height: none; padding-bottom: 64px; pointer-events: none; box-sizing: border-box; overflow: visible; }
518
+ .bb-ws-error-list { display: grid; gap: 6px; }
519
+ .bb-ws-error-count { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
520
+ .bb-ws-error-card { display: flex; align-items: start; gap: 8px; box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--bb-color-notice-border); border-left: 3px solid var(--bb-color-notice-danger); border-radius: 4px; background: var(--bb-color-notice-background); backdrop-filter: blur(5px); color: var(--bb-color-notice-text); font-size: 12px; line-height: 1.5; pointer-events: auto; }
521
+ .bb-ws-error-card.is-warning { border-left-color: var(--bb-color-notice-warning); }
522
+ .bb-ws-error-card > details { flex: 1; min-width: 0; }
523
+ .bb-ws-error-card summary { display: flex; align-items: center; gap: 6px; list-style: none; cursor: pointer; }
524
+ .bb-ws-error-card summary::-webkit-details-marker { display: none; }
525
+ .bb-ws-error-card summary > span:last-child { overflow-wrap: anywhere; }
526
+ .bb-ws-error-symbol { display: grid; flex: none; place-items: center; width: 14px; height: 14px; border: 1px solid var(--bb-color-notice-danger-symbol-border); color: var(--bb-color-notice-danger-text); border-radius: 3px; }
527
+ .bb-ws-error-card.is-warning .bb-ws-error-symbol { color: var(--bb-color-notice-warning-text); border-color: var(--bb-color-notice-warning-symbol-border); }
528
+ .bb-ws-error-card p { margin: 8px 0 0; overflow-wrap: anywhere; font-size: 11px; }
529
+ .bb-ws-error-card .bb-ws-button { flex: none; min-height: 18px; padding: 0 5px; border: 1px solid var(--bb-color-notice-control-border); border-radius: 4px; background: transparent; color: var(--bb-color-notice-text); font-size: 11px; line-height: 16px; }
530
+ .bb-ws-error-card :is(summary,button):focus-visible { outline: 2px solid var(--bb-color-notice-text); outline-offset: 2px; }
531
+ .bb-ws-inline-error { height: 15px; max-height: 15px; margin: 4px 0; padding: 0; overflow: hidden; color: var(--bb-color-inline-error); font-size: 11px; line-height: 15px; }
532
+ @media (prefers-reduced-motion: reduce) { .bb-ws-help-bubble { transition: none; } }
@@ -230,3 +230,68 @@ export function workspaceSectionTitle(doc, { title, lead } = {}) {
230
230
  if (lead) wrap.append(el(doc, 'p', { text: lead }));
231
231
  return wrap;
232
232
  }
233
+
234
+
235
+ let workspaceSequence = 0;
236
+ /** Shared explanatory help: hover/focus above the control, click for touch. */
237
+ export function workspaceHelp(doc, { text, label } = {}) {
238
+ const wrap = el(doc, 'span', { className: 'bb-ws-help' });
239
+ const control = workspaceButton(doc, { text: 'i', attrs: { 'aria-label': `${label}の説明`, 'aria-expanded': 'false' } });
240
+ control.className = 'bb-ws-help-button';
241
+ const bubble = el(doc, 'span', { className: 'bb-ws-help-bubble', text, attrs: { id: `bb-ws-help-${++workspaceSequence}`, role: 'tooltip' } });
242
+ control.setAttribute('aria-describedby', bubble.id || bubble.getAttribute?.('id') || bubble.attributes?.id);
243
+ control.addEventListener('click', event => {
244
+ event.stopPropagation(); event.preventDefault();
245
+ const opened = wrap.classList.toggle('is-open');
246
+ wrap.classList.remove('is-dismissed');
247
+ control.setAttribute('aria-expanded', String(opened));
248
+ });
249
+ wrap.addEventListener('mouseleave', () => wrap.classList.remove('is-dismissed'));
250
+ control.addEventListener('blur', () => { wrap.classList.remove('is-open', 'is-dismissed'); control.setAttribute('aria-expanded', 'false'); });
251
+ control.addEventListener('keydown', event => {
252
+ if (event.key !== 'Escape') return;
253
+ wrap.classList.remove('is-open'); wrap.classList.add('is-dismissed');
254
+ control.setAttribute('aria-expanded', 'false'); event.stopPropagation();
255
+ });
256
+ wrap.append(control, bubble); return wrap;
257
+ }
258
+
259
+ /** Native dialog keeps focus trapped, Escape close and focus restoration. */
260
+ export function workspaceDialog(doc, { title, content, opener } = {}) {
261
+ const dialog = el(doc, 'dialog', { className: 'bb-ws-dialog' });
262
+ const headingId = `bb-ws-dialog-${++workspaceSequence}`;
263
+ dialog.setAttribute('aria-labelledby', headingId);
264
+ const head = el(doc, 'header', { className: 'bb-ws-dialog-head' });
265
+ const close = workspaceButton(doc, { text: '×', attrs: { 'aria-label': '閉じる' }, onClick: () => dialog.close() });
266
+ head.append(el(doc, 'h2', { text: title, attrs: { id: headingId } }), close);
267
+ const body = el(doc, 'div', { className: 'bb-ws-dialog-body' }); appendContent(body, doc, content);
268
+ dialog.append(head, body); doc.body.append(dialog);
269
+ dialog.addEventListener('click', event => {
270
+ if (event.target !== dialog) return;
271
+ const rect = dialog.getBoundingClientRect();
272
+ if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close();
273
+ });
274
+ dialog.addEventListener('close', () => { dialog.remove(); if (opener?.isConnected) opener.focus(); }, { once: true });
275
+ dialog.showModal(); close.focus(); return dialog;
276
+ }
277
+
278
+ /** Persistent, initially closed notification. The caller owns its lifecycle. */
279
+ export function workspaceErrorCard(doc, { title, message, tone = 'danger', onRetry } = {}) {
280
+ const card = el(doc, 'section', { className: `bb-ws-error-card is-${tone}`, attrs: { role: 'listitem' } });
281
+ const details = el(doc, 'details');
282
+ const summary = el(doc, 'summary', { attrs: { 'aria-label': `${title}の通知` } });
283
+ summary.append(el(doc, 'span', { className: 'bb-ws-error-symbol', text: '!', attrs: { 'aria-hidden': 'true' } }), el(doc, 'span', { text: title }));
284
+ details.append(summary, el(doc, 'p', { text: message })); card.append(details);
285
+ if (onRetry) card.append(workspaceButton(doc, { text: '再試行', attrs: { 'aria-label': `${title}を再試行` }, onClick: onRetry }));
286
+ return card;
287
+ }
288
+
289
+ export function workspaceDisclosure(doc, { title, help, content, open = false, onToggle } = {}) {
290
+ const details = el(doc, 'details', { className: 'bb-ws-disclosure' }); details.open = open;
291
+ const summary = el(doc, 'summary');
292
+ summary.append(el(doc, 'h2', { text: title }));
293
+ if (help) summary.append(workspaceHelp(doc, { label: title, text: help }));
294
+ details.append(summary, content);
295
+ if (onToggle) details.addEventListener('toggle', () => onToggle(details.open));
296
+ return details;
297
+ }