@takagaki/cortex-decisions-viewer 0.4.83 → 0.4.85

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 +330 -185
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -1402,7 +1402,7 @@ a.ds-name:hover { text-decoration: underline; }
1402
1402
  button.ops-card-btn { cursor: pointer; }
1403
1403
  button.ops-card-btn:disabled { opacity: .6; cursor: default; }
1404
1404
  /* ---- 投入モーダル ---- */
1405
- .modal-overlay {
1405
+ .modal-overlay, .confirm-overlay {
1406
1406
  position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
1407
1407
  background: rgba(16,24,40,0.45); padding: 20px;
1408
1408
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
@@ -1434,6 +1434,12 @@ button.ops-card-btn:disabled { opacity: .6; cursor: default; }
1434
1434
  background: var(--surface); color: var(--muted); font-size: 13px; font-weight: 600; cursor: pointer;
1435
1435
  }
1436
1436
  .modal-cancel:hover { background: var(--bg); }
1437
+ /* 確認モーダル。能力モーダルの上に重なるので z-index は上(下地は inert で塞ぐ) */
1438
+ .confirm-overlay { z-index: 110; }
1439
+ .confirm-box { max-width: 420px; gap: 10px; }
1440
+ /* 貼ったURLを1行で出すので、はみ出させない(箱は420px・モバイルは実質335px) */
1441
+ .confirm-line { font-size: 14px; color: var(--text); line-height: 1.75; margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
1442
+ .confirm-line:first-child { font-weight: 700; }
1437
1443
  .modal-ok { color: #047857; font-weight: 600; }
1438
1444
  .modal-status-error { color: #dc2626; font-weight: 600; }
1439
1445
  .modal-paths { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
@@ -2083,6 +2089,25 @@ ${capabilityState.toString()}
2083
2089
  function isInternalSlack(s) {
2084
2090
  return s.type === "slack" && !s.workspace;
2085
2091
  }
2092
+ /**
2093
+ * Slack チャンネルの表示名。**# は表示のときだけ付ける**(保存値は素・2026-08-19)。
2094
+ *
2095
+ * 保存値に # を持たせていたせいで、人が手で # 無しに書いた行と Slack から引いた値が
2096
+ * 「# の有無だけ」で食い違い、「名前が違います」と出た(実機で発生)。**保存は素に
2097
+ * 決めた**ので、付けるのはここ1箇所だけ。決定の理由は infra/lambda/shared/slack-gate.mjs。
2098
+ *
2099
+ * **冪等にしてある**(先頭の # を落としてから付ける)。既存データには # 付きで
2100
+ * 保存された行が10件あり、これが無いと「##tf-project-cortex」になる。冪等なので
2101
+ * **データの移行を待たずにこの表示へ切り替えられる**(揃うのは名前が引き直された行から順に)。
2102
+ *
2103
+ * 印付きの名前(「(名前未取得: C0BQ…)」)には付けない——# は実名の印なので、
2104
+ * 引けていない名前に付けると実名に見えてしまう。
2105
+ */
2106
+ function slackDisplayName(name) {
2107
+ var s = String(name || "");
2108
+ if (!s || s.indexOf("(名前未取得") === 0) return s;
2109
+ return "#" + s.replace(/^#+/, "");
2110
+ }
2086
2111
  /** 行に出す接続状態。no_token だけは経路(社内/顧客)で文面が分かれる */
2087
2112
  function dsGateOf(s) {
2088
2113
  if (s.gate === "no_token" && s.type === "slack") {
@@ -2264,7 +2289,7 @@ ${capabilityState.toString()}
2264
2289
  // **何が読み取られるのかを必ず書く。** 一覧に並ぶものと読み取られるものの関係は、
2265
2290
  // 書いていないと分からない(チャットは「載っていれば全部」、開発はリポジトリ単位でOFFできる)
2266
2291
  if (cap === "チャット") {
2267
- m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるチャンネルは、例外なく全て毎晩の Gold 昇格が読み取り、顧客も見る Decision・用語集の材料になります。読み取りを止めるには「登録を解除」してください。" }));
2292
+ m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるチャンネルは、例外なく全て毎晩の Gold 昇格が読み取り、Decision・用語集の材料になります。読み取りを止めるには「登録を解除」してください。" }));
2268
2293
  } else if (cap === "開発") {
2269
2294
  m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるリポジトリの Issues を毎晩の Gold 昇格が読み取ります。リポジトリ単位で読み取りを止められます(止めたものは読み取られません)。" }));
2270
2295
  }
@@ -2960,7 +2985,7 @@ ${capabilityState.toString()}
2960
2985
 
2961
2986
  // 注記は全能力の後ろに1つ。**何が Gold の材料になるのかを1文で言う。**
2962
2987
  if (anyCap) {
2963
- sec.appendChild(el("p", { class: "ds-note", text: "登録されている Slack チャンネルと GitHub リポジトリは、毎晩の Gold 昇格が読み取り、顧客も見る Decision・用語集の材料になります。チャンネルは登録を解除すると、リポジトリは読み取りを止めると、以後は読み取られません。" }));
2988
+ sec.appendChild(el("p", { class: "ds-note", text: "登録されている Slack チャンネルと GitHub リポジトリは、毎晩の Gold 昇格が読み取り、Decision・用語集の材料になります。チャンネルは登録を解除すると、リポジトリは読み取りを止めると、以後は読み取られません。" }));
2964
2989
  }
2965
2990
 
2966
2991
  if (pipes.length) {
@@ -3084,104 +3109,99 @@ ${capabilityState.toString()}
3084
3109
  }
3085
3110
 
3086
3111
  /**
3087
- * 判定ごとの「次の一手」。**状態の語だけでは足りない**——今日の3件はどれも
3088
- * 「何が起きているか」は分かっても「次に何をすればいいか」が決まらなかった。
3112
+ * 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
3113
+ *
3114
+ * ## 短くした理由(2026-08-19・オーナー指示)
3115
+ *
3116
+ * 「接続を確認を押して出てくる文言が長ったらしい」。実際、成功時に4行出ていた。
3117
+ * **成功時は「読めている」以上の情報が要らない。**
3118
+ *
3119
+ * ## ただし失敗時は削りすぎない
3120
+ *
3121
+ * 短くしすぎると、今日と同じ「画面を見ても次に何をすればいいか決まらない」状態に戻る
3122
+ * (それで半日かかった)。落とすのは**内部の仕組みの説明**——「設定ファイルの表示名だけが
3123
+ * 書き換わります」「読み取りの対象・範囲は変わりません」のような、読む人の次の行動を
3124
+ * 変えない文。残すのは「何が起きているか」と「次に何をするか」だけ。
3125
+ *
3126
+ * 状態の語そのものは DS_GATE / DS_GATE_NO_TOKEN が持つ(probeVerdict 経由)ので、
3127
+ * ここが返すのは**それに足りない分だけ**。二重に言わない。
3128
+ *
3129
+ * @returns 本文に並べるノード(0〜2個)
3089
3130
  */
3090
3131
  function probeHelpNodes(s, r) {
3091
3132
  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
- }
3133
+ // 成功。**1行で終わり。** 状態の語(「接続OK」)が既に出ているので本文は空
3134
+ if (r.gate === "ok") return [];
3102
3135
  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;
3136
+ // 状態の語が「Slackで招待してください」なので、説明は足さずコマンドだけ出す。
3137
+ // **手で打たせない**——打ち間違いは黙って失敗し、「招待したのに読まれない」に見える
3138
+ return [copyRow("/invite @Cortex")];
3110
3139
  }
3111
3140
  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;
3141
+ // 不足スコープは頭のバッジ横に出る(probeSection)。ここは次の一手だけ
3142
+ return [p("招待では直りません。cortex の担当者にこのスコープ名をお伝えください。")];
3126
3143
  }
3127
3144
  if (r.gate === "no_token") {
3128
- // **社内/顧客の出し分けは既存の1箇所から取る。** ここで書き分けると必ずずれる
3129
- var help = probeVerdict(s, r).help;
3130
- if (help) out.push(p(help));
3131
- return out;
3145
+ // **社内/顧客の出し分けは変えない。** 社内は画面から設定できるものが無いので
3146
+ // 連絡先、顧客は接続へ誘導する(2026-08-18 の不具合の再発防止)。
3147
+ // 長い説明は落とし、次の行動の1行だけ残す
3148
+ return [p(isInternalSlack(s)
3149
+ ? "cortex の担当者にご連絡ください。"
3150
+ : "下の「お客様の Slack ワークスペースを接続」から接続してください。")];
3132
3151
  }
3133
3152
  if (r.gate === "unreachable") {
3134
- out.push(p("チャンネルが見つからないか、アーカイブされています。" +
3135
- "Slack でこのチャンネルがまだ存在するかを確かめてください。" +
3136
- "アーカイブしたチャンネルなら、この行の「登録を解除」で読み取り対象から外せます。" +
3137
- "存在するのに見つからない場合は、Cortex が入っていない別ワークスペースのチャンネルの可能性があるので、" +
3138
- "cortex の担当者にご連絡ください。"));
3139
- return out;
3153
+ return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
3140
3154
  }
3141
- // unknown。**生のエラーを隠さない**——「不明」だけだと切り分けが始められない
3142
- out.push(p("読めるかどうかを確かめられませんでした。Slack の応答は「" + (r.error || "不明") + "」です。" +
3143
- "少し時間をおいてもう一度お試しください。同じ応答が続く場合は、この文言をそのまま cortex の担当者にお伝えください。"));
3144
- return out;
3155
+ // unknown。**生のエラーは隠さない**——「不明」だけだと切り分けが始められない
3156
+ return [p("Slack の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。")];
3145
3157
  }
3146
3158
 
3147
3159
  /**
3148
- * 表示名の取り直し。**確認とは別のボタンにしてある。**
3160
+ * 表示名を Slack の実名へ**自動で揃える**(2026-08-19・オーナー判断)。
3161
+ *
3162
+ * ## なぜ自動なのか
3163
+ *
3164
+ * 「表示名は人が決めるものではなく、Slack から引いたものをそのまま出す」と決まった。
3165
+ * 正解が1つしかないので、**人に選ばせるボタンは摩擦でしかない**(差分表示と
3166
+ * 「表示名を更新する」ボタンは撤去した。Gold トグルを撤去したときと同じ理屈)。
3167
+ *
3168
+ * ## なぜサーバの ?op=probe ではなく画面から送るのか
3169
+ *
3170
+ * **疎通確認そのものは読むだけ、を保ちたいから。** 「読むだけで設定を変えない」が
3171
+ * ボタンを誰でも押せるようにした根拠なので、?op=probe を書き込む口にはしない。
3172
+ * 書き込みは既存の ?op=config を通す——他案件チェック・コミットのリトライ・
3173
+ * 画面への反映まで、設定変更と同じ守りがそのまま効く。
3174
+ *
3175
+ * **本来の置き場は夜間の接続状況生成(engine 側)。** そちらなら人の操作と無関係に
3176
+ * 1日1回揃い、改名にも追従する。engine が今回の変更対象外なので、いまは押したときに揃える。
3149
3177
  *
3150
- * 確認(?op=probe)は読むだけなので誰でも押してよいと決めた(オーナー判断・2026-08-19)。
3151
- * 名前の更新は案件リポジトリへのコミットなので、**設定変更と同じ入口(?op=config)**に
3152
- * 通す(sendConfig)。確認のついでに黙って書き込む形にすると、「読むだけだから誰でも
3153
- * 押してよい」という前提そのものが崩れる。**押す前に、何がどう変わるかを見せる。**
3178
+ * ## 静かに失敗させる
3154
3179
  *
3155
- * 出すのは**違っているときだけ**。同じ名前のときにボタンだけ出すと、押しても
3156
- * 「変更はありません」と返るだけの操作になる。
3180
+ * 揃えるのはおまけで、押した人の目的は「読めているか確かめる」こと。
3181
+ * 失敗しても疎通確認の結果は出したままにする(名前が古いままなだけ)。
3157
3182
  */
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;
3183
+ function autoAlignName(s, r, card) {
3184
+ // 引けていない/既に同じなら何もしない(押すたびにコミットを積まない)
3185
+ if (!r.name || !s.ref) return;
3186
+ if (String(s.name || "").replace(/^#+/, "") === String(r.name)) return;
3187
+ // **channels.json に無い行は書き換えられない。** dev-sources.json だけに載っている
3188
+ // 行(origin: explicit)へ送ると必ず404になる(「登録を解除」と同じ条件)
3189
+ if (s.origin === "explicit") return;
3190
+ var intake = SITE.intake;
3191
+ if (!intake) return;
3192
+ var payload = { op: "refresh-channel-name", ref: s.ref };
3193
+ if (s.workspace) payload.workspace = s.workspace;
3194
+ var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
3195
+ fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
3196
+ method: "POST",
3197
+ headers: { "content-type": "application/json" },
3198
+ body: JSON.stringify(payload),
3199
+ }).then(function (res) {
3200
+ if (!res.ok) return;
3201
+ // 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
3202
+ var nameEl = card.querySelector(".ds-name");
3203
+ if (nameEl) nameEl.textContent = slackDisplayName(r.name);
3204
+ }).catch(function () { /* 揃えられなくても疎通確認の結果は出したまま */ });
3185
3205
  }
3186
3206
 
3187
3207
  /**
@@ -3192,9 +3212,9 @@ ${capabilityState.toString()}
3192
3212
  * になり、この節で3回直してきた食い違いを画面の側から作ることになる。
3193
3213
  * 置き換えたことが分かるよう、いつ測ったかを必ず添える。
3194
3214
  *
3195
- * **結果は保存しない。** fleet-status.json は夜間のスナップショットのまま置く
3196
- * (押すたびにコミットが増えるのを避ける)。表示名の取り直しだけが例外で、
3197
- * そちらは正本(channels.json)を直す価値があるので別のボタンで書き込む。
3215
+ * **判定は保存しない。** fleet-status.json は夜間のスナップショットのまま置く
3216
+ * (押すたびにコミットが増えるのを避ける)。表示名だけが例外で、そちらは
3217
+ * Slack の実名が正なので自動で揃える(autoAlignName)。
3198
3218
  */
3199
3219
  function probeSection(s, card, gateEl) {
3200
3220
  var wrap = el("div", { class: "ds-probe" });
@@ -3234,19 +3254,24 @@ ${capabilityState.toString()}
3234
3254
  btn.disabled = false;
3235
3255
  return;
3236
3256
  }
3237
- msg.textContent = "";
3257
+ msg.className = "ds-toggle-msg" + (j.gate === "ok" ? " is-ok" : " is-error");
3258
+ msg.textContent = j.gate === "ok" ? "✓" : "✗";
3238
3259
  var v = probeVerdict(s, j);
3239
3260
  gateEl.className = "ds-gate " + v.cls;
3240
3261
  gateEl.textContent = v.text;
3262
+ // 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
3263
+ // 本文へ落とすと、いちばん要る情報が説明文に埋もれる
3264
+ if (j.gate === "missing_scope") {
3265
+ head.appendChild(el("code", { class: "ds-probe-scope", text: j.needed || "不明" }));
3266
+ }
3241
3267
  at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
3242
3268
  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
- }
3269
+ // **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
3270
+ // 下に残ると、同じ行に2つのことが書いてある状態になる
3271
+ var stale = card.querySelector(".ds-gate-help");
3272
+ if (stale) stale.hidden = true;
3273
+ // 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)
3274
+ autoAlignName(s, j, card);
3250
3275
  } catch (e) {
3251
3276
  msg.className = "ds-toggle-msg is-error";
3252
3277
  msg.textContent = "✗ 通信に失敗しました。";
@@ -3408,11 +3433,11 @@ ${capabilityState.toString()}
3408
3433
  ]);
3409
3434
  if (!editable) return row;
3410
3435
  var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
3411
- btn.addEventListener("click", function () {
3436
+ btn.addEventListener("click", async function () {
3412
3437
  // **最後の1件かどうかは「押した時点」で数える。** 描画時の件数で決めると、
3413
3438
  // 2件を1件ずつ外したとき最後の1件で確認が出ない(Figma で踏んだのと同じ)
3414
3439
  var last = targets.length === 1;
3415
- if (!confirmDanger(removeTargetConfirmLines(t, last))) { msg.textContent = ""; return; }
3440
+ if (!(await confirmDanger(removeTargetConfirmLines(t, last), { returnTo: btn }))) { msg.textContent = ""; return; }
3416
3441
  var payload = {
3417
3442
  op: "backlog-target-remove",
3418
3443
  // **projectKey では送らない。** body の projectKey は案件キー(cortex の
@@ -3468,25 +3493,7 @@ ${capabilityState.toString()}
3468
3493
  title: "+ 接続先を追加",
3469
3494
  placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
3470
3495
  requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
3471
- hint: "Backlog でこの案件のプロジェクトを開き、ブラウザのURLをそのまま貼ってください(課題の画面のURLでも構いません)。APIキーは下の「Backlog のAPIキー」から別に登録します。",
3472
- confirmLines: function (v) {
3473
- // **解析した結果を見せる。** 貼ったURLをそのまま出すと、どのプロジェクトを
3474
- // 指すことになるかが確定前に分からない。実際 /projects/PJ-CORTEX は
3475
- // ハイフンで切れて PJ と解釈される(黙って別プロジェクトを指す)
3476
- var t = backlogTargetOf(v);
3477
- return [
3478
- "この案件が同期する Backlog プロジェクトを追加します。",
3479
- "",
3480
- t ? t.domain + " / " + t.projectKey : v,
3481
- "",
3482
- "このプロジェクトの課題・ドキュメント・Wiki が、次の同期から取り込まれます。",
3483
- "貼ったURLがこの案件のものか、もう一度確認してください。",
3484
- "",
3485
- "※ 増えるのは同期先だけです。ビューアからの起票先は変わりません。リアルタイム同期を使う場合は、追加したプロジェクトの Backlog 側にも案件専用URLの登録が要ります。",
3486
- "",
3487
- "追加しますか?",
3488
- ];
3489
- },
3496
+ hint: "Backlog でこの案件のプロジェクトを開き、ブラウザのURLをそのまま貼ってください(課題の画面のURLでも構いません)。APIキーは下の「Backlog のAPIキー」から別に登録します。増えるのは同期先だけで、ビューアからの起票先は変わりません。リアルタイム同期を使う場合は、追加したプロジェクトの Backlog 側にも案件専用URLの登録が要ります。",
3490
3497
  build: function (url) {
3491
3498
  return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
3492
3499
  },
@@ -3627,17 +3634,6 @@ ${capabilityState.toString()}
3627
3634
  namePlaceholder: "メモ(任意)",
3628
3635
  requiredNote: "フォルダのURLを入力してください。",
3629
3636
  hint: "このフォルダの中身が 共有資料 にコピーされ、Markdown 化されます。取り込み用アカウントをそのフォルダの閲覧者に招待しておいてください(招待が無いと読めません)。",
3630
- confirmLines: function (v) {
3631
- return [
3632
- "このフォルダの中身を、顧客も見るリポジトリにコピーします。",
3633
- "",
3634
- "貼ったURLがこの案件のものか、もう一度確認してください。",
3635
- "",
3636
- v,
3637
- "",
3638
- "追加しますか?",
3639
- ];
3640
- },
3641
3637
  build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
3642
3638
  }));
3643
3639
  // **ON/OFFのトグルは出さない。** フォルダを追加した時点で取り込みは始まる
@@ -3736,17 +3732,6 @@ ${capabilityState.toString()}
3736
3732
  namePlaceholder: "メモ(任意)",
3737
3733
  requiredNote: "FigmaのURLを入力してください。",
3738
3734
  hint: "このファイルの画面インベントリ(画面名・Figmaへのリンク)が毎晩同期されます。画像は取り込まれません。",
3739
- confirmLines: function (v) {
3740
- return [
3741
- "このFigmaファイルの画面名とFigmaへのリンクを、顧客も見るリポジトリに取り込みます(画像は取り込みません)。",
3742
- "",
3743
- "貼ったURLがこの案件のものか、もう一度確認してください。",
3744
- "",
3745
- v,
3746
- "",
3747
- "追加しますか?",
3748
- ];
3749
- },
3750
3735
  build: function (url, name) { return { op: "add-figma-file", url: url, name: name }; },
3751
3736
  }));
3752
3737
  used = true;
@@ -3940,30 +3925,8 @@ ${capabilityState.toString()}
3940
3925
  // **招待の話を先頭に置く。** Slack の仕様で、ボットは**公開チャンネルでも**招待されるまで
3941
3926
  // 中身を1件も読めない(conversations.history が not_in_channel を返す・2026-08-16 実測+公式ドキュメント)。
3942
3927
  // そのあとに「追加=Gold 昇格の対象」を置く(手順と、その手順の重さ)。
3943
- hint: "先に Slack でそのチャンネルを開き、下のコマンドを貼って Cortex を招待してください。公開チャンネルでも招待が必要で、招待していないと中身を読めません。そのあと、チャンネル名を右クリック →「リンクをコピー」で出るURLを下に貼ります。追加したチャンネルは毎晩の Gold 昇格の対象になり、顧客も見る Decision・用語集の材料になります(読み取りを止めるには、追加した行の「登録を解除」を押します)。招待前に登録すると名前は「名前未取得」になるので、招待してから追加するのがおすすめです。",
3928
+ hint: "先に Slack でそのチャンネルを開き、下のコマンドを貼って Cortex を招待してください。公開チャンネルでも招待が必要で、招待していないと中身を読めません。そのあと、チャンネル名を右クリック →「リンクをコピー」で出るURLを下に貼ります。追加したチャンネルは毎晩の Gold 昇格の対象になり、Decision・用語集の材料になります(読み取りを止めるには、追加した行の「登録を解除」を押します)。招待前に登録すると名前は「名前未取得」になるので、招待してから追加するのがおすすめです。",
3944
3929
  copyText: "/invite @Cortex",
3945
- // 撤去した Gold トグルの確認文の趣旨をここへ移した。**追加が昇格の意思決定そのもの**
3946
- // なので、社内限定の会話が顧客可視の層に出てしまう話はここで伝えるしかない
3947
- // (実際にその事故が起きている)。あわせて他案件の取り違えも確認させる。
3948
- confirmLines: function (v) {
3949
- return [
3950
- "このチャンネルの内容を、顧客も見る Decision・用語集に蒸留するようになります。",
3951
- "",
3952
- v,
3953
- "",
3954
- "社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。",
3955
- "顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ追加してください。",
3956
- "",
3957
- "貼ったURLがこの案件のものか、もう一度確認してください。",
3958
- "",
3959
- // Slack の仕様(公開チャンネルでも招待が要る)を、最後にもう一度置く。
3960
- // 「追加したのに何も取り込まれない」の原因のほとんどがこれ。
3961
- "※ Slack でこのチャンネルに Cortex を招待していないと、中身は読めません。",
3962
- "※ 今夜の蒸留(22時ごろ)までに登録を解除すれば取り込まれません。一度生成された記録は自動では消えません。",
3963
- "",
3964
- "追加しますか?",
3965
- ];
3966
- },
3967
3930
  // **選んだワークスペースを送る(#104)。** 空文字=社内ワークスペースなのでキーごと落とす
3968
3931
  // (サーバ側は「未宣言・null・空」を区別しない約束で、無いことで既定を表す)
3969
3932
  build: function (url, name, workspace) {
@@ -3972,6 +3935,29 @@ ${capabilityState.toString()}
3972
3935
  return payload;
3973
3936
  },
3974
3937
  workspacePicker: true,
3938
+ // **別案件でも使われていたときに出す確認(2026-08-20)。**
3939
+ //
3940
+ // **追加のときに出る唯一のモーダル。** 同日に「追加のたびの確認」を廃止した
3941
+ // (全部取り込む方針にし、顧客に見せるかは別途フラグで判定するため)ので、
3942
+ // ここで止まるのは「他の案件と重複したとき」だけ。
3943
+ // だから**何が起きるかもここで言う**(どちらの案件の顧客にも出ること)。
3944
+ //
3945
+ // **他案件の名前は出さない。** この画面の操作はその案件の Basic 認証を通れる人
3946
+ // (顧客を含む)なら誰でもできるので、案件名の列挙に使わせない。
3947
+ // **貼ったURLを本文に出す。** 「URLを確かめてください」と言うのに出ていなかった
3948
+ duplicateConfirmLines: function (value) {
3949
+ return [
3950
+ "このチャンネルは、別の案件でも追加されています。",
3951
+ "",
3952
+ "このまま追加すると、同じ会話が両方の案件の Decision・用語集に出ます。",
3953
+ "",
3954
+ "全体定例のように複数の案件に関係するチャンネルなら、これは正しい状態です。",
3955
+ "別のチャンネルと取り違えていないか、貼ったURLを確かめてください:",
3956
+ String(value || ""),
3957
+ "",
3958
+ "追加しますか?",
3959
+ ];
3960
+ },
3975
3961
  adder: "slack",
3976
3962
  }));
