@takagaki/cortex-decisions-viewer 0.12.4 → 0.12.5
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/render.js +122 -564
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNSETTLED_LIMITS = exports.
|
|
3
|
+
exports.UNSETTLED_LIMITS = exports.LIVE_CONFIRM_LIMITS = void 0;
|
|
4
4
|
exports.renderSite = renderSite;
|
|
5
|
-
exports.livePending = livePending;
|
|
6
|
-
exports.nextCheckDelay = nextCheckDelay;
|
|
7
|
-
exports.settledPending = settledPending;
|
|
8
5
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
9
6
|
exports.sanitizeStateNow = sanitizeStateNow;
|
|
10
7
|
exports.overlayStateOnFleet = overlayStateOnFleet;
|
|
@@ -343,181 +340,49 @@ function backlogTargetOf(input) {
|
|
|
343
340
|
return num.test(view[1]) ? null : { domain: domain, projectKey: view[1].toUpperCase() };
|
|
344
341
|
return null;
|
|
345
342
|
}
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// 画面が「まだ答えが無い」あいだの見切り。
|
|
345
|
+
//
|
|
346
|
+
// **ここは純関数と定数だけを置く。** 画面側(クライアントJS)は toString() で実体を
|
|
347
|
+
// 流し込んで使い、テストは同じ実体を直接呼ぶ。クロージャの中に書くと DOM 経由でしか
|
|
348
|
+
// 触れず、判定を壊してもテストが気づけない。
|
|
349
|
+
//
|
|
350
|
+
// **クロージャを持たないこと**(外の変数を掴むと、流し込んだ先で undefined を参照して壊れる)。
|
|
351
|
+
// 参照してよいのは、クライアント側にも同名で注入している定数だけ。
|
|
352
|
+
//
|
|
353
|
+
// **「⏳ 反映中」の機構(Reflecting 型・REFLECT_OPS・livePending / settledPending /
|
|
354
|
+
// nextCheckDelay と localStorage の記録)は 2026-08-25 に退役した(#221)。**
|
|
355
|
+
// あれは「保存したのに一覧が変わらない」空白を埋めるためのもので、**埋める空白そのものが
|
|
356
|
+
// 無くなった**——画面は登録ファイルの現在値(registeredNow)を読み、足した行・消した行を
|
|
357
|
+
// その場で見せる。取り直しの間隔も未確定件数ベース(UNSETTLED_TTL_MS)へ移してある。
|
|
358
|
+
// 戻したくなったら、その削除コミットを revert すればよい。
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
346
360
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* 実測(cortex-context「セットアップ状況の自己チェック」の完了 run 112件、
|
|
350
|
-
* うち workflow_dispatch 51件):
|
|
351
|
-
*
|
|
352
|
-
* | | 全体(112) | dispatch のみ(51) |
|
|
353
|
-
* | min | 4秒 | 6秒 |
|
|
354
|
-
* | p50 | 41秒 | 47秒 |
|
|
355
|
-
* | p90 | 57秒 | 62秒 |
|
|
356
|
-
* | p95 | 65秒 | ― |
|
|
357
|
-
* | max | 400秒 | 400秒 |
|
|
361
|
+
* ライブ確認(op=state)の見切り(#256・設計v2 の1)。
|
|
358
362
|
*
|
|
359
|
-
*
|
|
360
|
-
* 10
|
|
363
|
+
* 通常応答は1〜2秒・Lambda のコールドスタートを含めても10秒で足りる。
|
|
364
|
+
* **10秒「確認中」が出続けるのは、嘘を断言するより許容できる。**
|
|
361
365
|
*
|
|
362
|
-
* 🔴
|
|
363
|
-
*
|
|
364
|
-
* 8%程度の確率で「15分間 反映中 → 未設定に戻る」が起きる(設計どおりの挙動)。
|
|
366
|
+
* 🔴 **これは「取り直しの予約」ではない。** 60秒の取り直し(UNSETTLED_POLL_MS)を
|
|
367
|
+
* 数えているテストは、この見切りを数から除くこと。
|
|
365
368
|
*/
|
|
366
|
-
const
|
|
367
|
-
/** 自動で取り直す間隔。60秒(op=state のキャッシュと同じ粒度) */
|
|
368
|
-
const REFLECT_POLL_MS = 60 * 1000;
|
|
369
|
+
const LIVE_CONFIRM_TIMEOUT_MS = 10000;
|
|
369
370
|
/**
|
|
370
|
-
*
|
|
371
|
+
* 見切りを**テストから固定するための入れ物**(infra の STATE_LIMITS と同じ作法)。
|
|
371
372
|
*
|
|
372
373
|
* ⚠ **定数そのものを export してはいけない。** `export const` は CommonJS へ落ちるときに
|
|
373
374
|
* 参照が `exports.X` へ書き換えられ、**toString() で流し込んだ先には exports が無いので
|
|
374
375
|
* クライアントJSが丸ごと落ちる**(実際に踏んだ)。流し込む関数から参照する値は、
|
|
375
376
|
* export しないローカルの const に置くこと。
|
|
376
377
|
*/
|
|
377
|
-
exports.REFLECTING_LIMITS = { ttlMs: REFLECT_TTL_MS, pollMs: REFLECT_POLL_MS };
|
|
378
|
-
/**
|
|
379
|
-
* ライブ確認(op=state)の見切り(#256・設計v2 の1)。
|
|
380
|
-
*
|
|
381
|
-
* 通常応答は1〜2秒・Lambda のコールドスタートを含めても10秒で足りる。
|
|
382
|
-
* **10秒「確認中」が出続けるのは、嘘を断言するより許容できる。**
|
|
383
|
-
*
|
|
384
|
-
* 🔴 **これは「取り直しの予約」ではない。** 60秒の取り直し(REFLECT_POLL_MS /
|
|
385
|
-
* UNSETTLED_POLL_MS)を数えているテストは、この見切りを数から除くこと。
|
|
386
|
-
*/
|
|
387
|
-
const LIVE_CONFIRM_TIMEOUT_MS = 10000;
|
|
388
|
-
/** 見切りを**テストから固定するための入れ物**(REFLECTING_LIMITS と同じ形) */
|
|
389
378
|
exports.LIVE_CONFIRM_LIMITS = { timeoutMs: LIVE_CONFIRM_TIMEOUT_MS };
|
|
390
|
-
/**
|
|
391
|
-
* 記録してよい操作の対応表。**既定は「記録しない」(null)。**
|
|
392
|
-
*
|
|
393
|
-
* 🔴 **settle できない操作を記録すると、期限まで「反映中」が出っぱなしになり、
|
|
394
|
-
* 本当の状態を隠す。** 「現れた/消えた」で判定できるものだけを載せる。
|
|
395
|
-
*
|
|
396
|
-
* @see CONFIG_OPS(infra/lambda/intake/logic.mjs)— **突き合わせるテストがある**。
|
|
397
|
-
* op を足してここに書き忘れると、その操作だけ黙って反映中にならない/
|
|
398
|
-
* settle できないものを記録して出っぱなしになる、という気づきにくい壊れ方をする
|
|
399
|
-
*/
|
|
400
|
-
exports.REFLECT_OPS = {
|
|
401
|
-
// ---- 記録する ----
|
|
402
|
-
// 接続先の追加・削除。一覧に「現れた/消えた」で反映を判定できる
|
|
403
|
-
"backlog-target-add": { cap: "課題管理", dir: "add", from: "url" },
|
|
404
|
-
// **削除はキーだけを送る**(サーバもキーだけで消す)ので、スペースは問わない
|
|
405
|
-
//
|
|
406
|
-
// ⚠ **最後の1件を外したときは、反映が届いても settle しない**(engine を読んで確認・2026-08-21)。
|
|
407
|
-
// engine の fleet-status は、正本(issues-config.json)が空になると
|
|
408
|
-
// 同期ミラー(issues/backlog-settings.json)の旧値へフォールバックし、
|
|
409
|
-
// backlogTarget に旧 domain / projectKey を載せ続ける(capabilities も on のまま)。
|
|
410
|
-
// ミラーは履歴保全のため**画面から外しても残す設計**なので、この値は消えない。
|
|
411
|
-
// つまり「消えた」を観測できるのは、ミラーが1つも無い案件(一度も同期していない)だけ。
|
|
412
|
-
// それ以外では**期限(15分)で落ちる**——嘘は残らないが、その間「反映中」と出る。
|
|
413
|
-
// 直すなら engine 側(backlogTarget.source === "secrets" かつ configPath === null を
|
|
414
|
-
// 「正本に接続先が無い」として画面に伝える)で、これは「未設定」の意味を変える
|
|
415
|
-
// 製品判断なので別issueにする。
|
|
416
|
-
"backlog-target-remove": { cap: "課題管理", dir: "remove", from: "backlogProjectKey" },
|
|
417
|
-
// ---- 記録しない ----
|
|
418
|
-
// **値の変更**。読み取りの ON/OFF は「現れた/消えた」では判定できない
|
|
419
|
-
"set-github-source-enabled": null,
|
|
420
|
-
// **表示名の取り直し**。sendConfig を通らない2本目の書き込み口でもある
|
|
421
|
-
"refresh-channel-name": null,
|
|
422
|
-
// 以下は add / remove の形だが、**その能力の「反映中」はまだ作っていない**。
|
|
423
|
-
// 追加フォームには既存の控え(.ds-pending-item「追加済み・反映中」)が出ており、
|
|
424
|
-
// 二重に出さないため、統合は別issueにする(設計 追補 中2)
|
|
425
|
-
"add-channel": null,
|
|
426
|
-
"remove-channel": null,
|
|
427
|
-
"add-github-source": null,
|
|
428
|
-
"remove-github-source": null,
|
|
429
|
-
"add-figma-file": null,
|
|
430
|
-
"remove-figma-file": null,
|
|
431
|
-
"add-drive-folder": null,
|
|
432
|
-
"remove-drive-folder": null,
|
|
433
|
-
// 1件だけ持てた頃の置き換え。いまの画面は出さない(backlog-target-add に統合済み)
|
|
434
|
-
"set-backlog-target": null,
|
|
435
|
-
};
|
|
436
|
-
/**
|
|
437
|
-
* 期限内のものだけ返す。
|
|
438
|
-
*
|
|
439
|
-
* 🔴 **経過時間(now - at)で判定すること。** 取り直した回数で数えると、
|
|
440
|
-
* スリープ復帰やバックグラウンドタブのスロットリングで期限が伸びる。
|
|
441
|
-
*
|
|
442
|
-
* 🔴 **経過時間は「上」だけでなく「下」も見る。** 上限(age < 期限)しか見ないと、
|
|
443
|
-
* **at が未来の記録は永久に期限が切れない**——
|
|
444
|
-
* - at = now + 1時間 … 1時間15分ずっと「反映中」
|
|
445
|
-
* - at = Infinity … 永久に「反映中」のまま60秒ごとに取り直し続ける
|
|
446
|
-
* 実在する経路: **端末の時計が進んだ状態で記録し、NTP で戻る**/別バージョンが書いた値/
|
|
447
|
-
* localStorage の手編集。「期限が必ず切れるから嘘は残らない」というこの設計の
|
|
448
|
-
* 唯一の安全弁が、入口で成立しなくなる。
|
|
449
|
-
*/
|
|
450
|
-
function livePending(list, now) {
|
|
451
|
-
const out = [];
|
|
452
|
-
for (const e of list || []) {
|
|
453
|
-
// NaN・欠けた at・未来の at・Infinity はここで落ちる
|
|
454
|
-
const age = e ? now - e.at : NaN;
|
|
455
|
-
if (!e || !Number.isFinite(age) || !(age >= 0 && age < REFLECT_TTL_MS))
|
|
456
|
-
continue;
|
|
457
|
-
out.push(e);
|
|
458
|
-
}
|
|
459
|
-
return out;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* 次に取り直すまでの ms。もう取り直す必要が無ければ null。
|
|
463
|
-
*
|
|
464
|
-
* 🔴 **最後の1回は期限ちょうどに来る**(min(60秒, 期限 − 経過))。
|
|
465
|
-
* 固定60秒で張ると、最後の確認が期限の60秒前になり、そこから期限までに反映が来ても
|
|
466
|
-
* 誰も見ない。さらに**期限そのものに誰も描き直さない**ので、「取り直しは止まったのに
|
|
467
|
-
* 反映中の文字が残る」時間ができる。
|
|
468
|
-
*/
|
|
469
|
-
function nextCheckDelay(list, now) {
|
|
470
|
-
const live = livePending(list, now);
|
|
471
|
-
if (!live.length)
|
|
472
|
-
return null;
|
|
473
|
-
let deadline = Infinity;
|
|
474
|
-
for (const e of live)
|
|
475
|
-
deadline = Math.min(deadline, e.at + REFLECT_TTL_MS);
|
|
476
|
-
const left = deadline - now;
|
|
477
|
-
return left < REFLECT_POLL_MS ? left : REFLECT_POLL_MS;
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* 反映が来たものを落とす。
|
|
481
|
-
*
|
|
482
|
-
* @param presentByCap 能力ごとの「**スナップショットが答えている集合**」。
|
|
483
|
-
* 🔴 **「画面に出ているか」を渡してはいけない。** 追加・削除の応答には一覧が添えられ、
|
|
484
|
-
* その場で画面が差し替わるので、記録した瞬間に反映済みと判定されて消える
|
|
485
|
-
* (=機能が丸ごと no-op になる)。渡すのは「未設定」を出すか決めているのと
|
|
486
|
-
* 同じ関数の戻り値(画面側は backlogTargetsNow)。
|
|
487
|
-
* その能力を判定できないときは null を渡す——落とさず、期限に任せる。
|
|
488
|
-
*/
|
|
489
|
-
function settledPending(list, presentByCap) {
|
|
490
|
-
const out = [];
|
|
491
|
-
for (const e of list || []) {
|
|
492
|
-
const present = presentByCap ? presentByCap[e.cap] : null;
|
|
493
|
-
if (!present) {
|
|
494
|
-
out.push(e);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
let hit = false;
|
|
498
|
-
for (const p of present) {
|
|
499
|
-
if (!p || !p.projectKey)
|
|
500
|
-
continue;
|
|
501
|
-
if (String(p.projectKey).toUpperCase() !== String(e.projectKey).toUpperCase())
|
|
502
|
-
continue;
|
|
503
|
-
// 空の domain は「どのスペースでもよい」(削除はキーだけを送るため)
|
|
504
|
-
if (e.domain && String(p.domain || "").toLowerCase() !== String(e.domain).toLowerCase())
|
|
505
|
-
continue;
|
|
506
|
-
hit = true;
|
|
507
|
-
}
|
|
508
|
-
// 消す操作は「消えたら反映済み」。ここを add と同じにすると、削除した接続先が
|
|
509
|
-
// 消えたその瞬間から期限まで「反映中」と言い続ける
|
|
510
|
-
const reflected = e.dir === "remove" ? !hit : hit;
|
|
511
|
-
if (!reflected)
|
|
512
|
-
out.push(e);
|
|
513
|
-
}
|
|
514
|
-
return out;
|
|
515
|
-
}
|
|
516
379
|
// ---------------------------------------------------------------------------
|
|
517
380
|
// 登録の即時値(registeredNow)— **まとめ表がまだ知らない行を足す**(段階1)
|
|
518
381
|
//
|
|
519
382
|
// 守るべき不変条件は3つ:
|
|
520
|
-
// 1.
|
|
383
|
+
// 1. **足すだけ・置き換えない**(実測(gate / gateAt / needed)はまとめ表と状態ストアに
|
|
384
|
+
// しか無く、既存の行の欄は書き換えない。チャットの登録メタ(origin / goldState /
|
|
385
|
+
// notify)は 2026-08-25 から**足す行にだけ**載る——#221)
|
|
521
386
|
// 2. **既知の欄だけ・葉は文字列だけ**(ライブ専用のキーなので build.ts の網にかからない)
|
|
522
387
|
// 3. **正規化しない**(engine が読むのは生値。揃えにいくと「画面には出るのに読まれない」行ができる)
|
|
523
388
|
// ---------------------------------------------------------------------------
|
|
@@ -532,11 +397,16 @@ const REGISTERED_CAP_NAMES = {
|
|
|
532
397
|
/**
|
|
533
398
|
* 通してよい欄。**ここに無い欄は1つも通さない**(Lambda 側の列挙と同じ形)
|
|
534
399
|
*
|
|
535
|
-
* 🔴
|
|
400
|
+
* 🔴 **真偽値は `orgUnknown` と `notify` の2欄だけ**(他の欄は全部文字列)。
|
|
536
401
|
* 篩の側にも `=== true` の分岐がある——`sanitizeRegisteredNow` を参照
|
|
402
|
+
*
|
|
403
|
+
* 🔴 **チャットの登録メタ(origin / goldState / notify)を通す(2026-08-25・#221)。**
|
|
404
|
+
* これが無いと、いま足した行の「登録を解除」「通知先」が出ない——夜間バッチが
|
|
405
|
+
* 追いつく翌朝まで、**追加した直後の主役の行だけ操作の無い行**になる。
|
|
406
|
+
* `orgUnknown` で同じ穴を1つ埋めた(0.12.1)のに、同族の3欄が残っていた。
|
|
537
407
|
*/
|
|
538
408
|
const REGISTERED_FIELDS = {
|
|
539
|
-
chat: ["ref", "name", "workspace", "orgUnknown"],
|
|
409
|
+
chat: ["ref", "name", "workspace", "orgUnknown", "origin", "goldState", "notify"],
|
|
540
410
|
dev: ["ref", "type"],
|
|
541
411
|
design: ["key", "name"],
|
|
542
412
|
materials: ["id", "name"],
|
|
@@ -576,13 +446,18 @@ function sanitizeRegisteredNow(v) {
|
|
|
576
446
|
const item = {};
|
|
577
447
|
for (const f of REGISTERED_FIELDS[cap]) {
|
|
578
448
|
const fv = r[f];
|
|
579
|
-
// 🔴 **真偽値を通すのは orgUnknown
|
|
580
|
-
//
|
|
581
|
-
//
|
|
449
|
+
// 🔴 **真偽値を通すのは orgUnknown と notify の2欄だけ**(2026-08-25)。葉を文字列に
|
|
450
|
+
// 限った篩がこの印を落としており、**追加した直後のチャンネルから「Slack ワークスペースに
|
|
451
|
+
// 接続する」の導線(orgUnknown)と「通知先」の札(notify)が消えていた**。
|
|
582
452
|
// **`=== true` に限る。** 文字列の "true" や 1 を真と読むと、意図しない値が
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
|
|
453
|
+
// 「組織不明」の案内を呼んで直すものが無い人に接続を勧め、通知していない
|
|
454
|
+
// チャンネルに「通知先」と書くことになる。
|
|
455
|
+
//
|
|
456
|
+
// **false はキーごと落とす。** 読み手は `=== true` で見る(`s.notify === true` /
|
|
457
|
+
// `r.orgUnknown === true`)ので、false を載せても意味は同じ——載せないほうが
|
|
458
|
+
// 「印が無い」と「印が false」の2通りを画面に持ち込まない。
|
|
459
|
+
// 例外はこの2欄だけ——他の欄まで真偽値を通すと、許可リストが篩の意味を失う
|
|
460
|
+
if (f === "orgUnknown" || f === "notify") {
|
|
586
461
|
if (fv === true)
|
|
587
462
|
item[f] = true;
|
|
588
463
|
continue;
|
|
@@ -697,9 +572,20 @@ function overlayStateOnFleet(view, rows, generatedAt) {
|
|
|
697
572
|
* 一意でないので、IDだけで突き合わせると別ワークスペースの同IDを同じものと見て
|
|
698
573
|
* 行が出ない(#104 で同じ理由の事故がある)。
|
|
699
574
|
*
|
|
700
|
-
* 🔴
|
|
701
|
-
*
|
|
702
|
-
*
|
|
575
|
+
* 🔴 **登録メタ(orgUnknown / origin / goldState / notify)をここで落とさない(2026-08-25)。**
|
|
576
|
+
* 篩を通しても**この関数がコピーしなければ行に乗らない**——0.12.1 で orgUnknown を
|
|
577
|
+
* 篩に通したときに、ここへのコピーを1行足して初めて画面が直った。落とすと:
|
|
578
|
+
*
|
|
579
|
+
* - `orgUnknown` … 行が `groupSlackByOrg` で社内の組に落ち、
|
|
580
|
+
* **「Slack ワークスペースに接続する」が画面から消える**(顧客Slackを繋ぐ唯一の入口)
|
|
581
|
+
* - `origin` … 「登録を解除」の出し分けが `s.origin !== "explicit"` なので、
|
|
582
|
+
* 欄が無い行にも出る。いまは derived しか来ないので結果は同じだが、
|
|
583
|
+
* **判定の材料を欠いたまま偶然合っている**状態を残さない
|
|
584
|
+
* - `goldState` … `sourceNeedsAttention` と「読み取り停止中」の札が見る。slack は
|
|
585
|
+
* 常に `"on"` なので表示は変わらないが、まとめ表の行と**同じ形にしておく**
|
|
586
|
+
* (片方に無い欄があると、差し替わった瞬間だけ判定が変わる経路ができる)
|
|
587
|
+
* - `notify` … **「通知先」の札が追加直後に出ない。** 通知が飛ぶチャンネルなのに
|
|
588
|
+
* 画面がそう言わないのは、行の意味を取り違えさせる
|
|
703
589
|
*/
|
|
704
590
|
function chatRowsToAdd(externals, rows) {
|
|
705
591
|
const have = {};
|
|
@@ -719,6 +605,13 @@ function chatRowsToAdd(externals, rows) {
|
|
|
719
605
|
row.workspace = r.workspace;
|
|
720
606
|
if (r.orgUnknown === true)
|
|
721
607
|
row.orgUnknown = true;
|
|
608
|
+
if (r.origin)
|
|
609
|
+
row.origin = r.origin;
|
|
610
|
+
if (r.goldState)
|
|
611
|
+
row.goldState = r.goldState;
|
|
612
|
+
// **真偽値の印は true のときだけ載せる**(読み手は `s.notify === true`。篩の項を参照)
|
|
613
|
+
if (r.notify === true)
|
|
614
|
+
row.notify = true;
|
|
722
615
|
out.push(row);
|
|
723
616
|
}
|
|
724
617
|
return out;
|
|
@@ -1060,22 +953,23 @@ function mergeRegisteredFleet(view, reg) {
|
|
|
1060
953
|
// 未確定の列がある間の取り直し(段階1の前提)
|
|
1061
954
|
//
|
|
1062
955
|
// 🔴 **これが無いと、登録したあと画面が二度と更新されない。** 60秒ごとの取り直しは
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
956
|
+
// 退役した「反映中」の記録がある間だけ張られていた(#221)。**取り直しの根拠を
|
|
957
|
+
// 「まだ答えが無い行があること」に移したのがここ**——registeredNow が入ると、
|
|
958
|
+
// 追加した瞬間に「もう出ている」と判定されて記録のほうは消えてしまう。
|
|
1065
959
|
//
|
|
1066
|
-
//
|
|
960
|
+
// **上限とタブ非表示の扱いは、次の2つを守る。**
|
|
1067
961
|
// - 上限は**経過時間**で見る(回数カウンタで数えない)。バックグラウンドタブの
|
|
1068
962
|
// スロットリングやスリープ復帰は、経過時間で見る限り自己修復する
|
|
1069
963
|
// - **可視性のイベントは足さない。** 遅れて起きたタイマがその回で期限切れを落とすので、
|
|
1070
964
|
// 非表示のあいだは自然に間隔が伸びるだけ。イベントを足すと、復帰の瞬間に
|
|
1071
965
|
// 全タブが同時に叩く形を新しく作ることになる
|
|
1072
966
|
// ---------------------------------------------------------------------------
|
|
1073
|
-
/**
|
|
967
|
+
/** 未確定の行があっても、取り直しを続ける上限(開始からの経過)。15分 */
|
|
1074
968
|
const UNSETTLED_TTL_MS = 15 * 60 * 1000;
|
|
1075
969
|
/** 取り直しの間隔。60秒(op=state のキャッシュと同じ粒度) */
|
|
1076
970
|
const UNSETTLED_POLL_MS = 60 * 1000;
|
|
1077
971
|
/**
|
|
1078
|
-
* 期限と間隔を**テストから固定するための入れ物**(
|
|
972
|
+
* 期限と間隔を**テストから固定するための入れ物**(infra の STATE_LIMITS と同じ作法)。
|
|
1079
973
|
*
|
|
1080
974
|
* ⚠ **定数そのものを export してはいけない**(CommonJS へ落ちるときに `exports.X` へ
|
|
1081
975
|
* 書き換えられ、toString() で流し込んだ先には exports が無い)。
|
|
@@ -1087,8 +981,9 @@ exports.UNSETTLED_LIMITS = { ttlMs: UNSETTLED_TTL_MS, pollMs: UNSETTLED_POLL_MS
|
|
|
1087
981
|
* @param count 未確定の行数(0 なら張らない)
|
|
1088
982
|
* @param since 取り直しを始めた時刻(Date.now())
|
|
1089
983
|
*
|
|
1090
|
-
* 🔴
|
|
1091
|
-
*
|
|
984
|
+
* 🔴 **経過は「上」だけでなく「下」も見る。** 端末の時計が進んだ状態で始めて
|
|
985
|
+
* NTP で戻ると、`since` が未来になり**永久に期限が切れない**——上限(`age < 期限`)
|
|
986
|
+
* しか見ないと、この画面は60秒ごとに永久に Lambda を叩き続ける。
|
|
1092
987
|
*/
|
|
1093
988
|
function nextStateDelay(count, since, now) {
|
|
1094
989
|
if (!(count > 0))
|
|
@@ -2816,18 +2711,9 @@ ${backlogTargetOf.toString()}
|
|
|
2816
2711
|
${issuesRefOf.toString()}
|
|
2817
2712
|
${sourceNeedsAttention.toString()}
|
|
2818
2713
|
${capabilityState.toString()}
|
|
2819
|
-
//
|
|
2820
|
-
// ——画面側に書き写すと、片方だけ動かしたときに死角(取り直しは止まったのに
|
|
2821
|
-
// 反映中が残る時間)ができ、しかも画面は正常に見えるので気づけない
|
|
2822
|
-
var REFLECT_TTL_MS = ${JSON.stringify(REFLECT_TTL_MS)};
|
|
2823
|
-
var REFLECT_POLL_MS = ${JSON.stringify(REFLECT_POLL_MS)};
|
|
2824
|
-
var REFLECT_OPS = ${JSON.stringify(exports.REFLECT_OPS)};
|
|
2825
|
-
// ライブ確認の見切り(#256)。**ここも書き写さない**——テストが固定している値と
|
|
2714
|
+
// ライブ確認の見切り(#256)。**書き写さない**——テストが固定している値と
|
|
2826
2715
|
// 画面が使う値が割れると、「10秒で戻る」検査が通ったまま実機では戻らなくなる
|
|
2827
2716
|
var LIVE_CONFIRM_TIMEOUT_MS = ${JSON.stringify(LIVE_CONFIRM_TIMEOUT_MS)};
|
|
2828
|
-
${livePending.toString()}
|
|
2829
|
-
${nextCheckDelay.toString()}
|
|
2830
|
-
${settledPending.toString()}
|
|
2831
2717
|
// 登録の即時値(registeredNow)の受け入れとマージ。**判定の実体はビルド時の定義**
|
|
2832
2718
|
// ——画面側に書き写すと、許可リストが2枚になって列挙し忘れが静かに消える
|
|
2833
2719
|
var REGISTERED_CAP_NAMES = ${JSON.stringify(REGISTERED_CAP_NAMES)};
|
|
@@ -4585,79 +4471,6 @@ ${deriveCapTile.toString()}
|
|
|
4585
4471
|
return d && d !== "on" ? d : null;
|
|
4586
4472
|
}
|
|
4587
4473
|
|
|
4588
|
-
// ---------- 反映待ち(「⏳ 反映中」)----------
|
|
4589
|
-
//
|
|
4590
|
-
// ## 何のためか
|
|
4591
|
-
//
|
|
4592
|
-
// 設定を保存してから画面に出るまで、自己チェック(fleet-status.json の作り直し)に
|
|
4593
|
-
// 数分かかる。その間、画面は「未設定」「未接続」「未使用」と出す
|
|
4594
|
-
// ——**保存はできているのに、事実に反する。** 利用者は「保存できていない」と読んで
|
|
4595
|
-
// 同じ操作を繰り返し、「既に登録されています」と言われる(この repo が何度も踏んだ形)。
|
|
4596
|
-
//
|
|
4597
|
-
// **嘘をやめる。** 反映待ちのあいだは「未設定」と言わず「反映中」と言う。
|
|
4598
|
-
//
|
|
4599
|
-
// ## 出すのは状態だけ。行は先出ししない
|
|
4600
|
-
//
|
|
4601
|
-
// 行そのものを先に出すと、保存に失敗していても入っているように見えて気づけないし、
|
|
4602
|
-
// 反映が来ないまま消えない行は削除もできない幽霊になる(撤去した pendingGold の
|
|
4603
|
-
// 「(画面反映待ち)」バッジと同じ罠)。**必ず期限切れする**ので、反映が来なければ
|
|
4604
|
-
// 元の「未設定」に戻る=嘘が残らない。
|
|
4605
|
-
//
|
|
4606
|
-
// ## 期限は Date.now() - at で見る
|
|
4607
|
-
//
|
|
4608
|
-
// 🔴 **回数カウンタで数えない。** バックグラウンドタブのスロットリングやスリープ復帰は、
|
|
4609
|
-
// 経過時間で見る限り自己修復する(遅れて起きたタイマがその回で期限切れを落とす)。
|
|
4610
|
-
|
|
4611
|
-
/** 取り直しのタイマ(setTimeout の連鎖。setInterval は使わない) */
|
|
4612
|
-
var reflectTimer = null;
|
|
4613
|
-
|
|
4614
|
-
/**
|
|
4615
|
-
* 保存先のキー。**案件ごとに分ける**(tutStoreKey と同じ作法)。
|
|
4616
|
-
*
|
|
4617
|
-
* intake が無い案件では案件キーが空文字になるが、それでも他案件のキーとは別物なので、
|
|
4618
|
-
* 別の案件の反映待ちを読むことはない。
|
|
4619
|
-
*
|
|
4620
|
-
* **cortex.pendingGold: と紛らわしい名前にしない。** 前方一致で掃除するコードが
|
|
4621
|
-
* 後から書かれると事故る(撤去済みの pendingGold と別語にしてある)。
|
|
4622
|
-
*/
|
|
4623
|
-
function reflectStoreKey() {
|
|
4624
|
-
return "cortex.reflecting:" + ((SITE.intake && SITE.intake.projectKey) || "");
|
|
4625
|
-
}
|
|
4626
|
-
|
|
4627
|
-
/**
|
|
4628
|
-
* localStorage は使えない環境がある(プライベートモード等)。**必ず try/catch で囲む**
|
|
4629
|
-
*
|
|
4630
|
-
* 🔴 **読み込み側でも「既定は記録しない」を効かせる。** 書き込み側(reflectEntryOf)が
|
|
4631
|
-
* REFLECT_OPS に無い op を弾いても、localStorage の中身は**別バージョンの画面・
|
|
4632
|
-
* 手編集・壊れた値**でいくらでも汚れる。settledPending は「判定できない能力は
|
|
4633
|
-
* 落とさない(期限に任せる)」設計なので、**表に無い cap の記録は期限(15分)まで
|
|
4634
|
-
* そのまま「反映中」として画面に出る**——本当の状態を隠す。
|
|
4635
|
-
* op / cap / dir を対応表と突き合わせ、1つでも食い違えば落とす。
|
|
4636
|
-
*/
|
|
4637
|
-
function readReflecting() {
|
|
4638
|
-
try {
|
|
4639
|
-
var raw = localStorage.getItem(reflectStoreKey());
|
|
4640
|
-
var v = raw ? JSON.parse(raw) : null;
|
|
4641
|
-
if (!Array.isArray(v)) return [];
|
|
4642
|
-
return v.filter(function (e) {
|
|
4643
|
-
if (!e || typeof e.at !== "number" || !Number.isFinite(e.at)) return false;
|
|
4644
|
-
if (!e.cap || !e.projectKey) return false;
|
|
4645
|
-
var d = REFLECT_OPS[String(e.op)];
|
|
4646
|
-
// 対応表に無い op(記録してはいけないもの・未知のもの)は落とす
|
|
4647
|
-
if (!d) return false;
|
|
4648
|
-
// 表と食い違う cap / dir も落とす(表の cap 以外は settle できない)
|
|
4649
|
-
return d.cap === e.cap && d.dir === e.dir;
|
|
4650
|
-
});
|
|
4651
|
-
} catch (e) { return []; }
|
|
4652
|
-
}
|
|
4653
|
-
|
|
4654
|
-
function writeReflecting(list) {
|
|
4655
|
-
try {
|
|
4656
|
-
if (!list || !list.length) localStorage.removeItem(reflectStoreKey());
|
|
4657
|
-
else localStorage.setItem(reflectStoreKey(), JSON.stringify(list));
|
|
4658
|
-
} catch (e) {}
|
|
4659
|
-
}
|
|
4660
|
-
|
|
4661
4474
|
/** その能力の情報源(fleet のスナップショット由来) */
|
|
4662
4475
|
function internalSourceOf(cap) {
|
|
4663
4476
|
var list = fleetVal("internalSources") || [];
|
|
@@ -4772,183 +4585,12 @@ ${deriveCapTile.toString()}
|
|
|
4772
4585
|
if (KEY_SLOTS.figma) fn(KEY_SLOTS.figma);
|
|
4773
4586
|
}
|
|
4774
4587
|
|
|
4775
|
-
/**
|
|
4776
|
-
* 能力ごとの「**スナップショットが答えている集合**」。
|
|
4777
|
-
*
|
|
4778
|
-
* 🔴 **「画面に出ているか」で判定してはいけない。** 追加・削除の応答には一覧が
|
|
4779
|
-
* 添えられていて adoptTargets がその場で差し替えるので、**記録した瞬間に
|
|
4780
|
-
* 「もう出ている」と判定されて消え、機能が丸ごと無意味になる**(査読者が実測)。
|
|
4781
|
-
*
|
|
4782
|
-
* 引くのは backlogTargetsNow ——「未設定」を出すか決めているのと**同一の関数**。
|
|
4783
|
-
* 別々に書くと、片方が「未設定」と言い、もう片方が「反映済み」と言う状態を作れてしまう。
|
|
4784
|
-
*
|
|
4785
|
-
* 載っていない能力は判定できない(null)=落とさず期限に任せる。
|
|
4786
|
-
* ただし REFLECT_OPS がその能力の op を記録しないので、実際には溜まらない。
|
|
4787
|
-
*/
|
|
4788
|
-
function reflectPresent() {
|
|
4789
|
-
var out = {};
|
|
4790
|
-
out["課題管理"] = backlogTargetsNow(internalSourceOf("課題管理"));
|
|
4791
|
-
return out;
|
|
4792
|
-
}
|
|
4793
|
-
|
|
4794
|
-
/** いま「反映中」と言うべきもの。**読むたびに期限切れ・反映済みを落として保存し直す** */
|
|
4795
|
-
function reflectingNow() {
|
|
4796
|
-
var before = readReflecting();
|
|
4797
|
-
if (!before.length) return before;
|
|
4798
|
-
var after = settledPending(livePending(before, Date.now()), reflectPresent());
|
|
4799
|
-
if (after.length !== before.length) writeReflecting(after);
|
|
4800
|
-
return after;
|
|
4801
|
-
}
|
|
4802
|
-
|
|
4803
|
-
/** 送った操作から反映待ちの1件を組み立てる。**対応表に無い op は必ず null** */
|
|
4804
|
-
function reflectEntryOf(op, payload, refreshUrl) {
|
|
4805
|
-
var d = REFLECT_OPS[String(op)];
|
|
4806
|
-
if (!d) return null;
|
|
4807
|
-
var t = null;
|
|
4808
|
-
if (d.from === "url") t = backlogTargetOf(payload.url);
|
|
4809
|
-
else if (d.from === "backlogProjectKey") t = { domain: "", projectKey: String(payload.backlogProjectKey || "").toUpperCase() };
|
|
4810
|
-
// 対象を特定できないものは記録しない(settle できず、期限まで出っぱなしになる)
|
|
4811
|
-
if (!t || !t.projectKey) return null;
|
|
4812
|
-
return {
|
|
4813
|
-
cap: d.cap, op: String(op), dir: d.dir,
|
|
4814
|
-
domain: t.domain || "", projectKey: t.projectKey,
|
|
4815
|
-
at: Date.now(), url: refreshUrl || "",
|
|
4816
|
-
};
|
|
4817
|
-
}
|
|
4818
|
-
|
|
4819
|
-
/**
|
|
4820
|
-
* 反映待ちとして記録し、その場で画面を「反映中」に差し替える。
|
|
4821
|
-
*
|
|
4822
|
-
* **同じ接続先の古い記録は捨てる**(追加のあと削除、のような入れ違いを残さない)。
|
|
4823
|
-
*/
|
|
4824
|
-
function rememberReflecting(payload, refreshUrl) {
|
|
4825
|
-
var e = reflectEntryOf(payload && payload.op, payload || {}, refreshUrl);
|
|
4826
|
-
if (!e) return;
|
|
4827
|
-
var list = readReflecting().filter(function (x) {
|
|
4828
|
-
return !(x.cap === e.cap && x.projectKey === e.projectKey && x.domain === e.domain);
|
|
4829
|
-
});
|
|
4830
|
-
list.push(e);
|
|
4831
|
-
writeReflecting(list);
|
|
4832
|
-
// **開いているモーダルの中も含めてその場で差し替える。** 削除で最後の1件を外すと
|
|
4833
|
-
// 応答で一覧が空になり「未設定」の1行が出る——記録しただけでは、その嘘が残る
|
|
4834
|
-
repaintReflect(document);
|
|
4835
|
-
scheduleReflectRefresh();
|
|
4836
|
-
}
|
|
4837
|
-
|
|
4838
|
-
// 差し替える文言。**engine 側の文言は変えない**——engine の detail は
|
|
4839
|
-
// 「そのスナップショット時点では未設定だった」という事実で、嘘ではない。
|
|
4840
|
-
// 変えるのは画面の出し分けだけ。
|
|
4841
|
-
var REFLECT_TEXT = {
|
|
4842
|
-
empty: "⏳ 反映中 — いま変更した接続先を画面に反映しています(数分かかります)。",
|
|
4843
|
-
note: "⏳ 反映中(いま変更した内容を画面に反映しています)",
|
|
4844
|
-
sub: "⏳ 反映中",
|
|
4845
|
-
// 見出し下の説明は出さない(下の「反映中」が説明している)
|
|
4846
|
-
desc: "",
|
|
4847
|
-
};
|
|
4848
|
-
|
|
4849
|
-
/**
|
|
4850
|
-
* 反映が終わったときに、その箇所を**いまのスナップショットから引き直す**関数を
|
|
4851
|
-
* 走らせている最中か。
|
|
4852
|
-
*
|
|
4853
|
-
* 引き直しは一覧の描き直し(paint)まで含み、その中でまた applyReflect が呼ばれる。
|
|
4854
|
-
* 目印が無いと無限に呼び合うので、内側は下地(data-reflect-base)に落とす。
|
|
4855
|
-
*/
|
|
4856
|
-
var reflectRestoring = false;
|
|
4857
|
-
|
|
4858
|
-
/**
|
|
4859
|
-
* 「未設定」と言っている箇所に印を付ける。反映待ちのあいだだけ文言を差し替える。
|
|
4860
|
-
*
|
|
4861
|
-
* @param role empty=一覧の0件行 / note=能力カードの「未使用」 /
|
|
4862
|
-
* sub=タイルの2行目 / desc=モーダルの見出し下
|
|
4863
|
-
* @param fresh 🔴 **反映が届いたときに、届いたデータからこの箇所を引き直す関数。**
|
|
4864
|
-
* 引数はその節点。{ text, hidden } の形で返す(返さなければ下地へ落とす)。
|
|
4865
|
-
* **省くと「反映中 → 未設定」に戻る**——applyReflect のコメントを読むこと。
|
|
4866
|
-
*/
|
|
4867
|
-
function reflectMark(node, cap, role, fresh) {
|
|
4868
|
-
node.setAttribute("data-reflect-cap", cap);
|
|
4869
|
-
node.setAttribute("data-reflect-role", role);
|
|
4870
|
-
node.setAttribute("data-reflect-base", node.textContent || "");
|
|
4871
|
-
if (fresh) node.__reflectFresh = fresh;
|
|
4872
|
-
applyReflect(node, reflectingNow());
|
|
4873
|
-
return node;
|
|
4874
|
-
}
|
|
4875
|
-
|
|
4876
|
-
function applyReflect(node, live) {
|
|
4877
|
-
var cap = node.getAttribute("data-reflect-cap");
|
|
4878
|
-
var role = node.getAttribute("data-reflect-role");
|
|
4879
|
-
var hit = null;
|
|
4880
|
-
for (var i = 0; i < live.length; i++) if (live[i].cap === cap) { hit = live[i]; break; }
|
|
4881
|
-
if (!hit) {
|
|
4882
|
-
// ------------------------------------------------------------------
|
|
4883
|
-
// 反映が来た(あるいは期限が切れた)。
|
|
4884
|
-
//
|
|
4885
|
-
// 🔴 **下地(data-reflect-base)へ戻してはいけない。** 下地は
|
|
4886
|
-
// **モーダルを開いた時点=古いスナップショットで作った文字列**で、その中身は
|
|
4887
|
-
// 「未接続」「未設定」「未使用」——**この機能が直そうとしている誤読そのもの**。
|
|
4888
|
-
// しかも settle が起きるのは「**新しいスナップショットが届いた**」瞬間なので、
|
|
4889
|
-
// 戻した先は既に事実ではない。
|
|
4890
|
-
//
|
|
4891
|
-
// モーダルを開いたまま待つと refreshDataSources が下地を作り直さない
|
|
4892
|
-
// (開いているモーダルの中身が切り離されるため、あのガードは外せない)ので、
|
|
4893
|
-
// 「⏳ 反映中」→(数分)→「未設定」が**閉じるかリロードするまで残った**。
|
|
4894
|
-
//
|
|
4895
|
-
// **届いたデータで引き直す。** 引き直せない箇所だけ下地に落とす。
|
|
4896
|
-
// ------------------------------------------------------------------
|
|
4897
|
-
var f = null;
|
|
4898
|
-
if (node.__reflectFresh && !reflectRestoring) {
|
|
4899
|
-
reflectRestoring = true;
|
|
4900
|
-
try { f = node.__reflectFresh(node); } catch (e) { f = null; }
|
|
4901
|
-
reflectRestoring = false;
|
|
4902
|
-
}
|
|
4903
|
-
node.textContent = f && typeof f.text === "string" ? f.text : (node.getAttribute("data-reflect-base") || "");
|
|
4904
|
-
node.hidden = !!(f && f.hidden);
|
|
4905
|
-
return;
|
|
4906
|
-
}
|
|
4907
|
-
var t = REFLECT_TEXT[role] || "";
|
|
4908
|
-
node.textContent = t;
|
|
4909
|
-
// 「出さない」箇所は空にして hidden にする。**消さずに残す**のは、
|
|
4910
|
-
// 反映が終わったときに元の文へ戻す先が要るため
|
|
4911
|
-
node.hidden = t === "";
|
|
4912
|
-
if (role === "empty" && hit.url) {
|
|
4913
|
-
node.appendChild(document.createTextNode(" "));
|
|
4914
|
-
node.appendChild(el("a", { href: hit.url, target: "_blank", rel: "noopener", text: "反映の状況 →" }));
|
|
4915
|
-
}
|
|
4916
|
-
}
|
|
4917
|
-
|
|
4918
|
-
/**
|
|
4919
|
-
* 反映中の箇所だけを描き直す。
|
|
4920
|
-
*
|
|
4921
|
-
* 🔴 **これが「モーダルを開いたまま待つ」への答え。** 画面へ反映する唯一の手段だった
|
|
4922
|
-
* refreshDataSources は、モーダルが開いている間は何もしない(下地を入れ替えると
|
|
4923
|
-
* 開いているモーダルの中身が切り離されるので、**そのガードは外せない**)。
|
|
4924
|
-
* ここは該当箇所だけを局所的に差し替えるので、開いたままでも追いつく。
|
|
4925
|
-
*/
|
|
4926
|
-
function repaintReflect(root) {
|
|
4927
|
-
if (!root || !root.querySelectorAll) return;
|
|
4928
|
-
var live = reflectingNow();
|
|
4929
|
-
var nodes = root.querySelectorAll("[data-reflect-role]");
|
|
4930
|
-
for (var i = 0; i < nodes.length; i++) applyReflect(nodes[i], live);
|
|
4931
|
-
}
|
|
4932
|
-
|
|
4933
|
-
/**
|
|
4934
|
-
* 次の取り直しを予約する。
|
|
4935
|
-
*
|
|
4936
|
-
* 🔴 **反映待ちが1件も無ければタイマを張らない。** typeof fetch のガードでは足りない
|
|
4937
|
-
* ——テストは load の**後**に fetch を注入するので、そこで張ってしまうと
|
|
4938
|
-
* jsdom が閉じられず node --test が返らなくなる(この repo で実際に踏んだ形)。
|
|
4939
|
-
*/
|
|
4940
|
-
function scheduleReflectRefresh() {
|
|
4941
|
-
if (reflectTimer) return;
|
|
4942
|
-
var delay = nextCheckDelay(reflectingNow(), Date.now());
|
|
4943
|
-
if (delay == null) return;
|
|
4944
|
-
reflectTimer = setTimeout(reflectTick, delay);
|
|
4945
|
-
}
|
|
4946
|
-
|
|
4947
4588
|
// ---------- 未確定の行がある間の取り直し(段階1の前提)----------
|
|
4948
4589
|
//
|
|
4949
|
-
// 🔴
|
|
4950
|
-
//
|
|
4951
|
-
//
|
|
4590
|
+
// 🔴 **取り直しの根拠は「まとめ表がまだ答えていない行があること」。** 退役した
|
|
4591
|
+
// 「反映中」の記録(#221)はこれを持てなかった——registeredNow が入ると、記録は
|
|
4592
|
+
// 追加した瞬間に「もう出ている」と判定されて消えるので、**登録した直後に画面が
|
|
4593
|
+
// 二度と更新されない**状態になっていた。
|
|
4952
4594
|
//
|
|
4953
4595
|
// 経路は増やさない(loadLiveFleet 1本)。取得の口が2つになると、上に出す
|
|
4954
4596
|
// 「◯◯時点」がどちらの値を指すのか説明できなくなる。
|
|
@@ -4986,17 +4628,6 @@ ${deriveCapTile.toString()}
|
|
|
4986
4628
|
loadLiveFleet();
|
|
4987
4629
|
}
|
|
4988
4630
|
|
|
4989
|
-
function reflectTick() {
|
|
4990
|
-
reflectTimer = null;
|
|
4991
|
-
var live = reflectingNow();
|
|
4992
|
-
// **期限に達した回でも、一度だけ描き直してから止める。** これが無いと
|
|
4993
|
-
// 「取り直しは止まったのに反映中の文字が残る」時間ができる
|
|
4994
|
-
repaintReflect(document);
|
|
4995
|
-
// **新しい取得経路を作らない。** 2箇所になると「いつ時点の値か」が説明できなくなる
|
|
4996
|
-
if (live.length) loadLiveFleet();
|
|
4997
|
-
scheduleReflectRefresh();
|
|
4998
|
-
}
|
|
4999
|
-
|
|
5000
4631
|
/**
|
|
5001
4632
|
* その能力にそのアダプタの操作を出してよいか(#125②)。
|
|
5002
4633
|
*
|
|
@@ -5065,22 +4696,32 @@ ${deriveCapTile.toString()}
|
|
|
5065
4696
|
// ようになったので、未接続の能力を押したときに中身が空になる場面が増えた。
|
|
5066
4697
|
// 「設定できるものがありません」だけだと、繋げないのか繋ぐ必要が無いのかが分からない
|
|
5067
4698
|
if (note) {
|
|
5068
|
-
|
|
5069
|
-
//
|
|
5070
|
-
//
|
|
4699
|
+
// 🔴 **未接続の一文だけは、開いた時点の値で引き直す。** 渡された note は
|
|
4700
|
+
// **タイルを作ったときの**スナップショット由来で、そのあとライブ確認や登録の
|
|
4701
|
+
// 反映が届いている。とくに**登録した行がある能力に「接続先が設定されていません」**
|
|
4702
|
+
// と出すのは、この画面が何度も直してきた嘘(タイル側は抑制をこちらへ委ねている
|
|
4703
|
+
// ——renderAdapterTile の modalNote の項)。
|
|
4704
|
+
//
|
|
4705
|
+
// 抑制してよいのは unset 由来のときだけ。この経路は「OFFに設定されています」
|
|
4706
|
+
// 「**設定に異常があります**」も同じ枠で運んでいるので、無条件に引き直すと
|
|
4707
|
+
// 異常が画面から消える——直したい誤読より悪い。
|
|
4708
|
+
//
|
|
5071
4709
|
// ⚠ 引き直しでは能力の代表アダプタの状態を使う(openCapabilityModal は
|
|
5072
4710
|
// どのアダプタのタイルから開かれたかを知らない。カタログは今日どの能力も1個)
|
|
5073
|
-
|
|
4711
|
+
var noteText = note;
|
|
4712
|
+
if (noteIsUnset) {
|
|
5074
4713
|
// 足した行があるなら、この一文(接続先が設定されていません)はもう事実ではない
|
|
5075
|
-
if (capPending(cap)) return { text: "", hidden: true };
|
|
5076
4714
|
var capsNow = capsView();
|
|
5077
4715
|
var dNow = capabilityDisplayState(cap, capsNow);
|
|
5078
4716
|
var infoNow = capsNow[cap];
|
|
5079
|
-
|
|
5080
|
-
?
|
|
5081
|
-
: "";
|
|
5082
|
-
|
|
5083
|
-
});
|
|
4717
|
+
noteText = capPending(cap) || !(dNow && dNow !== "on" && infoNow && infoNow.detail)
|
|
4718
|
+
? ""
|
|
4719
|
+
: (CAP_STATE_TEXT[dNow] || "") + "(" + infoNow.detail + ")";
|
|
4720
|
+
}
|
|
4721
|
+
var noteEl = el("p", { class: "modal-desc", text: noteText });
|
|
4722
|
+
// **消さずに hidden で伏せる。** 節点を残しておかないと、この一文を探している
|
|
4723
|
+
// 検査が「文言が変わった」と「箱ごと無くなった」を見分けられない
|
|
4724
|
+
noteEl.hidden = noteText === "";
|
|
5084
4725
|
m.modal.appendChild(noteEl);
|
|
5085
4726
|
}
|
|
5086
4727
|
if (!items.length) {
|
|
@@ -5300,25 +4941,8 @@ ${deriveCapTile.toString()}
|
|
|
5300
4941
|
// いま登録した行が一覧に出ているのに「この案件では設定されていません」と並ぶ
|
|
5301
4942
|
var inactive = s.enabled === false && !capPending(s.kind);
|
|
5302
4943
|
if (inactive) {
|
|
5303
|
-
//
|
|
5304
|
-
|
|
5305
|
-
col.appendChild(reflectMark(
|
|
5306
|
-
el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }),
|
|
5307
|
-
s.kind, "note",
|
|
5308
|
-
// **反映が届いたら、届いたスナップショットで判定し直す。** 下地へ戻すと
|
|
5309
|
-
// 「⏳ 反映中」→「未使用(この案件では設定されていません)」になり、
|
|
5310
|
-
// データが正しくなった**あと**に嘘をつく。灰色の見た目もここで外す
|
|
5311
|
-
//(節ごとの作り直しはモーダルを閉じるまで走らない)
|
|
5312
|
-
function (n) {
|
|
5313
|
-
var srcNow = internalSourceOf(s.kind);
|
|
5314
|
-
var stillInactive = !!(srcNow && srcNow.enabled === false) && !capPending(s.kind);
|
|
5315
|
-
if (!stillInactive) {
|
|
5316
|
-
var card = n && n.closest ? n.closest(".ds-card") : null;
|
|
5317
|
-
if (card) card.classList.remove("ds-inactive");
|
|
5318
|
-
}
|
|
5319
|
-
return { text: "未使用(この案件では設定されていません)", hidden: !stillInactive };
|
|
5320
|
-
},
|
|
5321
|
-
));
|
|
4944
|
+
// **灰色(.ds-inactive)だけにしない。** 注記が無いと、なぜ灰色なのかが読めない
|
|
4945
|
+
col.appendChild(el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }));
|
|
5322
4946
|
}
|
|
5323
4947
|
// id は接続マップのノードclickの飛び先(アンカー)
|
|
5324
4948
|
// **id は付けない。** #101 以前はこのカードが接続マップの飛び先だったが、
|
|
@@ -5904,11 +5528,11 @@ ${deriveCapTile.toString()}
|
|
|
5904
5528
|
// カタログ外の値はここへ来ない(capabilityAdapter が弾く)。旧データの "none" だけは
|
|
5905
5529
|
// 素通しすると顧客の画面に「none」というタイル名が出るので、上で空にしてある
|
|
5906
5530
|
var rawTool = adapter || "";
|
|
5907
|
-
// **2
|
|
5908
|
-
//
|
|
5909
|
-
//
|
|
5910
|
-
//
|
|
5911
|
-
//
|
|
5531
|
+
// **2行目の状態は、まとめ表がまだ知らない行がある能力では断言しない**——
|
|
5532
|
+
// **行と同じ語**に倒す(未接続とも接続OKとも言わない)。モーダルを開かなくても
|
|
5533
|
+
// 分かるようにするのが目的で、別のバッジを足すのではなくこの行を差し替える
|
|
5534
|
+
// ——並べると、同じタイルの中で2つの答えが矛盾する。
|
|
5535
|
+
// **倒すのは unset のときだけ**(off / broken は残す。R4 と同じ理由)
|
|
5912
5536
|
// 🔴 **導出が答えているなら、「登録状況を確認中です」より導出が勝つ(0.12.0)。**
|
|
5913
5537
|
// あの語は「まとめ表しか材料が無く、登録の現在値が分からない」ときの折衷だった。
|
|
5914
5538
|
// 正本(registeredNow)を読むようになった今は分かる——外した直後は「未接続」、
|
|
@@ -5932,33 +5556,6 @@ ${deriveCapTile.toString()}
|
|
|
5932
5556
|
? DS_GATE_PENDING.text
|
|
5933
5557
|
: tileSub(cap, st, derived, adapter, internals, sources, derive, confirming),
|
|
5934
5558
|
});
|
|
5935
|
-
// **反映が届いたら、届いたスナップショットから引き直す。** 下地へ戻すと
|
|
5936
|
-
// 「⏳ 反映中」→「⦿ 未接続」になる——データが正しくなったあとに嘘をつく。
|
|
5937
|
-
// 状態の語も色(ds-gate-*)も、判定はタイルを作るときと同じ式で引く
|
|
5938
|
-
if (derived === "unset") reflectMark(subEl, cap, "sub", function (n) {
|
|
5939
|
-
var capsNow = capsView();
|
|
5940
|
-
var insNow = fleetVal("internalSources") || [];
|
|
5941
|
-
var srcsNow = fleetVal("externalSources") || [];
|
|
5942
|
-
var dNow = adapter === capabilityAdapter(cap, capsNow) ? capabilityDisplayState(cap, capsNow) : "unset";
|
|
5943
|
-
var stNow = dNow === "unset" && capabilityDisplayState(cap, capsNow) !== "unset"
|
|
5944
|
-
? "idle"
|
|
5945
|
-
: capabilityState(cap, stateCtx());
|
|
5946
|
-
// 正本からの導出も、タイルを作るときと同じ式で引き直す(0.12.0)。
|
|
5947
|
-
// ここを省くと、反映が届いた瞬間だけ「● 接続OK」へ戻る
|
|
5948
|
-
var deriveNow = dNow && dNow !== "on" ? null : capDerivation(cap);
|
|
5949
|
-
if (deriveNow) stNow = deriveNow.state;
|
|
5950
|
-
var pendNow = capPending(cap) && !deriveNow;
|
|
5951
|
-
// 確認中かどうかも引き直す(#256)。**タイルを作るときと同じ式**——ここで
|
|
5952
|
-
// 断言に戻すと、反映待ちの取り直しが走った回だけ焼いた状態が顔を出す
|
|
5953
|
-
var confirmingNow = liveConfirming() && (dNow === null || dNow === "on");
|
|
5954
|
-
n.className = "ds-tile-sub " + (confirmingNow || pendNow ? DS_GATE_PENDING.cls : "ds-gate-" + stNow);
|
|
5955
|
-
return {
|
|
5956
|
-
text: pendNow
|
|
5957
|
-
? DS_GATE_PENDING.text
|
|
5958
|
-
: tileSub(cap, stNow, dNow, adapter, insNow, srcsNow, deriveNow, confirmingNow),
|
|
5959
|
-
hidden: false,
|
|
5960
|
-
};
|
|
5961
|
-
});
|
|
5962
5559
|
var body = el("div", { class: "ds-tile-body" }, [
|
|
5963
5560
|
el("div", { class: "ds-tile-name", text: (rawTool && (CM_TOOL_LABEL_C[rawTool] || rawTool)) || cap }),
|
|
5964
5561
|
el("div", { class: "ds-tile-desc", text: meta.desc }),
|
|
@@ -6026,7 +5623,7 @@ ${deriveCapTile.toString()}
|
|
|
6026
5623
|
el("span", { class: "ds-tile-chev", text: "›" }),
|
|
6027
5624
|
]);
|
|
6028
5625
|
// ⚠ **ここで足した行(段階1)を見て抑制しない。** unset の一文は開くときに引き直される
|
|
6029
|
-
// (openCapabilityModal
|
|
5626
|
+
// (openCapabilityModal が capPending を見る)ので二重になるうえ、
|
|
6030
5627
|
// ここで抑えると off / broken まで消える——R4 で「消さない」と決めたもの
|
|
6031
5628
|
var modalNote = derived && derived !== "on" && capInfo && capInfo.detail
|
|
6032
5629
|
? CAP_STATE_TEXT[derived] + "(" + capInfo.detail + ")"
|
|
@@ -6799,29 +6396,12 @@ ${deriveCapTile.toString()}
|
|
|
6799
6396
|
var listEl = el("div", { class: "ds-list" });
|
|
6800
6397
|
// **0件の1行は一覧の外に置く**(Figma / Drive と同じ形)。中に .ds-card として
|
|
6801
6398
|
// 置くと「行が1件ある」と読まれ、一覧を数えるテストにも行として数えられる
|
|
6802
|
-
//
|
|
6399
|
+
//
|
|
6400
|
+
// **出し入れは paint() が持つ。** 開いたまま待っているあいだに新しい値が届いたときの
|
|
6401
|
+
// 差し替えは box.__rebuild(取り直しの応答から呼ばれる)——0件の1行だけを
|
|
6402
|
+
// 別経路で追いつかせると、行の一覧と食い違う瞬間ができる
|
|
6803
6403
|
var EMPTY_TEXT = "同期する接続先はまだ登録されていません(未設定)。";
|
|
6804
|
-
var emptyEl =
|
|
6805
|
-
el("p", { class: "ds-empty", text: EMPTY_TEXT }),
|
|
6806
|
-
"課題管理", "empty",
|
|
6807
|
-
// 🔴 **反映が届いたら、届いた一覧で描き直す。** 下地(=この0件の文言)へ戻すと、
|
|
6808
|
-
// 反映が届いた瞬間に「⏳ 反映中」→「同期する接続先はまだ登録されていません(未設定)。」
|
|
6809
|
-
// になり、**行があるのに無いと言う**。settle は「新しいスナップショットが届いた」
|
|
6810
|
-
// ことなので、そのデータで一覧を作り直す(adoptTargets と同じ形)。
|
|
6811
|
-
// モーダルを開いたまま待っているときは、これが唯一の描き直しの機会
|
|
6812
|
-
function () {
|
|
6813
|
-
// まだ箱を作っていない(=最初の描画の途中)。paint() は箱へ出し入れするので、
|
|
6814
|
-
// ここで走らせない。最初の描画はこの直後の paint() が受け持つ
|
|
6815
|
-
if (!box) return null;
|
|
6816
|
-
// 🔴 **応答の一覧を上書きしない。** 追加・削除の応答に添えられた一覧
|
|
6817
|
-
//(adoptTargets)は、この画面が持っている中でいちばん新しい正本。
|
|
6818
|
-
// スナップショットから引き直すと、外したばかりの接続先が戻って見える
|
|
6819
|
-
if (adopted) return null;
|
|
6820
|
-
targets = backlogTargetsNow(internalSourceOf("課題管理"));
|
|
6821
|
-
paint();
|
|
6822
|
-
return { text: EMPTY_TEXT, hidden: false };
|
|
6823
|
-
},
|
|
6824
|
-
);
|
|
6404
|
+
var emptyEl = el("p", { class: "ds-empty", text: EMPTY_TEXT });
|
|
6825
6405
|
// 追加・削除の結果はここに1つだけ出す(行を描き直しても消えない位置)
|
|
6826
6406
|
var msg = el("div", { class: "ds-toggle-msg" });
|
|
6827
6407
|
|
|
@@ -6914,9 +6494,6 @@ ${deriveCapTile.toString()}
|
|
|
6914
6494
|
var here = emptyEl.parentNode === box;
|
|
6915
6495
|
if (!targets.length && !here) box.insertBefore(emptyEl, listEl.nextSibling);
|
|
6916
6496
|
else if (targets.length && here) box.removeChild(emptyEl);
|
|
6917
|
-
// **出し入れのたびに反映中を当て直す。** 外している間の描き直しはこの行に届かない
|
|
6918
|
-
// ので、戻すときに当てないと古い文言のまま出る
|
|
6919
|
-
applyReflect(emptyEl, reflectingNow());
|
|
6920
6497
|
// 🔴 **鍵の欄の出し入れは、描き直しのたびに当て直す(2026-08-22)。** 最初の1件を
|
|
6921
6498
|
// 足した瞬間に欄が現れ、最後の1件を外した瞬間に消えるのはここが効いているから。
|
|
6922
6499
|
// 節ごとの差し替え(refreshDataSources)はモーダルが開いている間は何もしない
|
|
@@ -7747,28 +7324,14 @@ ${deriveCapTile.toString()}
|
|
|
7747
7324
|
msg.appendChild(document.createTextNode(" "));
|
|
7748
7325
|
msg.appendChild(el("a", { href: j.runsUrl, target: "_blank", rel: "noopener", text: "実行状況を見る →" }));
|
|
7749
7326
|
}
|
|
7750
|
-
//
|
|
7751
|
-
//
|
|
7752
|
-
//
|
|
7753
|
-
//
|
|
7754
|
-
|
|
7755
|
-
if (j.refreshUrl) {
|
|
7756
|
-
msg.appendChild(document.createTextNode(" "));
|
|
7757
|
-
msg.appendChild(el("a", { href: j.refreshUrl, target: "_blank", rel: "noopener", text: "反映の状況 →" }));
|
|
7758
|
-
}
|
|
7327
|
+
// **「反映の状況 →」のリンクは無くなった(#221・2026-08-25)。** 指していたのは
|
|
7328
|
+
// 「画面を追いつかせるための fleet-status の実行」で、その起動ごと撤去した
|
|
7329
|
+
// ——保存の直後に読み直す(下の loadLiveFleet)ので、待つ対象がもう無い。
|
|
7330
|
+
// 応答に refreshUrl が載ることも二度と無い(Lambda 側で撤去済み)。
|
|
7331
|
+
|
|
7759
7332
|
// 🔴 **語を戻すのは onDone より先。** removeButton の onDone は押せない状態へ
|
|
7760
7333
|
// 戻す(外した行のボタン)ので、後に置くと「処理中…」の語が居座る
|
|
7761
7334
|
endBusy();
|
|
7762
|
-
// **反映待ちとして記録する。** 一覧はスナップショットから描かれるので、
|
|
7763
|
-
// 保存できていても数分は「未設定」と出る。その間だけ「⏳ 反映中」に差し替える。
|
|
7764
|
-
//
|
|
7765
|
-
// ⚠ **onDone の中に置かない。** removeRow は onDone に null を渡しているので、
|
|
7766
|
-
// 中に置くと**接続先の「削除」だけ黙って記録されない**(remove の判定が守りたい当のケース)。
|
|
7767
|
-
//
|
|
7768
|
-
// - changed === false(既に同じものが入っていた)は記録しない——待つものが無い
|
|
7769
|
-
// - refreshUrl が無い(自己チェックを起こせなかった)ときも記録しない
|
|
7770
|
-
// ——待っても来ないので「反映中」が嘘になる
|
|
7771
|
-
if (j.changed !== false && j.refreshUrl) rememberReflecting(payload, j.refreshUrl);
|
|
7772
7335
|
// **保存が効いたら、いまの登録を読み直す(段階1)。** 一覧はまとめ表(夜間バッチ)から
|
|
7773
7336
|
// 描かれるので、保存しただけでは行が出ない。ここで読み直すと、登録ファイルの中身が
|
|
7774
7337
|
// 返ってきて**足りない行として一覧に出る**——さらに「まとめ表がまだ答えていない行」が
|
|
@@ -8449,7 +8012,7 @@ ${deriveCapTile.toString()}
|
|
|
8449
8012
|
* 🔴 **チャンネルやフォルダを足すのは「モーダルを開いたまま」。** 節ごとの差し替え
|
|
8450
8013
|
* (refreshDataSources)はモーダルが開いている間は何もしない——下地を入れ替えると
|
|
8451
8014
|
* 開いているモーダルの中身が切り離され、入力中の値も「保存中…」も消えるので、
|
|
8452
|
-
*
|
|
8015
|
+
* **あのガードは外せない**。だから**その箇所だけを差し替える口**を置く。
|
|
8453
8016
|
*
|
|
8454
8017
|
* 差し替えるのは**レコードの一覧だけ**(追加フォーム・鍵の欄・結果メッセージは触らない)。
|
|
8455
8018
|
* 課題管理の adoptTargets が先にこの形を採っている。
|
|
@@ -8702,10 +8265,9 @@ ${deriveCapTile.toString()}
|
|
|
8702
8265
|
// モーダルが開いている間・入力中は何もせずに戻るので、中に入れると
|
|
8703
8266
|
// **モーダルを開いたまま最初の応答が届いた回だけ、図が「接続OK」のまま残る**
|
|
8704
8267
|
applyStateToConnMap();
|
|
8705
|
-
//
|
|
8268
|
+
// **モーダルを開いたままでも一覧は追いつかせる。** refreshDataSources は
|
|
8706
8269
|
// モーダルが開いている間・入力中は何もしない(下地を入れ替えると開いているモーダルの
|
|
8707
|
-
//
|
|
8708
|
-
repaintReflect(document);
|
|
8270
|
+
// 中身が切り離される)ので、開いている一覧だけを差し替える口をここに置く
|
|
8709
8271
|
repaintOpenLists();
|
|
8710
8272
|
// まだ足りない行があるなら取り直しを続ける(無くなったら止める)
|
|
8711
8273
|
refreshStateTimer();
|
|
@@ -8727,7 +8289,6 @@ ${deriveCapTile.toString()}
|
|
|
8727
8289
|
// 図も一覧も同じ焼いた時点なので、注記なしが正直・設計v2 の4)
|
|
8728
8290
|
restoreConfirmingConnMap();
|
|
8729
8291
|
refreshDataSources();
|
|
8730
|
-
repaintReflect(document);
|
|
8731
8292
|
repaintOpenLists();
|
|
8732
8293
|
// **失敗しても取り直しは続ける**(期限が来れば止まる)。ここで止めると、
|
|
8733
8294
|
// 一時的な失敗のあと画面が二度と追いつかない
|
|
@@ -8744,9 +8305,6 @@ ${deriveCapTile.toString()}
|
|
|
8744
8305
|
// **まとめ表がまだ答えていない行があれば取り直す。** 取得前は0件なので、
|
|
8745
8306
|
// 実際に張られるのは応答が届いてから(loadLiveFleet が呼び直す)
|
|
8746
8307
|
refreshStateTimer();
|
|
8747
|
-
// **反映待ちがあれば自動で取り直す(再読み込みしても続く)。** 反映待ちが
|
|
8748
|
-
// 1件も無ければタイマは張らない
|
|
8749
|
-
scheduleReflectRefresh();
|
|
8750
8308
|
// **取り込みカードと鍵カードは能力セクションへ移した**(#101)。
|
|
8751
8309
|
// ここで再度出すと二重になる。手動投入は 会議 / 共有資料 の中にある。
|
|
8752
8310
|
}
|
package/package.json
CHANGED