@takagaki/cortex-decisions-viewer 0.12.30 → 0.12.32
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/google-chat-guide-images.js +33 -0
- package/dist/meet-guide-images.js +6 -1
- package/dist/pending-ops.js +140 -17
- package/dist/render.js +488 -83
- package/package.json +1 -1
package/dist/pending-ops.js
CHANGED
|
@@ -22,17 +22,20 @@ exports.PENDING_OPS_LIMITS = void 0;
|
|
|
22
22
|
exports.pendingKindsTable = pendingKindsTable;
|
|
23
23
|
exports.pendingOpsKey = pendingOpsKey;
|
|
24
24
|
exports.pendingSlackChannelId = pendingSlackChannelId;
|
|
25
|
+
exports.pendingIsSpaceRef = pendingIsSpaceRef;
|
|
25
26
|
exports.pendingGoogleChatSpaceRef = pendingGoogleChatSpaceRef;
|
|
26
27
|
exports.pendingRepo = pendingRepo;
|
|
27
28
|
exports.pendingFigmaKey = pendingFigmaKey;
|
|
28
29
|
exports.pendingDriveFolderId = pendingDriveFolderId;
|
|
29
30
|
exports.backlogTargetOf = backlogTargetOf;
|
|
31
|
+
exports.pendingIsSlackTeamId = pendingIsSlackTeamId;
|
|
30
32
|
exports.pendingIssuesRef = pendingIssuesRef;
|
|
31
33
|
exports.pendingLabel = pendingLabel;
|
|
32
34
|
exports.pendingOpOf = pendingOpOf;
|
|
33
35
|
exports.sanitizePendingOps = sanitizePendingOps;
|
|
34
36
|
exports.rememberPendingOp = rememberPendingOp;
|
|
35
37
|
exports.pendingSiteTimeMs = pendingSiteTimeMs;
|
|
38
|
+
exports.pendingStale = pendingStale;
|
|
36
39
|
exports.pendingOpBuildCaughtUp = pendingOpBuildCaughtUp;
|
|
37
40
|
exports.pendingLiveRowMatches = pendingLiveRowMatches;
|
|
38
41
|
exports.pendingOpLiveAgrees = pendingOpLiveAgrees;
|
|
@@ -101,24 +104,42 @@ function pendingSlackChannelId(input) {
|
|
|
101
104
|
if (/^[CG][A-Z0-9]{6,}$/.test(s))
|
|
102
105
|
return s;
|
|
103
106
|
const m = s.match(/\/archives\/([CG][A-Z0-9]{6,})/);
|
|
104
|
-
|
|
107
|
+
if (m)
|
|
108
|
+
return m[1];
|
|
109
|
+
// ブラウザのアドレスバーの形(#398)。**team_id は使わない**——どのワークスペースかは
|
|
110
|
+
// サーバが実測して決める。ここで取るのはチャンネルIDだけ
|
|
111
|
+
// (team_id の位置の形は shared/secret-names.mjs の isSlackTeamId と同じ。値は採らない)
|
|
112
|
+
const client = s.match(/^https?:\/\/app\.slack\.com\/client\/[TE][A-Z0-9]{6,20}\/([CG][A-Z0-9]{6,})/);
|
|
113
|
+
return client ? client[1] : null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* スペース ref の形(サーバの `GOOGLE_CHAT_SPACE_REF` の写し)。ずれは parity テストが固定する。
|
|
117
|
+
*
|
|
118
|
+
* **写しがこのファイルに 2 つあったのを 1 つに寄せた(#398)。** ビューアは infra を
|
|
119
|
+
* import できないので写し自体は無くせないが、**同じ判定を 2 度書かない**。
|
|
120
|
+
*/
|
|
121
|
+
function pendingIsSpaceRef(v) {
|
|
122
|
+
return /^spaces\/[A-Za-z0-9_-]+$/.test(String(v == null ? "" : v));
|
|
105
123
|
}
|
|
106
124
|
function pendingGoogleChatSpaceRef(input) {
|
|
107
125
|
const s = String(input == null ? "" : input).trim();
|
|
108
126
|
if (!s || s.length > 500)
|
|
109
127
|
return null;
|
|
128
|
+
// 素の `spaces/<ID>`(#389)。URL ではないのでホスト検査より前に見る
|
|
129
|
+
if (pendingIsSpaceRef(s))
|
|
130
|
+
return s;
|
|
110
131
|
if (!/^https?:\/\/(?:chat|mail)\.google\.com\//i.test(s))
|
|
111
132
|
return null;
|
|
112
|
-
if (/^https?:\/\/chat\.google\.com\/(?:u\/\d+\/)?dm\//i.test(s) ||
|
|
133
|
+
if (/^https?:\/\/chat\.google\.com\/(?:u\/\d+\/)?(?:dm|app\/dm)\//i.test(s) ||
|
|
113
134
|
/^https?:\/\/mail\.google\.com\/[^#]*#chat\/dm\//i.test(s)) {
|
|
114
135
|
return null;
|
|
115
136
|
}
|
|
116
|
-
const m = s.match(/^https?:\/\/chat\.google\.com\/(?:u\/\d+\/)?room\/([^/?#]+)/i) ||
|
|
137
|
+
const m = s.match(/^https?:\/\/chat\.google\.com\/(?:u\/\d+\/)?(?:room|app\/(?:chat|join|management))\/([^/?#]+)/i) ||
|
|
117
138
|
s.match(/^https?:\/\/mail\.google\.com\/(?:chat|mail)\/u\/\d+\/[^#]*#chat\/space\/([^/?#]+)/i);
|
|
118
139
|
if (!m)
|
|
119
140
|
return null;
|
|
120
141
|
const ref = "spaces/" + m[1];
|
|
121
|
-
return
|
|
142
|
+
return pendingIsSpaceRef(ref) ? ref : null;
|
|
122
143
|
}
|
|
123
144
|
function pendingRepo(input) {
|
|
124
145
|
let s = String(input == null ? "" : input).trim();
|
|
@@ -171,14 +192,35 @@ function backlogTargetOf(input) {
|
|
|
171
192
|
return num.test(view[1]) ? null : { domain: domain, projectKey: view[1].toUpperCase() };
|
|
172
193
|
return null;
|
|
173
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Slack の team_id の形。**サーバの正本(shared/secret-names.mjs の `isSlackTeamId`)の写し**で、
|
|
197
|
+
* ずれは parity テストが固定する。
|
|
198
|
+
*
|
|
199
|
+
* 🔴 **狭くしないこと。** `T…` だけに絞ると **Enterprise Grid の `E…` を画面が弾き**、
|
|
200
|
+
* その組織のチャンネルだけ仮置きの行に札が出ない(サーバは受けているのに画面が捨てる形)。
|
|
201
|
+
*/
|
|
202
|
+
function pendingIsSlackTeamId(v) {
|
|
203
|
+
return /^[TE][A-Z0-9]{6,20}$/.test(String(v == null ? "" : v));
|
|
204
|
+
}
|
|
174
205
|
/** Backlog の接続先の鍵(render.ts の issuesRefOf と同形: ドメイン小文字 / キー大文字) */
|
|
175
206
|
function pendingIssuesRef(domain, projectKey) {
|
|
176
207
|
return String(domain == null ? "" : domain).toLowerCase() + "/" + String(projectKey == null ? "" : projectKey).toUpperCase();
|
|
177
208
|
}
|
|
178
209
|
// ---- エントリを作る(op → kind / action / targetId の対応表はここ 1 つ) ------------
|
|
179
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* 表示名の整形(サーバの cleanLabel の写し: 制御文字を落とし、連続空白を1つに畳み、
|
|
212
|
+
* 長すぎれば切る)。**空なら空文字**——サーバは null を返すが、画面側は `|| id` の
|
|
213
|
+
* 落とし先があるので文字列のまま扱う(parity テストは `|| null` にして突き合わせる)。
|
|
214
|
+
*
|
|
215
|
+
* 🔴 **連続空白の圧縮を落とさないこと(#391)。** サーバだけが畳むと、名前に連続空白を
|
|
216
|
+
* 含むチャンネルの update 仮置きが pendingOpLiveAgrees の名前比較で永久に同意せず、
|
|
217
|
+
* 24 時間(TTL)残り続ける。写しのずれはこの1行だけで起きる。
|
|
218
|
+
*/
|
|
180
219
|
function pendingLabel(s, max) {
|
|
181
|
-
const v = String(s == null ? "" : s)
|
|
220
|
+
const v = String(s == null ? "" : s)
|
|
221
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
222
|
+
.replace(/\s+/g, " ")
|
|
223
|
+
.trim();
|
|
182
224
|
const lim = max || 120;
|
|
183
225
|
return v.length > lim ? v.slice(0, lim) : v;
|
|
184
226
|
}
|
|
@@ -218,6 +260,15 @@ function pendingOpOf(payload, ctx) {
|
|
|
218
260
|
const data = { name: pendingLabel(payload.name) || id };
|
|
219
261
|
if (payload.workspace)
|
|
220
262
|
data.workspace = String(payload.workspace);
|
|
263
|
+
// **どの組織のチャンネルかは、サーバの実測を応答から受け取る(#391)。**
|
|
264
|
+
// 画面はワークスペースを送らない(プルダウンは 2026-08-20 に廃止)ので、
|
|
265
|
+
// これが無いと仮置きの行にだけ組織の札が出ない=本物の行と見た目が食い違う。
|
|
266
|
+
// 🔴 **文字列 1 欄で運ぶ**("unknown" / "internal" / team_id)。真偽値を増やすと
|
|
267
|
+
// sanitizePendingOps の許可リストの例外が増え、篩が篩の意味を失う。
|
|
268
|
+
// 応答に無ければ載せない——載せないことが「まだ分からない」の表明になる
|
|
269
|
+
const org = ctx.response && typeof ctx.response.slackOrg === "string" ? ctx.response.slackOrg : "";
|
|
270
|
+
if (org)
|
|
271
|
+
data.slackOrg = org;
|
|
221
272
|
return make("slack", "add", id, data);
|
|
222
273
|
}
|
|
223
274
|
case "remove-channel":
|
|
@@ -303,7 +354,7 @@ function sanitizePendingOps(raw, projectKey) {
|
|
|
303
354
|
const src = e.payload && typeof e.payload === "object" ? e.payload : {};
|
|
304
355
|
// 通してよい payload の欄(葉は文字列か、notify だけ真偽値)。関数の中に置くのは、
|
|
305
356
|
// クライアントへ toString() で流し込んだ先でも同じ一覧を参照できるようにするため
|
|
306
|
-
const fields = ["name", "workspace", "notify", "domain", "projectKey", "type"];
|
|
357
|
+
const fields = ["name", "workspace", "notify", "domain", "projectKey", "type", "slackOrg"];
|
|
307
358
|
for (const f of fields) {
|
|
308
359
|
if (!Object.prototype.hasOwnProperty.call(src, f))
|
|
309
360
|
continue;
|
|
@@ -387,8 +438,46 @@ function pendingSiteTimeMs(s) {
|
|
|
387
438
|
return Date.parse(s.replace(" UTC", "Z").replace(" ", "T"));
|
|
388
439
|
}
|
|
389
440
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
441
|
+
* **「操作より後のビルドが来たか」の唯一の判定(#391)。**
|
|
442
|
+
*
|
|
443
|
+
* 削除のトゥームストーン・アイコンの反映待ち・操作タブの仮置きは、どれも「押した結果が
|
|
444
|
+
* 次のビルドで本物になるまでブラウザが覚えておく」という同じ性質の待ちで、破棄の条件も
|
|
445
|
+
* 同じだった。**同じものが 3 実装あると、片方だけ直したときに挙動が割れる**(値が同じ
|
|
446
|
+
* うちは誰も気づかない)ので、ここ 1 つに寄せてある。
|
|
447
|
+
*
|
|
448
|
+
* 判定: いま画面が持っている世代(nowMs)が、**押した時刻+余裕**と**押したとき見ていた
|
|
449
|
+
* 世代+余裕**の両方より新しい。generatedAt はコミット時点ではない——押す前に始まった
|
|
450
|
+
* ビルドが後から完成すると、世代は押した時刻より新しいのに操作は入っていない。
|
|
451
|
+
* ビルドの所要時間とクライアント時計のずれを見て余裕(既定 30 分)を取る。
|
|
452
|
+
*
|
|
453
|
+
* **主軸はサーバ時刻同士の比較**(現行の世代と、押した画面の gen0)。クライアントの時計が
|
|
454
|
+
* 大きく狂っていても機能が丸ごとは死なない(進んだ時計なら破棄が遅れるだけ)。
|
|
455
|
+
* gen0 を持たない古い形式のエントリは at だけで見る(後方互換・安全側)。
|
|
456
|
+
* nowMs が読めない(NaN)ときは false =**隠し続ける/残し続ける側に倒す**(嘘の再表示より安全)。
|
|
457
|
+
*
|
|
458
|
+
* @param at 押した時刻(ms・クライアント時計)
|
|
459
|
+
* @param gen0 押した画面の世代(generatedAt の文字列)。読めなければ at だけで見る
|
|
460
|
+
* @param nowMs いまの世代(ms)
|
|
461
|
+
*
|
|
462
|
+
* ⚠ 余裕は引数にしない。3 経路とも 30 分で、可変にすると**呼び出しが 1 つも無い引数**が
|
|
463
|
+
* 残る(壊しても何も起きない=変異検証の網から静かに漏れる形)。
|
|
464
|
+
*/
|
|
465
|
+
function pendingStale(at, gen0, nowMs) {
|
|
466
|
+
const margin = PENDING_OPS_MARGIN_MS;
|
|
467
|
+
if (typeof nowMs !== "number" || isNaN(nowMs))
|
|
468
|
+
return false;
|
|
469
|
+
if (typeof at !== "number" || !isFinite(at))
|
|
470
|
+
return false;
|
|
471
|
+
if (!(nowMs > at + margin))
|
|
472
|
+
return false;
|
|
473
|
+
const g = pendingSiteTimeMs(gen0);
|
|
474
|
+
if (isNaN(g))
|
|
475
|
+
return true;
|
|
476
|
+
return nowMs > g + margin;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* 本物の一覧の世代が追いついたか。**判定の実体は pendingStale**(トゥームストーン・
|
|
480
|
+
* アイコンの反映待ちと同じもの・#391)。
|
|
392
481
|
*
|
|
393
482
|
* 🔴 **世代は「一覧の下地」のもの(fleet-status の generatedAt)を渡すこと。** ページの
|
|
394
483
|
* generatedAt(SITE.generatedAt)は記録の追加でも進むが、一覧の下地は夜間の自己チェックの
|
|
@@ -396,13 +485,9 @@ function pendingSiteTimeMs(s) {
|
|
|
396
485
|
* 生成時刻はコミット時点ではない(保存前に始まった実行が後から完成する)ので余裕を取る。
|
|
397
486
|
*/
|
|
398
487
|
function pendingOpBuildCaughtUp(e, siteNowMs) {
|
|
399
|
-
if (!e
|
|
488
|
+
if (!e)
|
|
400
489
|
return false;
|
|
401
|
-
|
|
402
|
-
const gen0 = pendingSiteTimeMs(e.baseBuildId);
|
|
403
|
-
if (isNaN(gen0))
|
|
404
|
-
return byAt;
|
|
405
|
-
return byAt && siteNowMs > gen0 + PENDING_OPS_MARGIN_MS;
|
|
490
|
+
return pendingStale(e.committedAt, e.baseBuildId, siteNowMs);
|
|
406
491
|
}
|
|
407
492
|
/** registeredNow の行が、このエントリの対象か */
|
|
408
493
|
function pendingLiveRowMatches(kind, r, targetId) {
|
|
@@ -443,8 +528,22 @@ function pendingOpLiveAgrees(e, live, liveAt) {
|
|
|
443
528
|
found = r;
|
|
444
529
|
break;
|
|
445
530
|
}
|
|
446
|
-
if (e.action === "remove")
|
|
447
|
-
|
|
531
|
+
if (e.action === "remove") {
|
|
532
|
+
if (found)
|
|
533
|
+
return false;
|
|
534
|
+
// 🔴 **github は「読み取り停止中」(exclude)の行が live の dev に載らない(#391)。**
|
|
535
|
+
// dev だけを見ると、exclude された行の remove は**保存が効く前から**「無い=同意」に
|
|
536
|
+
// なり、5 分で仮置きが捨てられて、まとめ表の行が次のビルドまで戻る(点滅する)。
|
|
537
|
+
// devExcluded を返すサーバなら、そちらにも無いことまで確かめてから同意する。
|
|
538
|
+
// 返さない古いサーバでは従来どおり(dev だけ)——判断材料が無いのに厳しくすると、
|
|
539
|
+
// 普通のリポジトリの解除まで 24 時間残る
|
|
540
|
+
if (e.kind === "github" && Array.isArray(live.devExcluded)) {
|
|
541
|
+
for (const r of live.devExcluded)
|
|
542
|
+
if (pendingLiveRowMatches(e.kind, r, e.targetId))
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
448
547
|
if (e.action === "add")
|
|
449
548
|
return !!found;
|
|
450
549
|
if (!found)
|
|
@@ -538,6 +637,24 @@ function pendingExternalRow(e) {
|
|
|
538
637
|
row.workspace = p.workspace;
|
|
539
638
|
if (p.notify === true)
|
|
540
639
|
row.notify = true;
|
|
640
|
+
// **サーバが実測した組織を、本物の行と同じ欄に展開する(#391)。**
|
|
641
|
+
// 展開できたときだけ pendingOrg を立てる——立っていない行に札を出すと、
|
|
642
|
+
// 測れていないのに「クラスメソッド」と名乗る(画面側の既定がそれなので)
|
|
643
|
+
// 値は 3 通りだけ通す("unknown" / "internal" / team_id)。**"internal" を team_id として
|
|
644
|
+
// 札に出さない**ための門で、形の検査はサーバの正本(pendingIsSlackTeamId)と同じにする
|
|
645
|
+
// ——狭くすると Enterprise Grid(E…)の組織だけ札が出ない
|
|
646
|
+
const org = typeof p.slackOrg === "string" ? p.slackOrg : "";
|
|
647
|
+
if (org === "unknown") {
|
|
648
|
+
row.pendingOrg = true;
|
|
649
|
+
row.orgUnknown = true;
|
|
650
|
+
}
|
|
651
|
+
else if (org === "internal") {
|
|
652
|
+
row.pendingOrg = true;
|
|
653
|
+
}
|
|
654
|
+
else if (pendingIsSlackTeamId(org)) {
|
|
655
|
+
row.pendingOrg = true;
|
|
656
|
+
row.workspace = org;
|
|
657
|
+
}
|
|
541
658
|
}
|
|
542
659
|
else if (e.kind === "google-chat") {
|
|
543
660
|
row.type = "google-chat";
|
|
@@ -602,6 +719,12 @@ function applyPendingOps(view, list) {
|
|
|
602
719
|
// dev-sources.json 側にも宣言された行(both / explicit)は channels.json から消しても
|
|
603
720
|
// エンジンがまだ読む——消すと「読まれているのに画面に無い」逆向きの嘘になり、
|
|
604
721
|
// live が追いついた後に復活する(設計レビュー②の中5)。そういう行は従来どおり取り消し線のまま
|
|
722
|
+
//
|
|
723
|
+
// **「読み取り停止中」(goldState: "excluded")の GitHub 行も、押したら消す(#391)。**
|
|
724
|
+
// サーバの applyRemoveGithubSource は sources と exclude の両方から実際に消すので、
|
|
725
|
+
// 保存は完全に効いている。ここで守ると「押しても何も起きない」=保存が失敗したのと
|
|
726
|
+
// 区別が付かない。消えたままにする仕掛けは 2 つ: 同意の判定(pendingOpLiveAgrees が
|
|
727
|
+
// devExcluded も見る)と、引き算(subtractRemovedRegistered が devExcluded で判断する)
|
|
605
728
|
const removable = function (s) {
|
|
606
729
|
return pendingExternalMatches(e.kind, s, e.targetId) && (e.kind !== "slack" || s.origin === "derived");
|
|
607
730
|
};
|