@takagaki/cortex-decisions-viewer 0.12.48 → 0.12.49
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/build.js +3 -0
- package/dist/render.js +520 -37
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -430,6 +430,9 @@ async function readFleetSources(repoRoot) {
|
|
|
430
430
|
label: String(s.label ?? ""),
|
|
431
431
|
url: s.url != null ? String(s.url) : undefined,
|
|
432
432
|
lastSync: s.lastSync != null ? String(s.lastSync) : undefined,
|
|
433
|
+
// 🔴 **時刻と一緒に「誰が測ったか」も通す(#523 決定1)。** これを落とすと、
|
|
434
|
+
// 画面は run の成功時刻(空振りの run を含む)を「最終同期」として出し続ける
|
|
435
|
+
lastSyncSource: s.lastSyncSource != null ? String(s.lastSyncSource) : undefined,
|
|
433
436
|
// 取り込み済みの件数。**「同期が走った」と「実際に入った」は別物。**
|
|
434
437
|
// これが無いと、初回全量同期をしていない案件が「接続OK」に見える
|
|
435
438
|
itemCount: typeof s.itemCount === "number" ? s.itemCount : undefined,
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNSETTLED_LIMITS = exports.ALREADY_REGISTERED = exports.LIVE_CONFIRM_LIMITS = exports.CLAUDE_CODE_LINK = void 0;
|
|
3
|
+
exports.STATE_CONFIRMING = exports.UNSETTLED_LIMITS = exports.ALREADY_REGISTERED = exports.LIVE_CONFIRM_LIMITS = exports.CLAUDE_CODE_LINK = void 0;
|
|
4
4
|
exports.renderSite = renderSite;
|
|
5
5
|
exports.renderSiteWithAssets = renderSiteWithAssets;
|
|
6
6
|
exports.viewerAssetSources = viewerAssetSources;
|
|
@@ -36,6 +36,9 @@ exports.sanitizeCapabilitiesNow = sanitizeCapabilitiesNow;
|
|
|
36
36
|
exports.sanitizeGithubOwnersNow = sanitizeGithubOwnersNow;
|
|
37
37
|
exports.splitNeededParts = splitNeededParts;
|
|
38
38
|
exports.ingestedAtOf = ingestedAtOf;
|
|
39
|
+
exports.fleetLastSyncOf = fleetLastSyncOf;
|
|
40
|
+
exports.stateRefKey = stateRefKey;
|
|
41
|
+
exports.registeredRefKeys = registeredRefKeys;
|
|
39
42
|
exports.keyMissingText = keyMissingText;
|
|
40
43
|
exports.isCalendarId = isCalendarId;
|
|
41
44
|
exports.calendarStateRow = calendarStateRow;
|
|
@@ -1860,20 +1863,124 @@ function splitNeededParts(text) {
|
|
|
1860
1863
|
*
|
|
1861
1864
|
* @param capId 応答側の能力名(issues / design / materials / meetings)
|
|
1862
1865
|
* @returns ISO文字列。1件も無ければ ""
|
|
1866
|
+
* @param refs 登録簿にある ref の鍵(`registeredRefKeys`)。**渡されたときだけ篩う**
|
|
1867
|
+
* ——孤児行(外した接続先の残骸)の `lastOkAt` を「最終同期」として出さないため。
|
|
1868
|
+
* 分からない(null / undefined)ときは篩わない(#523 S1・deriveCapTile と同じ規律)
|
|
1863
1869
|
*/
|
|
1864
|
-
function ingestedAtOf(rows, capId) {
|
|
1870
|
+
function ingestedAtOf(rows, capId, refs) {
|
|
1865
1871
|
if (!Array.isArray(rows) || !capId)
|
|
1866
1872
|
return "";
|
|
1867
1873
|
let best = "";
|
|
1868
1874
|
for (const r of rows) {
|
|
1869
1875
|
if (!r || r.cap !== capId || r.question !== "ingested")
|
|
1870
1876
|
continue;
|
|
1877
|
+
if (refs && !refs[stateRefKey(capId, r.ref, r.workspace)])
|
|
1878
|
+
continue;
|
|
1871
1879
|
const at = r.lastOkAt || (r.status === "ok" ? r.at : "");
|
|
1872
1880
|
if (typeof at === "string" && at > best)
|
|
1873
1881
|
best = at;
|
|
1874
1882
|
}
|
|
1875
1883
|
return best;
|
|
1876
1884
|
}
|
|
1885
|
+
/**
|
|
1886
|
+
* まとめ表(fleet-status.json)の「最終同期」を、画面に出してよい形で取る(#523 決定1)。
|
|
1887
|
+
*
|
|
1888
|
+
* 🔴 **run 由来の時刻は捨てる。** `lastSync` には2つの意味が入っている——
|
|
1889
|
+
* 接続状態ストアの `lastOkAt`(実際に取り込めた時刻・`lastSyncSource === "ingested"`)と、
|
|
1890
|
+
* ワークフローが成功した時刻(`"run"`。鍵が無くて1件も取り込めていない run も
|
|
1891
|
+
* 「成功」で終わる)。後者を「最終同期」として出すと、**登録したのに何も取り込めて
|
|
1892
|
+
* いない案件が「昨日同期できている」と読める**(engine #314 の「最終同期 9/8 18:34」)。
|
|
1893
|
+
*
|
|
1894
|
+
* 🔴 **欄が無い生成物も run 扱い(捨てる)。** この欄より前の engine が作った
|
|
1895
|
+
* fleet-status.json には `lastSyncSource` が無い。「どちらか分からない時刻」を出すのは
|
|
1896
|
+
* 嘘をつきうる側なので、出さない方へ倒す(決定1の趣旨)。
|
|
1897
|
+
*
|
|
1898
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
1899
|
+
*/
|
|
1900
|
+
function fleetLastSyncOf(internal) {
|
|
1901
|
+
if (!internal || typeof internal !== "object")
|
|
1902
|
+
return "";
|
|
1903
|
+
if (internal.lastSyncSource !== "ingested")
|
|
1904
|
+
return "";
|
|
1905
|
+
return typeof internal.lastSync === "string" ? internal.lastSync : "";
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* 接続状態ストアの行の `ref` を、**登録簿との突き合わせ鍵**に畳む(#523 S1・設計v2 §2 の表)。
|
|
1909
|
+
*
|
|
1910
|
+
* 🔴 **能力ごとに鍵が違う。** `registeredRefKeys` と**必ず同じ畳み方**にすること
|
|
1911
|
+
* ——片方だけ変えると、登録してある接続先の行が「登録簿に無い」と判定され、
|
|
1912
|
+
* **実測が届いているのに画面が黙る**(いま直そうとしている嘘の裏返し)。
|
|
1913
|
+
* - issues … `issuesRefOf`(ドメイン小文字 / キー大文字)。設定ファイルの値は人が
|
|
1914
|
+
* 書いたもので大小が揃っていない
|
|
1915
|
+
* - materials / design … folderId / fileKey の**生値**(どちらも大小を区別する不透明ID)
|
|
1916
|
+
* - chat … ワークスペース + チャンネルID(IDはワークスペースの中でしか一意でない。
|
|
1917
|
+
* `overlayStateOnFleet` と同じ鍵)
|
|
1918
|
+
* - google-chat / dev … ref の生値(ワークスペースを持たない)
|
|
1919
|
+
*
|
|
1920
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
|
|
1921
|
+
* `issuesRefOf` はこれより**前**に流し込んである)。
|
|
1922
|
+
*/
|
|
1923
|
+
function stateRefKey(capId, ref, workspace) {
|
|
1924
|
+
var s = String(ref == null ? "" : ref);
|
|
1925
|
+
if (!s)
|
|
1926
|
+
return "";
|
|
1927
|
+
if (capId === "issues") {
|
|
1928
|
+
var at = s.indexOf("/");
|
|
1929
|
+
if (at < 0)
|
|
1930
|
+
return "";
|
|
1931
|
+
return issuesRefOf(s.slice(0, at), s.slice(at + 1));
|
|
1932
|
+
}
|
|
1933
|
+
if (capId === "chat")
|
|
1934
|
+
return String(workspace == null ? "" : workspace) + "\t" + s;
|
|
1935
|
+
return s;
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* 登録簿(`registeredNow` の1能力分)から、突き合わせ鍵の集合を作る(#523 S1)。
|
|
1939
|
+
*
|
|
1940
|
+
* 🔴 **チャットは鍵を2本入れる。** 正式な鍵は `ワークスペース + ref` だが、ストアの行が
|
|
1941
|
+
* ワークスペースを持たない場合は厳密な鍵を組めない。そこで `"" + \t + ref` も入れて
|
|
1942
|
+
* **「分からないものは落とさない」**側へ倒す(篩い過ぎ=画面が黙る、が一番重い事故)。
|
|
1943
|
+
* 逆に、登録されていないワークスペースの同IDは落ちる(設計どおり)。
|
|
1944
|
+
*
|
|
1945
|
+
* @param regKey `registeredNow` のキー(chat / google-chat / dev / design / materials / issues)
|
|
1946
|
+
* @param rows その能力の登録行。配列でなければ null(=分からない。篩わない)
|
|
1947
|
+
*
|
|
1948
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
1949
|
+
*/
|
|
1950
|
+
function registeredRefKeys(regKey, rows) {
|
|
1951
|
+
if (!Array.isArray(rows))
|
|
1952
|
+
return null;
|
|
1953
|
+
var out = {};
|
|
1954
|
+
for (var i = 0; i < rows.length; i++) {
|
|
1955
|
+
var r = rows[i];
|
|
1956
|
+
if (!r || typeof r !== "object")
|
|
1957
|
+
continue;
|
|
1958
|
+
if (regKey === "issues") {
|
|
1959
|
+
if (r.domain && r.projectKey)
|
|
1960
|
+
out[issuesRefOf(r.domain, r.projectKey)] = true;
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
if (regKey === "design") {
|
|
1964
|
+
if (r.key)
|
|
1965
|
+
out[String(r.key)] = true;
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
if (regKey === "materials") {
|
|
1969
|
+
if (r.id)
|
|
1970
|
+
out[String(r.id)] = true;
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
if (!r.ref)
|
|
1974
|
+
continue;
|
|
1975
|
+
if (regKey === "chat") {
|
|
1976
|
+
out[String(r.workspace == null ? "" : r.workspace) + "\t" + String(r.ref)] = true;
|
|
1977
|
+
out["\t" + String(r.ref)] = true;
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
out[String(r.ref)] = true;
|
|
1981
|
+
}
|
|
1982
|
+
return out;
|
|
1983
|
+
}
|
|
1877
1984
|
/**
|
|
1878
1985
|
* タイルと接続グラフの導出に要る、能力ごとの材料(0.12.0・#231 / #235 / #227)。
|
|
1879
1986
|
*
|
|
@@ -1884,24 +1991,51 @@ function ingestedAtOf(rows, capId) {
|
|
|
1884
1991
|
* - `noFact` … 規則4(登録はあるが実績が無い)の文言。**能力ごとに待っているものが違う**
|
|
1885
1992
|
* ので1文に丸めない。会議・共有資料は中央 Apps Script の取り込み待ち(=まだ使っていない)、
|
|
1886
1993
|
* 課題管理・デザインは夜間同期待ち、チャット・開発は実測待ち
|
|
1994
|
+
* - `alsoBad` … **実績の問いとは別に、悪ければ⚠️にする問い**(#523 S1・決定3)。
|
|
1995
|
+
* 取り込む4能力は `reachable`(届くか)も見る——Drive のフォルダの共有が外れると
|
|
1996
|
+
* `reachable=unreachable` になるが `ingested` の行は**書かれない**(測れていないので)ので、
|
|
1997
|
+
* `ingested` だけを見ていると「接続OK(最終同期 ◯日前)」と言い続ける。
|
|
1998
|
+
* **「悪い方」で判定する**(どちらかが悪ければ⚠️)。実績(規則3)は `question` の側だけで数える
|
|
1999
|
+
* ——reachable が ok でも、取り込みが1件も無ければ「まだ取り込みがありません」が事実
|
|
2000
|
+
* - `adminOnNoToken` … `reachable=no_token` を「読めていません」と言わない能力(決定3)。
|
|
2001
|
+
* 鍵の呼び名があれば「◯が未登録です」(画面の鍵カードと同じ語)、無ければ
|
|
2002
|
+
* 「管理者の設定が必要です」——Drive の資格情報は艦隊側にあり、案件の人には打つ手が無い。
|
|
2003
|
+
* **チャット・開発には付けない**(あちらの `no_token` は行バッジ側が
|
|
2004
|
+
* `DS_GATE_NO_TOKEN` で経路ごとに言い換えており、タイルの語を変えると二重管理になる)
|
|
2005
|
+
* - `refKeyed` … 登録簿(`registeredNow`)との突き合わせで孤児行を落とせる能力(#523 S1・決定2)。
|
|
2006
|
+
* 会議は接続先の登録簿を持たない(ref は固定の `transcripts`)ので篩わない
|
|
1887
2007
|
*
|
|
1888
2008
|
* **6能力すべてを載せること。** ここに無い能力は導出そのものが働かず、規則5
|
|
1889
2009
|
* (fleet へのフォールバック)に落ちる=正本化の対象から静かに外れる。
|
|
1890
2010
|
*/
|
|
1891
2011
|
const TILE_CAPS = {
|
|
1892
|
-
"課題管理": { id: "issues", question: "ingested", noFact: "登録済み(まだ取り込みがありません)", keyLabel: "APIキー" },
|
|
1893
|
-
"会議": { id: "meetings", question: "ingested", noFact: "未使用(まだ取り込みがありません)", keyLabel: "" },
|
|
1894
|
-
"共有資料": { id: "materials", question: "ingested", noFact: "未使用(まだ取り込みがありません)", keyLabel: "" },
|
|
1895
|
-
"デザイン": { id: "design", question: "ingested", noFact: "登録済み(まだ取り込みがありません)", keyLabel: "トークン" },
|
|
1896
|
-
"チャット": { id: "chat", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "" },
|
|
2012
|
+
"課題管理": { id: "issues", question: "ingested", noFact: "登録済み(まだ取り込みがありません)", keyLabel: "APIキー", alsoBad: ["reachable"], adminOnNoToken: true, refKeyed: true },
|
|
2013
|
+
"会議": { id: "meetings", question: "ingested", noFact: "未使用(まだ取り込みがありません)", keyLabel: "", alsoBad: ["reachable"], adminOnNoToken: true },
|
|
2014
|
+
"共有資料": { id: "materials", question: "ingested", noFact: "未使用(まだ取り込みがありません)", keyLabel: "", alsoBad: ["reachable"], adminOnNoToken: true, refKeyed: true },
|
|
2015
|
+
"デザイン": { id: "design", question: "ingested", noFact: "登録済み(まだ取り込みがありません)", keyLabel: "トークン", alsoBad: ["reachable"], adminOnNoToken: true, refKeyed: true },
|
|
2016
|
+
"チャット": { id: "chat", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", refKeyed: true },
|
|
1897
2017
|
// チャットの2枚目のアダプタ(#153・判断⑲)。**タイルの導出はアダプタで割る**——
|
|
1898
2018
|
// 割らないと Google Chat タイルが Slack の登録件数・実測で判定される。
|
|
1899
2019
|
// 鍵はタイル側(capDerivation)が「能力-アダプタ」で引く。実測ストアの
|
|
1900
2020
|
// `cap: "google-chat"` の行は夜間の Gold 昇格が書く(#153 PR-E で結線済み)ので、
|
|
1901
2021
|
// 夜間が一度走れば規則2/3(⚠️ / 接続OK)まで進む。走る前は規則4(実績なし→中立)
|
|
1902
|
-
"チャット-google-chat": { id: "google-chat", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "" },
|
|
1903
|
-
"開発": { id: "dev", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "" },
|
|
2022
|
+
"チャット-google-chat": { id: "google-chat", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", refKeyed: true },
|
|
2023
|
+
"開発": { id: "dev", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", refKeyed: true },
|
|
1904
2024
|
};
|
|
2025
|
+
/**
|
|
2026
|
+
* 登録したばかりで、まだ実測の行が1件も届いていない接続先があるときの語(#523 決定6)。
|
|
2027
|
+
*
|
|
2028
|
+
* 🔴 **`DS_GATE_PENDING`(「登録状況を確認中です」)と混ぜない。** あちらは
|
|
2029
|
+
* 「登録簿が読めず、何件あるのかが分からない」という別の事実で、こちらは
|
|
2030
|
+
* **登録は見えている・実測の到着を待っている**。上限は `UNSETTLED_TTL_MS`(15分)で、
|
|
2031
|
+
* 過ぎたら規則4の中立文(「まだ取り込みがありません」)に戻す——待てば出るとは
|
|
2032
|
+
* 言い続けない。
|
|
2033
|
+
*
|
|
2034
|
+
* ⚠ **定数そのものを export しない**(CommonJS で `exports.X` に化け、流し込んだ先で落ちる)。
|
|
2035
|
+
*/
|
|
2036
|
+
const STATE_CONFIRMING_TEXT = "確認しています…";
|
|
2037
|
+
/** 語を**テストから固定するための入れ物**(UNSETTLED_LIMITS と同じ作法) */
|
|
2038
|
+
exports.STATE_CONFIRMING = { text: STATE_CONFIRMING_TEXT };
|
|
1905
2039
|
/**
|
|
1906
2040
|
* 「鍵が無くて飛ばした」の一文(#225)。**能力ごとに鍵の呼び名が違う。**
|
|
1907
2041
|
*
|
|
@@ -2254,10 +2388,16 @@ function calendarAddUrl(id) {
|
|
|
2254
2388
|
* `sanitizeStateNow` は0件を `null` に畳むので、`rows` だけを見ると
|
|
2255
2389
|
* 「読めて空(=まだ何も起きていない事実)」が「分からない」に化ける
|
|
2256
2390
|
* @param ctx.rows `stateNow` の行
|
|
2391
|
+
* @param ctx.refs 登録簿にある ref の鍵(`registeredRefKeys` の答え)。**分からなければ
|
|
2392
|
+
* 渡さない**——null を「登録が無い」として篩うと、登録簿が読めなかった回に
|
|
2393
|
+
* 実測が丸ごと無視され、画面が黙る(`registered: null` を 0 と混ぜないのと同じ理由)
|
|
2394
|
+
* @param ctx.confirming 登録したばかりで実測の到着を待っている接続先があるか(決定6)。
|
|
2395
|
+
* 規則4の文言だけを差し替える(状態は中立のまま)
|
|
2257
2396
|
* @returns 導出結果。`null` なら規則5(従来表示へフォールバック)
|
|
2258
2397
|
*
|
|
2259
2398
|
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
|
|
2260
|
-
* `TILE_CAPS` / `BAD_STATE_STATUSES` は CLIENT_JS 側にも
|
|
2399
|
+
* `TILE_CAPS` / `BAD_STATE_STATUSES` / `STATE_CONFIRMING_TEXT` は CLIENT_JS 側にも
|
|
2400
|
+
* `var` で流し込んである。`stateRefKey` もこれより前に流し込む)。
|
|
2261
2401
|
*/
|
|
2262
2402
|
function deriveCapTile(cap, ctx) {
|
|
2263
2403
|
var meta = TILE_CAPS[cap];
|
|
@@ -2271,25 +2411,43 @@ function deriveCapTile(cap, ctx) {
|
|
|
2271
2411
|
if (!ctx || !ctx.seen)
|
|
2272
2412
|
return null;
|
|
2273
2413
|
var rows = Array.isArray(ctx.rows) ? ctx.rows : [];
|
|
2274
|
-
|
|
2414
|
+
// **見る問いは「実績の問い」+ alsoBad(#523 決定3)。** 悪い方で判定する
|
|
2415
|
+
var questions = [meta.question].concat(meta.alsoBad || []);
|
|
2416
|
+
// 登録簿にある ref の鍵。**無い(null / undefined)なら篩わない**(規則1が null を
|
|
2417
|
+
// 0 と混ぜないのと同じ理由——読めなかったことを「登録が無い」と断定しない)
|
|
2418
|
+
var refs = ctx.refs || null;
|
|
2275
2419
|
var failed = 0;
|
|
2276
2420
|
var skipped = 0;
|
|
2421
|
+
var noToken = 0;
|
|
2277
2422
|
var unreachable = 0;
|
|
2278
2423
|
var okSeen = false;
|
|
2279
2424
|
for (var i = 0; i < rows.length; i++) {
|
|
2280
2425
|
var r = rows[i];
|
|
2281
|
-
if (!r || r.cap !== meta.id
|
|
2426
|
+
if (!r || r.cap !== meta.id)
|
|
2427
|
+
continue;
|
|
2428
|
+
if (questions.indexOf(r.question) < 0)
|
|
2429
|
+
continue;
|
|
2430
|
+
// 🔴 **登録簿に無い ref の行は数えない(#523 決定2)。** 接続先を外してもストアの行は
|
|
2431
|
+
// 残る(TTL が無い・既知の問題)ので、登録が1件以上ある能力では規則1が守ってくれない
|
|
2432
|
+
// ——外したフォルダの `skipped` が残っているだけで「⚠ 取り込みを飛ばしました」が
|
|
2433
|
+
// 出続ける(engine #314 の症状の候補3)。**篩い方の正本は stateRefKey 1箇所**
|
|
2434
|
+
if (refs && meta.refKeyed && !refs[stateRefKey(meta.id, r.ref, r.workspace)])
|
|
2282
2435
|
continue;
|
|
2283
2436
|
// **一度でも成功していれば実績あり。** `lastOkAt` を見るのは、いま失敗している行でも
|
|
2284
|
-
// 「繋がったことがある」のは事実だから(規則2
|
|
2285
|
-
|
|
2437
|
+
// 「繋がったことがある」のは事実だから(規則2が先に立つので表示は⚠️のまま)。
|
|
2438
|
+
// **実績は `question`(その能力が「実際に動いた」と言える問い)の側だけで数える**
|
|
2439
|
+
// ——Drive に届いていても取り込みが0件なら「まだ取り込みがありません」が事実
|
|
2440
|
+
if (r.question === meta.question && (r.status === "ok" || r.lastOkAt))
|
|
2286
2441
|
okSeen = true;
|
|
2442
|
+
var bad = BAD_STATE_STATUSES[r.question] || [];
|
|
2287
2443
|
if (bad.indexOf(r.status) < 0)
|
|
2288
2444
|
continue;
|
|
2289
2445
|
if (r.status === "failed")
|
|
2290
2446
|
failed++;
|
|
2291
2447
|
else if (r.status === "skipped")
|
|
2292
2448
|
skipped++;
|
|
2449
|
+
else if (r.status === "no_token" && meta.adminOnNoToken)
|
|
2450
|
+
noToken++;
|
|
2293
2451
|
else
|
|
2294
2452
|
unreachable++;
|
|
2295
2453
|
}
|
|
@@ -2303,6 +2461,13 @@ function deriveCapTile(cap, ctx) {
|
|
|
2303
2461
|
// 設定が済んでいないだけで、利用者が取るべき行動が違う(鍵を入れる/原因を調べる)
|
|
2304
2462
|
if (skipped)
|
|
2305
2463
|
return { rule: 2, state: "warn", text: keyMissingText(cap), keyMissing: !!meta.keyLabel };
|
|
2464
|
+
// 鍵が無くて「届くか」すら測れなかった(#523 決定3)。**「読めていません」と言わない**
|
|
2465
|
+
// ——読もうとする前に止まっているので、直すのは鍵の登録(または管理者の設定)
|
|
2466
|
+
if (noToken) {
|
|
2467
|
+
return meta.keyLabel
|
|
2468
|
+
? { rule: 2, state: "warn", text: keyMissingText(cap), keyMissing: true }
|
|
2469
|
+
: { rule: 2, state: "warn", text: "管理者の設定が必要です", keyMissing: false };
|
|
2470
|
+
}
|
|
2306
2471
|
if (unreachable) {
|
|
2307
2472
|
return { rule: 2, state: "warn", text: unreachable + "件が読めていません", keyMissing: false };
|
|
2308
2473
|
}
|
|
@@ -2310,6 +2475,13 @@ function deriveCapTile(cap, ctx) {
|
|
|
2310
2475
|
if (okSeen)
|
|
2311
2476
|
return { rule: 3, state: "ok", text: "接続OK", keyMissing: false };
|
|
2312
2477
|
// ---- 規則4: 実績なし → 中立 ----
|
|
2478
|
+
//
|
|
2479
|
+
// **登録したばかりなら「確認しています…」(#523 決定6)。** 登録した瞬間から
|
|
2480
|
+
// 「まだ取り込みがありません」と言うのは事実だが、利用者には「登録が効いていない」とも
|
|
2481
|
+
// 読める。実測が届くまでの短い間(`UNSETTLED_TTL_MS`)だけ、待っていることを言う。
|
|
2482
|
+
// **判定の材料は呼び出し側**(仮置き=pending-ops の add・capConfirming)
|
|
2483
|
+
if (ctx.confirming)
|
|
2484
|
+
return { rule: 4, state: "idle", text: STATE_CONFIRMING_TEXT, keyMissing: false };
|
|
2313
2485
|
return { rule: 4, state: "idle", text: meta.noFact, keyMissing: false };
|
|
2314
2486
|
}
|
|
2315
2487
|
function capabilityState(cap, ctx, adapter) {
|
|
@@ -2560,8 +2732,12 @@ function renderConnectionMap(site) {
|
|
|
2560
2732
|
else if (isLive) {
|
|
2561
2733
|
statusText = "ライブ参照";
|
|
2562
2734
|
}
|
|
2563
|
-
else if (internal
|
|
2564
|
-
|
|
2735
|
+
else if (fleetLastSyncOf(internal)) {
|
|
2736
|
+
// 🔴 **run 由来の時刻は焼かない(#523 決定1)。** ここは図の3行目(「最終同期 …」)で、
|
|
2737
|
+
// ライブが届くまでそのまま見えている。run の成功時刻(空振りを含む)を焼くと、
|
|
2738
|
+
// 何も取り込めていない案件の図が「昨日同期できている」と語る——
|
|
2739
|
+
// **ライブで訂正されるまでの数秒だけ嘘をつく**形も作らない
|
|
2740
|
+
statusTs = fleetLastSyncOf(internal);
|
|
2565
2741
|
}
|
|
2566
2742
|
// 時刻はクライアントでローカルタイム整形するため data-ts で渡す。
|
|
2567
2743
|
//
|
|
@@ -4304,9 +4480,17 @@ ${secretRegisteredAnywhere.toString()}
|
|
|
4304
4480
|
${sanitizeCapabilitiesNow.toString()}
|
|
4305
4481
|
${sanitizeGithubOwnersNow.toString()}
|
|
4306
4482
|
${splitNeededParts.toString()}
|
|
4483
|
+
// 🔴 **ingestedAtOf / deriveCapTile より前に置く。** 両方が突き合わせ鍵の畳み方を
|
|
4484
|
+
// ここから引く(#523 S1)。順序を崩すと、孤児行の篩が undefined を呼んで画面が落ちる
|
|
4485
|
+
${stateRefKey.toString()}
|
|
4486
|
+
${registeredRefKeys.toString()}
|
|
4487
|
+
${fleetLastSyncOf.toString()}
|
|
4307
4488
|
${ingestedAtOf.toString()}
|
|
4308
4489
|
var TILE_CAPS = ${JSON.stringify(TILE_CAPS)};
|
|
4309
4490
|
var BAD_STATE_STATUSES = ${JSON.stringify(BAD_STATE_STATUSES)};
|
|
4491
|
+
// 登録直後の一時表示(#523 決定6)。**この定数行を落とすと、流し込んだ deriveCapTile が
|
|
4492
|
+
// 未定義を参照して画面のスクリプトが丸ごと死ぬ**(定数は export できないので値はここで注入)
|
|
4493
|
+
var STATE_CONFIRMING_TEXT = ${JSON.stringify(STATE_CONFIRMING_TEXT)};
|
|
4310
4494
|
${keyMissingText.toString()}
|
|
4311
4495
|
${deriveCapTile.toString()}
|
|
4312
4496
|
// 案件カレンダー(#428)。**判定の実体はビルド時の定義**——画面側に書き写すと、
|
|
@@ -6489,10 +6673,52 @@ ${calendarAddUrl.toString()}
|
|
|
6489
6673
|
if (s.gate == null || s.gate === "") return DS_GATE_PENDING;
|
|
6490
6674
|
return DS_GATE[s.gate] || DS_GATE["unknown"];
|
|
6491
6675
|
}
|
|
6676
|
+
/* ---------------------------------------------------------------------------
|
|
6677
|
+
* 取り込む能力の接続先の行に出す「届くか」(#523 S1・決定2)
|
|
6678
|
+
*
|
|
6679
|
+
* Backlog の接続先・Drive のフォルダ・Figma のファイルにも、Slack の行と同じ
|
|
6680
|
+
* **札と「いつ確かめたか」**を出す。語は 「DS_GATE」 が正本で、**能力ごとに直す操作が
|
|
6681
|
+
* 違うものだけ**ここで言い換える:
|
|
6682
|
+
* - Drive の 「unreachable」 … 「アクセス不可」では何をすればよいか分からない。
|
|
6683
|
+
* 直すのは**フォルダ側の共有**(取り込み用アカウントの招待)
|
|
6684
|
+
* - 鍵が無い(「no_token」)… 画面に鍵の入力欄がある能力(Backlog / Figma)は
|
|
6685
|
+
* 鍵カードと同じ語(「keyMissingText」 と揃える)。Drive の資格情報は艦隊側にあり、
|
|
6686
|
+
* 案件の人には打つ手が無いので「管理者の設定が必要です」(決定3と同じ振り分け)
|
|
6687
|
+
*
|
|
6688
|
+
* 🔴 **「DS_GATE」 本体に足さない。** あちらは**実測が返す語彙**と1対1で、
|
|
6689
|
+
* 「state-vocab-parity.test.mjs」 が見張っている(足し忘れた判定が「不明」に落ちる事故の番人)。
|
|
6690
|
+
* 能力ごとの言い換えは語彙ではないので、同じ表に混ぜるとその番人が意味を失う
|
|
6691
|
+
* (「DS_GATE_NO_TOKEN」 / 「DS_GATE_RESTING_HELP」 と同じ判断)。
|
|
6692
|
+
*
|
|
6693
|
+
* **バッジは短語・次の一手は help に分ける**(バッジに長文を入れると行が折り返す)。
|
|
6694
|
+
* ------------------------------------------------------------------------- */
|
|
6695
|
+
var DS_GATE_CAP = {
|
|
6696
|
+
materials: {
|
|
6697
|
+
unreachable: {
|
|
6698
|
+
text: "フォルダを開けません",
|
|
6699
|
+
cls: "ds-gate-warn",
|
|
6700
|
+
help: DRIVE_INGEST_ACCOUNT + " を閲覧者として招待してください(招待が外れると読めません)。",
|
|
6701
|
+
},
|
|
6702
|
+
no_token: { text: "管理者の設定が必要です", cls: "ds-gate-muted" },
|
|
6703
|
+
},
|
|
6704
|
+
issues: { no_token: { text: "APIキーが未登録です", cls: "ds-gate-muted" } },
|
|
6705
|
+
design: { no_token: { text: "トークンが未登録です", cls: "ds-gate-muted" } },
|
|
6706
|
+
};
|
|
6492
6707
|
/**
|
|
6493
|
-
*
|
|
6494
|
-
*
|
|
6495
|
-
*
|
|
6708
|
+
* その能力の行に出す「届くか」の語。**能力ごとの言い換えが無ければ 「DS_GATE」 のまま。**
|
|
6709
|
+
* 知らない値は 「DS_GATE」 と同じく「不明」(engine が新しい語を足したのに画面が知らない、
|
|
6710
|
+
* という事実を黙って消さない)。
|
|
6711
|
+
*/
|
|
6712
|
+
function dsGateForCap(capId, status) {
|
|
6713
|
+
var byCap = DS_GATE_CAP[capId];
|
|
6714
|
+
if (byCap && byCap[status]) return byCap[status];
|
|
6715
|
+
return DS_GATE[status] || DS_GATE["unknown"];
|
|
6716
|
+
}
|
|
6717
|
+
/**
|
|
6718
|
+
* dsGateOf が持つ「次の一手」を、**必ず1個の DOM ノード**にする。呼び出しは3箇所
|
|
6719
|
+
* (行の常時表示・「接続を確認」を押した結果・取り込む能力の行の「届くか」)で、
|
|
6720
|
+
* gate.help / gate.request が正本——ここを通さずに文を書き写すと、片方だけ古くなる
|
|
6721
|
+
* (DS_GATE の項と同じ理由)。
|
|
6496
6722
|
*
|
|
6497
6723
|
* 🔴 **要素を2つに割らない(行の補足は最大1段・github-state-reason.test.mjs
|
|
6498
6724
|
* 「行の補足は最大1段」)。** gate.request(依頼文。手順が複数行あるもの)があるときは、
|
|
@@ -8099,15 +8325,114 @@ ${calendarAddUrl.toString()}
|
|
|
8099
8325
|
return registeredCountOf(derCap, adapter) !== null;
|
|
8100
8326
|
}
|
|
8101
8327
|
|
|
8328
|
+
/**
|
|
8329
|
+
* その能力の「登録簿にある ref」の鍵(#523 決定2)。**読めなければ null。**
|
|
8330
|
+
*
|
|
8331
|
+
* 🔴 **null を空の表に畳まないこと。** 空の表を渡すと「どの行も登録簿に無い」と
|
|
8332
|
+
* 判定され、実測が丸ごと捨てられる——**夜間が⚠️を書いているのにタイルが静か**という、
|
|
8333
|
+
* いま直そうとしている嘘と同じ形になる。
|
|
8334
|
+
*
|
|
8335
|
+
* 仮置き(#388)も勘案する: add した ref は登録簿に載る前から有効・remove した ref は
|
|
8336
|
+
* 載っていても無効(決定2「pending の 「remove-*」 も除外」)。一覧・件数と同じ材料を
|
|
8337
|
+
* 使うので、「行は消えたのにタイルだけ⚠️」が起きない。
|
|
8338
|
+
*
|
|
8339
|
+
* ⚠ **チャットの add だけは、登録簿が追いつくまでの数分、篩い過ぎる余地がある。**
|
|
8340
|
+
* 解除・追加の payload はワークスペースを持たない(どのワークスペースかはサーバが実測して
|
|
8341
|
+
* 決める)ので、仮置きが入れられる鍵はワークスペース無しの1本だけ。**以前に登録していた
|
|
8342
|
+
* チャンネルを再登録した直後**は、ストアに残った古い行(ワークスペース付き)がこの鍵に
|
|
8343
|
+
* 当たらず、数分だけタイルが黙る(登録簿が追いつけば直る)。倒す向きは
|
|
8344
|
+
* 「黙る」側で揃えてある——逆(測っていないものを⚠️にする)は直し方を示せない嘘になる。
|
|
8345
|
+
*/
|
|
8346
|
+
function refKeysOf(derCap) {
|
|
8347
|
+
var meta = TILE_CAPS[derCap];
|
|
8348
|
+
if (!meta || !meta.refKeyed || !LIVE_REGISTERED) return null;
|
|
8349
|
+
// チャットの混載タイル(アダプタ不明)は chat と google-chat の両方を抱えるが、
|
|
8350
|
+
// 鍵は能力(meta.id)ごとに引くので、ここで合算するものは無い
|
|
8351
|
+
var keys = registeredRefKeys(meta.id, LIVE_REGISTERED[meta.id]);
|
|
8352
|
+
if (!keys) return null;
|
|
8353
|
+
var ops = pendingOpsNow();
|
|
8354
|
+
for (var i = 0; i < ops.length; i++) {
|
|
8355
|
+
var e = ops[i];
|
|
8356
|
+
var km = e && PENDING_KINDS[e.kind];
|
|
8357
|
+
if (!km || km.reg !== meta.id) continue;
|
|
8358
|
+
// 仮置きの鍵(targetId)は登録簿と同じ畳み方(backlog は pendingIssuesRef=issuesRefOf)。
|
|
8359
|
+
// チャットだけワークスペースを持たない(どのワークスペースかはサーバが実測して決める)
|
|
8360
|
+
var key = meta.id === "chat" ? "\t" + e.targetId : e.targetId;
|
|
8361
|
+
if (e.action === "add") keys[key] = true;
|
|
8362
|
+
else if (e.action === "remove") {
|
|
8363
|
+
delete keys[key];
|
|
8364
|
+
// 🔴 **チャットは鍵が2本ある**(「ws + \t + ref」 と 「\t + ref」)。ワークスペース無しの
|
|
8365
|
+
// 1本だけ消すと、**一覧から行が消えたのにタイルだけ⚠️が残る**(解除した顧客
|
|
8366
|
+
// ワークスペースのチャンネルの実測が孤児として落ちない・実装レビュー🟡2)。
|
|
8367
|
+
// 解除の payload はワークスペースを持たないので、接尾一致で両方消す
|
|
8368
|
+
if (meta.id === "chat") {
|
|
8369
|
+
for (var k in keys) {
|
|
8370
|
+
if (!Object.prototype.hasOwnProperty.call(keys, k)) continue;
|
|
8371
|
+
if (k.slice(-key.length) === key) delete keys[k];
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
return keys;
|
|
8377
|
+
}
|
|
8378
|
+
|
|
8379
|
+
/**
|
|
8380
|
+
* 登録したばかりで、まだ実測の行が1件も届いていない接続先があるか(#523 決定6)。
|
|
8381
|
+
*
|
|
8382
|
+
* **対象は取り込む4能力だけ**(「alsoBad」 を持つ能力=規則4の文言が
|
|
8383
|
+
* 「まだ取り込みがありません」の系)。チャット・開発の規則4は既に
|
|
8384
|
+
* 「接続は未確認です」と言っており、待っていることが読める。
|
|
8385
|
+
*
|
|
8386
|
+
* 上限は 「UNSETTLED_TTL_MS」(15分)。過ぎたら中立文に戻る——**待てば出ると言い続けない**。
|
|
8387
|
+
*/
|
|
8388
|
+
function capConfirming(derCap) {
|
|
8389
|
+
var meta = TILE_CAPS[derCap];
|
|
8390
|
+
if (!meta || !meta.alsoBad) return false;
|
|
8391
|
+
var ops = pendingOpsNow();
|
|
8392
|
+
var now = Date.now();
|
|
8393
|
+
for (var i = 0; i < ops.length; i++) {
|
|
8394
|
+
var e = ops[i];
|
|
8395
|
+
var km = e && PENDING_KINDS[e.kind];
|
|
8396
|
+
if (!km || e.action !== "add" || km.reg !== meta.id) continue;
|
|
8397
|
+
if (!(now - e.committedAt < UNSETTLED_TTL_MS)) continue;
|
|
8398
|
+
if (stateRowSeenFor(meta.id, e.targetId)) continue;
|
|
8399
|
+
return true;
|
|
8400
|
+
}
|
|
8401
|
+
return false;
|
|
8402
|
+
}
|
|
8403
|
+
|
|
8404
|
+
/** その接続先について、ストアの行が1件でも届いているか(問いは問わない・capConfirming の材料) */
|
|
8405
|
+
function stateRowSeenFor(capId, targetId) {
|
|
8406
|
+
var rows = LIVE_STATE || [];
|
|
8407
|
+
for (var i = 0; i < rows.length; i++) {
|
|
8408
|
+
var r = rows[i];
|
|
8409
|
+
if (!r || r.cap !== capId) continue;
|
|
8410
|
+
// 仮置きの鍵はワークスペースを持たないので、ここは ref の生値で比べる
|
|
8411
|
+
// (issues だけは両側を issuesRefOf に畳む=pendingIssuesRef と同形)
|
|
8412
|
+
var k = capId === "issues" ? stateRefKey(capId, r.ref) : String(r.ref || "");
|
|
8413
|
+
if (k === targetId) return true;
|
|
8414
|
+
}
|
|
8415
|
+
return false;
|
|
8416
|
+
}
|
|
8417
|
+
|
|
8418
|
+
/**
|
|
8419
|
+
* 導出に使う鍵(「能力」または「能力-アダプタ」)。**アダプタが分かるタイルは割る**
|
|
8420
|
+
* (#153・判断⑲)——割らないと、Slack の登録件数・実測で Google Chat タイルが
|
|
8421
|
+
* 「接続OK」を名乗る(逆も)。「能力-アダプタ」の変種は TILE_CAPS が持つ。
|
|
8422
|
+
* **この解決を書き写さないこと**(写しがずれると、タイルと最終同期で別の能力を見る)
|
|
8423
|
+
*/
|
|
8424
|
+
function derCapOf(cap, adapter) {
|
|
8425
|
+
return adapter && TILE_CAPS[cap + "-" + adapter] ? cap + "-" + adapter : cap;
|
|
8426
|
+
}
|
|
8427
|
+
|
|
8102
8428
|
function capDerivation(cap, adapter) {
|
|
8103
|
-
|
|
8104
|
-
// 割らないと、Slack の登録件数・実測で Google Chat タイルが「接続OK」を名乗る(逆も)。
|
|
8105
|
-
// 「能力-アダプタ」の変種は TILE_CAPS が持つ(無いアダプタは能力単位のまま)
|
|
8106
|
-
var derCap = adapter && TILE_CAPS[cap + "-" + adapter] ? cap + "-" + adapter : cap;
|
|
8429
|
+
var derCap = derCapOf(cap, adapter);
|
|
8107
8430
|
return deriveCapTile(derCap, {
|
|
8108
8431
|
registered: registeredCountOf(derCap, adapter),
|
|
8109
8432
|
seen: LIVE_STATE_SEEN,
|
|
8110
8433
|
rows: LIVE_STATE,
|
|
8434
|
+
refs: refKeysOf(derCap),
|
|
8435
|
+
confirming: capConfirming(derCap),
|
|
8111
8436
|
});
|
|
8112
8437
|
}
|
|
8113
8438
|
|
|
@@ -8491,6 +8816,26 @@ ${calendarAddUrl.toString()}
|
|
|
8491
8816
|
*/
|
|
8492
8817
|
var KEY_SLOTS = { backlog: {}, backlogNamed: false, figma: null };
|
|
8493
8818
|
|
|
8819
|
+
/**
|
|
8820
|
+
* まとめ表の「最終同期」を後詰めに使ってよいか(#523 決定1・実装レビュー🔴1)。
|
|
8821
|
+
*
|
|
8822
|
+
* 🔴 **ストアを読めた回は使わない。** まとめ表の値がストア由来
|
|
8823
|
+
* (「lastSyncSource: "ingested"」)でも、engine 側(「materialsLastOkAtOf」)は
|
|
8824
|
+
* **登録簿で篩っていない**ので、
|
|
8825
|
+
* それは**外したフォルダの時刻でありうる**。画面がストア側で孤児行を落としたそばから、
|
|
8826
|
+
* 篩っていない同じ値をまとめ表から拾い直すと、#314 の「最終同期 9/8 18:34」がそのまま
|
|
8827
|
+
* 復活する(実装レビューで実測)。
|
|
8828
|
+
*
|
|
8829
|
+
* だから後詰めは **「ストアが読めなかった回の代用」** に限る——古い Lambda・ストアの
|
|
8830
|
+
* 読み取り失敗・ライブを持たない案件(焼いた HTML のまま見ている状態)。
|
|
8831
|
+
* そこでは比べる相手が無いので、まとめ表が唯一かつ最良の答えになる。
|
|
8832
|
+
* engine 側の篩は S5(「fleet-status.mjs」 の後始末)で入れる。
|
|
8833
|
+
*/
|
|
8834
|
+
function fleetSyncFallback(internal) {
|
|
8835
|
+
if (LIVE_STATE_SEEN) return "";
|
|
8836
|
+
return fleetLastSyncOf(internal);
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8494
8839
|
function buildDataSources() {
|
|
8495
8840
|
KEY_SLOTS = { backlog: {}, backlogNamed: false, figma: null };
|
|
8496
8841
|
// 鍵カードも受け皿と同じく作り直す(前回の切り離されたカードを掴んだままにしない)
|
|
@@ -8582,6 +8927,22 @@ ${calendarAddUrl.toString()}
|
|
|
8582
8927
|
capBox[cap].push({ node: node, adapter: adapter || "" });
|
|
8583
8928
|
}
|
|
8584
8929
|
|
|
8930
|
+
/**
|
|
8931
|
+
* 能力ブロックの「最終同期」(#523 決定1)。**出どころはストアが先・まとめ表は後詰め。**
|
|
8932
|
+
*
|
|
8933
|
+
* タイルの2行目(tileSub)と同じ材料で、同じ順に引く(「fleetSyncFallback」 が
|
|
8934
|
+
* 「後詰めを使ってよいか」の判定を1か所に持つ)。
|
|
8935
|
+
*
|
|
8936
|
+
* 出せないときは要素ごと出さない(「最終同期: —」のような空の行を置かない)。
|
|
8937
|
+
*/
|
|
8938
|
+
function capBlockSyncEl(s, inactive) {
|
|
8939
|
+
if (inactive || !s || !s.kind) return null;
|
|
8940
|
+
var capId = capIdOf(s.kind);
|
|
8941
|
+
var at = capId ? ingestedAtOf(LIVE_STATE, capId, refKeysOf(derCapOf(s.kind, ""))) : "";
|
|
8942
|
+
var t = dsFmtTime(at || fleetSyncFallback(s) || "");
|
|
8943
|
+
return t ? el("span", { class: "ds-sync", text: "最終同期: " + t }) : null;
|
|
8944
|
+
}
|
|
8945
|
+
|
|
8585
8946
|
if (internals.length) {
|
|
8586
8947
|
// **「いま同期する」ボタンは出さない。** 受け口(Apps Script の Webアプリ)を
|
|
8587
8948
|
// 公開できないため、押しても届かない。
|
|
@@ -8649,9 +9010,11 @@ ${calendarAddUrl.toString()}
|
|
|
8649
9010
|
"data-cap": s.kind || "",
|
|
8650
9011
|
}, [
|
|
8651
9012
|
col,
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
9013
|
+
// 🔴 **出どころはストアが先・まとめ表は後詰め(#523 決定1)。** まとめ表の
|
|
9014
|
+
// 「lastSync」 には run の成功時刻(空振りを含む)が混ざるので、「fleetLastSyncOf」 が
|
|
9015
|
+
// ストア由来のものだけを通す。タイルの「最終同期」と**同じ材料**にする
|
|
9016
|
+
// ——別々に引くと、同じ画面の2箇所が違う時刻を「最終同期」と呼ぶ
|
|
9017
|
+
capBlockSyncEl(s, inactive),
|
|
8655
9018
|
]);
|
|
8656
9019
|
// 会議・共有資料・デザインの操作をブロック内に置く(その情報源の文脈で押せるように)
|
|
8657
9020
|
var ctl = internalControls(s, inactive);
|
|
@@ -9195,10 +9558,20 @@ ${calendarAddUrl.toString()}
|
|
|
9195
9558
|
// 「internalSources.lastSync」 は run 全体の最終成功時刻で、**どの接続先のことか
|
|
9196
9559
|
// 分からない**(鍵が無くて1件も取り込めていない run でも「成功」で終わる)。
|
|
9197
9560
|
// ストアの行は「その能力の接続先が実際に取り込めた事実」なので、同じ「最終同期」の
|
|
9198
|
-
//
|
|
9561
|
+
// 位置に出すならこちらが正しい。
|
|
9562
|
+
//
|
|
9563
|
+
// 🔴 **まとめ表の後詰めは「ストアが読めなかった回」だけ(#523 決定1・「fleetSyncFallback」)。**
|
|
9564
|
+
// run 由来は常に捨て、ストア由来でも**ストアを読めた回は使わない**(engine 側は
|
|
9565
|
+
// まだ登録簿で篩っていないので、孤児行の時刻が混ざりうる)。出せないときは何も
|
|
9566
|
+
// 言わない——2行目の語が「まだ取り込みがありません」と既に答えている。
|
|
9567
|
+
// 孤児行(外した接続先の残骸)の時刻も数えない(決定2・refs を渡す)
|
|
9199
9568
|
if (st !== "off" && st !== "idle") {
|
|
9200
|
-
|
|
9201
|
-
|
|
9569
|
+
// 🔴 能力名は導出と同じ鍵から引く(「capIdOf」 はアダプタを見ないので、
|
|
9570
|
+
// google-chat のタイルで「件数は gc・時刻は chat」という食い違いができる)
|
|
9571
|
+
var derCapForId = derCapOf(cap, adapter);
|
|
9572
|
+
var stateCapId = (TILE_CAPS[derCapForId] && TILE_CAPS[derCapForId].id) || capIdOf(cap);
|
|
9573
|
+
var ingestedAt = ingestedAtOf(LIVE_STATE, stateCapId, refKeysOf(derCapForId));
|
|
9574
|
+
var t = dsFmtTime(ingestedAt || fleetSyncFallback(internal) || "");
|
|
9202
9575
|
if (t) parts.push("最終同期 " + t);
|
|
9203
9576
|
}
|
|
9204
9577
|
return parts.join(" ");
|
|
@@ -10412,6 +10785,87 @@ ${calendarAddUrl.toString()}
|
|
|
10412
10785
|
});
|
|
10413
10786
|
}
|
|
10414
10787
|
|
|
10788
|
+
/**
|
|
10789
|
+
* その接続先の「届くか」の行(接続状態ストア・#523 S1)。**無ければ null。**
|
|
10790
|
+
*
|
|
10791
|
+
* 🔴 **突き合わせは 「stateRefKey」 を通す(両側とも)。** タイルの孤児行の篩と同じ
|
|
10792
|
+
* 畳み方にしておかないと、「タイルは⚠️なのに、どの行にも札が付かない」が起きる
|
|
10793
|
+
* ——どの接続先の話なのかが画面から分からない状態に戻る。
|
|
10794
|
+
*
|
|
10795
|
+
* **行が無ければ何も出さない**(「ingestedEl」 と同じ原則)。測っていないことを
|
|
10796
|
+
* 「読めていない」と書くと、直す必要があると読ませる嘘になる。
|
|
10797
|
+
*/
|
|
10798
|
+
function reachableStateOf(capId, ref, workspace) {
|
|
10799
|
+
var rows = LIVE_STATE;
|
|
10800
|
+
if (!rows || !rows.length || !capId) return null;
|
|
10801
|
+
var want = stateRefKey(capId, ref, workspace);
|
|
10802
|
+
if (!want) return null;
|
|
10803
|
+
for (var i = 0; i < rows.length; i++) {
|
|
10804
|
+
var r = rows[i];
|
|
10805
|
+
if (!r || r.cap !== capId || r.question !== "reachable") continue;
|
|
10806
|
+
if (stateRefKey(capId, r.ref, r.workspace) === want) return r;
|
|
10807
|
+
}
|
|
10808
|
+
return null;
|
|
10809
|
+
}
|
|
10810
|
+
|
|
10811
|
+
/**
|
|
10812
|
+
* 接続先1件の「届くか」の札と「いつ確かめたか」を行に足す(#523 S1・決定2)。
|
|
10813
|
+
* **Slack の行と同じ見た目**(「.ds-gate」 + 「.ds-gate-at」)。
|
|
10814
|
+
*
|
|
10815
|
+
* 🔴 **次の一手・理由(「.ds-gate-help」)はここで足さない。** あれは 「flex: 1 1 100%」 で
|
|
10816
|
+
* 行を占めるので、行の途中に挟むと**ID と「この◯◯を外す」が案内文の下の行へ落ちる**
|
|
10817
|
+
* (Slack の行が 「gateHelpNodes」 をボタンより後ろに置いているのと同じ理由・実装レビュー🟡3)。
|
|
10818
|
+
* 行の組み立ての最後に 「appendReachableReason」 を呼ぶ。
|
|
10819
|
+
*/
|
|
10820
|
+
function appendReachableChip(row, capId, ref, workspace) {
|
|
10821
|
+
var r = reachableStateOf(capId, ref, workspace);
|
|
10822
|
+
if (!r) return row;
|
|
10823
|
+
var gate = dsGateForCap(capId, r.status);
|
|
10824
|
+
row.appendChild(el("span", { class: "ds-gate " + gate.cls, text: gate.text }));
|
|
10825
|
+
var when = rtAgo(r.at);
|
|
10826
|
+
if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when + "に確認" }));
|
|
10827
|
+
return row;
|
|
10828
|
+
}
|
|
10829
|
+
|
|
10830
|
+
/**
|
|
10831
|
+
* 「届くか」の次の一手か理由を、**行の最後に1段だけ**足す(#523 S1・決定2)。
|
|
10832
|
+
*
|
|
10833
|
+
* 🔴 **理由(「detail」)は社内ビューアにしか出ない。** この値の出どころは 「op=state」
|
|
10834
|
+
* (社内の操作タブ)だけで、顧客射影(engine が焼く fleet-status.json)には載らない。
|
|
10835
|
+
* ここで「顧客にも出る」前提の文を書かないこと(決定2)。
|
|
10836
|
+
*
|
|
10837
|
+
* **語そのものが次の一手を示している状態には、理由を重ねない**(「showsRestingReason」 と
|
|
10838
|
+
* 同じ振り分け——「ok」 / 「no_token」 / 「missing_scope」 / 「not_in_channel」)。
|
|
10839
|
+
* 重ねると「管理者の設定が必要です」の下に「(管理者の設定が必要です)」が並ぶ
|
|
10840
|
+
* (実装レビュー🟡4。規約「帯は症状・行は一手・繰り返さない」)。
|
|
10841
|
+
*
|
|
10842
|
+
* **書き手が button の detail は出さない**(「stateReasonOf」 の項——ボタンは判定の語
|
|
10843
|
+
* そのものや生のエラーを detail に書く。人向けの文は needed が持つ)。
|
|
10844
|
+
*/
|
|
10845
|
+
function appendReachableReason(row, capId, ref, workspace) {
|
|
10846
|
+
var r = reachableStateOf(capId, ref, workspace);
|
|
10847
|
+
if (!r) return row;
|
|
10848
|
+
var gate = dsGateForCap(capId, r.status);
|
|
10849
|
+
// 次の一手を持つ語(Drive の共有外れ等)は、その1文だけ
|
|
10850
|
+
if (gate.help || gate.request) {
|
|
10851
|
+
gateHelpNodes(gate, "ds-gate-help").forEach(function (n) { row.appendChild(n); });
|
|
10852
|
+
return row;
|
|
10853
|
+
}
|
|
10854
|
+
if (r.status === "ok" || r.status === "no_token" || r.status === "missing_scope" || r.status === "not_in_channel") return row;
|
|
10855
|
+
var reason = stateReasonOf({ needed: r.needed, detail: r.detail, gateBy: r.by });
|
|
10856
|
+
// needed は Slack の行と同じ部品を通す(URL がリンクになる・スコープ名がラベル化される)
|
|
10857
|
+
if (reason) row.appendChild(r.needed ? neededNode(reason, "ds-gate-help") : el("p", { class: "ds-gate-help", text: reason }));
|
|
10858
|
+
return row;
|
|
10859
|
+
}
|
|
10860
|
+
|
|
10861
|
+
/** 一覧に出ている接続先の「届くか」をひとつの文字列にする(描き直しの要否の判定用) */
|
|
10862
|
+
function reachSigOf(capId, refs) {
|
|
10863
|
+
return (refs || []).map(function (ref) {
|
|
10864
|
+
var r = reachableStateOf(capId, ref && ref.ref != null ? ref.ref : ref, ref && ref.workspace);
|
|
10865
|
+
return r ? r.status + "@" + r.at : "-";
|
|
10866
|
+
}).join(",");
|
|
10867
|
+
}
|
|
10868
|
+
|
|
10415
10869
|
/**
|
|
10416
10870
|
* いま一覧に出ている接続先の「取り込み」をひとつの文字列にする(差し替えの要否の判定用)。
|
|
10417
10871
|
*
|
|
@@ -10428,9 +10882,13 @@ ${calendarAddUrl.toString()}
|
|
|
10428
10882
|
return r ? r.status + "@" + r.at : "-";
|
|
10429
10883
|
}).join(",");
|
|
10430
10884
|
}
|
|
10431
|
-
/**
|
|
10885
|
+
/**
|
|
10886
|
+
* 取り直しで描き直すかの署名: 取り込みの状態+仮置きの印(#388・印だけ残さないため)
|
|
10887
|
+
* +**「届くか」の状態**(#523 S1——混ぜないと、ストアの reachable が届いても札が付かない)
|
|
10888
|
+
*/
|
|
10432
10889
|
function backlogPaintSigOf(list) {
|
|
10433
|
-
return ingestSigOf(list) + "|" + (list || []).map(function (t) { return t && t.pending === true ? "p" : "-"; }).join(",")
|
|
10890
|
+
return ingestSigOf(list) + "|" + (list || []).map(function (t) { return t && t.pending === true ? "p" : "-"; }).join(",")
|
|
10891
|
+
+ "|" + reachSigOf("issues", (list || []).map(function (t) { return issuesRefOf(t.domain, t.projectKey); }));
|
|
10434
10892
|
}
|
|
10435
10893
|
|
|
10436
10894
|
/**
|
|
@@ -10471,7 +10929,9 @@ ${calendarAddUrl.toString()}
|
|
|
10471
10929
|
return (list || []).map(function (f) {
|
|
10472
10930
|
var r = designIngestedStateOf(f && f.key);
|
|
10473
10931
|
return r ? r.status + "@" + r.at : "-";
|
|
10474
|
-
}).join(",")
|
|
10932
|
+
}).join(",")
|
|
10933
|
+
// 「届くか」も混ぜる(#523 S1。混ぜないと、ストアの reachable が届いても札が付かない)
|
|
10934
|
+
+ "|" + reachSigOf("design", (list || []).map(function (f) { return f && f.key; }));
|
|
10475
10935
|
}
|
|
10476
10936
|
|
|
10477
10937
|
/**
|
|
@@ -10520,7 +10980,12 @@ ${calendarAddUrl.toString()}
|
|
|
10520
10980
|
// この接続先の取り込みが最後にどうなったか(段C)。測っていなければ null
|
|
10521
10981
|
ingestedEl(ingestedStateOf(t.domain, t.projectKey), "課題管理"),
|
|
10522
10982
|
]);
|
|
10523
|
-
|
|
10983
|
+
// この接続先に届くか(#523 S1)。**「取り込み」とは別の問い**なので併記する
|
|
10984
|
+
// ——鍵はあるが 403 でプロジェクトが見えない、は取り込みの失敗より前の話。
|
|
10985
|
+
// 理由の1段は**行の最後**(ボタンより後ろ)に置く(appendReachableReason の項)
|
|
10986
|
+
var issuesRef = issuesRefOf(t.domain, t.projectKey);
|
|
10987
|
+
appendReachableChip(row, "issues", issuesRef);
|
|
10988
|
+
if (!editable) return appendReachableReason(row, "issues", issuesRef);
|
|
10524
10989
|
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
|
|
10525
10990
|
btn.addEventListener("click", async function () {
|
|
10526
10991
|
// **最後の1件かどうかは「押した時点」で数える。** 描画時の件数で決めると、
|
|
@@ -10540,7 +11005,7 @@ ${calendarAddUrl.toString()}
|
|
|
10540
11005
|
sendConfig(payload, msg, btn, null, targetsHooks());
|
|
10541
11006
|
});
|
|
10542
11007
|
row.appendChild(btn);
|
|
10543
|
-
return row;
|
|
11008
|
+
return appendReachableReason(row, "issues", issuesRef);
|
|
10544
11009
|
}
|
|
10545
11010
|
|
|
10546
11011
|
/**
|
|
@@ -10956,7 +11421,9 @@ ${calendarAddUrl.toString()}
|
|
|
10956
11421
|
var d = driveNow();
|
|
10957
11422
|
// 仮置きの印(drivePending)も署名に入れる——外れたときに印だけ残さない(#388)
|
|
10958
11423
|
// 「確かめられたか」も混ぜる(#409。混ぜないと注記の要否が変わっても作り直されない)
|
|
10959
|
-
|
|
11424
|
+
// 「届くか」も混ぜる(#523 S1。混ぜないと、ストアの reachable が届いても札が付かない)
|
|
11425
|
+
return JSON.stringify([d.driveFolderIds || [], d.driveFolderNames || {}, d.driveState || "", d.drivePending || {}, registrationUnreadable(),
|
|
11426
|
+
reachSigOf("materials", d.driveFolderIds || [])]);
|
|
10960
11427
|
}, function (listBox) {
|
|
10961
11428
|
var src = driveNow();
|
|
10962
11429
|
var folders = src.driveFolderIds || [];
|
|
@@ -10984,6 +11451,10 @@ ${calendarAddUrl.toString()}
|
|
|
10984
11451
|
? { pending: true, pendingAt: src.drivePending[id] }
|
|
10985
11452
|
: null),
|
|
10986
11453
|
]);
|
|
11454
|
+
// このフォルダに届くか(#523 S1)。engine の同期ジョブが毎回書いているのに
|
|
11455
|
+
// **これまで画面に出す場所が1つも無かった**(共有が外れたことを、取り込みが
|
|
11456
|
+
// 失敗するまで言えなかった)
|
|
11457
|
+
appendReachableChip(row, "materials", id);
|
|
10987
11458
|
// **名前を出したときはIDも併記する。** 同名のフォルダを見分けられなくなるのと、
|
|
10988
11459
|
// 「このIDを外す」という確認文と画面の表示が食い違うのを防ぐ
|
|
10989
11460
|
if (fname) row.appendChild(el("span", { class: "ds-sync", text: id }));
|
|
@@ -11006,6 +11477,8 @@ ${calendarAddUrl.toString()}
|
|
|
11006
11477
|
// **クラスのハード代入をやめる**(片方だけ直し忘れる形を構造的に消す)
|
|
11007
11478
|
onDone: function () { row.classList.add("ds-removed"); },
|
|
11008
11479
|
}));
|
|
11480
|
+
// 理由の1段は行の最後(ID と「このフォルダを外す」の後ろ・appendReachableReason の項)
|
|
11481
|
+
appendReachableReason(row, "materials", id);
|
|
11009
11482
|
dlist.appendChild(row);
|
|
11010
11483
|
});
|
|
11011
11484
|
listBox.appendChild(dlist);
|
|
@@ -11134,6 +11607,9 @@ ${calendarAddUrl.toString()}
|
|
|
11134
11607
|
// このファイルの取り込みが最後にどうなったか(段C)。測っていなければ null
|
|
11135
11608
|
ingestedEl(designIngestedStateOf(f.key), "デザイン"),
|
|
11136
11609
|
]);
|
|
11610
|
+
// このファイルに届くか(#523 S1)。書き手は S4(いまは行が無いので何も出ない)。
|
|
11611
|
+
// 理由の1段は行の最後に置く(appendReachableReason の項)
|
|
11612
|
+
appendReachableChip(row, "design", f.key);
|
|
11137
11613
|
row.appendChild(removeButton({
|
|
11138
11614
|
label: "このファイルを外す",
|
|
11139
11615
|
confirmLines: function () {
|
|
@@ -11167,6 +11643,7 @@ ${calendarAddUrl.toString()}
|
|
|
11167
11643
|
else delete figSending[f.key];
|
|
11168
11644
|
},
|
|
11169
11645
|
}));
|
|
11646
|
+
appendReachableReason(row, "design", f.key);
|
|
11170
11647
|
// 描き直しのあとも「外した」ことが分かる形に戻す(figsRemoved の項に経緯)
|
|
11171
11648
|
if (figsRemoved.indexOf(f.key) >= 0) {
|
|
11172
11649
|
row.classList.add("ds-removed");
|
|
@@ -13343,7 +13820,8 @@ ${calendarAddUrl.toString()}
|
|
|
13343
13820
|
sync.removeAttribute("data-ts");
|
|
13344
13821
|
// 接続OK のときだけ、ストア由来の取り込み時刻を添える(run の時刻ではない)。
|
|
13345
13822
|
// 出せなければ語だけ——**繋がっていないものに同期時刻を並べない**(タイルと同じ規律)
|
|
13346
|
-
|
|
13823
|
+
// 孤児行(外した接続先の残骸)の時刻は出さない(#523 決定2・タイルと同じ篩)
|
|
13824
|
+
var at = d.state === "ok" ? dsFmtTime(ingestedAtOf(LIVE_STATE, TILE_CAPS[cap].id, refKeysOf(cap))) : "";
|
|
13347
13825
|
sync.textContent = at ? "最終同期 " + at : d.text;
|
|
13348
13826
|
}
|
|
13349
13827
|
var link = wrap.querySelector('.cm-link[data-cm-cap="' + cap + '"]');
|
|
@@ -13461,6 +13939,11 @@ ${calendarAddUrl.toString()}
|
|
|
13461
13939
|
* 取れているときは図だけが古い。同じ画面で図が⚠️・直下が「接続OK」になる状態を
|
|
13462
13940
|
* 黙って出さないよう、図がいつ時点かを明示する(図の再描画は別途)。
|
|
13463
13941
|
* ライブが取れていないときは図と一覧が同じ時点なので、注記は出さない。
|
|
13942
|
+
*
|
|
13943
|
+
* 🔴 **「この図は◯時点」とは言わない(#523 決定5)。** ライブが届くと図の**状態**は
|
|
13944
|
+
* 当て直される(「applyStateToConnMap」)ので、古いのは**設定と件数だけ**。図ぜんたいを
|
|
13945
|
+
* 古いと言うと、訂正済みの状態まで疑わせる(逆に「最新」と言えば設定の古さが隠れる)。
|
|
13946
|
+
* 何が古くて何が最新かを、そのまま書く。
|
|
13464
13947
|
*/
|
|
13465
13948
|
function updateConnMapAsOf() {
|
|
13466
13949
|
var wrap = app.querySelector(".conn-map");
|
|
@@ -13470,7 +13953,7 @@ ${calendarAddUrl.toString()}
|
|
|
13470
13953
|
if (!LIVE_FLEET || !SITE.fleetGeneratedAt) return;
|
|
13471
13954
|
var mapAsOf = dsFmtTime(SITE.fleetGeneratedAt);
|
|
13472
13955
|
if (!mapAsOf) return;
|
|
13473
|
-
wrap.appendChild(el("p", { class: "conn-map-asof", text: "
|
|
13956
|
+
wrap.appendChild(el("p", { class: "conn-map-asof", text: "設定と件数は " + mapAsOf + " 時点・接続状態は最新です。" }));
|
|
13474
13957
|
}
|
|
13475
13958
|
function opsCard(icon, title, desc, kind, btnLabel) {
|
|
13476
13959
|
var btn = el("button", { class: "ops-card-btn", text: "+ " + btnLabel });
|
package/package.json
CHANGED