@takagaki/cortex-decisions-viewer 0.12.129 → 0.12.130

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.
Files changed (2) hide show
  1. package/dist/render.js +145 -355
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -10317,7 +10317,7 @@ ${calendarAddUrl.toString()}
10317
10317
  *
10318
10318
  * **ここを通さずに語を書き写さないこと。** 行バッジ・能力タイルの要約・
10319
10319
  * 「接続を確認」の結果が同じ関数を通るので、片方だけ古くなることが構造的に起きない
10320
- * (attentionSummary / probeVerdict の項に、実際に割れた経緯がある)。
10320
+ * (attentionSummary の項に、実際に割れた経緯がある)。
10321
10321
  */
10322
10322
  function dsGateOf(s) {
10323
10323
  if (s.gate === "no_token") {
@@ -11197,7 +11197,7 @@ ${calendarAddUrl.toString()}
11197
11197
  var again = !!(x.ref && seenRef[x.ref]);
11198
11198
  if (x.ref) seenRef[x.ref] = 1;
11199
11199
  var rowCard = extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again });
11200
- // 同じリポの issues / discussions を、接続確認の結果で揃えるための目印(syncGithubSiblingRows)
11200
+ // 同じリポの issues / discussions の行の目印(同じ dev 行から描くので、判定は 1 つの描き直しで揃う)
11201
11201
  if (rowCard && rowCard.setAttribute && x.ref) rowCard.setAttribute("data-ref", x.ref);
11202
11202
  return rowCard;
11203
11203
  });
@@ -11217,28 +11217,6 @@ ${calendarAddUrl.toString()}
11217
11217
  return out;
11218
11218
  }
11219
11219
 
11220
- /**
11221
- * 「接続を確認」の結果を、同じリポジトリの兄弟の行(issues / discussions)へそろえる。
11222
- *
11223
- * 帯は撤去した(#1233)。残すのは「行は 2 本でも接続は 1 つ」なので押した行と同じ判定に
11224
- * そろえる仕事だけ。**条件なしで毎回そろえる**(帯の有無・状態の変化に依らない)。
11225
- * 兄弟は同じ親(一覧・確かめられなかった組織の箱)の直下にいる。
11226
- */
11227
- function syncGithubSiblingRows(card, ref, ok) {
11228
- var parent = card.parentNode;
11229
- if (!parent || !ref) return;
11230
- var gateSrc = card.querySelector(".ds-gate");
11231
- var sibs = parent.querySelectorAll(":scope > .ds-card[data-ref]");
11232
- for (var si = 0; gateSrc && si < sibs.length; si++) {
11233
- var sib = sibs[si];
11234
- if (sib === card || sib.getAttribute("data-ref") !== ref) continue;
11235
- var sg = sib.querySelector(".ds-gate");
11236
- if (sg) { sg.className = gateSrc.className; sg.textContent = gateSrc.textContent; }
11237
- var sh = sib.querySelectorAll(".ds-gate-help");
11238
- for (var hi = 0; hi < sh.length; hi++) sh[hi].hidden = ok;
11239
- }
11240
- }
11241
-
11242
11220
  /** 確かめられなかった組織の案内。**導線は出さない** */
11243
11221
  function githubUnsureNodes(g) {
11244
11222
  var o = g.meta || {};
@@ -11337,6 +11315,15 @@ ${calendarAddUrl.toString()}
11337
11315
  if (min < 60 * 24) return Math.floor(min / 60) + "時間前";
11338
11316
  return dsFmtTime(iso);
11339
11317
  }
11318
+ /**
11319
+ * 「◯分前に確認」の語。**「たった今」だけは「たった今確認」**(「たった今に確認」は日本語として崩れる)。
11320
+ * 読めない値は空文字(呼び側が出し分けを切る)。行・タイル・押した直後の行は全部これを通す
11321
+ */
11322
+ function agoConfirmedText(iso) {
11323
+ var ago = rtAgo(iso);
11324
+ if (!ago) return "";
11325
+ return ago === "たった今" ? "たった今確認" : ago + "に確認";
11326
+ }
11340
11327
  // 名前表示: url があれば新しいタブで正本を開くリンクにする
