@unson/brainbase-mcp 0.41.9 → 0.41.11

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.
@@ -31,6 +31,8 @@ export interface CanonicalTaskRecord extends JsonRecord {
31
31
  assignee_display_name?: string | null;
32
32
  due_at?: string | null;
33
33
  waiting_on?: string | null;
34
+ /** The Graph person a waiting task waits on; null when it waits on a condition or nobody in particular. */
35
+ waiting_on_person_id?: string | null;
34
36
  review_at?: string | null;
35
37
  completed_at?: string | null;
36
38
  source_refs?: unknown[];
@@ -50,6 +52,7 @@ export interface CanonicalTaskCreateInput extends JsonRecord {
50
52
  assignee_person_id?: string | null;
51
53
  due_at?: string | null;
52
54
  waiting_on?: string | null;
55
+ waiting_on_person_id?: string | null;
53
56
  review_at?: string | null;
54
57
  source_refs?: unknown[];
55
58
  project_codes?: unknown;
@@ -63,6 +66,7 @@ export interface CanonicalTaskUpdateInput extends JsonRecord {
63
66
  assignee_person_id?: string | null;
64
67
  due_at?: string | null;
65
68
  waiting_on?: string | null;
69
+ waiting_on_person_id?: string | null;
66
70
  review_at?: string | null;
67
71
  source_refs?: unknown[];
68
72
  source_refs_append?: unknown[];
@@ -231,12 +235,14 @@ export declare class CanonicalTaskService {
231
235
  }, context: CanonicalTaskContext): Promise<CanonicalTaskRecord>;
232
236
  transitionTask(taskId: string, status: unknown, expectedVersion: number, context: CanonicalTaskContext, input?: {
233
237
  waitingOn?: unknown;
238
+ waitingOnPersonId?: unknown;
234
239
  reviewAt?: unknown;
235
240
  }): Promise<CanonicalTaskRecord>;
236
241
  transitionTask(taskId: string, input: {
237
242
  to_status?: unknown;
238
243
  expected_version?: number;
239
244
  waiting_on?: unknown;
245
+ waiting_on_person_id?: unknown;
240
246
  review_at?: unknown;
241
247
  }, context: CanonicalTaskContext): Promise<CanonicalTaskRecord>;
242
248
  deleteTask(taskId: string, expectedVersion: number, context: CanonicalTaskContext): Promise<{
@@ -34,6 +34,7 @@ const CREATE_FIELDS = new Set([
34
34
  'assignee_person_id',
35
35
  'due_at',
36
36
  'waiting_on',
37
+ 'waiting_on_person_id',
37
38
  'review_at',
38
39
  'source_refs',
39
40
  'project_codes',
@@ -48,6 +49,7 @@ const MUTABLE_FIELDS = new Set([
48
49
  'assignee_person_id',
49
50
  'due_at',
50
51
  'waiting_on',
52
+ 'waiting_on_person_id',
51
53
  'review_at',
52
54
  'source_refs',
53
55
  'source_refs_append',
@@ -78,6 +80,10 @@ function normalizeString(value, field, options = {}) {
78
80
  }
79
81
  return normalized;
80
82
  }
83
+ /** A Graph person ID, as for the assignee; empty or null means no person. */
84
+ function normalizeWaitingOnPersonId(value) {
85
+ return normalizeString(value, 'waiting_on_person_id', { max: 200 });
86
+ }
81
87
  /** A purpose label: a single line of at most 30 characters; empty or null clears it. */
82
88
  const MAX_PURPOSE_LABEL_LENGTH = 30;
83
89
  function normalizePurposeLabel(value) {
@@ -511,7 +517,7 @@ export class CanonicalTaskService {
511
517
  const expectedVersion = legacyInput ? legacyInput.expected_version : expectedVersionOrContext;
512
518
  const context = legacyInput ? expectedVersionOrContext : maybeContext;
513
519
  const input = legacyInput
514
- ? { waitingOn: legacyInput.waiting_on, reviewAt: legacyInput.review_at }
520
+ ? { waitingOn: legacyInput.waiting_on, waitingOnPersonId: legacyInput.waiting_on_person_id, reviewAt: legacyInput.review_at }
515
521
  : maybeInput;
516
522
  const normalizedContext = await this.normalizeContext(context);
517
523
  const expected = this.requireExpectedVersion(expectedVersion);
@@ -523,10 +529,13 @@ export class CanonicalTaskService {
523
529
  failValidation({ waiting_on: ['required_for_waiting'] });
524
530
  }
525
531
  patch.waiting_on = normalizeString(input.waitingOn, 'waiting_on', { required: true, max: 500 });
532
+ // The person is optional and always replaced, so waiting on a condition clears an earlier person.
533
+ patch.waiting_on_person_id = normalizeWaitingOnPersonId(input.waitingOnPersonId);
526
534
  patch.review_at = normalizeIsoDate(input.reviewAt, 'review_at');
527
535
  }
528
536
  else {
529
537
  patch.waiting_on = input.waitingOn === undefined ? null : normalizeString(input.waitingOn, 'waiting_on', { max: 500 });
538
+ patch.waiting_on_person_id = input.waitingOnPersonId === undefined ? null : normalizeWaitingOnPersonId(input.waitingOnPersonId);
530
539
  patch.review_at = input.reviewAt === undefined ? null : normalizeIsoDate(input.reviewAt, 'review_at');
531
540
  }
532
541
  return this.versionedMutation({
@@ -801,6 +810,7 @@ export class CanonicalTaskService {
801
810
  transition: {
802
811
  to_status: input.transitionStatus,
803
812
  ...(input.patch.waiting_on !== undefined ? { waiting_on: input.patch.waiting_on } : {}),
813
+ ...(input.patch.waiting_on_person_id !== undefined ? { waiting_on_person_id: input.patch.waiting_on_person_id } : {}),
804
814
  ...(input.patch.review_at !== undefined ? { review_at: input.patch.review_at } : {}),
805
815
  },
806
816
  };
@@ -818,6 +828,7 @@ export class CanonicalTaskService {
818
828
  if (!isCanonicalTaskPriority(priority))
819
829
  fail('validation_error', 'priority is invalid', 400, { field: 'priority' });
820
830
  const purposeLabel = normalizePurposeLabel(input.purpose_label);
831
+ const waitingOnPersonId = normalizeWaitingOnPersonId(input.waiting_on_person_id);
821
832
  const completionContract = normalizeCompletionContract(input.completion_contract);
822
833
  return {
823
834
  ...(completionContract ? { completion_contract: completionContract } : {}),
@@ -832,6 +843,8 @@ export class CanonicalTaskService {
832
843
  project_codes: normalizeProjectCodes(input.project_codes) ?? [],
833
844
  // Only a label that is there is written, so a store without the column still accepts unlabelled work.
834
845
  ...(purposeLabel ? { purpose_label: purposeLabel } : {}),
846
+ // Likewise only a person that is there is written.
847
+ ...(waitingOnPersonId ? { waiting_on_person_id: waitingOnPersonId } : {}),
835
848
  };
836
849
  }
837
850
  normalizeUpdateInput(input) {
@@ -866,6 +879,8 @@ export class CanonicalTaskService {
866
879
  patch.due_at = normalizeIsoDate(input.due_at, 'due_at');
867
880
  if ('waiting_on' in input)
868
881
  patch.waiting_on = normalizeString(input.waiting_on, 'waiting_on', { max: 500 });
882
+ if ('waiting_on_person_id' in input)
883
+ patch.waiting_on_person_id = normalizeWaitingOnPersonId(input.waiting_on_person_id);
869
884
  if ('review_at' in input)
870
885
  patch.review_at = normalizeIsoDate(input.review_at, 'review_at');
871
886
  if ('source_refs' in input && hasSourceRefsAppend) {
@@ -1052,6 +1067,8 @@ export class CanonicalTaskService {
1052
1067
  response.due_at = null;
1053
1068
  if (response.waiting_on === undefined)
1054
1069
  response.waiting_on = null;
1070
+ if (response.waiting_on_person_id === undefined)
1071
+ response.waiting_on_person_id = null;
1055
1072
  if (response.review_at === undefined)
1056
1073
  response.review_at = null;
1057
1074
  if (response.completed_at === undefined)
@@ -793,6 +793,79 @@ function groupFor(groups, sessionRef, key) {
793
793
  groups.set(groupKey, group);
794
794
  return group;
795
795
  }
796
+ const FRAME_USE_ROLES = new Set(['constraint', 'criterion', 'prediction']);
797
+ const FRAME_USE_VERDICTS = new Set(['satisfied', 'violated', 'tension']);
798
+ const FRAME_PREDICTION_CHECKS = new Set(['held', 'failed', 'unchecked']);
799
+ /**
800
+ * The usage value shared by every writer of judgment history references
801
+ * (docs/specs/judgment-history-frame-basis.md): the role in the chosen option,
802
+ * then the constraint verdict or the prediction check.
803
+ */
804
+ function frameUseUsage(use) {
805
+ const role = text(use.role);
806
+ if (!role || !FRAME_USE_ROLES.has(role))
807
+ return null;
808
+ if (role === 'prediction') {
809
+ const check = isObject(use.check) ? text(use.check.status) : null;
810
+ return `prediction:${check && FRAME_PREDICTION_CHECKS.has(check) ? check : 'unchecked'}`;
811
+ }
812
+ const verdict = text(use.verdict);
813
+ if (role === 'constraint')
814
+ return verdict && FRAME_USE_VERDICTS.has(verdict) ? `constraint:${verdict}` : null;
815
+ return verdict && FRAME_USE_VERDICTS.has(verdict) ? `criterion:${verdict}` : 'criterion';
816
+ }
817
+ /** Carry how the chosen option used each selected reference; rejected options never leak in. */
818
+ function frameReferencesWithUsage(frame) {
819
+ const references = frame.selected_references;
820
+ if (!Array.isArray(references))
821
+ return references;
822
+ const chosenLabel = text(frame.chosen_option);
823
+ const chosen = Array.isArray(frame.alternatives)
824
+ ? frame.alternatives.find((option) => isObject(option) && chosenLabel !== null && option.label === chosenLabel)
825
+ : null;
826
+ const uses = isObject(chosen) && Array.isArray(chosen.uses) ? chosen.uses.filter(isObject) : [];
827
+ return references.map((reference) => {
828
+ if (!isObject(reference))
829
+ return reference;
830
+ const ref = text(reference.ref);
831
+ const use = ref ? uses.find((candidate) => text(candidate.ref) === ref) : undefined;
832
+ const usage = use ? frameUseUsage(use) : null;
833
+ return usage && reference.usage === undefined ? { ...reference, usage } : reference;
834
+ });
835
+ }
836
+ /**
837
+ * Personal KG records the owner's answer applied in this turn. Only resolved
838
+ * answers count; their content never enters the history record.
839
+ */
840
+ function personalKnowledgeReferences(events) {
841
+ const references = [];
842
+ const seen = new Set();
843
+ for (const event of events) {
844
+ if (event.success !== true || text(event.event_kind) !== 'personal_answer')
845
+ continue;
846
+ const metadata = isObject(event.safe_metadata) ? event.safe_metadata : null;
847
+ const answer = metadata && isObject(metadata.personal_answer) ? metadata.personal_answer : null;
848
+ if (!answer || text(answer.status) !== 'resolved' || !Array.isArray(answer.reference_ids))
849
+ continue;
850
+ for (const value of answer.reference_ids) {
851
+ const ref = safeId(value);
852
+ if (!ref || seen.has(ref))
853
+ continue;
854
+ seen.add(ref);
855
+ references.push({ ref, kind: 'personal_knowledge', version: null, digest: null, why: null, usage: 'applied', availability: 'unknown' });
856
+ }
857
+ }
858
+ return references;
859
+ }
860
+ function withPersonalKnowledge(judgment, events) {
861
+ const personal = personalKnowledgeReferences(events);
862
+ if (personal.length === 0)
863
+ return judgment;
864
+ const existing = judgment.selected_references ?? [];
865
+ const known = new Set(existing.map((reference) => `${reference.kind ?? ''}\u0000${reference.ref}`));
866
+ const added = personal.filter((reference) => !known.has(`${reference.kind}\u0000${reference.ref}`));
867
+ return added.length === 0 ? judgment : { ...judgment, selected_references: [...existing, ...added] };
868
+ }
796
869
  function sourceBlocks(value) {
797
870
  if (!value)
798
871
  return [];
@@ -808,7 +881,7 @@ function sourceBlocks(value) {
808
881
  const frame = isObject(metadata.judgment_frame) ? metadata.judgment_frame : null;
809
882
  if (frame)
810
883
  blocks.push({
811
- selected_references: frame.selected_references,
884
+ selected_references: frameReferencesWithUsage(frame),
812
885
  alternatives: Array.isArray(frame.alternatives) ? frame.alternatives.map((option) => isObject(option) ? {
813
886
  label: option.label,
814
887
  evaluation: null,
@@ -1018,7 +1091,7 @@ function normalizeFinalRecord(final, sessionRef, key, configuredEntrypoint, turn
1018
1091
  recorded_at: recordedAt,
1019
1092
  project_code: projectCode,
1020
1093
  turn_ref: `${sessionRef}/${sha256(turnId)}`,
1021
- judgment: mergeJudgment(judgment, blocks, field(blocks[0] ?? null, 'missing_fields')),
1094
+ judgment: withPersonalKnowledge(mergeJudgment(judgment, blocks, field(blocks[0] ?? null, 'missing_fields')), events),
1022
1095
  execution: mergeExecution({ ...execution, status: finalExecutionStatus }, [...executionBlocks(final), ...events.flatMap((event) => executionBlocks(event)).reverse()], completed)
1023
1096
  };
1024
1097
  return { ...baseRecord, missing_fields: missingFields(baseRecord, firstPresent(blocks, 'missing_fields')) };
@@ -1034,7 +1107,7 @@ function mergeRecordWithFinal(adoptionRecord, final, sessionRef, key, turnInput,
1034
1107
  recorded_at: projected.recorded_at,
1035
1108
  project_code: adoptionRecord.project_code ?? projected.project_code,
1036
1109
  entrypoint: adoptionRecord.entrypoint === 'unknown' ? projected.entrypoint : adoptionRecord.entrypoint,
1037
- judgment: mergeJudgment(adoptionRecord.judgment, blocks, undefined),
1110
+ judgment: withPersonalKnowledge(mergeJudgment(adoptionRecord.judgment, blocks, undefined), events),
1038
1111
  execution
1039
1112
  };
1040
1113
  return { ...baseRecord, missing_fields: missingFields(baseRecord, firstPresent(blocks, 'missing_fields')) };
@@ -44,6 +44,9 @@ function routeFor(pathname, basePath) {
44
44
  if (segments.length === 4 && noun === 'minutes' && minutesId && action === 'confirm') {
45
45
  return { kind: 'confirm', meetingId, minutesId };
46
46
  }
47
+ if (segments.length === 4 && noun === 'minutes' && minutesId && action === 'withdraw') {
48
+ return { kind: 'withdraw', meetingId, minutesId };
49
+ }
47
50
  if (segments.length === 5 && noun === 'minutes' && minutesId && action === 'versions' && versionId) {
48
51
  return { kind: 'version', meetingId, minutesId, versionId };
49
52
  }
@@ -157,6 +160,13 @@ function confirmInput(body, minutesId, key, fingerprint) {
157
160
  ...idempotencyFields(key, fingerprint)
158
161
  };
159
162
  }
163
+ function withdrawInput(body, minutesId, key, fingerprint) {
164
+ return {
165
+ minutes_id: minutesId,
166
+ expected_revision: body.expected_revision,
167
+ ...idempotencyFields(key, fingerprint)
168
+ };
169
+ }
160
170
  export function createMeetingMinutesHttpHandler(options) {
161
171
  if (!options || !options.store || typeof options.resolveContext !== 'function') {
162
172
  throw new TypeError('store and resolveContext are required');
@@ -202,6 +212,15 @@ export function createMeetingMinutesHttpHandler(options) {
202
212
  }
203
213
  return { statusCode: 200, body: await options.store.confirm_version(confirmInput(body, document.minutes_id, key, fingerprint), context) };
204
214
  }
215
+ case 'withdraw': {
216
+ if (body === undefined)
217
+ throw new MeetingMinutesError('invalid_input', 'Request body is required');
218
+ const detail = await options.store.get_meeting(route.meetingId ?? '', context);
219
+ const document = detail.minutes.find((candidate) => candidate.minutes_id === route.minutesId);
220
+ if (!document)
221
+ throw new MeetingMinutesError('not_found', `Minutes document ${route.minutesId} was not found`);
222
+ return { statusCode: 200, body: await options.store.withdraw_minutes(withdrawInput(body, document.minutes_id, key, fingerprint), context) };
223
+ }
205
224
  case 'version':
206
225
  return {
207
226
  statusCode: 200,
@@ -1132,6 +1132,10 @@ export function createMeetingMinutesStore(options) {
1132
1132
  const meeting = findMeeting(snapshot, document.meeting_id);
1133
1133
  await authorized(authorizer, 'write', meeting, context);
1134
1134
  await authorized(authorizer, 'write', document, context);
1135
+ // Only the person who made the document takes it back (story-meeting-minutes-edit-and-withdraw-ui-v1).
1136
+ if (document.created_actor_id !== principal) {
1137
+ throw new MeetingMinutesError('authorization_denied', 'Only the person who made this minutes document can withdraw it');
1138
+ }
1135
1139
  // Withdrawing twice keeps the first record; a retry is not a second withdrawal.
1136
1140
  if (document.withdrawal && !idempotency.token)
1137
1141
  return detailForCaller(snapshot, meeting, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.41.9",
3
+ "version": "0.41.11",
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": "76bf4ebe18b116379d4687272baecdc4a74dbb35"
486
+ "gitHead": "3849d107c129672e91d6c28cfa3b8b5053d7f53a"
487
487
  }
@@ -292,7 +292,7 @@
292
292
  }
293
293
 
294
294
  .bb-jh-ledger .bb-ws-ledger-row {
295
- grid-template-columns: 92px minmax(0, 1fr) 250px 105px;
295
+ grid-template-columns: 92px minmax(0, 1fr) 250px 120px;
296
296
  align-items: center;
297
297
  min-height: 52px;
298
298
  }
@@ -581,7 +581,7 @@
581
581
  .bb-jh-ledger .bb-ws-ledger-row > span:nth-child(1)::before { content: '日時'; }
582
582
  .bb-jh-ledger .bb-ws-ledger-row > span:nth-child(2)::before { content: '行った判断'; }
583
583
  .bb-jh-ledger .bb-ws-ledger-row > span:nth-child(3)::before { content: '使った参照'; }
584
- .bb-jh-ledger .bb-ws-ledger-row > span:nth-child(4)::before { content: '入口・状態'; }
584
+ .bb-jh-ledger .bb-ws-ledger-row > span:nth-child(4)::before { content: '判断したAI'; }
585
585
  }
586
586
 
587
587
  /* Compact workspace header, search controls and shared floating notifications. */
@@ -617,3 +617,156 @@
617
617
  }
618
618
  .bb-jh-notifications .bb-jh-banner.is-danger { border-left-color: var(--bb-color-notice-danger); }
619
619
  .bb-jh-notifications .bb-jh-banner-sources > span { white-space: normal; }
620
+
621
+ /* Loading stays in the page, like the other workspace screens. */
622
+ .bb-jh-loading { color: var(--bb-color-muted); }
623
+ .bb-jh-summary-note { font-size: 11.5px; }
624
+ .bb-jh-entrypoint { display: grid; gap: 2px; }
625
+ .bb-jh-entrypoint small.bb-jh-waiting { color: var(--bb-color-warning-strong, var(--bb-color-accent-strong)); font-size: 11px; font-weight: 600; }
626
+ .bb-jh-filter-fields .bb-jh-filter .bb-ws-combobox { width: 100%; }
627
+
628
+ /* Detail: references that only have an id, the one question, and the record details. */
629
+ .bb-jh-references { display: grid; gap: 8px; }
630
+ .bb-jh-reference-list small { color: var(--bb-color-quiet); font-size: 11px; }
631
+ .bb-jh-reference-kinds { margin: 0; color: var(--bb-color-ink); font-size: 13px; font-weight: 600; }
632
+ .bb-jh-references .bb-jh-reference-note,
633
+ .bb-jh-handoff .bb-jh-reference-note { margin: 0; }
634
+ .bb-jh-feedback { display: grid; gap: 12px; }
635
+ .bb-jh-feedback-events { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
636
+ .bb-jh-feedback-answers { display: flex; flex-wrap: wrap; gap: 6px; }
637
+ .bb-jh-feedback-composer > .bb-ws-button { justify-self: start; }
638
+ .bb-jh-record-details { margin-top: 10px; }
639
+ .bb-jh-record-details > summary { color: var(--bb-color-muted); font-size: 12px; cursor: pointer; }
640
+ .bb-jh-record-details[open] > summary { margin-bottom: 8px; }
641
+ .bb-jh-record-details .bb-ws-definition dd { overflow-wrap: anywhere; }
642
+ .bb-jh-id-list { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; font-family: var(--bb-font-mono); font-size: 10.5px; color: var(--bb-color-quiet); overflow-wrap: anywhere; }
643
+ .bb-jh-handoff { display: grid; gap: 6px; margin-top: 14px; }
644
+ .bb-jh-handoff h4 { margin: 0; font-size: 12.5px; color: var(--bb-color-ink); }
645
+ .bb-jh-feedback-answers .bb-ws-button[aria-pressed="true"] { border-color: var(--bb-color-accent); background: var(--bb-color-accent-soft, #eeebfd); color: var(--bb-color-accent-strong, var(--bb-color-accent)); }
646
+ .bb-jh-feedback-composer [data-action="submit-judgment-feedback"] { border-color: var(--bb-color-accent); background: var(--bb-color-accent); color: #fff; }
647
+ .bb-jh-feedback-composer [data-action="submit-judgment-feedback"]:disabled { opacity: .55; cursor: default; }
648
+ .bb-jh-feedback-composer textarea,
649
+ .bb-jh-reference-export textarea { box-sizing: border-box; }
650
+
651
+ /* 判断の根拠と本人の考え (story-judgment-history-frame-basis-v1) */
652
+ .bb-jh-basis {
653
+ display: grid;
654
+ gap: 14px;
655
+ }
656
+
657
+ .bb-jh-basis-group {
658
+ display: grid;
659
+ gap: 6px;
660
+ min-width: 0;
661
+ }
662
+
663
+ .bb-jh-basis-group h4 {
664
+ margin: 0;
665
+ color: var(--bb-color-muted);
666
+ font-size: 11px;
667
+ font-weight: 680;
668
+ letter-spacing: 0.04em;
669
+ }
670
+
671
+ .bb-jh-basis-list {
672
+ display: grid;
673
+ gap: 10px;
674
+ margin: 0;
675
+ padding: 0;
676
+ list-style: none;
677
+ }
678
+
679
+ .bb-jh-basis-list li {
680
+ display: grid;
681
+ gap: 3px;
682
+ min-width: 0;
683
+ padding-left: 10px;
684
+ border-left: 2px solid var(--bb-color-border);
685
+ }
686
+
687
+ .bb-jh-basis-list strong {
688
+ color: var(--bb-color-ink);
689
+ font-size: 13px;
690
+ line-height: 1.45;
691
+ overflow-wrap: anywhere;
692
+ }
693
+
694
+ .bb-jh-basis-list strong.bb-jh-unrecorded {
695
+ color: var(--bb-color-quiet);
696
+ font-weight: 500;
697
+ }
698
+
699
+ .bb-jh-basis-usage {
700
+ color: var(--bb-color-muted);
701
+ font-size: 12px;
702
+ line-height: 1.5;
703
+ }
704
+
705
+ .bb-jh-basis-usage.is-violated,
706
+ .bb-jh-basis-usage.is-failed {
707
+ color: var(--bb-color-danger);
708
+ }
709
+
710
+ .bb-jh-basis-usage.is-tension {
711
+ color: var(--bb-color-warning);
712
+ }
713
+
714
+ .bb-jh-basis-usage.is-unrecorded {
715
+ color: var(--bb-color-quiet);
716
+ }
717
+
718
+ .bb-jh-basis-why {
719
+ color: var(--bb-color-muted);
720
+ font-size: 12px;
721
+ line-height: 1.5;
722
+ }
723
+
724
+ .bb-jh-basis-list small {
725
+ color: var(--bb-color-quiet);
726
+ font-size: 11px;
727
+ }
728
+
729
+ .bb-jh-owner {
730
+ display: grid;
731
+ gap: 4px;
732
+ padding: 10px 12px;
733
+ border: 1px solid var(--bb-color-border);
734
+ border-radius: var(--bb-radius-sm);
735
+ background: var(--bb-color-panel);
736
+ }
737
+
738
+ .bb-jh-owner.is-applied {
739
+ border-color: var(--bb-color-accent);
740
+ background: var(--bb-color-accent-soft);
741
+ }
742
+
743
+ .bb-jh-owner strong {
744
+ color: var(--bb-color-ink);
745
+ font-size: 13px;
746
+ line-height: 1.5;
747
+ }
748
+
749
+ .bb-jh-owner p {
750
+ margin: 0;
751
+ color: var(--bb-color-muted);
752
+ font-size: 12px;
753
+ line-height: 1.5;
754
+ }
755
+
756
+ .bb-jh-owner-badge {
757
+ width: fit-content;
758
+ padding: 1px 7px;
759
+ border: 1px solid var(--bb-color-border);
760
+ border-radius: 999px;
761
+ color: var(--bb-color-quiet);
762
+ font-size: 11px;
763
+ line-height: 1.6;
764
+ white-space: nowrap;
765
+ }
766
+
767
+ .bb-jh-owner-badge.is-applied {
768
+ border-color: var(--bb-color-accent);
769
+ background: var(--bb-color-accent-soft);
770
+ color: var(--bb-color-accent-strong);
771
+ font-weight: 680;
772
+ }