@takagaki/cortex-decisions-viewer 0.4.85 → 0.6.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 +29 -0
- package/dist/cli.js +1 -1
- package/dist/render.js +1180 -406
- package/package.json +3 -2
package/dist/render.js
CHANGED
|
@@ -1107,6 +1107,18 @@ a:hover { text-decoration: underline; }
|
|
|
1107
1107
|
display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
|
|
1108
1108
|
}
|
|
1109
1109
|
.ds-card-main { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
|
|
1110
|
+
/* 能力モーダルの中の「内部ソースのカード」だけ縦組みにする(2026-08-21)。
|
|
1111
|
+
横並びのままだと、名前ブロックが左の細い列になり、その右に設定一式が積まれる
|
|
1112
|
+
(オーナー指摘「なんで左側に共有資料、資料というふうに出てるんだ?」)。
|
|
1113
|
+
**flex-wrap: nowrap を必ず含める。** .ds-card は flex-wrap: wrap で、
|
|
1114
|
+
direction を column にしても wrap は残る。このカードは max-height: 90vh の
|
|
1115
|
+
column flex(.modal)の縮みうる子なので、wrap したままだと自動最小サイズが
|
|
1116
|
+
「最も高い子1つ分」まで落ち、縦に潰れて中身が複数列に折り返す=指摘された
|
|
1117
|
+
「左に列がある」状態を、もっと壊れた形で作り直すことになる。
|
|
1118
|
+
**子結合子(>)で書く。** 子孫セレクタだと、中に入れ子にしたレコードの行の
|
|
1119
|
+
.ds-card-main にも当たり(特異度で勝つ)、行の名前ブロックが伸びなくなる。 */
|
|
1120
|
+
.ds-card.ds-capbody { flex-direction: column; align-items: stretch; flex-wrap: nowrap; gap: 10px; }
|
|
1121
|
+
.ds-card.ds-capbody > .ds-card-main { flex: 0 0 auto; }
|
|
1110
1122
|
.ds-icon { font-size: 18px; line-height: 1; flex: 0 0 auto; }
|
|
1111
1123
|
.ds-type { font-size: 12px; font-weight: 600; color: var(--muted); flex: 0 0 auto; }
|
|
1112
1124
|
.ds-name { font-size: 14.5px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -1151,6 +1163,26 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1151
1163
|
.ds-group[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
1152
1164
|
.ds-group > .ds-list { margin-top: 8px; padding-left: 12px; border-left: 2px solid var(--border); }
|
|
1153
1165
|
.badge.ds-sum-ok { background: #e9f7ef; color: #1f7a45; }
|
|
1166
|
+
/* 「断定しない」——古い fleet-status では接続状態が分からない。OKとも警告とも言わない */
|
|
1167
|
+
.ds-sum-unknown { background: var(--bg); color: var(--muted); }
|
|
1168
|
+
/* 組織が分かっていない行のラベル。**警告色にする**(並びの中で見つかる必要がある) */
|
|
1169
|
+
.badge.ds-workspace-unknown { border-color: #f0b429; color: #8a5a00; background: #fff8e6; }
|
|
1170
|
+
/* 組織についての案内。行ではないので、行と見分けが付く形にする */
|
|
1171
|
+
.ds-orgnote {
|
|
1172
|
+
border: 1px solid #f0b429; background: #fffdf5; border-radius: 12px;
|
|
1173
|
+
padding: 12px 16px; display: flex; flex-direction: column; gap: 8px;
|
|
1174
|
+
}
|
|
1175
|
+
.ds-orgnote-title { font-weight: 700; font-size: 13.5px; color: #8a5a00; }
|
|
1176
|
+
/* 案内が指す行。**囲うことで「どのチャンネルの話か」を示す**(開閉はしない) */
|
|
1177
|
+
.ds-orgnote-rows { display: flex; flex-direction: column; gap: 8px; margin-top: 2px; }
|
|
1178
|
+
/* 手順のリスト。番号を出し、1手ずつ離す(一続きの文章にしない) */
|
|
1179
|
+
.sec-steps { margin: 0; padding-left: 1.35em; display: flex; flex-direction: column; gap: 8px; }
|
|
1180
|
+
.sec-steps li { line-height: 1.75; }
|
|
1181
|
+
.sec-steps li > span { display: block; }
|
|
1182
|
+
.ds-group-body { display: flex; flex-direction: column; gap: 10px; padding: 4px 0 2px; }
|
|
1183
|
+
/* 組のまとまりを、行より弱い線で示す */
|
|
1184
|
+
.ds-group-note { font-size: 13px; color: var(--muted); line-height: 1.7; margin: 0; }
|
|
1185
|
+
.ds-group-note strong { color: var(--text); }
|
|
1154
1186
|
.badge.ds-sum-warn { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
|
|
1155
1187
|
/* ---- 接続マップ(操作タブ最上部のSVG図) ---- */
|
|
1156
1188
|
.conn-map { overflow-x: auto; margin: 4px 0 20px; }
|
|
@@ -1251,7 +1283,7 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1251
1283
|
min-width: 0 … flex の子が min-content より縮めるようにする(無いと折り返しても溢れる) */
|
|
1252
1284
|
.ds-tile-name, .ds-tile-desc, .ds-tile-sub, .ds-tile-note,
|
|
1253
1285
|
.ds-check-label, .ds-check-action, .ds-check-detail,
|
|
1254
|
-
.ds-pipe-label, .ds-pipe-todo,
|
|
1286
|
+
.ds-pipe-label, .ds-pipe-todo,
|
|
1255
1287
|
.ds-empty, .sec-how, .modal-desc, .ds-toggle-msg,
|
|
1256
1288
|
.ds-drive-note, .ds-inactive-note, .ds-legacy-chip, .ds-keys-code, .ds-gate-help {
|
|
1257
1289
|
min-width: 0; white-space: normal; overflow-wrap: anywhere;
|
|
@@ -1262,8 +1294,10 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1262
1294
|
.ds-toggle-btn:disabled { opacity: .6; cursor: default; }
|
|
1263
1295
|
.ds-toggle-btn.ds-danger { color: #b42318; border-color: #f3c6c1; }
|
|
1264
1296
|
.ds-toggle-btn.ds-danger:hover:not(:disabled) { border-color: #b42318; color: #b42318; background: #fff5f4; }
|
|
1265
|
-
.ds-subitems
|
|
1266
|
-
|
|
1297
|
+
/* .ds-subitems / .ds-subitem は撤去した(2026-08-21)。共有資料のフォルダと Figma の
|
|
1298
|
+
ファイルの行は、Slack / GitHub と同じ .ds-list > .ds-card になったので、
|
|
1299
|
+
行間(gap)も .ds-list に揃う。**.ds-removed の規則は .ds-subitem に依存していない**
|
|
1300
|
+
ので、そのまま両方の行に効く。 */
|
|
1267
1301
|
/* 削除できたことを行に残す。一覧そのものは翌朝まで更新されないので、消えたように見せない */
|
|
1268
1302
|
.ds-removed .ds-name { text-decoration: line-through; color: var(--muted); }
|
|
1269
1303
|
.ds-toggle-msg { font-size: 12px; color: var(--muted); }
|
|
@@ -1298,7 +1332,13 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1298
1332
|
夜間のスナップショットの状態バッジと**同時に2つの判定を並べない**ため、押したあとは
|
|
1299
1333
|
状態バッジをこの中の判定へ置き換える(同じ画面で「⚠️」と「接続OK」が並ぶのが、
|
|
1300
1334
|
この節で3回直してきた壊れ方そのもの) */
|
|
1301
|
-
|
|
1335
|
+
/* 押した結果の詳細。**押されるまでは何も出さない。**
|
|
1336
|
+
以前は中身が空でも点線だけが全行に出ており、何も意味していなかった(2026-08-20) */
|
|
1337
|
+
.ds-probe { display: none; }
|
|
1338
|
+
.ds-probe.is-shown {
|
|
1339
|
+
display: block; flex: 1 1 100%;
|
|
1340
|
+
margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border);
|
|
1341
|
+
}
|
|
1302
1342
|
.ds-probe-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1303
1343
|
/* いつ測った結果かを必ず添える。夜間のスナップショットと混ざると、どちらの話か読めない */
|
|
1304
1344
|
.ds-probe-at { font-size: 11.5px; color: var(--muted); }
|
|
@@ -1311,13 +1351,10 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1311
1351
|
とは別物で、同じ色にすると「押して繋ぎましょう」と読まれる */
|
|
1312
1352
|
.ds-gate-off { color: #9ca3af; }
|
|
1313
1353
|
.ds-pipes { display: flex; flex-direction: column; gap: 8px; }
|
|
1314
|
-
/*
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
.ds-owner-name { font-weight: 700; font-size: 13px; }
|
|
1319
|
-
.ds-owner-state { font-size: 12.5px; }
|
|
1320
|
-
.ds-owner-install { font-size: 12.5px; margin-left: auto; white-space: nowrap; }
|
|
1354
|
+
/* GitHub App のインストールへのリンク。**独立した「組織ごとの接続状況」の節は
|
|
1355
|
+
2026-08-20 に廃止した**(行のラベル+必要な行だけ囲う形へ)が、
|
|
1356
|
+
このクラスだけは案内と手順の両方で使い続けている */
|
|
1357
|
+
.ds-owner-install { font-size: 12.5px; white-space: nowrap; }
|
|
1321
1358
|
/* 実行状況の行に直し方を添える。同じ run の未達を別の節にも出すと画面に2回並ぶ */
|
|
1322
1359
|
.ds-pipe-wrap { display: flex; flex-direction: column; gap: 3px; }
|
|
1323
1360
|
.ds-pipe-todo { font-size: 12.5px; color: var(--muted); line-height: 1.7; padding-left: 4px; }
|
|
@@ -1342,17 +1379,10 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1342
1379
|
/* 「この接続は何のためのものでないか」(2026-08-18)。**説明文より強く出す**——
|
|
1343
1380
|
ここを読み飛ばした人が、社内チャンネルのためにボタンを押し、必ず失敗する */
|
|
1344
1381
|
.ds-scope-note { color: var(--text); }
|
|
1345
|
-
/*
|
|
1346
|
-
.ds-
|
|
1347
|
-
.ds-
|
|
1348
|
-
.ds-
|
|
1349
|
-
.ds-current-value.is-unset { color: var(--muted); font-weight: 400; }
|
|
1350
|
-
/* 接続先の一覧(#127)。**1件のときは従来どおりラベルの右に1行**——単数の案件で
|
|
1351
|
-
見た目が変わると、複数対応と関係のない全案件に「何か変わった」と読ませる */
|
|
1352
|
-
.ds-target-list { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
1353
|
-
.ds-target-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
1354
|
-
/* 行の中のボタンは行に揃える(.ds-toggle の縦マージンは節と節の間隔なので効かせない) */
|
|
1355
|
-
.ds-target-row .ds-toggle-btn { flex: 0 0 auto; }
|
|
1382
|
+
/* .ds-current / .ds-current-label / .ds-current-value / .ds-target-list /
|
|
1383
|
+
.ds-target-row は撤去した(2026-08-21)。Backlog の接続先の行は、Slack / GitHub /
|
|
1384
|
+
Figma / Drive と同じ .ds-list > .ds-card になったので、行間も見た目も .ds-list と
|
|
1385
|
+
.ds-card に揃う(それまでは同じモーダルの中でここだけ独自レイアウトだった)。 */
|
|
1356
1386
|
/* 入力欄のすぐ下。貼る直前に読める位置に置く */
|
|
1357
1387
|
.sec-note { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 6px 0 0; }
|
|
1358
1388
|
.sec-how p { margin: 0; }
|
|
@@ -1386,6 +1416,17 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1386
1416
|
/* まだ受信していない接続先(複数接続のとき混在する)。稼働中の行と見分けが付く濃さに落とす */
|
|
1387
1417
|
.rt-live-line.is-waiting { color: var(--muted); font-weight: 500; }
|
|
1388
1418
|
/* 手順は畳んで残す(張り替え・再確認用)。開いたときだけ間隔を空ける */
|
|
1419
|
+
/* 鍵カードの発行手順。**畳んであるだけで、中身は消していない**(2026-08-21)。
|
|
1420
|
+
**マージンは持たせない**——.ops-card は column flex の gap: 10px なので、
|
|
1421
|
+
自前のマージンを足すとここだけ間隔が広くなる(短く見せたいカードで逆効果)。
|
|
1422
|
+
閉じているときに最後の段落の下マージンが残らないよう、中の余白も畳む */
|
|
1423
|
+
.sec-fold { border: none; margin: 0; }
|
|
1424
|
+
.sec-fold > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
1425
|
+
.sec-fold[open] > summary { margin-bottom: 8px; }
|
|
1426
|
+
.sec-fold > .sec-how:last-child { margin-bottom: 0; }
|
|
1427
|
+
/* 鍵の入力欄も同じ理由でマージンを持たせない(.ds-addform の中は従来どおり6px)。
|
|
1428
|
+
子結合子で書くのは、リアルタイム同期カードの .rt-url(.rt-url-row の中)に当てないため */
|
|
1429
|
+
.ops-card > .sec-input { margin-bottom: 0; }
|
|
1389
1430
|
.rt-steps-fold > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
1390
1431
|
.rt-steps-fold[open] > summary { margin-bottom: 10px; }
|
|
1391
1432
|
.rt-steps-fold[open] > *:not(summary) { margin-top: 10px; }
|
|
@@ -1591,8 +1632,12 @@ ${capabilityState.toString()}
|
|
|
1591
1632
|
var countEl = { textContent: "" }; // 件数表示は廃止(ヘッダーから削除済み)
|
|
1592
1633
|
var footerEl = document.getElementById("footer-meta");
|
|
1593
1634
|
|
|
1635
|
+
// **画面の版も出す(2026-08-20)。** 新しい画面は案件ごとに順次切り替わるので、
|
|
1636
|
+
// **案件によって版が違う**。出していないと「直したはずなのに直っていない」の正体が
|
|
1637
|
+
// 「その案件だけ古い版」であることを切り分けられない
|
|
1594
1638
|
footerEl.textContent = "生成: " + SITE.generatedAt +
|
|
1595
|
-
(SITE.repoBaseUrl ? " / " + SITE.repoBaseUrl.split("://").pop() + " (" + SITE.branch + ")" : "")
|
|
1639
|
+
(SITE.repoBaseUrl ? " / " + SITE.repoBaseUrl.split("://").pop() + " (" + SITE.branch + ")" : "") +
|
|
1640
|
+
(SITE.viewerVersion ? " / 画面 v" + SITE.viewerVersion : "");
|
|
1596
1641
|
|
|
1597
1642
|
/**
|
|
1598
1643
|
* 「貼り付けて使う文字列」を、選べる形+コピーボタンで出す。
|
|
@@ -1602,6 +1647,19 @@ ${capabilityState.toString()}
|
|
|
1602
1647
|
* clipboard API が使えない環境(http・古いブラウザ・権限拒否)では、**選択までしてやって**
|
|
1603
1648
|
* 次の一手を伝える(何も起きないのが一番悪い)。
|
|
1604
1649
|
*/
|
|
1650
|
+
/**
|
|
1651
|
+
* 共有資料の取り込みに使う Google アカウント(2026-08-20)。
|
|
1652
|
+
*
|
|
1653
|
+
* **画面に出す。** それまでは「取り込み用アカウント」としか書いておらず、
|
|
1654
|
+
* **誰を招待すればいいのかが画面から分からなかった**——担当者に聞かないと
|
|
1655
|
+
* 手順が完了できなかった。
|
|
1656
|
+
*
|
|
1657
|
+
* **艦隊で1つ。** 案件ごとに違うものではないので定数で足りる
|
|
1658
|
+
* (案件ごとになったら fleet-status から運ぶ器に変える)。
|
|
1659
|
+
* 議事録の取り込みでも同じアカウントを使っている。
|
|
1660
|
+
*/
|
|
1661
|
+
var DRIVE_INGEST_ACCOUNT = "cortex-notetaker@classmethod.jp";
|
|
1662
|
+
|
|
1605
1663
|
function copyRow(text) {
|
|
1606
1664
|
// **input と button を使わない。** 追加フォームの中に置くので、input を足すと
|
|
1607
1665
|
// 「先頭がURL欄・末尾が名前欄」、button を足すと「最初のボタンが送信」という
|
|
@@ -2047,6 +2105,10 @@ ${capabilityState.toString()}
|
|
|
2047
2105
|
// **状態の語で「招待」と言わない**——言うと、何度招待しても直らないものを繰り返させる
|
|
2048
2106
|
"missing_scope": { text: "権限が足りません", cls: "ds-gate-warn" },
|
|
2049
2107
|
"unreachable": { text: "アクセス不可", cls: "ds-gate-warn" },
|
|
2108
|
+
// **この素の語が出るのは、まだ対応していない種別のときだけ。**
|
|
2109
|
+
// slack / github は下の DS_GATE_NO_TOKEN で経路ごとに言い換える(dsGateOf を通す)。
|
|
2110
|
+
// 残してあるのは「知らない種別のソースを黙って消さない」ため——新しいアダプタが
|
|
2111
|
+
// 増えた日に、行が状態を持たずに並ぶより、素の語でも出ているほうがよい
|
|
2050
2112
|
"no_token": { text: "トークン未設定", cls: "ds-gate-muted" },
|
|
2051
2113
|
"unknown": { text: "不明", cls: "ds-gate-muted" },
|
|
2052
2114
|
};
|
|
@@ -2056,7 +2118,7 @@ ${capabilityState.toString()}
|
|
|
2056
2118
|
* Slack のチャンネルは、どのワークスペースにあるかで読み取りの鍵が別物になる:
|
|
2057
2119
|
* - workspace 省略 … クラスメソッドの社内ワークスペース。中央 Bot の鍵(案件リポジトリの
|
|
2058
2120
|
* 設定)で読む。**画面から設定できるものが1つも無い。**
|
|
2059
|
-
* - workspace: "T…" …
|
|
2121
|
+
* - workspace: "T…" … お客様のワークスペース。「Slack ワークスペースに接続する」で
|
|
2060
2122
|
* 承認を貰って読む。**画面から自己解決できる。**
|
|
2061
2123
|
*
|
|
2062
2124
|
* どちらも no_token に潰して「トークン未設定」と出していたため、社内チャンネルを
|
|
@@ -2073,13 +2135,38 @@ ${capabilityState.toString()}
|
|
|
2073
2135
|
internal: {
|
|
2074
2136
|
text: "管理者の設定が必要です",
|
|
2075
2137
|
cls: "ds-gate-muted",
|
|
2076
|
-
|
|
2138
|
+
// **「この画面では設定できません」を必ず言う(2026-08-18 の不具合)。**
|
|
2139
|
+
// 状態だけを出していたので、設定する場所を探した人が
|
|
2140
|
+
// 顧客ワークスペースの接続へ吸い込まれ、押しても必ず失敗する画面を
|
|
2141
|
+
// 何度も試した。**次に誰が何をするか**まで書く
|
|
2142
|
+
help: "このチャンネルはクラスメソッドの社内ワークスペースにあります。読み取りを始めるには cortex 側(管理者)の設定が必要で、この画面では設定できません。cortex の担当者にご連絡ください。",
|
|
2077
2143
|
},
|
|
2078
2144
|
// お客様のワークスペース。従来どおり接続へ誘導する(自己解決できる唯一の経路)
|
|
2079
2145
|
customer: {
|
|
2080
2146
|
text: "トークン未設定",
|
|
2081
2147
|
cls: "ds-gate-muted",
|
|
2082
|
-
help: "このチャンネルがあるお客様の Slack
|
|
2148
|
+
help: "このチャンネルがあるお客様の Slack ワークスペースが、まだ接続されていない(または接続が切れている)状態です。「Slack ワークスペースに接続する」から接続してください。",
|
|
2149
|
+
},
|
|
2150
|
+
/* -------------------------------------------------------------------------
|
|
2151
|
+
* GitHub。**「トークン未設定」と言ってはいけない**(2026-08-21)
|
|
2152
|
+
* -------------------------------------------------------------------------
|
|
2153
|
+
* 読み取り用の個人アクセストークンは廃止し、GitHub App のインストールトークンに
|
|
2154
|
+
* 一本化した。**画面のどこにも、そのトークンを入れる欄はもう無い。**
|
|
2155
|
+
* それでも「トークン未設定」と出すと、上の DS_GATE_NO_TOKEN の項が書いている
|
|
2156
|
+
* 2026-08-18 の不具合と同じ形になる——しかも**探した先が存在しない**ぶん悪い。
|
|
2157
|
+
*
|
|
2158
|
+
* engine 側は、画面がこう言う前提で値を返している(scripts/fleet-status.mjs の
|
|
2159
|
+
* probeGithubGate): App のトークンを持たずに読もうとして 404/403 で落ちたなら、
|
|
2160
|
+
* 原因はスコープではなく**この組織に App が入っていない**。
|
|
2161
|
+
* **同じモーダルの中で、組織の案内が「App を入れてください」と言っているのに
|
|
2162
|
+
* 行と能力タイルが「トークン未設定」と言う**状態を作らないこと。
|
|
2163
|
+
*
|
|
2164
|
+
* **help は付けない。** 次の一手(インストール導線)は組織ごとの案内
|
|
2165
|
+
* (githubNotConnectedNodes)が1つだけ持つ。行ごとに繰り返すと一覧が案内で埋まる。
|
|
2166
|
+
* ----------------------------------------------------------------------- */
|
|
2167
|
+
github: {
|
|
2168
|
+
text: "App が未インストール",
|
|
2169
|
+
cls: "ds-gate-muted",
|
|
2083
2170
|
},
|
|
2084
2171
|
};
|
|
2085
2172
|
/**
|
|
@@ -2108,10 +2195,26 @@ ${capabilityState.toString()}
|
|
|
2108
2195
|
if (!s || s.indexOf("(名前未取得") === 0) return s;
|
|
2109
2196
|
return "#" + s.replace(/^#+/, "");
|
|
2110
2197
|
}
|
|
2111
|
-
/**
|
|
2198
|
+
/**
|
|
2199
|
+
* 行に出す接続状態。**no_token だけは経路で文面が分かれる。**
|
|
2200
|
+
*
|
|
2201
|
+
* | 種別 | no_token の意味 | 語 |
|
|
2202
|
+
* |---|---|---|
|
|
2203
|
+
* | slack(社内) | 中央 Bot の鍵が無い。**画面から設定できない** | 管理者の設定が必要です |
|
|
2204
|
+
* | slack(顧客) | お客様のワークスペースが未接続。接続で直せる | トークン未設定 |
|
|
2205
|
+
* | github | 読む鍵が1本も無かった=**この組織に App が入っていない** | App が未インストール |
|
|
2206
|
+
*
|
|
2207
|
+
* **ここを通さずに語を書き写さないこと。** 行バッジ・能力タイルの要約・
|
|
2208
|
+
* 「接続を確認」の結果が同じ関数を通るので、片方だけ古くなることが構造的に起きない
|
|
2209
|
+
* (attentionSummary / probeVerdict の項に、実際に割れた経緯がある)。
|
|
2210
|
+
*/
|
|
2112
2211
|
function dsGateOf(s) {
|
|
2113
|
-
if (s.gate === "no_token"
|
|
2114
|
-
|
|
2212
|
+
if (s.gate === "no_token") {
|
|
2213
|
+
if (s.type === "slack") {
|
|
2214
|
+
return isInternalSlack(s) ? DS_GATE_NO_TOKEN.internal : DS_GATE_NO_TOKEN.customer;
|
|
2215
|
+
}
|
|
2216
|
+
// github-issues / github-discussions のどちらも同じ原因(組織単位の App)
|
|
2217
|
+
if (String(s.type || "").indexOf("github") === 0) return DS_GATE_NO_TOKEN.github;
|
|
2115
2218
|
}
|
|
2116
2219
|
return DS_GATE[s.gate] || DS_GATE["unknown"];
|
|
2117
2220
|
}
|
|
@@ -2123,6 +2226,14 @@ ${capabilityState.toString()}
|
|
|
2123
2226
|
* 場所が画面に無いので、探した人が顧客ワークスペースの接続へ吸い込まれる(DS_GATE_NO_TOKEN
|
|
2124
2227
|
* の項の事故がここから始まっている)。1件でも顧客側が混ざるときは従来の文面のまま——
|
|
2125
2228
|
* そちらは接続で直せるので、経路を名指しできない要約に倒す方が正しい。
|
|
2229
|
+
*
|
|
2230
|
+
* **GitHub も同じ理由で「トークン」と言わない(2026-08-21)。** 読み取り用の個人
|
|
2231
|
+
* アクセストークンは廃止済みで、**入力欄が画面のどこにも無い**。ここが古い語のまま
|
|
2232
|
+
* 残っていたので、同じモーダルの中で組織の案内は「App を入れてください」・
|
|
2233
|
+
* その直下の行と能力タイルは「トークン未設定」という食い違いが出ていた。
|
|
2234
|
+
*
|
|
2235
|
+
* **語は dsGateOf から引く(写経しない)。** 行バッジと同じ関数を通すので、
|
|
2236
|
+
* 片方だけ古くなることが構造的に起きない。
|
|
2126
2237
|
*/
|
|
2127
2238
|
function attentionSummary(items) {
|
|
2128
2239
|
var bad = items.filter(function (s) { return sourceNeedsAttention(s); });
|
|
@@ -2130,9 +2241,532 @@ ${capabilityState.toString()}
|
|
|
2130
2241
|
var head = "⚠️ " + bad.length + "件";
|
|
2131
2242
|
var noToken = bad.filter(function (s) { return s.gate === "no_token"; });
|
|
2132
2243
|
if (noToken.length !== bad.length) return head + "に問題";
|
|
2133
|
-
|
|
2134
|
-
|
|
2244
|
+
// 原因を名指しできるのは、行バッジの語が**1つに揃っている**ときだけ
|
|
2245
|
+
var texts = {};
|
|
2246
|
+
noToken.forEach(function (s) { texts[dsGateOf(s).text] = true; });
|
|
2247
|
+
var names = [];
|
|
2248
|
+
for (var t in texts) if (Object.prototype.hasOwnProperty.call(texts, t)) names.push(t);
|
|
2249
|
+
if (names.length === 1) return head + "が読めていません(" + names[0] + ")";
|
|
2250
|
+
// 語が割れた。**Slack の中だけで割れているなら従来どおり顧客側の語に倒す**——
|
|
2251
|
+
// そちらは接続で直せるので、「管理者の設定が必要です」と言い切ると直せる人を止める。
|
|
2252
|
+
// 種別をまたいで割れているとき(Slack と GitHub が同時に読めていない等)は、
|
|
2253
|
+
// 次にする人も操作も違うので1つの原因として書けない。件数だけにする
|
|
2254
|
+
var allSlack = noToken.every(function (s) { return s.type === "slack"; });
|
|
2255
|
+
if (allSlack) return head + "が読めていません(" + DS_GATE_NO_TOKEN.customer.text + ")";
|
|
2256
|
+
return head + "に問題";
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Slack のチャンネルを、所属する組織ごとに分ける(2026-08-20)。
|
|
2260
|
+
*
|
|
2261
|
+
* ## なぜ純関数にするか
|
|
2262
|
+
*
|
|
2263
|
+
* ここが**判断の全部**。描画に混ぜると「どう分けたか」を実測できなくなる。
|
|
2264
|
+
* 並べる側は受け取ったものを出すだけで、意味を持たない
|
|
2265
|
+
* (GitHub は組織・Backlog はスペースで同じ形が要るため)。
|
|
2266
|
+
*
|
|
2267
|
+
* ## 分け方
|
|
2268
|
+
*
|
|
2269
|
+
* 1. workspace(team_id)があれば、その組織。表示名は台帳から引く
|
|
2270
|
+
* 2. orgUnknown の印が付いていれば「組織が分かりません」
|
|
2271
|
+
* 3. どちらも無ければ社内(クラスメソッド)
|
|
2272
|
+
*
|
|
2273
|
+
* **印を先に見ない。** 判定が済んで workspace が入った行にも、
|
|
2274
|
+
* 古いデータでは印が残っているかもしれない——**決まっている方を信じる**。
|
|
2275
|
+
*
|
|
2276
|
+
* ## 「分かりません」に入れる条件(ここが誤りやすい)
|
|
2277
|
+
*
|
|
2278
|
+
* **印が唯一の目印。** 夜間の実測(gate: "unreachable")で判断してはいけない——
|
|
2279
|
+
* unreachable はアーカイブ・削除・URLの打ち間違いでも起きるので、
|
|
2280
|
+
* それを「組織が分かりません」と出すと**誤った案内**になる。
|
|
2281
|
+
* 印はサーバが「全部の鍵で届かなかった」と実測した記録なので、意味が違う。
|
|
2282
|
+
*
|
|
2283
|
+
* @param items チャンネルの行(fleet-status の externalSources のうち type === "slack")
|
|
2284
|
+
* @param workspaces 台帳(slackWorkspaces() の返り値。null なら「知らない」)
|
|
2285
|
+
* @returns [{ key, title, items, unknown }] key は並びの安定のためだけに使う
|
|
2286
|
+
*/
|
|
2287
|
+
function groupSlackByOrg(items, workspaces) {
|
|
2288
|
+
var known = {};
|
|
2289
|
+
(workspaces || []).forEach(function (w) { if (w && w.teamId) known[w.teamId] = w.name || w.teamId; });
|
|
2290
|
+
var order = [];
|
|
2291
|
+
var byKey = {};
|
|
2292
|
+
(items || []).forEach(function (s) {
|
|
2293
|
+
var key, title, unknown = false;
|
|
2294
|
+
if (s.workspace) {
|
|
2295
|
+
key = s.workspace;
|
|
2296
|
+
// **台帳に無ければ team_id を出す。** 空欄より、少なくとも突き合わせられる
|
|
2297
|
+
title = known[s.workspace] || s.workspace;
|
|
2298
|
+
} else if (s.orgUnknown === true) {
|
|
2299
|
+
key = "__unknown__";
|
|
2300
|
+
title = "組織不明";
|
|
2301
|
+
unknown = true;
|
|
2302
|
+
} else {
|
|
2303
|
+
key = "__internal__";
|
|
2304
|
+
title = "クラスメソッド";
|
|
2305
|
+
}
|
|
2306
|
+
var g = byKey[key];
|
|
2307
|
+
if (!g) { g = { key: key, title: title, items: [], unknown: unknown }; byKey[key] = g; order.push(g); }
|
|
2308
|
+
g.items.push(s);
|
|
2309
|
+
});
|
|
2310
|
+
// **社内を先、「分かりません」を最後にする。** 分かりませんが先頭に来ると、
|
|
2311
|
+
// 直すものが無い人にも毎回それが最初に見える
|
|
2312
|
+
order.sort(function (a, b) {
|
|
2313
|
+
var rank = function (g) { return g.key === "__internal__" ? 0 : g.unknown ? 2 : 1; };
|
|
2314
|
+
return rank(a) - rank(b);
|
|
2315
|
+
});
|
|
2316
|
+
return order;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
/**
|
|
2320
|
+
* 会議の取り込みの案内。**操作ではなく条件を示す。**
|
|
2321
|
+
*
|
|
2322
|
+
* 取り込まれるには2つとも要る:
|
|
2323
|
+
* ① cortex-notetaker Bot が会議に招待されている(=取り込む意思。これが同意のゲート)
|
|
2324
|
+
* ② 会議名が照合キーのどれかを含む(含まなければ、録れても案件に振り分けられない)
|
|
2325
|
+
*
|
|
2326
|
+
* ②の材料を画面が出さないと、招待したのに届かない理由が分からない。
|
|
2327
|
+
*/
|
|
2328
|
+
function meetingHowTo() {
|
|
2329
|
+
// **2つの条件がANDであることを、読み飛ばせない形で出す。** 散文で並べていたときに
|
|
2330
|
+
// 「会議名は合っているのにBotを招待しておらず取り込まれなかった」事故が実際に起きた。
|
|
2331
|
+
// 会議名の条件は上の行(ds-keys)が出すので、ここでは「上の語」と参照するに留める。
|
|
2332
|
+
var wrap = el("div", { class: "sec-how" });
|
|
2333
|
+
wrap.appendChild(el("p", { text: "次の2つが揃った会議だけが取り込まれます。" }));
|
|
2334
|
+
var ul = el("ul", { class: "sec-how-list" });
|
|
2335
|
+
ul.appendChild(el("li", { text: "cortex-notetaker Bot が招待されている(定例はシリーズに1回でOK)" }));
|
|
2336
|
+
ul.appendChild(el("li", { text: "会議名に上の目印が入っている" }));
|
|
2337
|
+
wrap.appendChild(ul);
|
|
2338
|
+
wrap.appendChild(el("p", { text: "どちらか一方だけでは取り込まれません。" }));
|
|
2339
|
+
return wrap;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* Slack の承認画面へ進むボタン(2026-08-20 に接続カードから切り出した)。
|
|
2344
|
+
*
|
|
2345
|
+
* ## 押す人についての注意を必ず添える
|
|
2346
|
+
*
|
|
2347
|
+
* **承認画面はワークスペースを指定できない**(開始の口に team を渡す口が無い)。
|
|
2348
|
+
* **承認する人がログインしているワークスペース**にボットが入る。だから
|
|
2349
|
+
* 「〈お客様の名前〉と接続する」とは書けない——保証できないことを書かない。
|
|
2350
|
+
*
|
|
2351
|
+
* お客様の Slack なら、クラスメソッドの人間は押せないことが多い
|
|
2352
|
+
* (その Slack の管理者にお願いすることになる)。それを書かないと、
|
|
2353
|
+
* 押しても進まない人が「壊れている」と読む。
|
|
2354
|
+
*/
|
|
2355
|
+
function slackConnectButton() {
|
|
2356
|
+
var wrap = el("div", { class: "ds-toggle" });
|
|
2357
|
+
// **「この」を付けない(2026-08-20・オーナー指摘)。**
|
|
2358
|
+
// どのワークスペースを指すのか読み取れない——そもそも**指定できない**
|
|
2359
|
+
// (承認する人がログインしているワークスペースに入る)ので、
|
|
2360
|
+
// 特定のものを指すような書き方は嘘にもなる
|
|
2361
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "Slack ワークスペースに接続する" });
|
|
2362
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
2363
|
+
btn.addEventListener("click", async function () {
|
|
2364
|
+
var intake = SITE.intake;
|
|
2365
|
+
if (!intake) return;
|
|
2366
|
+
btn.disabled = true;
|
|
2367
|
+
msg.className = "ds-toggle-msg";
|
|
2368
|
+
msg.textContent = "承認画面を準備しています…";
|
|
2369
|
+
try {
|
|
2370
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
2371
|
+
var res = await fetch(pfx + "op=slack-oauth-start&t=" + encodeURIComponent(intake.token), {
|
|
2372
|
+
method: "POST",
|
|
2373
|
+
headers: { "content-type": "application/json" },
|
|
2374
|
+
body: "{}",
|
|
2375
|
+
});
|
|
2376
|
+
var j = await res.json().catch(function () { return {}; });
|
|
2377
|
+
if (!res.ok || !j.url) {
|
|
2378
|
+
// サーバの文言をそのまま出す(未設定のときは「準備中です」が返る)
|
|
2379
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2380
|
+
msg.textContent = "✗ " + (j.message || "接続を開始できませんでした。");
|
|
2381
|
+
btn.disabled = false;
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
// **同じタブで遷移する。** 別タブだと、承認後の「接続しました」を見た人が
|
|
2385
|
+
// 元のタブに戻る動線を自分で見つけることになる
|
|
2386
|
+
location.href = j.url;
|
|
2387
|
+
} catch (e) {
|
|
2388
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2389
|
+
msg.textContent = "✗ 接続を開始できませんでした。時間をおいて試してください。";
|
|
2390
|
+
btn.disabled = false;
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
wrap.appendChild(el("p", {
|
|
2394
|
+
class: "ds-group-note",
|
|
2395
|
+
text: "ボタンを押すと Slack の承認画面が開きます。承認できるのは、その Slack の管理者だけです"
|
|
2396
|
+
+ "(お客様の Slack なら、お客様の管理者にお願いしてください)。"
|
|
2397
|
+
+ "お願いする権限は読み取りだけで、Cortex から投稿することはありません。",
|
|
2398
|
+
}));
|
|
2399
|
+
wrap.appendChild(btn);
|
|
2400
|
+
wrap.appendChild(msg);
|
|
2401
|
+
return wrap;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/** 台帳から、そのワークスペースの接続状態を引く(無ければ null=断定しない) */
|
|
2405
|
+
function tokenStateOf(workspaces, teamId) {
|
|
2406
|
+
var list = workspaces || [];
|
|
2407
|
+
for (var i = 0; i < list.length; i++) {
|
|
2408
|
+
if (list[i] && list[i].teamId === teamId) return list[i].tokenState || null;
|
|
2409
|
+
}
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/**
|
|
2414
|
+
* 「組織が分かりません」の行を、サーバに判定し直してもらう(2026-08-20)。
|
|
2415
|
+
*
|
|
2416
|
+
* **1回の表示につき1度だけ。** 組が複数あっても、呼ぶのは1回。
|
|
2417
|
+
*
|
|
2418
|
+
* ## 結果をその場で反映しない理由
|
|
2419
|
+
*
|
|
2420
|
+
* この画面が読んでいるのは**夜間に作られた実測ファイル**で、
|
|
2421
|
+
* 設定ファイルを直しても表示はすぐ変わらない。**嘘をつくよりは待たせる**——
|
|
2422
|
+
* 「反映しました」とだけ言い、表示が変わる時期を書く。
|
|
2423
|
+
* (設定を変える他の操作も同じ約束で「次回の Gold 昇格から反映されます」と言っている)
|
|
2424
|
+
*/
|
|
2425
|
+
var orgResolveDone = false;
|
|
2426
|
+
function requestOrgResolve() {
|
|
2427
|
+
if (orgResolveDone) return;
|
|
2428
|
+
orgResolveDone = true;
|
|
2429
|
+
var intake = SITE.intake;
|
|
2430
|
+
if (!intake) return;
|
|
2431
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
2432
|
+
// **描画の途中で投げない。** ここは行を組み立てている最中に呼ばれるので、
|
|
2433
|
+
// 例外が出ると**画面そのものが描かれない**(fetch が無い環境で実際にそうなった)。
|
|
2434
|
+
// 判定し直しは「できたら直る」性質のもので、失敗しても行は
|
|
2435
|
+
// 「組織が分かりません」のまま残る——画面を落としてまでやることではない。
|
|
2436
|
+
//
|
|
2437
|
+
// **守りは二重(この行と下の try/catch)。** 片方だけ消しても
|
|
2438
|
+
// もう片方が受けるので変異検証では赤にならないが、**両方消すと6件赤**になる
|
|
2439
|
+
// (実際に当てて確かめた)。二重なのは、投げ方が2通りあるため——
|
|
2440
|
+
// fetch が存在しない(ReferenceError)と、呼び出しが投げる
|
|
2441
|
+
if (typeof fetch !== "function") return;
|
|
2442
|
+
try {
|
|
2443
|
+
fetch(pfx + "op=slack-resolve&t=" + encodeURIComponent(intake.token), {
|
|
2444
|
+
method: "POST",
|
|
2445
|
+
headers: { "content-type": "application/json" },
|
|
2446
|
+
body: "{}",
|
|
2447
|
+
})
|
|
2448
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
2449
|
+
.then(function (j) {
|
|
2450
|
+
if (!j || !j.resolved) return;
|
|
2451
|
+
// **表示は次の更新まで変わらない。** それを書かないと、
|
|
2452
|
+
// 「反映したのに何も変わらない」と読まれてもう一度押すことになる
|
|
2453
|
+
var note = document.querySelector(".ds-org-resolved");
|
|
2454
|
+
if (!note) return;
|
|
2455
|
+
note.textContent = "✓ " + j.resolved + "件の所属を反映しました(この一覧の表示は次回の更新・毎晩22時ごろから正しくなります)。";
|
|
2456
|
+
})
|
|
2457
|
+
.catch(function () {
|
|
2458
|
+
// **黙って諦める。** 直せなかっただけで、行は「組織が分かりません」のまま残る。
|
|
2459
|
+
// 利用者にできることは変わらないので、失敗を出しても迷わせるだけ
|
|
2460
|
+
});
|
|
2461
|
+
} catch (e) {
|
|
2462
|
+
// 同上。呼び出しそのものが投げても画面は描く
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
/**
|
|
2467
|
+
* GitHub のリポジトリを、組織ごとに分ける(2026-08-20・Slack からの横展開)。
|
|
2468
|
+
*
|
|
2469
|
+
* ## Slack との違い
|
|
2470
|
+
*
|
|
2471
|
+
* **帰属を API に聞く必要が無い。** 組織は ref(owner/repo)に入っている。
|
|
2472
|
+
* だから判定は文字列を切るだけで、実測も印も要らない。
|
|
2473
|
+
*
|
|
2474
|
+
* ## 接続状態は台帳(githubOwners)から引く
|
|
2475
|
+
*
|
|
2476
|
+
* トークンは**組織ごとにしか効かない**(Fine-grained PAT も App の
|
|
2477
|
+
* インストールトークンも1組織分)ので、状態の単位も組織。
|
|
2478
|
+
*
|
|
2479
|
+
* @returns [{ key, title, items, state }] state は "ok" | "warn" | "unsure" | null
|
|
2480
|
+
*/
|
|
2481
|
+
/** 台帳から、その組織の情報を引く(無ければ null=断定しない) */
|
|
2482
|
+
function githubOwnerMeta(owner) {
|
|
2483
|
+
var list = fleetVal("githubOwners") || [];
|
|
2484
|
+
for (var i = 0; i < list.length; i++) {
|
|
2485
|
+
if (list[i] && list[i].owner === owner) return list[i];
|
|
2486
|
+
}
|
|
2487
|
+
return null;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function groupGithubByOwner(items, owners) {
|
|
2491
|
+
var known = {};
|
|
2492
|
+
(owners || []).forEach(function (o) { if (o && o.owner) known[o.owner] = o; });
|
|
2493
|
+
var order = [];
|
|
2494
|
+
var byKey = {};
|
|
2495
|
+
(items || []).forEach(function (s) {
|
|
2496
|
+
var owner = String(s.ref || "").split("/")[0] || "";
|
|
2497
|
+
var g = byKey[owner];
|
|
2498
|
+
if (!g) {
|
|
2499
|
+
var o = known[owner];
|
|
2500
|
+
// **台帳に無ければ断定しない。** 夜間バッチの成果物なので、
|
|
2501
|
+
// 立ち上げ直後の案件には無い(「0件」と「知らない」を同じ扱いにすると嘘になる)
|
|
2502
|
+
var state = null;
|
|
2503
|
+
// **「warn」 は else 側の総取り。** ここに載っていない値は全部「未接続」に落ちるので、
|
|
2504
|
+
// engine が 「source」 に新しい値を足したら**必ずここも足す**——載せ忘れると、
|
|
2505
|
+
// 読めている組織が顧客の画面で一斉に「まだ接続されていません」になる。
|
|
2506
|
+
//
|
|
2507
|
+
// 「workflow」 は「App は無いが、ワークフロー自身のトークンで全部読めた」=**接続OK**。
|
|
2508
|
+
// 「token」(廃止した個人アクセストークンで読めていた)も**当面 OK のまま受ける**——
|
|
2509
|
+
// 画面(Amplify で即配布)と engine(案件スタブが 「@v1」 を指しており、タグを
|
|
2510
|
+
// 進めるまで古い 「fleet-status.json」 のまま)は**別々に配られる**ので、
|
|
2511
|
+
// どちらを先に出しても片側だけ新しい期間ができる。その間に 「token」 を落とすと、
|
|
2512
|
+
// コミット済みの値を読んだ画面が偽の警告を出す。**外すのはフェーズ2**(engine の
|
|
2513
|
+
// v1 が全案件へ行き渡ったことを確かめてから)。
|
|
2514
|
+
if (o) {
|
|
2515
|
+
state = (o.source === "app" || o.source === "workflow" || o.source === "token") ? "ok"
|
|
2516
|
+
: o.source === "unknown" ? "unsure"
|
|
2517
|
+
: "warn";
|
|
2518
|
+
}
|
|
2519
|
+
g = { key: owner, title: owner || "(組織不明)", items: [], state: state, meta: o || null };
|
|
2520
|
+
byKey[owner] = g;
|
|
2521
|
+
order.push(g);
|
|
2522
|
+
}
|
|
2523
|
+
g.items.push(s);
|
|
2524
|
+
});
|
|
2525
|
+
// **繋がっている組織を先に、繋がっていない組織を後に。**
|
|
2526
|
+
// 直すものが無い人にも「未接続」が最初に見えるのは、優先順位が逆
|
|
2527
|
+
order.sort(function (a, b) {
|
|
2528
|
+
var rank = function (g) { return g.state === "warn" ? 2 : g.state === "unsure" ? 1 : 0; };
|
|
2529
|
+
return rank(a) - rank(b);
|
|
2530
|
+
});
|
|
2531
|
+
return order;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
/**
|
|
2535
|
+
* GitHub のリポジトリを**平らに**並べる(2026-08-20)。
|
|
2536
|
+
*
|
|
2537
|
+
* Slack と同じ形。並び順は組織ごとだが**箱で囲まない**。
|
|
2538
|
+
* どの組織かは**行のラベル**が示し、**接続が要る組だけ**案内が行を抱える。
|
|
2539
|
+
*/
|
|
2540
|
+
function githubRows(items) {
|
|
2541
|
+
var owners = fleetVal("githubOwners");
|
|
2542
|
+
var out = [];
|
|
2543
|
+
groupGithubByOwner(items, owners).forEach(function (g) {
|
|
2544
|
+
// **行ごとに meta を引く。** issues と discussions でアイコンとラベルが違う
|
|
2545
|
+
var cards = g.items.map(function (x) {
|
|
2546
|
+
return extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" });
|
|
2547
|
+
});
|
|
2548
|
+
if (g.state === "warn") {
|
|
2549
|
+
out.push(orgNoteBox(githubNotConnectedNodes(g), cards));
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
if (g.state === "unsure") {
|
|
2553
|
+
// **確かめられなかった夜は、そう言う。** gh の不在・タイムアウト・
|
|
2554
|
+
// ネットワーク断で起きる。「接続OK」と出すと同じ画面の集計と食い違い、
|
|
2555
|
+
// 「未接続」と出すと繋がっている組織に偽の警告を出す——どちらも嘘。
|
|
2556
|
+
// **導線は出さない**(すでに入っているかもしれないので、押させる理由が無い)
|
|
2557
|
+
out.push(orgNoteBox(githubUnsureNodes(g), cards));
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
cards.forEach(function (n) { out.push(n); });
|
|
2561
|
+
});
|
|
2562
|
+
return out;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
/** 確かめられなかった組織の案内。**導線は出さない** */
|
|
2566
|
+
function githubUnsureNodes(g) {
|
|
2567
|
+
var o = g.meta || {};
|
|
2568
|
+
return [
|
|
2569
|
+
el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " の接続を確認できませんでした" }),
|
|
2570
|
+
el("p", {
|
|
2571
|
+
class: "ds-group-note",
|
|
2572
|
+
// **件数を出す。** 4リポのうち3つは確かめられている夜に
|
|
2573
|
+
// 「4リポジトリを確かめられていません」と読めるのは実態と違う
|
|
2574
|
+
text: (o.repos || g.items.length) + "リポジトリ中 "
|
|
2575
|
+
+ (o.unchecked != null ? o.unchecked : (o.repos || g.items.length))
|
|
2576
|
+
+ "件の接続を確かめられていません。翌朝の表示をご確認ください。",
|
|
2577
|
+
}),
|
|
2578
|
+
];
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* 繋がっていない組織の案内。
|
|
2583
|
+
*
|
|
2584
|
+
* **「読めない」の原因は1つではない。** 以前はどの組織にも
|
|
2585
|
+
* 「この組織に Cortex の GitHub App を入れると読めるようになります」と出していたが、
|
|
2586
|
+
* **App は入っているのにそのリポジトリが選ばれていない**組織にはこれが効かない。
|
|
2587
|
+
* インストールは済んでいるので、押しても画面は変わらず、何度押しても直らない。
|
|
2588
|
+
*
|
|
2589
|
+
* 「appInstalled」 が原因を分ける(engine がインストールトークンを取れたかどうか):
|
|
2590
|
+
*
|
|
2591
|
+
* | 値 | 意味 | 案内 |
|
|
2592
|
+
* |---|---|---|
|
|
2593
|
+
* | 「false」 | App のトークンが取れなかった=未インストール | インストールへ |
|
|
2594
|
+
* | 「true」 | 取れたのに読めない=そのリポジトリが未選択 | **インストール設定**へ |
|
|
2595
|
+
* | 「null」 | その夜は確かめられなかった | **どちらも出さない**(押しても直るとは限らない) |
|
|
2596
|
+
*
|
|
2597
|
+
* **自社か顧客かで文言を分けない。** 組織名(「classmethod-*」)で分岐すると、
|
|
2598
|
+
* 新しい自社組織が増えたときに黙って間違った案内を出す。どちらにも通じる文にする。
|
|
2599
|
+
*/
|
|
2600
|
+
function githubNotConnectedNodes(g) {
|
|
2601
|
+
var out = [];
|
|
2602
|
+
var o = g.meta || {};
|
|
2603
|
+
var repos = o.repos || g.items.length;
|
|
2604
|
+
var unread = o.unreadable != null ? o.unreadable : g.items.length;
|
|
2605
|
+
var installed = o.appInstalled;
|
|
2606
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
2607
|
+
// **読める件数を出す。** 総数だけ出すと、同じ画面の集計と食い違う(#101 で3度直した形)
|
|
2608
|
+
var head = repos + "リポジトリ中 " + unread + "件が読めていません。";
|
|
2609
|
+
if (installed === true) {
|
|
2610
|
+
// App は入っている。**インストールをもう一度促さないこと**——済んでいる操作を促すのは
|
|
2611
|
+
// 「設定する必要が無いものに設定を促す」偽の警告と同じで、しかも直らない
|
|
2612
|
+
//
|
|
2613
|
+
// **原因を1つに断定しない(2026-08-21)。** 「appInstalled」 が分けられるのは
|
|
2614
|
+
// 「①未インストール」と「②未選択/③消えた・改名」までで、**②と③は分かれない**。
|
|
2615
|
+
// 「対象に選ばれていません」と言い切ると、③の人は一覧に無いリポジトリを
|
|
2616
|
+
// 探し続けることになる(押しても直らない導線と同じ形)。逃げ道を1文で示す。
|
|
2617
|
+
//
|
|
2618
|
+
// **設定画面の宛先も断定しない。** リンク先は 「/settings/installations」=
|
|
2619
|
+
// **押した本人の**インストール一覧で(appSettingsLink の項に、組織の設定ページが
|
|
2620
|
+
// 管理者以外で 404 になる理由がある)、App を入れたのが別のメンバーや
|
|
2621
|
+
// 顧客側のオーナーなら**着いた先は空**。そこで詰まる人に次の宛先を渡す。
|
|
2622
|
+
out.push(el("p", {
|
|
2623
|
+
class: "ds-group-note",
|
|
2624
|
+
text: head + "GitHub App はこの組織に入っていますが、これらのリポジトリが読み取りの対象に選ばれていません。"
|
|
2625
|
+
+ "インストール設定を開いて、対象のリポジトリを追加してください。"
|
|
2626
|
+
+ "設定は App を入れた方の画面に出ます。見当たらない場合は、この組織で App を入れた方にご依頼ください。"
|
|
2627
|
+
+ "リポジトリ自体が一覧に見当たらない場合は、移動・改名・削除されている可能性があるので、登録から外してください。"
|
|
2628
|
+
+ "お客様の組織では、組織のオーナーに承認を依頼してください。",
|
|
2629
|
+
}));
|
|
2630
|
+
out.push(appSettingsLink("インストール設定を開く →"));
|
|
2631
|
+
return out;
|
|
2632
|
+
}
|
|
2633
|
+
if (installed === false) {
|
|
2634
|
+
out.push(el("p", {
|
|
2635
|
+
class: "ds-group-note",
|
|
2636
|
+
text: head + "この組織に Cortex の GitHub App を入れると読めるようになります(Issues の読み取りだけ)。"
|
|
2637
|
+
+ "お客様の組織では、組織のオーナーに承認を依頼してください。",
|
|
2638
|
+
}));
|
|
2639
|
+
out.push(appInstallLink("この組織にインストール →"));
|
|
2640
|
+
return out;
|
|
2641
|
+
}
|
|
2642
|
+
// 「null」(確かめられなかった)と、この値を知らない古い fleet-status.json。
|
|
2643
|
+
// **導線を出さない。** どちらが原因か分からないので、押させると外れたほうを
|
|
2644
|
+
// 押させることになる(済んでいるインストールをもう一度、あるいは効かない設定画面へ)
|
|
2645
|
+
out.push(el("p", {
|
|
2646
|
+
class: "ds-group-note",
|
|
2647
|
+
text: head + "原因(GitHub App が入っていない/入っているがこれらのリポジトリが選ばれていない)は、"
|
|
2648
|
+
+ "今回の実行では確かめられませんでした。翌朝の表示をご確認ください。",
|
|
2649
|
+
}));
|
|
2650
|
+
return out;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
/**
|
|
2654
|
+
* Slack のチャンネルを**平らに**並べる(2026-08-20)。
|
|
2655
|
+
*
|
|
2656
|
+
* ## なぜ折りたたみをやめたか
|
|
2657
|
+
*
|
|
2658
|
+
* オーナー指摘:
|
|
2659
|
+
* > それぞれのワークスペースごとにセクション分けしてるけど、普通に並べて、
|
|
2660
|
+
* > 他の接続OKとかの横にどのワークスペースを出すだけでいい。
|
|
2661
|
+
* > 開いて閉じてみたいな感じで並ぶ必要があんまりわかんない。なんか複雑になっちゃってる。
|
|
2662
|
+
*
|
|
2663
|
+
* **並び順は組織ごと**(社内 → 顧客 → 分かりません)にするが、**箱で囲まない**。
|
|
2664
|
+
* どの組織かは**行のラベル**が示す(同じチャンネル名が2つの組織に並びうるので、
|
|
2665
|
+
* ラベル自体は必要)。
|
|
2666
|
+
*
|
|
2667
|
+
* ## 「組織が分かりません」の案内だけは別
|
|
2668
|
+
*
|
|
2669
|
+
* 3つの原因と、招待コマンド・接続ボタン・登録の解除が要る。**行ごとに繰り返すと
|
|
2670
|
+
* 一覧が案内で埋まる**ので、その並びの手前に1つだけ置く。
|
|
2671
|
+
*/
|
|
2672
|
+
function slackRows(items, meta) {
|
|
2673
|
+
var ws = slackWorkspaces();
|
|
2674
|
+
var out = [];
|
|
2675
|
+
groupSlackByOrg(items, ws).forEach(function (g) {
|
|
2676
|
+
var cards = g.items.map(function (x) { return extSourceCard(x, meta); });
|
|
2677
|
+
if (g.unknown) {
|
|
2678
|
+
// **開いたときに1度だけ、判定し直しをサーバへ頼む。**
|
|
2679
|
+
// 承認が通っても、誰かが判定し直さなければ設定ファイルは古いまま。
|
|
2680
|
+
// OAuth の戻り先は固定ページ(この画面には戻らない)ので、
|
|
2681
|
+
// **利用者が操作タブを開き直したこの瞬間**が唯一の合図になる
|
|
2682
|
+
requestOrgResolve();
|
|
2683
|
+
// **案内が行を抱える(2026-08-20・オーナー指摘)。**
|
|
2684
|
+
// 「次のチャンネル」と書いても、囲われていないと**どれを指すのか分からない**。
|
|
2685
|
+
// 折りたたみは付けない——開閉させたいのではなく、**範囲を示したい**
|
|
2686
|
+
out.push(orgNoteBox(unknownOrgNodes(), cards));
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (g.key !== "__internal__") {
|
|
2690
|
+
var st = tokenStateOf(ws, g.key);
|
|
2691
|
+
// **接続が切れたときの導線を必ず残す。** それまでは接続カードの中の一覧に
|
|
2692
|
+
// 出していた。カードごと消したので、ここへ移す——移さないと
|
|
2693
|
+
// **接続が切れたことに気づけず、直す手段も画面から消える**
|
|
2694
|
+
if (st === "revoked" || st === "missing") {
|
|
2695
|
+
out.push(orgNoteBox(brokenOrgNodes(g.title, st), cards));
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
cards.forEach(function (n) { out.push(n); });
|
|
2700
|
+
});
|
|
2701
|
+
return out;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* 案内と、その案内が指す行を1つの箱にまとめる(2026-08-20)。
|
|
2706
|
+
*
|
|
2707
|
+
* **囲わないと「次のチャンネル」がどれを指すのか分からない**(オーナー指摘)。
|
|
2708
|
+
* **折りたたみは付けない**——開閉させたいのではなく、範囲を示したいだけ。
|
|
2709
|
+
*/
|
|
2710
|
+
function orgNoteBox(noteNodes, cards) {
|
|
2711
|
+
var box = el("div", { class: "ds-orgnote" });
|
|
2712
|
+
noteNodes.forEach(function (n) { if (n) box.appendChild(n); });
|
|
2713
|
+
var list = el("div", { class: "ds-orgnote-rows" });
|
|
2714
|
+
cards.forEach(function (c) { list.appendChild(c); });
|
|
2715
|
+
box.appendChild(list);
|
|
2716
|
+
return box;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
/** 「組織不明」の案内。**原因を1つに絞らない** */
|
|
2720
|
+
function unknownOrgNodes() {
|
|
2721
|
+
// **接続だけを勧めると、招待すれば済む人に「直らない処方」を出す。**
|
|
2722
|
+
// 非公開チャンネルは、同じ Slack でも招待前は「存在しない」と返る
|
|
2723
|
+
var out = [];
|
|
2724
|
+
// **「読めませんでした」では、行の「Slackで招待してください」と区別が付かない**
|
|
2725
|
+
//(オーナー指摘・2026-08-20)。どちらも読めていないが、**分かっていることが違う**:
|
|
2726
|
+
// 行に「招待してください」… クラスメソッドのチャンネルだと**分かっている**。招待だけが足りない
|
|
2727
|
+
// この案内 … **どのSlackのものかすら分からない**(どの鍵でも届かなかった)
|
|
2728
|
+
// 状態の名前を「分かりません」に揃える
|
|
2729
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ 組織不明のチャンネル" }));
|
|
2730
|
+
out.push(el("p", {
|
|
2731
|
+
class: "ds-group-note",
|
|
2732
|
+
text: "Cortex が持っているどの鍵でも、このチャンネルに届きませんでした。次のどれかです。",
|
|
2733
|
+
}));
|
|
2734
|
+
var ul = el("ul", { class: "ds-group-note" });
|
|
2735
|
+
// **公開との違いをここで言い切る。** 「同じ未招待なのに、なぜ上の行は
|
|
2736
|
+
// 組織が分かっていて、こちらは分からないのか」が読み取れないと、
|
|
2737
|
+
// 利用者は同じ状態が2通りに見えることに戸惑う
|
|
2738
|
+
ul.appendChild(el("li", { text: "非公開チャンネルで、まだ Cortex を招待していない → 下のコマンドを Slack のそのチャンネルに貼ってください" }));
|
|
2739
|
+
ul.appendChild(el("li", { text: "お客様の Slack にあり、まだつながっていない → 下のボタンから接続します" }));
|
|
2740
|
+
ul.appendChild(el("li", { text: "チャンネルがアーカイブ/削除された、URLが違う → 「登録を解除」を押してください" }));
|
|
2741
|
+
out.push(ul);
|
|
2742
|
+
out.push(copyRow("/invite @Cortex"));
|
|
2743
|
+
out.push(slackConnectButton());
|
|
2744
|
+
// 判定し直しの結果を出す場所(何も起きなければ空のまま)
|
|
2745
|
+
out.push(el("p", { class: "ds-group-note ds-org-resolved" }));
|
|
2746
|
+
return out;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
/** 接続が切れた/未接続のワークスペースの案内 */
|
|
2750
|
+
function brokenOrgNodes(title, state) {
|
|
2751
|
+
var out = [];
|
|
2752
|
+
out.push(el("div", {
|
|
2753
|
+
class: "ds-orgnote-title",
|
|
2754
|
+
text: state === "revoked" ? "⚠️ " + title + " との接続が切れています" : "⚠️ " + title + " はまだ接続されていません",
|
|
2755
|
+
}));
|
|
2756
|
+
out.push(el("p", {
|
|
2757
|
+
class: "ds-group-note",
|
|
2758
|
+
// **revoked と missing を分ける。** 前者は「接続し直して」、後者は「まだ完了していない」で
|
|
2759
|
+
// 次の一手が違う。unknown には何も出さない——繋がっているのに
|
|
2760
|
+
// 「接続し直して」と言う方が、黙っているより悪い
|
|
2761
|
+
text: state === "revoked"
|
|
2762
|
+
? "Slack 側で Cortex のアプリが削除されたか、接続が無効になっています。もう一度接続してください。"
|
|
2763
|
+
: "このワークスペースのチャンネルが登録されていますが、接続が見つかりません。もう一度接続してください。",
|
|
2764
|
+
}));
|
|
2765
|
+
out.push(slackConnectButton());
|
|
2766
|
+
return out;
|
|
2135
2767
|
}
|
|
2768
|
+
|
|
2769
|
+
|
|
2136
2770
|
var DS_KIND_ICON = {
|
|
2137
2771
|
"課題管理": "📋",
|
|
2138
2772
|
"会議": "🗒️",
|
|
@@ -2226,6 +2860,63 @@ ${capabilityState.toString()}
|
|
|
2226
2860
|
*/
|
|
2227
2861
|
var GITHUB_APP_INSTALL_URL = "https://github.com/apps/classmethod-cortex-app/installations/new";
|
|
2228
2862
|
|
|
2863
|
+
/**
|
|
2864
|
+
* GitHub App のインストールへ行くリンク(2026-08-20 に部品化)。
|
|
2865
|
+
*
|
|
2866
|
+
* **同じものを2箇所に書かない。** 組織ごとの案内と、追加フォームの手順が
|
|
2867
|
+
* 同じ導線を持つ(#138 で「1箇所にしか無い」ことが事故になったので、
|
|
2868
|
+
* **むしろ複数あるのが正しい**)。写経すると、URLの張り替えで片方だけ古くなる。
|
|
2869
|
+
*
|
|
2870
|
+
* **正本は手順の側。** 組織ごとの案内は行が無いと出ないので、
|
|
2871
|
+
* 行0件の案件では手順側しか残らない。
|
|
2872
|
+
*
|
|
2873
|
+
* **⚠️ ただし手順は追加フォームの中にあり、追加フォームは
|
|
2874
|
+
* SITE.intake が無いと丸ごと消える**(renderExternalAdders の冒頭)。
|
|
2875
|
+
* つまり **intake 無し+行0件** の案件では App の導線が1本も無くなる。
|
|
2876
|
+
* いまは config.ts の intakeUrl が艦隊で1つ・全案件に渡っているので
|
|
2877
|
+
* その組み合わせは起きない(2026-08-20 に確認)。
|
|
2878
|
+
* **案件ごとに intake を出し分けるようになったら、ここを見直すこと。**
|
|
2879
|
+
*/
|
|
2880
|
+
function appInstallLink(label) {
|
|
2881
|
+
return el("a", {
|
|
2882
|
+
class: "ds-owner-install",
|
|
2883
|
+
href: GITHUB_APP_INSTALL_URL,
|
|
2884
|
+
target: "_blank", rel: "noopener",
|
|
2885
|
+
text: label || "GitHub App を入れる →",
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/**
|
|
2890
|
+
* すでに入っている App の**インストール設定**(対象リポジトリの追加)へ行くリンク。
|
|
2891
|
+
*
|
|
2892
|
+
* **インストールとは別の宛先。** 「installations/new」 は「入れる」ための画面で、
|
|
2893
|
+
* すでに入っている組織の人が押しても対象リポジトリは増えない。
|
|
2894
|
+
* リポジトリの追加は、その人が入れた先(個人 or 組織)の設定画面で行う。
|
|
2895
|
+
*
|
|
2896
|
+
* **組織名を URL に埋め込まない。** 組織ごとの設定ページは
|
|
2897
|
+
* 「github.com/organizations/<組織>/settings/installations」 だが、**その組織の
|
|
2898
|
+
* 管理者でないと 404 になる**。読み取り対象の組織のオーナーが Cortex の利用者とは
|
|
2899
|
+
* 限らない(お客様の組織がまさにそれ)ので、404 に飛ばすより、
|
|
2900
|
+
* 本人のインストール一覧から辿らせるほうが確実に着く。
|
|
2901
|
+
* ——「GITHUB_APP_INSTALL_URL」 の「確かめられないものを顧客に押させない」と同じ判断。
|
|
2902
|
+
*
|
|
2903
|
+
* ⚠ **ただし「押した本人が入れた App」しか出ない。** App を入れたのが別のメンバーや
|
|
2904
|
+
* 顧客側のオーナーなら、着いた先は空になる。だから**案内の本文の側が**
|
|
2905
|
+
* 「App を入れた方の画面に出ます/見当たらない場合はその方にご依頼ください」と断っている
|
|
2906
|
+
* (githubNotConnectedNodes)。**リンクだけ残して本文の断りを消さないこと**——
|
|
2907
|
+
* 消すと「開けば必ずそこにある」前提の案内に戻り、着いた先が空の人が詰む。
|
|
2908
|
+
*/
|
|
2909
|
+
var GITHUB_APP_SETTINGS_URL = "https://github.com/settings/installations";
|
|
2910
|
+
|
|
2911
|
+
function appSettingsLink(label) {
|
|
2912
|
+
return el("a", {
|
|
2913
|
+
class: "ds-owner-install",
|
|
2914
|
+
href: GITHUB_APP_SETTINGS_URL,
|
|
2915
|
+
target: "_blank", rel: "noopener",
|
|
2916
|
+
text: label || "インストール設定を開く →",
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2229
2920
|
/**
|
|
2230
2921
|
* 接続状況のライブ値(op=state の応答)。取得前・取得失敗時は null。
|
|
2231
2922
|
*
|
|
@@ -2471,7 +3162,14 @@ ${capabilityState.toString()}
|
|
|
2471
3162
|
// いまはタイルが飛び先。両方に付けると同じ id が2つある不正なHTMLになり、
|
|
2472
3163
|
// モーダルの中身がタイルより前に来る変更が入った瞬間、getElementById が
|
|
2473
3164
|
// 「開いていないモーダルの中」を指して黙って無反応になる
|
|
2474
|
-
|
|
3165
|
+
//
|
|
3166
|
+
// **ds-capbody を付けて縦組みにする(2026-08-21・オーナー指摘)。**
|
|
3167
|
+
// .ds-card は横並びの flex なので、名前ブロック(col)と設定一式(ctl)が
|
|
3168
|
+
// 左右に並び、「共有資料 資料」だけが左の細い列に見えていた。
|
|
3169
|
+
// Slack / GitHub の行と同じ「上にレコード・下にフォーム」に見せるため、
|
|
3170
|
+
// **このカードのときだけ** 縦に積む(.ds-card そのものの CSS は変えない
|
|
3171
|
+
// ——extSourceCard が使っており、変えると Slack と GitHub の行が壊れる)。
|
|
3172
|
+
var card = el("div", { class: "ds-card ds-capbody" + (inactive ? " ds-inactive" : "") }, [
|
|
2475
3173
|
el("div", { class: "ds-card-main" }, [
|
|
2476
3174
|
el("span", { class: "ds-icon", text: DS_KIND_ICON[s.kind] || "📁" }),
|
|
2477
3175
|
el("span", { class: "ds-type", text: s.kind || "" }),
|
|
@@ -2516,13 +3214,35 @@ ${capabilityState.toString()}
|
|
|
2516
3214
|
var groups = [];
|
|
2517
3215
|
var byType = {};
|
|
2518
3216
|
sources.forEach(function (s) {
|
|
2519
|
-
|
|
2520
|
-
|
|
3217
|
+
// **GitHub は種別をまたいで1つにまとめる(2026-08-20)。**
|
|
3218
|
+
// issues と discussions を別グループにすると、**同じ組織の案内が2箱出る**
|
|
3219
|
+
// (組織ごとの畳み直しが箱をまたげない。実測で再現した)。
|
|
3220
|
+
// 組織は type ではなく ref に入っているので、まとめて渡すのが正しい
|
|
3221
|
+
var key = String(s.type).indexOf("github") === 0 ? "__github__" : s.type;
|
|
3222
|
+
var g = byType[key];
|
|
3223
|
+
if (!g) { g = { type: key, items: [] }; byType[key] = g; groups.push(g); }
|
|
2521
3224
|
g.items.push(s);
|
|
2522
3225
|
});
|
|
2523
3226
|
var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
|
|
2524
3227
|
groups.forEach(function (g) {
|
|
2525
3228
|
var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
|
|
3229
|
+
// **Slack は所属する組織ごとに分ける(2026-08-20)。**
|
|
3230
|
+
// 追加のときにワークスペースを選ばせるのをやめたので、
|
|
3231
|
+
// 「どの組織のものか」「その組織とつながっているか」を画面が言う。
|
|
3232
|
+
// 分け方は groupSlackByOrg(純関数)が全部持っている
|
|
3233
|
+
if (g.type === "slack") {
|
|
3234
|
+
slackRows(g.items, meta).forEach(function (n) { pushCap("チャット", n); });
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
// **GitHub も組織ごとに分ける(2026-08-20・Slack からの横展開)。**
|
|
3238
|
+
// それまでは「リポジトリの一覧」と「組織ごとの接続状況」が**別々に並んで**おり、
|
|
3239
|
+
// どの行がどの組織のものか、行を見ても分からなかった。
|
|
3240
|
+
if (g.type === "__github__") {
|
|
3241
|
+
// **meta は行ごとに引く。** issues と discussions が混ざるので、
|
|
3242
|
+
// グループの type から1つに決められない
|
|
3243
|
+
githubRows(g.items).forEach(function (n) { pushCap("開発", n); });
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
2526
3246
|
var cards = el("div", { class: "ds-list" });
|
|
2527
3247
|
g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
|
|
2528
3248
|
if (g.items.length >= 4) {
|
|
@@ -2553,129 +3273,62 @@ ${capabilityState.toString()}
|
|
|
2553
3273
|
// **追加フォームはグループの外に置く。** グループは0件だと描かれず、4件以上だと
|
|
2554
3274
|
// 畳まれる(既定は閉)。中に入れると**新規案件(0件)=一番追加したい場面**で消える。
|
|
2555
3275
|
var adders = renderExternalAdders();
|
|
2556
|
-
//
|
|
2557
|
-
//
|
|
2558
|
-
|
|
3276
|
+
// **接続の入口は「読み取れていない」組にだけ出す(2026-08-20)。**
|
|
3277
|
+
//
|
|
3278
|
+
// それまでは「お客様の Slack ワークスペースを接続」という独立した節を
|
|
3279
|
+
// チャンネル追加より前に置いていた。オーナーの受け入れテストで
|
|
3280
|
+
// 「モーダルを開いても何から始めればいいか分からない」と指摘された——
|
|
3281
|
+
// 接続が先なのか追加が先なのか、そもそも自分に関係あるのかが読み取れない。
|
|
3282
|
+
//
|
|
3283
|
+
// **接続は「準備」ではなく、必要になった行から始まる。**
|
|
3284
|
+
// 当時の警告コメント(「箱そのものは消さない。0件の案件で入口が消える」)は
|
|
3285
|
+
// **新しい形が別の解き方をしている**——チャンネルを追加すれば
|
|
3286
|
+
// 読み取れていない組として現れ、そこに接続ボタンが出る。
|
|
3287
|
+
// 0件なら繋ぐ相手もまだ無いので、入口が要る場面にならない。
|
|
2559
3288
|
pushCap("チャット", adders.querySelector('[data-adder="slack"]'));
|
|
2560
|
-
//
|
|
2561
|
-
//
|
|
2562
|
-
//
|
|
2563
|
-
//
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
var row = el("div", { class: "ds-owner" }, [
|
|
2576
|
-
el("span", { class: "ds-owner-name", text: o.owner }),
|
|
2577
|
-
el("span", { class: "ds-owner-state ds-gate-" + (ok ? "ok" : "warn"),
|
|
2578
|
-
// **読める件数を出す。** 総数だけ出すと、同じ画面の
|
|
2579
|
-
// 「⚠️ 15件が読めていません」と食い違う(#101 で3度直した形)
|
|
2580
|
-
// **確かめられなかった件数を出す。** 未接続側は「18リポジトリ中 15件が
|
|
2581
|
-
// 読めていません」と出しているのに、こちらだけ総数だと同じ節の中で
|
|
2582
|
-
// 表現が揃わない。4リポ中1つだけ揺れた夜に「4リポジトリを確かめられて
|
|
2583
|
-
// いません」と読めるのも実態と違う
|
|
2584
|
-
text: unsure
|
|
2585
|
-
? "⚠️ 確認できませんでした " + o.repos + "リポジトリ中 " +
|
|
2586
|
-
(o.unchecked || o.repos) + "件の接続を確かめられていません"
|
|
2587
|
-
: ok
|
|
2588
|
-
? "● 接続OK " + o.repos + "リポジトリ" + (o.source === "app" ? "(GitHub App)" : "")
|
|
2589
|
-
: "⚠️ 未接続 " + o.repos + "リポジトリ中 " + o.unreadable + "件が読めていません" }),
|
|
2590
|
-
]);
|
|
2591
|
-
// **未接続のときだけ導線を出す。** 繋がっている組織に「インストール」を
|
|
2592
|
-
// 出すと、押す必要が無いものを押させることになる。
|
|
2593
|
-
// 確かめられなかっただけの組織にも出さない——すでに入っているかもしれないので
|
|
2594
|
-
if (!ok && !unsure) {
|
|
2595
|
-
row.appendChild(el("a", {
|
|
2596
|
-
class: "ds-owner-install",
|
|
2597
|
-
// どの組織かはこの行に出ているので、選ばせる形で足りる(URLの形は
|
|
2598
|
-
// GITHUB_APP_INSTALL_URL のコメントを参照)
|
|
2599
|
-
href: GITHUB_APP_INSTALL_URL,
|
|
2600
|
-
target: "_blank", rel: "noopener",
|
|
2601
|
-
text: "この組織にインストール →",
|
|
2602
|
-
}));
|
|
2603
|
-
}
|
|
2604
|
-
obox.appendChild(row);
|
|
2605
|
-
});
|
|
2606
|
-
pushCap("開発", obox);
|
|
2607
|
-
} else {
|
|
2608
|
-
// **組織が1件も無い案件にも、App の入口を置く(#138)。**
|
|
2609
|
-
// githubOwners は夜間バッチの成果物なので、立ち上げ直後の案件には無い。
|
|
2610
|
-
// インストールの導線を「未接続の組織の行」の中にしか置いていなかったため、
|
|
2611
|
-
// **まだ何も繋いでいない案件=一番 App を入れてほしい案件**の画面に、
|
|
2612
|
-
// App へ行く道が1本も無かった(実機で踏んだ)。
|
|
2613
|
-
var obox0 = el("div", { class: "ds-owners" });
|
|
2614
|
-
obox0.appendChild(el("div", { class: "ds-sub", text: "組織ごとの接続状況" }));
|
|
2615
|
-
obox0.appendChild(el("div", { class: "ds-owner" }, [
|
|
2616
|
-
el("span", { class: "ds-owner-state ds-gate-idle", text: "⦿ 未接続 読み取っている組織はまだありません" }),
|
|
2617
|
-
el("a", {
|
|
2618
|
-
class: "ds-owner-install",
|
|
2619
|
-
href: GITHUB_APP_INSTALL_URL,
|
|
2620
|
-
target: "_blank", rel: "noopener",
|
|
2621
|
-
text: "組織に GitHub App をインストール →",
|
|
2622
|
-
}),
|
|
2623
|
-
]));
|
|
2624
|
-
pushCap("開発", obox0);
|
|
2625
|
-
}
|
|
3289
|
+
// **組織ごとの接続状況は、行と一体にした(2026-08-20・Slack からの横展開)。**
|
|
3290
|
+
//
|
|
3291
|
+
// それまでは「リポジトリの一覧」と「組織ごとの接続状況」が**別々に並んで**おり、
|
|
3292
|
+
// 行を見てもどの組織のものか分からず、状況を見てもどの行の話か分からなかった。
|
|
3293
|
+
// いまは:
|
|
3294
|
+
// - どの組織かは**行のラベル**(extSourceCard)
|
|
3295
|
+
// - 繋がっていない組織は**案内が行を抱える**(githubRows)
|
|
3296
|
+
//
|
|
3297
|
+
// **⚠️ App の導線は、追加フォームの手順に常設してある(#138)。**
|
|
3298
|
+
// 組織ごとの案内は**行が無いと出ない**ので、
|
|
3299
|
+
// **行0件の案件=一番 App を入れてほしい案件**にはそちらしか無い。
|
|
3300
|
+
// 「案内側に導線があるから手順側は省く」とやると #138 が再発する。
|
|
3301
|
+
//
|
|
3302
|
+
// 立ち上げ直後(githubOwners が空)でも、追加フォームは必ず出る
|
|
3303
|
+
// (adders はグループの外に置いてある)。
|
|
2626
3304
|
|
|
2627
3305
|
pushCap("開発", adders.querySelector('[data-adder="github"]'));
|
|
2628
3306
|
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
* 取得に失敗した。**情報が無いので出す**(上の #118 の意図はここが担う)。
|
|
2647
|
-
* - 空の配列([])… 夜間バッチは走ったが、読み取り対象の開発リポジトリが0件。
|
|
2648
|
-
* 読むものが無いのだから鍵も要らない。**出さない**。
|
|
2649
|
-
* 以前はどちらも空配列に丸めていたので、0件の案件にも欄が出続けていた。
|
|
2650
|
-
* **この2つを混ぜると #118 を壊す**(立ち上げ直後の案件から唯一の入口が消える)。
|
|
2651
|
-
*/
|
|
2652
|
-
function githubTokenNeeded() {
|
|
2653
|
-
var list = fleetVal("githubOwners");
|
|
2654
|
-
if (!Array.isArray(list)) return true;
|
|
2655
|
-
if (!list.length) return false;
|
|
2656
|
-
return list.some(function (o) { return !o || (o.source !== "app" && o.source !== "token"); });
|
|
3307
|
+
// **0件の案件にも1行だけ残す(#138 のオーナー判断・置き場所だけ移した)。**
|
|
3308
|
+
// 元は GitHub の読み取りトークンの鍵カードを畳んだ跡地に出していたが、
|
|
3309
|
+
// その鍵ごと廃止したので、GitHub の節の側で持つ。
|
|
3310
|
+
//
|
|
3311
|
+
// **消さないこと。** 立ち上げ直後の案件は、この節に行が1本も無い
|
|
3312
|
+
//(=組織ごとの案内も出ない)。何も言わないと、上のフォームが何のためにあるのか、
|
|
3313
|
+
// 登録したあと何が起きるのかが画面から読み取れない。
|
|
3314
|
+
//
|
|
3315
|
+
// **「[]」(夜間バッチは走ったが対象0件)と 「undefined」(まだ走っていない)は別物。**
|
|
3316
|
+
// 後者に「まだありません」と書くと、実際には登録済みかもしれない案件に嘘を言う
|
|
3317
|
+
var ownersNow = fleetVal("githubOwners");
|
|
3318
|
+
if (Array.isArray(ownersNow) && ownersNow.length === 0) {
|
|
3319
|
+
pushCap("開発", el("p", {
|
|
3320
|
+
class: "sec-how ds-all-connected",
|
|
3321
|
+
text: "読み取り対象の開発リポジトリがまだありません。上のフォームで登録すると、"
|
|
3322
|
+
+ "読めているかどうかを翌朝からここに出します。",
|
|
3323
|
+
}));
|
|
2657
3324
|
}
|
|
2658
3325
|
|
|
2659
3326
|
// 連携の鍵は、その能力のところへ置く(画面下まで往復させない)
|
|
2660
|
-
|
|
3327
|
+
// **GitHub(「external-sources」)はここに無い。** 読み取りトークンは廃止し、
|
|
3328
|
+
// 読み取りは GitHub App に一本化した。**この表に戻すと入力欄が復活する**
|
|
3329
|
+
var SECRET_CAP = { figma: "デザイン", backlog: "課題管理" };
|
|
2661
3330
|
(SITE.secretKinds || []).forEach(function (k) {
|
|
2662
3331
|
if (!SECRET_FORMS[k]) return;
|
|
2663
|
-
// 全組織が繋がっているなら、入力欄ではなく**そう言う1行**に畳む(#118)。
|
|
2664
|
-
// 何も出さないと「トークンはどこで入れるのか」が分からなくなる
|
|
2665
|
-
if (k === "external-sources" && !githubTokenNeeded()) {
|
|
2666
|
-
// **0件の案件に「すべての組織に接続済み」と言わない(#138)。** 組織が1つも無いのに
|
|
2667
|
-
// 接続済みと書けば嘘になる。畳む理由が違うので、文も分ける——次の一歩
|
|
2668
|
-
//(リポジトリを登録する)が同じモーダルの上にあることまで書く
|
|
2669
|
-
var ownersNow = fleetVal("githubOwners");
|
|
2670
|
-
var noRepos = Array.isArray(ownersNow) && ownersNow.length === 0;
|
|
2671
|
-
pushCap(SECRET_CAP[k], el("p", {
|
|
2672
|
-
class: "sec-how ds-all-connected",
|
|
2673
|
-
text: noRepos
|
|
2674
|
-
? "読み取り対象の開発リポジトリがまだありません。このモーダルの上にあるリポジトリの追加フォームで登録すると、トークンが要るかどうかをここに出します。"
|
|
2675
|
-
: "すべての組織に接続済みです(GitHub App / 登録済みトークン)。読み取りトークンの登録は要りません。",
|
|
2676
|
-
}));
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
3332
|
// **意図的にOFFにした能力にだけ、入力欄を出さない(0.4.71)。** 使わないと決めたものに
|
|
2680
3333
|
// 鍵を求めるのは、設定する必要が無いものに設定を促す偽の警告と同じ。
|
|
2681
3334
|
// 何も出さないと「トークンはどこで入れるのか」が分からなくなるので、理由を1行だけ残す
|
|
@@ -2693,6 +3346,13 @@ ${capabilityState.toString()}
|
|
|
2693
3346
|
}));
|
|
2694
3347
|
return;
|
|
2695
3348
|
}
|
|
3349
|
+
// **Backlog は接続先のスペースごとにカードを出す(2026-08-20・#127)。**
|
|
3350
|
+
// キーはスペースごとに別なので、欄が1つだと**どちらの鍵を入れたのか
|
|
3351
|
+
// 誰にも分からない**まま片方の棚に入る(入れたのに動かない、が起きる)。
|
|
3352
|
+
if (k === "backlog") {
|
|
3353
|
+
backlogSecretCards().forEach(function (n) { pushCap(SECRET_CAP[k], n); });
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
2696
3356
|
pushCap(SECRET_CAP[k], secretCard(k, SECRET_FORMS[k]));
|
|
2697
3357
|
});
|
|
2698
3358
|
|
|
@@ -2717,7 +3377,9 @@ ${capabilityState.toString()}
|
|
|
2717
3377
|
"開発": { icon: "🐙", desc: "IssuesをGoldの材料に" },
|
|
2718
3378
|
};
|
|
2719
3379
|
var STATE_LABEL = { ok: "● 接続OK", idle: "⦿ 未接続", off: "未使用", warn: "⚠️" };
|
|
2720
|
-
|
|
3380
|
+
// **「開発」はここに無い。** GitHub の読み取りトークンは廃止したので、
|
|
3381
|
+
// 鍵の未設定を語る相手がいない(読めていないときは組織ごとの案内が拾う)
|
|
3382
|
+
var SECRET_OF_CAP = { "デザイン": "figma", "課題管理": "backlog" };
|
|
2721
3383
|
|
|
2722
3384
|
// ---- セットアップの残り(checks)----
|
|
2723
3385
|
//
|
|
@@ -2920,10 +3582,6 @@ ${capabilityState.toString()}
|
|
|
2920
3582
|
// 新規案件——**まさに何を入れれば繋がるのかを知りたい場面**——から、
|
|
2921
3583
|
// 鍵が未設定であることの手がかりまで消える
|
|
2922
3584
|
var sk = SECRET_OF_CAP[cap];
|
|
2923
|
-
// **GitHub は「トークンが無い」=「読めていない」ではない(#118)。** App で読めている
|
|
2924
|
-
// 案件に未設定の警告を出すと、直す必要が無いものを直させることになる。
|
|
2925
|
-
// 読めていない組織があるときは、この下の checks とタイルの状態(warn)が拾う
|
|
2926
|
-
var tokenIsFallback = sk === "external-sources" && !githubTokenNeeded();
|
|
2927
3585
|
// **#125②: 導出があるときは導出で判断する。** OFF(意図的に止めた)にだけ出さない
|
|
2928
3586
|
// ——入れる必要が無いものについて設定を促す偽の警告になる。
|
|
2929
3587
|
//
|
|
@@ -2932,7 +3590,7 @@ ${capabilityState.toString()}
|
|
|
2932
3590
|
// 0.4.70 は unset で入力欄を畳んでいたので注記も抑えていたが、案A で欄を出すようにしたため揃えた
|
|
2933
3591
|
// 導出が無いデータでは判断材料が無いので、注記を出す側(=入力欄と揃う側)に倒す
|
|
2934
3592
|
var tokenRelevant = derived != null ? derived !== "off" : true;
|
|
2935
|
-
if (
|
|
3593
|
+
if (tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !((fleetVal("secretSources") || {})[sk])) {
|
|
2936
3594
|
body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
|
|
2937
3595
|
}
|
|
2938
3596
|
// セットアップの残りのうち、この能力のもの。**鍵の注記と同じ位置**に出して
|
|
@@ -3147,7 +3805,7 @@ ${capabilityState.toString()}
|
|
|
3147
3805
|
// 長い説明は落とし、次の行動の1行だけ残す
|
|
3148
3806
|
return [p(isInternalSlack(s)
|
|
3149
3807
|
? "cortex の担当者にご連絡ください。"
|
|
3150
|
-
: "
|
|
3808
|
+
: "「Slack ワークスペースに接続する」から接続してください。")];
|
|
3151
3809
|
}
|
|
3152
3810
|
if (r.gate === "unreachable") {
|
|
3153
3811
|
return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
|
|
@@ -3216,14 +3874,24 @@ ${capabilityState.toString()}
|
|
|
3216
3874
|
* (押すたびにコミットが増えるのを避ける)。表示名だけが例外で、そちらは
|
|
3217
3875
|
* Slack の実名が正なので自動で揃える(autoAlignName)。
|
|
3218
3876
|
*/
|
|
3877
|
+
/**
|
|
3878
|
+
* 接続の確認。
|
|
3879
|
+
*
|
|
3880
|
+
* **ボタンは行の中(「登録を解除」の隣)に置く(2026-08-20)。**
|
|
3881
|
+
* それまでは行の下に独立した段を作っており、**1行が2段になって一覧が長かった**
|
|
3882
|
+
* (オーナー指摘「レコードをそれぞれもうちょっと短くできるよね」)。
|
|
3883
|
+
* 押した結果の詳細(測った時刻・次の一手)は下に出す——**押すまでは空**なので、
|
|
3884
|
+
* 一覧を眺めているだけの人には行が伸びない。
|
|
3885
|
+
*
|
|
3886
|
+
* @returns { btn, detail } btn は行に、detail は行の下に置く
|
|
3887
|
+
*/
|
|
3219
3888
|
function probeSection(s, card, gateEl) {
|
|
3889
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
|
|
3220
3890
|
var wrap = el("div", { class: "ds-probe" });
|
|
3221
3891
|
var head = el("div", { class: "ds-probe-head" });
|
|
3222
|
-
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
|
|
3223
3892
|
var msg = el("span", { class: "ds-toggle-msg" });
|
|
3224
3893
|
var at = el("span", { class: "ds-probe-at" });
|
|
3225
3894
|
var body = el("div");
|
|
3226
|
-
head.appendChild(btn);
|
|
3227
3895
|
head.appendChild(msg);
|
|
3228
3896
|
head.appendChild(at);
|
|
3229
3897
|
wrap.appendChild(head);
|
|
@@ -3233,6 +3901,10 @@ ${capabilityState.toString()}
|
|
|
3233
3901
|
var intake = SITE.intake;
|
|
3234
3902
|
if (!intake) return;
|
|
3235
3903
|
btn.disabled = true;
|
|
3904
|
+
// **押されて初めて枠を出す(2026-08-20・オーナー指摘)。**
|
|
3905
|
+
// それまでは中身が空でも点線だけが全行に出ており、**何も意味していなかった**。
|
|
3906
|
+
// 押した結果があるときだけ、行と結果を分ける線になる
|
|
3907
|
+
wrap.className = "ds-probe is-shown";
|
|
3236
3908
|
msg.className = "ds-toggle-msg";
|
|
3237
3909
|
msg.textContent = "確認中…";
|
|
3238
3910
|
at.textContent = "";
|
|
@@ -3278,7 +3950,7 @@ ${capabilityState.toString()}
|
|
|
3278
3950
|
}
|
|
3279
3951
|
btn.disabled = false;
|
|
3280
3952
|
});
|
|
3281
|
-
return wrap;
|
|
3953
|
+
return { btn: btn, detail: wrap };
|
|
3282
3954
|
}
|
|
3283
3955
|
|
|
3284
3956
|
function removeChannelButton(s, card) {
|
|
@@ -3394,6 +4066,42 @@ ${capabilityState.toString()}
|
|
|
3394
4066
|
* **読み手が見に行くのと別の棚へ黙って入る**(入れたのに動かない、が一番起きてほしくない形)。
|
|
3395
4067
|
* 名乗らなければサーバが従来どおり名簿の値を使う。
|
|
3396
4068
|
*/
|
|
4069
|
+
/**
|
|
4070
|
+
* Backlog のAPIキーの欄を、接続先のスペースごとに出す(2026-08-20・#127)。
|
|
4071
|
+
*
|
|
4072
|
+
* **キーはスペースごとに別。** 欄が1つだと、複数スペースの案件で
|
|
4073
|
+
* 「どちらの鍵を入れたのか」が誰にも分からないまま片方の棚に入る。
|
|
4074
|
+
*
|
|
4075
|
+
* ## 「現在:」は複数スペースのとき出さない
|
|
4076
|
+
*
|
|
4077
|
+
* 夜間の実測(secretSources.backlog)は**種別ごと**の1つの値で、
|
|
4078
|
+
* スペース別の充足を表せない。1枚目に帰属させると
|
|
4079
|
+
* **片方だけ入れた状態で両方に「登録済み」と出る**——画面が嘘をつく。
|
|
4080
|
+
* 器がスペース別になるまでは、複数のときは全カードで出さない。
|
|
4081
|
+
*
|
|
4082
|
+
* ## 接続先がまだ無いとき
|
|
4083
|
+
*
|
|
4084
|
+
* カードは1枚だけ出す(スペースは名乗らない)。サーバは名簿の値に倒す。
|
|
4085
|
+
*/
|
|
4086
|
+
function backlogSecretCards() {
|
|
4087
|
+
var spaces = backlogSpacesNow();
|
|
4088
|
+
if (spaces.length <= 1) return [secretCard("backlog", SECRET_FORMS["backlog"])];
|
|
4089
|
+
return spaces.map(function (space) {
|
|
4090
|
+
return secretCard("backlog", SECRET_FORMS["backlog"], { space: space, hideCurrent: true });
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
/** 画面が見ている接続先のスペース一覧(重複なし) */
|
|
4095
|
+
function backlogSpacesNow() {
|
|
4096
|
+
var list = backlogTargetsNow(null);
|
|
4097
|
+
var domains = [];
|
|
4098
|
+
for (var i = 0; i < list.length; i++) {
|
|
4099
|
+
var d = String(list[i].domain || "").toLowerCase();
|
|
4100
|
+
if (d && domains.indexOf(d) === -1) domains.push(d);
|
|
4101
|
+
}
|
|
4102
|
+
return domains;
|
|
4103
|
+
}
|
|
4104
|
+
|
|
3397
4105
|
function backlogSecretSpace() {
|
|
3398
4106
|
var list = backlogTargetsNow(null);
|
|
3399
4107
|
var domains = [];
|
|
@@ -3419,17 +4127,25 @@ ${capabilityState.toString()}
|
|
|
3419
4127
|
var targets = backlogTargetsNow(s);
|
|
3420
4128
|
var editable = backlogTargetsEditable();
|
|
3421
4129
|
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
4130
|
+
// **Slack / GitHub / Figma / Drive と同じレコードの行にする(2026-08-21・オーナー指摘)。**
|
|
4131
|
+
// それまでは「現在の接続先」というラベル付きの独自レイアウト(.ds-current /
|
|
4132
|
+
// .ds-target-row)で、同じモーダルの中でもここだけ行間と見た目が違っていた。
|
|
4133
|
+
var listEl = el("div", { class: "ds-list" });
|
|
4134
|
+
// **0件の1行は一覧の外に置く**(Figma / Drive と同じ形)。中に .ds-card として
|
|
4135
|
+
// 置くと「行が1件ある」と読まれ、一覧を数えるテストにも行として数えられる
|
|
4136
|
+
var emptyEl = el("p", { class: "ds-empty", text: "同期する接続先はまだ登録されていません(未設定)。" });
|
|
3427
4137
|
// 追加・削除の結果はここに1つだけ出す(行を描き直しても消えない位置)
|
|
3428
4138
|
var msg = el("div", { class: "ds-toggle-msg" });
|
|
3429
4139
|
|
|
3430
4140
|
function removeRow(t) {
|
|
3431
|
-
|
|
3432
|
-
|
|
4141
|
+
// **ds-type はツール名**(能力名を入れると上の見出しと重複する)。
|
|
4142
|
+
// **名前はリンクにする**——Drive / Figma の行と同じく、正本を新しいタブで開ける
|
|
4143
|
+
var row = el("div", { class: "ds-card" }, [
|
|
4144
|
+
el("div", { class: "ds-card-main" }, [
|
|
4145
|
+
el("span", { class: "ds-icon", text: "📋" }),
|
|
4146
|
+
el("span", { class: "ds-type", text: "Backlog" }),
|
|
4147
|
+
dsNameEl(t.domain + " / " + t.projectKey, "https://" + t.domain + "/projects/" + t.projectKey),
|
|
4148
|
+
]),
|
|
3433
4149
|
]);
|
|
3434
4150
|
if (!editable) return row;
|
|
3435
4151
|
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
|
|
@@ -3456,12 +4172,13 @@ ${capabilityState.toString()}
|
|
|
3456
4172
|
|
|
3457
4173
|
function paint() {
|
|
3458
4174
|
while (listEl.firstChild) listEl.removeChild(listEl.firstChild);
|
|
3459
|
-
if (!targets.length) {
|
|
3460
|
-
// **行ごと消さない。** 表示が無いと「まだ何も無い」のか「出し忘れ」なのか区別できない
|
|
3461
|
-
listEl.appendChild(el("span", { class: "ds-current-value is-unset", text: "未設定" }));
|
|
3462
|
-
return;
|
|
3463
|
-
}
|
|
3464
4175
|
for (var i = 0; i < targets.length; i++) listEl.appendChild(removeRow(targets[i]));
|
|
4176
|
+
// **0件を黙って見せない。** 表示が無いと「まだ何も無い」のか「出し忘れ」なのか
|
|
4177
|
+
// 区別できない。**一覧の外**(Figma / Drive と同じ)に、件数に応じて出し入れする
|
|
4178
|
+
// ——出しっぱなしにすると、設定済みの案件にも「未設定」と書いた行が残る
|
|
4179
|
+
var here = emptyEl.parentNode === box;
|
|
4180
|
+
if (!targets.length && !here) box.insertBefore(emptyEl, listEl.nextSibling);
|
|
4181
|
+
else if (targets.length && here) box.removeChild(emptyEl);
|
|
3465
4182
|
}
|
|
3466
4183
|
|
|
3467
4184
|
/**
|
|
@@ -3484,8 +4201,10 @@ ${capabilityState.toString()}
|
|
|
3484
4201
|
return { onTargets: adoptTargets, staleNote: TARGETS_RACE_NOTE };
|
|
3485
4202
|
}
|
|
3486
4203
|
|
|
4204
|
+
// **box を先に作る。** paint() は 0件の1行を box へ出し入れするので、
|
|
4205
|
+
// 箱が無いと最初の描画で「未設定」の行が出ない
|
|
4206
|
+
var box = el("div", {}, [listEl]);
|
|
3487
4207
|
paint();
|
|
3488
|
-
var box = el("div", {}, [current]);
|
|
3489
4208
|
if (editable) {
|
|
3490
4209
|
box.appendChild(addForm({
|
|
3491
4210
|
// **「追加」に戻す(#127)。** 1件しか持てなかった頃は「保存」(置き換え)だったが、
|
|
@@ -3493,7 +4212,27 @@ ${capabilityState.toString()}
|
|
|
3493
4212
|
title: "+ 接続先を追加",
|
|
3494
4213
|
placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
|
|
3495
4214
|
requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
|
|
3496
|
-
|
|
4215
|
+
// **手順のリストにする(2026-08-20・Slack からの横展開)。**
|
|
4216
|
+
// **リスト(ol.sec-steps)そのものは維持する**——他の能力の手順と形を揃える。
|
|
4217
|
+
//
|
|
4218
|
+
// **鍵の発行手順は二重に持たない。** 正本は下の鍵カード
|
|
4219
|
+
//(SECRET_FORMS.backlog の how)で、ここからは参照するだけ。
|
|
4220
|
+
//
|
|
4221
|
+
// ## 4行 → 2行に圧縮した(2026-08-21・オーナー指摘「何も変わってない気がする」)
|
|
4222
|
+
//
|
|
4223
|
+
// **この欄は URL を貼るだけ**なので、4行の説明が読む負担に見合わない。
|
|
4224
|
+
// ただし**落としてよいのは1行目だけ**だった(欄を見れば分かる内容)。残り3つは
|
|
4225
|
+
// ここ以外に置き場が無いので、**消さずに2行へ畳んでいる**:
|
|
4226
|
+
// - 「起票先は変わりません」… 追加の確認をやめたとき(2026-08-20)に
|
|
4227
|
+
// ここへ移してきたもの。落とすと知る手段が無くなる
|
|
4228
|
+
// - 「スペースごとに別のキー」… 鍵カードからは読めない。1スペースの案件は
|
|
4229
|
+
// カードのタイトルにスペース名が出ないので、これから2つ目を足す人に届かない
|
|
4230
|
+
// - 「案件専用URLの登録」… 救出先の realtimeSyncCard() は稼働中の案件では
|
|
4231
|
+
// 手順一式を <details> に畳むので、**いちばん要る人に対して閉じている**
|
|
4232
|
+
hint: [
|
|
4233
|
+
"URL を貼ると同期先が増えます。ビューアからの起票先は変わりません(スペースごとに別のAPIキーが要ります)。",
|
|
4234
|
+
"リアルタイム同期を使うなら、追加したプロジェクトにも案件専用URLの登録が要ります。",
|
|
4235
|
+
],
|
|
3497
4236
|
build: function (url) {
|
|
3498
4237
|
return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
|
|
3499
4238
|
},
|
|
@@ -3627,15 +4366,11 @@ ${capabilityState.toString()}
|
|
|
3627
4366
|
box.appendChild(el("p", { class: "sec-how", text: "共有資料の取り込み設定を読み取れないため、この画面からは変更できません。cortex の担当者にご連絡ください。" }));
|
|
3628
4367
|
used = true;
|
|
3629
4368
|
} else if (s.driveState) {
|
|
3630
|
-
//
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
requiredNote: "フォルダのURLを入力してください。",
|
|
3636
|
-
hint: "このフォルダの中身が 共有資料 にコピーされ、Markdown 化されます。取り込み用アカウントをそのフォルダの閲覧者に招待しておいてください(招待が無いと読めません)。",
|
|
3637
|
-
build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
|
|
3638
|
-
}));
|
|
4369
|
+
// **レコードが先・追加フォームが後(2026-08-21)。** Slack / GitHub と同じ並びに揃える。
|
|
4370
|
+
// それまではフォームが先だった。理由は「フォルダが1件も無いのに ON/OFF だけ出しても
|
|
4371
|
+
// 意味が無いから」だったが、**その ON/OFF トグルは既に廃止されている**(下の注記)。
|
|
4372
|
+
// 理由が消えたのに順序だけ残っていた。デザインと課題管理は元から「レコード → フォーム」。
|
|
4373
|
+
//
|
|
3639
4374
|
// **ON/OFFのトグルは出さない。** フォルダを追加した時点で取り込みは始まる
|
|
3640
4375
|
// (applyAddDriveFolder が enabled を立てる)ので「始める」を押す機会が無い。
|
|
3641
4376
|
// 止めたいならフォルダを外す——登録そのものが意思表示、という会議と同じ考え方。
|
|
@@ -3643,11 +4378,19 @@ ${capabilityState.toString()}
|
|
|
3643
4378
|
// 名前は任意。**無ければIDを出す**(既存の案件は名前を持っていない)
|
|
3644
4379
|
var fnames = s.driveFolderNames || {};
|
|
3645
4380
|
if (folders.length) {
|
|
3646
|
-
var dlist = el("div", { class: "ds-
|
|
4381
|
+
var dlist = el("div", { class: "ds-list" });
|
|
3647
4382
|
folders.forEach(function (id) {
|
|
3648
4383
|
var fname = fnames[id];
|
|
3649
|
-
|
|
3650
|
-
|
|
4384
|
+
// **Slack / GitHub の行と同じ構造にする(ds-card > ds-card-main >
|
|
4385
|
+
// ds-icon + ds-type + ds-name)。** extSourceCard は呼ばない——
|
|
4386
|
+
// あちらは type / gate / origin など外部ソース固有の判断を抱えている。
|
|
4387
|
+
// ds-type に入れるのは**ツール名**(能力名を入れると上の見出しと重複する)。
|
|
4388
|
+
var row = el("div", { class: "ds-card" }, [
|
|
4389
|
+
el("div", { class: "ds-card-main" }, [
|
|
4390
|
+
el("span", { class: "ds-icon", text: "📁" }),
|
|
4391
|
+
el("span", { class: "ds-type", text: "Google Drive" }),
|
|
4392
|
+
dsNameEl(fname || id, "https://drive.google.com/drive/folders/" + id),
|
|
4393
|
+
]),
|
|
3651
4394
|
]);
|
|
3652
4395
|
// **名前を出したときはIDも併記する。** 同名のフォルダを見分けられなくなるのと、
|
|
3653
4396
|
// 「このIDを外す」という確認文と画面の表示が食い違うのを防ぐ
|
|
@@ -3668,12 +4411,41 @@ ${capabilityState.toString()}
|
|
|
3668
4411
|
return lines;
|
|
3669
4412
|
},
|
|
3670
4413
|
build: function () { return { op: "remove-drive-folder", id: id }; },
|
|
3671
|
-
|
|
4414
|
+
// **クラスのハード代入をやめる**(片方だけ直し忘れる形を構造的に消す)
|
|
4415
|
+
onDone: function () { row.classList.add("ds-removed"); },
|
|
3672
4416
|
}));
|
|
3673
4417
|
dlist.appendChild(row);
|
|
3674
4418
|
});
|
|
3675
4419
|
box.appendChild(dlist);
|
|
4420
|
+
} else if (s.driveState === "empty") {
|
|
4421
|
+
// **0件を黙って見せない。** 追加フォームだけだと「まだ登録していない」のか
|
|
4422
|
+
// 「読めていない」のか区別できない。
|
|
4423
|
+
//
|
|
4424
|
+
// **判定は driveState === "empty" にする。** engine(fleet-status.mjs)は
|
|
4425
|
+
// driveFolderIds を「1件以上あるときだけ」出すので、キーの有無では
|
|
4426
|
+
// 未取得(unset / broken)と0件を見分けられない。unset / broken は
|
|
4427
|
+
// 上の分岐で先に返しているので、ここへ来る empty は「読めていて0件」だけ。
|
|
4428
|
+
box.appendChild(el("p", { class: "ds-empty", text: "取り込み対象のフォルダはまだ登録されていません。" }));
|
|
3676
4429
|
}
|
|
4430
|
+
box.appendChild(addForm({
|
|
4431
|
+
title: "+ Google Drive のフォルダを追加",
|
|
4432
|
+
placeholder: "https://drive.google.com/drive/folders/…",
|
|
4433
|
+
namePlaceholder: "メモ(任意)",
|
|
4434
|
+
requiredNote: "フォルダのURLを入力してください。",
|
|
4435
|
+
// **手順のリストにする(2026-08-20・Slack からの横展開)。**
|
|
4436
|
+
//
|
|
4437
|
+
// **取り込み用アカウントのアドレスを出す(オーナー判断・分類A)。**
|
|
4438
|
+
// それまでは「取り込み用アカウントを招待してください」としか書いておらず、
|
|
4439
|
+
// **誰を招待すればいいのかが画面から分からなかった**——担当者に聞かないと
|
|
4440
|
+
// 完了できない手順になっていた。
|
|
4441
|
+
// 招待されない限り何も読めないアカウントなので、出す判断をした。
|
|
4442
|
+
hint: [
|
|
4443
|
+
{ text: "読み取りたい Google Drive のフォルダを開き、下のアカウントを閲覧者として招待します(招待が無いと読めません)。", node: copyRow(DRIVE_INGEST_ACCOUNT) },
|
|
4444
|
+
"そのフォルダのURLを下の欄に貼って「追加」を押します。",
|
|
4445
|
+
"追加したフォルダの中身が 共有資料 にコピーされ、Markdown 化されます。止めるときは、その行の「このフォルダを外す」を押します。",
|
|
4446
|
+
],
|
|
4447
|
+
build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
|
|
4448
|
+
}));
|
|
3677
4449
|
used = true;
|
|
3678
4450
|
}
|
|
3679
4451
|
}
|
|
@@ -3687,10 +4459,18 @@ ${capabilityState.toString()}
|
|
|
3687
4459
|
// 描画時点の件数で判定すると、3件を1件ずつ外していったとき最後の1件でも警告が出ない。
|
|
3688
4460
|
var figsLeft = figs.length;
|
|
3689
4461
|
if (figs.length) {
|
|
3690
|
-
var flist = el("div", { class: "ds-
|
|
4462
|
+
var flist = el("div", { class: "ds-list" });
|
|
3691
4463
|
figs.forEach(function (f) {
|
|
3692
|
-
|
|
3693
|
-
|
|
4464
|
+
// **Slack / GitHub の行と同じ構造にする(ds-card > ds-card-main >
|
|
4465
|
+
// ds-icon + ds-type + ds-name)。** extSourceCard は呼ばない——
|
|
4466
|
+
// あちらは type / gate / origin など外部ソース固有の判断を抱えている。
|
|
4467
|
+
// ds-type に入れるのは**ツール名**(能力名を入れると上の見出しと重複する)。
|
|
4468
|
+
var row = el("div", { class: "ds-card" }, [
|
|
4469
|
+
el("div", { class: "ds-card-main" }, [
|
|
4470
|
+
el("span", { class: "ds-icon", text: "🎨" }),
|
|
4471
|
+
el("span", { class: "ds-type", text: "Figma" }),
|
|
4472
|
+
dsNameEl(f.name || f.key, "https://www.figma.com/design/" + f.key),
|
|
4473
|
+
]),
|
|
3694
4474
|
]);
|
|
3695
4475
|
row.appendChild(removeButton({
|
|
3696
4476
|
label: "このファイルを外す",
|
|
@@ -3714,7 +4494,8 @@ ${capabilityState.toString()}
|
|
|
3714
4494
|
},
|
|
3715
4495
|
build: function () { return { op: "remove-figma-file", key: f.key }; },
|
|
3716
4496
|
onDone: function () {
|
|
3717
|
-
|
|
4497
|
+
// **クラスのハード代入をやめる**(片方だけ直し忘れる形を構造的に消す)
|
|
4498
|
+
row.classList.add("ds-removed");
|
|
3718
4499
|
figsLeft -= 1;
|
|
3719
4500
|
},
|
|
3720
4501
|
}));
|
|
@@ -3731,7 +4512,18 @@ ${capabilityState.toString()}
|
|
|
3731
4512
|
placeholder: "https://www.figma.com/design/…",
|
|
3732
4513
|
namePlaceholder: "メモ(任意)",
|
|
3733
4514
|
requiredNote: "FigmaのURLを入力してください。",
|
|
3734
|
-
|
|
4515
|
+
// **手順のリストにする(2026-08-20・Slack からの横展開)。**
|
|
4516
|
+
// 一続きの文章だと、読む人が「自分がいま何をすべきか」を自分で切り出すことになる。
|
|
4517
|
+
//
|
|
4518
|
+
// **手順1・2は要約にとどめ、正本は下の鍵カードに置く**(SECRET_FORMS.figma の
|
|
4519
|
+
// how / note)。全文をここに移すと二重になり、片方だけ直る形になる
|
|
4520
|
+
// ——カードは接続マップとタイルの注記と条件を揃えているので、動かすと壊れやすい。
|
|
4521
|
+
hint: [
|
|
4522
|
+
"Figma でトークンを発行します(読み取りだけ。スコープ2つ)。手順は下の「Figma のトークン」の欄にあります。",
|
|
4523
|
+
"発行したアカウントを、読み取りたい Figma ファイルに閲覧者として招待します(招待が無いと、トークンが正しくても読めません)。",
|
|
4524
|
+
"そのファイルのURLを下の欄に貼って「追加」を押します。",
|
|
4525
|
+
"追加したファイルは、画面名と Figma へのリンクが毎晩同期されます(画像は取り込まれません)。",
|
|
4526
|
+
],
|
|
3735
4527
|
build: function (url, name) { return { op: "add-figma-file", url: url, name: name }; },
|
|
3736
4528
|
}));
|
|
3737
4529
|
used = true;
|
|
@@ -3740,156 +4532,6 @@ ${capabilityState.toString()}
|
|
|
3740
4532
|
return used ? box : null;
|
|
3741
4533
|
}
|
|
3742
4534
|
|
|
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
4535
|
|
|
3894
4536
|
/**
|
|
3895
4537
|
* 外部ソースの追加フォーム(Slack・GitHub)。
|
|
@@ -3925,8 +4567,21 @@ ${capabilityState.toString()}
|
|
|
3925
4567
|
// **招待の話を先頭に置く。** Slack の仕様で、ボットは**公開チャンネルでも**招待されるまで
|
|
3926
4568
|
// 中身を1件も読めない(conversations.history が not_in_channel を返す・2026-08-16 実測+公式ドキュメント)。
|
|
3927
4569
|
// そのあとに「追加=Gold 昇格の対象」を置く(手順と、その手順の重さ)。
|
|
3928
|
-
|
|
3929
|
-
|
|
4570
|
+
// **手順のリストにする(2026-08-20・オーナー指摘)。**
|
|
4571
|
+
// 一続きの文章だと、読む人が「自分がいま何をすべきか」を自分で切り出すことになる。
|
|
4572
|
+
//
|
|
4573
|
+
// **接続(お客様の Slack への導入)の説明もここに置く。** 独立した節を
|
|
4574
|
+
// 廃止したので、**繋ぎ方を知る場所はここしか無い**——
|
|
4575
|
+
// 「0件の案件で入口が消える」という過去の事故(renderExternalAdders の
|
|
4576
|
+
// 直上コメント)を、この手順が受け止めている。
|
|
4577
|
+
hint: [
|
|
4578
|
+
// **コマンドは手順1の中に置く。** フォームの下に離すと、どの手順のものか
|
|
4579
|
+
// 対応が読めない(手で打たせない理由は copyRow のコメント)
|
|
4580
|
+
{ text: "読み取りたいチャンネルで、下のコマンドを貼って Cortex を招待します。公開チャンネルでも招待が必要です。", node: copyRow("/invite @Cortex") },
|
|
4581
|
+
"下の欄に、そのチャンネルのリンクを貼って「追加」を押します(チャンネル名を右クリック →「リンクをコピー」)。",
|
|
4582
|
+
"「組織不明」と出たら、そこにある「Slack ワークスペースに接続する」から承認をもらいます。お客様の Slack のチャンネルは、この承認が済むまで読めません(承認できるのはその Slack の管理者です)。",
|
|
4583
|
+
"追加したチャンネルは毎晩の Gold 昇格の対象になり、Decision・用語集の材料になります。止めるときは、その行の「登録を解除」を押します。",
|
|
4584
|
+
],
|
|
3930
4585
|
// **選んだワークスペースを送る(#104)。** 空文字=社内ワークスペースなのでキーごと落とす
|
|
3931
4586
|
// (サーバ側は「未宣言・null・空」を区別しない約束で、無いことで既定を表す)
|
|
3932
4587
|
build: function (url, name, workspace) {
|
|
@@ -3934,7 +4589,6 @@ ${capabilityState.toString()}
|
|
|
3934
4589
|
if (workspace) payload.workspace = workspace;
|
|
3935
4590
|
return payload;
|
|
3936
4591
|
},
|
|
3937
|
-
workspacePicker: true,
|
|
3938
4592
|
// **別案件でも使われていたときに出す確認(2026-08-20)。**
|
|
3939
4593
|
//
|
|
3940
4594
|
// **追加のときに出る唯一のモーダル。** 同日に「追加のたびの確認」を廃止した
|
|
@@ -3967,7 +4621,21 @@ ${capabilityState.toString()}
|
|
|
3967
4621
|
placeholder: "owner/repo または https://github.com/owner/repo",
|
|
3968
4622
|
namePlaceholder: "メモ(任意)",
|
|
3969
4623
|
requiredNote: "リポジトリを入力してください。",
|
|
3970
|
-
|
|
4624
|
+
// **手順のリストにする(2026-08-20・Slack からの横展開)。**
|
|
4625
|
+
//
|
|
4626
|
+
// **App の導線をここに常設する(#138 の再発防止・唯一の防波堤)。**
|
|
4627
|
+
// それまでは「組織ごとの接続状況」という独立した一覧の中にしか
|
|
4628
|
+
// 導線が無く、**まだ何も繋いでいない案件=一番 App を入れてほしい案件**の
|
|
4629
|
+
// 画面に App へ行く道が1本も無かった(実機で踏んだ)。
|
|
4630
|
+
//
|
|
4631
|
+
// **行が0件でも、この手順は必ず出る**(追加フォームはグループの外にある)。
|
|
4632
|
+
// 組織ごとの案内は行が無いと出ないので、**常設の正本はこちら**。
|
|
4633
|
+
// 「案内側に導線があるから手順側は省く」とやると即再発する。
|
|
4634
|
+
hint: [
|
|
4635
|
+
{ text: "読み取りたいリポジトリがある組織に、Cortex の GitHub App を入れます(Issues の読み取りだけ)。", node: appInstallLink() },
|
|
4636
|
+
"そのリポジトリを下の欄に入れて「追加」を押します(owner/repo でも、GitHub のURLでも構いません)。",
|
|
4637
|
+
"追加したリポジトリの Issues が、毎晩の Gold 昇格の材料になります。止めるときは、その行の「読み取りを止める」を押します。",
|
|
4638
|
+
],
|
|
3971
4639
|
build: function (repo, name) { return { op: "add-github-source", repo: repo, name: name }; },
|
|
3972
4640
|
adder: "github",
|
|
3973
4641
|
}));
|
|
@@ -4279,6 +4947,31 @@ ${capabilityState.toString()}
|
|
|
4279
4947
|
* **これがある口では、その場の控え(「追加済み・反映中」)を出さない**——
|
|
4280
4948
|
* 応答で本物の一覧が増えるので、控えを併記すると同じものが2回出る
|
|
4281
4949
|
*/
|
|
4950
|
+
/**
|
|
4951
|
+
* フォームの説明。**配列なら手順のリストにする(2026-08-20)。**
|
|
4952
|
+
*
|
|
4953
|
+
* オーナー指摘: 「もっと手続き的に、結局ユーザー自身が何をすればいいのかを
|
|
4954
|
+
* わかるようにした方がいい。リスト形式でちゃんと並べてあげるとわかりやすい」。
|
|
4955
|
+
* 一続きの文章は、読む人が「自分がいま何をすべきか」を自分で切り出すことになる。
|
|
4956
|
+
*
|
|
4957
|
+
* **文字列の口は今までどおり。** 他の4フォーム(共有資料・Figma・Backlog・GitHub)は
|
|
4958
|
+
* まだ文章なので、そちらを壊さない。横展開でそれぞれ配列に変えていく。
|
|
4959
|
+
*
|
|
4960
|
+
* 各要素は文字列か、{ text, node } (手順の中に部品を挟むとき。招待コマンドなど)。
|
|
4961
|
+
*/
|
|
4962
|
+
function hintNode(hint) {
|
|
4963
|
+
if (!hint) return null;
|
|
4964
|
+
if (!Array.isArray(hint)) return el("p", { class: "sec-how", text: hint });
|
|
4965
|
+
var ol = el("ol", { class: "sec-how sec-steps" });
|
|
4966
|
+
hint.forEach(function (step) {
|
|
4967
|
+
var text = typeof step === "string" ? step : (step && step.text) || "";
|
|
4968
|
+
var li = el("li", {}, [el("span", { text: text })]);
|
|
4969
|
+
if (step && step.node) li.appendChild(step.node);
|
|
4970
|
+
ol.appendChild(li);
|
|
4971
|
+
});
|
|
4972
|
+
return ol;
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4282
4975
|
function addForm(opts) {
|
|
4283
4976
|
var main = el("input", { class: "sec-input", type: "text", placeholder: opts.placeholder, autocomplete: "off" });
|
|
4284
4977
|
var name = opts.needsName
|
|
@@ -4293,15 +4986,13 @@ ${capabilityState.toString()}
|
|
|
4293
4986
|
// ワークスペースの改名で変わるし、共有チャンネルでは貼った人のホストになる。
|
|
4294
4987
|
// 推測して間違えると「追加できたのに読まれない」チャンネルができ、
|
|
4295
4988
|
// 翌朝の接続状況で初めて分かる。人に選ばせる。
|
|
4296
|
-
|
|
4989
|
+
// **ワークスペースの選択は廃止した(2026-08-20)。**
|
|
4990
|
+
// オーナーの受け入れテストで「意味が分からない」。実際、チャンネルIDを見ても
|
|
4991
|
+
// どのワークスペースのものかは分からないし、**間違えると追加はできるのに
|
|
4992
|
+
// 永久に読まれない**(気づくのは翌朝の接続状況)。
|
|
4993
|
+
// いまはサーバが、持っている鍵を順に Slack へ投げて決める。
|
|
4994
|
+
// **URLのホスト名から推測しない**という下の判断は今も正しい——推測ではなく実測にした。
|
|
4297
4995
|
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
4996
|
var submitLabel = opts.submitLabel || "追加";
|
|
4306
4997
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: submitLabel });
|
|
4307
4998
|
var msg = el("div", { class: "ds-toggle-msg" });
|
|
@@ -4366,7 +5057,7 @@ ${capabilityState.toString()}
|
|
|
4366
5057
|
|
|
4367
5058
|
var box = el("div", opts.adder ? { class: "ds-addform", "data-adder": opts.adder } : { class: "ds-addform" }, [
|
|
4368
5059
|
el("div", { class: "ds-addform-title", text: opts.title }),
|
|
4369
|
-
|
|
5060
|
+
hintNode(opts.hint),
|
|
4370
5061
|
// 手順の中に「どこかへ貼る文字列」があるときは、**手で打たせない**。
|
|
4371
5062
|
// 「/invite @Cortex」のような短い文字列でも、打ち間違えると黙って失敗する
|
|
4372
5063
|
opts.copyText ? copyRow(opts.copyText) : null,
|
|
@@ -4438,15 +5129,35 @@ ${capabilityState.toString()}
|
|
|
4438
5129
|
// 行ごとに「対象です」と書いても情報が増えない。github の除外は行のトグル
|
|
4439
5130
|
// (「読み取りを始める/止める」)が向きで示す。
|
|
4440
5131
|
if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
|
|
4441
|
-
//
|
|
4442
|
-
//
|
|
4443
|
-
//
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
5132
|
+
// **どのワークスペースのチャンネルかを、必ず行に出す(#104・2026-08-20 に社内も)。**
|
|
5133
|
+
//
|
|
5134
|
+
// 折りたたみの見出しでまとめるのをやめた(オーナー指摘「複雑になっちゃってる」)ので、
|
|
5135
|
+
// **どの組織かを言うのは行のラベルだけ**になった。社内に何も出さないと、
|
|
5136
|
+
// ラベルの無い行が「社内」なのか「まだ分かっていない」のかを読み分けられない。
|
|
5137
|
+
if (s.type === "slack") {
|
|
5138
|
+
// **順序は groupSlackByOrg と揃える(workspace が先)。**
|
|
5139
|
+
// 逆にすると、判定が済んで workspace が入った行に古い印が残っているとき、
|
|
5140
|
+
// **並びは「Hapio AI」なのにラベルだけ「組織が分かりません」**になる
|
|
5141
|
+
// ——同じ画面で2つの判定が食い違う、このプロダクトが何度も直した形
|
|
5142
|
+
if (s.workspace) {
|
|
5143
|
+
badges.appendChild(el("span", {
|
|
5144
|
+
class: "badge ds-workspace",
|
|
5145
|
+
text: workspaceLabel(s.workspace),
|
|
5146
|
+
title: "接続した Slack ワークスペースのチャンネルです(読み取り専用の接続なので、通知は届きません)。",
|
|
5147
|
+
}));
|
|
5148
|
+
} else if (s.orgUnknown === true) {
|
|
5149
|
+
badges.appendChild(el("span", {
|
|
5150
|
+
class: "badge ds-workspace ds-workspace-unknown",
|
|
5151
|
+
text: "組織不明",
|
|
5152
|
+
title: "どの Slack のチャンネルか、まだ分かっていません。上の案内をご覧ください。",
|
|
5153
|
+
}));
|
|
5154
|
+
} else {
|
|
5155
|
+
badges.appendChild(el("span", {
|
|
5156
|
+
class: "badge ds-workspace",
|
|
5157
|
+
text: "クラスメソッド",
|
|
5158
|
+
title: "クラスメソッドの社内 Slack のチャンネルです。",
|
|
5159
|
+
}));
|
|
5160
|
+
}
|
|
4450
5161
|
}
|
|
4451
5162
|
// 登録の由来(#118)。**「登録から削除」が出る/出ないの理由がこれ。** 導出されたリポは
|
|
4452
5163
|
// 登録ファイルから消しても翌日また現れるので削除を出していないが、画面には
|
|
@@ -4456,6 +5167,30 @@ ${capabilityState.toString()}
|
|
|
4456
5167
|
// ようになった(explicit には出さない)が、Slack で explicit なのは登録ファイルに
|
|
4457
5168
|
// 直接書いた例外的な行だけで、バッジのために文面をもう一組増やすには釣り合わない。
|
|
4458
5169
|
// #119 以降、新しいデータの github は必ず explicit(詳細は ORIGIN_LABEL)
|
|
5170
|
+
// **どの組織のリポジトリかを行に出す(2026-08-20)。**
|
|
5171
|
+
// 組織ごとの独立した一覧をやめたので、**組織を言うのは行のラベルだけ**になった。
|
|
5172
|
+
// トークンは組織ごとにしか効かないので、これが判断の単位になる
|
|
5173
|
+
if (String(s.type).indexOf("github") === 0 && s.ref) {
|
|
5174
|
+
var ghOwner = String(s.ref).split("/")[0];
|
|
5175
|
+
if (ghOwner) {
|
|
5176
|
+
// **何で読んでいるかを添える。** 台帳に無ければ何も言わない。
|
|
5177
|
+
//
|
|
5178
|
+
// **添えるのは App のときだけ(2026-08-21・読み取りトークンの廃止で判断し直した)。**
|
|
5179
|
+
// かつては「App/個人トークン」で直し方が違ったが、個人トークンは廃止した。
|
|
5180
|
+
// いま App が付かない行は 「workflow」(ワークフロー自身のトークンで読めた=
|
|
5181
|
+
// だいたい公開リポジトリ)で、**読めている以上その人に直す作業は無い**。
|
|
5182
|
+
// ここに「(ワークフローのトークン)」のような札を足すと、直しようのない
|
|
5183
|
+
// 内部事情を全行に並べることになる——押す必要が無いものを説明する側の混雑。
|
|
5184
|
+
// 一方 App の札は「この組織はもう繋いである」という**運用上の意味**を持つので残す。
|
|
5185
|
+
var ghMeta = githubOwnerMeta(ghOwner);
|
|
5186
|
+
var via = ghMeta && ghMeta.source === "app" ? "(GitHub App)" : "";
|
|
5187
|
+
badges.appendChild(el("span", {
|
|
5188
|
+
class: "badge ds-workspace" + (ghMeta && ghMeta.source === "unknown" ? " ds-workspace-unknown" : ""),
|
|
5189
|
+
text: ghOwner + via,
|
|
5190
|
+
title: "トークンは組織ごとにしか効きません(Fine-grained PAT も GitHub App のインストールトークンも1組織分)。",
|
|
5191
|
+
}));
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
4459
5194
|
if (String(s.type).indexOf("github") === 0 && ORIGIN_LABEL[s.origin]) {
|
|
4460
5195
|
badges.appendChild(el("span", {
|
|
4461
5196
|
class: "badge ds-origin",
|
|
@@ -4552,7 +5287,11 @@ ${capabilityState.toString()}
|
|
|
4552
5287
|
// **origin === "explicit" でも出す。** 確認は読むだけで、どの登録ファイル由来かに
|
|
4553
5288
|
// 関係なく効く(書き込みになる「表示名を更新する」だけが由来で出し分かれる)
|
|
4554
5289
|
if (SITE.intake && s.type === "slack" && s.ref) {
|
|
4555
|
-
|
|
5290
|
+
var probe = probeSection(s, card, gateEl);
|
|
5291
|
+
// **ボタンは行の中(「登録を解除」の隣)。** 独立した段を作ると1行が2段になる
|
|
5292
|
+
card.appendChild(probe.btn);
|
|
5293
|
+
// 押した結果の詳細だけを下に。**押すまでは空**なので行は伸びない
|
|
5294
|
+
card.appendChild(probe.detail);
|
|
4556
5295
|
}
|
|
4557
5296
|
return card;
|
|
4558
5297
|
}
|
|
@@ -4841,12 +5580,25 @@ ${capabilityState.toString()}
|
|
|
4841
5580
|
].concat(steps));
|
|
4842
5581
|
}
|
|
4843
5582
|
|
|
4844
|
-
/**
|
|
5583
|
+
/**
|
|
5584
|
+
* 鍵の入力欄に出す説明。kind は Lambda 側(resolveSecretTarget)と綴りを合わせること。
|
|
5585
|
+
*
|
|
5586
|
+
* **発行の手順(how / note / selfServe.note)は <details> に畳む**
|
|
5587
|
+
*(2026-08-21・オーナー指摘「カードが長すぎる」)。**文言は消していない**——
|
|
5588
|
+
* 畳んだ中に全部残っており、summary を開けば読める。
|
|
5589
|
+
* 畳まないのは .sec-state / .sec-preferred / .sec-input(secretCard 側の 🔴 コメント参照)。
|
|
5590
|
+
*
|
|
5591
|
+
* **「畳まない種別」の枝は置いていない。** 当初は fold: true の opt-in にしていたが、
|
|
5592
|
+
* 2026-08-21 に external-sources(GitHub の読み取りトークン)が廃止され、
|
|
5593
|
+
* **残る2種別が両方とも畳む=その枝が到達不能になった**。
|
|
5594
|
+
* 到達不能な枝は**壊しても何も起きない**ので、変異検証の網から静かに漏れる
|
|
5595
|
+
* ——このリポジトリが繰り返し踏んでいる形なので、枝ごと落とした。
|
|
5596
|
+
* **畳んではいけない種別を足すときに、テストと一緒に枝を戻すこと。**
|
|
5597
|
+
*/
|
|
4845
5598
|
var SECRET_FORMS = {
|
|
4846
5599
|
"figma": {
|
|
4847
5600
|
icon: "🎨",
|
|
4848
5601
|
title: "Figma のトークン",
|
|
4849
|
-
desc: "デザインの画面インベントリと、Figma に付いたコメント(指摘)を毎晩同期するために使います。",
|
|
4850
5602
|
// **必要なスコープは実装が叩くAPIから決まる。** sync-designs は
|
|
4851
5603
|
// GET /v1/files/{key} → file_content:read(画面インベントリ)
|
|
4852
5604
|
// GET /v1/files/{key}/comments → file_comments:read(指摘の同期)
|
|
@@ -4863,7 +5615,6 @@ ${capabilityState.toString()}
|
|
|
4863
5615
|
"backlog": {
|
|
4864
5616
|
icon: "📋",
|
|
4865
5617
|
title: "Backlog のAPIキー",
|
|
4866
|
-
desc: "課題・Wiki・ドキュメントを毎晩同期するために使います。",
|
|
4867
5618
|
how: "Backlog → 個人設定 → API で発行(この案件のプロジェクトが見えるアカウントで)",
|
|
4868
5619
|
// **案件の担当者が自分で繋ぐ。** 以前は艦隊共通の読み取りアカウントを1つ運用していたが、
|
|
4869
5620
|
// 案件が増えるたびに「その1人がプロジェクトへ参加追加してキーを配る」ことになり、
|
|
@@ -4880,21 +5631,6 @@ ${capabilityState.toString()}
|
|
|
4880
5631
|
"(前の担当者のアカウントが無効になると同期が止まります)。",
|
|
4881
5632
|
},
|
|
4882
5633
|
},
|
|
4883
|
-
"external-sources": {
|
|
4884
|
-
icon: "🐙",
|
|
4885
|
-
title: "GitHub の読み取りトークン",
|
|
4886
|
-
desc: "開発リポジトリの Issues / Discussions を読み、Gold の原料にするために使います。",
|
|
4887
|
-
// **第一の案内は GitHub App(#138)。** 実装の本線は App のインストールトークンで、
|
|
4888
|
-
// 個人トークンは App を入れられない組織のフォールバックでしかない(既存案件は
|
|
4889
|
-
// すでに App で読めている)。画面が個人トークンを勧め続けると、発行と入れ直しが
|
|
4890
|
-
// 要る側に人が流れ続け、**入れ直しの要らない仕組みへ移れない**。
|
|
4891
|
-
// 順序を入れ替えるだけで、どちらも残す——App を入れられない組織は実在する。
|
|
4892
|
-
appFirst: {
|
|
4893
|
-
text: "まず、読み取りたいリポジトリのある組織に Cortex の GitHub App を入れてください。トークンの発行も、期限が来たときの入れ直しも要りません。",
|
|
4894
|
-
linkText: "組織に GitHub App をインストール →",
|
|
4895
|
-
},
|
|
4896
|
-
how: "App を入れられない組織では、代わりにトークンを使います。GitHub → Settings → Developer settings → Personal access tokens で、対象リポジトリに Contents / Issues の Read-only を付けて発行",
|
|
4897
|
-
},
|
|
4898
5634
|
};
|
|
4899
5635
|
|
|
4900
5636
|
/**
|
|
@@ -4907,27 +5643,53 @@ ${capabilityState.toString()}
|
|
|
4907
5643
|
* カードは #101 で能力モーダルの中へ移した(buildDataSources の pushCap 経由)。
|
|
4908
5644
|
* まとめて並べる renderSecrets() は呼び出し元が無くなったので消してある。
|
|
4909
5645
|
*/
|
|
4910
|
-
function secretPayload(intake, kind, value) {
|
|
5646
|
+
function secretPayload(intake, kind, value, cardSpace) {
|
|
4911
5647
|
var body = { projectKey: intake.projectKey, kind: kind, value: value };
|
|
4912
5648
|
if (kind === "backlog") {
|
|
4913
|
-
|
|
5649
|
+
// **カードが担当するスペースを最優先する(2026-08-20)。**
|
|
5650
|
+
// 複数スペースのときは欄がスペースごとに分かれているので、
|
|
5651
|
+
// どの棚へ入れるかは**そのカードが知っている**。
|
|
5652
|
+
// 1枚だけのときは従来どおり(1つに定まるなら名乗る・定まらなければ名簿に倒す)
|
|
5653
|
+
var space = cardSpace || backlogSecretSpace();
|
|
4914
5654
|
if (space) body.space = space;
|
|
4915
5655
|
}
|
|
4916
5656
|
return body;
|
|
4917
5657
|
}
|
|
4918
5658
|
|
|
4919
|
-
|
|
5659
|
+
/**
|
|
5660
|
+
* @param opts { space, hideCurrent }
|
|
5661
|
+
* space … このカードが担当する Backlog スペース(複数スペースのときだけ)
|
|
5662
|
+
* hideCurrent … 「現在:」を出さない(スペース別の充足を表せないため)
|
|
5663
|
+
*/
|
|
5664
|
+
function secretCard(kind, meta, opts) {
|
|
5665
|
+
var cardSpace = (opts && opts.space) || "";
|
|
5666
|
+
var hideCurrent = !!(opts && opts.hideCurrent);
|
|
4920
5667
|
// **いま何で動いているか**を先に出す。入れ直したのに反映されていない状態が
|
|
4921
5668
|
// 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
|
|
4922
5669
|
// 保存の直後は前日の値のまま。時刻は上の節に出している)。
|
|
5670
|
+
// **複数スペースのときは出さない。** この値は種別ごとの1つで、
|
|
5671
|
+
// スペース別の充足を表せない——1枚目に帰属させると嘘になる
|
|
4923
5672
|
var src = (fleetVal("secretSources") || {})[kind] || "";
|
|
5673
|
+
// **どのスペースの鍵かを見出しに出す。** 欄が2つ並ぶので、
|
|
5674
|
+
// 見分けが付かないと「どちらに入れたか」が分からなくなる
|
|
5675
|
+
var cardTitle = cardSpace ? meta.title + "(" + cardSpace + ")" : meta.title;
|
|
4924
5676
|
// **前方一致で見る。** #105 の移行期は「Secrets Manager(旧名)」も来る。
|
|
4925
5677
|
// 完全一致にすると、旧名で動いている案件だけが repo secret と同じ見た目になり、
|
|
4926
5678
|
// 「管理下にあるのに未移行」という一番知りたい状態が画面から消える
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
5679
|
+
// **複数スペースのときは、何も出さない(2026-08-20)。**
|
|
5680
|
+
// この値は種別ごとの1つで、スペース別の充足を表せない。
|
|
5681
|
+
// 「未設定」に倒すのも嘘——**片方は入っているかもしれない**。
|
|
5682
|
+
// 出さない代わりに、どちらの鍵かは見出し(スペース名)で示す
|
|
5683
|
+
var state = hideCurrent
|
|
5684
|
+
// **消えた理由を書く(レビュー指摘)。** 1スペースの案件では出ていた行が
|
|
5685
|
+
// 黙って消えると、利用者は「入っているのか未設定なのか」も
|
|
5686
|
+
// 「なぜ表示が無いのか」も知る手段が無い
|
|
5687
|
+
? el("p", { class: "sec-state is-none",
|
|
5688
|
+
text: "スペースごとの登録状況は、まだ表示できません(貼り直すと、このスペースの鍵として上書きされます)" })
|
|
5689
|
+
: src
|
|
5690
|
+
? el("p", { class: "sec-state" + (src.indexOf("Secrets Manager") === 0 ? " is-managed" : ""),
|
|
5691
|
+
text: "現在: " + src + " から取得中" })
|
|
5692
|
+
: el("p", { class: "sec-state is-none", text: "現在: 未設定" });
|
|
4931
5693
|
|
|
4932
5694
|
var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
|
|
4933
5695
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
|
|
@@ -4951,7 +5713,7 @@ ${capabilityState.toString()}
|
|
|
4951
5713
|
// こちらが渡すのは「どのスペースの鍵か」という材料だけ。
|
|
4952
5714
|
// **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
|
|
4953
5715
|
// 貼った鍵が「スペースを特定できません」で断られる
|
|
4954
|
-
body: JSON.stringify(secretPayload(intake, kind, value)),
|
|
5716
|
+
body: JSON.stringify(secretPayload(intake, kind, value, cardSpace)),
|
|
4955
5717
|
});
|
|
4956
5718
|
var j = await res.json().catch(function () { return {}; });
|
|
4957
5719
|
if (!res.ok) {
|
|
@@ -4977,38 +5739,50 @@ ${capabilityState.toString()}
|
|
|
4977
5739
|
}
|
|
4978
5740
|
});
|
|
4979
5741
|
|
|
5742
|
+
// 発行の手順ひとそろい。**<details> に畳む**(文言は消さない。畳んだ中に全部残る)
|
|
5743
|
+
var guidance = [
|
|
5744
|
+
el("p", { class: "sec-how", text: meta.how }),
|
|
5745
|
+
// **鍵の作り方に付随する注意(招待漏れ等)。** 発行手順(how)とは別に出す——
|
|
5746
|
+
// 「トークンは正しいのに読めない」は権限の問題に見えないので、切り分けに時間がかかる。
|
|
5747
|
+
meta.note ? el("p", { class: "sec-how", text: meta.note }) : null,
|
|
5748
|
+
meta.selfServe ? el("p", { class: "sec-how", text: meta.selfServe.note }) : null,
|
|
5749
|
+
].filter(Boolean);
|
|
5750
|
+
// **「畳んである中に何があるか」を summary に書く。** ただの「詳細」だと、
|
|
5751
|
+
// 発行のしかたを探している人がここを開く手掛かりを持てない(rt カードと同じ流儀)
|
|
5752
|
+
var guidanceBox = el("details", { class: "sec-fold" }, [el("summary", { text: "発行のしかた" })]);
|
|
5753
|
+
for (var gi = 0; gi < guidance.length; gi++) guidanceBox.appendChild(guidance[gi]);
|
|
5754
|
+
|
|
4980
5755
|
// ライブ取得後に「現在: …」だけを差し替えるための目印(カードごと作り直すと入力が消える)
|
|
5756
|
+
//
|
|
5757
|
+
// **並びは「状態 → 貼る → 畳んだ手順」。** カードを短く見せるために、
|
|
5758
|
+
// いちばんよく使う操作(貼って保存)を上へ出し、長い説明を下へ送っている。
|
|
5759
|
+
//
|
|
5760
|
+
// 🔴 **.sec-state / .sec-preferred / .sec-input は <details> の外に置くこと。**
|
|
5761
|
+
// - .sec-state … 保存したのに反映されていない状態が分かる唯一の手段。
|
|
5762
|
+
// 押すまで見えないと成立しない
|
|
5763
|
+
// - .sec-preferred … 「誰のアカウントで発行するのか」を**手順より先に**伝える行。
|
|
5764
|
+
// 未設定のときだけ出る=これから貼る人にだけ出るので、畳むと存在理由が消える
|
|
5765
|
+
// - .sec-input … 貼る欄そのもの。ライブ差し替えもこのノードを直接触る
|
|
4981
5766
|
return el("div", { class: "ops-card", "data-secret-kind": kind }, [
|
|
4982
5767
|
el("div", { class: "ops-card-head" }, [
|
|
4983
5768
|
el("span", { class: "ops-card-icon", text: meta.icon }),
|
|
4984
|
-
el("h3", { class: "ops-card-title", text:
|
|
5769
|
+
el("h3", { class: "ops-card-title", text: cardTitle }),
|
|
4985
5770
|
]),
|
|
4986
|
-
|
|
5771
|
+
// **desc は無い種別がある(2026-08-21)。** el() は text をそのまま textContent へ
|
|
5772
|
+
// 代入するので、無条件に出すと画面に文字列 undefined が出る
|
|
5773
|
+
meta.desc ? el("p", { class: "ops-card-desc", text: meta.desc }) : null,
|
|
4987
5774
|
state,
|
|
4988
5775
|
// **未設定のときだけ、先に試すべき手を出す。** 既に動いているカードに
|
|
4989
5776
|
// 「まずこれをやれ」と出すと、直す必要が無いものを触らせることになる
|
|
4990
5777
|
!src && meta.selfServe ? el("p", { class: "sec-preferred", text: meta.selfServe.text }) : null,
|
|
4991
|
-
// **本線(App)はトークンの手順より先に、いつでも出す(#138)。**
|
|
4992
|
-
// トークンで動いている案件こそ App へ移してほしいので、selfServe と違って
|
|
4993
|
-
// 「未設定のときだけ」にはしない
|
|
4994
|
-
meta.appFirst
|
|
4995
|
-
? el("p", { class: "sec-preferred" }, [
|
|
4996
|
-
document.createTextNode(meta.appFirst.text + " "),
|
|
4997
|
-
el("a", { href: GITHUB_APP_INSTALL_URL, target: "_blank", rel: "noopener", text: meta.appFirst.linkText }),
|
|
4998
|
-
])
|
|
4999
|
-
: null,
|
|
5000
|
-
el("p", { class: "sec-how", text: meta.how }),
|
|
5001
|
-
// **鍵の作り方に付随する注意(招待漏れ等)。** 発行手順(how)とは別に出す——
|
|
5002
|
-
// 「トークンは正しいのに読めない」は権限の問題に見えないので、切り分けに時間がかかる。
|
|
5003
|
-
meta.note ? el("p", { class: "sec-how", text: meta.note }) : null,
|
|
5004
|
-
meta.selfServe ? el("p", { class: "sec-how", text: meta.selfServe.note }) : null,
|
|
5005
5778
|
input,
|
|
5779
|
+
el("div", { class: "ds-toggle" }, [btn, msg]),
|
|
5780
|
+
guidanceBox,
|
|
5006
5781
|
// **扱いの説明はカードに持たせる。** 節の見出しの下に置いていたときは、
|
|
5007
5782
|
// #101 でカードだけがモーダルへ移り、説明だけが画面から消えた。
|
|
5008
5783
|
// 顧客にトークンの貼り付けを求める画面で、貼った値がどう扱われるかの
|
|
5009
5784
|
// 説明が落ちるのは、いちばん落としてはいけない種類の欠落
|
|
5010
5785
|
el("p", { class: "sec-note", text: "入れた値はここには表示されません(保存だけできます)。保存先は案件ごとに分かれているので、他の案件には影響しません。" }),
|
|
5011
|
-
el("div", { class: "ds-toggle" }, [btn, msg]),
|
|
5012
5786
|
]);
|
|
5013
5787
|
}
|
|
5014
5788
|
|