@unson/brainbase-mcp 0.23.7 → 0.23.9
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/world-extension.js +1 -1
- package/package.json +2 -2
- package/ui/outcome-knowledge.css +38 -8
- package/ui/outcome-knowledge.js +205 -30
- package/ui/world/world-canvas-notes.js +31 -0
- package/ui/world/world-canvas-ui.css +352 -0
- package/ui/world/world-canvas-ui.js +467 -0
- package/ui/world/world-district.js +79 -11
- package/ui/world/world-view.css +2 -0
- package/ui/world/world-view.js +241 -65
- package/ui/world/world-work-rail.js +3 -3
- package/ui/world-model-view.css +57 -0
- package/ui/world-model-view.js +45 -7
package/dist/world-extension.js
CHANGED
|
@@ -347,7 +347,7 @@ export function createWorldExtension(options = {}) {
|
|
|
347
347
|
return {
|
|
348
348
|
id: WORLD_EXTENSION_ID,
|
|
349
349
|
uiDir: fileURLToPath(new URL('../ui/world/', import.meta.url)),
|
|
350
|
-
uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js', 'world-scenery.js'],
|
|
350
|
+
uiFiles: ['world-view.js', 'world-view.css', 'world-canvas-ui.js', 'world-canvas-ui.css', 'world-canvas-notes.js', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js', 'world-scenery.js'],
|
|
351
351
|
screenEntry: 'world-view.js',
|
|
352
352
|
// P17: the world sits above the home and opens first.
|
|
353
353
|
navPosition: 'first',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.9",
|
|
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",
|
|
@@ -423,5 +423,5 @@
|
|
|
423
423
|
"engines": {
|
|
424
424
|
"node": ">=20"
|
|
425
425
|
},
|
|
426
|
-
"gitHead": "
|
|
426
|
+
"gitHead": "ffddfb19cbde2520ba00f8370a251de08cd92ab5"
|
|
427
427
|
}
|
package/ui/outcome-knowledge.css
CHANGED
|
@@ -154,6 +154,7 @@
|
|
|
154
154
|
.knowledge-outcome .knowledge-outcome-primary,
|
|
155
155
|
.knowledge-outcome .knowledge-outcome-detail {
|
|
156
156
|
display: grid;
|
|
157
|
+
align-content: start;
|
|
157
158
|
gap: 0;
|
|
158
159
|
min-width: 0;
|
|
159
160
|
padding: 1.15rem 0 0;
|
|
@@ -169,7 +170,7 @@
|
|
|
169
170
|
}
|
|
170
171
|
.knowledge-outcome .knowledge-discovery-view,
|
|
171
172
|
.knowledge-outcome .knowledge-focused-view { min-width: 0; }
|
|
172
|
-
.knowledge-outcome .knowledge-discovery-view { display: grid; gap: 0; }
|
|
173
|
+
.knowledge-outcome .knowledge-discovery-view { display: grid; align-content: start; gap: 0; }
|
|
173
174
|
.knowledge-outcome .knowledge-discovery-toolbar { margin-top: 1rem; }
|
|
174
175
|
.knowledge-outcome .knowledge-discovery-list { min-width: 0; }
|
|
175
176
|
|
|
@@ -249,7 +250,7 @@
|
|
|
249
250
|
.knowledge-outcome .knowledge-list-rows { display: grid; gap: 0; margin-top: 0; }
|
|
250
251
|
.knowledge-outcome .knowledge-list-row {
|
|
251
252
|
display: grid;
|
|
252
|
-
grid-template-columns: minmax(0, 1fr) auto
|
|
253
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
253
254
|
gap: .45rem;
|
|
254
255
|
align-items: center;
|
|
255
256
|
width: 100%;
|
|
@@ -265,13 +266,45 @@
|
|
|
265
266
|
}
|
|
266
267
|
.knowledge-outcome .knowledge-list-row:hover,
|
|
267
268
|
.knowledge-outcome .knowledge-list-row[aria-pressed="true"] { box-shadow: inset 3px 0 var(--knowledge-green); background: var(--knowledge-green-soft); }
|
|
268
|
-
.knowledge-outcome .knowledge-row-
|
|
269
|
+
.knowledge-outcome .knowledge-list-row:focus-visible { box-shadow: inset 3px 0 var(--knowledge-green), 0 0 0 2px color-mix(in srgb, var(--knowledge-green) 24%, transparent); }
|
|
270
|
+
.knowledge-outcome .knowledge-row-main { display: grid; min-width: 0; gap: .2rem; }
|
|
271
|
+
.knowledge-outcome .knowledge-row-title {
|
|
272
|
+
display: -webkit-box;
|
|
273
|
+
min-width: 0;
|
|
274
|
+
overflow: hidden;
|
|
275
|
+
font-size: .8rem;
|
|
276
|
+
font-weight: 720;
|
|
277
|
+
text-overflow: ellipsis;
|
|
278
|
+
white-space: normal;
|
|
279
|
+
overflow-wrap: anywhere;
|
|
280
|
+
line-clamp: 2;
|
|
281
|
+
-webkit-box-orient: vertical;
|
|
282
|
+
-webkit-line-clamp: 2;
|
|
283
|
+
}
|
|
284
|
+
.knowledge-outcome .knowledge-row-meta { display: flex; flex-wrap: wrap; gap: .15rem .55rem; min-width: 0; }
|
|
269
285
|
.knowledge-outcome .knowledge-row-type,
|
|
270
286
|
.knowledge-outcome .knowledge-row-scope,
|
|
271
287
|
.knowledge-outcome .knowledge-row-state { color: var(--knowledge-muted); font-size: .68rem; white-space: nowrap; }
|
|
288
|
+
.knowledge-outcome .knowledge-row-chevron { align-self: center; color: var(--knowledge-quiet); font-size: 1.2rem; font-weight: 500; line-height: 1; }
|
|
289
|
+
.knowledge-outcome .knowledge-list-row:hover .knowledge-row-chevron,
|
|
290
|
+
.knowledge-outcome .knowledge-list-row:focus-visible .knowledge-row-chevron,
|
|
291
|
+
.knowledge-outcome .knowledge-list-row[aria-pressed="true"] .knowledge-row-chevron { color: var(--knowledge-green); }
|
|
292
|
+
.knowledge-outcome .knowledge-detail-jump {
|
|
293
|
+
display: none;
|
|
294
|
+
margin-top: .65rem;
|
|
295
|
+
color: var(--knowledge-green);
|
|
296
|
+
font-size: .76rem;
|
|
297
|
+
font-weight: 700;
|
|
298
|
+
text-underline-offset: .15em;
|
|
299
|
+
}
|
|
300
|
+
.knowledge-outcome .knowledge-detail-jump:focus-visible { outline: 3px solid color-mix(in srgb, var(--knowledge-green) 28%, transparent); outline-offset: 2px; }
|
|
301
|
+
.knowledge-outcome #knowledge-selected-detail { scroll-margin-top: 5rem; }
|
|
272
302
|
.knowledge-outcome .knowledge-empty,
|
|
273
303
|
.knowledge-outcome .knowledge-detail-empty,
|
|
274
304
|
.knowledge-outcome .knowledge-unknown-copy { color: var(--knowledge-muted); padding: 1.1rem .25rem; font-size: .8rem; }
|
|
305
|
+
.knowledge-outcome .knowledge-selection-guide { padding-top: .25rem; }
|
|
306
|
+
.knowledge-outcome .knowledge-selection-guide h4 { margin-top: 1.1rem; color: var(--knowledge-muted); }
|
|
307
|
+
.knowledge-outcome .knowledge-selection-guide-list { display: grid; gap: .45rem; margin: .55rem 0 0; padding: 0 0 0 1.1rem; color: var(--knowledge-ink); font-size: .78rem; }
|
|
275
308
|
.knowledge-outcome .knowledge-skeleton-row { display: grid; grid-template-columns: 4rem minmax(0, 1fr) 3rem; gap: .6rem; padding: .8rem .35rem; }
|
|
276
309
|
.knowledge-outcome .knowledge-skeleton-line { display: block; height: .72rem; border-radius: 999px; background: linear-gradient(90deg, var(--knowledge-canvas), #fff, var(--knowledge-canvas)); background-size: 220% 100%; animation: knowledge-shimmer 1.4s linear infinite; }
|
|
277
310
|
.knowledge-outcome .knowledge-skeleton-line.short { width: 4rem; }
|
|
@@ -290,7 +323,7 @@
|
|
|
290
323
|
border-radius: 0;
|
|
291
324
|
background: transparent;
|
|
292
325
|
}
|
|
293
|
-
.knowledge-outcome .knowledge-detail-panel > h3 { margin-top: .15rem; }
|
|
326
|
+
.knowledge-outcome .knowledge-detail-panel > h3 { margin-top: .15rem; overflow-wrap: anywhere; }
|
|
294
327
|
.knowledge-outcome .knowledge-detail-panel > .knowledge-eyebrow { margin-top: .15rem; }
|
|
295
328
|
.knowledge-outcome .knowledge-definition { display: grid; grid-template-columns: minmax(5rem, .48fr) minmax(0, 1fr); gap: .3rem .7rem; margin: .95rem 0; font-size: .76rem; }
|
|
296
329
|
.knowledge-outcome .knowledge-definition dt { color: var(--knowledge-muted); }
|
|
@@ -379,6 +412,7 @@
|
|
|
379
412
|
.knowledge-outcome .knowledge-outcome-body { grid-template-columns: minmax(0, 1fr); }
|
|
380
413
|
.knowledge-outcome .knowledge-outcome-primary { padding-right: 0; }
|
|
381
414
|
.knowledge-outcome .knowledge-outcome-detail { margin-top: 1.4rem; padding: 1.15rem 0 0; border-top: 1px solid var(--knowledge-line); border-left: 0; }
|
|
415
|
+
.knowledge-outcome .knowledge-detail-jump { display: inline-flex; align-items: center; min-height: 2.75rem; }
|
|
382
416
|
}
|
|
383
417
|
|
|
384
418
|
@media (max-width: 700px) {
|
|
@@ -387,10 +421,6 @@
|
|
|
387
421
|
.knowledge-outcome .knowledge-filters,
|
|
388
422
|
.knowledge-outcome .knowledge-comparison { grid-template-columns: minmax(0, 1fr); }
|
|
389
423
|
.knowledge-outcome .knowledge-filter-actions { grid-column: auto; }
|
|
390
|
-
.knowledge-outcome .knowledge-list-row { grid-template-columns: minmax(0, 1fr) auto; }
|
|
391
|
-
.knowledge-outcome .knowledge-row-type,
|
|
392
|
-
.knowledge-outcome .knowledge-row-scope { grid-column: auto; }
|
|
393
|
-
.knowledge-outcome .knowledge-row-state { grid-column: 2; grid-row: 1; }
|
|
394
424
|
.knowledge-outcome .knowledge-preview-result { grid-template-columns: minmax(0, 1fr) auto; }
|
|
395
425
|
.knowledge-outcome .knowledge-preview-result > :last-child { grid-column: 1 / -1; }
|
|
396
426
|
}
|
package/ui/outcome-knowledge.js
CHANGED
|
@@ -66,12 +66,67 @@ const STATE_LABELS = Object.freeze({
|
|
|
66
66
|
const TYPE_LABELS = Object.freeze({
|
|
67
67
|
fact: '事実',
|
|
68
68
|
decision: '判断',
|
|
69
|
+
judgment: '判断',
|
|
70
|
+
knowledge: '知識',
|
|
69
71
|
principle: '原則',
|
|
70
72
|
procedure: '手順',
|
|
71
73
|
term: '用語',
|
|
72
74
|
policy: '方針',
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
const SCOPE_LABELS = Object.freeze({
|
|
78
|
+
project: 'このプロジェクト',
|
|
79
|
+
organization: '組織共通',
|
|
80
|
+
org: '組織共通',
|
|
81
|
+
all: 'すべて',
|
|
82
|
+
personal: '個人',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const LIFECYCLE_STATUS_LABELS = Object.freeze({
|
|
86
|
+
active: '有効',
|
|
87
|
+
inactive: '無効',
|
|
88
|
+
retired: '失効',
|
|
89
|
+
superseded: '置換済み',
|
|
90
|
+
draft: '下書き',
|
|
91
|
+
candidate: '未確定候補',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const SOURCE_KIND_LABELS = Object.freeze({
|
|
95
|
+
team_document: 'チーム文書',
|
|
96
|
+
document: '文書',
|
|
97
|
+
doc: '文書',
|
|
98
|
+
meeting: '会議',
|
|
99
|
+
repository: 'リポジトリ',
|
|
100
|
+
url: 'Webページ',
|
|
101
|
+
fixture: '検証用データ',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const APPLICABILITY_STATE_LABELS = Object.freeze({
|
|
105
|
+
unknown: '未確認',
|
|
106
|
+
conditional: '条件付き',
|
|
107
|
+
always: '常時適用',
|
|
108
|
+
unconditional: '常時適用',
|
|
109
|
+
applicable: '適用',
|
|
110
|
+
not_applicable: '対象外',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generic labels for the knowledge workspace. Hosts may override these
|
|
115
|
+
* labels, but the shared UI remains usable without organization-specific
|
|
116
|
+
* wording.
|
|
117
|
+
*/
|
|
118
|
+
export const KNOWLEDGE_UI_LABELS = Object.freeze({
|
|
119
|
+
discovery: '探す',
|
|
120
|
+
capture: '登録案',
|
|
121
|
+
preview: '公開前テスト',
|
|
122
|
+
source: '保存先',
|
|
123
|
+
inspector: '選択中の知識',
|
|
124
|
+
selected: '選択中',
|
|
125
|
+
unselected: '未選択',
|
|
126
|
+
emptyInspectorTitle: '一覧から知識・判断を選択',
|
|
127
|
+
emptyInspectorDescription: '本文・出典・使える範囲・これまでの変更を確認できます。',
|
|
128
|
+
});
|
|
129
|
+
|
|
75
130
|
function getDocument() {
|
|
76
131
|
if (typeof document === 'undefined' || !document?.createElement) throw new Error('document_unavailable');
|
|
77
132
|
return document;
|
|
@@ -102,6 +157,11 @@ function clear(parent) {
|
|
|
102
157
|
return parent;
|
|
103
158
|
}
|
|
104
159
|
|
|
160
|
+
function focusKnowledgeDetail() {
|
|
161
|
+
const detail = globalThis.document?.getElementById?.('knowledge-selected-detail');
|
|
162
|
+
detail?.focus?.({ preventScroll: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
105
165
|
function text(value, fallback = UNKNOWN) {
|
|
106
166
|
if (value === null || value === undefined) return fallback;
|
|
107
167
|
if (typeof value === 'string') return value.trim() || fallback;
|
|
@@ -114,6 +174,14 @@ function optionalText(value) {
|
|
|
114
174
|
return typeof value === 'string' ? value : String(value);
|
|
115
175
|
}
|
|
116
176
|
|
|
177
|
+
function resolveKnowledgeUiLabels(value) {
|
|
178
|
+
const source = value && typeof value === 'object' ? value : {};
|
|
179
|
+
return Object.fromEntries(Object.entries(KNOWLEDGE_UI_LABELS).map(([key, fallback]) => {
|
|
180
|
+
const candidate = optionalText(source[key]).trim();
|
|
181
|
+
return [key, candidate || fallback];
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
|
|
117
185
|
function asArray(value) {
|
|
118
186
|
return Array.isArray(value) ? value : null;
|
|
119
187
|
}
|
|
@@ -123,6 +191,26 @@ function typeLabel(value) {
|
|
|
123
191
|
return TYPE_LABELS[normalized] ?? text(value);
|
|
124
192
|
}
|
|
125
193
|
|
|
194
|
+
function scopeLabel(value) {
|
|
195
|
+
const normalized = optionalText(value).trim().toLowerCase();
|
|
196
|
+
return SCOPE_LABELS[normalized] ?? text(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function lifecycleStatusLabel(value) {
|
|
200
|
+
const normalized = optionalText(value).trim().toLowerCase();
|
|
201
|
+
return LIFECYCLE_STATUS_LABELS[normalized] ?? text(value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function sourceKindLabel(value) {
|
|
205
|
+
const normalized = optionalText(value).trim().toLowerCase();
|
|
206
|
+
return SOURCE_KIND_LABELS[normalized] ?? text(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function applicabilityStateLabel(value) {
|
|
210
|
+
const normalized = optionalText(value).trim().toLowerCase();
|
|
211
|
+
return APPLICABILITY_STATE_LABELS[normalized] ?? text(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
126
214
|
function stateLabel(value) {
|
|
127
215
|
return STATE_LABELS[value] ?? (value ? text(value) : UNKNOWN);
|
|
128
216
|
}
|
|
@@ -472,7 +560,7 @@ function renderSource(parent, source) {
|
|
|
472
560
|
? makeElement('a', { className: 'knowledge-source-link', text: source.pointer, attrs: { href: pointer, target: '_blank', rel: 'noopener noreferrer' } })
|
|
473
561
|
: makeElement('span', { className: 'knowledge-source-link', text: source?.pointer || UNKNOWN });
|
|
474
562
|
append(section, renderDefinitionList(makeElement('div'), [
|
|
475
|
-
['種別',
|
|
563
|
+
['種別', sourceKindLabel(source?.kind)],
|
|
476
564
|
['本文', source?.state === 'fetched' ? '取得済み' : source?.state === 'pointer_only' ? '参照先のみ(本文未取得)' : '本文取得状態未確認'],
|
|
477
565
|
]));
|
|
478
566
|
const pointerRow = makeElement('p', { className: 'knowledge-source-pointer' });
|
|
@@ -494,11 +582,11 @@ export function renderDiscoveryFilters(root, filters = {}, callbacks = {}) {
|
|
|
494
582
|
const queryLabel = makeElement('label', { text: 'キーワード' });
|
|
495
583
|
const query = makeElement('input', { className: 'knowledge-control', value: filters.q ?? filters.query ?? '', attrs: { type: 'search', name: 'q', maxlength: '200', placeholder: 'タイトル・要旨を検索' } });
|
|
496
584
|
queryLabel.append(query);
|
|
497
|
-
const
|
|
585
|
+
const scopeFieldLabel = makeElement('label', { text: '範囲' });
|
|
498
586
|
const scope = makeElement('select', { className: 'knowledge-control', attrs: { name: 'scope' } });
|
|
499
|
-
for (const
|
|
587
|
+
for (const value of ['project', 'organization', 'all']) append(scope, makeElement('option', { text: scopeLabel(value), value, attrs: { value, ...(filters.scope === value ? { selected: 'selected' } : {}) } }));
|
|
500
588
|
scope.value = filters.scope || 'all';
|
|
501
|
-
|
|
589
|
+
scopeFieldLabel.append(scope);
|
|
502
590
|
const statusLabel = makeElement('label', { text: '状態' });
|
|
503
591
|
const status = makeElement('select', { className: 'knowledge-control', attrs: { name: 'status' } });
|
|
504
592
|
for (const [value, label] of [['active', '有効'], ['inactive', '失効を含む'], ['all', 'すべて']]) append(status, makeElement('option', { text: label, value, attrs: { value, ...(filters.status === value ? { selected: 'selected' } : {}) } }));
|
|
@@ -506,7 +594,7 @@ export function renderDiscoveryFilters(root, filters = {}, callbacks = {}) {
|
|
|
506
594
|
statusLabel.append(status);
|
|
507
595
|
const submit = makeElement('button', { className: 'knowledge-button primary', text: '探す', attrs: { type: 'submit' } });
|
|
508
596
|
const reset = makeElement('button', { className: 'knowledge-button secondary', text: '条件をクリア', attrs: { type: 'button' } });
|
|
509
|
-
append(form, queryLabel,
|
|
597
|
+
append(form, queryLabel, scopeFieldLabel, statusLabel, makeElement('div', { className: 'knowledge-filter-actions' }));
|
|
510
598
|
form.lastChild.append(submit, reset);
|
|
511
599
|
form.addEventListener('submit', (event) => {
|
|
512
600
|
event.preventDefault();
|
|
@@ -525,10 +613,13 @@ export function renderDiscoveryFilters(root, filters = {}, callbacks = {}) {
|
|
|
525
613
|
export function renderKnowledgeList(root, model = {}, callbacks = {}) {
|
|
526
614
|
clear(root);
|
|
527
615
|
const collection = model.records === undefined && (model.items || model.results || model.state) ? normalizeKnowledgeCollection(model) : model;
|
|
616
|
+
const searchedScope = Array.isArray(collection.searched_scope) && collection.searched_scope.length
|
|
617
|
+
? collection.searched_scope.map(scopeLabel).join(' / ')
|
|
618
|
+
: null;
|
|
528
619
|
const section = makeElement('section', { className: 'knowledge-list-panel', attrs: { 'aria-labelledby': 'knowledge-list-title' } });
|
|
529
620
|
const header = makeElement('div', { className: 'knowledge-panel-head' });
|
|
530
621
|
append(header, makeElement('div', {},), makeElement('span', { className: 'knowledge-count', text: Array.isArray(collection.records) ? `${collection.records.length}件` : UNKNOWN }));
|
|
531
|
-
header.firstChild.append(makeElement('h3', { text: '知識・判断', attrs: { id: 'knowledge-list-title' } }), makeElement('p', { text:
|
|
622
|
+
header.firstChild.append(makeElement('h3', { text: '知識・判断', attrs: { id: 'knowledge-list-title' } }), makeElement('p', { text: searchedScope ? `検索範囲: ${searchedScope}` : '検索範囲: 未確認' }));
|
|
532
623
|
section.append(header);
|
|
533
624
|
const state = normaliseStatus(collection.state);
|
|
534
625
|
if (state === 'loading') {
|
|
@@ -545,18 +636,37 @@ export function renderKnowledgeList(root, model = {}, callbacks = {}) {
|
|
|
545
636
|
section.append(statusNotice({ state: 'unknown', detail: '結果の有無を確認できません。空の一覧とは扱いません。' }, { retry: true, onRetry: callbacks.onRetry }));
|
|
546
637
|
} else {
|
|
547
638
|
const rows = makeElement('div', { className: 'knowledge-list-rows' });
|
|
639
|
+
const hasSelectedId = callbacks.selectedId !== undefined && callbacks.selectedId !== null;
|
|
548
640
|
if (!collection.records.length) rows.append(makeElement('div', { className: 'knowledge-empty', text: '検索結果を確認できません。空の一覧とは扱いません。' }));
|
|
549
641
|
for (const item of collection.records) {
|
|
550
|
-
const row = makeElement('button', { className: 'knowledge-list-row', attrs: { type: 'button', 'aria-pressed': String(item.id === callbacks.selectedId) } });
|
|
551
642
|
const title = item.title ? text(item.title) : 'タイトル未確認';
|
|
552
|
-
|
|
553
|
-
|
|
643
|
+
const row = makeElement('button', { className: 'knowledge-list-row', attrs: { type: 'button', 'aria-pressed': String(item.id === callbacks.selectedId), 'aria-controls': 'knowledge-selected-detail', title } });
|
|
644
|
+
const main = makeElement('span', { className: 'knowledge-row-main' });
|
|
645
|
+
const meta = makeElement('span', { className: 'knowledge-row-meta' });
|
|
646
|
+
append(meta,
|
|
554
647
|
makeElement('span', { className: 'knowledge-row-type', text: item.type ? typeLabel(item.type) : UNKNOWN }),
|
|
555
|
-
makeElement('span', { className: 'knowledge-row-scope', text: item.scope ?
|
|
556
|
-
makeElement('span', { className: 'knowledge-row-state', text: item.lifecycle.status !== UNKNOWN ?
|
|
648
|
+
makeElement('span', { className: 'knowledge-row-scope', text: item.scope ? scopeLabel(item.scope) : UNKNOWN }),
|
|
649
|
+
makeElement('span', { className: 'knowledge-row-state', text: item.lifecycle.status !== UNKNOWN ? lifecycleStatusLabel(item.lifecycle.status) : UNKNOWN }),
|
|
650
|
+
);
|
|
651
|
+
append(main,
|
|
652
|
+
makeElement('span', { className: 'knowledge-row-title', text: title, attrs: { title } }),
|
|
653
|
+
meta,
|
|
654
|
+
);
|
|
655
|
+
append(row,
|
|
656
|
+
main,
|
|
657
|
+
makeElement('span', { className: 'knowledge-row-chevron', text: '›', attrs: { 'aria-hidden': 'true' } }),
|
|
557
658
|
);
|
|
558
659
|
row.addEventListener('click', () => callbacks.onSelect?.(item));
|
|
559
660
|
rows.append(row);
|
|
661
|
+
if (hasSelectedId && item.id === callbacks.selectedId) {
|
|
662
|
+
const jump = makeElement('a', {
|
|
663
|
+
className: 'knowledge-detail-jump',
|
|
664
|
+
text: '選択した知識の詳細へ',
|
|
665
|
+
attrs: { href: '#knowledge-selected-detail', 'aria-controls': 'knowledge-selected-detail' },
|
|
666
|
+
});
|
|
667
|
+
jump.addEventListener('click', () => focusKnowledgeDetail());
|
|
668
|
+
rows.append(jump);
|
|
669
|
+
}
|
|
560
670
|
}
|
|
561
671
|
section.append(rows);
|
|
562
672
|
}
|
|
@@ -566,9 +676,41 @@ export function renderKnowledgeList(root, model = {}, callbacks = {}) {
|
|
|
566
676
|
|
|
567
677
|
export function renderKnowledgeDetail(root, item, callbacks = {}) {
|
|
568
678
|
clear(root);
|
|
569
|
-
const
|
|
679
|
+
const labels = resolveKnowledgeUiLabels(callbacks.labels);
|
|
680
|
+
const showSelectionGuide = callbacks.showSelectionGuide !== false;
|
|
681
|
+
const detailState = callbacks.detailState === undefined
|
|
682
|
+
? (item ? 'ready' : 'idle')
|
|
683
|
+
: normaliseStatus(callbacks.detailState?.state ?? callbacks.detailState);
|
|
684
|
+
const hasDetailHeading = (detailState === 'ready' && Boolean(item))
|
|
685
|
+
|| (!item && showSelectionGuide);
|
|
686
|
+
const section = makeElement('section', {
|
|
687
|
+
className: `knowledge-detail-panel${!item && showSelectionGuide ? ' knowledge-detail-panel-empty' : ''}`,
|
|
688
|
+
attrs: hasDetailHeading ? { 'aria-labelledby': 'knowledge-detail-title' } : { 'aria-label': '知識の詳細' },
|
|
689
|
+
});
|
|
690
|
+
if (detailState === 'loading') {
|
|
691
|
+
section.append(makeElement('p', { className: 'knowledge-inline-status', text: '正本の詳細と履歴を読み込み中です。', attrs: { role: 'status' } }));
|
|
692
|
+
root.append(section);
|
|
693
|
+
return section;
|
|
694
|
+
}
|
|
695
|
+
if (['error', 'error_retryable', 'permission_denied', 'unknown'].includes(detailState)) {
|
|
696
|
+
section.append(statusNotice(callbacks.detailState, { retry: Boolean(callbacks.onRetry), onRetry: callbacks.onRetry }));
|
|
697
|
+
section.append(makeElement('p', { className: 'knowledge-warning-copy', text: '正本の詳細を確認できないため、版の変更や失効は実行できません。' }));
|
|
698
|
+
root.append(section);
|
|
699
|
+
return section;
|
|
700
|
+
}
|
|
570
701
|
if (!item) {
|
|
571
|
-
|
|
702
|
+
if (showSelectionGuide) {
|
|
703
|
+
const guide = makeElement('section', { className: 'knowledge-selection-guide', attrs: { 'aria-labelledby': 'knowledge-selection-guide-title' } });
|
|
704
|
+
append(guide,
|
|
705
|
+
makeElement('h3', { text: labels.emptyInspectorTitle, attrs: { id: 'knowledge-detail-title' } }),
|
|
706
|
+
makeElement('p', { className: 'knowledge-detail-empty', text: labels.emptyInspectorDescription }),
|
|
707
|
+
makeElement('h4', { text: '選択すると確認できること', attrs: { id: 'knowledge-selection-guide-title' } }),
|
|
708
|
+
);
|
|
709
|
+
const guideItems = makeElement('ul', { className: 'knowledge-selection-guide-list' });
|
|
710
|
+
for (const label of ['内容を読む', '出典と使える範囲を確かめる', '変更の履歴を見る']) guideItems.append(makeElement('li', { text: label }));
|
|
711
|
+
guide.append(guideItems);
|
|
712
|
+
section.append(guide);
|
|
713
|
+
}
|
|
572
714
|
root.append(section);
|
|
573
715
|
return section;
|
|
574
716
|
}
|
|
@@ -581,10 +723,10 @@ export function renderKnowledgeDetail(root, item, callbacks = {}) {
|
|
|
581
723
|
renderDefinitionList(section, [
|
|
582
724
|
['正本ID', item.id ? text(item.id) : UNKNOWN],
|
|
583
725
|
['版', item.version !== null && item.version !== undefined && String(item.version).trim() !== '' ? String(item.version) : UNKNOWN],
|
|
584
|
-
['範囲', item.scope ?
|
|
726
|
+
['範囲', item.scope ? scopeLabel(item.scope) : UNKNOWN],
|
|
585
727
|
['責任者', ownerValue(item.owner)],
|
|
586
|
-
['有効状態', item.lifecycle.status],
|
|
587
|
-
['適用', item.applicability.state ?
|
|
728
|
+
['有効状態', lifecycleStatusLabel(item.lifecycle.status)],
|
|
729
|
+
['適用', item.applicability.state ? applicabilityStateLabel(item.applicability.state) : UNKNOWN],
|
|
588
730
|
['発効', displayDateTime(item.lifecycle.effective_at)],
|
|
589
731
|
['失効', displayDateTime(item.lifecycle.expires_at)],
|
|
590
732
|
['更新', displayDateTime(item.updated_at)],
|
|
@@ -822,7 +964,7 @@ export function renderLifecycle(root, item, callbacks = {}) {
|
|
|
822
964
|
const section = makeElement('section', { className: 'knowledge-lifecycle-panel', attrs: { 'aria-labelledby': 'knowledge-lifecycle-title' } });
|
|
823
965
|
append(section, makeElement('h3', { text: '版と失効', attrs: { id: 'knowledge-lifecycle-title' } }), makeElement('p', { text: '改訂・置換・失効は履歴を残し、正本のreadback確認後に状態を更新します。' }));
|
|
824
966
|
if (!item) { section.append(makeElement('p', { text: '対象の判断を選択してください。' })); root.append(section); return section; }
|
|
825
|
-
renderDefinitionList(section, [['現在の版', item.version !== null && item.version !== undefined ? String(item.version) : UNKNOWN], ['状態', item.lifecycle?.status
|
|
967
|
+
renderDefinitionList(section, [['現在の版', item.version !== null && item.version !== undefined ? String(item.version) : UNKNOWN], ['状態', lifecycleStatusLabel(item.lifecycle?.status)], ['正本ID', item.id ? text(item.id) : UNKNOWN]]);
|
|
826
968
|
const canEdit = callbacks.canEdit !== false && (typeof callbacks.canEdit !== 'function' || callbacks.canEdit(item));
|
|
827
969
|
const actions = makeElement('div', { className: 'knowledge-lifecycle-actions' });
|
|
828
970
|
if (canEdit && callbacks.onRevision) {
|
|
@@ -1053,11 +1195,15 @@ function defaultCandidateBody(values, context = {}) {
|
|
|
1053
1195
|
* project code; only a `true` result shows the registration form. Without
|
|
1054
1196
|
* it, the form follows `canManageKnowledge(session)`. Revision,
|
|
1055
1197
|
* supersession, and retirement always follow `canManageKnowledge`.
|
|
1198
|
+
* @param {Partial<typeof KNOWLEDGE_UI_LABELS>} [options.labels]
|
|
1199
|
+
* Optional host labels for the workspace regions. Unspecified labels use
|
|
1200
|
+
* the generic Japanese defaults.
|
|
1056
1201
|
*/
|
|
1057
1202
|
export function createKnowledgeOutcomeController(options = {}) {
|
|
1058
1203
|
const { root, api, apiMutation, project, session, onStateChange, onCommitted, onRetired, resolveDecisionDomain, serializeDraftInput, serializeCandidate, serializeRevision, serializeRetire, serializePreview } = options;
|
|
1059
1204
|
if (!root || typeof root.replaceChildren !== 'function') throw new Error('knowledge_root_unavailable');
|
|
1060
1205
|
const code = projectCodeOf(project);
|
|
1206
|
+
const labels = resolveKnowledgeUiLabels(options.labels);
|
|
1061
1207
|
const canEditDestination = () => (typeof options.canEditDestination === 'function'
|
|
1062
1208
|
? options.canEditDestination((typeof session === 'function' ? session() : session) ?? null, code ?? null) === true
|
|
1063
1209
|
: canManageKnowledge(session));
|
|
@@ -1082,6 +1228,7 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1082
1228
|
authority: { state: 'loading', domains: null },
|
|
1083
1229
|
destination: { state: 'unknown', registration: null, draft: {}, message: '保存先APIの契約を確認できていません。' },
|
|
1084
1230
|
};
|
|
1231
|
+
let selectionGeneration = 0;
|
|
1085
1232
|
|
|
1086
1233
|
function resumeUrl(draftId) {
|
|
1087
1234
|
const location = options.location ?? globalThis.location;
|
|
@@ -1121,20 +1268,20 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1121
1268
|
const discoveryHeader = makeElement('div', { className: 'knowledge-view-heading' });
|
|
1122
1269
|
append(discoveryHeader,
|
|
1123
1270
|
makeElement('div', {},),
|
|
1124
|
-
makeElement('span', { className: 'knowledge-view-kicker', text:
|
|
1271
|
+
makeElement('span', { className: 'knowledge-view-kicker', text: labels.discovery }),
|
|
1125
1272
|
);
|
|
1126
|
-
discoveryHeader.firstChild.append(makeElement('h3', { id: 'knowledge-discovery-title', text: '知識・判断を探す' }), makeElement('p', { text: '
|
|
1273
|
+
discoveryHeader.firstChild.append(makeElement('h3', { id: 'knowledge-discovery-title', text: '知識・判断を探す' }), makeElement('p', { text: '選んだ知識の本文・出典・履歴を確認できます。' }));
|
|
1127
1274
|
discovery.append(discoveryHeader);
|
|
1128
1275
|
const filters = makeElement('div', { className: 'knowledge-filter-region knowledge-discovery-toolbar' });
|
|
1129
1276
|
renderDiscoveryFilters(filters, state.filters, { onChange: (next) => { state.filters = next; void loadItems(); }, });
|
|
1130
1277
|
discovery.append(filters);
|
|
1131
1278
|
const list = makeElement('div', { className: 'knowledge-discovery-list' });
|
|
1132
|
-
renderKnowledgeList(list, state.list, { selectedId: state.selected?.id, onSelect: (item) => {
|
|
1279
|
+
renderKnowledgeList(list, state.list, { labels, selectedId: state.selected?.id, onSelect: (item) => { void selectItem(item); }, onRetry: () => loadItems() });
|
|
1133
1280
|
discovery.append(list);
|
|
1134
1281
|
primary.append(discovery);
|
|
1135
1282
|
const captureWorkspace = makeElement('section', { className: 'knowledge-focused-view knowledge-capture-view', hidden: state.view !== 'capture', attrs: { 'aria-labelledby': 'knowledge-capture-workspace-title' } });
|
|
1136
1283
|
const captureHeading = makeElement('div', { className: 'knowledge-view-heading' });
|
|
1137
|
-
append(captureHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text:
|
|
1284
|
+
append(captureHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text: labels.capture }));
|
|
1138
1285
|
captureHeading.firstChild.append(makeElement('h3', { id: 'knowledge-capture-workspace-title', text: '登録案を作る' }), makeElement('p', { text: '自然文を候補へ整理し、正本へ保存する前に内容と根拠を確認します。' }));
|
|
1139
1286
|
captureWorkspace.append(captureHeading);
|
|
1140
1287
|
const capture = makeElement('div', { className: 'knowledge-capture-form-region' });
|
|
@@ -1154,7 +1301,7 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1154
1301
|
primary.append(captureWorkspace);
|
|
1155
1302
|
const preview = makeElement('section', { className: 'knowledge-focused-view knowledge-preview-view', hidden: state.view !== 'preview', attrs: { 'aria-labelledby': 'knowledge-preview-workspace-title' } });
|
|
1156
1303
|
const previewHeading = makeElement('div', { className: 'knowledge-view-heading' });
|
|
1157
|
-
append(previewHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text:
|
|
1304
|
+
append(previewHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text: labels.preview }));
|
|
1158
1305
|
previewHeading.firstChild.append(makeElement('h3', { id: 'knowledge-preview-workspace-title', text: '公開前テスト' }), makeElement('p', { text: '隔離した質問で候補の使われ方と根拠を確認します。' }));
|
|
1159
1306
|
preview.append(previewHeading);
|
|
1160
1307
|
const previewContent = makeElement('div', { className: 'knowledge-preview-content' });
|
|
@@ -1163,7 +1310,7 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1163
1310
|
primary.append(preview);
|
|
1164
1311
|
const destination = makeElement('section', { className: 'knowledge-focused-view knowledge-destination-view', hidden: state.view !== 'destination', attrs: { 'aria-labelledby': 'knowledge-destination-workspace-title' } });
|
|
1165
1312
|
const destinationHeading = makeElement('div', { className: 'knowledge-view-heading' });
|
|
1166
|
-
append(destinationHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text:
|
|
1313
|
+
append(destinationHeading, makeElement('div', {},), makeElement('span', { className: 'knowledge-view-kicker', text: labels.source }));
|
|
1167
1314
|
destinationHeading.firstChild.append(makeElement('h3', { id: 'knowledge-destination-workspace-title', text: '知識文書の保存先' }), makeElement('p', { text: '正本ドキュメントの場所を登録し、保存後の状態を再取得して確認します。' }));
|
|
1168
1315
|
destination.append(destinationHeading);
|
|
1169
1316
|
const destinationContent = makeElement('div', { className: 'knowledge-destination-content' });
|
|
@@ -1175,21 +1322,40 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1175
1322
|
destination.append(destinationContent);
|
|
1176
1323
|
primary.append(destination);
|
|
1177
1324
|
body.append(primary);
|
|
1178
|
-
const detail = makeElement('aside', {
|
|
1325
|
+
const detail = makeElement('aside', {
|
|
1326
|
+
className: 'knowledge-outcome-detail knowledge-inspector',
|
|
1327
|
+
attrs: {
|
|
1328
|
+
'aria-label': labels.inspector,
|
|
1329
|
+
id: 'knowledge-selected-detail',
|
|
1330
|
+
tabindex: '-1',
|
|
1331
|
+
},
|
|
1332
|
+
});
|
|
1179
1333
|
const inspectorHeading = makeElement('div', { className: 'knowledge-inspector-heading' });
|
|
1180
|
-
append(inspectorHeading, makeElement('span', { className: 'knowledge-view-kicker', text:
|
|
1334
|
+
append(inspectorHeading, makeElement('span', { className: 'knowledge-view-kicker', text: labels.inspector }), makeElement('span', { className: 'knowledge-inspector-state', text: state.selected ? labels.selected : labels.unselected }));
|
|
1181
1335
|
detail.append(inspectorHeading);
|
|
1182
1336
|
const detailContent = makeElement('div', { className: 'knowledge-inspector-detail' });
|
|
1183
|
-
|
|
1337
|
+
const hasSelectableKnowledge = normaliseStatus(state.list.state) === 'ready' && Array.isArray(state.list.records) && state.list.records.length > 0;
|
|
1338
|
+
renderKnowledgeDetail(detailContent, state.selected, {
|
|
1339
|
+
labels,
|
|
1340
|
+
detailState: state.detail,
|
|
1341
|
+
showSelectionGuide: Boolean(state.selected) || hasSelectableKnowledge,
|
|
1342
|
+
onRetry: state.selected ? () => void selectItem(state.selected.id) : null,
|
|
1343
|
+
onPreview: (item) => { state.preview = { state: 'idle', question: '', context: '', item_id: item.id, candidate_version: item.version, item_version: item.version }; state.view = 'preview'; render(); },
|
|
1344
|
+
});
|
|
1184
1345
|
detail.append(detailContent);
|
|
1185
1346
|
const lifecycle = makeElement('div', { className: 'knowledge-inspector-lifecycle' });
|
|
1186
|
-
|
|
1347
|
+
const detailUnavailable = ['loading', 'error', 'error_retryable', 'permission_denied', 'unknown'].includes(state.detail.state);
|
|
1348
|
+
if (state.selected && !detailUnavailable) renderLifecycle(lifecycle, state.selected, { canEdit: canManageKnowledge(session), ownerCandidates, revisionDraft: state.lifecycle.revisionDraft, supersessionDraft: state.lifecycle.supersessionDraft, supersessionCandidates: state.list.records, onRevision: reviseItem, onSupersede: supersedeItem, onRetire: retireItem }); detail.append(lifecycle);
|
|
1187
1349
|
if (state.commit.state !== 'idle') { const commit = makeElement('div'); renderCommitState(commit, state.commit, { onReadback: state.commit.pending_save ? retryPendingSave : readbackCommit }); detail.append(commit); }
|
|
1188
1350
|
body.append(detail); shell.append(body); root.append(shell); notify();
|
|
1189
1351
|
return shell;
|
|
1190
1352
|
}
|
|
1191
1353
|
|
|
1192
1354
|
async function loadItems() {
|
|
1355
|
+
selectionGeneration += 1;
|
|
1356
|
+
state.selected = null;
|
|
1357
|
+
state.detail = { state: 'idle' };
|
|
1358
|
+
state.lifecycle = { state: 'idle' };
|
|
1193
1359
|
state.list = { ...state.list, state: 'loading' }; render();
|
|
1194
1360
|
try { state.list = normalizeKnowledgeCollection(await callKnowledgeApi(api, buildKnowledgeItemsPath(code, state.filters))); }
|
|
1195
1361
|
catch (error) { state.list = { state: error.code === 'permission_denied' || error.status === 403 ? 'permission_denied' : 'error_retryable', records: null, message: apiErrorText(error), error: error.code }; }
|
|
@@ -1251,17 +1417,26 @@ export function createKnowledgeOutcomeController(options = {}) {
|
|
|
1251
1417
|
}
|
|
1252
1418
|
|
|
1253
1419
|
async function selectItem(itemOrId) {
|
|
1420
|
+
const requestGeneration = ++selectionGeneration;
|
|
1254
1421
|
const id = typeof itemOrId === 'object' ? itemOrId?.id : itemOrId;
|
|
1255
1422
|
const listItem = typeof itemOrId === 'object' ? itemOrId : state.list.records?.find((item) => item.id === id);
|
|
1256
|
-
state.selected = listItem ?? null; state.detail = { state: 'loading' }; render();
|
|
1257
|
-
if (!id) {
|
|
1423
|
+
state.selected = listItem ?? null; state.detail = { state: 'loading' }; state.lifecycle = { state: 'idle' }; render();
|
|
1424
|
+
if (!id) {
|
|
1425
|
+
if (requestGeneration !== selectionGeneration) return null;
|
|
1426
|
+
state.detail = { state: 'unknown', message: '正本IDを確認できません。' }; render(); return null;
|
|
1427
|
+
}
|
|
1258
1428
|
try {
|
|
1259
1429
|
const payload = await callKnowledgeApi(api, knowledgePath(code, `${KNOWLEDGE_ROUTES.items}/${encodeURIComponent(id)}`));
|
|
1430
|
+
if (requestGeneration !== selectionGeneration) return null;
|
|
1260
1431
|
const historyPayload = await callKnowledgeApi(api, knowledgePath(code, `${KNOWLEDGE_ROUTES.items}/${encodeURIComponent(id)}/${KNOWLEDGE_ROUTES.history}`));
|
|
1432
|
+
if (requestGeneration !== selectionGeneration) return null;
|
|
1261
1433
|
const item = normalizeKnowledgeItem(payload?.item ?? payload?.record ?? payload?.data ?? payload) ?? listItem;
|
|
1262
1434
|
if (item) item.history = Array.isArray(historyPayload?.entries) ? historyPayload.entries : null;
|
|
1263
1435
|
state.selected = item; state.detail = { state: 'ready', item }; state.lifecycle = { state: 'ready', item }; render(); return item;
|
|
1264
|
-
} catch (error) {
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
if (requestGeneration !== selectionGeneration) return null;
|
|
1438
|
+
state.detail = { state: error.code === 'permission_denied' ? 'permission_denied' : 'error_retryable', message: apiErrorText(error), error: error.code }; render(); return null;
|
|
1439
|
+
}
|
|
1265
1440
|
}
|
|
1266
1441
|
|
|
1267
1442
|
async function createDraft(input) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Compact notices are projections of the same source-read states as the full rail.
|
|
2
|
+
* A task's needs_check is a record gap, never an unavailable read or failed job.
|
|
3
|
+
*/
|
|
4
|
+
import { readReasonText, shortTime } from './world-work-rail.js';
|
|
5
|
+
|
|
6
|
+
const READ_LABELS = Object.freeze({ tasks: '仕事', persons: '人物', decisions: '方針の決定', relations: '関係' });
|
|
7
|
+
const STATE_TEXT = Object.freeze({ partial: '一部だけ読めた', failed: '読込失敗', forbidden: '権限がなく読めない', not_connected: '未接続', unavailable: '読めない' });
|
|
8
|
+
|
|
9
|
+
export function workCanvasNotes(work) {
|
|
10
|
+
if (!work) return [];
|
|
11
|
+
if (work.status !== 'ok') {
|
|
12
|
+
const state = STATE_TEXT[work.status] ?? '読めない';
|
|
13
|
+
return [{ label: '区画の仕事', tone: 'warning', summary: `仕事:${state}(0件とは確認できません)`, text: `仕事は${state}。${readReasonText(work.reason) ?? '理由不明'}。仕事の件数は未確認です。0件とは確認できません。` }];
|
|
14
|
+
}
|
|
15
|
+
const notes = [];
|
|
16
|
+
for (const [key, label] of Object.entries(READ_LABELS)) {
|
|
17
|
+
const read = work.reads?.[key];
|
|
18
|
+
if (!read || read.state !== 'complete') {
|
|
19
|
+
const state = STATE_TEXT[read?.state] ?? '状態不明';
|
|
20
|
+
notes.push({ label: `区画の${label}`, tone: 'warning', summary: `${label}:${state}`, text: `${label}は${state}。${readReasonText(read?.reason) ?? '理由不明'}。読めていない記録は0件とは限りません。` });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const summary = work.summary;
|
|
24
|
+
if (summary?.state === 'complete' || summary?.state === 'partial') {
|
|
25
|
+
const needs = summary.needs_check?.length;
|
|
26
|
+
if (needs > 0) notes.push({ label: '記録の確認', tone: 'attention', summary: `記録の要確認 ${needs}件`, text: `読めた未完了の仕事のうち${needs}件に、記録から把握できていないことがあります。仕事が止まっているという意味ではありません。詳細の絞り込みから根拠を確認できます。` });
|
|
27
|
+
}
|
|
28
|
+
const at = work.reads?.tasks?.read_at;
|
|
29
|
+
notes.push({ label: '区画の時点', tone: 'info', text: `仕事の読み込み:${shortTime(at) ?? '時点不明'}。表示は読み込んだ時点の記録です。` });
|
|
30
|
+
return notes;
|
|
31
|
+
}
|