@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.
- package/dist/render.js +330 -185
- 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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
3104
|
-
|
|
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
|
-
//
|
|
3113
|
-
|
|
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
|
-
//
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
return
|
|
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
|
-
|
|
3135
|
-
"Slack でこのチャンネルがまだ存在するかを確かめてください。" +
|
|
3136
|
-
"アーカイブしたチャンネルなら、この行の「登録を解除」で読み取り対象から外せます。" +
|
|
3137
|
-
"存在するのに見つからない場合は、Cortex が入っていない別ワークスペースのチャンネルの可能性があるので、" +
|
|
3138
|
-
"cortex の担当者にご連絡ください。"));
|
|
3139
|
-
return out;
|
|
3153
|
+
return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
|
|
3140
3154
|
}
|
|
3141
|
-
// unknown
|
|
3142
|
-
|
|
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
|
-
*
|
|
3151
|
-
* 名前の更新は案件リポジトリへのコミットなので、**設定変更と同じ入口(?op=config)**に
|
|
3152
|
-
* 通す(sendConfig)。確認のついでに黙って書き込む形にすると、「読むだけだから誰でも
|
|
3153
|
-
* 押してよい」という前提そのものが崩れる。**押す前に、何がどう変わるかを見せる。**
|
|
3178
|
+
* ## 静かに失敗させる
|
|
3154
3179
|
*
|
|
3155
|
-
*
|
|
3156
|
-
*
|
|
3180
|
+
* 揃えるのはおまけで、押した人の目的は「読めているか確かめる」こと。
|
|
3181
|
+
* 失敗しても疎通確認の結果は出したままにする(名前が古いままなだけ)。
|
|
3157
3182
|
*/
|
|
3158
|
-
function
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
var
|
|
3166
|
-
|
|
3167
|
-
var
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
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
|
-
*
|
|
3196
|
-
*
|
|
3197
|
-
*
|
|
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.
|
|
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
|
-
//
|
|
3244
|
-
//
|
|
3245
|
-
|
|
3246
|
-
if (
|
|
3247
|
-
|
|
3248
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
|
4500
|
+
"Issues の内容から、Decision・用語集が作られます。",
|
|
4356
4501
|
"",
|
|
4357
4502
|
s.ref,
|
|
4358
4503
|
"",
|
package/package.json
CHANGED