@takagaki/cortex-decisions-viewer 0.12.58 → 0.12.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/render.js +656 -90
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.devRepositoryRefs = devRepositoryRefs;
|
|
|
10
10
|
exports.claudeCodeRepositories = claudeCodeRepositories;
|
|
11
11
|
exports.claudeCodeUrl = claudeCodeUrl;
|
|
12
12
|
exports.toolStateLabel = toolStateLabel;
|
|
13
|
-
exports.
|
|
13
|
+
exports.regRowOfToolItem = regRowOfToolItem;
|
|
14
14
|
exports.toolsFoldSummary = toolsFoldSummary;
|
|
15
15
|
exports.isAlreadyRegistered = isAlreadyRegistered;
|
|
16
16
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
@@ -19,6 +19,8 @@ exports.overlayStateOnFleet = overlayStateOnFleet;
|
|
|
19
19
|
exports.chatRowsToAdd = chatRowsToAdd;
|
|
20
20
|
exports.gchatRowsToAdd = gchatRowsToAdd;
|
|
21
21
|
exports.overlayGchatNotify = overlayGchatNotify;
|
|
22
|
+
exports.overlaySlackNotify = overlaySlackNotify;
|
|
23
|
+
exports.overlayNotifyAuto = overlayNotifyAuto;
|
|
22
24
|
exports.devRowsToAdd = devRowsToAdd;
|
|
23
25
|
exports.markExcludedPendingRows = markExcludedPendingRows;
|
|
24
26
|
exports.designRowsToAdd = designRowsToAdd;
|
|
@@ -57,6 +59,10 @@ exports.calendarTemplateUrl = calendarTemplateUrl;
|
|
|
57
59
|
exports.calendarAddUrl = calendarAddUrl;
|
|
58
60
|
exports.deriveCapTile = deriveCapTile;
|
|
59
61
|
exports.derivePendingTile = derivePendingTile;
|
|
62
|
+
exports.liveRowVerdict = liveRowVerdict;
|
|
63
|
+
exports.liveRegistryOf = liveRegistryOf;
|
|
64
|
+
exports.liveToolKey = liveToolKey;
|
|
65
|
+
exports.liveToolItems = liveToolItems;
|
|
60
66
|
exports.renderConnectionMap = renderConnectionMap;
|
|
61
67
|
const node_crypto_1 = require("node:crypto");
|
|
62
68
|
const google_chat_guide_images_1 = require("./google-chat-guide-images");
|
|
@@ -523,6 +529,11 @@ function claudeCodeUrl(prompt, projectRepo, externals) {
|
|
|
523
529
|
// **ビューアは登録簿(channels.json 等)を読まない。** 読むと「使っていると言う条件」が
|
|
524
530
|
// engine と画面で 2 枚になり、片方だけ直った日から一覧と AI の起動文が食い違う
|
|
525
531
|
// (viewer/test/no-registry-read.test.mjs が見張っている)。
|
|
532
|
+
//
|
|
533
|
+
// **一覧の行の有無と状態は、ライブの登録一覧(op=state の registeredNow・操作タブが既に
|
|
534
|
+
// 読んでいるもの)と接続状態ストア(stateNow)で差し替える(#581・`liveToolItems`)。**
|
|
535
|
+
// まとめ表は毎朝の schedule でしか作り直されないので、そのまま描くと日中に外した接続先が
|
|
536
|
+
// 翌朝まで残る。行ごとの状態の判定はタイル(`derivePendingTile`)と同じ `liveRowVerdict`。
|
|
526
537
|
// ---------------------------------------------------------------------------
|
|
527
538
|
/**
|
|
528
539
|
* 種別のアイコン。**DS_TYPE(データソース一覧)と同じ絵柄にそろえる**——同じツールが
|
|
@@ -538,11 +549,15 @@ const TOOL_KIND_ICONS = {
|
|
|
538
549
|
"google-meet": "🎥",
|
|
539
550
|
};
|
|
540
551
|
/**
|
|
541
|
-
* 状態の表示語と色。**4
|
|
552
|
+
* 状態の表示語と色。**4 語+「登録状況を確認中です」しか出さない**(読み取り中/承認待ち/
|
|
553
|
+
* 停止中/未確認、そして登録簿にしか無くまだ測っていない行の「確認中」・#581)。
|
|
542
554
|
*
|
|
543
555
|
* 色は既存の `ds-gate-*` を流用する——同じ画面で「緑=読めている・橙=人の操作待ち・
|
|
544
556
|
* 灰=分からない」の意味を 2 通りに増やさない。
|
|
545
557
|
*
|
|
558
|
+
* 🔴 **`pending` の語は操作タブの行(`DS_GATE_PENDING`)と同じ「登録状況を確認中です」。**
|
|
559
|
+
* 新しい言い方を作ると、同じ接続先が Home と操作タブで違う語になる。
|
|
560
|
+
*
|
|
546
561
|
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
547
562
|
*/
|
|
548
563
|
function toolStateLabel(state) {
|
|
@@ -553,53 +568,72 @@ function toolStateLabel(state) {
|
|
|
553
568
|
// 止めているものは警告色にしない(直す必要が無いので、橙にすると直しに行かせてしまう)
|
|
554
569
|
if (state === "stopped")
|
|
555
570
|
return { text: "停止中", cls: "ds-gate-muted" };
|
|
571
|
+
// 登録簿にはあるが、まだ誰も測っていない(#581)。**既定分岐(未確認)より前に置く**
|
|
572
|
+
if (state === "pending")
|
|
573
|
+
return { text: "登録状況を確認中です", cls: "ds-gate-muted" };
|
|
556
574
|
return { text: "未確認", cls: "ds-gate-muted" };
|
|
557
575
|
}
|
|
558
576
|
/**
|
|
559
|
-
*
|
|
577
|
+
* 種別 → ライブの登録一覧(`registeredNow`)のキー(#581)。**この表は Home の一覧の
|
|
578
|
+
* 突き合わせにしか使わない**。会議(google-meet)は登録簿を持たない(ref 固定 `transcripts`)
|
|
579
|
+
* ので載せない=Home ではまとめ表の行がそのまま残る。
|
|
560
580
|
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
581
|
+
* GitHub は `dev`(読み取り中)と `devExcluded`(停止中)の 2 キーに分かれて届く。
|
|
582
|
+
* 「停止中」と「登録から消えた」を分ける唯一の材料が `devExcluded` なので、
|
|
583
|
+
* **両方が配列のときだけ登録簿を正本にする**(`liveToolItems` の項)
|
|
584
|
+
*/
|
|
585
|
+
const TOOL_KIND_REG = {
|
|
586
|
+
backlog: "issues",
|
|
587
|
+
github: "dev",
|
|
588
|
+
slack: "chat",
|
|
589
|
+
"google-chat": "google-chat",
|
|
590
|
+
figma: "design",
|
|
591
|
+
"google-drive": "materials",
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* 種別の人向けの名前。**engine `project-tools.mjs` の `TOOL_LABELS` の写し**(一致は
|
|
595
|
+
* `home-tools-live.test.mjs` が契約 fixture と突き合わせる)。まとめ表の行が 1 つでも
|
|
596
|
+
* あればそちらの `label` を優先し、ここを引くのは**登録簿にしか無い種別の行**だけ。
|
|
597
|
+
*/
|
|
598
|
+
const TOOL_KIND_LABELS = {
|
|
599
|
+
backlog: "Backlog",
|
|
600
|
+
github: "GitHub",
|
|
601
|
+
slack: "Slack",
|
|
602
|
+
"google-chat": "Google Chat",
|
|
603
|
+
figma: "Figma",
|
|
604
|
+
"google-drive": "Google Drive",
|
|
605
|
+
"google-meet": "Google Meet",
|
|
606
|
+
};
|
|
607
|
+
/** ライブの登録行から組む href に許す字(英数と `/` `.` `_` `-`)。形が外れたら url は null */
|
|
608
|
+
const TOOL_URL_SAFE = /^[A-Za-z0-9._\/-]+$/;
|
|
609
|
+
/**
|
|
610
|
+
* まとめ表(`SITE.tools`)の 1 行を、ライブの登録一覧の行と同じ形に写す(#581)。
|
|
563
611
|
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
* ストアの ref は、人が貼った URL 由来で大小が揺れる。
|
|
612
|
+
* **状態の判定を 1 本にするための小さな写し。** 登録簿が読めなかった回(`registered` が
|
|
613
|
+
* null)でも、まとめ表の行をこの形にして `liveRowVerdict` に渡す——「登録簿が届いたか
|
|
614
|
+
* どうか」で別の関数から状態が出る形を残さない(設計レビュー🔴5)。
|
|
568
615
|
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
616
|
+
* 鍵の畳み方は `registeredRefKeys` が持つ。ここは欄の名前を合わせるだけ
|
|
617
|
+
* (issues は `domain/KEY` を割る。割れない値は空の行=鍵が組めず未測定に倒れる)。
|
|
571
618
|
*
|
|
572
619
|
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
573
620
|
*/
|
|
574
|
-
function
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (
|
|
578
|
-
return "
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (item.cap === "chat" && String(r.workspace || "") !== String(item.workspace || ""))
|
|
591
|
-
return;
|
|
592
|
-
hit = r;
|
|
593
|
-
});
|
|
594
|
-
if (!hit)
|
|
595
|
-
return String(item.state || "unknown");
|
|
596
|
-
if (hit.status === "ok")
|
|
597
|
-
return "reading";
|
|
598
|
-
// **「測ったが分からなかった」は「承認待ち」に寄せない**(人が押す操作が無い)
|
|
599
|
-
if (hit.status === "unknown")
|
|
600
|
-
return "unknown";
|
|
601
|
-
// failed / skipped / not_in_channel / missing_scope / no_token … は全部「人の操作待ち」
|
|
602
|
-
return "waiting";
|
|
621
|
+
function regRowOfToolItem(item) {
|
|
622
|
+
var kind = String(item && item.kind != null ? item.kind : "");
|
|
623
|
+
var ref = String(item && item.ref != null ? item.ref : "");
|
|
624
|
+
if (kind === "slack")
|
|
625
|
+
return { ref: ref, workspace: String(item.workspace == null ? "" : item.workspace), name: item.name };
|
|
626
|
+
if (kind === "figma")
|
|
627
|
+
return { key: ref, name: item.name };
|
|
628
|
+
if (kind === "google-drive")
|
|
629
|
+
return { id: ref, name: item.name };
|
|
630
|
+
if (kind === "backlog") {
|
|
631
|
+
var at = ref.indexOf("/");
|
|
632
|
+
if (at <= 0 || at === ref.length - 1)
|
|
633
|
+
return {};
|
|
634
|
+
return { domain: ref.slice(0, at), projectKey: ref.slice(at + 1) };
|
|
635
|
+
}
|
|
636
|
+
return { ref: ref, name: item.name };
|
|
603
637
|
}
|
|
604
638
|
/**
|
|
605
639
|
* 畳んだ種別の 1 行に出す、状態の要約(#424)。
|
|
@@ -617,9 +651,9 @@ function toolStateWithLive(item, rows) {
|
|
|
617
651
|
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
618
652
|
*/
|
|
619
653
|
function toolsFoldSummary(states) {
|
|
620
|
-
//
|
|
621
|
-
var order = ["reading", "waiting", "stopped", "unknown"];
|
|
622
|
-
var count = { reading: 0, waiting: 0, stopped: 0, unknown: 0 };
|
|
654
|
+
// 表示の順は状態の語と同じ(読み取り中→承認待ち→停止中→未確認→登録状況を確認中です)
|
|
655
|
+
var order = ["reading", "waiting", "stopped", "unknown", "pending"];
|
|
656
|
+
var count = { reading: 0, waiting: 0, stopped: 0, unknown: 0, pending: 0 };
|
|
623
657
|
(states || []).forEach(function (s) {
|
|
624
658
|
// ⚠ 4 語のどれかに当たるかは **order で見る**(素のオブジェクトの添字だと
|
|
625
659
|
// "constructor" のような語が継承プロパティに当たって、知らない語が素通りする)
|
|
@@ -855,9 +889,11 @@ const REGISTERED_CAP_NAMES = {
|
|
|
855
889
|
* `orgUnknown` で同じ穴を1つ埋めた(0.12.1)のに、同族の3欄が残っていた。
|
|
856
890
|
*/
|
|
857
891
|
const REGISTERED_FIELDS = {
|
|
858
|
-
|
|
892
|
+
// notifyAuto(#589): 接続が確認できたので自動で通知先になった行の印。
|
|
893
|
+
// **`notify` と対で読む**——サーバは通知先である行にしか付けない
|
|
894
|
+
chat: ["ref", "name", "workspace", "orgUnknown", "origin", "goldState", "notify", "notifyAuto"],
|
|
859
895
|
// notify(#572): スペースも通知先にできる。真偽値の扱いは chat の notify と同じ篩
|
|
860
|
-
"google-chat": ["ref", "name", "notify"],
|
|
896
|
+
"google-chat": ["ref", "name", "notify", "notifyAuto"],
|
|
861
897
|
dev: ["ref", "type"],
|
|
862
898
|
// 🔴 **意図的に外した開発リポジトリ(#391)。** サーバは前から別キーで返していたが、
|
|
863
899
|
// ここに無いので篩が丸ごと落としていた。**これが読めないと「読み取り停止中」の行を
|
|
@@ -942,8 +978,9 @@ function sanitizeRegisteredNow(v) {
|
|
|
942
978
|
// **false はキーごと落とす。** 読み手は `=== true` で見る(`s.notify === true` /
|
|
943
979
|
// `r.orgUnknown === true`)ので、false を載せても意味は同じ——載せないほうが
|
|
944
980
|
// 「印が無い」と「印が false」の2通りを画面に持ち込まない。
|
|
945
|
-
// 例外はこの
|
|
946
|
-
|
|
981
|
+
// 例外はこの3欄だけ(#589 で `notifyAuto` を足した)——他の欄まで真偽値を通すと、
|
|
982
|
+
// 許可リストが篩の意味を失う
|
|
983
|
+
if (f === "orgUnknown" || f === "notify" || f === "notifyAuto") {
|
|
947
984
|
if (fv === true)
|
|
948
985
|
item[f] = true;
|
|
949
986
|
continue;
|
|
@@ -1158,6 +1195,10 @@ function chatRowsToAdd(externals, rows) {
|
|
|
1158
1195
|
// **真偽値の印は true のときだけ載せる**(読み手は `s.notify === true`。篩の項を参照)
|
|
1159
1196
|
if (r.notify === true)
|
|
1160
1197
|
row.notify = true;
|
|
1198
|
+
// 自動で設定された通知先の印(#589)。**足す行にも載せる**——載せないと、追加して
|
|
1199
|
+
// 接続を確認した直後(一番この説明が要る瞬間)だけ「自動で設定」が出ない
|
|
1200
|
+
if (r.notify === true && r.notifyAuto === true)
|
|
1201
|
+
row.notifyAuto = true;
|
|
1161
1202
|
out.push(row);
|
|
1162
1203
|
}
|
|
1163
1204
|
return out;
|
|
@@ -1184,6 +1225,9 @@ function gchatRowsToAdd(externals, rows) {
|
|
|
1184
1225
|
// 通知先の印(#572)。真偽値の印は true のときだけ載せる(chatRowsToAdd と同じ)
|
|
1185
1226
|
if (r.notify === true)
|
|
1186
1227
|
row.notify = true;
|
|
1228
|
+
// 自動で設定された通知先の印(#589・chatRowsToAdd と同じ条件)
|
|
1229
|
+
if (r.notify === true && r.notifyAuto === true)
|
|
1230
|
+
row.notifyAuto = true;
|
|
1187
1231
|
out.push(row);
|
|
1188
1232
|
}
|
|
1189
1233
|
return out;
|
|
@@ -1232,6 +1276,106 @@ function overlayGchatNotify(externals, rows) {
|
|
|
1232
1276
|
});
|
|
1233
1277
|
return changed ? out : list;
|
|
1234
1278
|
}
|
|
1279
|
+
/**
|
|
1280
|
+
* まとめ表が既に持っている Slack の行に、live(registeredNow)が「通知先」と言う印を**足す**(#589)。
|
|
1281
|
+
*
|
|
1282
|
+
* 🔴 **#589 で、まとめ表を待たずに通知先が増えるようになった。** まとめ表(fleet-status)は
|
|
1283
|
+
* 夜間の生成物なので、「接続を確認」で自動的に通知先になったチャンネルは**翌朝まで
|
|
1284
|
+
* `notify` を持たない**——札が出ないだけでなく、トグルが「通知先にする」のままになる。
|
|
1285
|
+
* **通知は届いているのに画面は通知先でないと言う**、という取り違えが最長 24 時間続く。
|
|
1286
|
+
*
|
|
1287
|
+
* 🔴 **足すだけで、外さない(`overlayGchatNotify` との違い)。** 画面の `registeredNow` からは
|
|
1288
|
+
* 「宣言を読んだうえで通知先でなかった」と「宣言を読めなかった」が区別できない
|
|
1289
|
+
* (どちらも `notify` のキーが無い。サーバ側 `buildRegisteredNow` は notify.json を読めなかった
|
|
1290
|
+
* 便では**全行から印を落とす**)。外す向きにも効かせると、GitHub が不調だった 1 回の読み取りで
|
|
1291
|
+
* **本物の通知先が画面から「通知先でない」に化ける**——通知の宛先を取り違えさせる方向なので、
|
|
1292
|
+
* 足す向きだけに限る。外す向きが遅れる(最長 24 時間「通知先」の札が残る)のは #589 より前からの
|
|
1293
|
+
* Slack の挙動で、こちらは悪化させない。Google Chat が両向きなのは、まとめ表が
|
|
1294
|
+
* その欄を**そもそも持たない**ので「読めなかった」と混ざりようが無いから。
|
|
1295
|
+
*
|
|
1296
|
+
* 新しく足す行の印は `chatRowsToAdd` が持つ(こちらは既にある行だけを直す)。
|
|
1297
|
+
*
|
|
1298
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
1299
|
+
*/
|
|
1300
|
+
function overlaySlackNotify(externals, rows) {
|
|
1301
|
+
const list = externals || [];
|
|
1302
|
+
if (!Array.isArray(rows))
|
|
1303
|
+
return list;
|
|
1304
|
+
const live = {};
|
|
1305
|
+
for (const r of rows) {
|
|
1306
|
+
if (!r || !r.ref || r.notify !== true)
|
|
1307
|
+
continue;
|
|
1308
|
+
// 鍵はワークスペース込み(チャンネルIDはワークスペースの中でしか一意でない・#104)
|
|
1309
|
+
live[String(r.workspace || "") + "\t" + String(r.ref)] = true;
|
|
1310
|
+
}
|
|
1311
|
+
let changed = false;
|
|
1312
|
+
const out = list.map(function (s) {
|
|
1313
|
+
if (!s || s.type !== "slack" || !s.ref || s.notify === true)
|
|
1314
|
+
return s;
|
|
1315
|
+
if (live[String(s.workspace || "") + "\t" + String(s.ref)] !== true)
|
|
1316
|
+
return s;
|
|
1317
|
+
changed = true;
|
|
1318
|
+
return Object.assign({}, s, { notify: true });
|
|
1319
|
+
});
|
|
1320
|
+
return changed ? out : list;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* 「自動で設定された通知先」の印(#589)を、まとめ表の行に重ねる。
|
|
1324
|
+
*
|
|
1325
|
+
* 🔴 **まとめ表(fleet-status)はこの印を持たない。** 印の置き場は案件リポの
|
|
1326
|
+
* `チャット/notify.json` の `autoDefault` で、それを読んで行に付けるのは中央(intake)の
|
|
1327
|
+
* `registeredNow` だけ。重ねないと、**自動で通知先になった行が「通知先」としか出ず、
|
|
1328
|
+
* 担当者は自分が設定した覚えのない通知先を前にして、外してよいのかも分からない**
|
|
1329
|
+
* (オーナー決定 4「外せる」が画面から読めない)。
|
|
1330
|
+
*
|
|
1331
|
+
* **印だけを重ねる。** `notify` そのものには触らない——通知先かどうかを live で重ねるのは
|
|
1332
|
+
* `overlayGchatNotify`(Google Chat)と `overlaySlackNotify`(Slack)の仕事で、**この関数より
|
|
1333
|
+
* 先に走る**。ここで二重に決めると「どちらが正か」が読めなくなる。印は `notify` が立っている
|
|
1334
|
+
* 行にしか付けない(サーバ側も同じ条件で付けている)。
|
|
1335
|
+
*
|
|
1336
|
+
* live に該当の能力のキーが無ければ(読めていない)何も変えない。
|
|
1337
|
+
*
|
|
1338
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
1339
|
+
*/
|
|
1340
|
+
function overlayNotifyAuto(externals, reg) {
|
|
1341
|
+
const list = externals || [];
|
|
1342
|
+
if (!reg || typeof reg !== "object")
|
|
1343
|
+
return list;
|
|
1344
|
+
const auto = {};
|
|
1345
|
+
const known = {};
|
|
1346
|
+
const scan = (rows, type, withWorkspace) => {
|
|
1347
|
+
if (!Array.isArray(rows))
|
|
1348
|
+
return;
|
|
1349
|
+
for (const r of rows) {
|
|
1350
|
+
if (!r || !r.ref)
|
|
1351
|
+
continue;
|
|
1352
|
+
const key = type + "\t" + (withWorkspace ? String(r.workspace || "") : "") + "\t" + String(r.ref);
|
|
1353
|
+
known[key] = true;
|
|
1354
|
+
auto[key] = r.notifyAuto === true && r.notify === true;
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
scan(reg.chat, "slack", true);
|
|
1358
|
+
scan(reg["google-chat"], "google-chat", false);
|
|
1359
|
+
let changed = false;
|
|
1360
|
+
const out = list.map(function (s) {
|
|
1361
|
+
if (!s || !s.ref || (s.type !== "slack" && s.type !== "google-chat"))
|
|
1362
|
+
return s;
|
|
1363
|
+
const key = s.type + "\t" + (s.type === "slack" ? String(s.workspace || "") : "") + "\t" + String(s.ref);
|
|
1364
|
+
if (!Object.prototype.hasOwnProperty.call(known, key))
|
|
1365
|
+
return s;
|
|
1366
|
+
const want = auto[key] === true && s.notify === true;
|
|
1367
|
+
if ((s.notifyAuto === true) === want)
|
|
1368
|
+
return s;
|
|
1369
|
+
changed = true;
|
|
1370
|
+
const row = Object.assign({}, s);
|
|
1371
|
+
if (want)
|
|
1372
|
+
row.notifyAuto = true;
|
|
1373
|
+
else
|
|
1374
|
+
delete row.notifyAuto;
|
|
1375
|
+
return row;
|
|
1376
|
+
});
|
|
1377
|
+
return changed ? out : list;
|
|
1378
|
+
}
|
|
1235
1379
|
/**
|
|
1236
1380
|
* 開発の行で、まとめ表がまだ知らないもの。**鍵は `owner/repo` の生文字列。**
|
|
1237
1381
|
*
|
|
@@ -1638,6 +1782,10 @@ function mergeRegisteredFleet(view, reg) {
|
|
|
1638
1782
|
return out;
|
|
1639
1783
|
// Google Chat の行の「通知先」は live が正(#572・まとめ表は持っていない)。足す前に重ねる
|
|
1640
1784
|
out.externalSources = overlayGchatNotify(v.externalSources, reg["google-chat"]);
|
|
1785
|
+
// Slack の行の「通知先」も live が正(#589・まとめ表は夜間の生成物で、自動設定に追いつかない)
|
|
1786
|
+
out.externalSources = overlaySlackNotify(out.externalSources, reg.chat);
|
|
1787
|
+
// 「自動で設定」の印も live が正(#589・まとめ表は持っていない)。`notify` を重ねた後に置く
|
|
1788
|
+
out.externalSources = overlayNotifyAuto(out.externalSources, reg);
|
|
1641
1789
|
const add = registeredRowsToAdd(v, reg);
|
|
1642
1790
|
if (add.externalSources.length)
|
|
1643
1791
|
out.externalSources = (out.externalSources || []).concat(add.externalSources);
|
|
@@ -2538,9 +2686,15 @@ function calendarTemplateUrl(id) {
|
|
|
2538
2686
|
*
|
|
2539
2687
|
* ⚠ `calendarTemplateUrl` と同じく**実測は未了**(#425 の 3)。共有されている人が開くと
|
|
2540
2688
|
* 「カレンダーを追加しますか」が出る想定。
|
|
2689
|
+
*
|
|
2690
|
+
* 🔴 **engine の `projectCalendarUrl`(Home の「使用ツール」の Google Meet 行)と同じ形にする**
|
|
2691
|
+
* (cortex-engine #343)。`u/0` は「1 番目にログインしている Google アカウント」で、
|
|
2692
|
+
* 片方だけに付いていると、業務アカウントが 2 番目の人には**Home のリンクは開けるのに
|
|
2693
|
+
* この接続タブは開けない(あるいはその逆)**という、説明できない差になる。
|
|
2694
|
+
* 外すときは engine 側と同じ回に外す。
|
|
2541
2695
|
*/
|
|
2542
2696
|
function calendarAddUrl(id) {
|
|
2543
|
-
return "https://calendar.google.com/calendar/r?cid=" + encodeURIComponent(id);
|
|
2697
|
+
return "https://calendar.google.com/calendar/u/0/r?cid=" + encodeURIComponent(id);
|
|
2544
2698
|
}
|
|
2545
2699
|
/**
|
|
2546
2700
|
* タイル1行(と接続グラフの1ノード)の状態を、**正本から導出する**(0.12.0)。
|
|
@@ -2717,8 +2871,6 @@ function deriveCapTile(cap, ctx) {
|
|
|
2717
2871
|
function derivePendingTile(regKey, registered, rows) {
|
|
2718
2872
|
if (!Array.isArray(registered) || registered.length === 0)
|
|
2719
2873
|
return null;
|
|
2720
|
-
var state = Array.isArray(rows) ? rows : [];
|
|
2721
|
-
var bad = BAD_STATE_STATUSES.reachable || [];
|
|
2722
2874
|
var latestAt = "";
|
|
2723
2875
|
var latestMs = -Infinity;
|
|
2724
2876
|
var unmeasured = false;
|
|
@@ -2727,37 +2879,25 @@ function derivePendingTile(regKey, registered, rows) {
|
|
|
2727
2879
|
var reg = registered[i];
|
|
2728
2880
|
if (!reg || typeof reg !== "object")
|
|
2729
2881
|
continue;
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
var
|
|
2733
|
-
for (var j = 0; j < state.length; j++) {
|
|
2734
|
-
var r = state[j];
|
|
2735
|
-
if (!r || r.cap !== regKey || r.question !== "reachable")
|
|
2736
|
-
continue;
|
|
2737
|
-
if (!keys || !keys[stateRefKey(regKey, r.ref, r.workspace)])
|
|
2738
|
-
continue;
|
|
2739
|
-
// **最新の 1 行が勝つ。** 読めない at は最古(NaN は比較で常に負けるので -Infinity に畳む)。
|
|
2740
|
-
// 同値は後勝ち(`>=`)——サーバが並べた順を崩さない
|
|
2741
|
-
var ms = Date.parse(String(r.at || ""));
|
|
2742
|
-
if (ms !== ms)
|
|
2743
|
-
ms = -Infinity;
|
|
2744
|
-
if (hit === null || ms >= hitMs) {
|
|
2745
|
-
hit = r;
|
|
2746
|
-
hitMs = ms;
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2882
|
+
// 行ごとの判定は Home の「使用ツール」と同じ 1 本(`liveRowVerdict`・#581)。
|
|
2883
|
+
// ここに鍵の突き合わせや語の分類を書き戻すと、タイルと Home が別の判定を持つ
|
|
2884
|
+
var v = liveRowVerdict(regKey, reg, rows, "reachable");
|
|
2749
2885
|
// 🔴 **測っていない登録行が 1 つでもあれば「接続OK」と断言しない。** 1 件の OK で能力
|
|
2750
2886
|
// 全体を「接続OK」にすると、登録したばかりで一度も測っていないチャンネルまで繋がっている
|
|
2751
2887
|
// ことになる(この関数が直そうとしている嘘の裏返し)。ただし**ループは最後まで回す**
|
|
2752
2888
|
// ——測定済みの失敗は事実なので、未測定の行が混ざっていても失敗は出す(下の優先順)。
|
|
2753
2889
|
// `unknown`(測れなかった)も未測定と同じ扱い: 行は「不明」と出るが、タイルは断言しない
|
|
2754
|
-
if (
|
|
2890
|
+
if (v === null || v.status === "unknown") {
|
|
2755
2891
|
unmeasured = true;
|
|
2756
2892
|
continue;
|
|
2757
2893
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2894
|
+
var hit = v.row;
|
|
2895
|
+
if (v.status === "ok") {
|
|
2896
|
+
var ms = Date.parse(String(hit.at || ""));
|
|
2897
|
+
if (ms !== ms)
|
|
2898
|
+
ms = -Infinity;
|
|
2899
|
+
if (ms >= latestMs) {
|
|
2900
|
+
latestMs = ms;
|
|
2761
2901
|
latestAt = String(hit.at || "");
|
|
2762
2902
|
}
|
|
2763
2903
|
continue;
|
|
@@ -2779,6 +2919,338 @@ function derivePendingTile(regKey, registered, rows) {
|
|
|
2779
2919
|
return null;
|
|
2780
2920
|
return { kind: "ok", at: latestAt };
|
|
2781
2921
|
}
|
|
2922
|
+
/**
|
|
2923
|
+
* 登録行 1 つに対する、接続状態ストアの答え(#576 の行ごとの判定を切り出したもの・#581)。
|
|
2924
|
+
*
|
|
2925
|
+
* **タイル(`derivePendingTile`)と Home の「使用ツール」(`liveToolItems`)が同じこの
|
|
2926
|
+
* 関数で行の状態を決める**——鍵の突き合わせ・複数行の勝ち方・語の分類を 2 枚にしない
|
|
2927
|
+
* (`ops-ui-controls-policy.md` R10)。
|
|
2928
|
+
*
|
|
2929
|
+
* - 鍵は `registeredRefKeys`(登録行側)と `stateRefKey`(ストア側)の正本を使う。チャットは
|
|
2930
|
+
* ワークスペース付き/無しの 2 本(#523)。**`dev` だけ両側を小文字に畳む**——登録簿の
|
|
2931
|
+
* owner/repo とストアの ref は人が貼った URL 由来で大小が揺れる(engine の `toolState`・
|
|
2932
|
+
* 撤去前の `toolStateWithLive` と同じ「github だけ大小を無視」)。`registeredRefKeys` /
|
|
2933
|
+
* `stateRefKey` 本体は触らない(`deriveCapTile` の篩=#523 の挙動を変えない)
|
|
2934
|
+
* - 当たった行が複数なら `at` が最新の 1 行(読めない `at` は最古・同値は後勝ち。#576 決定 4)
|
|
2935
|
+
* - 分類は `BAD_STATE_STATUSES[question]` の 1 か所: `ok` → ok/悪い語 → bad/
|
|
2936
|
+
* それ以外(`unknown`=測れなかった)→ unknown。**当たる行が無ければ `null`(未測定)**
|
|
2937
|
+
*
|
|
2938
|
+
* @param regKey `registeredNow` のキー(chat / google-chat / dev / design / materials / issues)
|
|
2939
|
+
* @param reg 登録行(`registeredNow` の 1 行。まとめ表の行は `regRowOfToolItem` で同じ形にする)
|
|
2940
|
+
* @param rows 接続状態ストアの行(`LIVE_STATE`)
|
|
2941
|
+
* @param question 見る問い(タイルは `reachable`、Home は種別の問い=`TILE_CAPS[*].question`)
|
|
2942
|
+
*
|
|
2943
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
|
|
2944
|
+
* `registeredRefKeys` / `stateRefKey` / `BAD_STATE_STATUSES` はこれより前に流し込んである)。
|
|
2945
|
+
*/
|
|
2946
|
+
function liveRowVerdict(regKey, reg, rows, question) {
|
|
2947
|
+
if (!reg || typeof reg !== "object")
|
|
2948
|
+
return null;
|
|
2949
|
+
var keys = registeredRefKeys(regKey, [reg]);
|
|
2950
|
+
if (!keys)
|
|
2951
|
+
return null;
|
|
2952
|
+
var fold = regKey === "dev";
|
|
2953
|
+
if (fold) {
|
|
2954
|
+
var folded = {};
|
|
2955
|
+
for (var k in keys)
|
|
2956
|
+
if (Object.prototype.hasOwnProperty.call(keys, k))
|
|
2957
|
+
folded[k.toLowerCase()] = true;
|
|
2958
|
+
keys = folded;
|
|
2959
|
+
}
|
|
2960
|
+
var state = Array.isArray(rows) ? rows : [];
|
|
2961
|
+
var hit = null;
|
|
2962
|
+
var hitMs = -Infinity;
|
|
2963
|
+
for (var j = 0; j < state.length; j++) {
|
|
2964
|
+
var r = state[j];
|
|
2965
|
+
if (!r || r.cap !== regKey || r.question !== question)
|
|
2966
|
+
continue;
|
|
2967
|
+
var key = stateRefKey(regKey, r.ref, r.workspace);
|
|
2968
|
+
if (fold)
|
|
2969
|
+
key = key.toLowerCase();
|
|
2970
|
+
if (!key || !keys[key])
|
|
2971
|
+
continue;
|
|
2972
|
+
// **最新の 1 行が勝つ。** 読めない at は最古(NaN は比較で常に負けるので -Infinity に畳む)。
|
|
2973
|
+
// 同値は後勝ち(`>=`)——サーバが並べた順を崩さない
|
|
2974
|
+
var ms = Date.parse(String(r.at || ""));
|
|
2975
|
+
if (ms !== ms)
|
|
2976
|
+
ms = -Infinity;
|
|
2977
|
+
if (hit === null || ms >= hitMs) {
|
|
2978
|
+
hit = r;
|
|
2979
|
+
hitMs = ms;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
if (hit === null)
|
|
2983
|
+
return null;
|
|
2984
|
+
if (hit.status === "ok")
|
|
2985
|
+
return { status: "ok", row: hit };
|
|
2986
|
+
var bad = BAD_STATE_STATUSES[question] || [];
|
|
2987
|
+
if (bad.indexOf(hit.status) >= 0)
|
|
2988
|
+
return { status: "bad", row: hit };
|
|
2989
|
+
return { status: "unknown", row: hit };
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* ライブの登録一覧から、Home の 1 種別ぶんの登録行を取り出す(#581)。
|
|
2993
|
+
*
|
|
2994
|
+
* 返すのは `{ reg, excluded }` の配列。**読めていなければ null**(配列でない=その種別の
|
|
2995
|
+
* 登録ファイルが読めなかった。「読めない」を「0 件」に化けさせない)。
|
|
2996
|
+
* GitHub は `dev` と `devExcluded` が**両方**配列のときだけ読めたと見なす——片方しか無い
|
|
2997
|
+
* 古い Lambda では、停止中(exclude)の行が `dev` に居ないので「登録から消えた」と誤読して
|
|
2998
|
+
* 消してしまう(`pendingOpLiveAgrees` と同じ倒し方・設計レビュー🔴2)。
|
|
2999
|
+
*
|
|
3000
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
3001
|
+
*/
|
|
3002
|
+
function liveRegistryOf(registered, kind) {
|
|
3003
|
+
var regKey = TOOL_KIND_REG[kind];
|
|
3004
|
+
if (!regKey || !registered || typeof registered !== "object")
|
|
3005
|
+
return null;
|
|
3006
|
+
var out = [];
|
|
3007
|
+
var i;
|
|
3008
|
+
if (kind === "github") {
|
|
3009
|
+
if (!Array.isArray(registered.dev) || !Array.isArray(registered.devExcluded))
|
|
3010
|
+
return null;
|
|
3011
|
+
for (i = 0; i < registered.dev.length; i++)
|
|
3012
|
+
if (registered.dev[i] && typeof registered.dev[i] === "object")
|
|
3013
|
+
out.push({ reg: registered.dev[i], excluded: false });
|
|
3014
|
+
for (i = 0; i < registered.devExcluded.length; i++)
|
|
3015
|
+
if (registered.devExcluded[i] && typeof registered.devExcluded[i] === "object")
|
|
3016
|
+
out.push({ reg: registered.devExcluded[i], excluded: true });
|
|
3017
|
+
return out;
|
|
3018
|
+
}
|
|
3019
|
+
var rows = registered[regKey];
|
|
3020
|
+
if (!Array.isArray(rows))
|
|
3021
|
+
return null;
|
|
3022
|
+
for (i = 0; i < rows.length; i++)
|
|
3023
|
+
if (rows[i] && typeof rows[i] === "object")
|
|
3024
|
+
out.push({ reg: rows[i], excluded: false });
|
|
3025
|
+
return out;
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* 登録行の突き合わせ鍵(Home の一覧で「同じ接続先か」を決める 1 本・#581)。
|
|
3029
|
+
*
|
|
3030
|
+
* `registeredRefKeys` の**ワークスペース無しの鍵**(チャットは `"\t" + ref`)を使う——
|
|
3031
|
+
* 消す側の突き合わせは ref だけ(`subtractRemovedRegistered` の前例。まとめ表の workspace は
|
|
3032
|
+
* engine が実測で埋めることがあり、宣言側と食い違う。誤削除は嘘・引き損ねは安全側)。
|
|
3033
|
+
* **`dev` は小文字**(`liveRowVerdict` と同じ)。鍵が組めなければ空文字。
|
|
3034
|
+
*
|
|
3035
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
3036
|
+
*/
|
|
3037
|
+
function liveToolKey(kind, reg) {
|
|
3038
|
+
var regKey = TOOL_KIND_REG[kind];
|
|
3039
|
+
if (!regKey || !reg || typeof reg !== "object")
|
|
3040
|
+
return "";
|
|
3041
|
+
var keys = registeredRefKeys(regKey, [reg]);
|
|
3042
|
+
if (!keys)
|
|
3043
|
+
return "";
|
|
3044
|
+
var key = "";
|
|
3045
|
+
if (regKey === "chat")
|
|
3046
|
+
key = "\t" + String(reg.ref == null ? "" : reg.ref).trim();
|
|
3047
|
+
else
|
|
3048
|
+
for (var k in keys)
|
|
3049
|
+
if (Object.prototype.hasOwnProperty.call(keys, k)) {
|
|
3050
|
+
key = k;
|
|
3051
|
+
break;
|
|
3052
|
+
}
|
|
3053
|
+
if (!keys[key])
|
|
3054
|
+
return "";
|
|
3055
|
+
return regKey === "dev" ? key.toLowerCase() : key;
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Home の「使用ツール」に出す行を、**操作タブと同じ 3 つの材料**から作る(#581)。
|
|
3059
|
+
*
|
|
3060
|
+
* ## 何を直したか
|
|
3061
|
+
*
|
|
3062
|
+
* まとめ表(`SITE.tools`・fleet-status.json)は案件リポの `fleet-status.yml` が**毎朝 08:30 JST
|
|
3063
|
+
* の schedule でしか再生成しない**ので、日中に操作タブで外した接続先(demo-sushiro)が
|
|
3064
|
+
* 翌朝まで Home に残り、足した接続先は翌朝まで出なかった。**登録簿が読めた種別は登録簿が正**
|
|
3065
|
+
* (消えた行は消す・無い行は足す)、状態はストアの行(`liveRowVerdict`=タイルと同じ判定)。
|
|
3066
|
+
*
|
|
3067
|
+
* ## 規則(上から順)
|
|
3068
|
+
*
|
|
3069
|
+
* 1. `registered` が null(Lambda が古い・失敗・intake 無し)→ まとめ表の行をそのまま。
|
|
3070
|
+
* **消さない・足さない**(読めなかったことを「登録が無い」にしない)。状態だけは
|
|
3071
|
+
* 同じ `liveRowVerdict` で出す(`regRowOfToolItem` で登録行の形に写す)
|
|
3072
|
+
* 2. 登録簿を持たない種別(google-meet)はまとめ表の行をそのまま
|
|
3073
|
+
* 3. 種別の登録簿が読めた(`liveRegistryOf` が配列)→ まとめ表の行は登録簿に当たるものだけ残す
|
|
3074
|
+
* (並びはまとめ表の順)。登録簿にしか無い行はその種別の最後の行の後ろに登録簿の順で足す
|
|
3075
|
+
* (まとめ表にその種別が無ければ末尾)。同じ鍵の行は 1 件に畳む(GitHub の issues /
|
|
3076
|
+
* discussions の 2 行)。読めなかった種別は規則 1 と同じ
|
|
3077
|
+
* 4. 足す行: `label` は同じ種別のまとめ表の行の語、無ければ `TOOL_KIND_LABELS`。`name` は
|
|
3078
|
+
* 登録簿の name(Slack は `#` を補う・engine と同じ)/GitHub は ref/Backlog は projectKey/
|
|
3079
|
+
* Drive は name か「共有資料フォルダ」。`url` は操作タブが既に組んでいる形を再利用できる
|
|
3080
|
+
* 種別だけ(GitHub・Backlog・Figma・Drive)。Slack・Google Chat は null(ワークスペースの
|
|
3081
|
+
* ドメインが登録簿に無い)。`cap` / `question` は `TILE_CAPS`(engine の `TOOL_STORE_KEYS` と同じ表)
|
|
3082
|
+
* 5. 状態: GitHub は `devExcluded` 所属なら `stopped`(まとめ表の `stopped` は引き継がない——
|
|
3083
|
+
* 日中に読み取りを再開した repo を翌朝まで「停止中」と言わない)。Drive はまとめ表の
|
|
3084
|
+
* `stopped`(`enabled: false`)を引き継ぐ(登録簿に enabled が無い)。それ以外は
|
|
3085
|
+
* `liveRowVerdict`: ok → `reading`/bad → `waiting`/unknown → `unknown`。行が無ければ、
|
|
3086
|
+
* まとめ表の行は**ビルド時の state**(「不明」に化けさせない)、登録簿にしか無い行は
|
|
3087
|
+
* `pending`(「登録状況を確認中です」・操作タブの行と同じ語)
|
|
3088
|
+
*
|
|
3089
|
+
* @param tools `SITE.tools`(`{ version, items }`)
|
|
3090
|
+
* @param registered `LIVE_REGISTERED`(仮置きを重ねた後のもの。null なら読めていない)
|
|
3091
|
+
* @param rows `LIVE_STATE`
|
|
3092
|
+
* @returns 描く行(まとめ表の行の形+ `state`。足した行は `live: true`)
|
|
3093
|
+
*
|
|
3094
|
+
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
|
|
3095
|
+
* `TOOL_KIND_REG` / `TOOL_KIND_LABELS` / `TOOL_URL_SAFE` / `TILE_CAPS` と上の 4 関数は
|
|
3096
|
+
* これより前に流し込む)。
|
|
3097
|
+
*/
|
|
3098
|
+
function liveToolItems(tools, registered, rows) {
|
|
3099
|
+
var items = tools && Array.isArray(tools.items) ? tools.items : [];
|
|
3100
|
+
var reg = registered && typeof registered === "object" && !Array.isArray(registered) ? registered : null;
|
|
3101
|
+
var questionOf = function (regKey) {
|
|
3102
|
+
for (var cap in TILE_CAPS) {
|
|
3103
|
+
if (Object.prototype.hasOwnProperty.call(TILE_CAPS, cap) && TILE_CAPS[cap].id === regKey)
|
|
3104
|
+
return TILE_CAPS[cap].question;
|
|
3105
|
+
}
|
|
3106
|
+
return "reachable";
|
|
3107
|
+
};
|
|
3108
|
+
var stateOf = function (regKey, regRow, fallback) {
|
|
3109
|
+
var v = liveRowVerdict(regKey, regRow, rows, questionOf(regKey));
|
|
3110
|
+
if (!v)
|
|
3111
|
+
return fallback;
|
|
3112
|
+
if (v.status === "ok")
|
|
3113
|
+
return "reading";
|
|
3114
|
+
if (v.status === "bad")
|
|
3115
|
+
return "waiting";
|
|
3116
|
+
return "unknown";
|
|
3117
|
+
};
|
|
3118
|
+
var out = [];
|
|
3119
|
+
var kept = {};
|
|
3120
|
+
var labels = {};
|
|
3121
|
+
var i;
|
|
3122
|
+
for (i = 0; i < items.length; i++) {
|
|
3123
|
+
var it = items[i];
|
|
3124
|
+
if (!it)
|
|
3125
|
+
continue;
|
|
3126
|
+
var kind = String(it.kind == null ? "" : it.kind);
|
|
3127
|
+
var regKey = TOOL_KIND_REG[kind] || "";
|
|
3128
|
+
if (it.label && !labels[kind])
|
|
3129
|
+
labels[kind] = String(it.label);
|
|
3130
|
+
var live = reg ? liveRegistryOf(reg, kind) : null;
|
|
3131
|
+
var stopped = it.state === "stopped";
|
|
3132
|
+
var regRow = regRowOfToolItem(it);
|
|
3133
|
+
// ストアに行が無いときの値(ビルド時の state)
|
|
3134
|
+
var fallback = String(it.state || "unknown");
|
|
3135
|
+
var key = live ? liveToolKey(kind, regRow) : "";
|
|
3136
|
+
// **鍵が組めないまとめ表の行は消さない**(分からないものは落とさない・`registeredRefKeys` と同じ倒し方)
|
|
3137
|
+
if (live && key) {
|
|
3138
|
+
// 登録簿が正。当たる行が無ければ消えた(規則 3)
|
|
3139
|
+
var match = null;
|
|
3140
|
+
for (var m = 0; m < live.length; m++) {
|
|
3141
|
+
if (liveToolKey(kind, live[m].reg) === key) {
|
|
3142
|
+
match = live[m];
|
|
3143
|
+
break;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
if (!match)
|
|
3147
|
+
continue;
|
|
3148
|
+
if (!kept[kind])
|
|
3149
|
+
kept[kind] = {};
|
|
3150
|
+
kept[kind][key] = true;
|
|
3151
|
+
// 停止中の正本(規則 5): GitHub は登録簿、Drive はまとめ表、他の種別に停止中は無い。
|
|
3152
|
+
// 🔴 **読み取りを再開した repo(まとめ表は stopped・登録簿は dev)は、ストアに行が無くても
|
|
3153
|
+
// 「停止中」に戻さない**——engine が stopped でない行に置くビルド時の値は unknown(実装レビュー🔴1)
|
|
3154
|
+
if (kind === "github") {
|
|
3155
|
+
stopped = match.excluded;
|
|
3156
|
+
if (!stopped && fallback === "stopped")
|
|
3157
|
+
fallback = "unknown";
|
|
3158
|
+
}
|
|
3159
|
+
regRow = Object.assign({}, match.reg);
|
|
3160
|
+
// まとめ表の Slack 行が持つワークスペースを登録行が持たなければ補う(実装レビュー🟡3)。
|
|
3161
|
+
// 登録簿は宣言に workspace があるときだけ載せるので、無いままだとワークスペース付きの
|
|
3162
|
+
// ストア行に当たらず、ライブが届いた瞬間に「読み取り中」が「未確認」へ退化する
|
|
3163
|
+
if (kind === "slack" && !regRow.workspace && it.workspace)
|
|
3164
|
+
regRow.workspace = String(it.workspace);
|
|
3165
|
+
}
|
|
3166
|
+
// 登録簿を持たない種別(google-meet)も、ストアの行(cap=meetings / ingested)は同じ判定で重ねる
|
|
3167
|
+
var stateKey = regKey || String(it.cap == null ? "" : it.cap);
|
|
3168
|
+
var st = stopped ? "stopped" : !stateKey ? fallback : stateOf(stateKey, regRow, fallback);
|
|
3169
|
+
out.push(Object.assign({}, it, { state: st }));
|
|
3170
|
+
}
|
|
3171
|
+
if (!reg)
|
|
3172
|
+
return out;
|
|
3173
|
+
// 登録簿にしか無い行を、種別ごとにまとめ表の最後の行の後ろへ(規則 3〜4)
|
|
3174
|
+
var kinds = ["backlog", "github", "slack", "google-chat", "figma", "google-drive"];
|
|
3175
|
+
for (var ki = 0; ki < kinds.length; ki++) {
|
|
3176
|
+
var k2 = kinds[ki];
|
|
3177
|
+
var live2 = liveRegistryOf(reg, k2);
|
|
3178
|
+
if (!live2)
|
|
3179
|
+
continue;
|
|
3180
|
+
var regKey2 = TOOL_KIND_REG[k2];
|
|
3181
|
+
var seen = kept[k2] || {};
|
|
3182
|
+
var added = [];
|
|
3183
|
+
for (var a = 0; a < live2.length; a++) {
|
|
3184
|
+
var r2 = live2[a].reg;
|
|
3185
|
+
var key2 = liveToolKey(k2, r2);
|
|
3186
|
+
if (!key2 || seen[key2])
|
|
3187
|
+
continue;
|
|
3188
|
+
seen[key2] = true;
|
|
3189
|
+
var ref = "";
|
|
3190
|
+
var name = "";
|
|
3191
|
+
var url = null;
|
|
3192
|
+
if (k2 === "slack") {
|
|
3193
|
+
ref = String(r2.ref).trim();
|
|
3194
|
+
var raw = String(r2.name || ref).trim();
|
|
3195
|
+
name = raw.charAt(0) === "#" ? raw : "#" + raw;
|
|
3196
|
+
}
|
|
3197
|
+
else if (k2 === "google-chat") {
|
|
3198
|
+
ref = String(r2.ref).trim();
|
|
3199
|
+
name = String(r2.name || ref).trim();
|
|
3200
|
+
}
|
|
3201
|
+
else if (k2 === "github") {
|
|
3202
|
+
ref = String(r2.ref).trim();
|
|
3203
|
+
name = ref;
|
|
3204
|
+
if (TOOL_URL_SAFE.test(ref) && ref.split("/").length === 2)
|
|
3205
|
+
url = "https://github.com/" + ref;
|
|
3206
|
+
}
|
|
3207
|
+
else if (k2 === "figma") {
|
|
3208
|
+
ref = String(r2.key).trim();
|
|
3209
|
+
name = String(r2.name || ref).trim();
|
|
3210
|
+
if (TOOL_URL_SAFE.test(ref) && ref.indexOf("/") < 0)
|
|
3211
|
+
url = "https://www.figma.com/design/" + ref;
|
|
3212
|
+
}
|
|
3213
|
+
else if (k2 === "google-drive") {
|
|
3214
|
+
ref = String(r2.id).trim();
|
|
3215
|
+
name = String(r2.name || "").trim() || "共有資料フォルダ";
|
|
3216
|
+
if (TOOL_URL_SAFE.test(ref) && ref.indexOf("/") < 0)
|
|
3217
|
+
url = "https://drive.google.com/drive/folders/" + ref;
|
|
3218
|
+
}
|
|
3219
|
+
else {
|
|
3220
|
+
var domain = String(r2.domain).trim();
|
|
3221
|
+
var pk = String(r2.projectKey).trim();
|
|
3222
|
+
ref = issuesRefOf(domain, pk);
|
|
3223
|
+
name = pk;
|
|
3224
|
+
if (TOOL_URL_SAFE.test(domain) && domain.indexOf("/") < 0 && TOOL_URL_SAFE.test(pk) && pk.indexOf("/") < 0)
|
|
3225
|
+
url = "https://" + domain + "/projects/" + pk;
|
|
3226
|
+
}
|
|
3227
|
+
added.push({
|
|
3228
|
+
kind: k2,
|
|
3229
|
+
label: labels[k2] || TOOL_KIND_LABELS[k2] || k2,
|
|
3230
|
+
name: name,
|
|
3231
|
+
url: url,
|
|
3232
|
+
note: "",
|
|
3233
|
+
cap: regKey2,
|
|
3234
|
+
question: questionOf(regKey2),
|
|
3235
|
+
ref: ref,
|
|
3236
|
+
workspace: k2 === "slack" ? String(r2.workspace == null ? "" : r2.workspace) : "",
|
|
3237
|
+
state: live2[a].excluded ? "stopped" : stateOf(regKey2, r2, "pending"),
|
|
3238
|
+
live: true,
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
if (!added.length)
|
|
3242
|
+
continue;
|
|
3243
|
+
var at = -1;
|
|
3244
|
+
for (var o = 0; o < out.length; o++)
|
|
3245
|
+
if (out[o].kind === k2)
|
|
3246
|
+
at = o;
|
|
3247
|
+
if (at < 0)
|
|
3248
|
+
out = out.concat(added);
|
|
3249
|
+
else
|
|
3250
|
+
out = out.slice(0, at + 1).concat(added, out.slice(at + 1));
|
|
3251
|
+
}
|
|
3252
|
+
return out;
|
|
3253
|
+
}
|
|
2782
3254
|
function capabilityState(cap, ctx, adapter) {
|
|
2783
3255
|
// **能力→パイプラインの対応はここに持つ。** 呼び出し側に渡させると、
|
|
2784
3256
|
// クライアント側(この関数を .toString() で受け取る側)が同じ表を書き写すことになり、
|
|
@@ -4719,6 +5191,8 @@ ${sanitizeRegisteredNow.toString()}
|
|
|
4719
5191
|
${chatRowsToAdd.toString()}
|
|
4720
5192
|
${gchatRowsToAdd.toString()}
|
|
4721
5193
|
${overlayGchatNotify.toString()}
|
|
5194
|
+
${overlaySlackNotify.toString()}
|
|
5195
|
+
${overlayNotifyAuto.toString()}
|
|
4722
5196
|
${devRowsToAdd.toString()}
|
|
4723
5197
|
${markExcludedPendingRows.toString()}
|
|
4724
5198
|
${designRowsToAdd.toString()}
|
|
@@ -4777,7 +5251,6 @@ ${overlayStateOnFleet.toString()}
|
|
|
4777
5251
|
// 🔴 この定数行を落とすと、流し込んだ描画がアイコンを引けずに素の kind を出す
|
|
4778
5252
|
var TOOL_KIND_ICONS = ${JSON.stringify(TOOL_KIND_ICONS)};
|
|
4779
5253
|
${toolStateLabel.toString()}
|
|
4780
|
-
${toolStateWithLive.toString()}
|
|
4781
5254
|
// 畳んだ種別の 1 行に出す状態の要約(#424)。**同じ 4 語を使う**——ここで別の言い方を
|
|
4782
5255
|
// 作ると、畳んだ 1 行と開いた行が違う語で同じ状態を指す
|
|
4783
5256
|
${toolsFoldSummary.toString()}
|
|
@@ -4816,6 +5289,18 @@ ${deriveCapTile.toString()}
|
|
|
4816
5289
|
// 登録直後のタイル(#576)。**registeredRefKeys / stateRefKey / BAD_STATE_STATUSES より後に置く**
|
|
4817
5290
|
//(上の 3 つを引く。前に置くと var の BAD_STATE_STATUSES が undefined で判定が丸ごと死ぬ)
|
|
4818
5291
|
${derivePendingTile.toString()}
|
|
5292
|
+
// Home の「使用ツール」をライブの登録一覧+ストアで描く(#581)。**行ごとの判定
|
|
5293
|
+
// (liveRowVerdict)はタイル(derivePendingTile)と共有**——registeredRefKeys / stateRefKey /
|
|
5294
|
+
// BAD_STATE_STATUSES / TILE_CAPS より後に置く。🔴 下の 3 つの定数行を落とすと、流し込んだ
|
|
5295
|
+
// liveToolItems が未定義を参照して Home の描画が丸ごと死ぬ
|
|
5296
|
+
var TOOL_KIND_REG = ${JSON.stringify(TOOL_KIND_REG)};
|
|
5297
|
+
var TOOL_KIND_LABELS = ${JSON.stringify(TOOL_KIND_LABELS)};
|
|
5298
|
+
var TOOL_URL_SAFE = new RegExp(${JSON.stringify(TOOL_URL_SAFE.source)});
|
|
5299
|
+
${regRowOfToolItem.toString()}
|
|
5300
|
+
${liveRowVerdict.toString()}
|
|
5301
|
+
${liveRegistryOf.toString()}
|
|
5302
|
+
${liveToolKey.toString()}
|
|
5303
|
+
${liveToolItems.toString()}
|
|
4819
5304
|
// 案件カレンダー(#428)。**判定の実体はビルド時の定義**——画面側に書き写すと、
|
|
4820
5305
|
// 「作成済みか」の判定が 2 枚になって片方だけ直る(=2 本目のカレンダーを作らせる)。
|
|
4821
5306
|
// 🔴 **BAD_STATE_STATUSES より後に置く。** calendarPhase が「悪い」語をそこから引く
|
|
@@ -6315,13 +6800,78 @@ ${calendarAddUrl.toString()}
|
|
|
6315
6800
|
}
|
|
6316
6801
|
|
|
6317
6802
|
/**
|
|
6318
|
-
* Home
|
|
6803
|
+
* Home の「使用ツール」に、ライブの登録一覧(「LIVE_REGISTERED」)へ仮置き(#388)を重ねた
|
|
6804
|
+
* 登録簿を渡す(#581)。「pendingTileVerdict」 と同じ材料を見る:
|
|
6805
|
+
*
|
|
6806
|
+
* - **remove で live に在る行は外す・add で live に無い行は足す。** Lambda の 「registeredNow」
|
|
6807
|
+
* は据え置き(保存直後の 1 回だけ迂回)なので、外した直後に Home を再読み込みすると
|
|
6808
|
+
* 据え置きの古い一覧を読む窓がある——仮置きを重ねないと「操作タブでは消えたのに Home には
|
|
6809
|
+
* まだ在る」が最大 1 分並ぶ
|
|
6810
|
+
* - 足す行は 「pendingExternalRow」(Slack のワークスペースは 「slackOrg」 から展開される)。
|
|
6811
|
+
* figma / drive / backlog は payload から登録行の形に組む
|
|
6812
|
+
* - update(通知先トグル等)は行の有無に関わらないので何もしない
|
|
6813
|
+
* - 「LIVE_REGISTERED」 が null なら null のまま(仮置きだけで一覧を作らない・規則 1)
|
|
6814
|
+
*
|
|
6815
|
+
* 既知の限界(「pendingTileVerdict」 と同じ): GitHub の remove は 「targetId」 が小文字で
|
|
6816
|
+
* 「pendingLiveRowMatches」 は生値一致なので、混在綴りの repo は据え置きの窓のあいだ消えない
|
|
6817
|
+
*/
|
|
6818
|
+
function registryForHome() {
|
|
6819
|
+
if (!LIVE_REGISTERED) return null;
|
|
6820
|
+
var ops = pendingOpsNow();
|
|
6821
|
+
if (!ops.length) return LIVE_REGISTERED;
|
|
6822
|
+
var reg = Object.assign({}, LIVE_REGISTERED);
|
|
6823
|
+
for (var i = 0; i < ops.length; i++) {
|
|
6824
|
+
var e = ops[i];
|
|
6825
|
+
var km = e && PENDING_KINDS[e.kind];
|
|
6826
|
+
if (!km || !Array.isArray(reg[km.reg])) continue;
|
|
6827
|
+
var rows = reg[km.reg];
|
|
6828
|
+
var found = null;
|
|
6829
|
+
for (var k = 0; k < rows.length; k++) {
|
|
6830
|
+
if (pendingLiveRowMatches(e.kind, rows[k], e.targetId)) { found = rows[k]; break; }
|
|
6831
|
+
}
|
|
6832
|
+
// 停止中(devExcluded)の GitHub 行を「登録から削除」した直後は、その行は dev ではなく
|
|
6833
|
+
// devExcluded に居る。こちらも外さないと据え置きの窓のあいだ「停止中」が残る(実装レビュー🟡4)
|
|
6834
|
+
if (e.action === "remove" && e.kind === "github" && Array.isArray(reg.devExcluded)) {
|
|
6835
|
+
reg.devExcluded = reg.devExcluded.filter(function (r) { return !pendingLiveRowMatches(e.kind, r, e.targetId); });
|
|
6836
|
+
}
|
|
6837
|
+
if (e.action === "remove" && found) {
|
|
6838
|
+
reg[km.reg] = rows.filter(function (r) { return r !== found; });
|
|
6839
|
+
} else if (e.action === "add" && !found) {
|
|
6840
|
+
var p = e.payload || {};
|
|
6841
|
+
var row;
|
|
6842
|
+
if (e.kind === "figma") row = { key: e.targetId, name: p.name || "" };
|
|
6843
|
+
else if (e.kind === "drive") row = { id: e.targetId, name: p.name || "" };
|
|
6844
|
+
else if (e.kind === "backlog") row = { domain: p.domain || "", projectKey: p.projectKey || "" };
|
|
6845
|
+
else row = pendingExternalRow(e);
|
|
6846
|
+
reg[km.reg] = rows.concat([row]);
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
return reg;
|
|
6850
|
+
}
|
|
6851
|
+
|
|
6852
|
+
/**
|
|
6853
|
+
* いま Home に出す「使用ツール」の行(#581)。**判定は 「liveToolItems」 の 1 か所**——
|
|
6854
|
+
* まとめ表(「SITE.tools」)を初期表示に、ライブの登録一覧(仮置き込み)と接続状態ストアが
|
|
6855
|
+
* 届いていれば、消えた行は消し・足した行は足し・状態はタイルと同じ行ごとの判定で出す。
|
|
6856
|
+
* 届く前(先読みが飛行中・intake 無し)は 「LIVE_REGISTERED」 が null なので、まとめ表の
|
|
6857
|
+
* 行がそのまま出る
|
|
6858
|
+
*/
|
|
6859
|
+
function homeToolsItemsNow() {
|
|
6860
|
+
return liveToolItems(SITE.tools, registryForHome(), LIVE_STATE);
|
|
6861
|
+
}
|
|
6862
|
+
|
|
6863
|
+
/**
|
|
6864
|
+
* Home の「使用ツール」(#275)。engine が登録簿 7 種から算出した SITE.tools を初期表示に、
|
|
6865
|
+
* ライブの登録一覧+接続状態ストア(「homeToolsItemsNow」・#581)で描く。
|
|
6319
6866
|
*
|
|
6320
6867
|
* 🔴 **SITE.tools が無ければ何も出さない。** 生成が古い案件(この項目を知らない
|
|
6321
6868
|
* fleet-status.json)では、手書きの節が本文に残っているので、そちらがそのまま出る
|
|
6322
6869
|
* ——ここで「まだ接続していません」と出すと、繋いである案件に嘘を出すことになる。
|
|
6870
|
+
* ライブだけで節を出すことはしない(手書きの節との重複回避 「dropLegacyHomeSections」 と
|
|
6871
|
+
* 衝突する)。効くのは「SITE.tools はあるが古い」案件
|
|
6323
6872
|
*
|
|
6324
|
-
* **0 件は 1 行で言い切る。**
|
|
6873
|
+
* **0 件は 1 行で言い切る。** 空の一覧を出すと「読み込み中」に見える。ライブで全部消えて
|
|
6874
|
+
* 0 件になった場合も同じ 1 行
|
|
6325
6875
|
*/
|
|
6326
6876
|
function homeToolsSection() {
|
|
6327
6877
|
var t = SITE.tools;
|
|
@@ -6330,7 +6880,7 @@ ${calendarAddUrl.toString()}
|
|
|
6330
6880
|
if (!t || !Array.isArray(t.items)) return null;
|
|
6331
6881
|
var box = el("section", { class: "home-tools" });
|
|
6332
6882
|
box.appendChild(el("h2", { class: "home-grounding-title", text: "使用ツール" }));
|
|
6333
|
-
var items =
|
|
6883
|
+
var items = homeToolsItemsNow();
|
|
6334
6884
|
if (!items.length) {
|
|
6335
6885
|
// 「操作タブ」はその場で移れる口にする(文言は変えない・#424)
|
|
6336
6886
|
box.appendChild(el("p", { class: "home-tools-empty" }, [
|
|
@@ -6368,12 +6918,12 @@ ${calendarAddUrl.toString()}
|
|
|
6368
6918
|
if (!ul) { ul = el("ul", { class: "home-tools-list" }); box.appendChild(ul); }
|
|
6369
6919
|
g.items.forEach(function (it) { ul.appendChild(homeToolRow(it)); });
|
|
6370
6920
|
});
|
|
6371
|
-
//
|
|
6372
|
-
//
|
|
6921
|
+
// **どこで直せるかを 1 行で言う。** 「(この一覧は毎朝更新されます)」は #581 で落とした
|
|
6922
|
+
// ——一覧はライブの登録一覧で描くので、操作タブで足した・外した行はその場で出る・消える
|
|
6373
6923
|
box.appendChild(el("p", { class: "home-tools-note" }, [
|
|
6374
6924
|
document.createTextNode("接続の追加・変更は"),
|
|
6375
6925
|
opsTabLink("操作タブ"),
|
|
6376
|
-
document.createTextNode("
|
|
6926
|
+
document.createTextNode("で行います。"),
|
|
6377
6927
|
]));
|
|
6378
6928
|
return box;
|
|
6379
6929
|
}
|
|
@@ -6389,13 +6939,15 @@ ${calendarAddUrl.toString()}
|
|
|
6389
6939
|
text: (TOOL_KIND_ICONS[it.kind] || "🔗") + " " + (it.label || it.kind),
|
|
6390
6940
|
}));
|
|
6391
6941
|
var name = it.name || it.ref || "";
|
|
6392
|
-
//
|
|
6393
|
-
//
|
|
6942
|
+
// **開ける先が無い行はリンクにしない。** 押せないリンクを出すより素のテキストのほうが
|
|
6943
|
+
// 正直(url は engine が null で「無い」と言い切っている)。Google Meet の行も
|
|
6944
|
+
// 案件カレンダーができていれば url を持つ(cortex-engine #343。カレンダーがまだ無い案件は null)
|
|
6394
6945
|
// href に載せる前に isHttp を通す(build 側の許可リストと二重の守り。el() の注意書きのとおり)
|
|
6395
6946
|
if (it.url && isHttp(it.url)) li.appendChild(el("a", { class: "home-tools-name", href: it.url, target: "_blank", rel: "noopener", text: name }));
|
|
6396
6947
|
else li.appendChild(el("span", { class: "home-tools-name", text: name }));
|
|
6397
|
-
//
|
|
6398
|
-
|
|
6948
|
+
// 状態は 「liveToolItems」 が決めてある(#581)。**ここで判定し直さない**——タイルと同じ
|
|
6949
|
+
// 行ごとの判定(liveRowVerdict)から出た値を語に変えるだけ
|
|
6950
|
+
var st = String(it.state || "unknown");
|
|
6399
6951
|
var lab = toolStateLabel(st);
|
|
6400
6952
|
var chip = el("span", { class: "home-tools-state ds-gate " + lab.cls, text: lab.text });
|
|
6401
6953
|
// 状態そのものを属性に残す(表示語を読み替えずに検査・スタイルできるように)
|
|
@@ -6430,8 +6982,8 @@ ${calendarAddUrl.toString()}
|
|
|
6430
6982
|
// ⚠ **id に使うので、種別の字をそのまま持ち込まない。** kind は fleet-status.json 由来で、
|
|
6431
6983
|
// 空白の入った値が来ると id が2つの idref に読まれ、aria-controls の対応が壊れる
|
|
6432
6984
|
var key = (String(kind).replace(/[^A-Za-z0-9_-]/g, "") || "other") + "-" + seq;
|
|
6433
|
-
//
|
|
6434
|
-
var sum = toolsFoldSummary(group.map(function (it) { return
|
|
6985
|
+
// 状態は行に決めてあるものを畳む(行と要約が別の判定を持たない・#581)
|
|
6986
|
+
var sum = toolsFoldSummary(group.map(function (it) { return String(it.state || "unknown"); }));
|
|
6435
6987
|
var chip = el("span", { class: "home-tools-fold-state ds-gate " + sum.cls, text: sum.text });
|
|
6436
6988
|
chip.setAttribute("data-state", sum.state);
|
|
6437
6989
|
// 説明として読ませるので id が要る
|
|
@@ -10814,8 +11366,19 @@ ${calendarAddUrl.toString()}
|
|
|
10814
11366
|
* 「通知先」の札。**組み立てる場所を1つにする**——下のトグルで後から付け外しするので、
|
|
10815
11367
|
* 描画時と押した後で別々に作ると、押した行だけ見た目の違う札が並ぶ。
|
|
10816
11368
|
*/
|
|
10817
|
-
|
|
10818
|
-
|
|
11369
|
+
/**
|
|
11370
|
+
* @param auto 接続が確認できたので**自動で**通知先になった行か(#589)。
|
|
11371
|
+
* 語を増やしすぎない——札は「通知先(自動で設定)」の1つで、外せることと
|
|
11372
|
+
* 「外したら戻さない」は hover の説明に置く(行に長い定型文を入れない)。
|
|
11373
|
+
* **押して通知先にした行には付けない**(トグルが作る札は常に素の「通知先」)
|
|
11374
|
+
*/
|
|
11375
|
+
function notifyBadge(auto) {
|
|
11376
|
+
if (auto !== true) return el("span", { class: "badge ds-notify", text: "通知先" });
|
|
11377
|
+
return el("span", {
|
|
11378
|
+
class: "badge ds-notify",
|
|
11379
|
+
text: "通知先(自動で設定)",
|
|
11380
|
+
title: "接続が確認できたので、自動でこのチャンネルを通知先にしました。外すには「通知を止める」を押してください(外したら自動では戻しません)。",
|
|
11381
|
+
});
|
|
10819
11382
|
}
|
|
10820
11383
|
|
|
10821
11384
|
/**
|
|
@@ -10859,10 +11422,13 @@ ${calendarAddUrl.toString()}
|
|
|
10859
11422
|
// 行の元データも合わせる。ライブの読み直しで同じ行が描き直されるまでの間、
|
|
10860
11423
|
// ここだけが「いまどちらか」を持っている
|
|
10861
11424
|
s.notify = next;
|
|
11425
|
+
// **押した時点で「自動で設定」ではなくなる(#589)。** 人が入り切りした行に
|
|
11426
|
+
// 「自動で設定」と出したままだと、外して押し直した行が自動で戻ったように見える
|
|
11427
|
+
s.notifyAuto = false;
|
|
10862
11428
|
btn.textContent = labelOf();
|
|
10863
11429
|
var badge = badges.querySelector(".ds-notify");
|
|
10864
11430
|
// 並びは描画時と同じ(札の先頭)。後ろに足すと、押した行だけ順番が変わる
|
|
10865
|
-
if (on && !badge) badges.insertBefore(notifyBadge(), badges.firstChild);
|
|
11431
|
+
if (on && !badge) badges.insertBefore(notifyBadge(false), badges.firstChild);
|
|
10866
11432
|
if (!on && badge) badges.removeChild(badge);
|
|
10867
11433
|
});
|
|
10868
11434
|
});
|
|
@@ -13368,7 +13934,7 @@ ${calendarAddUrl.toString()}
|
|
|
13368
13934
|
title: "このリポジトリは読み取り対象から除外されています(開発の登録ファイル dev-sources.json の exclude)。この画面からは戻せません。",
|
|
13369
13935
|
}));
|
|
13370
13936
|
}
|
|
13371
|
-
if (s.notify === true) badges.appendChild(notifyBadge());
|
|
13937
|
+
if (s.notify === true) badges.appendChild(notifyBadge(s.notifyAuto === true));
|
|
13372
13938
|
// **どのワークスペースのチャンネルかを、必ず行に出す(#104・2026-08-20 に社内も)。**
|
|
13373
13939
|
//
|
|
13374
13940
|
// 折りたたみの見出しでまとめるのをやめた(オーナー指摘「複雑になっちゃってる」)ので、
|
package/package.json
CHANGED