@takagaki/cortex-decisions-viewer 0.7.1 → 0.8.0
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 +560 -9
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REFLECT_OPS = exports.REFLECTING_LIMITS = void 0;
|
|
3
4
|
exports.renderSite = renderSite;
|
|
5
|
+
exports.livePending = livePending;
|
|
6
|
+
exports.nextCheckDelay = nextCheckDelay;
|
|
7
|
+
exports.settledPending = settledPending;
|
|
4
8
|
exports.renderConnectionMap = renderConnectionMap;
|
|
5
9
|
/** SiteDataを単一の自己完結HTML(埋め込みデータ+CSS+JS)にレンダリングする */
|
|
6
10
|
function renderSite(site) {
|
|
@@ -310,6 +314,164 @@ function backlogTargetOf(input) {
|
|
|
310
314
|
return num.test(view[1]) ? null : { domain: domain, projectKey: view[1].toUpperCase() };
|
|
311
315
|
return null;
|
|
312
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* 反映待ちの期限。**15分。**
|
|
319
|
+
*
|
|
320
|
+
* 実測(cortex-context「セットアップ状況の自己チェック」の完了 run 112件、
|
|
321
|
+
* うち workflow_dispatch 51件):
|
|
322
|
+
*
|
|
323
|
+
* | | 全体(112) | dispatch のみ(51) |
|
|
324
|
+
* | min | 4秒 | 6秒 |
|
|
325
|
+
* | p50 | 41秒 | 47秒 |
|
|
326
|
+
* | p90 | 57秒 | 62秒 |
|
|
327
|
+
* | p95 | 65秒 | ― |
|
|
328
|
+
* | max | 400秒 | 400秒 |
|
|
329
|
+
*
|
|
330
|
+
* 必要な余裕は 最悪400秒 + op=state のキャッシュ60秒 + 取り直し間隔とのズレ60秒 = 520秒。
|
|
331
|
+
* 10分では余裕が1分20秒しかなく、自己チェックが待ち行列(concurrency)に入れば超える。
|
|
332
|
+
*
|
|
333
|
+
* 🔴 **期限が無いと、反映が来ない案件で「反映中」が永久に出て、本当の「未設定」を隠す。**
|
|
334
|
+
* 完了 run の 8%(9/112)は failure で、失敗した run はコミットしない。つまり
|
|
335
|
+
* 8%程度の確率で「15分間 反映中 → 未設定に戻る」が起きる(設計どおりの挙動)。
|
|
336
|
+
*/
|
|
337
|
+
const REFLECT_TTL_MS = 15 * 60 * 1000;
|
|
338
|
+
/** 自動で取り直す間隔。60秒(op=state のキャッシュと同じ粒度) */
|
|
339
|
+
const REFLECT_POLL_MS = 60 * 1000;
|
|
340
|
+
/**
|
|
341
|
+
* 期限と間隔を**テストから固定するための入れ物**(infra の STATE_LIMITS と同じ形)。
|
|
342
|
+
*
|
|
343
|
+
* ⚠ **定数そのものを export してはいけない。** `export const` は CommonJS へ落ちるときに
|
|
344
|
+
* 参照が `exports.X` へ書き換えられ、**toString() で流し込んだ先には exports が無いので
|
|
345
|
+
* クライアントJSが丸ごと落ちる**(実際に踏んだ)。流し込む関数から参照する値は、
|
|
346
|
+
* export しないローカルの const に置くこと。
|
|
347
|
+
*/
|
|
348
|
+
exports.REFLECTING_LIMITS = { ttlMs: REFLECT_TTL_MS, pollMs: REFLECT_POLL_MS };
|
|
349
|
+
/**
|
|
350
|
+
* 記録してよい操作の対応表。**既定は「記録しない」(null)。**
|
|
351
|
+
*
|
|
352
|
+
* 🔴 **settle できない操作を記録すると、期限まで「反映中」が出っぱなしになり、
|
|
353
|
+
* 本当の状態を隠す。** 「現れた/消えた」で判定できるものだけを載せる。
|
|
354
|
+
*
|
|
355
|
+
* @see CONFIG_OPS(infra/lambda/intake/logic.mjs)— **突き合わせるテストがある**。
|
|
356
|
+
* op を足してここに書き忘れると、その操作だけ黙って反映中にならない/
|
|
357
|
+
* settle できないものを記録して出っぱなしになる、という気づきにくい壊れ方をする
|
|
358
|
+
*/
|
|
359
|
+
exports.REFLECT_OPS = {
|
|
360
|
+
// ---- 記録する ----
|
|
361
|
+
// 接続先の追加・削除。一覧に「現れた/消えた」で反映を判定できる
|
|
362
|
+
"backlog-target-add": { cap: "課題管理", dir: "add", from: "url" },
|
|
363
|
+
// **削除はキーだけを送る**(サーバもキーだけで消す)ので、スペースは問わない
|
|
364
|
+
//
|
|
365
|
+
// ⚠ **最後の1件を外したときは、反映が届いても settle しない**(engine を読んで確認・2026-08-21)。
|
|
366
|
+
// engine の fleet-status は、正本(issues-config.json)が空になると
|
|
367
|
+
// 同期ミラー(issues/backlog-settings.json)の旧値へフォールバックし、
|
|
368
|
+
// backlogTarget に旧 domain / projectKey を載せ続ける(capabilities も on のまま)。
|
|
369
|
+
// ミラーは履歴保全のため**画面から外しても残す設計**なので、この値は消えない。
|
|
370
|
+
// つまり「消えた」を観測できるのは、ミラーが1つも無い案件(一度も同期していない)だけ。
|
|
371
|
+
// それ以外では**期限(15分)で落ちる**——嘘は残らないが、その間「反映中」と出る。
|
|
372
|
+
// 直すなら engine 側(backlogTarget.source === "secrets" かつ configPath === null を
|
|
373
|
+
// 「正本に接続先が無い」として画面に伝える)で、これは「未設定」の意味を変える
|
|
374
|
+
// 製品判断なので別issueにする。
|
|
375
|
+
"backlog-target-remove": { cap: "課題管理", dir: "remove", from: "backlogProjectKey" },
|
|
376
|
+
// ---- 記録しない ----
|
|
377
|
+
// **値の変更**。読み取りの ON/OFF は「現れた/消えた」では判定できない
|
|
378
|
+
"set-github-source-enabled": null,
|
|
379
|
+
// **表示名の取り直し**。sendConfig を通らない2本目の書き込み口でもある
|
|
380
|
+
"refresh-channel-name": null,
|
|
381
|
+
// 以下は add / remove の形だが、**その能力の「反映中」はまだ作っていない**。
|
|
382
|
+
// 追加フォームには既存の控え(.ds-pending-item「追加済み・反映中」)が出ており、
|
|
383
|
+
// 二重に出さないため、統合は別issueにする(設計 追補 中2)
|
|
384
|
+
"add-channel": null,
|
|
385
|
+
"remove-channel": null,
|
|
386
|
+
"add-github-source": null,
|
|
387
|
+
"remove-github-source": null,
|
|
388
|
+
"add-figma-file": null,
|
|
389
|
+
"remove-figma-file": null,
|
|
390
|
+
"add-drive-folder": null,
|
|
391
|
+
"remove-drive-folder": null,
|
|
392
|
+
// 1件だけ持てた頃の置き換え。いまの画面は出さない(backlog-target-add に統合済み)
|
|
393
|
+
"set-backlog-target": null,
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* 期限内のものだけ返す。
|
|
397
|
+
*
|
|
398
|
+
* 🔴 **経過時間(now - at)で判定すること。** 取り直した回数で数えると、
|
|
399
|
+
* スリープ復帰やバックグラウンドタブのスロットリングで期限が伸びる。
|
|
400
|
+
*
|
|
401
|
+
* 🔴 **経過時間は「上」だけでなく「下」も見る。** 上限(age < 期限)しか見ないと、
|
|
402
|
+
* **at が未来の記録は永久に期限が切れない**——
|
|
403
|
+
* - at = now + 1時間 … 1時間15分ずっと「反映中」
|
|
404
|
+
* - at = Infinity … 永久に「反映中」のまま60秒ごとに取り直し続ける
|
|
405
|
+
* 実在する経路: **端末の時計が進んだ状態で記録し、NTP で戻る**/別バージョンが書いた値/
|
|
406
|
+
* localStorage の手編集。「期限が必ず切れるから嘘は残らない」というこの設計の
|
|
407
|
+
* 唯一の安全弁が、入口で成立しなくなる。
|
|
408
|
+
*/
|
|
409
|
+
function livePending(list, now) {
|
|
410
|
+
const out = [];
|
|
411
|
+
for (const e of list || []) {
|
|
412
|
+
// NaN・欠けた at・未来の at・Infinity はここで落ちる
|
|
413
|
+
const age = e ? now - e.at : NaN;
|
|
414
|
+
if (!e || !Number.isFinite(age) || !(age >= 0 && age < REFLECT_TTL_MS))
|
|
415
|
+
continue;
|
|
416
|
+
out.push(e);
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* 次に取り直すまでの ms。もう取り直す必要が無ければ null。
|
|
422
|
+
*
|
|
423
|
+
* 🔴 **最後の1回は期限ちょうどに来る**(min(60秒, 期限 − 経過))。
|
|
424
|
+
* 固定60秒で張ると、最後の確認が期限の60秒前になり、そこから期限までに反映が来ても
|
|
425
|
+
* 誰も見ない。さらに**期限そのものに誰も描き直さない**ので、「取り直しは止まったのに
|
|
426
|
+
* 反映中の文字が残る」時間ができる。
|
|
427
|
+
*/
|
|
428
|
+
function nextCheckDelay(list, now) {
|
|
429
|
+
const live = livePending(list, now);
|
|
430
|
+
if (!live.length)
|
|
431
|
+
return null;
|
|
432
|
+
let deadline = Infinity;
|
|
433
|
+
for (const e of live)
|
|
434
|
+
deadline = Math.min(deadline, e.at + REFLECT_TTL_MS);
|
|
435
|
+
const left = deadline - now;
|
|
436
|
+
return left < REFLECT_POLL_MS ? left : REFLECT_POLL_MS;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* 反映が来たものを落とす。
|
|
440
|
+
*
|
|
441
|
+
* @param presentByCap 能力ごとの「**スナップショットが答えている集合**」。
|
|
442
|
+
* 🔴 **「画面に出ているか」を渡してはいけない。** 追加・削除の応答には一覧が添えられ、
|
|
443
|
+
* その場で画面が差し替わるので、記録した瞬間に反映済みと判定されて消える
|
|
444
|
+
* (=機能が丸ごと no-op になる)。渡すのは「未設定」を出すか決めているのと
|
|
445
|
+
* 同じ関数の戻り値(画面側は backlogTargetsNow)。
|
|
446
|
+
* その能力を判定できないときは null を渡す——落とさず、期限に任せる。
|
|
447
|
+
*/
|
|
448
|
+
function settledPending(list, presentByCap) {
|
|
449
|
+
const out = [];
|
|
450
|
+
for (const e of list || []) {
|
|
451
|
+
const present = presentByCap ? presentByCap[e.cap] : null;
|
|
452
|
+
if (!present) {
|
|
453
|
+
out.push(e);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
let hit = false;
|
|
457
|
+
for (const p of present) {
|
|
458
|
+
if (!p || !p.projectKey)
|
|
459
|
+
continue;
|
|
460
|
+
if (String(p.projectKey).toUpperCase() !== String(e.projectKey).toUpperCase())
|
|
461
|
+
continue;
|
|
462
|
+
// 空の domain は「どのスペースでもよい」(削除はキーだけを送るため)
|
|
463
|
+
if (e.domain && String(p.domain || "").toLowerCase() !== String(e.domain).toLowerCase())
|
|
464
|
+
continue;
|
|
465
|
+
hit = true;
|
|
466
|
+
}
|
|
467
|
+
// 消す操作は「消えたら反映済み」。ここを add と同じにすると、削除した接続先が
|
|
468
|
+
// 消えたその瞬間から期限まで「反映中」と言い続ける
|
|
469
|
+
const reflected = e.dir === "remove" ? !hit : hit;
|
|
470
|
+
if (!reflected)
|
|
471
|
+
out.push(e);
|
|
472
|
+
}
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
313
475
|
function capabilityState(cap, ctx) {
|
|
314
476
|
// **能力→パイプラインの対応はここに持つ。** 呼び出し側に渡させると、
|
|
315
477
|
// クライアント側(この関数を .toString() で受け取る側)が同じ表を書き写すことになり、
|
|
@@ -1589,6 +1751,15 @@ ${capabilityDisplayState.toString()}
|
|
|
1589
1751
|
${backlogTargetOf.toString()}
|
|
1590
1752
|
${sourceNeedsAttention.toString()}
|
|
1591
1753
|
${capabilityState.toString()}
|
|
1754
|
+
// 反映待ち(⏳ 反映中)の判定。**期限・間隔・対応表もビルド時の定義から流し込む**
|
|
1755
|
+
// ——画面側に書き写すと、片方だけ動かしたときに死角(取り直しは止まったのに
|
|
1756
|
+
// 反映中が残る時間)ができ、しかも画面は正常に見えるので気づけない
|
|
1757
|
+
var REFLECT_TTL_MS = ${JSON.stringify(REFLECT_TTL_MS)};
|
|
1758
|
+
var REFLECT_POLL_MS = ${JSON.stringify(REFLECT_POLL_MS)};
|
|
1759
|
+
var REFLECT_OPS = ${JSON.stringify(exports.REFLECT_OPS)};
|
|
1760
|
+
${livePending.toString()}
|
|
1761
|
+
${nextCheckDelay.toString()}
|
|
1762
|
+
${settledPending.toString()}
|
|
1592
1763
|
// 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
1593
1764
|
var TUTORIAL = (function () {
|
|
1594
1765
|
var elx = document.getElementById("tutorial-data");
|
|
@@ -2974,6 +3145,269 @@ ${capabilityState.toString()}
|
|
|
2974
3145
|
return SITE[key === "generatedAt" ? "fleetGeneratedAt" : key];
|
|
2975
3146
|
}
|
|
2976
3147
|
|
|
3148
|
+
// ---------- 反映待ち(「⏳ 反映中」)----------
|
|
3149
|
+
//
|
|
3150
|
+
// ## 何のためか
|
|
3151
|
+
//
|
|
3152
|
+
// 設定を保存してから画面に出るまで、自己チェック(fleet-status.json の作り直し)に
|
|
3153
|
+
// 数分かかる。その間、画面は「未設定」「未接続」「未使用」と出す
|
|
3154
|
+
// ——**保存はできているのに、事実に反する。** 利用者は「保存できていない」と読んで
|
|
3155
|
+
// 同じ操作を繰り返し、「既に登録されています」と言われる(この repo が何度も踏んだ形)。
|
|
3156
|
+
//
|
|
3157
|
+
// **嘘をやめる。** 反映待ちのあいだは「未設定」と言わず「反映中」と言う。
|
|
3158
|
+
//
|
|
3159
|
+
// ## 出すのは状態だけ。行は先出ししない
|
|
3160
|
+
//
|
|
3161
|
+
// 行そのものを先に出すと、保存に失敗していても入っているように見えて気づけないし、
|
|
3162
|
+
// 反映が来ないまま消えない行は削除もできない幽霊になる(撤去した pendingGold の
|
|
3163
|
+
// 「(画面反映待ち)」バッジと同じ罠)。**必ず期限切れする**ので、反映が来なければ
|
|
3164
|
+
// 元の「未設定」に戻る=嘘が残らない。
|
|
3165
|
+
//
|
|
3166
|
+
// ## 期限は Date.now() - at で見る
|
|
3167
|
+
//
|
|
3168
|
+
// 🔴 **回数カウンタで数えない。** バックグラウンドタブのスロットリングやスリープ復帰は、
|
|
3169
|
+
// 経過時間で見る限り自己修復する(遅れて起きたタイマがその回で期限切れを落とす)。
|
|
3170
|
+
|
|
3171
|
+
/** 取り直しのタイマ(setTimeout の連鎖。setInterval は使わない) */
|
|
3172
|
+
var reflectTimer = null;
|
|
3173
|
+
|
|
3174
|
+
/**
|
|
3175
|
+
* 保存先のキー。**案件ごとに分ける**(tutStoreKey と同じ作法)。
|
|
3176
|
+
*
|
|
3177
|
+
* intake が無い案件では案件キーが空文字になるが、それでも他案件のキーとは別物なので、
|
|
3178
|
+
* 別の案件の反映待ちを読むことはない。
|
|
3179
|
+
*
|
|
3180
|
+
* **cortex.pendingGold: と紛らわしい名前にしない。** 前方一致で掃除するコードが
|
|
3181
|
+
* 後から書かれると事故る(撤去済みの pendingGold と別語にしてある)。
|
|
3182
|
+
*/
|
|
3183
|
+
function reflectStoreKey() {
|
|
3184
|
+
return "cortex.reflecting:" + ((SITE.intake && SITE.intake.projectKey) || "");
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
/**
|
|
3188
|
+
* localStorage は使えない環境がある(プライベートモード等)。**必ず try/catch で囲む**
|
|
3189
|
+
*
|
|
3190
|
+
* 🔴 **読み込み側でも「既定は記録しない」を効かせる。** 書き込み側(reflectEntryOf)が
|
|
3191
|
+
* REFLECT_OPS に無い op を弾いても、localStorage の中身は**別バージョンの画面・
|
|
3192
|
+
* 手編集・壊れた値**でいくらでも汚れる。settledPending は「判定できない能力は
|
|
3193
|
+
* 落とさない(期限に任せる)」設計なので、**表に無い cap の記録は期限(15分)まで
|
|
3194
|
+
* そのまま「反映中」として画面に出る**——本当の状態を隠す。
|
|
3195
|
+
* op / cap / dir を対応表と突き合わせ、1つでも食い違えば落とす。
|
|
3196
|
+
*/
|
|
3197
|
+
function readReflecting() {
|
|
3198
|
+
try {
|
|
3199
|
+
var raw = localStorage.getItem(reflectStoreKey());
|
|
3200
|
+
var v = raw ? JSON.parse(raw) : null;
|
|
3201
|
+
if (!Array.isArray(v)) return [];
|
|
3202
|
+
return v.filter(function (e) {
|
|
3203
|
+
if (!e || typeof e.at !== "number" || !Number.isFinite(e.at)) return false;
|
|
3204
|
+
if (!e.cap || !e.projectKey) return false;
|
|
3205
|
+
var d = REFLECT_OPS[String(e.op)];
|
|
3206
|
+
// 対応表に無い op(記録してはいけないもの・未知のもの)は落とす
|
|
3207
|
+
if (!d) return false;
|
|
3208
|
+
// 表と食い違う cap / dir も落とす(表の cap 以外は settle できない)
|
|
3209
|
+
return d.cap === e.cap && d.dir === e.dir;
|
|
3210
|
+
});
|
|
3211
|
+
} catch (e) { return []; }
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
function writeReflecting(list) {
|
|
3215
|
+
try {
|
|
3216
|
+
if (!list || !list.length) localStorage.removeItem(reflectStoreKey());
|
|
3217
|
+
else localStorage.setItem(reflectStoreKey(), JSON.stringify(list));
|
|
3218
|
+
} catch (e) {}
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
/** その能力の情報源(fleet のスナップショット由来) */
|
|
3222
|
+
function internalSourceOf(cap) {
|
|
3223
|
+
var list = fleetVal("internalSources") || [];
|
|
3224
|
+
for (var i = 0; i < list.length; i++) if (list[i] && list[i].kind === cap) return list[i];
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
/**
|
|
3229
|
+
* 能力ごとの「**スナップショットが答えている集合**」。
|
|
3230
|
+
*
|
|
3231
|
+
* 🔴 **「画面に出ているか」で判定してはいけない。** 追加・削除の応答には一覧が
|
|
3232
|
+
* 添えられていて adoptTargets がその場で差し替えるので、**記録した瞬間に
|
|
3233
|
+
* 「もう出ている」と判定されて消え、機能が丸ごと無意味になる**(査読者が実測)。
|
|
3234
|
+
*
|
|
3235
|
+
* 引くのは backlogTargetsNow ——「未設定」を出すか決めているのと**同一の関数**。
|
|
3236
|
+
* 別々に書くと、片方が「未設定」と言い、もう片方が「反映済み」と言う状態を作れてしまう。
|
|
3237
|
+
*
|
|
3238
|
+
* 載っていない能力は判定できない(null)=落とさず期限に任せる。
|
|
3239
|
+
* ただし REFLECT_OPS がその能力の op を記録しないので、実際には溜まらない。
|
|
3240
|
+
*/
|
|
3241
|
+
function reflectPresent() {
|
|
3242
|
+
var out = {};
|
|
3243
|
+
out["課題管理"] = backlogTargetsNow(internalSourceOf("課題管理"));
|
|
3244
|
+
return out;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
/** いま「反映中」と言うべきもの。**読むたびに期限切れ・反映済みを落として保存し直す** */
|
|
3248
|
+
function reflectingNow() {
|
|
3249
|
+
var before = readReflecting();
|
|
3250
|
+
if (!before.length) return before;
|
|
3251
|
+
var after = settledPending(livePending(before, Date.now()), reflectPresent());
|
|
3252
|
+
if (after.length !== before.length) writeReflecting(after);
|
|
3253
|
+
return after;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
/** 送った操作から反映待ちの1件を組み立てる。**対応表に無い op は必ず null** */
|
|
3257
|
+
function reflectEntryOf(op, payload, refreshUrl) {
|
|
3258
|
+
var d = REFLECT_OPS[String(op)];
|
|
3259
|
+
if (!d) return null;
|
|
3260
|
+
var t = null;
|
|
3261
|
+
if (d.from === "url") t = backlogTargetOf(payload.url);
|
|
3262
|
+
else if (d.from === "backlogProjectKey") t = { domain: "", projectKey: String(payload.backlogProjectKey || "").toUpperCase() };
|
|
3263
|
+
// 対象を特定できないものは記録しない(settle できず、期限まで出っぱなしになる)
|
|
3264
|
+
if (!t || !t.projectKey) return null;
|
|
3265
|
+
return {
|
|
3266
|
+
cap: d.cap, op: String(op), dir: d.dir,
|
|
3267
|
+
domain: t.domain || "", projectKey: t.projectKey,
|
|
3268
|
+
at: Date.now(), url: refreshUrl || "",
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* 反映待ちとして記録し、その場で画面を「反映中」に差し替える。
|
|
3274
|
+
*
|
|
3275
|
+
* **同じ接続先の古い記録は捨てる**(追加のあと削除、のような入れ違いを残さない)。
|
|
3276
|
+
*/
|
|
3277
|
+
function rememberReflecting(payload, refreshUrl) {
|
|
3278
|
+
var e = reflectEntryOf(payload && payload.op, payload || {}, refreshUrl);
|
|
3279
|
+
if (!e) return;
|
|
3280
|
+
var list = readReflecting().filter(function (x) {
|
|
3281
|
+
return !(x.cap === e.cap && x.projectKey === e.projectKey && x.domain === e.domain);
|
|
3282
|
+
});
|
|
3283
|
+
list.push(e);
|
|
3284
|
+
writeReflecting(list);
|
|
3285
|
+
// **開いているモーダルの中も含めてその場で差し替える。** 削除で最後の1件を外すと
|
|
3286
|
+
// 応答で一覧が空になり「未設定」の1行が出る——記録しただけでは、その嘘が残る
|
|
3287
|
+
repaintReflect(document);
|
|
3288
|
+
scheduleReflectRefresh();
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// 差し替える文言。**engine 側の文言は変えない**——engine の detail は
|
|
3292
|
+
// 「そのスナップショット時点では未設定だった」という事実で、嘘ではない。
|
|
3293
|
+
// 変えるのは画面の出し分けだけ。
|
|
3294
|
+
var REFLECT_TEXT = {
|
|
3295
|
+
empty: "⏳ 反映中 — いま変更した接続先を画面に反映しています(数分かかります)。",
|
|
3296
|
+
note: "⏳ 反映中(いま変更した内容を画面に反映しています)",
|
|
3297
|
+
sub: "⏳ 反映中",
|
|
3298
|
+
// 見出し下の説明は出さない(下の「反映中」が説明している)
|
|
3299
|
+
desc: "",
|
|
3300
|
+
};
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* 反映が終わったときに、その箇所を**いまのスナップショットから引き直す**関数を
|
|
3304
|
+
* 走らせている最中か。
|
|
3305
|
+
*
|
|
3306
|
+
* 引き直しは一覧の描き直し(paint)まで含み、その中でまた applyReflect が呼ばれる。
|
|
3307
|
+
* 目印が無いと無限に呼び合うので、内側は下地(data-reflect-base)に落とす。
|
|
3308
|
+
*/
|
|
3309
|
+
var reflectRestoring = false;
|
|
3310
|
+
|
|
3311
|
+
/**
|
|
3312
|
+
* 「未設定」と言っている箇所に印を付ける。反映待ちのあいだだけ文言を差し替える。
|
|
3313
|
+
*
|
|
3314
|
+
* @param role empty=一覧の0件行 / note=能力カードの「未使用」 /
|
|
3315
|
+
* sub=タイルの2行目 / desc=モーダルの見出し下
|
|
3316
|
+
* @param fresh 🔴 **反映が届いたときに、届いたデータからこの箇所を引き直す関数。**
|
|
3317
|
+
* 引数はその節点。{ text, hidden } の形で返す(返さなければ下地へ落とす)。
|
|
3318
|
+
* **省くと「反映中 → 未設定」に戻る**——applyReflect のコメントを読むこと。
|
|
3319
|
+
*/
|
|
3320
|
+
function reflectMark(node, cap, role, fresh) {
|
|
3321
|
+
node.setAttribute("data-reflect-cap", cap);
|
|
3322
|
+
node.setAttribute("data-reflect-role", role);
|
|
3323
|
+
node.setAttribute("data-reflect-base", node.textContent || "");
|
|
3324
|
+
if (fresh) node.__reflectFresh = fresh;
|
|
3325
|
+
applyReflect(node, reflectingNow());
|
|
3326
|
+
return node;
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
function applyReflect(node, live) {
|
|
3330
|
+
var cap = node.getAttribute("data-reflect-cap");
|
|
3331
|
+
var role = node.getAttribute("data-reflect-role");
|
|
3332
|
+
var hit = null;
|
|
3333
|
+
for (var i = 0; i < live.length; i++) if (live[i].cap === cap) { hit = live[i]; break; }
|
|
3334
|
+
if (!hit) {
|
|
3335
|
+
// ------------------------------------------------------------------
|
|
3336
|
+
// 反映が来た(あるいは期限が切れた)。
|
|
3337
|
+
//
|
|
3338
|
+
// 🔴 **下地(data-reflect-base)へ戻してはいけない。** 下地は
|
|
3339
|
+
// **モーダルを開いた時点=古いスナップショットで作った文字列**で、その中身は
|
|
3340
|
+
// 「未接続」「未設定」「未使用」——**この機能が直そうとしている誤読そのもの**。
|
|
3341
|
+
// しかも settle が起きるのは「**新しいスナップショットが届いた**」瞬間なので、
|
|
3342
|
+
// 戻した先は既に事実ではない。
|
|
3343
|
+
//
|
|
3344
|
+
// モーダルを開いたまま待つと refreshDataSources が下地を作り直さない
|
|
3345
|
+
// (開いているモーダルの中身が切り離されるため、あのガードは外せない)ので、
|
|
3346
|
+
// 「⏳ 反映中」→(数分)→「未設定」が**閉じるかリロードするまで残った**。
|
|
3347
|
+
//
|
|
3348
|
+
// **届いたデータで引き直す。** 引き直せない箇所だけ下地に落とす。
|
|
3349
|
+
// ------------------------------------------------------------------
|
|
3350
|
+
var f = null;
|
|
3351
|
+
if (node.__reflectFresh && !reflectRestoring) {
|
|
3352
|
+
reflectRestoring = true;
|
|
3353
|
+
try { f = node.__reflectFresh(node); } catch (e) { f = null; }
|
|
3354
|
+
reflectRestoring = false;
|
|
3355
|
+
}
|
|
3356
|
+
node.textContent = f && typeof f.text === "string" ? f.text : (node.getAttribute("data-reflect-base") || "");
|
|
3357
|
+
node.hidden = !!(f && f.hidden);
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
var t = REFLECT_TEXT[role] || "";
|
|
3361
|
+
node.textContent = t;
|
|
3362
|
+
// 「出さない」箇所は空にして hidden にする。**消さずに残す**のは、
|
|
3363
|
+
// 反映が終わったときに元の文へ戻す先が要るため
|
|
3364
|
+
node.hidden = t === "";
|
|
3365
|
+
if (role === "empty" && hit.url) {
|
|
3366
|
+
node.appendChild(document.createTextNode(" "));
|
|
3367
|
+
node.appendChild(el("a", { href: hit.url, target: "_blank", rel: "noopener", text: "反映の状況 →" }));
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
/**
|
|
3372
|
+
* 反映中の箇所だけを描き直す。
|
|
3373
|
+
*
|
|
3374
|
+
* 🔴 **これが「モーダルを開いたまま待つ」への答え。** 画面へ反映する唯一の手段だった
|
|
3375
|
+
* refreshDataSources は、モーダルが開いている間は何もしない(下地を入れ替えると
|
|
3376
|
+
* 開いているモーダルの中身が切り離されるので、**そのガードは外せない**)。
|
|
3377
|
+
* ここは該当箇所だけを局所的に差し替えるので、開いたままでも追いつく。
|
|
3378
|
+
*/
|
|
3379
|
+
function repaintReflect(root) {
|
|
3380
|
+
if (!root || !root.querySelectorAll) return;
|
|
3381
|
+
var live = reflectingNow();
|
|
3382
|
+
var nodes = root.querySelectorAll("[data-reflect-role]");
|
|
3383
|
+
for (var i = 0; i < nodes.length; i++) applyReflect(nodes[i], live);
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
/**
|
|
3387
|
+
* 次の取り直しを予約する。
|
|
3388
|
+
*
|
|
3389
|
+
* 🔴 **反映待ちが1件も無ければタイマを張らない。** typeof fetch のガードでは足りない
|
|
3390
|
+
* ——テストは load の**後**に fetch を注入するので、そこで張ってしまうと
|
|
3391
|
+
* jsdom が閉じられず node --test が返らなくなる(この repo で実際に踏んだ形)。
|
|
3392
|
+
*/
|
|
3393
|
+
function scheduleReflectRefresh() {
|
|
3394
|
+
if (reflectTimer) return;
|
|
3395
|
+
var delay = nextCheckDelay(reflectingNow(), Date.now());
|
|
3396
|
+
if (delay == null) return;
|
|
3397
|
+
reflectTimer = setTimeout(reflectTick, delay);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
function reflectTick() {
|
|
3401
|
+
reflectTimer = null;
|
|
3402
|
+
var live = reflectingNow();
|
|
3403
|
+
// **期限に達した回でも、一度だけ描き直してから止める。** これが無いと
|
|
3404
|
+
// 「取り直しは止まったのに反映中の文字が残る」時間ができる
|
|
3405
|
+
repaintReflect(document);
|
|
3406
|
+
// **新しい取得経路を作らない。** 2箇所になると「いつ時点の値か」が説明できなくなる
|
|
3407
|
+
if (live.length) loadLiveFleet();
|
|
3408
|
+
scheduleReflectRefresh();
|
|
3409
|
+
}
|
|
3410
|
+
|
|
2977
3411
|
/**
|
|
2978
3412
|
* その能力にそのアダプタの操作を出してよいか(#125②)。
|
|
2979
3413
|
*
|
|
@@ -3019,7 +3453,14 @@ ${capabilityState.toString()}
|
|
|
3019
3453
|
return (tool && CM_TOOL_LABEL_C[tool]) || cap;
|
|
3020
3454
|
}
|
|
3021
3455
|
|
|
3022
|
-
|
|
3456
|
+
/**
|
|
3457
|
+
* @param noteIsUnset その説明が「未接続(unset)」由来か。
|
|
3458
|
+
* 🔴 **反映待ちで抑制してよいのはこれだけ。** この経路は「未接続」「OFFに設定されて
|
|
3459
|
+
* います」「**設定に異常があります**」を同じ枠で運んでいる。無条件に抑制すると、
|
|
3460
|
+
* 保存直後の15分間、異常が画面から消える——直したい誤読より悪い
|
|
3461
|
+
*/
|
|
3462
|
+
function openCapabilityModal(cap, items, note, noteIsUnset) {
|
|
3463
|
+
// 見出しは押したタイルのツール名(Backlog・Google ドライブ…)。capToolLabel の説明を参照
|
|
3023
3464
|
var m = makeModal(capToolLabel(cap), true);
|
|
3024
3465
|
// **何が読み取られるのかを必ず書く。** 一覧に並ぶものと読み取られるものの関係は、
|
|
3025
3466
|
// 書いていないと分からない(チャットは「載っていれば全部」、開発はリポジトリ単位でOFFできる)
|
|
@@ -3031,7 +3472,23 @@ ${capabilityState.toString()}
|
|
|
3031
3472
|
// **なぜこの状態なのかを、押した先でも読めるようにする(#125②)。** タイルは常時出す
|
|
3032
3473
|
// ようになったので、未接続の能力を押したときに中身が空になる場面が増えた。
|
|
3033
3474
|
// 「設定できるものがありません」だけだと、繋げないのか繋ぐ必要が無いのかが分からない
|
|
3034
|
-
if (note)
|
|
3475
|
+
if (note) {
|
|
3476
|
+
var noteEl = el("p", { class: "modal-desc", text: note });
|
|
3477
|
+
// 反映が届いたら**届いたスナップショットから引き直す**(開いた時点の
|
|
3478
|
+
// 「未接続(…)」へ戻さない)。作り方はタイル側の modalNote と同じ式。
|
|
3479
|
+
// ⚠ 引き直しでは能力の代表アダプタの状態を使う(openCapabilityModal は
|
|
3480
|
+
// どのアダプタのタイルから開かれたかを知らない。カタログは今日どの能力も1個)
|
|
3481
|
+
if (noteIsUnset) reflectMark(noteEl, cap, "desc", function () {
|
|
3482
|
+
var capsNow = fleetVal("capabilities") || {};
|
|
3483
|
+
var dNow = capabilityDisplayState(cap, capsNow);
|
|
3484
|
+
var infoNow = capsNow[cap];
|
|
3485
|
+
var t = dNow && dNow !== "on" && infoNow && infoNow.detail
|
|
3486
|
+
? (CAP_STATE_TEXT[dNow] || "") + "(" + infoNow.detail + ")"
|
|
3487
|
+
: "";
|
|
3488
|
+
return { text: t, hidden: t === "" };
|
|
3489
|
+
});
|
|
3490
|
+
m.modal.appendChild(noteEl);
|
|
3491
|
+
}
|
|
3035
3492
|
if (!items.length) {
|
|
3036
3493
|
m.modal.appendChild(el("p", { class: "ds-empty", text: "この能力にはまだ設定できるものがありません。" }));
|
|
3037
3494
|
}
|
|
@@ -3234,7 +3691,25 @@ ${capabilityState.toString()}
|
|
|
3234
3691
|
// 未使用(enabled: false)はグレー行。アダプターの存在は見せつつ、この案件では設定されていないことを示す
|
|
3235
3692
|
var inactive = s.enabled === false;
|
|
3236
3693
|
if (inactive) {
|
|
3237
|
-
|
|
3694
|
+
// **反映待ちのあいだは「⏳ 反映中」に差し替える(消さない)。** 消すと
|
|
3695
|
+
// .ds-inactive の灰色だけが残り、なぜ灰色なのかが読めなくなる
|
|
3696
|
+
col.appendChild(reflectMark(
|
|
3697
|
+
el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }),
|
|
3698
|
+
s.kind, "note",
|
|
3699
|
+
// **反映が届いたら、届いたスナップショットで判定し直す。** 下地へ戻すと
|
|
3700
|
+
// 「⏳ 反映中」→「未使用(この案件では設定されていません)」になり、
|
|
3701
|
+
// データが正しくなった**あと**に嘘をつく。灰色の見た目もここで外す
|
|
3702
|
+
//(節ごとの作り直しはモーダルを閉じるまで走らない)
|
|
3703
|
+
function (n) {
|
|
3704
|
+
var srcNow = internalSourceOf(s.kind);
|
|
3705
|
+
var stillInactive = !!(srcNow && srcNow.enabled === false);
|
|
3706
|
+
if (!stillInactive) {
|
|
3707
|
+
var card = n && n.closest ? n.closest(".ds-card") : null;
|
|
3708
|
+
if (card) card.classList.remove("ds-inactive");
|
|
3709
|
+
}
|
|
3710
|
+
return { text: "未使用(この案件では設定されていません)", hidden: !stillInactive };
|
|
3711
|
+
},
|
|
3712
|
+
));
|
|
3238
3713
|
}
|
|
3239
3714
|
// id は接続マップのノードclickの飛び先(アンカー)
|
|
3240
3715
|
// **id は付けない。** #101 以前はこのカードが接続マップの飛び先だったが、
|
|
@@ -3539,9 +4014,17 @@ ${capabilityState.toString()}
|
|
|
3539
4014
|
* @param derived engine が導出した5値(capabilities 由来)。古いデータでは null
|
|
3540
4015
|
* @param adapter このタイルのアダプタ(件数の出し分けに使う)
|
|
3541
4016
|
*/
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
4017
|
+
/**
|
|
4018
|
+
* タイルの2行目。
|
|
4019
|
+
*
|
|
4020
|
+
* @param ins 内部ソースの一覧 / @param srcs 外部ソースの一覧。
|
|
4021
|
+
* **引数で受ける**(節を作ったときの値を掴まない)。反映が届いたあと、
|
|
4022
|
+
* 節を作り直せない場面(モーダルを開いたまま)でも、**届いた一覧で
|
|
4023
|
+
* 引き直す**ために同じ関数を使う——写経すると片方だけ古くなる
|
|
4024
|
+
*/
|
|
4025
|
+
function tileSub(cap, st, derived, adapter, ins, srcs) {
|
|
4026
|
+
var internal = ins.filter(function (s) { return s.kind === cap; })[0];
|
|
4027
|
+
var live = srcs.filter(function (s) { return externalCapability(s.type) === cap; });
|
|
3545
4028
|
var parts = [];
|
|
3546
4029
|
// **導出が on 以外なら、導出の語を優先する(#125②)。** broken は状態としては
|
|
3547
4030
|
// 警告(⚠️)だが、原因は「設定そのものが壊れている」であって同期の失敗ではない。
|
|
@@ -3663,10 +4146,30 @@ ${capabilityState.toString()}
|
|
|
3663
4146
|
// カタログ外の値はここへ来ない(capabilityAdapter が弾く)。旧データの "none" だけは
|
|
3664
4147
|
// 素通しすると顧客の画面に「none」というタイル名が出るので、上で空にしてある
|
|
3665
4148
|
var rawTool = adapter || "";
|
|
4149
|
+
// **2行目は反映待ちのあいだ「⏳ 反映中」にする。** モーダルを開かなくても分かる
|
|
4150
|
+
// ようにするのが目的だが、別のバッジを足すのではなく**この行を差し替える**
|
|
4151
|
+
// ——抑制しないと、同じタイルの中で「⏳ 反映中」と「⦿ 未接続」が並んで矛盾する。
|
|
4152
|
+
// **抑制するのは unset のときだけ**(off / broken は残す。R4 と同じ理由)
|
|
4153
|
+
var subEl = el("div", { class: "ds-tile-sub ds-gate-" + st, text: tileSub(cap, st, derived, adapter, internals, sources) });
|
|
4154
|
+
// **反映が届いたら、届いたスナップショットから引き直す。** 下地へ戻すと
|
|
4155
|
+
// 「⏳ 反映中」→「⦿ 未接続」になる——データが正しくなったあとに嘘をつく。
|
|
4156
|
+
// 状態の語も色(ds-gate-*)も、判定はタイルを作るときと同じ式で引く
|
|
4157
|
+
if (derived === "unset") reflectMark(subEl, cap, "sub", function (n) {
|
|
4158
|
+
var capsNow = fleetVal("capabilities") || {};
|
|
4159
|
+
var insNow = fleetVal("internalSources") || [];
|
|
4160
|
+
var srcsNow = fleetVal("externalSources") || [];
|
|
4161
|
+
var pipesNow = fleetVal("pipelines") || [];
|
|
4162
|
+
var dNow = adapter === capabilityAdapter(cap, capsNow) ? capabilityDisplayState(cap, capsNow) : "unset";
|
|
4163
|
+
var stNow = dNow === "unset" && capabilityDisplayState(cap, capsNow) !== "unset"
|
|
4164
|
+
? "idle"
|
|
4165
|
+
: capabilityState(cap, { capabilities: capsNow, internals: insNow, externals: srcsNow, pipes: pipesNow });
|
|
4166
|
+
n.className = "ds-tile-sub ds-gate-" + stNow;
|
|
4167
|
+
return { text: tileSub(cap, stNow, dNow, adapter, insNow, srcsNow), hidden: false };
|
|
4168
|
+
});
|
|
3666
4169
|
var body = el("div", { class: "ds-tile-body" }, [
|
|
3667
4170
|
el("div", { class: "ds-tile-name", text: (rawTool && (CM_TOOL_LABEL_C[rawTool] || rawTool)) || cap }),
|
|
3668
4171
|
el("div", { class: "ds-tile-desc", text: meta.desc }),
|
|
3669
|
-
|
|
4172
|
+
subEl,
|
|
3670
4173
|
]);
|
|
3671
4174
|
// **なぜその状態なのかを添える(#125②)。** engine が書いた一文(detail)で、
|
|
3672
4175
|
// 「何が無いのか」を出す。on のときは読み取り範囲(件数)が既に同じ行にある。
|
|
@@ -3723,7 +4226,7 @@ ${capabilityState.toString()}
|
|
|
3723
4226
|
var modalNote = derived && derived !== "on" && capInfo && capInfo.detail
|
|
3724
4227
|
? CAP_STATE_TEXT[derived] + "(" + capInfo.detail + ")"
|
|
3725
4228
|
: "";
|
|
3726
|
-
tile.addEventListener("click", function () { openCapabilityModal(cap, items, modalNote); });
|
|
4229
|
+
tile.addEventListener("click", function () { openCapabilityModal(cap, items, modalNote, derived === "unset"); });
|
|
3727
4230
|
sec.appendChild(tile);
|
|
3728
4231
|
}
|
|
3729
4232
|
|
|
@@ -4240,6 +4743,8 @@ ${capabilityState.toString()}
|
|
|
4240
4743
|
function backlogTargetsSection(s) {
|
|
4241
4744
|
var targets = backlogTargetsNow(s);
|
|
4242
4745
|
var editable = backlogTargetsEditable();
|
|
4746
|
+
/** 追加・削除の応答で一覧を受け取ったか(受け取っていたら、そちらが正本) */
|
|
4747
|
+
var adopted = false;
|
|
4243
4748
|
|
|
4244
4749
|
// **Slack / GitHub / Figma / Drive と同じレコードの行にする(2026-08-21・オーナー指摘)。**
|
|
4245
4750
|
// それまでは「現在の接続先」というラベル付きの独自レイアウト(.ds-current /
|
|
@@ -4247,7 +4752,29 @@ ${capabilityState.toString()}
|
|
|
4247
4752
|
var listEl = el("div", { class: "ds-list" });
|
|
4248
4753
|
// **0件の1行は一覧の外に置く**(Figma / Drive と同じ形)。中に .ds-card として
|
|
4249
4754
|
// 置くと「行が1件ある」と読まれ、一覧を数えるテストにも行として数えられる
|
|
4250
|
-
|
|
4755
|
+
// **反映待ちのあいだは「未設定」と言わない。** 保存はできているので事実に反する
|
|
4756
|
+
var EMPTY_TEXT = "同期する接続先はまだ登録されていません(未設定)。";
|
|
4757
|
+
var emptyEl = reflectMark(
|
|
4758
|
+
el("p", { class: "ds-empty", text: EMPTY_TEXT }),
|
|
4759
|
+
"課題管理", "empty",
|
|
4760
|
+
// 🔴 **反映が届いたら、届いた一覧で描き直す。** 下地(=この0件の文言)へ戻すと、
|
|
4761
|
+
// 反映が届いた瞬間に「⏳ 反映中」→「同期する接続先はまだ登録されていません(未設定)。」
|
|
4762
|
+
// になり、**行があるのに無いと言う**。settle は「新しいスナップショットが届いた」
|
|
4763
|
+
// ことなので、そのデータで一覧を作り直す(adoptTargets と同じ形)。
|
|
4764
|
+
// モーダルを開いたまま待っているときは、これが唯一の描き直しの機会
|
|
4765
|
+
function () {
|
|
4766
|
+
// まだ箱を作っていない(=最初の描画の途中)。paint() は箱へ出し入れするので、
|
|
4767
|
+
// ここで走らせない。最初の描画はこの直後の paint() が受け持つ
|
|
4768
|
+
if (!box) return null;
|
|
4769
|
+
// 🔴 **応答の一覧を上書きしない。** 追加・削除の応答に添えられた一覧
|
|
4770
|
+
//(adoptTargets)は、この画面が持っている中でいちばん新しい正本。
|
|
4771
|
+
// スナップショットから引き直すと、外したばかりの接続先が戻って見える
|
|
4772
|
+
if (adopted) return null;
|
|
4773
|
+
targets = backlogTargetsNow(internalSourceOf("課題管理"));
|
|
4774
|
+
paint();
|
|
4775
|
+
return { text: EMPTY_TEXT, hidden: false };
|
|
4776
|
+
},
|
|
4777
|
+
);
|
|
4251
4778
|
// 追加・削除の結果はここに1つだけ出す(行を描き直しても消えない位置)
|
|
4252
4779
|
var msg = el("div", { class: "ds-toggle-msg" });
|
|
4253
4780
|
|
|
@@ -4332,6 +4859,9 @@ ${capabilityState.toString()}
|
|
|
4332
4859
|
var here = emptyEl.parentNode === box;
|
|
4333
4860
|
if (!targets.length && !here) box.insertBefore(emptyEl, listEl.nextSibling);
|
|
4334
4861
|
else if (targets.length && here) box.removeChild(emptyEl);
|
|
4862
|
+
// **出し入れのたびに反映中を当て直す。** 外している間の描き直しはこの行に届かない
|
|
4863
|
+
// ので、戻すときに当てないと古い文言のまま出る
|
|
4864
|
+
applyReflect(emptyEl, reflectingNow());
|
|
4335
4865
|
}
|
|
4336
4866
|
|
|
4337
4867
|
/**
|
|
@@ -4339,6 +4869,9 @@ ${capabilityState.toString()}
|
|
|
4339
4869
|
* (=他の人が先に変えていた。409 の文面をこちらの言葉に差し替える判断に使う)。
|
|
4340
4870
|
*/
|
|
4341
4871
|
function adoptTargets(list) {
|
|
4872
|
+
// **応答の一覧を受け取ったら、以後はスナップショットから引き直さない**
|
|
4873
|
+
//(応答のほうが新しい。反映待ちが終わっても、この一覧を上書きしない)
|
|
4874
|
+
adopted = true;
|
|
4342
4875
|
var before = targetsGenerationOf(targets);
|
|
4343
4876
|
var next = [];
|
|
4344
4877
|
for (var i = 0; i < list.length; i++) {
|
|
@@ -4939,6 +5472,16 @@ ${capabilityState.toString()}
|
|
|
4939
5472
|
msg.appendChild(el("a", { href: j.refreshUrl, target: "_blank", rel: "noopener", text: "反映の状況 →" }));
|
|
4940
5473
|
}
|
|
4941
5474
|
btn.disabled = false;
|
|
5475
|
+
// **反映待ちとして記録する。** 一覧はスナップショットから描かれるので、
|
|
5476
|
+
// 保存できていても数分は「未設定」と出る。その間だけ「⏳ 反映中」に差し替える。
|
|
5477
|
+
//
|
|
5478
|
+
// ⚠ **onDone の中に置かない。** removeRow は onDone に null を渡しているので、
|
|
5479
|
+
// 中に置くと**接続先の「削除」だけ黙って記録されない**(remove の判定が守りたい当のケース)。
|
|
5480
|
+
//
|
|
5481
|
+
// - changed === false(既に同じものが入っていた)は記録しない——待つものが無い
|
|
5482
|
+
// - refreshUrl が無い(自己チェックを起こせなかった)ときも記録しない
|
|
5483
|
+
// ——待っても来ないので「反映中」が嘘になる
|
|
5484
|
+
if (j.changed !== false && j.refreshUrl) rememberReflecting(payload, j.refreshUrl);
|
|
4942
5485
|
// **何も変わっていないなら「追加した」と見せない。** 既に同じものが入っていた場合
|
|
4943
5486
|
// サーバは 200 + changed:false を返す。ここを res.ok だけで判定すると
|
|
4944
5487
|
// 「変更はありません」と言いながら追加した行が出る
|
|
@@ -5606,6 +6149,10 @@ ${capabilityState.toString()}
|
|
|
5606
6149
|
LIVE_FLEET_FAILED = false;
|
|
5607
6150
|
liveFleetRequested = false;
|
|
5608
6151
|
refreshDataSources();
|
|
6152
|
+
// **モーダルを開いたままでも「反映中」だけは追いつかせる。** refreshDataSources は
|
|
6153
|
+
// モーダルが開いている間・入力中は何もしない(下地を入れ替えると開いているモーダルの
|
|
6154
|
+
// 中身が切り離される)ので、局所的に差し替える口をここに置く
|
|
6155
|
+
repaintReflect(document);
|
|
5609
6156
|
})
|
|
5610
6157
|
.catch(function () {
|
|
5611
6158
|
// 静的ビルドの値で描き続ける。ただし「古いかもしれない」ことは画面に出す
|
|
@@ -5613,6 +6160,7 @@ ${capabilityState.toString()}
|
|
|
5613
6160
|
LIVE_FLEET_FAILED = true;
|
|
5614
6161
|
liveFleetRequested = false;
|
|
5615
6162
|
refreshDataSources();
|
|
6163
|
+
repaintReflect(document);
|
|
5616
6164
|
});
|
|
5617
6165
|
}
|
|
5618
6166
|
|
|
@@ -5620,6 +6168,9 @@ ${capabilityState.toString()}
|
|
|
5620
6168
|
renderConnMap();
|
|
5621
6169
|
renderDataSources();
|
|
5622
6170
|
loadLiveFleet();
|
|
6171
|
+
// **反映待ちがあれば自動で取り直す(再読み込みしても続く)。** 反映待ちが
|
|
6172
|
+
// 1件も無ければタイマは張らない
|
|
6173
|
+
scheduleReflectRefresh();
|
|
5623
6174
|
// **取り込みカードと鍵カードは能力セクションへ移した**(#101)。
|
|
5624
6175
|
// ここで再度出すと二重になる。手動投入は 会議 / 共有資料 の中にある。
|
|
5625
6176
|
}
|