11341
11328
  function dsNameEl(text, url) {
11342
11329
  if (url) return el("a", { class: "ds-name", href: url, target: "_blank", rel: "noopener", text: text });
@@ -11857,8 +11844,8 @@ ${calendarAddUrl.toString()}
11857
11844
  var v = derivePendingTile(regKey, live, LIVE_STATE);
11858
11845
  if (!v) return null;
11859
11846
  if (v.kind === "ok") {
11860
- var ago = rtAgo(v.at);
11861
- return { text: CAP_STATE_MARK.ok + "接続OK" + (ago ? " " + ago + "に確認" : ""), cls: "ds-gate-ok" };
11847
+ var ago = agoConfirmedText(v.at);
11848
+ return { text: CAP_STATE_MARK.ok + "接続OK" + (ago ? " " + ago : ""), cls: "ds-gate-ok" };
11862
11849
  }
11863
11850
  var first = v.rows[0];
11864
11851
  var gate;
@@ -13908,34 +13895,26 @@ ${calendarAddUrl.toString()}
13908
13895
  * DS_GATE_NO_TOKEN が持っていて、どちらも dsGateOf を通して取る。ここで文面を
13909
13896
  * 書き写すと、**同じ行の状態バッジと確認結果で語が割れる**(この節で3回直した形)。
13910
13897
  *
13898
+ * **結果の描画は行の関数 1 本(cortex-engine #1328)。** 押した結果は applyProbeResult が
13899
+ * ストアの行と同じ形で LIVE_STATE に置き、行を描き直す。押下の処理は札・時刻・説明文を
13900
+ * 自分で描かない(以前の probeHelpNodes / probeVerdict は撤去した。開き直した行と形が割れ、
13901
+ * 押した直後に 2 回見た目が変わっていた)。
13902
+ *
13911
13903
  * 他アダプタ(Backlog / Figma / Drive)を足すときの拡張点:
13912
13904
  * - 判定と語彙は infra/lambda/shared/slack-gate.mjs 側に足す(画面は DS_GATE のまま)
13913
- * - この節に増やすのは probeHelpNodes の「次の一手」だけ。**アダプタ固有の分岐を
13914
- * probeVerdict に入れない**(入れた瞬間に判定が画面側にも生まれる)
13915
- * - 送る種別(probeTypeOf)と、行に出す条件(下の extSourceCard)に足す
13905
+ * - **次の一手は DS_GATE の help(行の常時表示と同じ表)に足す。**この節に判定の分岐を作らない
13906
+ * - 送る種別(probeTypeOf)・ストアの能力名(probeCapOfType)と、行に出す条件
13907
+ * (下の extSourceCard)に足す
13916
13908
  *
13917
13909
  * **開発(GitHub)は #234 で足した。** 判定は shared/github-gate.mjs、次の一手は
13918
13910
  * サーバの needed(理由と設定ページのURLを1文で持つ)をそのまま出している
13919
13911
  * ——画面に分岐が生まれないよう、言い分けを全部あちらに置いた
13920
13912
  *
13921
13913
  * **Google Chat は #347 で足した。** 判定は shared/google-chat-gate.mjs で、夜間の実測と
13922
- * 同じ叩き先・同じスコープで測る。次の一手(googleChatProbeHelpNodes)は行の常時表示と
13914
+ * 同じ叩き先・同じスコープで測る。次の一手は行の常時表示と
13923
13915
  * 同じ表(DS_GATE の help)から引くだけで、画面に判定の分岐は無い
13924
13916
  * ------------------------------------------------------------------------- */
13925
13917
 
13926
- /**
13927
- * 確認結果の状態バッジ。**既存の dsGateOf をそのまま通す。**
13928
- *
13929
- * こうしておくと no_token の社内/顧客の出し分け(DS_GATE_NO_TOKEN)が確認結果にも
13930
- * そのまま効く。写経すると、同じ画面で行は「管理者の設定が必要です」・確認結果は
13931
- * 「トークン未設定」になり、社内チャンネルを登録した人が**また**顧客ワークスペースの
13932
- * 接続へ吸い込まれる(2026-08-18 の不具合そのもの)。
13933
- */
13934
- function probeVerdict(s, r) {
13935
- // reason は GitHub のテナント外の断り(#841)。無ければ従来どおり
13936
- return dsGateOf({ type: s.type, workspace: s.workspace, gate: r.gate, reason: r.reason });
13937
- }
13938
-
13939
13918
  /**
13940
13919
  * 確認したいアダプタの種別(「op=probe」 の 「type」)。**画面の種別をそのまま送らない**
13941
13920
  * ——「github-issues」 / 「github-discussions」 は同じ1つのリポジトリの読み取りで、
@@ -13973,179 +13952,6 @@ ${calendarAddUrl.toString()}
13973
13952
  return p;
13974
13953
  }
13975
13954
 
13976
- /**
13977
- * 開発(GitHub)の確認結果の本文(#233 / #234)。
13978
- *
13979
- * **理由と対処はサーバが1文で持っている**(「shared/github-gate.mjs」 の 「needed」)。
13980
- * 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
13981
- * 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
13982
- * (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
13983
- * **ただし状態に応じた定型の1文だけは画面の表(DS_GATE_RESTING_HELP の probed)から引く**
13984
- * (#346 → #356)——「request は組織オーナーの承認待ち」のような GitHub 側の意味は、
13985
- * サーバの2つの書き手が別々に文を作るので、片方だけ古くなる。
13986
- *
13987
- * 🔴 **行の常時表示(resting)を押した結果に繰り返さない(#356)。** 0.12.22 では admin の
13988
- * 4文が行と押した結果の両方に出ていた。行の1文は押した瞬間に隠れ、needed が同じ操作
13989
- * (Configure から追加)を宛先つきで言うので、ここは needed が言わない1文だけ
13990
- */
13991
- function githubProbeHelpNodes(r) {
13992
- if (r.gate === "ok") return [];
13993
- if (r.needed) {
13994
- var nodes = [neededNode(r.needed)];
13995
- // 状態に応じた1文は**行と同じ表から引く**(ここに文を直書きすると表と割れる)
13996
- var probed = probedHelpPartsOf("github", r.gate);
13997
- if (probed) nodes.push(restingHelpNode(probed, "ds-probe-help"));
13998
- return nodes;
13999
- }
14000
- // needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
14001
- return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
14002
- }
14003
-
14004
- /**
14005
- * Google Chat の確認結果の本文(#347)。
14006
- *
14007
- * **判定は infra/lambda/shared/google-chat-gate.mjs にしか無い。** ここが返すのは
14008
- * 「次の一手」だけで、状態の語(バッジ)は probeVerdict が dsGateOf から取る。
14009
- *
14010
- * **文を書き写さない。** 「アプリと統合から追加してください」「管理者の設定が済むと…」は
14011
- * 行の常時表示(DS_GATE の 「help」)が既に持っている——写経すると、同じ行の常時表示と
14012
- * 押した結果で語が割れる(この節が何度も直してきた形)。DS_GATE から引いて1文だけ出す。
14013
- *
14014
- * ⚠ **403(Workspace 管理者の承認待ち)は #521 で「admin_approval_pending」に分類済み。**
14015
- * 機械語(「permission_denied」)はもう出さない。**「時間をおいて」とも言わない**
14016
- * ——待っても直らない(管理者が承認するまで同じ応答が返る)。401 / 429 は依然
14017
- * Phase 0 未採取で「unknown」のまま、サーバの機械語をそのまま出す。
14018
- */
14019
- function googleChatProbeHelpNodes(s, r) {
14020
- // 成功。**1行で終わり**(状態の語が既に「接続OK」と出ている)
14021
- if (r.gate === "ok") return [];
14022
- // not_in_space / no_credentials / admin_approval_pending は行の常時表示と同じ文面
14023
- // (DS_GATE から引く。依頼文の details も gateHelpNodes 経由で同じものが出る)。
14024
- // 押した瞬間に行側の 「.ds-gate-help」 は隠れるので、二重には出ない
14025
- var gate = dsGateOf({ type: s.type, gate: r.gate });
14026
- var nodes = gateHelpNodes(gate, "ds-probe-help");
14027
- if (nodes.length) return nodes;
14028
- var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
14029
- if (r.gate === "unreachable") {
14030
- // 帯(「アクセス不可」)が症状を言っている。**ここは一手だけ**
14031
- return [p("時間をおいて、もう一度「接続を確認」を押してください。")];
14032
- }
14033
- // unknown。**生の機械語を隠さない**——「不明」だけだと切り分けが始められない
14034
- return [p("Google Chat の応答:" + (r.error || "不明") + "。cortex の担当者にこの語をお伝えください。")];
14035
- }
14036
-
14037
- /**
14038
- * 「アクセス不可」の次の一手(0.12.2・実オンボーディングで踏んだ)。
14039
- *
14040
- * ## なぜ2つの導線を併記するのか
14041
- *
14042
- * それまでは「見つからないか、アーカイブされています。不要なら登録を解除」だけを
14043
- * 出していた。**この行がまさに、まだ読めていない主役の行**なのに、
14044
- * 生きているチャンネルを読めるようにする道が1本も書かれていなかった。
14045
- *
14046
- * 原因は**行の分類では絞れない**:
14047
- *
14048
- * - 非公開チャンネルは、招待されるまで「存在しない」と返る(招待で直る)
14049
- * - Slack コネクトの共有チャンネルは、**URL がどちらの組織のドメインにもなりうる**。
14050
- * だから「クラスメソッド」に分類された行の正しい対処が
14051
- * **ワークスペース接続(OAuth)** のこともある
14052
- * (実測: 招待しようとしたら「組織外のユーザー」で断られた)
14053
- *
14054
- * **どちらかに賭けて片方だけ出すと、外したほうの人は詰まる。** 招待だけを出すと
14055
- * 何度招待しても直らないものを繰り返させ、接続だけを出すと招待で済む人に
14056
- * 「直らない処方」を渡す。**両方出して、読む人に選ばせる**のが正しい
14057
- * (組織不明の案内が同じ判断をしている)。
14058
- *
14059
- * ## 社内/顧客で出し分けない
14060
- *
14061
- * no_token は出し分けている(社内は画面から設定できるものが本当に無い)が、
14062
- * こちらは**分類そのものが当てにならない**のが問題の本体なので、
14063
- * 同じ理屈を持ち込むと直したい誤読を画面の側から作り直すことになる。
14064
- */
14065
- function slackUnreachableHelpNodes() {
14066
- var out = [];
14067
- out.push(el("p", { class: "ds-probe-help", text: "Cortex からこのチャンネルが見えませんでした。次のどれかです。" }));
14068
- var ul = el("ul", { class: "ds-probe-help ds-probe-why" });
14069
- // **押したあと何をするかまで書く。** 直したあと表示が変わらないと「効かなかった」と
14070
- // 読まれる——この画面の一覧は夜間のスナップショットなので、その場で確かめる手段は
14071
- // 「接続を確認」をもう一度押すこと以外に無い
14072
- ul.appendChild(el("li", {
14073
- text: "非公開チャンネル … 下のコマンドをそのチャンネルに貼って Cortex を招待してから、もう一度「接続を確認」を押してください",
14074
- }));
14075
- // 🔴 **指す先の常設ボタンは撤去した(c-07)ので、同じ段にインラインの[接続する]を置く**
14076
- var connectLi = el("li", {
14077
- text: "お客様の Slack、または Slack コネクトで共有されたチャンネル … "
14078
- + "[接続する]から、そのワークスペースに Cortex を追加するリクエストを送ってください。"
14079
- + "接続できたら、もう一度「接続を確認」を押してください",
14080
- });
14081
- var connectBtn = actNode("connect");
14082
- if (connectBtn) connectLi.appendChild(connectBtn);
14083
- ul.appendChild(connectLi);
14084
- ul.appendChild(el("li", {
14085
- text: "アーカイブ・削除された、URL が違う … 「登録を解除」を押してください",
14086
- }));
14087
- out.push(ul);
14088
- // **手で打たせない**(copyRow のコメント)。招待の候補を出したなら、
14089
- // そのコマンドも同じ場所に無いと、貼る文字列を探しに行くことになる
14090
- out.push(copyRow("/invite @Cortex"));
14091
- return out;
14092
- }
14093
-
14094
- /**
14095
- * 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
14096
- *
14097
- * ## 短くした理由(2026-08-19・オーナー指示)
14098
- *
14099
- * 「接続を確認を押して出てくる文言が長ったらしい」。実際、成功時に4行出ていた。
14100
- * **成功時は「読めている」以上の情報が要らない。**
14101
- *
14102
- * ## ただし失敗時は削りすぎない
14103
- *
14104
- * 短くしすぎると、今日と同じ「画面を見ても次に何をすればいいか決まらない」状態に戻る
14105
- * (それで半日かかった)。落とすのは**内部の仕組みの説明**——「設定ファイルの表示名だけが
14106
- * 書き換わります」「読み取りの対象・範囲は変わりません」のような、読む人の次の行動を
14107
- * 変えない文。残すのは「何が起きているか」と「次に何をするか」だけ。
14108
- *
14109
- * 状態の語そのものは DS_GATE / DS_GATE_NO_TOKEN が持つ(probeVerdict 経由)ので、
14110
- * ここが返すのは**それに足りない分だけ**。二重に言わない。
14111
- *
14112
- * @returns 本文に並べるノード(0〜2個)
14113
- */
14114
- function probeHelpNodes(s, r) {
14115
- var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
14116
- if (probeTypeOf(s.type) === "github") return githubProbeHelpNodes(r);
14117
- if (probeTypeOf(s.type) === "google-chat") return googleChatProbeHelpNodes(s, r);
14118
- // 成功。**1行で終わり。** 状態の語(「接続OK」)が既に出ているので本文は空
14119
- if (r.gate === "ok") return [];
14120
- if (r.gate === "not_in_channel") {
14121
- // 状態の語が「Slackで招待してください」なので、説明は足さずコマンドだけ出す。
14122
- // **手で打たせない**——打ち間違いは黙って失敗し、「招待したのに読まれない」に見える
14123
- return [copyRow("/invite @Cortex")];
14124
- }
14125
- if (r.gate === "missing_scope") {
14126
- // 不足スコープは頭のバッジ横に出る(probeSection)。ここは次の一手だけ
14127
- return [p("招待では直りません。cortex の担当者にこのスコープ名をお伝えください。")];
14128
- }
14129
- if (r.gate === "no_token") {
14130
- // **社内/顧客の出し分けは変えない。** 社内は画面から設定できるものが無いので
14131
- // 連絡先、顧客は接続へ誘導する(2026-08-18 の不具合の再発防止)。
14132
- // 長い説明は落とし、次の行動の1行だけ残す
14133
- if (isInternalSlack(s)) return [p("cortex の担当者にご連絡ください。")];
14134
- // 常設のボタンは撤去した(c-07)。同じ段にインラインの[接続する]を置く
14135
- var connectP = p("この Slack を Cortex に接続してください。");
14136
- var connectNow = actNode("connect");
14137
- if (connectNow) connectP.appendChild(connectNow);
14138
- return [connectP];
14139
- }
14140
- if (r.gate === "unreachable") {
14141
- // **ここだけ本文が2つ以上になる(0.12.2)。** 原因が3つあり、しかも
14142
- // 行の分類では絞れないので、候補と導線を並べる(関数側に理由がある)
14143
- return slackUnreachableHelpNodes();
14144
- }
14145
- // unknown。**生のエラーは隠さない**——「不明」だけだと切り分けが始められない
14146
- return [p("Slack の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。")];
14147
- }
14148
-
14149
13955
  /**
14150
13956
  * 表示名を Slack の実名へ**自動で揃える**(2026-08-19・オーナー判断)。
14151
13957
  *
@@ -14188,6 +13994,9 @@ ${calendarAddUrl.toString()}
14188
13994
  // 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
14189
13995
  var nameEl = card.querySelector(".ds-name");
14190
13996
  if (nameEl) nameEl.textContent = slackDisplayName(r.name);
13997
+ // 押した結果で行が描き直されていると、上の card は切り離されている。仮置き(notePendingOp)から描き直す
13998
+ repaintOpenLists();
13999
+ refreshDataSources();
14191
14000
  }).catch(function () { /* 揃えられなくても疎通確認の結果は出したまま */ });