3977
3963
 
@@ -3982,18 +3968,6 @@ ${capabilityState.toString()}
3982
3968
  namePlaceholder: "メモ(任意)",
3983
3969
  requiredNote: "リポジトリを入力してください。",
3984
3970
  hint: "このリポジトリの Issues が、毎晩の Gold 昇格の材料になります。",
3985
- confirmLines: function (v) {
3986
- return [
3987
- "このリポジトリの Issues を、Gold 昇格の材料に追加します。",
3988
- "",
3989
- "追加した時点で読み取りの対象になります。",
3990
- "Issues の内容から、顧客も見る Decision・用語集が作られます。",
3991
- "",
3992
- v,
3993
- "",
3994
- "追加しますか?",
3995
- ];
3996
- },
3997
3971
  build: function (repo, name) { return { op: "add-github-source", repo: repo, name: name }; },
3998
3972
  adder: "github",
3999
3973
  }));
@@ -4035,6 +4009,49 @@ ${capabilityState.toString()}
4035
4009
  // 一覧が添えられていれば、断られていても先に画面を追いつかせる(#127)
4036
4010
  var raced = hooks && hooks.onTargets && Array.isArray(j.targets) ? hooks.onTargets(j.targets) : false;
4037
4011
  if (!res.ok) {
4012
+ // **確認すれば覆せる断り方(2026-08-20・Slack の重複だけ)。**
4013
+ //
4014
+ // 全体定例のように複数案件に関係するチャンネルは実在するのに、
4015
+ // 「取り違えていないか確認してください」で終わっていて**確認しても進めなかった**。
4016
+ //
4017
+ // **完全一致で見る。** j.confirmable を truthy で見ると、次に触る人が他の能力にも
4018
+ // 印を足したときに Drive でも確認が出て押せてしまう(サーバは通さないので実害は
4019
+ // 「押しても409」だが、「確認すれば通る」というUIの約束が壊れる)。
4020
+ //
4021
+ // **一度「確認済み」で送って、それでも断られたら二度目は出さない。**
4022
+ //
4023
+ // **理由を正確に書く(実装レビューの指摘)。** 「画面が先に新しくなった時間帯に
4024
+ // 押し続けることになる」は成立しない——古いサーバは印を返さないので、そもそも
4025
+ // 確認が出ない(canConfirm が最初から false)。このガードが本当に守るのは
4026
+ // **サーバを切り戻したとき**と**デプロイ中の版ズレ**。どちらも確認済みの再送だけが
4027
+ // 断られる状態で、そこで出し続けると「はい」を押し続けることになる。
4028
+ // 配る順番はサーバ→画面(これは順序の話とは別に必要)。
4029
+ var canConfirm =
4030
+ res.status === 409 &&
4031
+ j.confirmable === "duplicate" &&
4032
+ j.confirmableKind === "slack" &&
4033
+ hooks &&
4034
+ typeof hooks.onDuplicate === "function" &&
4035
+ payload.confirmDuplicate !== true;
4036
+ if (canConfirm) {
4037
+ // **ボタンは必ず先に戻す。** 確認の前に戻しておかないと、「いいえ」を押した人の
4038
+ // フォームが押せないまま残り、再読み込みするまで追加できなくなる
4039
+ btn.disabled = false;
4040
+ msg.className = "ds-toggle-msg";
4041
+ msg.textContent = "";
4042
+ if (await hooks.onDuplicate()) {
4043
+ // 「はい」——同じ内容に確認の印を付けて送り直す
4044
+ var again = {};
4045
+ for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) again[k] = payload[k];
4046
+ again.confirmDuplicate = true;
4047
+ await sendConfig(again, msg, btn, onDone, hooks);
4048
+ return;
4049
+ }
4050
+ // 「いいえ」——サーバの文言を出して終わる(ボタンは戻してある)
4051
+ msg.className = "ds-toggle-msg is-error";
4052
+ msg.textContent = "✗ " + (j.message || "保存できませんでした。");
4053
+ return;
4054
+ }
4038
4055
  // サーバの文言をそのまま出す(貼り方の案内・取り違えの警告を含んでいる)。
