@takagaki/cortex-decisions-viewer 0.10.7 → 0.11.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 +677 -57
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -19,6 +19,14 @@ exports.registeredRowsToAdd = registeredRowsToAdd;
|
|
|
19
19
|
exports.subtractRemovedRegistered = subtractRemovedRegistered;
|
|
20
20
|
exports.mergeRegisteredFleet = mergeRegisteredFleet;
|
|
21
21
|
exports.nextStateDelay = nextStateDelay;
|
|
22
|
+
exports.sanitizeSecretsNow = sanitizeSecretsNow;
|
|
23
|
+
exports.secretStateFromNow = secretStateFromNow;
|
|
24
|
+
exports.secretRegisteredAnywhere = secretRegisteredAnywhere;
|
|
25
|
+
exports.sanitizeCapabilitiesNow = sanitizeCapabilitiesNow;
|
|
26
|
+
exports.sanitizeGithubOwnersNow = sanitizeGithubOwnersNow;
|
|
27
|
+
exports.splitNeededParts = splitNeededParts;
|
|
28
|
+
exports.ingestedAtOf = ingestedAtOf;
|
|
29
|
+
exports.usageFactMissing = usageFactMissing;
|
|
22
30
|
exports.renderConnectionMap = renderConnectionMap;
|
|
23
31
|
/** SiteDataを単一の自己完結HTML(埋め込みデータ+CSS+JS)にレンダリングする */
|
|
24
32
|
function renderSite(site) {
|
|
@@ -1058,6 +1066,273 @@ function nextStateDelay(count, since, now) {
|
|
|
1058
1066
|
return null;
|
|
1059
1067
|
return left < UNSETTLED_POLL_MS ? left : UNSETTLED_POLL_MS;
|
|
1060
1068
|
}
|
|
1069
|
+
// ---------------------------------------------------------------------------
|
|
1070
|
+
// 工程1.5: 正本を直接読む(op=state v2 の secretsNow / capabilitiesNow / githubOwners)
|
|
1071
|
+
//
|
|
1072
|
+
// 🔴 **これは React 移行(#224)までの止血パッチ。** 差し替えるのは「読む先」だけで、
|
|
1073
|
+
// 4層(静的 → まとめ表 → registeredNow → stateNow)の合成そのものには手を付けない。
|
|
1074
|
+
// 捨てる予定のコードなので、ここを起点に整理を始めない。
|
|
1075
|
+
//
|
|
1076
|
+
// 3つに共通する規則:
|
|
1077
|
+
// 1. **読めなかったら従来のまとめ表の値へ落ちる。** 画面と Lambda は別々に配られるので、
|
|
1078
|
+
// 新しいキーを返さない応答を読む期間が必ずある
|
|
1079
|
+
// 2. **"unknown"(確かめられなかった)を「無い」に丸めない。** 鍵が入っている案件に
|
|
1080
|
+
// 「登録してください」と言うのが、この修正がまさに殺そうとしている嘘(#227)
|
|
1081
|
+
// 3. ライブ専用キーは build.ts の許可リストの網にかからないので、**画面側でも通す**
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* 応答のキー(ASCII)→ 画面の能力名。**registeredNow の対応表に会議を足したもの。**
|
|
1085
|
+
*
|
|
1086
|
+
* 会議は登録の一覧を返さない(`REGISTERED_FIELDS` に無い)が、**能力の状態は持つ**
|
|
1087
|
+
* (`ingest-config.json` を読んだ結果)。対応表を分けると、能力を足したときに
|
|
1088
|
+
* 片方だけ書き足す形になるので、こちらは向こうを取り込んで1箇所に保つ。
|
|
1089
|
+
*/
|
|
1090
|
+
const STATE_CAP_NAMES = Object.assign({}, REGISTERED_CAP_NAMES, { meetings: "会議" });
|
|
1091
|
+
/** 鍵の登録状況(列2)の語彙。サーバ側 `secretsNowRow` と同じ(増やさない) */
|
|
1092
|
+
const SECRET_NOW_STATES = ["registered", "missing", "unknown"];
|
|
1093
|
+
/**
|
|
1094
|
+
* `secretsNow`(鍵が登録されているか・列2)の許可リスト。
|
|
1095
|
+
*
|
|
1096
|
+
* 応答の1行は `{ adapter, kind, state, source, space? }`。画面が使うのは kind / state /
|
|
1097
|
+
* space だけで、**adapter は使わない**(鍵カードは種別で引く)。source は「どの棚が
|
|
1098
|
+
* 答えたか」の内部語なので、画面に出す語へ畳むのは読み出し側(`secretStateFromNow`)。
|
|
1099
|
+
*/
|
|
1100
|
+
function sanitizeSecretsNow(v) {
|
|
1101
|
+
if (!Array.isArray(v))
|
|
1102
|
+
return null;
|
|
1103
|
+
const out = [];
|
|
1104
|
+
for (const r of v) {
|
|
1105
|
+
if (out.length >= 50)
|
|
1106
|
+
break;
|
|
1107
|
+
if (r == null || typeof r !== "object" || Array.isArray(r))
|
|
1108
|
+
continue;
|
|
1109
|
+
const kind = typeof r.kind === "string" ? r.kind : "";
|
|
1110
|
+
const state = typeof r.state === "string" ? r.state : "";
|
|
1111
|
+
// 知らない語は「未登録」に化けさせず落とす(落ちればまとめ表へフォールバックする)
|
|
1112
|
+
if (!kind || SECRET_NOW_STATES.indexOf(state) < 0)
|
|
1113
|
+
continue;
|
|
1114
|
+
const row = { kind, state };
|
|
1115
|
+
// 🔴 **スペースは小文字に畳む。** 突き合わせる相手(画面の行のドメイン)が小文字なので、
|
|
1116
|
+
// 揃えないと**入っている鍵を「未登録」と言う**(`issuesRefOf` と同じ理由)
|
|
1117
|
+
if (typeof r.space === "string" && r.space)
|
|
1118
|
+
row.space = r.space.toLowerCase();
|
|
1119
|
+
out.push(row);
|
|
1120
|
+
}
|
|
1121
|
+
return out.length ? out : null;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* `secretsNow` から、その鍵カードの答えを引く。**答えられないときは null。**
|
|
1125
|
+
*
|
|
1126
|
+
* null は「まとめ表の値へ落ちる」の合図で、次の3つで返る:
|
|
1127
|
+
* - その種別の行が1つも来ていない(古い Lambda・鍵を持たないアダプタ)
|
|
1128
|
+
* - スペース別に聞かれたのに、そのスペースの行が来ていない
|
|
1129
|
+
* - `unknown`(権限・スロットリングで確かめられなかった)
|
|
1130
|
+
*
|
|
1131
|
+
* @param space Backlog のスペース(画面の行のドメイン)。他の鍵では空
|
|
1132
|
+
*/
|
|
1133
|
+
function secretStateFromNow(rows, kind, space) {
|
|
1134
|
+
if (!Array.isArray(rows) || !rows.length || !kind)
|
|
1135
|
+
return null;
|
|
1136
|
+
const mine = rows.filter((r) => r && r.kind === kind);
|
|
1137
|
+
if (!mine.length)
|
|
1138
|
+
return null;
|
|
1139
|
+
let hit = null;
|
|
1140
|
+
if (space) {
|
|
1141
|
+
hit = mine.filter((r) => r.space === String(space).toLowerCase())[0] || null;
|
|
1142
|
+
if (!hit)
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
// **1行に決まるときだけ答える。** スペースが複数ある Backlog を種別だけで聞かれたら、
|
|
1147
|
+
// どの棚のことか決まらない——片方だけ入れた状態を「登録済み」と言うことになる
|
|
1148
|
+
if (mine.length > 1)
|
|
1149
|
+
return null;
|
|
1150
|
+
hit = mine[0];
|
|
1151
|
+
}
|
|
1152
|
+
if (hit.state === "registered")
|
|
1153
|
+
return { known: "yes", src: "Secrets Manager" };
|
|
1154
|
+
if (hit.state === "missing")
|
|
1155
|
+
return { known: "no", src: "" };
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* その種別の鍵が**どこかに**登録されているか(タイルの「⚠ トークンが未設定です」用)。
|
|
1160
|
+
*
|
|
1161
|
+
* タイルは能力に1枚しかないので、スペース別の答えは持てない。**まとめ表の
|
|
1162
|
+
* `secretSources[kind]`(種別ごとの1つの値)と同じ粒度**に畳む——1本でも入っていれば
|
|
1163
|
+
* 注記を出さない。スペースごとの充足は行の中の鍵カードが答える。
|
|
1164
|
+
*
|
|
1165
|
+
* @returns "yes" | "no" | "unknown"(unknown はまとめ表へフォールバック)
|
|
1166
|
+
*/
|
|
1167
|
+
function secretRegisteredAnywhere(rows, kind) {
|
|
1168
|
+
if (!Array.isArray(rows) || !kind)
|
|
1169
|
+
return "unknown";
|
|
1170
|
+
const mine = rows.filter((r) => r && r.kind === kind);
|
|
1171
|
+
if (!mine.length)
|
|
1172
|
+
return "unknown";
|
|
1173
|
+
if (mine.some((r) => r.state === "registered"))
|
|
1174
|
+
return "yes";
|
|
1175
|
+
if (mine.every((r) => r.state === "missing"))
|
|
1176
|
+
return "no";
|
|
1177
|
+
return "unknown";
|
|
1178
|
+
}
|
|
1179
|
+
/** 能力の状態の語彙。サーバ側 `CAPABILITY_STATES` と同じ(増やさない) */
|
|
1180
|
+
const CAPABILITY_NOW_STATES = ["on", "off", "empty", "broken", "unset"];
|
|
1181
|
+
/**
|
|
1182
|
+
* `capabilitiesNow`(能力が配線されているか)を、**まとめ表の `capabilities` と同じ形**
|
|
1183
|
+
* (能力名=日本語のキー)に畳む。読めた能力だけを返し、1つも無ければ null。
|
|
1184
|
+
*
|
|
1185
|
+
* ⚠ **応答は `caps` で要求した能力ぶんしか来ない**(画面が開いた能力だけを読む・
|
|
1186
|
+
* `OPEN_CAPS` の項)。だから**キー単位でまとめ表へ落ちる**——ここだけは
|
|
1187
|
+
* `rawFleetVal` の「キーごとにフォールバックしない」の例外で、そうしないと
|
|
1188
|
+
* 開いていない能力のタイルが全部「未接続」に落ちる。
|
|
1189
|
+
*/
|
|
1190
|
+
function sanitizeCapabilitiesNow(v) {
|
|
1191
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
1192
|
+
return null;
|
|
1193
|
+
const out = {};
|
|
1194
|
+
let any = false;
|
|
1195
|
+
for (const id in STATE_CAP_NAMES) {
|
|
1196
|
+
if (!Object.prototype.hasOwnProperty.call(STATE_CAP_NAMES, id))
|
|
1197
|
+
continue;
|
|
1198
|
+
const c = v[id];
|
|
1199
|
+
if (c == null || typeof c !== "object" || Array.isArray(c))
|
|
1200
|
+
continue;
|
|
1201
|
+
const state = typeof c.state === "string" ? c.state : "";
|
|
1202
|
+
if (CAPABILITY_NOW_STATES.indexOf(state) < 0)
|
|
1203
|
+
continue;
|
|
1204
|
+
const row = { state };
|
|
1205
|
+
if (typeof c.adapter === "string" && c.adapter)
|
|
1206
|
+
row.adapter = c.adapter;
|
|
1207
|
+
if (typeof c.detail === "string" && c.detail)
|
|
1208
|
+
row.detail = c.detail;
|
|
1209
|
+
out[STATE_CAP_NAMES[id]] = row;
|
|
1210
|
+
any = true;
|
|
1211
|
+
}
|
|
1212
|
+
return any ? out : null;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* `registeredNow.githubOwners`(組織ごとの App の導入状況・#233)の許可リスト。
|
|
1216
|
+
*
|
|
1217
|
+
* 🔴 **`appInstalled` は `=== true` / `=== false` / それ以外は null の厳格正規化。**
|
|
1218
|
+
* サーバ側(`withGithubInstallations`)が同じ畳み方をしているが、**画面でも畳む**
|
|
1219
|
+
* ——生の値(インストールトークンが入りうる)を素通しする経路をどちらにも作らない。
|
|
1220
|
+
* null は「その回は確かめられなかった」で、**どちらの案内も出させない値**。
|
|
1221
|
+
*/
|
|
1222
|
+
function sanitizeGithubOwnersNow(v) {
|
|
1223
|
+
if (!Array.isArray(v))
|
|
1224
|
+
return null;
|
|
1225
|
+
const out = [];
|
|
1226
|
+
for (const r of v) {
|
|
1227
|
+
if (out.length >= 50)
|
|
1228
|
+
break;
|
|
1229
|
+
if (r == null || typeof r !== "object" || Array.isArray(r))
|
|
1230
|
+
continue;
|
|
1231
|
+
const owner = typeof r.owner === "string" ? r.owner : "";
|
|
1232
|
+
if (!owner)
|
|
1233
|
+
continue;
|
|
1234
|
+
const row = { owner, appInstalled: r.appInstalled === true ? true : r.appInstalled === false ? false : null };
|
|
1235
|
+
if (typeof r.repos === "number" && Number.isFinite(r.repos) && r.repos >= 0)
|
|
1236
|
+
row.repos = r.repos;
|
|
1237
|
+
out.push(row);
|
|
1238
|
+
}
|
|
1239
|
+
return out.length ? out : null;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* サーバが返した「次の一手」(`needed`)を、素の文字列と URL に切り分ける。
|
|
1243
|
+
*
|
|
1244
|
+
* 🔴 **正規表現をクライアント側に書き写さないこと。** 埋め込みJSはテンプレート
|
|
1245
|
+
* リテラルの中なので、`\/` や `\s` がリテラルの解釈で潰れて**別物の正規表現**になる
|
|
1246
|
+
* (`backlogTargetOf` の項に、実際にクライアントJSが丸ごと構文エラーで落ちた経緯がある)。
|
|
1247
|
+
* ここで切り分けて、画面側は DOM を組み立てるだけにする。
|
|
1248
|
+
*
|
|
1249
|
+
* @returns [{ text, url }] の配列。url:true の部分だけをリンクにする
|
|
1250
|
+
*/
|
|
1251
|
+
function splitNeededParts(text) {
|
|
1252
|
+
const s = String(text == null ? "" : text);
|
|
1253
|
+
const out = [];
|
|
1254
|
+
// 日本語の文の途中に URL が挟まる(「… https://… で追加してください」)。
|
|
1255
|
+
// 空白と句読点で切る——読点・句点まで飲み込むと、開けないリンクになる
|
|
1256
|
+
const re = /https?:\/\/[^\s、。]+/g;
|
|
1257
|
+
let at = 0;
|
|
1258
|
+
let m;
|
|
1259
|
+
while ((m = re.exec(s)) !== null) {
|
|
1260
|
+
if (m.index > at)
|
|
1261
|
+
out.push({ text: s.slice(at, m.index), url: false });
|
|
1262
|
+
out.push({ text: m[0], url: true });
|
|
1263
|
+
at = m.index + m[0].length;
|
|
1264
|
+
}
|
|
1265
|
+
if (at < s.length)
|
|
1266
|
+
out.push({ text: s.slice(at), url: false });
|
|
1267
|
+
return out;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* `stateNow` から、その能力の「最後に取り込めた時刻」を引く(#227 の時刻の一貫化)。
|
|
1271
|
+
*
|
|
1272
|
+
* **「最終同期」(`internalSources.lastSync`)より優先する。** あちらは run 全体の
|
|
1273
|
+
* 最終成功時刻(空振りした run も含む run 単位のスナップショット)で、こちらは
|
|
1274
|
+
* **接続先ごとに実際に取り込めた事実**。同じ行に2つの時刻の意味を混ぜない。
|
|
1275
|
+
*
|
|
1276
|
+
* **成功の時刻だけを採る。** 失敗した行の `at` は「最後に試した時刻」で、
|
|
1277
|
+
* 「最終同期」として出すと**取り込めていないのに同期できたように読める**。
|
|
1278
|
+
*
|
|
1279
|
+
* @param capId 応答側の能力名(issues / design / materials / meetings)
|
|
1280
|
+
* @returns ISO文字列。1件も無ければ ""
|
|
1281
|
+
*/
|
|
1282
|
+
function ingestedAtOf(rows, capId) {
|
|
1283
|
+
if (!Array.isArray(rows) || !capId)
|
|
1284
|
+
return "";
|
|
1285
|
+
let best = "";
|
|
1286
|
+
for (const r of rows) {
|
|
1287
|
+
if (!r || r.cap !== capId || r.question !== "ingested")
|
|
1288
|
+
continue;
|
|
1289
|
+
const at = r.lastOkAt || (r.status === "ok" ? r.at : "");
|
|
1290
|
+
if (typeof at === "string" && at > best)
|
|
1291
|
+
best = at;
|
|
1292
|
+
}
|
|
1293
|
+
return best;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* 「取り込みの事実」でタイルと線を決める能力(画面の能力名 → ストアの能力名)。
|
|
1297
|
+
*
|
|
1298
|
+
* **この2つだけ。** 会議と共有資料は中央 Apps Script が取り込むので、設定が済んでいても
|
|
1299
|
+
* (capabilities が `on` でも)**一度も取り込まれていない**ことが普通に起きる。他の4能力は
|
|
1300
|
+
* 判定の材料が違う(夜間の reachable・パイプラインの成否)ので、ここに足さないこと——
|
|
1301
|
+
* 足した瞬間、実績の無い案件が一斉に「未使用」になる。
|
|
1302
|
+
*/
|
|
1303
|
+
const FACT_CAPS = { "会議": "meetings", "共有資料": "materials" };
|
|
1304
|
+
/**
|
|
1305
|
+
* その能力が「設定は済んでいるのに、まだ一度も取り込めていない」か(#235 の表示層)。
|
|
1306
|
+
*
|
|
1307
|
+
* 🔴 **`seen`(応答に `stateNow` の欄があったか)と `rows`(その中身)は別物。**
|
|
1308
|
+
* サーバは**読めたなら0件でも欄を付ける**ので、この2つで3つの状態が区別できる:
|
|
1309
|
+
*
|
|
1310
|
+
* - `seen === false` … 分からない(古い Lambda・ストアの読み取りに失敗)→ **偽**。
|
|
1311
|
+
* 従来表示のままにする。分からないことを「未使用」と言い切らない
|
|
1312
|
+
* - `seen === true` かつ行が0件 … 読めて空=**まだ一度も取り込めていない**→ 真。
|
|
1313
|
+
* 初期化直後の案件がここに来る(この機能が一番効いてほしい案件)
|
|
1314
|
+
* - `seen === true` かつ行がある … その能力の取り込み実績で決める
|
|
1315
|
+
*
|
|
1316
|
+
* **`rows` の空と `seen` を混ぜないこと。** `sanitizeStateNow` は0件を `null` に畳むので、
|
|
1317
|
+
* `rows` だけを見ると「読めて空」が「分からない」に化け、**まさに未使用の案件でだけ
|
|
1318
|
+
* 機能しない**(サーバ側を直した意味が消える)。
|
|
1319
|
+
*
|
|
1320
|
+
* **成功の事実は `ingestedAtOf` に一本化する。** あちらは `lastOkAt` も見るので、
|
|
1321
|
+
* 「一度取り込めた後に失敗した」能力を未使用に落とさない。判定を写経すると、そこがずれる。
|
|
1322
|
+
*
|
|
1323
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
|
|
1324
|
+
* `FACT_CAPS` は CLIENT_JS 側にも `var` で流し込んである)。
|
|
1325
|
+
*/
|
|
1326
|
+
function usageFactMissing(cap, rows, seen) {
|
|
1327
|
+
var capId = FACT_CAPS[cap];
|
|
1328
|
+
if (!capId)
|
|
1329
|
+
return false;
|
|
1330
|
+
if (!seen)
|
|
1331
|
+
return false;
|
|
1332
|
+
if (!Array.isArray(rows) || !rows.length)
|
|
1333
|
+
return true;
|
|
1334
|
+
return ingestedAtOf(rows, capId) === "";
|
|
1335
|
+
}
|
|
1061
1336
|
function capabilityState(cap, ctx) {
|
|
1062
1337
|
// **能力→パイプラインの対応はここに持つ。** 呼び出し側に渡させると、
|
|
1063
1338
|
// クライアント側(この関数を .toString() で受け取る側)が同じ表を書き写すことになり、
|
|
@@ -1303,8 +1578,19 @@ function renderConnectionMap(site) {
|
|
|
1303
1578
|
//
|
|
1304
1579
|
// cm-warn は引く。**壊れているのは「繋がったうえで失敗している」状態**で、
|
|
1305
1580
|
// 経路自体は存在する(線を消すと、設定が無いのと区別が付かなくなる)。
|
|
1581
|
+
//
|
|
1582
|
+
// **`data-cm-cap` は線を後から取り消すための取っ手(#235 の表示層)。** この図は
|
|
1583
|
+
// ビルド時に焼くので、**取り込みの事実(stateNow)はここでは分からない**——分かるのは
|
|
1584
|
+
// ライブ応答が届いた後だけ。届いてから会議・共有資料の線を外せるように、線に
|
|
1585
|
+
// 能力名を持たせておく(外す側は applyUsageFactToConnMap)。
|
|
1586
|
+
// 位置で数えさせないこと——線は ok/warn の能力ぶんしか積まれないので、
|
|
1587
|
+
// 並び順は案件ごとに変わる。
|
|
1588
|
+
//
|
|
1589
|
+
// 🔴 **`data-cap` は使わない。** あちらは能力ブロック(`.datasources` の中)が
|
|
1590
|
+
// 既に名乗っていて、図はページの**上**にあるので `[data-cap="会議"]` で引くと
|
|
1591
|
+
// 図の線のほうが先に当たる。実際、最初にそう書いてテストが12件落ちた。
|
|
1306
1592
|
if (state === "cm-ok" || state === "cm-warn") {
|
|
1307
|
-
links.push(`<path class="cm-link ${state}" d="M ${cx} 78 C ${cx} 152, ${REPO_X} 162, ${REPO_X} 238"></path>`);
|
|
1593
|
+
links.push(`<path class="cm-link ${state}" data-cm-cap="${escapeHtml(c.cap)}" d="M ${cx} 78 C ${cx} 152, ${REPO_X} 162, ${REPO_X} 238"></path>`);
|
|
1308
1594
|
}
|
|
1309
1595
|
});
|
|
1310
1596
|
// **中央ノードは GitHub のリポジトリ名そのもの(オーナー指示・2026-08-12)。**
|
|
@@ -2386,6 +2672,20 @@ var STATE_ROW_FIELDS = ${JSON.stringify(STATE_ROW_FIELDS)};
|
|
|
2386
2672
|
${sanitizeStateNow.toString()}
|
|
2387
2673
|
${overlayStateOnFleet.toString()}
|
|
2388
2674
|
${nextStateDelay.toString()}
|
|
2675
|
+
// 工程1.5: 正本を直接読む(secretsNow / capabilitiesNow / githubOwners)。
|
|
2676
|
+
// **判定の実体はビルド時の定義**——画面側に書き写すと許可リストが2枚になる
|
|
2677
|
+
var STATE_CAP_NAMES = ${JSON.stringify(STATE_CAP_NAMES)};
|
|
2678
|
+
var SECRET_NOW_STATES = ${JSON.stringify(SECRET_NOW_STATES)};
|
|
2679
|
+
var CAPABILITY_NOW_STATES = ${JSON.stringify(CAPABILITY_NOW_STATES)};
|
|
2680
|
+
${sanitizeSecretsNow.toString()}
|
|
2681
|
+
${secretStateFromNow.toString()}
|
|
2682
|
+
${secretRegisteredAnywhere.toString()}
|
|
2683
|
+
${sanitizeCapabilitiesNow.toString()}
|
|
2684
|
+
${sanitizeGithubOwnersNow.toString()}
|
|
2685
|
+
${splitNeededParts.toString()}
|
|
2686
|
+
${ingestedAtOf.toString()}
|
|
2687
|
+
var FACT_CAPS = ${JSON.stringify(FACT_CAPS)};
|
|
2688
|
+
${usageFactMissing.toString()}
|
|
2389
2689
|
// 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
2390
2690
|
var TUTORIAL = (function () {
|
|
2391
2691
|
var elx = document.getElementById("tutorial-data");
|
|
@@ -3327,7 +3627,7 @@ ${nextStateDelay.toString()}
|
|
|
3327
3627
|
*/
|
|
3328
3628
|
/** 台帳から、その組織の情報を引く(無ければ null=断定しない) */
|
|
3329
3629
|
function githubOwnerMeta(owner) {
|
|
3330
|
-
var list =
|
|
3630
|
+
var list = githubOwnersView() || [];
|
|
3331
3631
|
for (var i = 0; i < list.length; i++) {
|
|
3332
3632
|
if (list[i] && list[i].owner === owner) return list[i];
|
|
3333
3633
|
}
|
|
@@ -3358,7 +3658,7 @@ ${nextStateDelay.toString()}
|
|
|
3358
3658
|
// どちらを先に出しても片側だけ新しい期間ができる。その間に 「token」 を落とすと、
|
|
3359
3659
|
// コミット済みの値を読んだ画面が偽の警告を出す。**外すのはフェーズ2**(engine の
|
|
3360
3660
|
// v1 が全案件へ行き渡ったことを確かめてから)。
|
|
3361
|
-
if (o) {
|
|
3661
|
+
if (o && o.source) {
|
|
3362
3662
|
state = (o.source === "app" || o.source === "workflow" || o.source === "token") ? "ok"
|
|
3363
3663
|
: o.source === "unknown" ? "unsure"
|
|
3364
3664
|
: "warn";
|
|
@@ -3369,6 +3669,50 @@ ${nextStateDelay.toString()}
|
|
|
3369
3669
|
}
|
|
3370
3670
|
g.items.push(s);
|
|
3371
3671
|
});
|
|
3672
|
+
// 🔴 **「source」 を持たない行(op=state の registeredNow.githubOwners・工程1.5)は
|
|
3673
|
+
// 「appInstalled」 と行の実測から決める。** PR-B でまとめ表から 「githubOwners」 が
|
|
3674
|
+
// 消えたため、ここが無いと #233 の出し分け(App 未導入 / リポジトリ未選択)が
|
|
3675
|
+
// 画面から消える。**行の判定を数え直すのは、この関数がそれを持っているから**
|
|
3676
|
+
// ——「items」 は既に stateNow を重ねた後の行(overlayStateOnFleet)なので、
|
|
3677
|
+
// 「押して確かめた結果」もそのまま効く。
|
|
3678
|
+
//
|
|
3679
|
+
// ⚠ **まとめ表の行(「source」 を持つ)には触らない。** あちらは夜間が数えた
|
|
3680
|
+
// 「repos」 / 「unreadable」 を持っており、そちらが件数の正本
|
|
3681
|
+
order.forEach(function (g) {
|
|
3682
|
+
var o = g.meta;
|
|
3683
|
+
if (!o || o.source) return;
|
|
3684
|
+
// 読めていないリポジトリの数。**測った行だけを数える**——一度も測っていない行を
|
|
3685
|
+
// 「読めていない」に数えると、立ち上げ直後の組織に偽の警告を出す
|
|
3686
|
+
var seen = {};
|
|
3687
|
+
var shown = 0;
|
|
3688
|
+
var unread = 0;
|
|
3689
|
+
g.items.forEach(function (s) {
|
|
3690
|
+
var ref = String(s.ref || "");
|
|
3691
|
+
// 同じリポジトリの issues と discussions は1つと数える(行は2本でも接続は1つ)
|
|
3692
|
+
if (!ref || seen[ref]) return;
|
|
3693
|
+
seen[ref] = 1;
|
|
3694
|
+
shown++;
|
|
3695
|
+
if (s.gate && s.gate !== "ok") unread++;
|
|
3696
|
+
});
|
|
3697
|
+
// 総数は登録の件数(正本が数えたもの)を優先する。画面に出ている行は
|
|
3698
|
+
// フィルタや読み込み途中でこれより少ないことがある
|
|
3699
|
+
g.repos = o.repos != null ? o.repos : shown;
|
|
3700
|
+
if (o.appInstalled === false) {
|
|
3701
|
+
// App が入っていない=この組織のリポジトリは1つも読めない
|
|
3702
|
+
g.state = "warn";
|
|
3703
|
+
g.unread = g.repos;
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
if (o.appInstalled === true) {
|
|
3707
|
+
// 入っているのに読めない行がある=そのリポジトリが読み取り対象に選ばれていない
|
|
3708
|
+
g.state = unread > 0 ? "warn" : "ok";
|
|
3709
|
+
g.unread = unread;
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
// 「null」(確かめられなかった)。**どちらの案内も出さない**——押しても直るとは
|
|
3713
|
+
// 限らないし、「未接続」と言えば入っている組織に偽の警告を出す
|
|
3714
|
+
g.state = null;
|
|
3715
|
+
});
|
|
3372
3716
|
// **繋がっている組織を先に、繋がっていない組織を後に。**
|
|
3373
3717
|
// 直すものが無い人にも「未接続」が最初に見えるのは、優先順位が逆
|
|
3374
3718
|
order.sort(function (a, b) {
|
|
@@ -3385,7 +3729,7 @@ ${nextStateDelay.toString()}
|
|
|
3385
3729
|
* どの組織かは**行のラベル**が示し、**接続が要る組だけ**案内が行を抱える。
|
|
3386
3730
|
*/
|
|
3387
3731
|
function githubRows(items) {
|
|
3388
|
-
var owners =
|
|
3732
|
+
var owners = githubOwnersView();
|
|
3389
3733
|
var out = [];
|
|
3390
3734
|
groupGithubByOwner(items, owners).forEach(function (g) {
|
|
3391
3735
|
// **行ごとに meta を引く。** issues と discussions でアイコンとラベルが違う
|
|
@@ -3447,8 +3791,11 @@ ${nextStateDelay.toString()}
|
|
|
3447
3791
|
function githubNotConnectedNodes(g) {
|
|
3448
3792
|
var out = [];
|
|
3449
3793
|
var o = g.meta || {};
|
|
3450
|
-
|
|
3451
|
-
|
|
3794
|
+
// **件数はグループが数えたものを優先する(工程1.5)。** まとめ表の 「repos」 /
|
|
3795
|
+
// 「unreadable」 は夜間の値で、いま画面に出ている行の数と食い違うことがある
|
|
3796
|
+
//(#101 で3度直した形)。「groupGithubByOwner」 は**いま並んでいる行**を数えている
|
|
3797
|
+
var repos = o.repos != null ? o.repos : g.repos != null ? g.repos : g.items.length;
|
|
3798
|
+
var unread = o.unreadable != null ? o.unreadable : g.unread != null ? g.unread : g.items.length;
|
|
3452
3799
|
var installed = o.appInstalled;
|
|
3453
3800
|
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
3454
3801
|
// **読める件数を出す。** 総数だけ出すと、同じ画面の集計と食い違う(#101 で3度直した形)
|
|
@@ -3783,6 +4130,39 @@ ${nextStateDelay.toString()}
|
|
|
3783
4130
|
var LIVE_REGISTERED = null;
|
|
3784
4131
|
// 接続状態ストアの行(届くか・取り込めたか)。op=state の stateNow から
|
|
3785
4132
|
var LIVE_STATE = null;
|
|
4133
|
+
/**
|
|
4134
|
+
* 応答に 「stateNow」 の欄があったか(=サーバがストアを読めたか)。
|
|
4135
|
+
*
|
|
4136
|
+
* 🔴 **LIVE_STATE が null かどうかでは代用できない。** sanitize は0件を null に畳むので、
|
|
4137
|
+
* 「読めて0件」と「そもそも読めていない」が同じ値になる。サーバは**読めたなら0件でも
|
|
4138
|
+
* 欄を付ける**ので、区別できるのはこの旗だけ——潰すと、初期化直後の案件
|
|
4139
|
+
* (=会議・共有資料の「未使用」表示が一番効いてほしい案件)でだけ機能が死ぬ。
|
|
4140
|
+
*/
|
|
4141
|
+
var LIVE_STATE_SEEN = false;
|
|
4142
|
+
/**
|
|
4143
|
+
* 鍵がいま登録されているか(op=state の secretsNow・列2)。取得前・取得失敗時は null。
|
|
4144
|
+
*
|
|
4145
|
+
* 🔴 **まとめ表の secretSources / secretSpaces より必ず優先する。** あちらは夜間の
|
|
4146
|
+
* スナップショットなので、**鍵を保存した直後は前日の「未登録」を出し続ける**
|
|
4147
|
+
* ——「保存したのに未登録」(#227)の正体。こちらは Secrets Manager を都度見た答え。
|
|
4148
|
+
*/
|
|
4149
|
+
var LIVE_SECRETS = null;
|
|
4150
|
+
/**
|
|
4151
|
+
* 能力が配線されているか(op=state の capabilitiesNow)。取得前・取得失敗時は null。
|
|
4152
|
+
*
|
|
4153
|
+
* **形はまとめ表の capabilities と同じ**(能力名のキー・{adapter, state, detail})なので、
|
|
4154
|
+
* 読む側は capsView() で重ねるだけでよい。**要求した能力ぶんしか来ない**ので、
|
|
4155
|
+
* 欠けている能力はまとめ表の値で描く(sanitizeCapabilitiesNow の項)。
|
|
4156
|
+
*/
|
|
4157
|
+
var LIVE_CAPS = null;
|
|
4158
|
+
/**
|
|
4159
|
+
* 組織ごとの GitHub App の導入状況(op=state の registeredNow.githubOwners・#233)。
|
|
4160
|
+
*
|
|
4161
|
+
* **registeredNow の他のキーとは別に持つ。** あちらは「まとめ表がまだ知らない行を足す」
|
|
4162
|
+
* ための材料で、こちらは組織の状態そのもの。同じ入れ物に混ぜると、行の追加の判定
|
|
4163
|
+
* (unsettled/pending)に組織の行が紛れ込む。
|
|
4164
|
+
*/
|
|
4165
|
+
var LIVE_GH_OWNERS = null;
|
|
3786
4166
|
/** マージ済みの3キー(作り直しは setLive* だけが行う) */
|
|
3787
4167
|
var MERGED = null;
|
|
3788
4168
|
/** マージの対象になるキー。これ以外は素の値をそのまま返す */
|
|
@@ -3825,6 +4205,55 @@ ${nextStateDelay.toString()}
|
|
|
3825
4205
|
return rawFleetVal(key);
|
|
3826
4206
|
}
|
|
3827
4207
|
|
|
4208
|
+
/**
|
|
4209
|
+
* 能力の状態(capabilities)。**能力ごとに、正本(capabilitiesNow)があればそちら。**
|
|
4210
|
+
*
|
|
4211
|
+
* ⚠ **ここは rawFleetVal の「キー単位でフォールバックしない」の意図的な例外**(工程1.5)。
|
|
4212
|
+
* あちらの禁止は「同じ画面に2つの時点が混ざると、上に出す『◯◯時点』が嘘になる」ため
|
|
4213
|
+
* だが、capabilitiesNow は**画面が開いた能力ぶんしか来ない**(OPEN_CAPS の項)ので、
|
|
4214
|
+
* 丸ごと採るか丸ごと捨てるかしか無いと、**開いていない能力のタイルが全部「未接続」に
|
|
4215
|
+
* 落ちる**。能力ごとに正本を優先し、来ていない能力はまとめ表のまま出す。
|
|
4216
|
+
*/
|
|
4217
|
+
function capsView() {
|
|
4218
|
+
var base = fleetVal("capabilities") || {};
|
|
4219
|
+
if (!LIVE_CAPS) return base;
|
|
4220
|
+
var out = {};
|
|
4221
|
+
for (var k in base) if (Object.prototype.hasOwnProperty.call(base, k)) out[k] = base[k];
|
|
4222
|
+
for (var n in LIVE_CAPS) if (Object.prototype.hasOwnProperty.call(LIVE_CAPS, n)) out[n] = LIVE_CAPS[n];
|
|
4223
|
+
return out;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
/**
|
|
4227
|
+
* 組織ごとの接続状況(#233 の出し分けの材料)。
|
|
4228
|
+
*
|
|
4229
|
+
* **まとめ表からは消えた(PR-B)。** fleet-status.json の専用プローブを廃止したので、
|
|
4230
|
+
* いまの供給元は op=state の 「registeredNow.githubOwners」(App の導入状況の実測)。
|
|
4231
|
+
* 古い生成物を読んでいる案件のために、まとめ表の行も残す(組織ごとに正本が勝つ)。
|
|
4232
|
+
*/
|
|
4233
|
+
function githubOwnersView() {
|
|
4234
|
+
var fleet = fleetVal("githubOwners");
|
|
4235
|
+
if (!LIVE_GH_OWNERS) {
|
|
4236
|
+
// **「対象0件と確かめた」だけは正本から言える。** 開発の登録一覧が読めていて0件なら、
|
|
4237
|
+
// 組織の行が1つも無いのは当たり前(下の「まだありません」の1行はこれで出る)。
|
|
4238
|
+
// まとめ表を持たない案件で 「undefined」(まだ走っていない)と混ぜない
|
|
4239
|
+
if (!fleet && LIVE_REGISTERED && Array.isArray(LIVE_REGISTERED.dev) && LIVE_REGISTERED.dev.length === 0) return [];
|
|
4240
|
+
return fleet;
|
|
4241
|
+
}
|
|
4242
|
+
var out = [];
|
|
4243
|
+
var byOwner = {};
|
|
4244
|
+
for (var i = 0; i < LIVE_GH_OWNERS.length; i++) {
|
|
4245
|
+
byOwner[LIVE_GH_OWNERS[i].owner] = 1;
|
|
4246
|
+
out.push(LIVE_GH_OWNERS[i]);
|
|
4247
|
+
}
|
|
4248
|
+
// 正本が知らない組織(まとめ表にしか無い行)は落とさない——消すと、その組織の
|
|
4249
|
+
// 案内だけが画面から黙って消える
|
|
4250
|
+
var old = Array.isArray(fleet) ? fleet : [];
|
|
4251
|
+
for (var j = 0; j < old.length; j++) {
|
|
4252
|
+
if (old[j] && old[j].owner && !byOwner[old[j].owner]) out.push(old[j]);
|
|
4253
|
+
}
|
|
4254
|
+
return out;
|
|
4255
|
+
}
|
|
4256
|
+
|
|
3828
4257
|
/** まとめ表がまだ答えていない行の数(取り直しを続けるかの判断) */
|
|
3829
4258
|
function unsettledNow() {
|
|
3830
4259
|
return unsettledRegisteredCount(rawView(), LIVE_REGISTERED);
|
|
@@ -3903,7 +4332,10 @@ ${nextStateDelay.toString()}
|
|
|
3903
4332
|
String(rawFleetVal("generatedAt") || ""),
|
|
3904
4333
|
);
|
|
3905
4334
|
return {
|
|
3906
|
-
|
|
4335
|
+
// 🔴 **能力の状態は正本(capabilitiesNow)を通す(工程1.5)。** タイルの語は
|
|
4336
|
+
// capsView から引いているので、ここを素のまとめ表のままにすると
|
|
4337
|
+
// 「タイルの文字は未接続・状態の色は接続OK」が同じタイルの中で並ぶ
|
|
4338
|
+
capabilities: capsView(),
|
|
3907
4339
|
internals: rawFleetVal("internalSources") || [],
|
|
3908
4340
|
externals: ov.externalSources || [],
|
|
3909
4341
|
pipes: rawFleetVal("pipelines") || [],
|
|
@@ -4398,7 +4830,7 @@ ${nextStateDelay.toString()}
|
|
|
4398
4830
|
if (noteIsUnset) reflectMark(noteEl, cap, "desc", function () {
|
|
4399
4831
|
// 足した行があるなら、この一文(接続先が設定されていません)はもう事実ではない
|
|
4400
4832
|
if (capPending(cap)) return { text: "", hidden: true };
|
|
4401
|
-
var capsNow =
|
|
4833
|
+
var capsNow = capsView();
|
|
4402
4834
|
var dNow = capabilityDisplayState(cap, capsNow);
|
|
4403
4835
|
var infoNow = capsNow[cap];
|
|
4404
4836
|
var t = dNow && dNow !== "on" && infoNow && infoNow.detail
|
|
@@ -4457,7 +4889,9 @@ ${nextStateDelay.toString()}
|
|
|
4457
4889
|
var sources = fleetVal("externalSources") || [];
|
|
4458
4890
|
var pipes = fleetVal("pipelines") || [];
|
|
4459
4891
|
// 能力ごとの導出済みの状態(#125② の正本)。持たない古いデータでは {}
|
|
4460
|
-
|
|
4892
|
+
// **読む先は capsView(工程1.5)。** 能力ごとに、正本(capabilitiesNow)があれば
|
|
4893
|
+
// そちら・来ていない能力はまとめ表のまま
|
|
4894
|
+
var caps = capsView();
|
|
4461
4895
|
var hasCaps = Object.keys(caps).length > 0;
|
|
4462
4896
|
// **この節に入るものが1つでもあるなら出す。**
|
|
4463
4897
|
//
|
|
@@ -4784,7 +5218,7 @@ ${nextStateDelay.toString()}
|
|
|
4784
5218
|
));
|
|
4785
5219
|
if (liveListWanted("github")) pushCap("開発", liveList(
|
|
4786
5220
|
"開発",
|
|
4787
|
-
function () { return externalsSig("github",
|
|
5221
|
+
function () { return externalsSig("github", githubOwnersView() || null); },
|
|
4788
5222
|
function (box) {
|
|
4789
5223
|
var items = liveExternals("github");
|
|
4790
5224
|
if (!items.length) return;
|
|
@@ -4837,7 +5271,7 @@ ${nextStateDelay.toString()}
|
|
|
4837
5271
|
//
|
|
4838
5272
|
// **「[]」(夜間バッチは走ったが対象0件)と 「undefined」(まだ走っていない)は別物。**
|
|
4839
5273
|
// 後者に「まだありません」と書くと、実際には登録済みかもしれない案件に嘘を言う
|
|
4840
|
-
var ownersNow =
|
|
5274
|
+
var ownersNow = githubOwnersView();
|
|
4841
5275
|
if (Array.isArray(ownersNow) && ownersNow.length === 0) {
|
|
4842
5276
|
pushCap("開発", el("p", {
|
|
4843
5277
|
class: "sec-how ds-all-connected",
|
|
@@ -4948,14 +5382,9 @@ ${nextStateDelay.toString()}
|
|
|
4948
5382
|
// 両方に出すと、同じ失敗が1画面に2回並ぶ。実行状況はパイプライン節に任せ、
|
|
4949
5383
|
// ここでは直し方だけをその行に添える(下の renderPipelines で使う)
|
|
4950
5384
|
var PIPE_CHECK = { "update-gold": "nightly_decisionlog", "sync-backlog": "nightly_backlog", "sync-designs": "figma_sync" };
|
|
4951
|
-
//
|
|
4952
|
-
//
|
|
4953
|
-
//
|
|
4954
|
-
var CHECK_CAP = {
|
|
4955
|
-
backlog_secrets: "課題管理", backlog_synced: "課題管理",
|
|
4956
|
-
figma_token: "デザイン", figma_inventory: "デザイン",
|
|
4957
|
-
dev_sources_registered: "開発", dev_issues_derived: "開発",
|
|
4958
|
-
};
|
|
5385
|
+
// ⚠ **能力タイルへの振り分け表(CHECK_CAP)は撤去した(#230)。** タイルに長文の
|
|
5386
|
+
// 対処手順を出すのをやめたので、振り分け先が無い。チェックは全部「セットアップの残り」に
|
|
5387
|
+
// 並ぶ(実行状況の行に添える PIPE_CHECK だけが例外)
|
|
4959
5388
|
// 🔴 **鍵の欄を指すチェックは、鍵の欄が出ているときだけ出す(オーナー決定 2026-08-22)。**
|
|
4960
5389
|
// 「この画面の『Backlog のAPIキー』に貼ってください」は、接続先が0件で欄を畳んでいる
|
|
4961
5390
|
// あいだは**存在しない欄を指す案内**になる(最後の接続先を外した直後に実際に出た。
|
|
@@ -4967,13 +5396,12 @@ ${nextStateDelay.toString()}
|
|
|
4967
5396
|
var kc = KEY_POINTING_CHECKS[c.id];
|
|
4968
5397
|
return !(kc && keyTargetCount(kc) === 0);
|
|
4969
5398
|
});
|
|
4970
|
-
//
|
|
4971
|
-
|
|
4972
|
-
//
|
|
5399
|
+
// **どこかに出せたものを覚えておく。** 出せなかった分は下の「セットアップの残り」へ
|
|
5400
|
+
// 落とす——振り分けを間違えても、消えるよりは出る方がよい。
|
|
5401
|
+
//
|
|
5402
|
+
// ⚠ **能力タイルはもう立てない(#230)。** 立てていた頃の名残で名前が残っているが、
|
|
5403
|
+
// いま立てるのは実行状況の行(同じ run の未達を2箇所に出さないため)だけ
|
|
4973
5404
|
var shownOnTile = {};
|
|
4974
|
-
function checksFor(cap) {
|
|
4975
|
-
return allChecks.filter(function (c) { return CHECK_CAP[c.id] === cap; });
|
|
4976
|
-
}
|
|
4977
5405
|
/** 残りの1件を、直し方まで含めて1行にする */
|
|
4978
5406
|
function checkLine(c) {
|
|
4979
5407
|
var row = el("div", { class: "ds-check" });
|
|
@@ -5000,15 +5428,30 @@ ${nextStateDelay.toString()}
|
|
|
5000
5428
|
* 節を作り直せない場面(モーダルを開いたまま)でも、**届いた一覧で
|
|
5001
5429
|
* 引き直す**ために同じ関数を使う——写経すると片方だけ古くなる
|
|
5002
5430
|
*/
|
|
5003
|
-
|
|
5431
|
+
/**
|
|
5432
|
+
* @param noFact 「設定は済んでいるが、まだ一度も取り込めていない」と言い切れるか
|
|
5433
|
+
* (#235 の表示層)。**呼び出し側が渡す**——ここで LIVE_STATE から引き直すと、
|
|
5434
|
+
* タイルの色(st)を決めた材料と 2 回引くことになり、片方だけずれる余地ができる
|
|
5435
|
+
*/
|
|
5436
|
+
function tileSub(cap, st, derived, adapter, ins, srcs, noFact) {
|
|
5004
5437
|
var internal = ins.filter(function (s) { return s.kind === cap; })[0];
|
|
5005
5438
|
var live = srcs.filter(function (s) { return externalCapability(s.type) === cap; });
|
|
5006
5439
|
var parts = [];
|
|
5440
|
+
// 🔴 **取り込みの実績が無いなら、まずそれを言う(#235 の表示層)。**
|
|
5441
|
+
// 設定は済んでいる(capabilities は on)ので「未接続」ではないし、設定ミスでもないので
|
|
5442
|
+
// 警告色にもしない(#230 の決定どおり中立表示)。ここを通るのは会議・共有資料だけで、
|
|
5443
|
+
// しかも呼び出し側が st === "ok" のときにしか立てない——OFF・設定異常・同期失敗の
|
|
5444
|
+
// 語を、この一文で上書きしないため。
|
|
5445
|
+
//
|
|
5446
|
+
// 実績が無い= st は "idle" に降りているので、下の「件数」「最終同期」は自動で消える
|
|
5447
|
+
// (繋がっていないものに読み取り範囲と同期時刻を並べない、という既存の規律に乗る)
|
|
5448
|
+
if (noFact) {
|
|
5449
|
+
parts.push((CAP_STATE_MARK[st] || "") + "未使用(まだ取り込みがありません)");
|
|
5007
5450
|
// **導出が on 以外なら、導出の語を優先する(#125②)。** broken は状態としては
|
|
5008
5451
|
// 警告(⚠️)だが、原因は「設定そのものが壊れている」であって同期の失敗ではない。
|
|
5009
5452
|
// 下の warn の文(同期が失敗しています/N件が読めていません)を出すと、
|
|
5010
5453
|
// 直す場所が違うところを指す
|
|
5011
|
-
if (derived && derived !== "on") {
|
|
5454
|
+
} else if (derived && derived !== "on") {
|
|
5012
5455
|
parts.push((CAP_STATE_MARK[st] || "") + (CAP_STATE_TEXT[derived] || "状態が判定できません"));
|
|
5013
5456
|
} else if (st === "warn") {
|
|
5014
5457
|
if (live.length) {
|
|
@@ -5063,8 +5506,15 @@ ${nextStateDelay.toString()}
|
|
|
5063
5506
|
}
|
|
5064
5507
|
// **未接続・未使用のときは最終同期を出さない。** 空振りしたrunの時刻なので、
|
|
5065
5508
|
// 「未接続」と並べると「一度は繋がった」と読めてしまう
|
|
5066
|
-
|
|
5067
|
-
|
|
5509
|
+
//
|
|
5510
|
+
// 🔴 **接続状態ストアに取り込みの行があるなら、その時刻を優先する(工程1.5・#227)。**
|
|
5511
|
+
// 「internalSources.lastSync」 は run 全体の最終成功時刻で、**どの接続先のことか
|
|
5512
|
+
// 分からない**(鍵が無くて1件も取り込めていない run でも「成功」で終わる)。
|
|
5513
|
+
// ストアの行は「その能力の接続先が実際に取り込めた事実」なので、同じ「最終同期」の
|
|
5514
|
+
// 位置に出すならこちらが正しい。行が無ければ従来どおり run の時刻
|
|
5515
|
+
if (st !== "off" && st !== "idle") {
|
|
5516
|
+
var ingestedAt = ingestedAtOf(LIVE_STATE, capIdOf(cap));
|
|
5517
|
+
var t = dsFmtTime(ingestedAt || (internal && internal.lastSync) || "");
|
|
5068
5518
|
if (t) parts.push("最終同期 " + t);
|
|
5069
5519
|
}
|
|
5070
5520
|
return parts.join(" ");
|
|
@@ -5118,6 +5568,15 @@ ${nextStateDelay.toString()}
|
|
|
5118
5568
|
// カタログにあるが導出が答えていないアダプタ(今日は発生しない。#107 の前方互換)
|
|
5119
5569
|
? "idle"
|
|
5120
5570
|
: capabilityState(cap, stateCtx());
|
|
5571
|
+
// 🔴 **「接続OK」と言うのは、取り込めた事実があるときだけ(#235 の表示層)。**
|
|
5572
|
+
// 会議・共有資料は中央 Apps Script が取り込むので、設定が済んでいるのに一度も
|
|
5573
|
+
// 取り込まれていない案件が普通にある。そこへ「● 接続OK」と出すと、**画面が
|
|
5574
|
+
// 動いていないものを動いていると言う**——顧客がそれを信じて資料を置かなくなる。
|
|
5575
|
+
//
|
|
5576
|
+
// **降ろすのは ok からだけ。** warn(繋がったうえで失敗している)は隠さないし、
|
|
5577
|
+
// idle / off はもともと繋がっていると言っていない。
|
|
5578
|
+
var noFact = st === "ok" && usageFactMissing(cap, LIVE_STATE, LIVE_STATE_SEEN);
|
|
5579
|
+
if (noFact) st = "idle";
|
|
5121
5580
|
// **ロゴはタイルの左に独立して置く。** 名前の行に入れると名前の高さに
|
|
5122
5581
|
// 縛られて小さくなる。ロゴで能力を見分けられるのが一覧の主な用途
|
|
5123
5582
|
var logo = ADAPTER_ICONS[adapter];
|
|
@@ -5135,23 +5594,27 @@ ${nextStateDelay.toString()}
|
|
|
5135
5594
|
var pend = capPending(cap);
|
|
5136
5595
|
var subEl = el("div", {
|
|
5137
5596
|
class: "ds-tile-sub " + (pend ? DS_GATE_PENDING.cls : "ds-gate-" + st),
|
|
5138
|
-
text: pend ? DS_GATE_PENDING.text : tileSub(cap, st, derived, adapter, internals, sources),
|
|
5597
|
+
text: pend ? DS_GATE_PENDING.text : tileSub(cap, st, derived, adapter, internals, sources, noFact),
|
|
5139
5598
|
});
|
|
5140
5599
|
// **反映が届いたら、届いたスナップショットから引き直す。** 下地へ戻すと
|
|
5141
5600
|
// 「⏳ 反映中」→「⦿ 未接続」になる——データが正しくなったあとに嘘をつく。
|
|
5142
5601
|
// 状態の語も色(ds-gate-*)も、判定はタイルを作るときと同じ式で引く
|
|
5143
5602
|
if (derived === "unset") reflectMark(subEl, cap, "sub", function (n) {
|
|
5144
|
-
var capsNow =
|
|
5603
|
+
var capsNow = capsView();
|
|
5145
5604
|
var insNow = fleetVal("internalSources") || [];
|
|
5146
5605
|
var srcsNow = fleetVal("externalSources") || [];
|
|
5147
5606
|
var dNow = adapter === capabilityAdapter(cap, capsNow) ? capabilityDisplayState(cap, capsNow) : "unset";
|
|
5148
5607
|
var stNow = dNow === "unset" && capabilityDisplayState(cap, capsNow) !== "unset"
|
|
5149
5608
|
? "idle"
|
|
5150
5609
|
: capabilityState(cap, stateCtx());
|
|
5610
|
+
// 実績の判定も、タイルを作るときと同じ式で引き直す(#235 の表示層)。
|
|
5611
|
+
// ここを省くと、反映が届いた瞬間だけ「● 接続OK」へ戻る
|
|
5612
|
+
var noFactNow = stNow === "ok" && usageFactMissing(cap, LIVE_STATE, LIVE_STATE_SEEN);
|
|
5613
|
+
if (noFactNow) stNow = "idle";
|
|
5151
5614
|
var pendNow = capPending(cap);
|
|
5152
5615
|
n.className = "ds-tile-sub " + (pendNow ? DS_GATE_PENDING.cls : "ds-gate-" + stNow);
|
|
5153
5616
|
return {
|
|
5154
|
-
text: pendNow ? DS_GATE_PENDING.text : tileSub(cap, stNow, dNow, adapter, insNow, srcsNow),
|
|
5617
|
+
text: pendNow ? DS_GATE_PENDING.text : tileSub(cap, stNow, dNow, adapter, insNow, srcsNow, noFactNow),
|
|
5155
5618
|
hidden: false,
|
|
5156
5619
|
};
|
|
5157
5620
|
});
|
|
@@ -5191,21 +5654,21 @@ ${nextStateDelay.toString()}
|
|
|
5191
5654
|
// 導出が無いデータでは判断材料が無いので、注記を出す側(=入力欄と揃う側)に倒す
|
|
5192
5655
|
// 接続先0件の抑止は入力欄と**同じ関数**を通す(keyTargetCount の 🔴 不変条件)
|
|
5193
5656
|
var tokenRelevant = (derived != null ? derived !== "off" : true) && keyTargetCount(cap) > 0;
|
|
5194
|
-
if (tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !(
|
|
5657
|
+
if (tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !secretPresentForTile(sk)) {
|
|
5195
5658
|
body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
|
|
5196
5659
|
}
|
|
5197
|
-
//
|
|
5198
|
-
//
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5660
|
+
// 🔴 **セットアップの残り(checks)の長文はタイルに出さない(#230・オーナー指摘)。**
|
|
5661
|
+
//
|
|
5662
|
+
// 出所は engine の夜間チェックの 「action」 欄で、**この設定画面が無かった頃の
|
|
5663
|
+
// 手順**(「Secrets に FIGMA_TOKEN を登録しても同じです」「Run workflow を
|
|
5664
|
+
// 実行すると取り込まれます」「手元の Claude Code なら /sync-designs」)が
|
|
5665
|
+
// そのまま流れ込んでいた——UIから登録させたいのに UI を迂回する裏口を案内し、
|
|
5666
|
+
// しかも鍵を保存すれば同期は自動で起きるので**半分は嘘**になっていた。
|
|
5667
|
+
//
|
|
5668
|
+
// **タイルは短い状態表示だけにする。** 何が足りないかは「⚠ トークンが未設定です」で
|
|
5669
|
+
// 足り、対処は開いたモーダルの中の案内が持つ。
|
|
5670
|
+
// **shownOnTile を立てない**ので、これらの行は下の「セットアップの残り」で拾われる
|
|
5671
|
+
//(消すのではなく、置き場所を1つに戻す)。文言そのものの手当ては engine 側の別issue。
|
|
5209
5672
|
// **id は能力ごとに1つ(先頭のアダプタ)。** 接続マップのノードの飛び先で、
|
|
5210
5673
|
// 同じ id が2つある不正なHTMLになると getElementById が思わぬ側を指す
|
|
5211
5674
|
var tile = el("button", { class: "ds-tile", type: "button", id: ai ? "ds-" + cap + "-" + adapter : "ds-" + cap }, [
|
|
@@ -5353,11 +5816,15 @@ ${nextStateDelay.toString()}
|
|
|
5353
5816
|
* DS_GATE_NO_TOKEN が持っていて、どちらも dsGateOf を通して取る。ここで文面を
|
|
5354
5817
|
* 書き写すと、**同じ行の状態バッジと確認結果で語が割れる**(この節で3回直した形)。
|
|
5355
5818
|
*
|
|
5356
|
-
* 他アダプタ(
|
|
5819
|
+
* 他アダプタ(Backlog / Figma / Drive)を足すときの拡張点:
|
|
5357
5820
|
* - 判定と語彙は infra/lambda/shared/slack-gate.mjs 側に足す(画面は DS_GATE のまま)
|
|
5358
5821
|
* - この節に増やすのは probeHelpNodes の「次の一手」だけ。**アダプタ固有の分岐を
|
|
5359
5822
|
* probeVerdict に入れない**(入れた瞬間に判定が画面側にも生まれる)
|
|
5360
|
-
* -
|
|
5823
|
+
* - 送る種別(probeTypeOf)と、行に出す条件(下の extSourceCard)に足す
|
|
5824
|
+
*
|
|
5825
|
+
* **開発(GitHub)は #234 で足した。** 判定は shared/github-gate.mjs、次の一手は
|
|
5826
|
+
* サーバの needed(理由と設定ページのURLを1文で持つ)をそのまま出している
|
|
5827
|
+
* ——画面に分岐が生まれないよう、言い分けを全部あちらに置いた
|
|
5361
5828
|
* ------------------------------------------------------------------------- */
|
|
5362
5829
|
|
|
5363
5830
|
/**
|
|
@@ -5372,6 +5839,50 @@ ${nextStateDelay.toString()}
|
|
|
5372
5839
|
return dsGateOf({ type: s.type, workspace: s.workspace, gate: r.gate });
|
|
5373
5840
|
}
|
|
5374
5841
|
|
|
5842
|
+
/**
|
|
5843
|
+
* 確認したいアダプタの種別(「op=probe」 の 「type」)。**画面の種別をそのまま送らない**
|
|
5844
|
+
* ——「github-issues」 / 「github-discussions」 は同じ1つのリポジトリの読み取りで、
|
|
5845
|
+
* サーバの受け口は 「github」 の1種別しか知らない(「PROBE_REF_RULES」)。
|
|
5846
|
+
*/
|
|
5847
|
+
function probeTypeOf(type) {
|
|
5848
|
+
if (String(type || "").indexOf("github") === 0) return "github";
|
|
5849
|
+
return type === "slack" ? "slack" : "";
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
/**
|
|
5853
|
+
* サーバが返した 「needed」(次の一手。URL を含むことがある)を、リンク付きの1段落にする。
|
|
5854
|
+
*
|
|
5855
|
+
* 🔴 **innerHTML を使わない。** 「needed」 はサーバの文だが、そこには**組織名や
|
|
5856
|
+
* リポジトリ名(人が登録した値)が埋め込まれている**。HTML として流し込む経路を
|
|
5857
|
+
* 1本でも作ると、登録した文字列が画面のマークアップになる。テキストノードと
|
|
5858
|
+
* 「<a>」 の組み立てだけで作る(この画面の他の表示と同じ流儀)。
|
|
5859
|
+
*
|
|
5860
|
+
* **URL 以外は素のまま出す。** 言い換えると検索できないので、サーバの文言を削らない。
|
|
5861
|
+
*/
|
|
5862
|
+
function neededNode(text) {
|
|
5863
|
+
var p = el("p", { class: "ds-probe-help" });
|
|
5864
|
+
splitNeededParts(text).forEach(function (part) {
|
|
5865
|
+
if (part.url) p.appendChild(el("a", { href: part.text, target: "_blank", rel: "noopener", text: part.text }));
|
|
5866
|
+
else p.appendChild(document.createTextNode(part.text));
|
|
5867
|
+
});
|
|
5868
|
+
return p;
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
/**
|
|
5872
|
+
* 開発(GitHub)の確認結果の本文(#233 / #234)。
|
|
5873
|
+
*
|
|
5874
|
+
* **理由と対処はサーバが1文で持っている**(「shared/github-gate.mjs」 の 「needed」)。
|
|
5875
|
+
* 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
|
|
5876
|
+
* 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
|
|
5877
|
+
* (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
|
|
5878
|
+
*/
|
|
5879
|
+
function githubProbeHelpNodes(r) {
|
|
5880
|
+
if (r.gate === "ok") return [];
|
|
5881
|
+
if (r.needed) return [neededNode(r.needed)];
|
|
5882
|
+
// needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
|
|
5883
|
+
return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5375
5886
|
/**
|
|
5376
5887
|
* 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
|
|
5377
5888
|
*
|
|
@@ -5394,6 +5905,7 @@ ${nextStateDelay.toString()}
|
|
|
5394
5905
|
*/
|
|
5395
5906
|
function probeHelpNodes(s, r) {
|
|
5396
5907
|
var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
|
|
5908
|
+
if (probeTypeOf(s.type) === "github") return githubProbeHelpNodes(r);
|
|
5397
5909
|
// 成功。**1行で終わり。** 状態の語(「接続OK」)が既に出ているので本文は空
|
|
5398
5910
|
if (r.gate === "ok") return [];
|
|
5399
5911
|
if (r.gate === "not_in_channel") {
|
|
@@ -5515,7 +6027,9 @@ ${nextStateDelay.toString()}
|
|
|
5515
6027
|
msg.textContent = "確認中…";
|
|
5516
6028
|
at.textContent = "";
|
|
5517
6029
|
body.textContent = "";
|
|
5518
|
-
|
|
6030
|
+
// **種別はサーバの語彙に畳む**(github-issues / github-discussions → github)。
|
|
6031
|
+
// ワークスペースは Slack にしか無い概念なので、他アダプタでは載せない
|
|
6032
|
+
var payload = { type: probeTypeOf(s.type), ref: s.ref };
|
|
5519
6033
|
if (s.workspace) payload.workspace = s.workspace;
|
|
5520
6034
|
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
5521
6035
|
try {
|
|
@@ -5547,8 +6061,10 @@ ${nextStateDelay.toString()}
|
|
|
5547
6061
|
var oldScopes = card.querySelectorAll(".ds-probe-scope");
|
|
5548
6062
|
for (var oi = 0; oi < oldScopes.length; oi++) oldScopes[oi].remove();
|
|
5549
6063
|
// 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
|
|
5550
|
-
//
|
|
5551
|
-
|
|
6064
|
+
// 本文へ落とすと、いちばん要る情報が説明文に埋もれる。
|
|
6065
|
+
// ⚠ **札にするのは Slack のスコープ名だけ。** GitHub の needed は URL 込みの
|
|
6066
|
+
// 一文なので、札に押し込むとバッジの横で折り返して読めない(本文へ出す)
|
|
6067
|
+
if (j.gate === "missing_scope" && probeTypeOf(s.type) === "slack") {
|
|
5552
6068
|
head.appendChild(el("code", { class: "ds-probe-scope", text: j.needed || "不明" }));
|
|
5553
6069
|
}
|
|
5554
6070
|
at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
|
|
@@ -5557,8 +6073,10 @@ ${nextStateDelay.toString()}
|
|
|
5557
6073
|
// 下に残ると、同じ行に2つのことが書いてある状態になる
|
|
5558
6074
|
var stale = card.querySelector(".ds-gate-help");
|
|
5559
6075
|
if (stale) stale.hidden = true;
|
|
5560
|
-
// 表示名を Slack
|
|
5561
|
-
|
|
6076
|
+
// 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)。
|
|
6077
|
+
// **GitHub は揃えない。** 行の名前は owner/repo そのもので、書き換える口
|
|
6078
|
+
//(op=config の refresh-channel-name)は channels.json 専用
|
|
6079
|
+
if (s.type === "slack") autoAlignName(s, j, card);
|
|
5562
6080
|
} catch (e) {
|
|
5563
6081
|
msg.className = "ds-toggle-msg is-error";
|
|
5564
6082
|
msg.textContent = "✗ 通信に失敗しました。";
|
|
@@ -7153,7 +7671,9 @@ ${nextStateDelay.toString()}
|
|
|
7153
7671
|
// 内部事情を全行に並べることになる——押す必要が無いものを説明する側の混雑。
|
|
7154
7672
|
// 一方 App の札は「この組織はもう繋いである」という**運用上の意味**を持つので残す。
|
|
7155
7673
|
var ghMeta = githubOwnerMeta(ghOwner);
|
|
7156
|
-
|
|
7674
|
+
// **正本(registeredNow.githubOwners)は source を持たない**(工程1.5)。
|
|
7675
|
+
// App が入っていると実測できたときが、まとめ表の source === "app" に当たる
|
|
7676
|
+
var via = ghMeta && (ghMeta.source === "app" || ghMeta.appInstalled === true) ? "(GitHub App)" : "";
|
|
7157
7677
|
badges.appendChild(el("span", {
|
|
7158
7678
|
class: "badge ds-workspace" + (ghMeta && ghMeta.source === "unknown" ? " ds-workspace-unknown" : ""),
|
|
7159
7679
|
text: ghOwner + via,
|
|
@@ -7260,13 +7780,15 @@ ${nextStateDelay.toString()}
|
|
|
7260
7780
|
// 気づいた人がその場で確かめられるほうがよい(オーナー判断)。書き込みになる
|
|
7261
7781
|
// 「表示名を更新する」は、押した結果の中から別のボタンとして出る(probeSection)。
|
|
7262
7782
|
//
|
|
7263
|
-
// **いまは Slack
|
|
7264
|
-
//
|
|
7265
|
-
//
|
|
7783
|
+
// **いまは Slack と 開発(GitHub)。** GitHub は #234 で足した——「アクセス不可 →
|
|
7784
|
+
// App の Configure でリポジトリを追加 → 直ったか確かめる」が、それまで**翌朝まで
|
|
7785
|
+
// 待つしかなかった**(受け入れテストでは fleet-status.yml を手で起動して確かめた)。
|
|
7786
|
+
// 判定と文面は infra/lambda/shared/github-gate.mjs 側(画面に分岐を持たせない)。
|
|
7787
|
+
// Backlog / Figma / Drive は実測そのものが新規なので、まだ出さない。
|
|
7266
7788
|
//
|
|
7267
7789
|
// **origin === "explicit" でも出す。** 確認は読むだけで、どの登録ファイル由来かに
|
|
7268
7790
|
// 関係なく効く(書き込みになる「表示名を更新する」だけが由来で出し分かれる)
|
|
7269
|
-
if (SITE.intake && s.type
|
|
7791
|
+
if (SITE.intake && probeTypeOf(s.type) && s.ref) {
|
|
7270
7792
|
var probe = probeSection(s, card, gateEl);
|
|
7271
7793
|
// **ボタンは行の中(「登録を解除」の隣)。** 独立した段を作ると1行が2段になる
|
|
7272
7794
|
card.appendChild(probe.btn);
|
|
@@ -7442,6 +7964,20 @@ ${nextStateDelay.toString()}
|
|
|
7442
7964
|
* その能力の行が応答から消え、**タイルの件数だけが行ったり来たりする**。
|
|
7443
7965
|
*/
|
|
7444
7966
|
var OPEN_CAPS = [];
|
|
7967
|
+
/**
|
|
7968
|
+
* 画面の能力名 → 接続状態ストアの能力名(対応表の実体は STATE_CAP_NAMES)。
|
|
7969
|
+
*
|
|
7970
|
+
* **「stateCapIdOf」(サーバへ「開いた」と伝える側)とは分ける。** あちらは
|
|
7971
|
+
* **登録ファイルを読ませる**要求なので、増やすと GitHub の枠を削る。こちらは
|
|
7972
|
+
* 届いた 「stateNow」 の行を引くだけで、**要求とは無関係に会議の行も届く**
|
|
7973
|
+
* (ストアの問い合わせは能力で絞られていない)
|
|
7974
|
+
*/
|
|
7975
|
+
function capIdOf(cap) {
|
|
7976
|
+
for (var sid in STATE_CAP_NAMES) {
|
|
7977
|
+
if (Object.prototype.hasOwnProperty.call(STATE_CAP_NAMES, sid) && STATE_CAP_NAMES[sid] === cap) return sid;
|
|
7978
|
+
}
|
|
7979
|
+
return "";
|
|
7980
|
+
}
|
|
7445
7981
|
/** 画面の能力名 → op=state の識別子(対応表の実体は REGISTERED_CAP_NAMES) */
|
|
7446
7982
|
function stateCapIdOf(cap) {
|
|
7447
7983
|
for (var id in REGISTERED_CAP_NAMES) {
|
|
@@ -7514,6 +8050,14 @@ ${nextStateDelay.toString()}
|
|
|
7514
8050
|
// ここより後に置くと**その案件だけ素通り**する
|
|
7515
8051
|
LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
|
|
7516
8052
|
LIVE_STATE = sanitizeStateNow(j && j.stateNow);
|
|
8053
|
+
// **欄の有無をそのまま覚える。** sanitize を通した後では「読めて0件」が消える
|
|
8054
|
+
LIVE_STATE_SEEN = !!(j && Array.isArray(j.stateNow));
|
|
8055
|
+
// 🔴 **鍵・能力・組織も fleet より先に読む(工程1.5)。** まとめ表を
|
|
8056
|
+
// まだ持たない案件は下で throw するので、ここより後に置くと
|
|
8057
|
+
// **立ち上げ直後の案件=この修正が一番効く案件**だけ素通りする
|
|
8058
|
+
LIVE_SECRETS = sanitizeSecretsNow(j && j.secretsNow);
|
|
8059
|
+
LIVE_CAPS = sanitizeCapabilitiesNow(j && j.capabilitiesNow);
|
|
8060
|
+
LIVE_GH_OWNERS = sanitizeGithubOwnersNow(j && j.registeredNow && j.registeredNow.githubOwners);
|
|
7517
8061
|
MERGED = null;
|
|
7518
8062
|
if (!j || !j.fleet) throw new Error("no fleet");
|
|
7519
8063
|
LIVE_FLEET = normalizeFleet(j.fleet);
|
|
@@ -7521,6 +8065,10 @@ ${nextStateDelay.toString()}
|
|
|
7521
8065
|
liveFleetRequested = false;
|
|
7522
8066
|
MERGED = null;
|
|
7523
8067
|
refreshDataSources();
|
|
8068
|
+
// 🔴 **図の降格は refreshDataSources の外で呼ぶ(#235 の表示層)。** あちらは
|
|
8069
|
+
// モーダルが開いている間・入力中は何もせずに戻るので、中に入れると
|
|
8070
|
+
// **モーダルを開いたまま最初の応答が届いた回だけ、図が「接続OK」のまま残る**
|
|
8071
|
+
applyUsageFactToConnMap();
|
|
7524
8072
|
// **モーダルを開いたままでも「反映中」だけは追いつかせる。** refreshDataSources は
|
|
7525
8073
|
// モーダルが開いている間・入力中は何もしない(下地を入れ替えると開いているモーダルの
|
|
7526
8074
|
// 中身が切り離される)ので、局所的に差し替える口をここに置く
|
|
@@ -7826,6 +8374,13 @@ ${nextStateDelay.toString()}
|
|
|
7826
8374
|
* @returns { known, src } src は取得元の文字列("yes" のときだけ意味を持つ)
|
|
7827
8375
|
*/
|
|
7828
8376
|
function secretStateOf(kind, stateSpace, spaceCount) {
|
|
8377
|
+
// 🔴 **正本(secretsNow)が答えられるならそれ(工程1.5・#227)。** まとめ表は夜間の
|
|
8378
|
+
// スナップショットなので、**鍵を保存した直後は前日の「未登録」を出し続ける**
|
|
8379
|
+
// ——入れた人は「保存できていない」と読んで貼り直す。正本は Secrets Manager を
|
|
8380
|
+
// 都度見た答えなので、次のポーリングで「登録済み」になる。
|
|
8381
|
+
// 答えられないとき(行が来ていない・unknown)だけ、下のまとめ表の判定へ落ちる
|
|
8382
|
+
var now = secretStateFromNow(LIVE_SECRETS, kind, kind === "backlog" ? stateSpace : "");
|
|
8383
|
+
if (now) return now;
|
|
7829
8384
|
var perKind = (fleetVal("secretSources") || {})[kind] || "";
|
|
7830
8385
|
var spaces = fleetVal("secretSpaces");
|
|
7831
8386
|
if (kind === "backlog" && stateSpace && spaces && typeof spaces === "object") {
|
|
@@ -7842,6 +8397,22 @@ ${nextStateDelay.toString()}
|
|
|
7842
8397
|
return { known: "unknown", src: "" };
|
|
7843
8398
|
}
|
|
7844
8399
|
|
|
8400
|
+
/**
|
|
8401
|
+
* タイルの「⚠ トークンが未設定です」を出さないでよいか(工程1.5・#227)。
|
|
8402
|
+
*
|
|
8403
|
+
* **タイルは能力に1枚しかない**ので、スペースごとの答えは持てない。まとめ表の
|
|
8404
|
+
* 「secretSources[kind]」(種別ごとの1つの値)と**同じ粒度**に畳んで、1本でも
|
|
8405
|
+
* 入っていれば注記を出さない——スペースごとの充足は行の中の鍵カードが答える。
|
|
8406
|
+
*
|
|
8407
|
+
* 正本が「確かめられなかった」(unknown)と答えたときだけ、まとめ表の値に落ちる。
|
|
8408
|
+
*/
|
|
8409
|
+
function secretPresentForTile(kind) {
|
|
8410
|
+
var now = secretRegisteredAnywhere(LIVE_SECRETS, kind);
|
|
8411
|
+
if (now === "yes") return true;
|
|
8412
|
+
if (now === "no") return false;
|
|
8413
|
+
return !!((fleetVal("secretSources") || {})[kind]);
|
|
8414
|
+
}
|
|
8415
|
+
|
|
7845
8416
|
/**
|
|
7846
8417
|
* @param opts { space, stateSpace, spaceCount, inRow }
|
|
7847
8418
|
* space … 保存時に名乗る Backlog スペース(名乗らないときは空)
|
|
@@ -8012,6 +8583,55 @@ ${nextStateDelay.toString()}
|
|
|
8012
8583
|
});
|
|
8013
8584
|
app.appendChild(wrap);
|
|
8014
8585
|
updateConnMapAsOf();
|
|
8586
|
+
applyUsageFactToConnMap();
|
|
8587
|
+
}
|
|
8588
|
+
|
|
8589
|
+
/**
|
|
8590
|
+
* 取り込みの実績が無い能力を、接続マップから降ろす(#235 の表示層)。
|
|
8591
|
+
*
|
|
8592
|
+
* **図はビルド時に焼かれている。** 焼いた時点では 「stateNow」 を持っていないので、
|
|
8593
|
+
* 会議・共有資料は capabilities が 「on」 なだけで「● 接続OK・線あり」になる。
|
|
8594
|
+
* ライブ応答が届いてから、実績の無い2能力だけを**その場で**降ろす。
|
|
8595
|
+
*
|
|
8596
|
+
* - 線は消す(「data-cm-cap」 で引く。位置で数えると案件ごとに並びが変わる)
|
|
8597
|
+
* - ノードは 「cm-off」(灰・破線)にして、3行目を「未使用」にする。図の語彙は
|
|
8598
|
+
* renderConnectionMap の off と同じにする——同じ画面で図とタイルの語を割らない
|
|
8599
|
+
*
|
|
8600
|
+
* **降ろすのは 「cm-ok」 からだけ**(タイル側と同じ規律。「cm-warn」 は隠さない)。
|
|
8601
|
+
*
|
|
8602
|
+
* ⚠ **戻す道は無い。** 画面を開いたまま初回の取り込みが完走した場合、下の一覧だけが
|
|
8603
|
+
* 「接続OK」に変わり、図は次に開くまで「未使用」のまま。図の遅れは
|
|
8604
|
+
* 「この図は◯◯時点です(下の一覧は最新)」で既に断ってあるので、ここでは受け入れる
|
|
8605
|
+
* ——「cm-ok」 を条件にしているので、何度呼んでも二重に降ろすことはない。
|
|
8606
|
+
*/
|
|
8607
|
+
function applyUsageFactToConnMap() {
|
|
8608
|
+
var wrap = app.querySelector(".conn-map");
|
|
8609
|
+
if (!wrap) return;
|
|
8610
|
+
for (var cap in FACT_CAPS) {
|
|
8611
|
+
if (!Object.prototype.hasOwnProperty.call(FACT_CAPS, cap)) continue;
|
|
8612
|
+
if (!usageFactMissing(cap, LIVE_STATE, LIVE_STATE_SEEN)) continue;
|
|
8613
|
+
var node = wrap.querySelector('[data-target="ds-' + cap + '"]');
|
|
8614
|
+
if (!node || node.getAttribute("class").indexOf("cm-ok") < 0) continue;
|
|
8615
|
+
node.setAttribute("class", "cm-node cm-off");
|
|
8616
|
+
var sync = node.querySelector(".cm-sync");
|
|
8617
|
+
if (!sync) {
|
|
8618
|
+
// 3行目そのものが無いノード(最終同期を持たないまま ok になっていた)。
|
|
8619
|
+
// **状態を書く場所を作る**——灰色になっただけで理由が読めないのは、
|
|
8620
|
+
// 「名前だけ出て状態が出ない」という #124 の症状に戻ること
|
|
8621
|
+
var label = node.querySelector(".cm-label");
|
|
8622
|
+
sync = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
8623
|
+
sync.setAttribute("class", "cm-sync");
|
|
8624
|
+
sync.setAttribute("x", label ? label.getAttribute("x") : "0");
|
|
8625
|
+
sync.setAttribute("y", "67");
|
|
8626
|
+
sync.setAttribute("text-anchor", "middle");
|
|
8627
|
+
node.appendChild(sync);
|
|
8628
|
+
}
|
|
8629
|
+
// 時刻の整形(renderConnMap の [data-ts])が後から書き戻さないよう、印ごと外す
|
|
8630
|
+
sync.removeAttribute("data-ts");
|
|
8631
|
+
sync.textContent = "未使用";
|
|
8632
|
+
var link = wrap.querySelector('.cm-link[data-cm-cap="' + cap + '"]');
|
|
8633
|
+
if (link && link.parentNode) link.parentNode.removeChild(link);
|
|
8634
|
+
}
|
|
8015
8635
|
}
|
|
8016
8636
|
|
|
8017
8637
|
/**
|
package/package.json
CHANGED