@takagaki/cortex-decisions-viewer 0.9.1 → 0.10.1
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 +262 -6
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -6,6 +6,8 @@ exports.livePending = livePending;
|
|
|
6
6
|
exports.nextCheckDelay = nextCheckDelay;
|
|
7
7
|
exports.settledPending = settledPending;
|
|
8
8
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
9
|
+
exports.sanitizeStateNow = sanitizeStateNow;
|
|
10
|
+
exports.overlayStateOnFleet = overlayStateOnFleet;
|
|
9
11
|
exports.chatRowsToAdd = chatRowsToAdd;
|
|
10
12
|
exports.devRowsToAdd = devRowsToAdd;
|
|
11
13
|
exports.designRowsToAdd = designRowsToAdd;
|
|
@@ -13,6 +15,7 @@ exports.materialRowsToAdd = materialRowsToAdd;
|
|
|
13
15
|
exports.issueRowsToAdd = issueRowsToAdd;
|
|
14
16
|
exports.unsettledRegisteredCount = unsettledRegisteredCount;
|
|
15
17
|
exports.registeredRowsToAdd = registeredRowsToAdd;
|
|
18
|
+
exports.subtractRemovedRegistered = subtractRemovedRegistered;
|
|
16
19
|
exports.mergeRegisteredFleet = mergeRegisteredFleet;
|
|
17
20
|
exports.nextStateDelay = nextStateDelay;
|
|
18
21
|
exports.renderConnectionMap = renderConnectionMap;
|
|
@@ -564,6 +567,66 @@ function sanitizeRegisteredNow(v) {
|
|
|
564
567
|
}
|
|
565
568
|
return any ? out : null;
|
|
566
569
|
}
|
|
570
|
+
/** 接続状態ストアの語彙(サーバ側 connection-state.mjs と同じ。増やさない) */
|
|
571
|
+
const STATE_STATUSES = {
|
|
572
|
+
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown"],
|
|
573
|
+
ingested: ["ok", "failed", "skipped"],
|
|
574
|
+
};
|
|
575
|
+
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "lastOkAt", "lastFailAt"];
|
|
576
|
+
/** stateNow はライブ専用キーで build.ts の網にかからない。画面側でも許可リストを通す */
|
|
577
|
+
function sanitizeStateNow(v) {
|
|
578
|
+
if (!Array.isArray(v))
|
|
579
|
+
return null;
|
|
580
|
+
const out = [];
|
|
581
|
+
for (const r of v) {
|
|
582
|
+
if (out.length >= 500)
|
|
583
|
+
break;
|
|
584
|
+
if (r == null || typeof r !== "object" || Array.isArray(r))
|
|
585
|
+
continue;
|
|
586
|
+
const item = {};
|
|
587
|
+
for (const f of STATE_ROW_FIELDS) {
|
|
588
|
+
const fv = r[f];
|
|
589
|
+
if (typeof fv === "string" && fv !== "")
|
|
590
|
+
item[f] = fv;
|
|
591
|
+
}
|
|
592
|
+
const vocab = STATE_STATUSES[item.question];
|
|
593
|
+
if (!vocab || vocab.indexOf(item.status) < 0)
|
|
594
|
+
continue; // 知らない語は「不明」に化けさせず落とす
|
|
595
|
+
if (!item.cap || !item.ref || !item.at)
|
|
596
|
+
continue;
|
|
597
|
+
out.push(item);
|
|
598
|
+
}
|
|
599
|
+
return out.length ? out : null;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* ストアの「届くか」を行に重ねる。**まとめ表より新しいときだけ**——古い確認結果で
|
|
603
|
+
* 新しい実測を潰さない(時刻の比較は ISO 文字列の辞書順で足りる)。
|
|
604
|
+
* 対象はチャットの行だけ(段A。確認ボタンが書くのがチャットだけのため)。
|
|
605
|
+
*/
|
|
606
|
+
function overlayStateOnFleet(view, rows, generatedAt) {
|
|
607
|
+
const v = view || {};
|
|
608
|
+
if (!rows || !rows.length || !Array.isArray(v.externalSources))
|
|
609
|
+
return v;
|
|
610
|
+
const byKey = {};
|
|
611
|
+
for (const r of rows) {
|
|
612
|
+
if (r.cap !== "chat" || r.question !== "reachable")
|
|
613
|
+
continue;
|
|
614
|
+
byKey[String(r.workspace || "") + "\t" + String(r.ref)] = r;
|
|
615
|
+
}
|
|
616
|
+
let changed = false;
|
|
617
|
+
const externals = v.externalSources.map((s) => {
|
|
618
|
+
if (!s || s.type !== "slack" || !s.ref)
|
|
619
|
+
return s;
|
|
620
|
+
const hit = byKey[String(s.workspace || "") + "\t" + String(s.ref)];
|
|
621
|
+
if (!hit)
|
|
622
|
+
return s;
|
|
623
|
+
if (generatedAt && hit.at <= generatedAt)
|
|
624
|
+
return s;
|
|
625
|
+
changed = true;
|
|
626
|
+
return Object.assign({}, s, { gate: hit.status, gateAt: hit.at, gateBy: hit.by });
|
|
627
|
+
});
|
|
628
|
+
return changed ? Object.assign({}, v, { externalSources: externals }) : v;
|
|
629
|
+
}
|
|
567
630
|
/**
|
|
568
631
|
* チャットの行で、まとめ表がまだ知らないもの。
|
|
569
632
|
*
|
|
@@ -707,9 +770,140 @@ function registeredRowsToAdd(view, reg) {
|
|
|
707
770
|
};
|
|
708
771
|
}
|
|
709
772
|
/**
|
|
710
|
-
*
|
|
773
|
+
* 登録ファイルから**消された**行を、まとめ表の写しから引く(段A補・2026-08-22)。
|
|
774
|
+
*
|
|
775
|
+
* 「解除しました」と出るのに、リロードすると数分間残る——追加は足していたのに、
|
|
776
|
+
* 削除はまとめ表の作り直し待ちだった。登録ファイルは今の正本なので、
|
|
777
|
+
* そこに無い行は消されたということ。
|
|
778
|
+
*
|
|
779
|
+
* 🔴 **能力のキーが在るときだけ引く。** キーが無い=登録ファイルが読めなかった、なので
|
|
780
|
+
* 引くと「読めない」が「全部消えた」に化ける。
|
|
781
|
+
* 🔴 **チャットは origin が "derived" の行だけ。** dev-sources.json 側にも宣言された行
|
|
782
|
+
* (explicit / both)は channels.json から消してもエンジンがまだ読む——隠すと
|
|
783
|
+
* 「読まれているのに画面に無い」という逆向きの嘘になる。
|
|
784
|
+
* 🔴 **チャットの突き合わせは ref だけ(追加側と非対称にしてある)。** まとめ表の
|
|
785
|
+
* workspace はエンジンが実測で埋めることがあり、宣言(登録ファイル)側と食い違う。
|
|
786
|
+
* ID+WSで引くと、実測で埋まった行を**誤って隠す**。ref 一致の登録が1つでも残って
|
|
787
|
+
* いれば引かない——引き損ね(数分の遅れ)は安全側、誤削除は嘘なので。
|
|
788
|
+
*/
|
|
789
|
+
function subtractRemovedRegistered(view, reg) {
|
|
790
|
+
const v = view || {};
|
|
791
|
+
if (!reg)
|
|
792
|
+
return v;
|
|
793
|
+
let out = v;
|
|
794
|
+
const set = (patch) => { out = out === v ? Object.assign({}, v, patch) : Object.assign(out, patch); };
|
|
795
|
+
if (Array.isArray(reg.chat) || Array.isArray(reg.dev)) {
|
|
796
|
+
const chatRefs = {};
|
|
797
|
+
for (const r of reg.chat || [])
|
|
798
|
+
if (r && r.ref)
|
|
799
|
+
chatRefs[String(r.ref)] = true;
|
|
800
|
+
const devRefs = {};
|
|
801
|
+
for (const r of reg.dev || [])
|
|
802
|
+
if (r && r.ref)
|
|
803
|
+
devRefs[String(r.ref)] = true;
|
|
804
|
+
const before = v.externalSources || [];
|
|
805
|
+
const after = before.filter((s) => {
|
|
806
|
+
if (!s || !s.ref)
|
|
807
|
+
return true;
|
|
808
|
+
if (s.type === "slack" && Array.isArray(reg.chat)) {
|
|
809
|
+
if (s.origin !== "derived")
|
|
810
|
+
return true;
|
|
811
|
+
return chatRefs[String(s.ref)] === true;
|
|
812
|
+
}
|
|
813
|
+
if (String(s.type || "").indexOf("github") === 0 && Array.isArray(reg.dev)) {
|
|
814
|
+
// 「読み取り停止中」(exclude)の行は登録簿の sources に居ないが、
|
|
815
|
+
// まとめ表はバッジを出すために保持している——引くと停止中の表示ごと消える
|
|
816
|
+
if (s.goldState === "excluded")
|
|
817
|
+
return true;
|
|
818
|
+
return devRefs[String(s.ref)] === true;
|
|
819
|
+
}
|
|
820
|
+
return true;
|
|
821
|
+
});
|
|
822
|
+
if (after.length !== before.length)
|
|
823
|
+
set({ externalSources: after });
|
|
824
|
+
}
|
|
825
|
+
if (Array.isArray(reg.design) || Array.isArray(reg.materials)) {
|
|
826
|
+
const internals = ((out.internalSources || v.internalSources) || []).slice();
|
|
827
|
+
let changed = false;
|
|
828
|
+
if (Array.isArray(reg.design)) {
|
|
829
|
+
const keys = {};
|
|
830
|
+
for (const r of reg.design)
|
|
831
|
+
if (r && r.key)
|
|
832
|
+
keys[String(r.key)] = true;
|
|
833
|
+
for (let i = 0; i < internals.length; i++) {
|
|
834
|
+
const it = internals[i];
|
|
835
|
+
if (!it || it.kind !== REGISTERED_CAP_NAMES.design || !Array.isArray(it.figmaFiles))
|
|
836
|
+
continue;
|
|
837
|
+
const files = it.figmaFiles.filter((f) => f && keys[String(f.key)] === true);
|
|
838
|
+
if (files.length !== it.figmaFiles.length) {
|
|
839
|
+
internals[i] = Object.assign({}, it, { figmaFiles: files });
|
|
840
|
+
changed = true;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (Array.isArray(reg.materials)) {
|
|
845
|
+
const ids = {};
|
|
846
|
+
for (const r of reg.materials)
|
|
847
|
+
if (r && r.id)
|
|
848
|
+
ids[String(r.id)] = true;
|
|
849
|
+
for (let i = 0; i < internals.length; i++) {
|
|
850
|
+
const it = internals[i];
|
|
851
|
+
if (!it || it.kind !== REGISTERED_CAP_NAMES.materials || !Array.isArray(it.driveFolderIds))
|
|
852
|
+
continue;
|
|
853
|
+
const kept = it.driveFolderIds.filter((id) => ids[String(id)] === true);
|
|
854
|
+
if (kept.length !== it.driveFolderIds.length) {
|
|
855
|
+
const names = {};
|
|
856
|
+
for (const id of kept)
|
|
857
|
+
if (it.driveFolderNames && it.driveFolderNames[id])
|
|
858
|
+
names[id] = it.driveFolderNames[id];
|
|
859
|
+
const patch = { driveFolderIds: kept, driveFolderNames: names };
|
|
860
|
+
// 0件になったら empty に倒す(正本が0件と言っている)。倒さないと
|
|
861
|
+
// 一覧が**無言で空**になり「まだ登録されていません」も出ない。
|
|
862
|
+
// **倒すのは on のときだけ**——broken(設定が読めない)/ off(意図的な停止)は
|
|
863
|
+
// 別の答えなので潰さない
|
|
864
|
+
if (!kept.length && it.driveState === "on")
|
|
865
|
+
patch.driveState = "empty";
|
|
866
|
+
internals[i] = Object.assign({}, it, patch);
|
|
867
|
+
changed = true;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (changed)
|
|
872
|
+
set({ internalSources: internals });
|
|
873
|
+
}
|
|
874
|
+
if (Array.isArray(reg.issues)) {
|
|
875
|
+
// 鍵の畳み方は足す側(issueRowsToAdd)と同じにする——別の畳み方にすると
|
|
876
|
+
// 大小の違いで「引いた直後に別綴りで足し直される」形になる
|
|
877
|
+
const issueKey = (t) => String(t.domain).toLowerCase() + "/" + String(t.projectKey).toUpperCase();
|
|
878
|
+
const keys = {};
|
|
879
|
+
for (const r of reg.issues)
|
|
880
|
+
if (r)
|
|
881
|
+
keys[issueKey(r)] = true;
|
|
882
|
+
const bt = (out.backlogTarget || v.backlogTarget);
|
|
883
|
+
const cur = bt && Array.isArray(bt.targets) ? bt.targets : bt && bt.domain && bt.projectKey ? [bt] : [];
|
|
884
|
+
const kept = cur.filter((t) => t && keys[issueKey(t)] === true);
|
|
885
|
+
if (kept.length !== cur.length) {
|
|
886
|
+
const patch = Object.assign({}, bt || {}, { targets: kept });
|
|
887
|
+
// 単数の2つは先頭の接続先。0件なら欄ごと消す(「無い」を装った値で残さない)
|
|
888
|
+
if (kept.length) {
|
|
889
|
+
patch.domain = kept[0].domain;
|
|
890
|
+
patch.projectKey = kept[0].projectKey;
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
delete patch.domain;
|
|
894
|
+
delete patch.projectKey;
|
|
895
|
+
}
|
|
896
|
+
set({ backlogTarget: patch });
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return out;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* まとめ表の3キー(externalSources / internalSources / backlogTarget)を、登録ファイルの
|
|
903
|
+
* 今の姿に合わせる——**消された行を引いてから、足りない行を足す**(段A補)。
|
|
711
904
|
*
|
|
712
|
-
*
|
|
905
|
+
* 引き算の条件と守りは subtractRemovedRegistered が持つ。既存の行の**欄**は書き換えない
|
|
906
|
+
* (状態・origin・名前はまとめ表のまま)。
|
|
713
907
|
*
|
|
714
908
|
* ⚠ 同期ソースの行がまだ無い能力(=夜間バッチが一度も走っていない案件)には、
|
|
715
909
|
* **その能力の行を作る**——作らないと、登録した Figma ファイル・Drive フォルダを
|
|
@@ -719,7 +913,8 @@ function registeredRowsToAdd(view, reg) {
|
|
|
719
913
|
* (まとめ表が持っている値は上書きしない)。
|
|
720
914
|
*/
|
|
721
915
|
function mergeRegisteredFleet(view, reg) {
|
|
722
|
-
|
|
916
|
+
// 先に「消された行」を引き、そのうえで「足りない行」を足す(登録ファイルが今の正本)
|
|
917
|
+
const v = subtractRemovedRegistered(view || {}, reg);
|
|
723
918
|
const out = {
|
|
724
919
|
externalSources: v.externalSources,
|
|
725
920
|
internalSources: v.internalSources,
|
|
@@ -1830,6 +2025,7 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1830
2025
|
/* 全組織が接続済みのときに、トークン入力欄の代わりに出す1行(#118) */
|
|
1831
2026
|
.ds-all-connected { color: var(--muted); }
|
|
1832
2027
|
.ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
|
|
2028
|
+
.ds-gate-at { font-size: 11px; color: var(--muted); flex: 0 0 auto; }
|
|
1833
2029
|
.ds-gate::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; }
|
|
1834
2030
|
.ds-gate-ok { color: #1f7a45; }
|
|
1835
2031
|
.ds-gate-warn { color: #b45309; }
|
|
@@ -2121,9 +2317,14 @@ ${designRowsToAdd.toString()}
|
|
|
2121
2317
|
${materialRowsToAdd.toString()}
|
|
2122
2318
|
${issueRowsToAdd.toString()}
|
|
2123
2319
|
${internalOf.toString()}
|
|
2320
|
+
${subtractRemovedRegistered.toString()}
|
|
2124
2321
|
${registeredRowsToAdd.toString()}
|
|
2125
2322
|
${unsettledRegisteredCount.toString()}
|
|
2126
2323
|
${mergeRegisteredFleet.toString()}
|
|
2324
|
+
var STATE_STATUSES = ${JSON.stringify(STATE_STATUSES)};
|
|
2325
|
+
var STATE_ROW_FIELDS = ${JSON.stringify(STATE_ROW_FIELDS)};
|
|
2326
|
+
${sanitizeStateNow.toString()}
|
|
2327
|
+
${overlayStateOnFleet.toString()}
|
|
2127
2328
|
${nextStateDelay.toString()}
|
|
2128
2329
|
// 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
2129
2330
|
var TUTORIAL = (function () {
|
|
@@ -3520,6 +3721,8 @@ ${nextStateDelay.toString()}
|
|
|
3520
3721
|
* 指すのか説明できなくなる(realtime を別に持っているのと同じ理由)。
|
|
3521
3722
|
*/
|
|
3522
3723
|
var LIVE_REGISTERED = null;
|
|
3724
|
+
// 接続状態ストアの行(届くか・取り込めたか)。op=state の stateNow から
|
|
3725
|
+
var LIVE_STATE = null;
|
|
3523
3726
|
/** マージ済みの3キー(作り直しは setLive* だけが行う) */
|
|
3524
3727
|
var MERGED = null;
|
|
3525
3728
|
/** マージの対象になるキー。これ以外は素の値をそのまま返す */
|
|
@@ -3547,8 +3750,16 @@ ${nextStateDelay.toString()}
|
|
|
3547
3750
|
* 足す場所を描画側に散らすと「タイルは1件・一覧は2件」のような食い違いが必ず出る。
|
|
3548
3751
|
*/
|
|
3549
3752
|
function fleetVal(key) {
|
|
3550
|
-
if (LIVE_REGISTERED && MERGE_KEYS[key]) {
|
|
3551
|
-
|
|
3753
|
+
if ((LIVE_REGISTERED || LIVE_STATE) && MERGE_KEYS[key]) {
|
|
3754
|
+
// 登録の追加 → 状態の上書き、の順で1回だけ合成する。
|
|
3755
|
+
// 状態ストアの答えは、まとめ表より新しいときだけ勝つ(overlayStateOnFleet が判定)
|
|
3756
|
+
if (!MERGED) {
|
|
3757
|
+
MERGED = overlayStateOnFleet(
|
|
3758
|
+
mergeRegisteredFleet(rawView(), LIVE_REGISTERED),
|
|
3759
|
+
LIVE_STATE,
|
|
3760
|
+
String(rawFleetVal("generatedAt") || ""),
|
|
3761
|
+
);
|
|
3762
|
+
}
|
|
3552
3763
|
return MERGED[key];
|
|
3553
3764
|
}
|
|
3554
3765
|
return rawFleetVal(key);
|
|
@@ -3585,6 +3796,29 @@ ${nextStateDelay.toString()}
|
|
|
3585
3796
|
if (add.design.length) out[REGISTERED_CAP_NAMES.design] = 1;
|
|
3586
3797
|
if (add.materials.length) out[REGISTERED_CAP_NAMES.materials] = 1;
|
|
3587
3798
|
if (add.issues.length) out[REGISTERED_CAP_NAMES.issues] = 1;
|
|
3799
|
+
// **引き算で0件になった能力も同じ語に倒す(段A補)。** 夜間の capabilities は
|
|
3800
|
+
// まだ「on」なので、そのまま出すとタイルは「接続OK」・一覧は0件という矛盾になる。
|
|
3801
|
+
// 「未接続」と言い切るのは夜間が追いついてから(capabilities は引き算の対象外)
|
|
3802
|
+
var raw = rawView();
|
|
3803
|
+
var sub = subtractRemovedRegistered(raw, LIVE_REGISTERED);
|
|
3804
|
+
var count = function (list, isType) {
|
|
3805
|
+
var n = 0;
|
|
3806
|
+
for (var j = 0; j < (list || []).length; j++) if (list[j] && isType(list[j])) n++;
|
|
3807
|
+
return n;
|
|
3808
|
+
};
|
|
3809
|
+
var isSlack = function (x) { return x.type === "slack"; };
|
|
3810
|
+
var isGh = function (x) { return String(x.type || "").indexOf("github") === 0; };
|
|
3811
|
+
if (count(raw.externalSources, isSlack) > 0 && count(sub.externalSources, isSlack) === 0) out["チャット"] = 1;
|
|
3812
|
+
if (count(raw.externalSources, isGh) > 0 && count(sub.externalSources, isGh) === 0) out["開発"] = 1;
|
|
3813
|
+
var dRaw = internalOf(raw.internalSources || [], REGISTERED_CAP_NAMES.design);
|
|
3814
|
+
var dSub = internalOf(sub.internalSources || [], REGISTERED_CAP_NAMES.design);
|
|
3815
|
+
if (dRaw && (dRaw.figmaFiles || []).length > 0 && dSub && (dSub.figmaFiles || []).length === 0) out[REGISTERED_CAP_NAMES.design] = 1;
|
|
3816
|
+
var mRaw = internalOf(raw.internalSources || [], REGISTERED_CAP_NAMES.materials);
|
|
3817
|
+
var mSub = internalOf(sub.internalSources || [], REGISTERED_CAP_NAMES.materials);
|
|
3818
|
+
if (mRaw && (mRaw.driveFolderIds || []).length > 0 && mSub && (mSub.driveFolderIds || []).length === 0) out[REGISTERED_CAP_NAMES.materials] = 1;
|
|
3819
|
+
var btRaw = raw.backlogTarget;
|
|
3820
|
+
var rawTargets = btRaw && Array.isArray(btRaw.targets) ? btRaw.targets.length : btRaw && btRaw.domain ? 1 : 0;
|
|
3821
|
+
if (rawTargets > 0 && Array.isArray(LIVE_REGISTERED.issues) && LIVE_REGISTERED.issues.length === 0) out[REGISTERED_CAP_NAMES.issues] = 1;
|
|
3588
3822
|
return out;
|
|
3589
3823
|
}
|
|
3590
3824
|
/** その能力に、まとめ表がまだ知らない行があるか */
|
|
@@ -3599,10 +3833,19 @@ ${nextStateDelay.toString()}
|
|
|
3599
3833
|
* 変わったとき(例えば pending の語をやめたとき)に「接続OK」が復活しないようにする。
|
|
3600
3834
|
*/
|
|
3601
3835
|
function stateCtx() {
|
|
3836
|
+
// 🔴 **ストアの上書き(overlayStateOnFleet)だけは適用する。** 行の一覧は fleetVal
|
|
3837
|
+
// (上書き済み)で描くので、ここが素のままだと「行は接続OK・タイルは⚠️」という
|
|
3838
|
+
// 同じ画面での食い違いが出る(実装レビューで指摘)。registeredNow の**行の追加**を
|
|
3839
|
+
// 混ぜない、という上の注意はそのまま——追加はここでは適用しない
|
|
3840
|
+
var ov = overlayStateOnFleet(
|
|
3841
|
+
{ externalSources: rawFleetVal("externalSources") || [] },
|
|
3842
|
+
LIVE_STATE,
|
|
3843
|
+
String(rawFleetVal("generatedAt") || ""),
|
|
3844
|
+
);
|
|
3602
3845
|
return {
|
|
3603
3846
|
capabilities: rawFleetVal("capabilities") || {},
|
|
3604
3847
|
internals: rawFleetVal("internalSources") || [],
|
|
3605
|
-
externals:
|
|
3848
|
+
externals: ov.externalSources || [],
|
|
3606
3849
|
pipes: rawFleetVal("pipelines") || [],
|
|
3607
3850
|
};
|
|
3608
3851
|
}
|
|
@@ -5107,6 +5350,9 @@ ${nextStateDelay.toString()}
|
|
|
5107
5350
|
var v = probeVerdict(s, j);
|
|
5108
5351
|
gateEl.className = "ds-gate " + v.cls;
|
|
5109
5352
|
gateEl.textContent = v.text;
|
|
5353
|
+
// 古い「X分前に確認」を残さない(下に出るいまの結果と、時刻が2つ並ぶ)
|
|
5354
|
+
var oldAt = card.querySelector(".ds-gate-at");
|
|
5355
|
+
if (oldAt) oldAt.remove();
|
|
5110
5356
|
// 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
|
|
5111
5357
|
// 本文へ落とすと、いちばん要る情報が説明文に埋もれる
|
|
5112
5358
|
if (j.gate === "missing_scope") {
|
|
@@ -5177,6 +5423,10 @@ ${nextStateDelay.toString()}
|
|
|
5177
5423
|
if (x && x.domain && x.projectKey) out.push({ domain: String(x.domain), projectKey: String(x.projectKey) });
|
|
5178
5424
|
}
|
|
5179
5425
|
if (out.length) return out;
|
|
5426
|
+
// 🔴 **正本(issues-config.json)が読めていて0件のときは、url から復元しない(段A補)。**
|
|
5427
|
+
// このフォールバックは「古いまとめ表に targets が無い」案件を救うためのもの。
|
|
5428
|
+
// 正本が「無い」と言っているのに復元すると、**最後の1件を解除してもリロードで戻ってくる**
|
|
5429
|
+
if (LIVE_REGISTERED && Array.isArray(LIVE_REGISTERED.issues) && LIVE_REGISTERED.issues.length === 0) return [];
|
|
5180
5430
|
var u = (s && s.url && backlogTargetOf(s.url)) || null;
|
|
5181
5431
|
return u ? [u] : [];
|
|
5182
5432
|
}
|
|
@@ -6530,6 +6780,10 @@ ${nextStateDelay.toString()}
|
|
|
6530
6780
|
// **「接続を確認」が押されたら、この要素を live の判定で置き換える**(probeSection)。
|
|
6531
6781
|
// 2つの判定を同じ行に並べないため、参照を持っておく
|
|
6532
6782
|
var gateEl = el("span", { class: "ds-gate " + gate.cls, text: gate.text });
|
|
6783
|
+
// 状態ストア由来の判定には「いつ確かめたか」を必ず添える
|
|
6784
|
+
var gateAtEl = s.gateAt
|
|
6785
|
+
? el("span", { class: "ds-gate-at", text: rtAgo(s.gateAt) + "に確認" })
|
|
6786
|
+
: null;
|
|
6533
6787
|
var card = el("div", { class: "ds-card" }, [
|
|
6534
6788
|
el("div", { class: "ds-card-main" }, [
|
|
6535
6789
|
el("span", { class: "ds-icon", text: meta.icon }),
|
|
@@ -6540,6 +6794,7 @@ ${nextStateDelay.toString()}
|
|
|
6540
6794
|
badges,
|
|
6541
6795
|
gateEl,
|
|
6542
6796
|
]);
|
|
6797
|
+
if (gateAtEl) card.appendChild(gateAtEl);
|
|
6543
6798
|
// チャンネルの登録解除。書き込み経路(intake Lambda)がある案件でだけ出す。
|
|
6544
6799
|
// **Slack だけ。** github は登録ファイルが別(dev-sources.json)で、下のトグルと
|
|
6545
6800
|
// 「登録から削除」が担う。
|
|
@@ -6849,6 +7104,7 @@ ${nextStateDelay.toString()}
|
|
|
6849
7104
|
// (=立ち上げ直後=この仕組みが一番効く案件)は下で throw するので、
|
|
6850
7105
|
// ここより後に置くと**その案件だけ素通り**する
|
|
6851
7106
|
LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
|
|
7107
|
+
LIVE_STATE = sanitizeStateNow(j && j.stateNow);
|
|
6852
7108
|
MERGED = null;
|
|
6853
7109
|
if (!j || !j.fleet) throw new Error("no fleet");
|
|
6854
7110
|
LIVE_FLEET = normalizeFleet(j.fleet);
|