4039
4056
  // **例外は「他の人が先に変えていた」だけ**——サーバは再読み込みを案内するが、
4040
4057
  // 画面は既に追いついているので、済んでいることをやらせる案内になる(#127)
@@ -4080,9 +4097,116 @@ ${capabilityState.toString()}
4080
4097
  * 書いていたが、取り違えはサーバ側が409で実際に弾く(findRefInOtherProjects)。
4081
4098
  * 実在する守りがあるのに脅し文を重ねると、操作者が萎縮するだけで安全は増えない。
4082
4099
  * 「貼ったURLがこの案件のものか確認してください」で足りる。
4100
+ *
4101
+ * **ただし Slack のチャンネルだけは、この前提が成り立たなくなった(2026-08-20)。**
4102
+ * 重複は 409 で断られたあと**確認すれば覆せる**ので、「サーバが実際に弾く」とは
4103
+ * 言えない。だから2枚目の確認(duplicateConfirmLines)では、重複そのものの事実と
4104
+ * 「両方の案件の顧客が見る場所に出る」ことをはっきり書いている——**そこは脅し文ではなく、
4105
+ * 押す前に知っておくべき事実**。1枚目(confirmLines)は変えていない。
4106
+ * ※ この region はテンプレート文字列の中なので、コメントにもバッククォートを書かない。
4083
4107
  */
