@takagaki/cortex-decisions-viewer 0.4.83 → 0.4.84

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 +113 -93
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -2083,6 +2083,25 @@ ${capabilityState.toString()}
2083
2083
  function isInternalSlack(s) {
2084
2084
  return s.type === "slack" && !s.workspace;
2085
2085
  }
2086
+ /**
2087
+ * Slack チャンネルの表示名。**# は表示のときだけ付ける**(保存値は素・2026-08-19)。
2088
+ *
2089
+ * 保存値に # を持たせていたせいで、人が手で # 無しに書いた行と Slack から引いた値が
2090
+ * 「# の有無だけ」で食い違い、「名前が違います」と出た(実機で発生)。**保存は素に
2091
+ * 決めた**ので、付けるのはここ1箇所だけ。決定の理由は infra/lambda/shared/slack-gate.mjs。
2092
+ *
2093
+ * **冪等にしてある**(先頭の # を落としてから付ける)。既存データには # 付きで
2094
+ * 保存された行が10件あり、これが無いと「##tf-project-cortex」になる。冪等なので
2095
+ * **データの移行を待たずにこの表示へ切り替えられる**(揃うのは名前が引き直された行から順に)。
2096
+ *
2097
+ * 印付きの名前(「(名前未取得: C0BQ…)」)には付けない——# は実名の印なので、
2098
+ * 引けていない名前に付けると実名に見えてしまう。
2099
+ */
2100
+ function slackDisplayName(name) {
2101
+ var s = String(name || "");
2102
+ if (!s || s.indexOf("(名前未取得") === 0) return s;
2103
+ return "#" + s.replace(/^#+/, "");
2104
+ }
2086
2105
  /** 行に出す接続状態。no_token だけは経路(社内/顧客)で文面が分かれる */
2087
2106
  function dsGateOf(s) {
2088
2107
  if (s.gate === "no_token" && s.type === "slack") {
@@ -3084,104 +3103,99 @@ ${capabilityState.toString()}
3084
3103
  }
3085
3104
 
3086
3105
  /**
3087
- * 判定ごとの「次の一手」。**状態の語だけでは足りない**——今日の3件はどれも
3088
- * 「何が起きているか」は分かっても「次に何をすればいいか」が決まらなかった。
3106
+ * 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
3107
+ *
3108
+ * ## 短くした理由(2026-08-19・オーナー指示)
3109
+ *
3110
+ * 「接続を確認を押して出てくる文言が長ったらしい」。実際、成功時に4行出ていた。
3111
+ * **成功時は「読めている」以上の情報が要らない。**
3112
+ *
3113
+ * ## ただし失敗時は削りすぎない
3114
+ *
3115
+ * 短くしすぎると、今日と同じ「画面を見ても次に何をすればいいか決まらない」状態に戻る
3116
+ * (それで半日かかった)。落とすのは**内部の仕組みの説明**——「設定ファイルの表示名だけが
3117
+ * 書き換わります」「読み取りの対象・範囲は変わりません」のような、読む人の次の行動を
3118
+ * 変えない文。残すのは「何が起きているか」と「次に何をするか」だけ。
3119
+ *
3120
+ * 状態の語そのものは DS_GATE / DS_GATE_NO_TOKEN が持つ(probeVerdict 経由)ので、
3121
+ * ここが返すのは**それに足りない分だけ**。二重に言わない。
3122
+ *
3123
+ * @returns 本文に並べるノード(0〜2個)
3089
3124
  */
3090
3125
  function probeHelpNodes(s, r) {
3091
3126
  var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
3092
- var out = [];
3093
- if (r.gate === "ok") {
3094
- // **表示名が引けたかまで言う。** 読めているのに名前が「(名前未取得: C0BQ…)」の
3095
- // ままだった案件があり、読めていること自体は正しかったので誰も疑わなかった
3096
- out.push(p(r.name
3097
- ? "読めています。Slack で確認できた表示名は「" + r.name + "」です。"
3098
- : "読めています。ただし表示名は引けませんでした(Slack の応答: " +
3099
- (r.error || "不明") + ")。一覧の名前は登録したときのままになります。"));
3100
- return out;
3101
- }
3127
+ // 成功。**1行で終わり。** 状態の語(「接続OK」)が既に出ているので本文は空
3128
+ if (r.gate === "ok") return [];
3102
3129
  if (r.gate === "not_in_channel") {
3103
- out.push(p("このチャンネルに Cortex を招待してください。公開チャンネルでも招待が必要で、" +
3104
- "招待していないと中身を1件も読めません。Slack でこのチャンネルを開き、次のコマンドを貼ります。" +
3105
- "招待したら、もう一度「接続を確認」を押して直ったことを確かめてください。"));
3106
- // **手で打たせない。** 短い文字列でも打ち間違いは黙って失敗し、
3107
- // 利用者には「招待したのに読まれない」としか見えない(copyRow の直上コメント)
3108
- out.push(copyRow("/invite @Cortex"));
3109
- return out;
3130
+ // 状態の語が「Slackで招待してください」なので、説明は足さずコマンドだけ出す。
3131
+ // **手で打たせない**——打ち間違いは黙って失敗し、「招待したのに読まれない」に見える
3132
+ return [copyRow("/invite @Cortex")];
3110
3133
  }
3111
3134
  if (r.gate === "missing_scope") {
3112
- // **足りないスコープ名をそのまま出す(2026-08-19 の不具合の本体)。**
3113
- // Slack は needed で名指しで返しているのに、実測がそれを捨てて「不明」にしていた。
3114
- // **招待と言わない**——招待では直らないので、言うと直らない操作を繰り返させる
3115
- var line = el("p", { class: "ds-probe-help" });
3116
- line.appendChild(document.createTextNode("Cortex の Slack App に権限(スコープ)が足りません。不足しているのは "));
3117
- line.appendChild(el("code", { class: "ds-probe-scope", text: r.needed || "不明(Slack が名前を返しませんでした)" }));
3118
- line.appendChild(document.createTextNode(" です。"));
3119
- out.push(line);
3120
- out.push(p("これは招待では直りません。Slack App にこのスコープを追加して再インストールする必要があるので、" +
3121
- "cortex の担当者にこのスコープ名をそのまま伝えてください。" +
3122
- (r.readable
3123
- ? "なお、チャンネルの中身そのものは読めています(毎晩の取り込みは動いています)。直らないのは表示名です。"
3124
- : "")));
3125
- return out;
3135
+ // 不足スコープは頭のバッジ横に出る(probeSection)。ここは次の一手だけ
3136
+ return [p("招待では直りません。cortex の担当者にこのスコープ名をお伝えください。")];
3126
3137
  }
3127
3138
  if (r.gate === "no_token") {
3128
- // **社内/顧客の出し分けは既存の1箇所から取る。** ここで書き分けると必ずずれる
3129
- var help = probeVerdict(s, r).help;
3130
- if (help) out.push(p(help));
3131
- return out;
3139
+ // **社内/顧客の出し分けは変えない。** 社内は画面から設定できるものが無いので
3140
+ // 連絡先、顧客は接続へ誘導する(2026-08-18 の不具合の再発防止)。
3141
+ // 長い説明は落とし、次の行動の1行だけ残す
3142
+ return [p(isInternalSlack(s)
3143
+ ? "cortex の担当者にご連絡ください。"
3144
+ : "下の「お客様の Slack ワークスペースを接続」から接続してください。")];
3132
3145
  }
3133
3146
  if (r.gate === "unreachable") {
3134
- out.push(p("チャンネルが見つからないか、アーカイブされています。" +
3135
- "Slack でこのチャンネルがまだ存在するかを確かめてください。" +
3136
- "アーカイブしたチャンネルなら、この行の「登録を解除」で読み取り対象から外せます。" +
3137
- "存在するのに見つからない場合は、Cortex が入っていない別ワークスペースのチャンネルの可能性があるので、" +
3138
- "cortex の担当者にご連絡ください。"));
3139
- return out;
3147
+ return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
3140
3148
  }
3141
- // unknown。**生のエラーを隠さない**——「不明」だけだと切り分けが始められない
3142
- out.push(p("読めるかどうかを確かめられませんでした。Slack の応答は「" + (r.error || "不明") + "」です。" +
3143
- "少し時間をおいてもう一度お試しください。同じ応答が続く場合は、この文言をそのまま cortex の担当者にお伝えください。"));
3144
- return out;
3149
+ // unknown。**生のエラーは隠さない**——「不明」だけだと切り分けが始められない
3150
+ return [p("Slack の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。")];
3145
3151
  }
3146
3152
 
3147
3153
  /**
3148
- * 表示名の取り直し。**確認とは別のボタンにしてある。**
3154
+ * 表示名を Slack の実名へ**自動で揃える**(2026-08-19・オーナー判断)。
3155
+ *
3156
+ * ## なぜ自動なのか
3157
+ *
3158
+ * 「表示名は人が決めるものではなく、Slack から引いたものをそのまま出す」と決まった。
3159
+ * 正解が1つしかないので、**人に選ばせるボタンは摩擦でしかない**(差分表示と
3160
+ * 「表示名を更新する」ボタンは撤去した。Gold トグルを撤去したときと同じ理屈)。
3161
+ *
3162
+ * ## なぜサーバの ?op=probe ではなく画面から送るのか
3149
3163
  *
3150
- * 確認(?op=probe)は読むだけなので誰でも押してよいと決めた(オーナー判断・2026-08-19)。
3151
- * 名前の更新は案件リポジトリへのコミットなので、**設定変更と同じ入口(?op=config)**に
3152
- * 通す(sendConfig)。確認のついでに黙って書き込む形にすると、「読むだけだから誰でも
3153
- * 押してよい」という前提そのものが崩れる。**押す前に、何がどう変わるかを見せる。**
3164
+ * **疎通確認そのものは読むだけ、を保ちたいから。** 「読むだけで設定を変えない」が
3165
+ * ボタンを誰でも押せるようにした根拠なので、?op=probe を書き込む口にはしない。
3166
+ * 書き込みは既存の ?op=config を通す——他案件チェック・コミットのリトライ・
3167
+ * 画面への反映まで、設定変更と同じ守りがそのまま効く。
3154
3168
  *
3155
- * 出すのは**違っているときだけ**。同じ名前のときにボタンだけ出すと、押しても
3156
- * 「変更はありません」と返るだけの操作になる。
3169
+ * **本来の置き場は夜間の接続状況生成(engine 側)。** そちらなら人の操作と無関係に
3170
+ * 1日1回揃い、改名にも追従する。engine が今回の変更対象外なので、いまは押したときに揃える。
3171
+ *
3172
+ * ## 静かに失敗させる
3173
+ *
3174
+ * 揃えるのはおまけで、押した人の目的は「読めているか確かめる」こと。
3175
+ * 失敗しても疎通確認の結果は出したままにする(名前が古いままなだけ)。
3157
3176
  */
3158
- function renameChannelRow(s, r, card) {
3159
- if (!r.name || r.name === s.name) return null;
3160
- var box = el("div");
3161
- box.appendChild(el("p", { class: "ds-probe-help",
3162
- text: "一覧の表示名「" + (s.name || "(未設定)") + "」は、Slack のいまの名前「" + r.name + "」と違います。" +
3163
- "更新すると、この案件の設定ファイル(チャット/channels.json)の表示名だけが書き換わります。" +
3164
- "読み取りの対象・範囲は変わりません。" }));
3165
- var row = el("div", { class: "ds-toggle" });
3166
- var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "表示名を更新する" });
3167
- var msg = el("div", { class: "ds-toggle-msg" });
3168
- btn.addEventListener("click", function () {
3169
- // **名前は送らない。** サーバが Slack から引き直したものだけを書く(引けなければ
3170
- // 断って既存の名前を残す)。画面から任意の名前を送れる形にすると、
3171
- // 名前の取り直しの顔をした自由な改名になる
3172
- var payload = { op: "refresh-channel-name", ref: s.ref };
3173
- if (s.workspace) payload.workspace = s.workspace;
3174
- sendConfig(payload, msg, btn, function () {
3175
- // 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
3176
- var nameEl = card.querySelector(".ds-name");
3177
- if (nameEl) nameEl.textContent = r.name;
3178
- btn.disabled = true;
3179
- });
3180
- });
3181
- row.appendChild(btn);
3182
- row.appendChild(msg);
3183
- box.appendChild(row);
3184
- return box;
3177
+ function autoAlignName(s, r, card) {
3178
+ // 引けていない/既に同じなら何もしない(押すたびにコミットを積まない)
3179
+ if (!r.name || !s.ref) return;
3180
+ if (String(s.name || "").replace(/^#+/, "") === String(r.name)) return;
3181
+ // **channels.json に無い行は書き換えられない。** dev-sources.json だけに載っている
3182
+ // 行(origin: explicit)へ送ると必ず404になる(「登録を解除」と同じ条件)
3183
+ if (s.origin === "explicit") return;
3184
+ var intake = SITE.intake;
3185
+ if (!intake) return;
3186
+ var payload = { op: "refresh-channel-name", ref: s.ref };
3187
+ if (s.workspace) payload.workspace = s.workspace;
3188
+ var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
3189
+ fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
3190
+ method: "POST",
3191
+ headers: { "content-type": "application/json" },
3192
+ body: JSON.stringify(payload),
3193
+ }).then(function (res) {
3194
+ if (!res.ok) return;
3195
+ // 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
3196
+ var nameEl = card.querySelector(".ds-name");
3197
+ if (nameEl) nameEl.textContent = slackDisplayName(r.name);
3198
+ }).catch(function () { /* 揃えられなくても疎通確認の結果は出したまま */ });
3185
3199
  }
3186
3200
 
3187
3201
  /**
@@ -3192,9 +3206,9 @@ ${capabilityState.toString()}
3192
3206
  * になり、この節で3回直してきた食い違いを画面の側から作ることになる。
3193
3207
  * 置き換えたことが分かるよう、いつ測ったかを必ず添える。
3194
3208
  *
3195
- * **結果は保存しない。** fleet-status.json は夜間のスナップショットのまま置く
3196
- * (押すたびにコミットが増えるのを避ける)。表示名の取り直しだけが例外で、
3197
- * そちらは正本(channels.json)を直す価値があるので別のボタンで書き込む。
3209
+ * **判定は保存しない。** fleet-status.json は夜間のスナップショットのまま置く
3210
+ * (押すたびにコミットが増えるのを避ける)。表示名だけが例外で、そちらは
3211
+ * Slack の実名が正なので自動で揃える(autoAlignName)。
3198
3212
  */
3199
3213
  function probeSection(s, card, gateEl) {
3200
3214
  var wrap = el("div", { class: "ds-probe" });
@@ -3234,19 +3248,24 @@ ${capabilityState.toString()}
3234
3248
  btn.disabled = false;
3235
3249
  return;
3236
3250
  }
3237
- msg.textContent = "";
3251
+ msg.className = "ds-toggle-msg" + (j.gate === "ok" ? " is-ok" : " is-error");
3252
+ msg.textContent = j.gate === "ok" ? "✓" : "✗";
3238
3253
  var v = probeVerdict(s, j);
3239
3254
  gateEl.className = "ds-gate " + v.cls;
3240
3255
  gateEl.textContent = v.text;
3256
+ // 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
3257
+ // 本文へ落とすと、いちばん要る情報が説明文に埋もれる
3258
+ if (j.gate === "missing_scope") {
3259
+ head.appendChild(el("code", { class: "ds-probe-scope", text: j.needed || "不明" }));
3260
+ }
3241
3261
  at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
3242
3262
  probeHelpNodes(s, j).forEach(function (n) { body.appendChild(n); });
3243
- // **登録ファイルが channels.json の行にだけ出す。** 書き換える先がそこなので、
3244
- // dev-sources.json だけに載っている行(origin: explicit)では必ず 404 になる
3245
- // ——「登録を解除」が同じ理由で同じ条件を持っている
3246
- if (s.origin !== "explicit") {
3247
- var rename = renameChannelRow(s, j, card);
3248
- if (rename) body.appendChild(rename);
3249
- }
3263
+ // **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
3264
+ // 下に残ると、同じ行に2つのことが書いてある状態になる
3265
+ var stale = card.querySelector(".ds-gate-help");
3266
+ if (stale) stale.hidden = true;
3267
+ // 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)
3268
+ autoAlignName(s, j, card);
3250
3269
  } catch (e) {
3251
3270
  msg.className = "ds-toggle-msg is-error";
3252
3271
  msg.textContent = "✗ 通信に失敗しました。";
@@ -4327,7 +4346,8 @@ ${capabilityState.toString()}
4327
4346
  el("div", { class: "ds-card-main" }, [
4328
4347
  el("span", { class: "ds-icon", text: meta.icon }),
4329
4348
  el("span", { class: "ds-type", text: meta.label }),
4330
- dsNameEl(s.name || "", s.url),
4349
+ // Slack だけ # を表示側で付ける(保存値は素)。他のアダプタの名前には触らない
4350
+ dsNameEl(s.type === "slack" ? slackDisplayName(s.name) : (s.name || ""), s.url),
4331
4351
  ]),
4332
4352
  badges,
4333
4353
  gateEl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.83",
3
+ "version": "0.4.84",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,