@takagaki/cortex-decisions-viewer 0.4.85 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.js +3 -0
- package/dist/render.js +478 -183
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -152,6 +152,9 @@ async function readFleetSources(repoRoot) {
|
|
|
152
152
|
// (このマッピングは素通しではなく明示の許可リスト)。落とすと、同じIDの
|
|
153
153
|
// チャンネルを2つのワークスペースに持つ案件で「登録を解除」が別の行を消す
|
|
154
154
|
workspace: s.workspace != null ? String(s.workspace) : undefined,
|
|
155
|
+
// **厳密比較。** サーバは true しか書かないので、
|
|
156
|
+
// 他の値("true" や 1)を印として扱わない
|
|
157
|
+
orgUnknown: s.orgUnknown === true ? true : undefined,
|
|
155
158
|
}))
|
|
156
159
|
: undefined;
|
|
157
160
|
// 接続済み Slack ワークスペース(#104)。**空配列と undefined を区別して通す**——
|
package/dist/render.js
CHANGED
|
@@ -1151,6 +1151,26 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1151
1151
|
.ds-group[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
1152
1152
|
.ds-group > .ds-list { margin-top: 8px; padding-left: 12px; border-left: 2px solid var(--border); }
|
|
1153
1153
|
.badge.ds-sum-ok { background: #e9f7ef; color: #1f7a45; }
|
|
1154
|
+
/* 「断定しない」——古い fleet-status では接続状態が分からない。OKとも警告とも言わない */
|
|
1155
|
+
.ds-sum-unknown { background: var(--bg); color: var(--muted); }
|
|
1156
|
+
/* 組織が分かっていない行のラベル。**警告色にする**(並びの中で見つかる必要がある) */
|
|
1157
|
+
.badge.ds-workspace-unknown { border-color: #f0b429; color: #8a5a00; background: #fff8e6; }
|
|
1158
|
+
/* 組織についての案内。行ではないので、行と見分けが付く形にする */
|
|
1159
|
+
.ds-orgnote {
|
|
1160
|
+
border: 1px solid #f0b429; background: #fffdf5; border-radius: 12px;
|
|
1161
|
+
padding: 12px 16px; display: flex; flex-direction: column; gap: 8px;
|
|
1162
|
+
}
|
|
1163
|
+
.ds-orgnote-title { font-weight: 700; font-size: 13.5px; color: #8a5a00; }
|
|
1164
|
+
/* 案内が指す行。**囲うことで「どのチャンネルの話か」を示す**(開閉はしない) */
|
|
1165
|
+
.ds-orgnote-rows { display: flex; flex-direction: column; gap: 8px; margin-top: 2px; }
|
|
1166
|
+
/* 手順のリスト。番号を出し、1手ずつ離す(一続きの文章にしない) */
|
|
1167
|
+
.sec-steps { margin: 0; padding-left: 1.35em; display: flex; flex-direction: column; gap: 8px; }
|
|
1168
|
+
.sec-steps li { line-height: 1.75; }
|
|
1169
|
+
.sec-steps li > span { display: block; }
|
|
1170
|
+
.ds-group-body { display: flex; flex-direction: column; gap: 10px; padding: 4px 0 2px; }
|
|
1171
|
+
/* 組のまとまりを、行より弱い線で示す */
|
|
1172
|
+
.ds-group-note { font-size: 13px; color: var(--muted); line-height: 1.7; margin: 0; }
|
|
1173
|
+
.ds-group-note strong { color: var(--text); }
|
|
1154
1174
|
.badge.ds-sum-warn { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
|
|
1155
1175
|
/* ---- 接続マップ(操作タブ最上部のSVG図) ---- */
|
|
1156
1176
|
.conn-map { overflow-x: auto; margin: 4px 0 20px; }
|
|
@@ -1298,7 +1318,13 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1298
1318
|
夜間のスナップショットの状態バッジと**同時に2つの判定を並べない**ため、押したあとは
|
|
1299
1319
|
状態バッジをこの中の判定へ置き換える(同じ画面で「⚠️」と「接続OK」が並ぶのが、
|
|
1300
1320
|
この節で3回直してきた壊れ方そのもの) */
|
|
1301
|
-
|
|
1321
|
+
/* 押した結果の詳細。**押されるまでは何も出さない。**
|
|
1322
|
+
以前は中身が空でも点線だけが全行に出ており、何も意味していなかった(2026-08-20) */
|
|
1323
|
+
.ds-probe { display: none; }
|
|
1324
|
+
.ds-probe.is-shown {
|
|
1325
|
+
display: block; flex: 1 1 100%;
|
|
1326
|
+
margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border);
|
|
1327
|
+
}
|
|
1302
1328
|
.ds-probe-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1303
1329
|
/* いつ測った結果かを必ず添える。夜間のスナップショットと混ざると、どちらの話か読めない */
|
|
1304
1330
|
.ds-probe-at { font-size: 11.5px; color: var(--muted); }
|
|
@@ -2056,7 +2082,7 @@ ${capabilityState.toString()}
|
|
|
2056
2082
|
* Slack のチャンネルは、どのワークスペースにあるかで読み取りの鍵が別物になる:
|
|
2057
2083
|
* - workspace 省略 … クラスメソッドの社内ワークスペース。中央 Bot の鍵(案件リポジトリの
|
|
2058
2084
|
* 設定)で読む。**画面から設定できるものが1つも無い。**
|
|
2059
|
-
* - workspace: "T…" …
|
|
2085
|
+
* - workspace: "T…" … お客様のワークスペース。「Slack ワークスペースに接続する」で
|
|
2060
2086
|
* 承認を貰って読む。**画面から自己解決できる。**
|
|
2061
2087
|
*
|
|
2062
2088
|
* どちらも no_token に潰して「トークン未設定」と出していたため、社内チャンネルを
|
|
@@ -2073,13 +2099,17 @@ ${capabilityState.toString()}
|
|
|
2073
2099
|
internal: {
|
|
2074
2100
|
text: "管理者の設定が必要です",
|
|
2075
2101
|
cls: "ds-gate-muted",
|
|
2076
|
-
|
|
2102
|
+
// **「この画面では設定できません」を必ず言う(2026-08-18 の不具合)。**
|
|
2103
|
+
// 状態だけを出していたので、設定する場所を探した人が
|
|
2104
|
+
// 顧客ワークスペースの接続へ吸い込まれ、押しても必ず失敗する画面を
|
|
2105
|
+
// 何度も試した。**次に誰が何をするか**まで書く
|
|
2106
|
+
help: "このチャンネルはクラスメソッドの社内ワークスペースにあります。読み取りを始めるには cortex 側(管理者)の設定が必要で、この画面では設定できません。cortex の担当者にご連絡ください。",
|
|
2077
2107
|
},
|
|
2078
2108
|
// お客様のワークスペース。従来どおり接続へ誘導する(自己解決できる唯一の経路)
|
|
2079
2109
|
customer: {
|
|
2080
2110
|
text: "トークン未設定",
|
|
2081
2111
|
cls: "ds-gate-muted",
|
|
2082
|
-
help: "このチャンネルがあるお客様の Slack
|
|
2112
|
+
help: "このチャンネルがあるお客様の Slack ワークスペースが、まだ接続されていない(または接続が切れている)状態です。「Slack ワークスペースに接続する」から接続してください。",
|
|
2083
2113
|
},
|
|
2084
2114
|
};
|
|
2085
2115
|
/**
|
|
@@ -2133,6 +2163,331 @@ ${capabilityState.toString()}
|
|
|
2133
2163
|
var allInternal = noToken.every(function (s) { return isInternalSlack(s); });
|
|
2134
2164
|
return head + "が読めていません(" + (allInternal ? "管理者の設定が必要です" : "トークン未設定") + ")";
|
|
2135
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Slack のチャンネルを、所属する組織ごとに分ける(2026-08-20)。
|
|
2168
|
+
*
|
|
2169
|
+
* ## なぜ純関数にするか
|
|
2170
|
+
*
|
|
2171
|
+
* ここが**判断の全部**。描画に混ぜると「どう分けたか」を実測できなくなる。
|
|
2172
|
+
* sourceGroup は受け取ったものを並べるだけで、意味を持たない
|
|
2173
|
+
* (GitHub は組織・Backlog はスペースで同じ形が要るため)。
|
|
2174
|
+
*
|
|
2175
|
+
* ## 分け方
|
|
2176
|
+
*
|
|
2177
|
+
* 1. workspace(team_id)があれば、その組織。表示名は台帳から引く
|
|
2178
|
+
* 2. orgUnknown の印が付いていれば「組織が分かりません」
|
|
2179
|
+
* 3. どちらも無ければ社内(クラスメソッド)
|
|
2180
|
+
*
|
|
2181
|
+
* **印を先に見ない。** 判定が済んで workspace が入った行にも、
|
|
2182
|
+
* 古いデータでは印が残っているかもしれない——**決まっている方を信じる**。
|
|
2183
|
+
*
|
|
2184
|
+
* ## 「分かりません」に入れる条件(ここが誤りやすい)
|
|
2185
|
+
*
|
|
2186
|
+
* **印が唯一の目印。** 夜間の実測(gate: "unreachable")で判断してはいけない——
|
|
2187
|
+
* unreachable はアーカイブ・削除・URLの打ち間違いでも起きるので、
|
|
2188
|
+
* それを「組織が分かりません」と出すと**誤った案内**になる。
|
|
2189
|
+
* 印はサーバが「全部の鍵で届かなかった」と実測した記録なので、意味が違う。
|
|
2190
|
+
*
|
|
2191
|
+
* @param items チャンネルの行(fleet-status の externalSources のうち type === "slack")
|
|
2192
|
+
* @param workspaces 台帳(slackWorkspaces() の返り値。null なら「知らない」)
|
|
2193
|
+
* @returns [{ key, title, items, unknown }] key は並びの安定のためだけに使う
|
|
2194
|
+
*/
|
|
2195
|
+
function groupSlackByOrg(items, workspaces) {
|
|
2196
|
+
var known = {};
|
|
2197
|
+
(workspaces || []).forEach(function (w) { if (w && w.teamId) known[w.teamId] = w.name || w.teamId; });
|
|
2198
|
+
var order = [];
|
|
2199
|
+
var byKey = {};
|
|
2200
|
+
(items || []).forEach(function (s) {
|
|
2201
|
+
var key, title, unknown = false;
|
|
2202
|
+
if (s.workspace) {
|
|
2203
|
+
key = s.workspace;
|
|
2204
|
+
// **台帳に無ければ team_id を出す。** 空欄より、少なくとも突き合わせられる
|
|
2205
|
+
title = known[s.workspace] || s.workspace;
|
|
2206
|
+
} else if (s.orgUnknown === true) {
|
|
2207
|
+
key = "__unknown__";
|
|
2208
|
+
title = "組織不明";
|
|
2209
|
+
unknown = true;
|
|
2210
|
+
} else {
|
|
2211
|
+
key = "__internal__";
|
|
2212
|
+
title = "クラスメソッド";
|
|
2213
|
+
}
|
|
2214
|
+
var g = byKey[key];
|
|
2215
|
+
if (!g) { g = { key: key, title: title, items: [], unknown: unknown }; byKey[key] = g; order.push(g); }
|
|
2216
|
+
g.items.push(s);
|
|
2217
|
+
});
|
|
2218
|
+
// **社内を先、「分かりません」を最後にする。** 分かりませんが先頭に来ると、
|
|
2219
|
+
// 直すものが無い人にも毎回それが最初に見える
|
|
2220
|
+
order.sort(function (a, b) {
|
|
2221
|
+
var rank = function (g) { return g.key === "__internal__" ? 0 : g.unknown ? 2 : 1; };
|
|
2222
|
+
return rank(a) - rank(b);
|
|
2223
|
+
});
|
|
2224
|
+
return order;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* 会議の取り込みの案内。**操作ではなく条件を示す。**
|
|
2229
|
+
*
|
|
2230
|
+
* 取り込まれるには2つとも要る:
|
|
2231
|
+
* ① cortex-notetaker Bot が会議に招待されている(=取り込む意思。これが同意のゲート)
|
|
2232
|
+
* ② 会議名が照合キーのどれかを含む(含まなければ、録れても案件に振り分けられない)
|
|
2233
|
+
*
|
|
2234
|
+
* ②の材料を画面が出さないと、招待したのに届かない理由が分からない。
|
|
2235
|
+
*/
|
|
2236
|
+
function meetingHowTo() {
|
|
2237
|
+
// **2つの条件がANDであることを、読み飛ばせない形で出す。** 散文で並べていたときに
|
|
2238
|
+
// 「会議名は合っているのにBotを招待しておらず取り込まれなかった」事故が実際に起きた。
|
|
2239
|
+
// 会議名の条件は上の行(ds-keys)が出すので、ここでは「上の語」と参照するに留める。
|
|
2240
|
+
var wrap = el("div", { class: "sec-how" });
|
|
2241
|
+
wrap.appendChild(el("p", { text: "次の2つが揃った会議だけが取り込まれます。" }));
|
|
2242
|
+
var ul = el("ul", { class: "sec-how-list" });
|
|
2243
|
+
ul.appendChild(el("li", { text: "cortex-notetaker Bot が招待されている(定例はシリーズに1回でOK)" }));
|
|
2244
|
+
ul.appendChild(el("li", { text: "会議名に上の目印が入っている" }));
|
|
2245
|
+
wrap.appendChild(ul);
|
|
2246
|
+
wrap.appendChild(el("p", { text: "どちらか一方だけでは取り込まれません。" }));
|
|
2247
|
+
return wrap;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
/**
|
|
2251
|
+
* Slack の承認画面へ進むボタン(2026-08-20 に接続カードから切り出した)。
|
|
2252
|
+
*
|
|
2253
|
+
* ## 押す人についての注意を必ず添える
|
|
2254
|
+
*
|
|
2255
|
+
* **承認画面はワークスペースを指定できない**(開始の口に team を渡す口が無い)。
|
|
2256
|
+
* **承認する人がログインしているワークスペース**にボットが入る。だから
|
|
2257
|
+
* 「〈お客様の名前〉と接続する」とは書けない——保証できないことを書かない。
|
|
2258
|
+
*
|
|
2259
|
+
* お客様の Slack なら、クラスメソッドの人間は押せないことが多い
|
|
2260
|
+
* (その Slack の管理者にお願いすることになる)。それを書かないと、
|
|
2261
|
+
* 押しても進まない人が「壊れている」と読む。
|
|
2262
|
+
*/
|
|
2263
|
+
function slackConnectButton() {
|
|
2264
|
+
var wrap = el("div", { class: "ds-toggle" });
|
|
2265
|
+
// **「この」を付けない(2026-08-20・オーナー指摘)。**
|
|
2266
|
+
// どのワークスペースを指すのか読み取れない——そもそも**指定できない**
|
|
2267
|
+
// (承認する人がログインしているワークスペースに入る)ので、
|
|
2268
|
+
// 特定のものを指すような書き方は嘘にもなる
|
|
2269
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "Slack ワークスペースに接続する" });
|
|
2270
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
2271
|
+
btn.addEventListener("click", async function () {
|
|
2272
|
+
var intake = SITE.intake;
|
|
2273
|
+
if (!intake) return;
|
|
2274
|
+
btn.disabled = true;
|
|
2275
|
+
msg.className = "ds-toggle-msg";
|
|
2276
|
+
msg.textContent = "承認画面を準備しています…";
|
|
2277
|
+
try {
|
|
2278
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
2279
|
+
var res = await fetch(pfx + "op=slack-oauth-start&t=" + encodeURIComponent(intake.token), {
|
|
2280
|
+
method: "POST",
|
|
2281
|
+
headers: { "content-type": "application/json" },
|
|
2282
|
+
body: "{}",
|
|
2283
|
+
});
|
|
2284
|
+
var j = await res.json().catch(function () { return {}; });
|
|
2285
|
+
if (!res.ok || !j.url) {
|
|
2286
|
+
// サーバの文言をそのまま出す(未設定のときは「準備中です」が返る)
|
|
2287
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2288
|
+
msg.textContent = "✗ " + (j.message || "接続を開始できませんでした。");
|
|
2289
|
+
btn.disabled = false;
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
// **同じタブで遷移する。** 別タブだと、承認後の「接続しました」を見た人が
|
|
2293
|
+
// 元のタブに戻る動線を自分で見つけることになる
|
|
2294
|
+
location.href = j.url;
|
|
2295
|
+
} catch (e) {
|
|
2296
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2297
|
+
msg.textContent = "✗ 接続を開始できませんでした。時間をおいて試してください。";
|
|
2298
|
+
btn.disabled = false;
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
wrap.appendChild(el("p", {
|
|
2302
|
+
class: "ds-group-note",
|
|
2303
|
+
text: "ボタンを押すと Slack の承認画面が開きます。承認できるのは、その Slack の管理者だけです"
|
|
2304
|
+
+ "(お客様の Slack なら、お客様の管理者にお願いしてください)。"
|
|
2305
|
+
+ "お願いする権限は読み取りだけで、Cortex から投稿することはありません。",
|
|
2306
|
+
}));
|
|
2307
|
+
wrap.appendChild(btn);
|
|
2308
|
+
wrap.appendChild(msg);
|
|
2309
|
+
return wrap;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
/** 台帳から、そのワークスペースの接続状態を引く(無ければ null=断定しない) */
|
|
2313
|
+
function tokenStateOf(workspaces, teamId) {
|
|
2314
|
+
var list = workspaces || [];
|
|
2315
|
+
for (var i = 0; i < list.length; i++) {
|
|
2316
|
+
if (list[i] && list[i].teamId === teamId) return list[i].tokenState || null;
|
|
2317
|
+
}
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
/**
|
|
2322
|
+
* 「組織が分かりません」の行を、サーバに判定し直してもらう(2026-08-20)。
|
|
2323
|
+
*
|
|
2324
|
+
* **1回の表示につき1度だけ。** 組が複数あっても、呼ぶのは1回。
|
|
2325
|
+
*
|
|
2326
|
+
* ## 結果をその場で反映しない理由
|
|
2327
|
+
*
|
|
2328
|
+
* この画面が読んでいるのは**夜間に作られた実測ファイル**で、
|
|
2329
|
+
* 設定ファイルを直しても表示はすぐ変わらない。**嘘をつくよりは待たせる**——
|
|
2330
|
+
* 「反映しました」とだけ言い、表示が変わる時期を書く。
|
|
2331
|
+
* (設定を変える他の操作も同じ約束で「次回の Gold 昇格から反映されます」と言っている)
|
|
2332
|
+
*/
|
|
2333
|
+
var orgResolveDone = false;
|
|
2334
|
+
function requestOrgResolve() {
|
|
2335
|
+
if (orgResolveDone) return;
|
|
2336
|
+
orgResolveDone = true;
|
|
2337
|
+
var intake = SITE.intake;
|
|
2338
|
+
if (!intake) return;
|
|
2339
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
2340
|
+
// **描画の途中で投げない。** ここは行を組み立てている最中に呼ばれるので、
|
|
2341
|
+
// 例外が出ると**画面そのものが描かれない**(fetch が無い環境で実際にそうなった)。
|
|
2342
|
+
// 判定し直しは「できたら直る」性質のもので、失敗しても行は
|
|
2343
|
+
// 「組織が分かりません」のまま残る——画面を落としてまでやることではない。
|
|
2344
|
+
//
|
|
2345
|
+
// **守りは二重(この行と下の try/catch)。** 片方だけ消しても
|
|
2346
|
+
// もう片方が受けるので変異検証では赤にならないが、**両方消すと6件赤**になる
|
|
2347
|
+
// (実際に当てて確かめた)。二重なのは、投げ方が2通りあるため——
|
|
2348
|
+
// fetch が存在しない(ReferenceError)と、呼び出しが投げる
|
|
2349
|
+
if (typeof fetch !== "function") return;
|
|
2350
|
+
try {
|
|
2351
|
+
fetch(pfx + "op=slack-resolve&t=" + encodeURIComponent(intake.token), {
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers: { "content-type": "application/json" },
|
|
2354
|
+
body: "{}",
|
|
2355
|
+
})
|
|
2356
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
2357
|
+
.then(function (j) {
|
|
2358
|
+
if (!j || !j.resolved) return;
|
|
2359
|
+
// **表示は次の更新まで変わらない。** それを書かないと、
|
|
2360
|
+
// 「反映したのに何も変わらない」と読まれてもう一度押すことになる
|
|
2361
|
+
var note = document.querySelector(".ds-org-resolved");
|
|
2362
|
+
if (!note) return;
|
|
2363
|
+
note.textContent = "✓ " + j.resolved + "件の所属を反映しました(この一覧の表示は次回の更新・毎晩22時ごろから正しくなります)。";
|
|
2364
|
+
})
|
|
2365
|
+
.catch(function () {
|
|
2366
|
+
// **黙って諦める。** 直せなかっただけで、行は「組織が分かりません」のまま残る。
|
|
2367
|
+
// 利用者にできることは変わらないので、失敗を出しても迷わせるだけ
|
|
2368
|
+
});
|
|
2369
|
+
} catch (e) {
|
|
2370
|
+
// 同上。呼び出しそのものが投げても画面は描く
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
/**
|
|
2375
|
+
* Slack のチャンネルを**平らに**並べる(2026-08-20)。
|
|
2376
|
+
*
|
|
2377
|
+
* ## なぜ折りたたみをやめたか
|
|
2378
|
+
*
|
|
2379
|
+
* オーナー指摘:
|
|
2380
|
+
* > それぞれのワークスペースごとにセクション分けしてるけど、普通に並べて、
|
|
2381
|
+
* > 他の接続OKとかの横にどのワークスペースを出すだけでいい。
|
|
2382
|
+
* > 開いて閉じてみたいな感じで並ぶ必要があんまりわかんない。なんか複雑になっちゃってる。
|
|
2383
|
+
*
|
|
2384
|
+
* **並び順は組織ごと**(社内 → 顧客 → 分かりません)にするが、**箱で囲まない**。
|
|
2385
|
+
* どの組織かは**行のラベル**が示す(同じチャンネル名が2つの組織に並びうるので、
|
|
2386
|
+
* ラベル自体は必要)。
|
|
2387
|
+
*
|
|
2388
|
+
* ## 「組織が分かりません」の案内だけは別
|
|
2389
|
+
*
|
|
2390
|
+
* 3つの原因と、招待コマンド・接続ボタン・登録の解除が要る。**行ごとに繰り返すと
|
|
2391
|
+
* 一覧が案内で埋まる**ので、その並びの手前に1つだけ置く。
|
|
2392
|
+
*/
|
|
2393
|
+
function slackRows(items, meta) {
|
|
2394
|
+
var ws = slackWorkspaces();
|
|
2395
|
+
var out = [];
|
|
2396
|
+
groupSlackByOrg(items, ws).forEach(function (g) {
|
|
2397
|
+
var cards = g.items.map(function (x) { return extSourceCard(x, meta); });
|
|
2398
|
+
if (g.unknown) {
|
|
2399
|
+
// **開いたときに1度だけ、判定し直しをサーバへ頼む。**
|
|
2400
|
+
// 承認が通っても、誰かが判定し直さなければ設定ファイルは古いまま。
|
|
2401
|
+
// OAuth の戻り先は固定ページ(この画面には戻らない)ので、
|
|
2402
|
+
// **利用者が操作タブを開き直したこの瞬間**が唯一の合図になる
|
|
2403
|
+
requestOrgResolve();
|
|
2404
|
+
// **案内が行を抱える(2026-08-20・オーナー指摘)。**
|
|
2405
|
+
// 「次のチャンネル」と書いても、囲われていないと**どれを指すのか分からない**。
|
|
2406
|
+
// 折りたたみは付けない——開閉させたいのではなく、**範囲を示したい**
|
|
2407
|
+
out.push(orgNoteBox(unknownOrgNodes(), cards));
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
if (g.key !== "__internal__") {
|
|
2411
|
+
var st = tokenStateOf(ws, g.key);
|
|
2412
|
+
// **接続が切れたときの導線を必ず残す。** それまでは接続カードの中の一覧に
|
|
2413
|
+
// 出していた。カードごと消したので、ここへ移す——移さないと
|
|
2414
|
+
// **接続が切れたことに気づけず、直す手段も画面から消える**
|
|
2415
|
+
if (st === "revoked" || st === "missing") {
|
|
2416
|
+
out.push(orgNoteBox(brokenOrgNodes(g.title, st), cards));
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
cards.forEach(function (n) { out.push(n); });
|
|
2421
|
+
});
|
|
2422
|
+
return out;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* 案内と、その案内が指す行を1つの箱にまとめる(2026-08-20)。
|
|
2427
|
+
*
|
|
2428
|
+
* **囲わないと「次のチャンネル」がどれを指すのか分からない**(オーナー指摘)。
|
|
2429
|
+
* **折りたたみは付けない**——開閉させたいのではなく、範囲を示したいだけ。
|
|
2430
|
+
*/
|
|
2431
|
+
function orgNoteBox(noteNodes, cards) {
|
|
2432
|
+
var box = el("div", { class: "ds-orgnote" });
|
|
2433
|
+
noteNodes.forEach(function (n) { if (n) box.appendChild(n); });
|
|
2434
|
+
var list = el("div", { class: "ds-orgnote-rows" });
|
|
2435
|
+
cards.forEach(function (c) { list.appendChild(c); });
|
|
2436
|
+
box.appendChild(list);
|
|
2437
|
+
return box;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
/** 「組織不明」の案内。**原因を1つに絞らない** */
|
|
2441
|
+
function unknownOrgNodes() {
|
|
2442
|
+
// **接続だけを勧めると、招待すれば済む人に「直らない処方」を出す。**
|
|
2443
|
+
// 非公開チャンネルは、同じ Slack でも招待前は「存在しない」と返る
|
|
2444
|
+
var out = [];
|
|
2445
|
+
// **「読めませんでした」では、行の「Slackで招待してください」と区別が付かない**
|
|
2446
|
+
//(オーナー指摘・2026-08-20)。どちらも読めていないが、**分かっていることが違う**:
|
|
2447
|
+
// 行に「招待してください」… クラスメソッドのチャンネルだと**分かっている**。招待だけが足りない
|
|
2448
|
+
// この案内 … **どのSlackのものかすら分からない**(どの鍵でも届かなかった)
|
|
2449
|
+
// 状態の名前を「分かりません」に揃える
|
|
2450
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ 組織不明のチャンネル" }));
|
|
2451
|
+
out.push(el("p", {
|
|
2452
|
+
class: "ds-group-note",
|
|
2453
|
+
text: "Cortex が持っているどの鍵でも、このチャンネルに届きませんでした。次のどれかです。",
|
|
2454
|
+
}));
|
|
2455
|
+
var ul = el("ul", { class: "ds-group-note" });
|
|
2456
|
+
// **公開との違いをここで言い切る。** 「同じ未招待なのに、なぜ上の行は
|
|
2457
|
+
// 組織が分かっていて、こちらは分からないのか」が読み取れないと、
|
|
2458
|
+
// 利用者は同じ状態が2通りに見えることに戸惑う
|
|
2459
|
+
ul.appendChild(el("li", { text: "非公開チャンネルで、まだ Cortex を招待していない → 下のコマンドを Slack のそのチャンネルに貼ってください" }));
|
|
2460
|
+
ul.appendChild(el("li", { text: "お客様の Slack にあり、まだつながっていない → 下のボタンから接続します" }));
|
|
2461
|
+
ul.appendChild(el("li", { text: "チャンネルがアーカイブ/削除された、URLが違う → 「登録を解除」を押してください" }));
|
|
2462
|
+
out.push(ul);
|
|
2463
|
+
out.push(copyRow("/invite @Cortex"));
|
|
2464
|
+
out.push(slackConnectButton());
|
|
2465
|
+
// 判定し直しの結果を出す場所(何も起きなければ空のまま)
|
|
2466
|
+
out.push(el("p", { class: "ds-group-note ds-org-resolved" }));
|
|
2467
|
+
return out;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/** 接続が切れた/未接続のワークスペースの案内 */
|
|
2471
|
+
function brokenOrgNodes(title, state) {
|
|
2472
|
+
var out = [];
|
|
2473
|
+
out.push(el("div", {
|
|
2474
|
+
class: "ds-orgnote-title",
|
|
2475
|
+
text: state === "revoked" ? "⚠️ " + title + " との接続が切れています" : "⚠️ " + title + " はまだ接続されていません",
|
|
2476
|
+
}));
|
|
2477
|
+
out.push(el("p", {
|
|
2478
|
+
class: "ds-group-note",
|
|
2479
|
+
// **revoked と missing を分ける。** 前者は「接続し直して」、後者は「まだ完了していない」で
|
|
2480
|
+
// 次の一手が違う。unknown には何も出さない——繋がっているのに
|
|
2481
|
+
// 「接続し直して」と言う方が、黙っているより悪い
|
|
2482
|
+
text: state === "revoked"
|
|
2483
|
+
? "Slack 側で Cortex のアプリが削除されたか、接続が無効になっています。もう一度接続してください。"
|
|
2484
|
+
: "このワークスペースのチャンネルが登録されていますが、接続が見つかりません。もう一度接続してください。",
|
|
2485
|
+
}));
|
|
2486
|
+
out.push(slackConnectButton());
|
|
2487
|
+
return out;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
|
|
2136
2491
|
var DS_KIND_ICON = {
|
|
2137
2492
|
"課題管理": "📋",
|
|
2138
2493
|
"会議": "🗒️",
|
|
@@ -2523,6 +2878,14 @@ ${capabilityState.toString()}
|
|
|
2523
2878
|
var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
|
|
2524
2879
|
groups.forEach(function (g) {
|
|
2525
2880
|
var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
|
|
2881
|
+
// **Slack は所属する組織ごとに分ける(2026-08-20)。**
|
|
2882
|
+
// 追加のときにワークスペースを選ばせるのをやめたので、
|
|
2883
|
+
// 「どの組織のものか」「その組織とつながっているか」を画面が言う。
|
|
2884
|
+
// 分け方は groupSlackByOrg(純関数)が全部持っている
|
|
2885
|
+
if (g.type === "slack") {
|
|
2886
|
+
slackRows(g.items, meta).forEach(function (n) { pushCap("チャット", n); });
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2526
2889
|
var cards = el("div", { class: "ds-list" });
|
|
2527
2890
|
g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
|
|
2528
2891
|
if (g.items.length >= 4) {
|
|
@@ -2553,9 +2916,18 @@ ${capabilityState.toString()}
|
|
|
2553
2916
|
// **追加フォームはグループの外に置く。** グループは0件だと描かれず、4件以上だと
|
|
2554
2917
|
// 畳まれる(既定は閉)。中に入れると**新規案件(0件)=一番追加したい場面**で消える。
|
|
2555
2918
|
var adders = renderExternalAdders();
|
|
2556
|
-
//
|
|
2557
|
-
//
|
|
2558
|
-
|
|
2919
|
+
// **接続の入口は「読み取れていない」組にだけ出す(2026-08-20)。**
|
|
2920
|
+
//
|
|
2921
|
+
// それまでは「お客様の Slack ワークスペースを接続」という独立した節を
|
|
2922
|
+
// チャンネル追加より前に置いていた。オーナーの受け入れテストで
|
|
2923
|
+
// 「モーダルを開いても何から始めればいいか分からない」と指摘された——
|
|
2924
|
+
// 接続が先なのか追加が先なのか、そもそも自分に関係あるのかが読み取れない。
|
|
2925
|
+
//
|
|
2926
|
+
// **接続は「準備」ではなく、必要になった行から始まる。**
|
|
2927
|
+
// 当時の警告コメント(「箱そのものは消さない。0件の案件で入口が消える」)は
|
|
2928
|
+
// **新しい形が別の解き方をしている**——チャンネルを追加すれば
|
|
2929
|
+
// 読み取れていない組として現れ、そこに接続ボタンが出る。
|
|
2930
|
+
// 0件なら繋ぐ相手もまだ無いので、入口が要る場面にならない。
|
|
2559
2931
|
pushCap("チャット", adders.querySelector('[data-adder="slack"]'));
|
|
2560
2932
|
// **組織ごとの接続状況を、開発の一番上に置く。** トークンは組織ごとにしか
|
|
2561
2933
|
// 効かない(Fine-grained PAT も GitHub App のインストールトークンも1組織分)ので、
|
|
@@ -3147,7 +3519,7 @@ ${capabilityState.toString()}
|
|
|
3147
3519
|
// 長い説明は落とし、次の行動の1行だけ残す
|
|
3148
3520
|
return [p(isInternalSlack(s)
|
|
3149
3521
|
? "cortex の担当者にご連絡ください。"
|
|
3150
|
-
: "
|
|
3522
|
+
: "「Slack ワークスペースに接続する」から接続してください。")];
|
|
3151
3523
|
}
|
|
3152
3524
|
if (r.gate === "unreachable") {
|
|
3153
3525
|
return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
|
|
@@ -3216,14 +3588,24 @@ ${capabilityState.toString()}
|
|
|
3216
3588
|
* (押すたびにコミットが増えるのを避ける)。表示名だけが例外で、そちらは
|
|
3217
3589
|
* Slack の実名が正なので自動で揃える(autoAlignName)。
|
|
3218
3590
|
*/
|
|
3591
|
+
/**
|
|
3592
|
+
* 接続の確認。
|
|
3593
|
+
*
|
|
3594
|
+
* **ボタンは行の中(「登録を解除」の隣)に置く(2026-08-20)。**
|
|
3595
|
+
* それまでは行の下に独立した段を作っており、**1行が2段になって一覧が長かった**
|
|
3596
|
+
* (オーナー指摘「レコードをそれぞれもうちょっと短くできるよね」)。
|
|
3597
|
+
* 押した結果の詳細(測った時刻・次の一手)は下に出す——**押すまでは空**なので、
|
|
3598
|
+
* 一覧を眺めているだけの人には行が伸びない。
|
|
3599
|
+
*
|
|
3600
|
+
* @returns { btn, detail } btn は行に、detail は行の下に置く
|
|
3601
|
+
*/
|
|
3219
3602
|
function probeSection(s, card, gateEl) {
|
|
3603
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
|
|
3220
3604
|
var wrap = el("div", { class: "ds-probe" });
|
|
3221
3605
|
var head = el("div", { class: "ds-probe-head" });
|
|
3222
|
-
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
|
|
3223
3606
|
var msg = el("span", { class: "ds-toggle-msg" });
|
|
3224
3607
|
var at = el("span", { class: "ds-probe-at" });
|
|
3225
3608
|
var body = el("div");
|
|
3226
|
-
head.appendChild(btn);
|
|
3227
3609
|
head.appendChild(msg);
|
|
3228
3610
|
head.appendChild(at);
|
|
3229
3611
|
wrap.appendChild(head);
|
|
@@ -3233,6 +3615,10 @@ ${capabilityState.toString()}
|
|
|
3233
3615
|
var intake = SITE.intake;
|
|
3234
3616
|
if (!intake) return;
|
|
3235
3617
|
btn.disabled = true;
|
|
3618
|
+
// **押されて初めて枠を出す(2026-08-20・オーナー指摘)。**
|
|
3619
|
+
// それまでは中身が空でも点線だけが全行に出ており、**何も意味していなかった**。
|
|
3620
|
+
// 押した結果があるときだけ、行と結果を分ける線になる
|
|
3621
|
+
wrap.className = "ds-probe is-shown";
|
|
3236
3622
|
msg.className = "ds-toggle-msg";
|
|
3237
3623
|
msg.textContent = "確認中…";
|
|
3238
3624
|
at.textContent = "";
|
|
@@ -3278,7 +3664,7 @@ ${capabilityState.toString()}
|
|
|
3278
3664
|
}
|
|
3279
3665
|
btn.disabled = false;
|
|
3280
3666
|
});
|
|
3281
|
-
return wrap;
|
|
3667
|
+
return { btn: btn, detail: wrap };
|
|
3282
3668
|
}
|
|
3283
3669
|
|
|
3284
3670
|
function removeChannelButton(s, card) {
|
|
@@ -3740,156 +4126,6 @@ ${capabilityState.toString()}
|
|
|
3740
4126
|
return used ? box : null;
|
|
3741
4127
|
}
|
|
3742
4128
|
|
|
3743
|
-
/**
|
|
3744
|
-
* 会議の取り込みの案内。**操作ではなく条件を示す。**
|
|
3745
|
-
*
|
|
3746
|
-
* 取り込まれるには2つとも要る:
|
|
3747
|
-
* ① cortex-notetaker Bot が会議に招待されている(=取り込む意思。これが同意のゲート)
|
|
3748
|
-
* ② 会議名が照合キーのどれかを含む(含まなければ、録れても案件に振り分けられない)
|
|
3749
|
-
*
|
|
3750
|
-
* ②の材料を画面が出さないと、招待したのに届かない理由が分からない。
|
|
3751
|
-
*/
|
|
3752
|
-
function meetingHowTo() {
|
|
3753
|
-
// **2つの条件がANDであることを、読み飛ばせない形で出す。** 散文で並べていたときに
|
|
3754
|
-
// 「会議名は合っているのにBotを招待しておらず取り込まれなかった」事故が実際に起きた。
|
|
3755
|
-
// 会議名の条件は上の行(ds-keys)が出すので、ここでは「上の語」と参照するに留める。
|
|
3756
|
-
var wrap = el("div", { class: "sec-how" });
|
|
3757
|
-
wrap.appendChild(el("p", { text: "次の2つが揃った会議だけが取り込まれます。" }));
|
|
3758
|
-
var ul = el("ul", { class: "sec-how-list" });
|
|
3759
|
-
ul.appendChild(el("li", { text: "cortex-notetaker Bot が招待されている(定例はシリーズに1回でOK)" }));
|
|
3760
|
-
ul.appendChild(el("li", { text: "会議名に上の目印が入っている" }));
|
|
3761
|
-
wrap.appendChild(ul);
|
|
3762
|
-
wrap.appendChild(el("p", { text: "どちらか一方だけでは取り込まれません。" }));
|
|
3763
|
-
return wrap;
|
|
3764
|
-
}
|
|
3765
|
-
|
|
3766
|
-
/* ---------------------------------------------------------------------------
|
|
3767
|
-
* Slack ワークスペースの接続(#104)
|
|
3768
|
-
*
|
|
3769
|
-
* 顧客のワークスペースを読み始める日の入口。押す → サーバが署名つきの承認リンクを
|
|
3770
|
-
* 作って返す → そこへ遷移 → 顧客が Slack で承認 → 別の Lambda が受け取って棚に鍵を入れる。
|
|
3771
|
-
*
|
|
3772
|
-
* **リンクを静的に焼き込まない。** 承認リンクには「この案件へ書き込んでよい」という
|
|
3773
|
-
* 10分間有効な署名(state)が載っている。HTML に埋めると、配信物を読める人(=その案件の
|
|
3774
|
-
* Basic 認証を通れる人)が期限のないリンクを手に入れることになるし、
|
|
3775
|
-
* ビルド時点の state はどのみち期限切れで使えない。押した瞬間にサーバから貰う。
|
|
3776
|
-
*
|
|
3777
|
-
* **「トークン」という語を出さない。** 顧客に見せる画面なので、内部語ではなく
|
|
3778
|
-
* 「接続する/読み取りたいチャンネルに招待する」で通す(内部語排除の原則の資格情報版)。
|
|
3779
|
-
* ------------------------------------------------------------------------- */
|
|
3780
|
-
function slackConnectBox() {
|
|
3781
|
-
if (!SITE.intake) return null;
|
|
3782
|
-
// **この項目を知らない古い fleet-status では何も出さない。** 「0件」と「知らない」を
|
|
3783
|
-
// 同じ扱いにすると、engine が @v1 で止まっている案件に「まだ接続されていません」と
|
|
3784
|
-
// 嘘を出すことになる(実際には社内ワークスペースで動いている)
|
|
3785
|
-
var list = slackWorkspaces();
|
|
3786
|
-
if (!list) return null;
|
|
3787
|
-
|
|
3788
|
-
var box = el("div", { class: "ds-addform", "data-adder": "slack-workspace" }, [
|
|
3789
|
-
el("div", { class: "ds-addform-title", text: "お客様の Slack ワークスペースを接続" }),
|
|
3790
|
-
el("p", {
|
|
3791
|
-
class: "sec-how",
|
|
3792
|
-
text:
|
|
3793
|
-
"お客様の Slack ワークスペースのチャンネルを読み取り対象にするための接続です。" +
|
|
3794
|
-
"ボタンを押すと Slack の承認画面が開きます(承認するのはワークスペースの管理者です)。" +
|
|
3795
|
-
"お願いする権限は読み取りだけで、Cortex から投稿することはありません。" +
|
|
3796
|
-
"接続したあと、読み取りたいチャンネルにボットを招待し、この画面から追加してください。",
|
|
3797
|
-
}),
|
|
3798
|
-
// **「これは何のためのものでないか」を書く(2026-08-18 の不具合報告)。**
|
|
3799
|
-
// 社内ワークスペースのチャンネルが読めていない人が、設定する場所を探して
|
|
3800
|
-
// ここへ来る。押すと社内ワークスペースの承認画面が出て必ず失敗し、
|
|
3801
|
-
// 「もう一度お試しください」に従ってまた押す——何度試しても直らない。
|
|
3802
|
-
//
|
|
3803
|
-
// **箱そのものは消さない。** 直上の renderExternalAdders のコメントが警告している
|
|
3804
|
-
// のと同じ失敗で、「顧客チャンネルがある案件だけ出す」にすると
|
|
3805
|
-
// **新規案件(0件)=一番繋ぎたい場面**でこの入口が消える。出したまま、
|
|
3806
|
-
// 使う場面と使わない場面をここで言い切る
|
|
3807
|
-
el("p", {
|
|
3808
|
-
class: "sec-how ds-scope-note",
|
|
3809
|
-
text:
|
|
3810
|
-
"クラスメソッドの社内ワークスペース(Classmethod Group)のチャンネルには、この接続は使いません。" +
|
|
3811
|
-
"社内のチャンネルは Cortex の中央ボットで読んでいるので、ここで接続しても読めるようにはなりません。" +
|
|
3812
|
-
"社内のチャンネルが読めていない場合は、cortex の担当者にご連絡ください。",
|
|
3813
|
-
}),
|
|
3814
|
-
]);
|
|
3815
|
-
|
|
3816
|
-
// 接続済みの一覧。**0件でも「まだありません」と書く**——空白だと、
|
|
3817
|
-
// 一覧が出ていないのか繋がっていないのかが読めない
|
|
3818
|
-
var listBox = el("div", { class: "ds-ws-list" });
|
|
3819
|
-
if (!list.length) {
|
|
3820
|
-
listBox.appendChild(el("div", { class: "ds-ws-empty", text: "接続済みのワークスペースはまだありません。" }));
|
|
3821
|
-
} else {
|
|
3822
|
-
list.forEach(function (w) {
|
|
3823
|
-
var row = el("div", { class: "ds-ws" }, [
|
|
3824
|
-
el("span", { class: "ds-ws-name", text: w.name || w.teamId }),
|
|
3825
|
-
el("span", {
|
|
3826
|
-
class: "ds-ws-count",
|
|
3827
|
-
text: (w.channels || 0) + "チャンネル",
|
|
3828
|
-
}),
|
|
3829
|
-
]);
|
|
3830
|
-
// **revoked と missing を分けて出す。** 前者は「接続し直してください」、
|
|
3831
|
-
// 後者は「まだ接続が完了していない」で、次の一手が違う。
|
|
3832
|
-
// unknown(確かめられなかった回)には何も出さない——繋がっているのに
|
|
3833
|
-
// 「接続し直して」と言う方が、黙っているより悪い。
|
|
3834
|
-
// 注: ここは埋め込みJSのテンプレートリテラル内。**コメントでもバッククォートを使わない**
|
|
3835
|
-
if (w.tokenState === "revoked") {
|
|
3836
|
-
row.appendChild(el("span", {
|
|
3837
|
-
class: "ds-gate ds-gate-warn",
|
|
3838
|
-
text: "⚠️ 接続が切れています",
|
|
3839
|
-
title: "Slack 側で Cortex のアプリが削除されたか、接続が無効になっています。もう一度接続してください。",
|
|
3840
|
-
}));
|
|
3841
|
-
} else if (w.tokenState === "missing") {
|
|
3842
|
-
row.appendChild(el("span", {
|
|
3843
|
-
class: "ds-gate ds-gate-warn",
|
|
3844
|
-
text: "⚠️ 未接続",
|
|
3845
|
-
title: "このワークスペースのチャンネルが登録されていますが、接続が見つかりません。もう一度接続してください。",
|
|
3846
|
-
}));
|
|
3847
|
-
} else if (w.tokenState === "ok") {
|
|
3848
|
-
row.appendChild(el("span", { class: "ds-gate ds-gate-ok", text: "● 接続OK" }));
|
|
3849
|
-
}
|
|
3850
|
-
listBox.appendChild(row);
|
|
3851
|
-
});
|
|
3852
|
-
}
|
|
3853
|
-
box.appendChild(listBox);
|
|
3854
|
-
|
|
3855
|
-
// **ボタン自身に「お客様の」と書く(2026-08-18 の不具合報告)。** 社内チャンネルが
|
|
3856
|
-
// 読めていない人が押したのはこのボタンで、当時の文言は「Slack ワークスペースを接続」
|
|
3857
|
-
// ——誰のワークスペースの話かが書かれておらず、唯一それらしいボタンに見えた
|
|
3858
|
-
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "お客様の Slack ワークスペースを接続" });
|
|
3859
|
-
var msg = el("div", { class: "ds-toggle-msg" });
|
|
3860
|
-
btn.addEventListener("click", async function () {
|
|
3861
|
-
var intake = SITE.intake;
|
|
3862
|
-
if (!intake) return;
|
|
3863
|
-
btn.disabled = true;
|
|
3864
|
-
msg.className = "ds-toggle-msg";
|
|
3865
|
-
msg.textContent = "承認画面を準備しています…";
|
|
3866
|
-
try {
|
|
3867
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
3868
|
-
var res = await fetch(pfx + "op=slack-oauth-start&t=" + encodeURIComponent(intake.token), {
|
|
3869
|
-
method: "POST",
|
|
3870
|
-
headers: { "content-type": "application/json" },
|
|
3871
|
-
body: "{}",
|
|
3872
|
-
});
|
|
3873
|
-
var j = await res.json().catch(function () { return {}; });
|
|
3874
|
-
if (!res.ok || !j.url) {
|
|
3875
|
-
// サーバの文言をそのまま出す(未設定のときは「準備中です」が返る)
|
|
3876
|
-
msg.className = "ds-toggle-msg is-error";
|
|
3877
|
-
msg.textContent = "✗ " + (j.message || "接続を開始できませんでした。");
|
|
3878
|
-
btn.disabled = false;
|
|
3879
|
-
return;
|
|
3880
|
-
}
|
|
3881
|
-
// **同じタブで遷移する。** 別タブだと、承認後の「接続しました」を見た人が
|
|
3882
|
-
// 元のタブに戻る動線を自分で見つけることになる
|
|
3883
|
-
location.href = j.url;
|
|
3884
|
-
} catch (e) {
|
|
3885
|
-
msg.className = "ds-toggle-msg is-error";
|
|
3886
|
-
msg.textContent = "✗ 接続を開始できませんでした。時間をおいて試してください。";
|
|
3887
|
-
btn.disabled = false;
|
|
3888
|
-
}
|
|
3889
|
-
});
|
|
3890
|
-
box.appendChild(el("div", { class: "ds-toggle" }, [btn, msg]));
|
|
3891
|
-
return box;
|
|
3892
|
-
}
|
|
3893
4129
|
|
|
3894
4130
|
/**
|
|
3895
4131
|
* 外部ソースの追加フォーム(Slack・GitHub)。
|
|
@@ -3925,8 +4161,21 @@ ${capabilityState.toString()}
|
|
|
3925
4161
|
// **招待の話を先頭に置く。** Slack の仕様で、ボットは**公開チャンネルでも**招待されるまで
|
|
3926
4162
|
// 中身を1件も読めない(conversations.history が not_in_channel を返す・2026-08-16 実測+公式ドキュメント)。
|
|
3927
4163
|
// そのあとに「追加=Gold 昇格の対象」を置く(手順と、その手順の重さ)。
|
|
3928
|
-
|
|
3929
|
-
|
|
4164
|
+
// **手順のリストにする(2026-08-20・オーナー指摘)。**
|
|
4165
|
+
// 一続きの文章だと、読む人が「自分がいま何をすべきか」を自分で切り出すことになる。
|
|
4166
|
+
//
|
|
4167
|
+
// **接続(お客様の Slack への導入)の説明もここに置く。** 独立した節を
|
|
4168
|
+
// 廃止したので、**繋ぎ方を知る場所はここしか無い**——
|
|
4169
|
+
// 「0件の案件で入口が消える」という過去の事故(renderExternalAdders の
|
|
4170
|
+
// 直上コメント)を、この手順が受け止めている。
|
|
4171
|
+
hint: [
|
|
4172
|
+
// **コマンドは手順1の中に置く。** フォームの下に離すと、どの手順のものか
|
|
4173
|
+
// 対応が読めない(手で打たせない理由は copyRow のコメント)
|
|
4174
|
+
{ text: "読み取りたいチャンネルで、下のコマンドを貼って Cortex を招待します。公開チャンネルでも招待が必要です。", node: copyRow("/invite @Cortex") },
|
|
4175
|
+
"下の欄に、そのチャンネルのリンクを貼って「追加」を押します(チャンネル名を右クリック →「リンクをコピー」)。",
|
|
4176
|
+
"「組織不明」と出たら、そこにある「Slack ワークスペースに接続する」から承認をもらいます。お客様の Slack のチャンネルは、この承認が済むまで読めません(承認できるのはその Slack の管理者です)。",
|
|
4177
|
+
"追加したチャンネルは毎晩の Gold 昇格の対象になり、Decision・用語集の材料になります。止めるときは、その行の「登録を解除」を押します。",
|
|
4178
|
+
],
|
|
3930
4179
|
// **選んだワークスペースを送る(#104)。** 空文字=社内ワークスペースなのでキーごと落とす
|
|
3931
4180
|
// (サーバ側は「未宣言・null・空」を区別しない約束で、無いことで既定を表す)
|
|
3932
4181
|
build: function (url, name, workspace) {
|
|
@@ -3934,7 +4183,6 @@ ${capabilityState.toString()}
|
|
|
3934
4183
|
if (workspace) payload.workspace = workspace;
|
|
3935
4184
|
return payload;
|
|
3936
4185
|
},
|
|
3937
|
-
workspacePicker: true,
|
|
3938
4186
|
// **別案件でも使われていたときに出す確認(2026-08-20)。**
|
|
3939
4187
|
//
|
|
3940
4188
|
// **追加のときに出る唯一のモーダル。** 同日に「追加のたびの確認」を廃止した
|
|
@@ -4279,6 +4527,31 @@ ${capabilityState.toString()}
|
|
|
4279
4527
|
* **これがある口では、その場の控え(「追加済み・反映中」)を出さない**——
|
|
4280
4528
|
* 応答で本物の一覧が増えるので、控えを併記すると同じものが2回出る
|
|
4281
4529
|
*/
|
|
4530
|
+
/**
|
|
4531
|
+
* フォームの説明。**配列なら手順のリストにする(2026-08-20)。**
|
|
4532
|
+
*
|
|
4533
|
+
* オーナー指摘: 「もっと手続き的に、結局ユーザー自身が何をすればいいのかを
|
|
4534
|
+
* わかるようにした方がいい。リスト形式でちゃんと並べてあげるとわかりやすい」。
|
|
4535
|
+
* 一続きの文章は、読む人が「自分がいま何をすべきか」を自分で切り出すことになる。
|
|
4536
|
+
*
|
|
4537
|
+
* **文字列の口は今までどおり。** 他の4フォーム(共有資料・Figma・Backlog・GitHub)は
|
|
4538
|
+
* まだ文章なので、そちらを壊さない。横展開でそれぞれ配列に変えていく。
|
|
4539
|
+
*
|
|
4540
|
+
* 各要素は文字列か、{ text, node } (手順の中に部品を挟むとき。招待コマンドなど)。
|
|
4541
|
+
*/
|
|
4542
|
+
function hintNode(hint) {
|
|
4543
|
+
if (!hint) return null;
|
|
4544
|
+
if (!Array.isArray(hint)) return el("p", { class: "sec-how", text: hint });
|
|
4545
|
+
var ol = el("ol", { class: "sec-how sec-steps" });
|
|
4546
|
+
hint.forEach(function (step) {
|
|
4547
|
+
var text = typeof step === "string" ? step : (step && step.text) || "";
|
|
4548
|
+
var li = el("li", {}, [el("span", { text: text })]);
|
|
4549
|
+
if (step && step.node) li.appendChild(step.node);
|
|
4550
|
+
ol.appendChild(li);
|
|
4551
|
+
});
|
|
4552
|
+
return ol;
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4282
4555
|
function addForm(opts) {
|
|
4283
4556
|
var main = el("input", { class: "sec-input", type: "text", placeholder: opts.placeholder, autocomplete: "off" });
|
|
4284
4557
|
var name = opts.needsName
|
|
@@ -4293,15 +4566,13 @@ ${capabilityState.toString()}
|
|
|
4293
4566
|
// ワークスペースの改名で変わるし、共有チャンネルでは貼った人のホストになる。
|
|
4294
4567
|
// 推測して間違えると「追加できたのに読まれない」チャンネルができ、
|
|
4295
4568
|
// 翌朝の接続状況で初めて分かる。人に選ばせる。
|
|
4296
|
-
|
|
4569
|
+
// **ワークスペースの選択は廃止した(2026-08-20)。**
|
|
4570
|
+
// オーナーの受け入れテストで「意味が分からない」。実際、チャンネルIDを見ても
|
|
4571
|
+
// どのワークスペースのものかは分からないし、**間違えると追加はできるのに
|
|
4572
|
+
// 永久に読まれない**(気づくのは翌朝の接続状況)。
|
|
4573
|
+
// いまはサーバが、持っている鍵を順に Slack へ投げて決める。
|
|
4574
|
+
// **URLのホスト名から推測しない**という下の判断は今も正しい——推測ではなく実測にした。
|
|
4297
4575
|
var wsSelect = null;
|
|
4298
|
-
if (wsList.length) {
|
|
4299
|
-
wsSelect = el("select", { class: "sec-input" });
|
|
4300
|
-
wsSelect.appendChild(el("option", { value: "", text: "社内ワークスペース(クラスメソッド)" }));
|
|
4301
|
-
wsList.forEach(function (w) {
|
|
4302
|
-
wsSelect.appendChild(el("option", { value: w.teamId, text: w.name || w.teamId }));
|
|
4303
|
-
});
|
|
4304
|
-
}
|
|
4305
4576
|
var submitLabel = opts.submitLabel || "追加";
|
|
4306
4577
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: submitLabel });
|
|
4307
4578
|
var msg = el("div", { class: "ds-toggle-msg" });
|
|
@@ -4366,7 +4637,7 @@ ${capabilityState.toString()}
|
|
|
4366
4637
|
|
|
4367
4638
|
var box = el("div", opts.adder ? { class: "ds-addform", "data-adder": opts.adder } : { class: "ds-addform" }, [
|
|
4368
4639
|
el("div", { class: "ds-addform-title", text: opts.title }),
|
|
4369
|
-
|
|
4640
|
+
hintNode(opts.hint),
|
|
4370
4641
|
// 手順の中に「どこかへ貼る文字列」があるときは、**手で打たせない**。
|
|
4371
4642
|
// 「/invite @Cortex」のような短い文字列でも、打ち間違えると黙って失敗する
|
|
4372
4643
|
opts.copyText ? copyRow(opts.copyText) : null,
|
|
@@ -4438,15 +4709,35 @@ ${capabilityState.toString()}
|
|
|
4438
4709
|
// 行ごとに「対象です」と書いても情報が増えない。github の除外は行のトグル
|
|
4439
4710
|
// (「読み取りを始める/止める」)が向きで示す。
|
|
4440
4711
|
if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
|
|
4441
|
-
//
|
|
4442
|
-
//
|
|
4443
|
-
//
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4712
|
+
// **どのワークスペースのチャンネルかを、必ず行に出す(#104・2026-08-20 に社内も)。**
|
|
4713
|
+
//
|
|
4714
|
+
// 折りたたみの見出しでまとめるのをやめた(オーナー指摘「複雑になっちゃってる」)ので、
|
|
4715
|
+
// **どの組織かを言うのは行のラベルだけ**になった。社内に何も出さないと、
|
|
4716
|
+
// ラベルの無い行が「社内」なのか「まだ分かっていない」のかを読み分けられない。
|
|
4717
|
+
if (s.type === "slack") {
|
|
4718
|
+
// **順序は groupSlackByOrg と揃える(workspace が先)。**
|
|
4719
|
+
// 逆にすると、判定が済んで workspace が入った行に古い印が残っているとき、
|
|
4720
|
+
// **並びは「Hapio AI」なのにラベルだけ「組織が分かりません」**になる
|
|
4721
|
+
// ——同じ画面で2つの判定が食い違う、このプロダクトが何度も直した形
|
|
4722
|
+
if (s.workspace) {
|
|
4723
|
+
badges.appendChild(el("span", {
|
|
4724
|
+
class: "badge ds-workspace",
|
|
4725
|
+
text: workspaceLabel(s.workspace),
|
|
4726
|
+
title: "接続した Slack ワークスペースのチャンネルです(読み取り専用の接続なので、通知は届きません)。",
|
|
4727
|
+
}));
|
|
4728
|
+
} else if (s.orgUnknown === true) {
|
|
4729
|
+
badges.appendChild(el("span", {
|
|
4730
|
+
class: "badge ds-workspace ds-workspace-unknown",
|
|
4731
|
+
text: "組織不明",
|
|
4732
|
+
title: "どの Slack のチャンネルか、まだ分かっていません。上の案内をご覧ください。",
|
|
4733
|
+
}));
|
|
4734
|
+
} else {
|
|
4735
|
+
badges.appendChild(el("span", {
|
|
4736
|
+
class: "badge ds-workspace",
|
|
4737
|
+
text: "クラスメソッド",
|
|
4738
|
+
title: "クラスメソッドの社内 Slack のチャンネルです。",
|
|
4739
|
+
}));
|
|
4740
|
+
}
|
|
4450
4741
|
}
|
|
4451
4742
|
// 登録の由来(#118)。**「登録から削除」が出る/出ないの理由がこれ。** 導出されたリポは
|
|
4452
4743
|
// 登録ファイルから消しても翌日また現れるので削除を出していないが、画面には
|
|
@@ -4552,7 +4843,11 @@ ${capabilityState.toString()}
|
|
|
4552
4843
|
// **origin === "explicit" でも出す。** 確認は読むだけで、どの登録ファイル由来かに
|
|
4553
4844
|
// 関係なく効く(書き込みになる「表示名を更新する」だけが由来で出し分かれる)
|
|
4554
4845
|
if (SITE.intake && s.type === "slack" && s.ref) {
|
|
4555
|
-
|
|
4846
|
+
var probe = probeSection(s, card, gateEl);
|
|
4847
|
+
// **ボタンは行の中(「登録を解除」の隣)。** 独立した段を作ると1行が2段になる
|
|
4848
|
+
card.appendChild(probe.btn);
|
|
4849
|
+
// 押した結果の詳細だけを下に。**押すまでは空**なので行は伸びない
|
|
4850
|
+
card.appendChild(probe.detail);
|
|
4556
4851
|
}
|
|
4557
4852
|
return card;
|
|
4558
4853
|
}
|