4084
- function confirmDanger(lines) {
4085
- return window.confirm(lines.join(String.fromCharCode(10)));
4108
+ function confirmDanger(lines, opts) {
4109
+ return openConfirm(lines, opts);
4110
+ }
4111
+
4112
+ var confirmSeq = 0;
4113
+
4114
+ /**
4115
+ * 画面内の確認モーダル。**window.confirm の置き換え(2026-08-20)。**
4116
+ * 返すのは Promise で、中身は「進めてよいか」。
4117
+ *
4118
+ * ## なぜ makeModal を使わないのか(一番大事)
4119
+ *
4120
+ * **makeModal は開いているモーダルを畳む**(existing.__close(true))。
4121
+ * ところが確認が要る5か所は**すべて能力モーダルの中にいる**——
4122
+ * openCapabilityModal の items.forEach(m.modal.appendChild(n)) は
4123
+ * **ノードの移動**なので、利用者が「追加」を押した時点でフォームはモーダルの子。
4124
+ * makeModal で確認を開くと**親ごと消え、フォームが無くなる**。
4125
+ * しかも sendConfig の再送は掴んだままの msg/btn に書くので、
4126
+ * **追加は成功するのに画面には何も起きない**(利用者はもう一度押す)。
4127
+ *
4128
+ * makeModal の close() は refreshDataSources と能力モーダル専用の戻り先処理に
4129
+ * 絡んでいるため、フラグで分岐させると**あちらを壊すリスクをこちらの都合で背負う**。
4130
+ * 確認は「短い文と2つのボタン」しかないので、小さく別に持つ。
4131
+ *
4132
+ * ## なぜクラスを .modal-overlay にしないのか
4133
+ *
4134
+ * isModalOpen()・expandAllCaps・openCapModal がいずれも .modal-overlay を
4135
+ * 見ている。名前を共有すると「モーダルが開いている間は一覧を差し替えない」等の
4136
+ * 判定に確認が混ざる。見た目だけ CSS のカンマセレクタで揃える。
4137
+ *
4138
+ * ## なぜ Esc を capture で受けて止めるのか
4139
+ *
4140
+ * **makeModal は開いている間ずっと document に keydown を張ったままにする。**
4141
+ * inert は document レベルの keydown を止めない。素直に足すと
4142
+ * **親のリスナが先に登録済みなので先に発火し、Esc 一発で親モーダルが閉じる**
4143
+ * (=上と同じ破壊が Esc 経由で起きる)。capture で受けて stopPropagation すれば
4144
+ * 同一ノードの bubble リスナ=親の onKey には届かない。
4145
+ * オーバーレイ要素だけにリスナを張るのでは足りない(フォーカスが body にあると経路を通らない)。
4146
+ *
4147
+ * @param lines 表示する行(1行1段落)。先頭行が主文=読み上げの名前になる
4148
+ * @param opts { okLabel, returnTo }
4149
+ */
4150
+ function openConfirm(lines, opts) {
4151
+ return new Promise(function (resolve) {
4152
+ // **多重オープンはキャンセル扱いで無音。** 下地を inert にするので通常は起きない。
4153
+ // ここで警告を出しても操作者にできることが無いし、2枚重なって
4154
+ // 「下の1枚が residual で操作を受け付けない」状態のほうが害が大きい
4155
+ if (app.querySelector(".confirm-overlay")) { resolve(false); return; }
4156
+ var o = opts || {};
4157
+ var overlay = el("div", { class: "confirm-overlay" });
4158
+ // 確認は dialog ではなく alertdialog(「注意を促して答えを待つ」もの)
4159
+ var box = el("div", { class: "modal confirm-box", role: "alertdialog", "aria-modal": "true" });
4160
+ confirmSeq += 1;
4161
+ var textId = "confirm-line-" + confirmSeq;
4162
+ (lines || []).forEach(function (line, i) {
4163
+ var pnode = el("p", { class: "confirm-line", text: line });
4164
+ // 先頭行を読み上げの名前にする。見出しは付けない(主文を2回言うことになる)
4165
+ if (i === 0) { pnode.id = textId; box.setAttribute("aria-labelledby", textId); }
4166
+ box.appendChild(pnode);
4167
+ });
4168
+ var cancel = el("button", { class: "modal-cancel", type: "button", text: "キャンセル" });
4169
+ var ok = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: o.okLabel || "はい" });
4170
+ box.appendChild(el("div", { class: "modal-actions" }, [cancel, ok]));
4171
+ overlay.appendChild(box);
4172
+
4173
+ function finish(answer) {
4174
+ document.removeEventListener("keydown", onKey, true);
4175
+ // **順序が仕様。** ①自分をDOMから外す → ②inertを全部外す →
4176
+ // ③親が残っていれば掛け直す → ④それからフォーカスを戻す
4177
+ if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
4178
+ setBackdropInert(overlay, false);
4179
+ // **親モーダルはまだ開いている。** 全部外したままにすると、その後ろの画面が
4180
+ // 触れるようになり Tab でタブ行へ抜けられる(別タブに移ると close() を
4181
+ // 通らないので keydown が1本残る)
4182
+ var rest = app.querySelector(".modal-overlay");
4183
+ if (rest) setBackdropInert(rest, true);
4184
+ // **戻し先は呼び出し側からもらう。** activeElement を記録する方式は破綻する——
4185
+ // sendConfig は冒頭で btn.disabled = true にしており、実ブラウザでは
4186
+ // フォーカス中の要素を disabled にした時点でフォーカスが body に落ちる。
4187
+ // disabled を戻してもフォーカスは戻らない。**jsdom は落とさないのでテストから見えない。**
4188
+ // Safari はクリックでボタンにフォーカスを当てないので、なおさら明示が要る
4189
+ var back = o.returnTo;
4190
+ if (back && typeof back.focus === "function" && app.contains(back)) back.focus();
4191
+ resolve(answer);
4192
+ }
4193
+ function onKey(e) {
4194
+ if (e.key !== "Escape") return;
4195
+ e.stopPropagation();
4196
+ e.preventDefault();
4197
+ finish(false);
4198
+ }
4199
+ document.addEventListener("keydown", onKey, true);
4200
+ // 外側クリックとキャンセルは「いいえ」。**確認は安全側に倒す**
4201
+ overlay.addEventListener("click", function (e) { if (e.target === overlay) finish(false); });
4202
+ cancel.addEventListener("click", function () { finish(false); });
4203
+ ok.addEventListener("click", function () { finish(true); });
4204
+
4205
+ app.appendChild(overlay);
4206
+ setBackdropInert(overlay, true);
4207
+ // **キャンセルに載せる**(showModal と同じ「押しても何も壊れないものへ」の原則)
4208
+ cancel.focus();
4209
+ });
4086
4210
  }