14192
14001
  }
14193
14002
 
@@ -14214,32 +14023,25 @@ ${calendarAddUrl.toString()}
14214
14023
  *
14215
14024
  * @returns { btn, detail } btn は行に、detail は行の下に置く
14216
14025
  */
14217
- function probeSection(s, card, gateEl) {
14026
+ function probeSection(s, card) {
14218
14027
  var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
14219
14028
  var wrap = el("div", { class: "ds-probe" });
14220
14029
  var head = el("div", { class: "ds-probe-head" });
14030
+ // 失敗の 1 文だけをここへ出す。**結果(判定・時刻・次の一手)は行そのものが持つ**
14221
14031
  var msg = el("span", { class: "ds-toggle-msg" });
14222
- var at = el("span", { class: "ds-probe-at" });
14223
- var body = el("div");
14224
14032
  head.appendChild(msg);
14225
- head.appendChild(at);
14226
14033
  wrap.appendChild(head);
14227
- wrap.appendChild(body);
14228
14034
 
14229
14035
  btn.addEventListener("click", async function () {
14230
14036
  var intake = SITE.intake;
14231
14037
  if (!intake) return;
14232
- // 押している間はボタンごと「確認中…」にする(#222・beginBusy の項)。
14233
- // **語は下の ds-toggle-msg と揃える**——同じ操作を2つの語で言わない
14038
+ // 押している間は**ボタンだけ**が「確認中…」になる(#222・beginBusy の項)。
14039
+ // 🔴 行の中身は変えない——ここで札・時刻・説明文を別の形で描くと、結果が出たあとの
14040
+ // 行(開き直したときと同じ形)と 2 回見た目が変わる(cortex-engine #1328 のちらつき)
14234
14041
  var endBusy = beginBusy(btn, "確認中…");
14235
- // **押されて初めて枠を出す(2026-08-20・オーナー指摘)。**
14236
- // それまでは中身が空でも点線だけが全行に出ており、**何も意味していなかった**。
14237
- // 押した結果があるときだけ、行と結果を分ける線になる
14238
- wrap.className = "ds-probe is-shown";
14042
+ wrap.className = "ds-probe";
14239
14043
  msg.className = "ds-toggle-msg";
14240
- msg.textContent = "確認中…";
14241
- at.textContent = "";
14242
- body.textContent = "";
14044
+ msg.textContent = "";
14243
14045
  // **種別はサーバの語彙に畳む**(github-issues / github-discussions → github)。
14244
14046
  // ワークスペースは Slack にしか無い概念なので、他アダプタでは載せない
14245
14047
  var payload = { type: probeTypeOf(s.type), ref: s.ref };
@@ -14249,69 +14051,31 @@ ${calendarAddUrl.toString()}
14249
14051
  var j = await res.json().catch(function () { return {}; });
14250
14052
  if (!res.ok) {
14251
14053
  // サーバの文言をそのまま出す(回数制限の案内・未対応のアダプタの説明を含んでいる)
14054
+ wrap.className = "ds-probe is-shown";
14252
14055
  msg.className = "ds-toggle-msg is-error";
14253
14056
  msg.textContent = "✗ " + (j.message || "確認できませんでした。");
14254
14057
  endBusy();
14255
14058
  return;
14256
14059
  }
14257
- msg.className = "ds-toggle-msg" + (j.gate === "ok" ? " is-ok" : " is-error");
14258
- msg.textContent = j.gate === "ok" ? "✓" : "✗";
14259
- var v = probeVerdict(s, j);
14260
- gateEl.className = "ds-gate " + v.cls;
14261
- gateEl.textContent = v.text;
14262
- // 🔴 **「活動を記録中」の札も、押した結果で出し入れする(#660)。** ここを忘れると、
14263
- // アクセス不可の行を直して押したときに**行は「接続OK」なのに札だけ消えたまま**に
14264
- // なり、「決定の材料になっている」と読める(逆向きも同じで、届かなくなった行が
14265
- // 「記録中」と言い続ける)。**判定は 「devExcludedBadgeVisible」 1 本**——
14266
- // 描画時と押した後で別の条件を書くと、同じ行が 2 つのことを言う
14267
- var exBadgeNow = card.querySelector(".badge.ds-not-ingested");
14268
- if (exBadgeNow) {
14269
- exBadgeNow.hidden = !devExcludedBadgeVisible({
14270
- ingestExcluded: s.ingestExcluded, goldState: s.goldState, gate: j.gate,
14271
- });
14272
- }
14273
- // 古い「X分前に確認」を残さない(下に出るいまの結果と、時刻が2つ並ぶ)
14274
- var oldAt = card.querySelector(".ds-gate-at");
14275
- if (oldAt) oldAt.remove();
14276
- // 古い run の導線も残さない(#560)。「いま測った結果」の横に夜間の run のログが並ぶ形になる
14277
- removeRunTrail(card);
14278
- // 🔴 **古いスコープ名の札も必ず消してから出し直す。** 行には夜間の判定に添えた
14279
- // 札が出ていることがあり(段B)、置き換えた「接続OK」の横に古いスコープ名が
14280
- // 残ると、同じ行が2つのことを言う——この節が何度も直してきた形そのもの。
14281
- // 押し直すたびに札が増えていくのも、まとめてここで断つ
14282
- var oldScopes = card.querySelectorAll(".ds-probe-scope");
14283
- for (var oi = 0; oi < oldScopes.length; oi++) oldScopes[oi].remove();
14284
- // 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
14285
- // 本文へ落とすと、いちばん要る情報が説明文に埋もれる。
14286
- // ⚠ **札にするのは Slack のスコープ名だけ。** GitHub の needed は URL 込みの
14287
- // 一文なので、札に押し込むとバッジの横で折り返して読めない(本文へ出す)
14288
- if (j.gate === "missing_scope" && probeTypeOf(s.type) === "slack") {
14289
- head.appendChild(el("code", { class: "ds-probe-scope", text: j.needed || "不明" }));
14290
- }
14291
- at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
14292
- probeHelpNodes(s, j).forEach(function (n) { body.appendChild(n); });
14293
- // **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
14294
- // 下に残ると、同じ行に2つのことが書いてある状態になる。
14295
- // **1つ目だけでなく全部隠す(#346)。** 理由文(.ds-gate-reason)・定型補足
14296
- // (.ds-gate-resting)・DS_GATE の help のどれが付いていても、押した結果と
14297
- // 食い違う案内を残さない
14298
- var stale = card.querySelectorAll(".ds-gate-help");
14299
- for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
14300
14060
  // 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)。
14301
14061
  // **GitHub は揃えない。** 行の名前は owner/repo そのもので、書き換える口
14302
14062
  //(op=config の refresh-channel-name)は channels.json 専用
14303
14063
  if (s.type === "slack") autoAlignName(s, j, card);
14304
- // ストアにも 「button」 の行が書かれている(#576)。**取りに行くのは 1 回だけ**——
14305
- // 届いた行でモーダルを閉じたときにタイルが引き直され、登録直後のタイルが行と同じ
14306
- // 「接続OK」になる(「pendingTileVerdict」)。ここで取らないと、画面がストアの行を
14307
- // 読むのは次の op=state(追加行が未確定の 15 分間は 60 秒ごと・過ぎていれば
14308
- // 再読み込みまで)で、「接続を確認 → 閉じる → その場で接続OK」にならない。
14309
- // 据え置きあり=GitHub は叩かない(ストアの行は据え置きの対象外で毎回引く)。
14310
- // 取り込む能力の即答(appendDispatchProbe)は先に同じ 1 行を持っている
14311
- // 同じリポジトリの兄弟の行(issues / discussions)も同じ判定にそろえる
14312
- if (probeTypeOf(s.type) === "github") syncGithubSiblingRows(card, s.ref, j.gate === "ok");
14313
- reloadLiveState();
14064
+ // 🔴 **結果は、ストアの行と同じ形で LIVE_STATE に置いて、行を描き直す**(applyProbeResult)。
14065
+ // 判定・時刻・「活動を記録中」の札・スコープ名の札・次の一手・兄弟の行(issues /
14066
+ // discussions)の判定は、全部この描き直しが決める。ここで DOM を別の形で書き足すと、
14067
+ // 開き直した行と形が割れる(#1328)
14068
+ try {
14069
+ applyProbeResult(probeCapOfType(s.type), s.ref, s.workspace || "", {
14070
+ gate: j.gate,
14071
+ checkedAt: j.checkedAt,
14072
+ needed: j.needed || "",
14073
+ // サーバがストアへ書く値と同じ(op=probe の保存側の項)
14074
+ detail: j.reason || j.error || (j.gate === "ok" ? "" : j.gate),
14075
+ });
14076
+ } catch (drawErr) { /* 描き直しの不調を「通信に失敗」と言わない(測れた事実は取れている)。続く読み直しに任せる */ }
14314
14077
  } catch (e) {
14078
+ wrap.className = "ds-probe is-shown";
14315
14079
  msg.className = "ds-toggle-msg is-error";
14316
14080
  msg.textContent = "✗ 通信に失敗しました。";
14317
14081
  }
@@ -14320,6 +14084,84 @@ ${calendarAddUrl.toString()}
14320
14084
  return { btn: btn, detail: wrap };
14321
14085
  }
14322
14086
 
14087
+ /**
14088
+ * 押した直後の結果を、**接続状態ストアの行と同じ形**で LIVE_STATE に置き、一覧を描き直す。
14089
+ *
14090
+ * 🔴 **行の見た目を決める関数は、開き直したときと同じ 1 本(extSourceCard など)。** 押下の処理は
14091
+ * 札・時刻・説明文を自分で描かない。以前は押下が「いま測った結果」の枠を先に描き、直後の
14092
+ * reloadLiveState で行がいつもの形に描き直されて、2 回見た目が変わっていた(cortex-engine #1328)。
14093
+ *
14094
+ * 形はサーバが書く行(by が button の行)と同じに揃える。続く reloadLiveState が本物の行で
14095
+ * 置き換えても DOM は変わらない。**語彙に無い値は行ごと落とす**(sanitizeStateNow)——
14096
+ * その場合は描き直しの代わりに reloadLiveState に任せる。
14097
+ *
14098
+ * @param capId 接続状態ストアの能力名(chat / google-chat / dev / issues / materials / design)
14099
+ * @param r { gate, checkedAt, needed, detail }
14100
+ */
14101
+ function applyProbeResult(capId, ref, workspace, r) {
14102
+ var row = {
14103
+ cap: capId, question: "reachable", ref: String(ref), status: String(r.gate || ""),
14104
+ at: r.checkedAt ? String(r.checkedAt) : new Date().toISOString(),
14105
+ by: "button", needed: String(r.needed || ""), detail: String(r.detail || ""),
14106
+ };
14107
+ if (workspace) row.workspace = String(workspace);
14108
+ var want = stateRefKey(capId, ref, workspace);
14109
+ var next = (LIVE_STATE || []).filter(function (x) {
14110
+ return !(x && x.cap === capId && x.question === "reachable" && stateRefKey(capId, x.ref, x.workspace) === want);
14111
+ });
14112
+ next.push(row);
14113
+ var clean = sanitizeStateNow(next);
14114
+ // 行が語彙に無く落ちたときは据え置き(reloadLiveState が本物を持ってくる)
14115
+ if (clean && clean.some(function (x) { return x.cap === capId && x.question === "reachable" && x.at === row.at && stateRefKey(capId, x.ref, x.workspace) === want; })) {
14116
+ // 押した結果の控え。**すでに飛んでいる読み直し(押す前の行を返す)や、保存が追いつく前の読み直しが
14117
+ // 届いても、行が古い判定へ戻らない**ように、本物の行が追いつくまで重ねる(「mergeProbeLocal」)
14118
+ PROBE_LOCAL[capId + "\t" + want] = { row: row, t: Date.now() };
14119
+ LIVE_STATE = clean;
14120
+ MERGED = null;
14121
+ repaintOpenLists();
14122
+ refreshDataSources();
14123
+ }
14124
+ // 本物の行を 1 回だけ取りに行く(据え置きのまま・「reloadLiveState」の項)
14125
+ reloadLiveState();
14126
+ }
14127
+
14128
+ /** 押した結果の行の控え(鍵は 能力+畳んだ ref)。本物の行が同じ時刻以降で届いたら捨てる */
14129
+ var PROBE_LOCAL = {};
14130
+ /**
14131
+ * 読み直しで届いた LIVE_STATE に、まだ追いつかれていない押した結果を重ねる。
14132
+ * 🔴 重ねないと、押した瞬間に飛んでいた読み直し(押す前の行)が届いた回だけ、行が古い判定に戻る(#1328)
14133
+ */
14134
+ function mergeProbeLocal() {
14135
+ var keys = Object.keys(PROBE_LOCAL);
14136
+ if (!keys.length) return;
14137
+ var rows = (LIVE_STATE || []).slice();
14138
+ keys.forEach(function (k) {
14139
+ var local = PROBE_LOCAL[k].row;
14140
+ // 10 分たっても本物が追いつかなければ控えを捨てる(本物の行に任せる)
14141
+ if (Date.now() - PROBE_LOCAL[k].t > 10 * 60 * 1000) { delete PROBE_LOCAL[k]; return; }
14142
+ var want = stateRefKey(local.cap, local.ref, local.workspace);
14143
+ var caught = false;
14144
+ for (var i = 0; i < rows.length; i++) {
14145
+ var x = rows[i];
14146
+ if (x && x.cap === local.cap && x.question === "reachable" && stateRefKey(x.cap, x.ref, x.workspace) === want) {
14147
+ if (String(x.at) >= String(local.at)) caught = true;
14148
+ else rows.splice(i, 1);
14149
+ break;
14150
+ }
14151
+ }
14152
+ if (caught) { delete PROBE_LOCAL[k]; return; }
14153
+ rows.push(local);
14154
+ });
14155
+ LIVE_STATE = sanitizeStateNow(rows);
14156
+ }
14157
+
14158
+ /** 画面の種別 → 接続状態ストアの能力名(「接続を確認」の行) */
14159
+ function probeCapOfType(type) {
14160
+ if (String(type || "").indexOf("github") === 0) return "dev";
14161
+ if (type === "google-chat") return "google-chat";
14162
+ return "chat";
14163
+ }
14164
+
14323
14165
  function removeChannelButton(s, card) {
14324
14166
  return removeButton({
14325
14167
  label: "登録を解除",
@@ -14771,6 +14613,9 @@ ${calendarAddUrl.toString()}
14771
14613
  * **この印のものだけ**消す——「取り込み: 〜に成功」の横の語(別の行のもの)は残す
14772
14614
  */
14773
14615
  function runTrailNodes(row, kind) {
14616
+ // 🔴 「その場で確認」は「◯分前に確認」の横に出さない(時刻が出ていて、重ねると同じことを 2 度言う)。
14617
+ // 押した直後の行と開き直した行で形が割れないよう、書き手が button の行は語を持たない
14618
+ if (kind === "gate" && row && row.by === "button") return [];
14774
14619
  var word = runSourceWordOf(row);
14775
14620
  var node = word ? el("span", { class: "ds-run-src", text: word }) : null;
14776
14621
  if (!node) return [];
@@ -14785,15 +14630,6 @@ ${calendarAddUrl.toString()}
14785
14630
  return f;
14786
14631
  }
14787
14632
 
14788
- /**
14789
- * 「いま測った結果」に差し替えるとき、**「◯分前に確認」に添えた**古い出どころの語を外す
14790
- * (同じ行が 2 つのことを言わない)。「取り込み: 〜に成功」の横の語は別の行のものなので残す
14791
- */
14792
- function removeRunTrail(scope) {
14793
- var old = scope.querySelectorAll('.ds-run-src[data-trail="gate"]');
14794
- for (var i = 0; i < old.length; i++) old[i].remove();
14795
- }
14796
-
14797
14633
  /**
14798
14634
  * 接続先1件の「取り込み」の表示。**測っていなければ何も出さない(null)。**
14799
14635
  *
@@ -14886,8 +14722,8 @@ ${calendarAddUrl.toString()}
14886
14722
  if (!r) return row;
14887
14723
  var gate = dsGateForCap(capId, r.status);
14888
14724
  row.appendChild(el("span", { class: "ds-gate " + gate.cls, text: gate.text }));
14889
- var when = rtAgo(r.at);
14890
- if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when + "に確認" }));
14725
+ var when = agoConfirmedText(r.at);
14726
+ if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when }));
14891
14727
  // run の無い書き手は出どころの語を添える(#560)
14892
14728
  runTrailNodes(r, "gate").forEach(function (n) { row.appendChild(n); });
14893
14729
  return row;
@@ -14931,7 +14767,7 @@ ${calendarAddUrl.toString()}
14931
14767
  * -------------------------------------------------------------------------
14932
14768
  * #548 までは、この3つは中央が鍵を読まない(#105 決定5)ので、押すとその案件の同期が
14933
14769
  * 1回起きるだけだった。オーナーがその境界を覆し、いまは「接続を確認」は Slack / GitHub と
14934
- * 同じく**中央がその場で叩いて答えを返す**(即答・「showProbeVerdict」)。同期を1回
14770
+ * 同じく**中央がその場で叩いて答えを返す**(即答・「applyProbeResult」)。同期を1回
14935
14771
  * 起こす従来の動きは「今すぐ同期」(送る値に 「sync: true」)に移った。
14936
14772
  *
14937
14773
  * 待ちの仕組み(下)は「今すぐ同期」と、Backlog が IP 制限で叩けず夜間と同じ経路へ
@@ -15020,6 +14856,7 @@ ${calendarAddUrl.toString()}
15020
14856
  * 登録簿が読めない・ロール未登録・起動失敗が、全部ここへ来る)。
15021
14857
  */
15022
14858
  function probeDeclined(note, key, j) {
14859
+ note.hidden = false;
15023
14860
  note.textContent = (j && j.message) || "確認できませんでした。";
15024
14861
  delete PROBE_WAITS[key];
15025
14862
  }
@@ -15039,6 +14876,7 @@ ${calendarAddUrl.toString()}
15039
14876
  startedAt: Date.now(),
15040
14877
  message: (j && j.message) || "",
15041
14878
  };
14879
+ note.hidden = false;
15042
14880
  probeWaitNote(note, PROBE_WAITS[key]);
15043
14881
  // 待っているあいだは取り直しを回す(届いた行で一覧が描き直される)
15044
14882
  refreshStateTimer();
@@ -15049,58 +14887,6 @@ ${calendarAddUrl.toString()}
15049
14887
  note.textContent = (wait && wait.message) || "";
15050
14888
  }
15051
14889
 
15052
- /**
15053
- * 即答(#548 の direct)を行に映す。**「probeSection」 と同じ見た目にする**——
15054
- * 夜間のスナップショットの判定といま測った判定を**同じ行に並べない**ため
15055
- * (並べると「⚠️ と 接続OK が同時に出ている」になり、この節で何度も直してきた
15056
- * 食い違いを画面の側から作ることになる)。置き換えたことが分かるよう、
15057
- * いつ測ったかを必ず添える。
15058
- *
15059
- * 🔴 **札が無い行もある**(一度も測っていない接続先= 「appendReachableChip」 が
15060
- * 何も足していない行)。その場合は札を作って**ボタンの前**に差し込む——後ろに置くと
15061
- * 行の読み順(札 → ボタン)が崩れる。
15062
- *
15063
- * @param anchor 差し込む位置の目印(1つ目のボタン)
15064
- */
15065
- function showProbeVerdict(row, capId, anchorIn, j) {
15066
- var gate = dsGateForCap(capId, j.gate);
15067
- // 差し込む位置は**行の最初のボタン**の前(「外す」が出る行では「外す」の前)。
15068
- // 「接続を確認」の前に置くと、外せる行だけ「外す → 札 → ボタン」の並びになる(🟡10)
15069
- var anchor = anchorIn;
15070
- var btns = row.querySelectorAll(".ds-toggle-btn");
15071
- for (var bi = 0; bi < btns.length; bi++) {
15072
- // **行の直下にあるボタンだけ**が目印になれる(入れ子の中のボタンの前には差し込めない)
15073
- if (btns[bi].parentNode === row) { anchor = btns[bi]; break; }
15074
- }
15075
- var chip = row.querySelector(".ds-gate");
15076
- if (!chip) {
15077
- chip = el("span", { class: "ds-gate" });
15078
- row.insertBefore(chip, anchor);
15079
- }
15080
- chip.className = "ds-gate " + gate.cls;
15081
- chip.textContent = gate.text;
15082
- var at = row.querySelector(".ds-gate-at");
15083
- if (!at) {
15084
- at = el("span", { class: "ds-gate-at" });
15085
- row.insertBefore(at, anchor);
15086
- }
15087
- at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
15088
- // 古い run の導線も外す(#560)。残すと「いま測った結果」の横に夜間の run のログが並ぶ
15089
- removeRunTrail(row);
15090
- // **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が下に残ると、
15091
- // 同じ行が2つのことを言う(probeSection の stale 処理と同じ)
15092
- var stale = row.querySelectorAll(".ds-gate-help");
15093
- for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
15094
- // **押し直しで段が増えないよう、前回の結果は消してから出す**(probeSection の札と同じ)
15095
- var old = row.querySelectorAll(".ds-probe-help");
15096
- for (var oi = 0; oi < old.length; oi++) old[oi].remove();
15097
- // 次の一手は語の表(DS_GATE_CAP)が持っていればそれ。無ければ判定器の1文(detail)
15098
- // ——どちらも**1段だけ**(帯は症状・行は一手・繰り返さない)
15099
- var nodes = gateHelpNodes(gate, "ds-probe-help");
15100
- if (!nodes.length && j.detail) nodes = [el("p", { class: "ds-probe-help", text: String(j.detail) })];
15101
- nodes.forEach(function (n) { row.appendChild(n); });
15102
- }
15103
-
15104
14890
  /**
15105
14891
  * 接続先1件の「接続を確認」「今すぐ同期」(2ボタン+その場の返事1文)を行に足す。
15106
14892
  *
@@ -15129,9 +14915,11 @@ ${calendarAddUrl.toString()}
15129
14915
  else note.hidden = true;
15130
14916
 
15131
14917
  checkBtn.addEventListener("click", async function () {
14918
+ // 押している間は**ボタンだけ**が「確認中…」になる。行の中身は変えない(#1328)。
14919
+ // 前回の返事(「同期を開始しました」等)は押した時点で消す
15132
14920
  var endBusy = beginBusy(checkBtn, "確認中…");
15133
- note.hidden = false;
15134
- note.textContent = "確認中…";
14921
+ note.hidden = true;
14922
+ note.textContent = "";
15135
14923
  // 🔴 **押す前に、いまの行の 「at」 を控える(🔴2)。** 行が無ければ空文字
15136
14924
  //(どんな 「at」 が来ても「新しい」と読める)。使うのは IP 制限で倒れた回だけだが、
15137
14925
  // 応答が返ってからでは「押す前の行」を控えられない
@@ -15158,11 +14946,11 @@ ${calendarAddUrl.toString()}
15158
14946
  delete PROBE_WAITS[key];
15159
14947
  note.hidden = true;
15160
14948
  note.textContent = "";
15161
- showProbeVerdict(row, capId, checkBtn, j);
15162
- // ストアにも 「button」 の行が書かれている。**取りに行くのは1回だけ**——
15163
- // 届いた行で一覧が描き直され、この行の札が正本の値に揃う
15164
- reloadLiveState();
14949
+ // 🔴 結果は行を描く関数に渡して描き直す(札・時刻・次の一手は行が決める。#1328)。
14950
+ // 次の一手は語の表が持っていればそれ、無ければ判定器の 1 文(detail)——どちらも行の側の規則
14951
+ applyProbeResult(capId, ref, "", { gate: j.gate, checkedAt: j.checkedAt, needed: "", detail: j.detail || "" });
15165
14952
  } catch (e) {
14953
+ note.hidden = false;
15166
14954
  note.textContent = "通信に失敗しました。";
15167
14955
  delete PROBE_WAITS[key];
15168
14956
  }
@@ -16956,7 +16744,7 @@ ${calendarAddUrl.toString()}
16956
16744
  var gateEl = el("span", { class: "ds-gate " + gate.cls, text: gate.text });
16957
16745
  // 状態ストア由来の判定には「いつ確かめたか」を必ず添える
16958
16746
  var gateAtEl = s.gateAt
16959
- ? el("span", { class: "ds-gate-at", text: rtAgo(s.gateAt) + "に確認" })
16747
+ ? el("span", { class: "ds-gate-at", text: agoConfirmedText(s.gateAt) })
16960
16748
  : null;
16961
16749
  // run の無い書き手は出どころの語を添える(#560)。overlay が写した 「gateRunUrl」 / 「gateBy」 から決める
16962
16750
  var gateTrail = s.gateAt ? runTrailNodes({ runUrl: s.gateRunUrl || "", by: s.gateBy || "" }, "gate") : [];
@@ -17116,7 +16904,7 @@ ${calendarAddUrl.toString()}
17116
16904
  // **origin === "explicit" でも出す。** 確認は読むだけで、どの登録ファイル由来かに
17117
16905
  // 関係なく効く(書き込みになる「表示名を更新する」だけが由来で出し分かれる)
17118
16906
  if (SITE.intake && probeTypeOf(s.type) && s.ref) {
17119
- var probe = probeSection(s, card, gateEl);
16907
+ var probe = probeSection(s, card);
17120
16908
  // **ボタンは行の中(「登録を解除」の隣)。** 独立した段を作ると1行が2段になる
17121
16909
  card.appendChild(probe.btn);
17122
16910
  appendRowHelp();
@@ -17500,6 +17288,8 @@ ${calendarAddUrl.toString()}
17500
17288
  LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
17501
17289
  LIVE_REGISTERED_AT = startedAt;
17502
17290
  LIVE_STATE = sanitizeStateNow(j && j.stateNow);
17291
+ // 押した結果が本物の行に追いつくまで重ねる(mergeProbeLocal の項)
17292
+ mergeProbeLocal();
17503
17293
  // **欄の有無をそのまま覚える。** sanitize を通した後では「読めて0件」が消える
17504
17294
  LIVE_STATE_SEEN = !!(j && Array.isArray(j.stateNow));
17505
17295
  // **カレンダーの行だけは sanitize の前に数える**(#428・上の宣言の項)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.129",
3
+ "version": "0.12.130",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,