4087
4211
 
4088
4212
  /**
@@ -4100,10 +4224,10 @@ ${capabilityState.toString()}
4100
4224
  };
4101
4225
  paint();
4102
4226
 
4103
- btn.addEventListener("click", function () {
4227
+ btn.addEventListener("click", async function () {
4104
4228
  var next = !on;
4105
4229
  // 読み始める方向(危険)だけ確認する。止める方向は素通し
4106
- if (next && opts.confirmOn && !confirmDanger(opts.confirmOn)) { msg.textContent = ""; return; }
4230
+ if (next && opts.confirmOn && !(await confirmDanger(opts.confirmOn, { returnTo: btn }))) { msg.textContent = ""; return; }
4107
4231
  sendConfig(opts.build(next), msg, btn, function () {
4108
4232
  // **押した内容をボタンに反映し、逆向きの操作を残す。**
4109
4233
  // **カードごと作り直さない。** 上の msg に出した結果表示が消えてしまう
@@ -4130,8 +4254,8 @@ ${capabilityState.toString()}
4130
4254
  var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: opts.label || "削除" });
4131
4255
  var msg = el("div", { class: "ds-toggle-msg" });
4132
4256
 
4133
- btn.addEventListener("click", function () {
4134
- if (!confirmDanger(opts.confirmLines())) { msg.textContent = ""; return; }
4257
+ btn.addEventListener("click", async function () {
4258
+ if (!(await confirmDanger(opts.confirmLines(), { returnTo: btn }))) { msg.textContent = ""; return; }
4135
4259
  sendConfig(opts.build(), msg, btn, function () {
4136
4260
  btn.disabled = true;
4137
4261
  if (opts.onDone) opts.onDone();
@@ -4150,7 +4274,7 @@ ${capabilityState.toString()}
4150
4274
  * 2つ目を足せると読めるため、そういう口だけ submitLabel で語を変える。押した結果の
4151
4275
  * 表示(+/追加済み)も同じ理由で語を揃える。
4152
4276
  *
4153
- * @param opts { title, placeholder, hint, needsName, confirmLines, build(values), submitLabel, hooks }
4277
+ * @param opts { title, placeholder, hint, needsName, build(values), submitLabel, hooks }
4154
4278
  * hooks … sendConfig へ渡す追い付き処理を返す関数(接続先の一覧・#127)。
4155
4279
  * **これがある口では、その場の控え(「追加済み・反映中」)を出さない**——
4156
4280
  * 応答で本物の一覧が増えるので、控えを併記すると同じものが2回出る
@@ -4182,7 +4306,7 @@ ${capabilityState.toString()}
4182
4306
  var btn = el("button", { class: "ds-toggle-btn", type: "button", text: submitLabel });
4183
4307
  var msg = el("div", { class: "ds-toggle-msg" });
4184
4308
 
4185
- btn.addEventListener("click", function () {
4309
+ btn.addEventListener("click", async function () {
4186
4310
  if (!main.value.trim()) {
4187
4311
  msg.className = "ds-toggle-msg is-error";
4188
4312
  msg.textContent = "⚠ " + (opts.requiredNote || "入力してください。");
@@ -4193,7 +4317,6 @@ ${capabilityState.toString()}
4193
4317
  msg.textContent = "⚠ 表示名を入力してください。";
4194
4318
  return;
4195
4319
  }
4196
- if (opts.confirmLines && !confirmDanger(opts.confirmLines(main.value.trim()))) { msg.textContent = ""; return; }
4197
4320
  var added = main.value.trim();
4198
4321
  var addedName = name ? name.value.trim() : "";
4199
4322
  var payload = opts.build(added, addedName, wsSelect ? wsSelect.value : "");
@@ -4214,7 +4337,28 @@ ${capabilityState.toString()}
4214
4337
  el("span", { class: "ds-pending-note", text: submitLabel + "済み・反映中" }),
4215
4338
  ]),
4216
4339
  );
4217
- }, opts.hooks ? opts.hooks() : null);
4340
+ }, (function () {
4341
+ // **既存の hooks(接続先の一覧が持つ onTargets)と、重複の確認を1つにまとめる。**
4342
+ // Slack の追加フォームは onTargets を持たないが、重複の確認は要る。
4343
+ // 逆に接続先の一覧(Backlog)は onTargets を持つが確認は要らない——
4344
+ // 共通に足すと、あちらの「他の人が先に変えていた」の判定と混ざる。
4345
+ var h = opts.hooks ? opts.hooks() : null;
4346
+ if (!opts.duplicateConfirmLines) return h;
4347
+ h = h || {};
4348
+ // **返り値が「進めてよいか」(Promise)。** 呼び出し側は必ず await すること——
4349
+ // await を落とすと Promise は truthy なので**確認なしで「はい」扱いになる**。
4350
+ // 2026-08-20 に window.confirm から画面内のモーダルに変えた(openConfirm)。
4351
+ // **戻し先に btn を渡す。** sendConfig が冒頭で btn.disabled = true にしており、
4352
+ // 実ブラウザではその時点でフォーカスが body に落ちるので、
4353
+ // activeElement を記録する方式では戻し先が消える
4354
+ h.onDuplicate = function () {
4355
+ return confirmDanger(opts.duplicateConfirmLines(main.value.trim()), {
4356
+ okLabel: "追加する",
4357
+ returnTo: btn,
4358
+ });
4359
+ };
4360
+ return h;
4361
+ })());
4218
4362
  });
4219
4363
 
4220
4364
  // このセッションで追加したものを並べる場所(リロードすると消える=正式な一覧は上に出る)
@@ -4327,7 +4471,8 @@ ${capabilityState.toString()}
4327
4471
  el("div", { class: "ds-card-main" }, [
4328
4472
  el("span", { class: "ds-icon", text: meta.icon }),
4329
4473
  el("span", { class: "ds-type", text: meta.label }),
4330
- dsNameEl(s.name || "", s.url),
4474
+ // Slack だけ # を表示側で付ける(保存値は素)。他のアダプタの名前には触らない
4475
+ dsNameEl(s.type === "slack" ? slackDisplayName(s.name) : (s.name || ""), s.url),
4331
4476
  ]),
4332
4477
  badges,
4333
4478
  gateEl,
@@ -4352,7 +4497,7 @@ ${capabilityState.toString()}
4352
4497
  confirmOn: [
4353
4498
  "このリポジトリの Issues を、Gold 昇格の材料に戻します。",
4354
4499
  "",
4355
- "Issues の内容から、顧客も見る Decision・用語集が作られます。",
4500
+ "Issues の内容から、Decision・用語集が作られます。",
4356
4501
  "",
4357
4502
  s.ref,
4358
4503
  "",
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.85",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,