@takagaki/cortex-decisions-viewer 0.12.134 → 0.12.137
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 +6 -1
- package/dist/pending-ops.js +81 -2
- package/dist/render.js +247 -63
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -653,6 +653,10 @@ async function readFleetSources(repoRoot) {
|
|
|
653
653
|
capabilities = out;
|
|
654
654
|
}
|
|
655
655
|
const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
|
|
656
|
+
// 画面は案内の宛先に出し、[コピー]の中身にもする。fleet-status.json は案件リポにコミットされる=人が書き換えうるので、メールの形だけ通す
|
|
657
|
+
const driveReaderEmail = typeof obj.driveReaderEmail === "string" && /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(obj.driveReaderEmail)
|
|
658
|
+
? obj.driveReaderEmail
|
|
659
|
+
: undefined;
|
|
656
660
|
// いま同期している Backlog の接続先(fleet-status.json トップレベル・#111)。
|
|
657
661
|
//
|
|
658
662
|
// **画面に現在値を出すために要る。** 「接続先を変更」フォームが入力欄だけだったため、
|
|
@@ -712,7 +716,7 @@ async function readFleetSources(repoRoot) {
|
|
|
712
716
|
if (Object.keys(out).length)
|
|
713
717
|
secretSpaces = out;
|
|
714
718
|
}
|
|
715
|
-
return { externalSources, slackWorkspaces, internalSources, pipelines, capabilities, fleetGeneratedAt, secretSources, secretSpaces, checks, githubOwners, backlogTarget, tools };
|
|
719
|
+
return { externalSources, slackWorkspaces, internalSources, pipelines, capabilities, fleetGeneratedAt, driveReaderEmail, secretSources, secretSpaces, checks, githubOwners, backlogTarget, tools };
|
|
716
720
|
}
|
|
717
721
|
/**
|
|
718
722
|
* 区画のレコードを活性度・強さの順に並べる(用語集だけが動く・#665 便 12b → #1094 便 e4 で帯を外した)。
|
|
@@ -2651,6 +2655,7 @@ async function buildSite(opts, views) {
|
|
|
2651
2655
|
externalSources: fleet.externalSources,
|
|
2652
2656
|
slackWorkspaces: fleet.slackWorkspaces,
|
|
2653
2657
|
fleetGeneratedAt: fleet.fleetGeneratedAt,
|
|
2658
|
+
driveReaderEmail: fleet.driveReaderEmail,
|
|
2654
2659
|
secretSources: fleet.secretSources,
|
|
2655
2660
|
secretSpaces: fleet.secretSpaces,
|
|
2656
2661
|
internalSources: fleet.internalSources,
|
package/dist/pending-ops.js
CHANGED
|
@@ -34,6 +34,9 @@ exports.pendingLabel = pendingLabel;
|
|
|
34
34
|
exports.pendingOpOf = pendingOpOf;
|
|
35
35
|
exports.sanitizePendingOps = sanitizePendingOps;
|
|
36
36
|
exports.rememberPendingOp = rememberPendingOp;
|
|
37
|
+
exports.pendingOpsOfTarget = pendingOpsOfTarget;
|
|
38
|
+
exports.rollbackPendingOp = rollbackPendingOp;
|
|
39
|
+
exports.settlePendingOp = settlePendingOp;
|
|
37
40
|
exports.pendingSiteTimeMs = pendingSiteTimeMs;
|
|
38
41
|
exports.pendingStale = pendingStale;
|
|
39
42
|
exports.pendingOpBuildCaughtUp = pendingOpBuildCaughtUp;
|
|
@@ -387,6 +390,8 @@ function sanitizePendingOps(raw, projectKey) {
|
|
|
387
390
|
payload: payload,
|
|
388
391
|
committedAt: e.committedAt,
|
|
389
392
|
baseBuildId: typeof e.baseBuildId === "string" ? e.baseBuildId : "",
|
|
393
|
+
...(typeof e.nonce === "string" && e.nonce && e.nonce.length <= 64 ? { nonce: e.nonce } : {}),
|
|
394
|
+
...(e.inflight === true ? { inflight: true } : {}),
|
|
390
395
|
});
|
|
391
396
|
}
|
|
392
397
|
out.sort(function (a, b) { return a.committedAt - b.committedAt; });
|
|
@@ -417,7 +422,7 @@ function rememberPendingOp(list, entry) {
|
|
|
417
422
|
for (const k in entry.payload)
|
|
418
423
|
if (Object.prototype.hasOwnProperty.call(entry.payload, k))
|
|
419
424
|
merged[k] = entry.payload[k];
|
|
420
|
-
return { v: 1, projectKey: e.projectKey, kind: e.kind, targetId: e.targetId, action: e.action, payload: merged, committedAt: e.committedAt, baseBuildId: e.baseBuildId };
|
|
425
|
+
return { v: 1, projectKey: e.projectKey, kind: e.kind, targetId: e.targetId, action: e.action, payload: merged, committedAt: e.committedAt, baseBuildId: e.baseBuildId, ...(e.nonce ? { nonce: e.nonce } : {}), ...(e.inflight === true ? { inflight: true } : {}) };
|
|
421
426
|
});
|
|
422
427
|
if (folded)
|
|
423
428
|
return out;
|
|
@@ -436,13 +441,84 @@ function rememberPendingOp(list, entry) {
|
|
|
436
441
|
if (Object.prototype.hasOwnProperty.call(entry.payload, k))
|
|
437
442
|
merged[k] = entry.payload[k];
|
|
438
443
|
const rest = cur.filter(function (e) { return !(same(e) && e.action === "update"); });
|
|
439
|
-
rest.push({ v: 1, projectKey: entry.projectKey, kind: entry.kind, targetId: entry.targetId, action: entry.action, payload: merged, committedAt: entry.committedAt, baseBuildId: entry.baseBuildId });
|
|
444
|
+
rest.push({ v: 1, projectKey: entry.projectKey, kind: entry.kind, targetId: entry.targetId, action: entry.action, payload: merged, committedAt: entry.committedAt, baseBuildId: entry.baseBuildId, ...(entry.nonce ? { nonce: entry.nonce } : {}), ...(entry.inflight === true ? { inflight: true } : {}) });
|
|
440
445
|
return rest;
|
|
441
446
|
}
|
|
442
447
|
const kept = cur.filter(function (e) { return !same(e); });
|
|
443
448
|
kept.push(entry);
|
|
444
449
|
return kept;
|
|
445
450
|
}
|
|
451
|
+
// ---- 楽観更新の巻き戻し(cortex-engine #968 E)-------------------------------
|
|
452
|
+
/**
|
|
453
|
+
* 同じ (kind, targetId) の仮置きだけを抜き出す。送信時に仮置きを書く前の姿(`rollbackPendingOp` の戻し先)。
|
|
454
|
+
*/
|
|
455
|
+
function pendingOpsOfTarget(list, entry) {
|
|
456
|
+
if (!entry)
|
|
457
|
+
return [];
|
|
458
|
+
return (list || []).filter(function (e) { return e.kind === entry.kind && e.targetId === entry.targetId; });
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 送信時に書いた仮置き(`entry`)を**自分の nonce の行だけ**取り消す(他の送信中の仮置きは消さない)。
|
|
462
|
+
*
|
|
463
|
+
* - 自分の行が置き換えた送信前の行(`before`)は、その対象にいま別の行が無く、かつ失敗済みの送信の行(`failed` の nonce)でないときだけ戻す
|
|
464
|
+
* (失敗済みの行を復活させない・あとから来た別の操作の行を潰さない)。
|
|
465
|
+
* - update が add に畳まれていた(自分の nonce の行が無い)ときは、同じ nonce の行を送信前の行へ差し替える。
|
|
466
|
+
* @param failed すでに巻き戻した送信の nonce の一覧
|
|
467
|
+
*/
|
|
468
|
+
function rollbackPendingOp(list, entry, before, failed) {
|
|
469
|
+
const cur = list || [];
|
|
470
|
+
if (!entry)
|
|
471
|
+
return cur.slice();
|
|
472
|
+
const dead = failed || [];
|
|
473
|
+
const nonce = entry.nonce;
|
|
474
|
+
const sameTarget = function (e) { return e.kind === entry.kind && e.targetId === entry.targetId; };
|
|
475
|
+
const back = (before || []).filter(function (b) { return sameTarget(b) && !(b.nonce && dead.indexOf(b.nonce) >= 0); });
|
|
476
|
+
let found = false;
|
|
477
|
+
let out = cur.filter(function (e) {
|
|
478
|
+
if (nonce && e.nonce === nonce) {
|
|
479
|
+
found = true;
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
});
|
|
484
|
+
if (!found && entry.action === "update") {
|
|
485
|
+
// 畳まれた update: add の行(同じ nonce の送信前の行があれば)を送信前へ戻す
|
|
486
|
+
out = out.map(function (e) {
|
|
487
|
+
if (!sameTarget(e) || !e.nonce)
|
|
488
|
+
return e;
|
|
489
|
+
for (let i = 0; i < back.length; i++)
|
|
490
|
+
if (back[i].nonce === e.nonce)
|
|
491
|
+
return back[i];
|
|
492
|
+
return e;
|
|
493
|
+
});
|
|
494
|
+
out.sort(function (a, b) { return a.committedAt - b.committedAt; });
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
if (!out.some(sameTarget))
|
|
498
|
+
out = out.concat(back);
|
|
499
|
+
out.sort(function (a, b) { return a.committedAt - b.committedAt; });
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 応答が成功したとき、自分の nonce の行を「送信中」から「確定」へ(送信中の印を外し、サーバの応答で分かった欄を足す)。
|
|
504
|
+
* 自分の行がもう無い(あとの操作に置き換えられた)なら何もしない——**あとの操作の行を書き直さない**。
|
|
505
|
+
* @param final 応答つきで作り直した仮置き(`pendingOpOf` の結果)。無ければ印を外すだけ
|
|
506
|
+
*/
|
|
507
|
+
function settlePendingOp(list, nonce, final) {
|
|
508
|
+
return (list || []).map(function (e) {
|
|
509
|
+
if (!nonce || e.nonce !== nonce)
|
|
510
|
+
return e;
|
|
511
|
+
const payload = {};
|
|
512
|
+
for (const k in e.payload)
|
|
513
|
+
if (Object.prototype.hasOwnProperty.call(e.payload, k))
|
|
514
|
+
payload[k] = e.payload[k];
|
|
515
|
+
if (final && final.payload)
|
|
516
|
+
for (const k in final.payload)
|
|
517
|
+
if (Object.prototype.hasOwnProperty.call(final.payload, k))
|
|
518
|
+
payload[k] = final.payload[k];
|
|
519
|
+
return { v: 1, projectKey: e.projectKey, kind: e.kind, targetId: e.targetId, action: e.action, payload: payload, committedAt: e.committedAt, baseBuildId: e.baseBuildId, nonce: e.nonce };
|
|
520
|
+
});
|
|
521
|
+
}
|
|
446
522
|
// ---- 失効 ----------------------------------------------------------------
|
|
447
523
|
/** SITE.generatedAt("2026-09-03 00:00 UTC" / ISO)→ ms。読めなければ NaN */
|
|
448
524
|
function pendingSiteTimeMs(s) {
|
|
@@ -616,6 +692,9 @@ function prunePendingOps(list, ctx) {
|
|
|
616
692
|
return false;
|
|
617
693
|
if (now > e.committedAt + PENDING_OPS_TTL_MS)
|
|
618
694
|
return false;
|
|
695
|
+
// 送信中のまま 2 分を過ぎた行(画面を閉じた・落ちた送信の置き土産)は捨てる(cortex-engine #968 E)
|
|
696
|
+
if (e.inflight === true && now > e.committedAt + 120000)
|
|
697
|
+
return false;
|
|
619
698
|
if (pendingOpBuildCaughtUp(e, c.siteNowMs))
|
|
620
699
|
return false;
|
|
621
700
|
if (pendingOpLiveAgrees(e, c.live, c.liveAt))
|
package/dist/render.js
CHANGED
|
@@ -6398,6 +6398,9 @@ ${pending_ops_1.pendingIsSlackTeamId.toString()}
|
|
|
6398
6398
|
${pending_ops_1.pendingOpOf.toString()}
|
|
6399
6399
|
${pending_ops_1.sanitizePendingOps.toString()}
|
|
6400
6400
|
${pending_ops_1.rememberPendingOp.toString()}
|
|
6401
|
+
${pending_ops_1.pendingOpsOfTarget.toString()}
|
|
6402
|
+
${pending_ops_1.rollbackPendingOp.toString()}
|
|
6403
|
+
${pending_ops_1.settlePendingOp.toString()}
|
|
6401
6404
|
${pending_ops_1.pendingSiteTimeMs.toString()}
|
|
6402
6405
|
${pending_ops_1.pendingStale.toString()}
|
|
6403
6406
|
${pending_ops_1.pendingOpBuildCaughtUp.toString()}
|
|
@@ -6649,11 +6652,14 @@ ${calendarAddUrl.toString()}
|
|
|
6649
6652
|
* **誰を招待すればいいのかが画面から分からなかった**——担当者に聞かないと
|
|
6650
6653
|
* 手順が完了できなかった。
|
|
6651
6654
|
*
|
|
6652
|
-
* **艦隊で1つ。**
|
|
6653
|
-
*
|
|
6655
|
+
* **艦隊で1つ。** 案件ごとに違うものではない。**アドレスはここに直書きしない**——
|
|
6656
|
+
* engine が運営の設定(config/operator.json の driveReaderEmail)から
|
|
6657
|
+
* fleet-status.json のトップに置いた値を読む(cortex-engine #1094 便 γ 5-14)。
|
|
6658
|
+
* 値が届いていない(古い生成物)ときは空にして、宛先を名指さない文に倒す
|
|
6659
|
+
* (他の組織の画面に別の会社のアドレスを出さない)。
|
|
6654
6660
|
* 議事録の取り込みでも同じアカウントを使っている。
|
|
6655
6661
|
*/
|
|
6656
|
-
var DRIVE_INGEST_ACCOUNT = "
|
|
6662
|
+
var DRIVE_INGEST_ACCOUNT = typeof SITE.driveReaderEmail === "string" ? SITE.driveReaderEmail : "";
|
|
6657
6663
|
// Google Chat の「スペースにアプリを追加する」手順の画像(#389)。理由は
|
|
6658
6664
|
// src/google-chat-guide-images.ts に書いてある
|
|
6659
6665
|
// Google Chat アプリの表示名。**Marketplace 公開名(2026-09-03 オーナー決定で「Cortex Bot」から
|
|
@@ -10034,10 +10040,9 @@ ${calendarAddUrl.toString()}
|
|
|
10034
10040
|
customer: {
|
|
10035
10041
|
text: "トークン未設定",
|
|
10036
10042
|
cls: "ds-gate-muted",
|
|
10037
|
-
//
|
|
10038
|
-
//
|
|
10039
|
-
//
|
|
10040
|
-
// ——slackConnectInline の項)
|
|
10043
|
+
// 一手「このワークスペースに Cortex が入っていません。[ワークスペースを接続する]」は、
|
|
10044
|
+
// 行のタイルの中が持つ(slackRowHelpParts・g-66)。承認画面はワークスペースを指定できない
|
|
10045
|
+
// ——slackConnectInline の項
|
|
10041
10046
|
},
|
|
10042
10047
|
/* -------------------------------------------------------------------------
|
|
10043
10048
|
* GitHub。**「トークン未設定」と言ってはいけない**(2026-08-21)
|
|
@@ -10205,31 +10210,32 @@ ${calendarAddUrl.toString()}
|
|
|
10205
10210
|
return [say, pick ? { link: "インストール設定を開く →", kind: "settings", slug: raw } : { link: "GitHub App を入れる →", kind: "install", slug: raw }];
|
|
10206
10211
|
}
|
|
10207
10212
|
/**
|
|
10208
|
-
* Slack
|
|
10209
|
-
*
|
|
10213
|
+
* Slack の「見えない」行(unreachable・no_token・組織不明の仮置き)に出す一手(c-07・g-66)。
|
|
10214
|
+
* **行のタイルの中**に出す(組の先頭=タイルの外には出さない)。症状は帯が言い、行は一手だけ。
|
|
10210
10215
|
*
|
|
10211
|
-
* -
|
|
10212
|
-
*
|
|
10213
|
-
* -
|
|
10214
|
-
*
|
|
10216
|
+
* - 接続で直ると確かな行(ワークスペースが分かっていて台帳の接続が切れている/未接続、または no_token)
|
|
10217
|
+
* → 1 行「このワークスペースに Cortex が入っていません。[ワークスペースを接続する]」
|
|
10218
|
+
* - 招待で直るか接続で直るか見分けられない行(社内・組織不明の unreachable・仮置き)
|
|
10219
|
+
* → 1 行目 招待(/invite @Cortex[コピー])、2 行目「…入っていなければ、ワークスペースを接続してください。[接続する]」
|
|
10220
|
+
* - 接続が生きているワークスペース → 招待だけ
|
|
10215
10221
|
*
|
|
10216
|
-
* 🔴
|
|
10217
|
-
*
|
|
10218
|
-
* 🔴 **空の配列を返す(null にしない)。** null を返すと呼び側が夜間の理由文(機械の語)を代わりに出す。
|
|
10219
|
-
* 投入の口(SITE.intake)が無い案件は接続を出せないので、従来どおり招待だけを返す。
|
|
10222
|
+
* 🔴 同じ行の中で同じ説明を 2 回言わない。投入の口(SITE.intake)が無い案件は接続を出せないので、招待だけ。
|
|
10223
|
+
* 🔴 **空の配列は返さない**(null も)。null を返すと呼び側が夜間の理由文(機械の語)を代わりに出す。
|
|
10220
10224
|
*/
|
|
10221
10225
|
function slackRowHelpParts(row) {
|
|
10222
10226
|
if (!row) return null;
|
|
10223
|
-
var invite = ["Cortex
|
|
10224
|
-
if (row
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10227
|
+
var invite = ["Cortex からこのチャンネルが見えません。チャンネルで Cortex を招待してください:", { copy: "/invite @Cortex" }];
|
|
10228
|
+
if (!slackRowNeedsConnect(row)) return invite;
|
|
10229
|
+
if (slackRowConnectSure(row)) return ["このワークスペースに Cortex が入っていません。", { act: "connect", label: "ワークスペースを接続する" }];
|
|
10230
|
+
return invite.concat([{ br: true }, "このワークスペースに Cortex が入っていなければ、ワークスペースを接続してください。", { act: "connect" }]);
|
|
10231
|
+
}
|
|
10232
|
+
/** 接続で直ると確かな行か(no_token、またはワークスペースが分かっていて台帳の接続が切れている/未接続) */
|
|
10233
|
+
function slackRowConnectSure(s) {
|
|
10234
|
+
return s.gate === "no_token" || !!s.workspace;
|
|
10229
10235
|
}
|
|
10230
10236
|
/**
|
|
10231
10237
|
* この Slack の行が、ワークスペースの接続([接続する])で直る行か(#1328)。
|
|
10232
|
-
*
|
|
10238
|
+
* 行の一手に[接続する]を出すかどうかの判断に使う。
|
|
10233
10239
|
*
|
|
10234
10240
|
* - 顧客の no_token(組織不明の行を含む。社内の no_token は画面から直せないので含めない・2026-08-18)
|
|
10235
10241
|
* - まだ測っていない組織不明の仮置きの行
|
|
@@ -10245,21 +10251,6 @@ ${calendarAddUrl.toString()}
|
|
|
10245
10251
|
var st = tokenStateOf(slackWorkspaces(), s.workspace);
|
|
10246
10252
|
return st === "revoked" || st === "missing";
|
|
10247
10253
|
}
|
|
10248
|
-
/**
|
|
10249
|
-
* ワークスペースごとの組の先頭に置く、接続の一手(0 または 1 個の段落)。
|
|
10250
|
-
* 「この Slack を Cortex に接続してください。[接続する]」(その組が顧客のワークスペースだと分かっているとき)か、
|
|
10251
|
-
* 「お客様の Slack なら、ワークスペースを接続してください。[接続する]」(社内・組織不明の組)。
|
|
10252
|
-
* **行の中の ds-gate-help ではない**(行の補足は最大 1 段・押した結果が行の補足を隠す仕掛けに巻き込まない)。
|
|
10253
|
-
* @param g groupSlackByOrg の 1 組
|
|
10254
|
-
*/
|
|
10255
|
-
function slackConnectNote(g) {
|
|
10256
|
-
var needy = g.items.filter(slackRowNeedsConnect);
|
|
10257
|
-
if (!needy.length) return null;
|
|
10258
|
-
var sure = (g.key !== "__internal__" && g.key !== "__unknown__")
|
|
10259
|
-
|| needy.every(function (s) { return s.gate === "no_token"; });
|
|
10260
|
-
var say = sure ? "この Slack を Cortex に接続してください。" : "お客様の Slack なら、ワークスペースを接続してください。";
|
|
10261
|
-
return restingHelpNode([say, { act: "connect" }], "ds-group-note ds-ws-connect");
|
|
10262
|
-
}
|
|
10263
10254
|
/** 押した結果に添えるパーツ(無ければ null)。行と同じ表から引く(写経しない) */
|
|
10264
10255
|
function probedHelpPartsOf(type, gate) {
|
|
10265
10256
|
var e = restingHelpEntryOf(type, gate);
|
|
@@ -10268,13 +10259,14 @@ ${calendarAddUrl.toString()}
|
|
|
10268
10259
|
/**
|
|
10269
10260
|
* パーツを1段落にする。包む要素の class は置き場所で違う(行は ds-gate-help・押した結果は ds-probe-help)。
|
|
10270
10261
|
* 部品: 文字列=テキスト / { link, kind } = GitHub App のリンク /
|
|
10271
|
-
* { copy } =コピー付きの 1 語(文字列と[コピー]) / { act } =ボタン(actNode
|
|
10262
|
+
* { copy } =コピー付きの 1 語(文字列と[コピー]) / { act } =ボタン(actNode) / { br } =同じ段落の中の改行。
|
|
10272
10263
|
* **部品は同じ段落の中に置く**(段を増やさない=行の補足は最大 1 段)。
|
|
10273
10264
|
*/
|
|
10274
10265
|
function restingHelpNode(parts, cls) {
|
|
10275
10266
|
var p = el("p", { class: cls });
|
|
10276
10267
|
parts.forEach(function (part) {
|
|
10277
10268
|
if (typeof part === "string") p.appendChild(document.createTextNode(part));
|
|
10269
|
+
else if (part && part.br) p.appendChild(el("br"));
|
|
10278
10270
|
else if (part && part.link) p.appendChild(el("a", { href: githubAppLinkUrl(part.kind, part.slug), target: "_blank", rel: "noopener", text: part.link }));
|
|
10279
10271
|
// bare … 文面に値が含まれているので[コピー]だけを出す(固定 IP の依頼文)
|
|
10280
10272
|
else if (part && part.copy) p.appendChild(copyInline(part.copy, !part.bare));
|
|
@@ -10457,9 +10449,11 @@ ${calendarAddUrl.toString()}
|
|
|
10457
10449
|
unreachable: {
|
|
10458
10450
|
text: "フォルダを開けません",
|
|
10459
10451
|
cls: "ds-gate-warn",
|
|
10460
|
-
// 🔴
|
|
10452
|
+
// 🔴 **一手は「このフォルダを〈取り込み用アカウント〉に閲覧者として共有してください。[コピー]」の 1 行(c-07)。**
|
|
10461
10453
|
// コピーは追加の欄の手順 1 にだけあったので、ここへ移した(help の文面にアドレスが含まれるので[コピー]だけ)
|
|
10462
|
-
help:
|
|
10454
|
+
help: DRIVE_INGEST_ACCOUNT
|
|
10455
|
+
? "このフォルダを " + DRIVE_INGEST_ACCOUNT + " に閲覧者として共有してください。"
|
|
10456
|
+
: "このフォルダを取り込み用のアカウントに閲覧者として共有してください(アカウントは管理者に確かめてください)。",
|
|
10463
10457
|
copy: DRIVE_INGEST_ACCOUNT,
|
|
10464
10458
|
},
|
|
10465
10459
|
no_token: { text: "管理者の設定が必要です", cls: "ds-gate-muted" },
|
|
@@ -11368,11 +11362,7 @@ ${calendarAddUrl.toString()}
|
|
|
11368
11362
|
out.push(resolved);
|
|
11369
11363
|
}
|
|
11370
11364
|
// 🔴 **接続が切れた/未接続のワークスペースの枠(brokenOrgNodes)は撤去した(c-07)。**
|
|
11371
|
-
//
|
|
11372
|
-
// 1 回だけ置く(slackConnectNote・#1328)。行は札と、その行だけの一手(招待)だけを持つ。
|
|
11373
|
-
// 入口を失わないための約束は決定メモに書いた
|
|
11374
|
-
var connectNote = slackConnectNote(g);
|
|
11375
|
-
if (connectNote) out.push(connectNote);
|
|
11365
|
+
// 接続の一手は、見えない行のタイルの中に行ごとに置く(slackRowHelpParts・g-66。組の先頭=タイルの外には出さない)
|
|
11376
11366
|
cards.forEach(function (n) { out.push(n); });
|
|
11377
11367
|
});
|
|
11378
11368
|
return out;
|
|
@@ -11654,6 +11644,67 @@ ${calendarAddUrl.toString()}
|
|
|
11654
11644
|
MERGED = null;
|
|
11655
11645
|
return entry;
|
|
11656
11646
|
}
|
|
11647
|
+
/**
|
|
11648
|
+
* 送信時の仮置き(cortex-engine #968 E・本当の楽観更新)。**サーバの答えを待たずに**仮置きを書き、
|
|
11649
|
+
* 戻し先(送信前のその対象の仮置き)を控えて返す。作れなければ null(従来どおり応答後に任せる)。
|
|
11650
|
+
* 応答が来たら notePendingOp(…, 応答) で同じ対象を書き直す(サーバが実測した組織などを足す)。
|
|
11651
|
+
*/
|
|
11652
|
+
var OPT_SEQ = 0;
|
|
11653
|
+
/** 巻き戻した送信の nonce(失敗済みの行を、あとの巻き戻しで復活させないための控え) */
|
|
11654
|
+
var FAILED_NONCES = [];
|
|
11655
|
+
function noteOptimisticOp(payload, targets) {
|
|
11656
|
+
if (!SITE.intake) return null;
|
|
11657
|
+
PENDING_OPS = readPendingOps();
|
|
11658
|
+
var probe = pendingOpOf(payload, {
|
|
11659
|
+
projectKey: SITE.intake.projectKey,
|
|
11660
|
+
now: Date.now(),
|
|
11661
|
+
generatedAt: rawFleetVal("generatedAt") || "",
|
|
11662
|
+
targets: targets || null,
|
|
11663
|
+
response: null,
|
|
11664
|
+
});
|
|
11665
|
+
if (!probe) return null;
|
|
11666
|
+
var before = pendingOpsOfTarget(pendingOpsNow(), probe);
|
|
11667
|
+
// 送信ごとの一意 ID と送信中の印を付けて書く(巻き戻しは自分の行だけを外す)
|
|
11668
|
+
OPT_SEQ += 1;
|
|
11669
|
+
var nonce = "n" + Date.now().toString(36) + "-" + OPT_SEQ + "-" + Math.floor(Math.random() * 1e6).toString(36);
|
|
11670
|
+
probe.nonce = nonce;
|
|
11671
|
+
probe.inflight = true;
|
|
11672
|
+
PENDING_OPS = rememberPendingOp(pendingOpsNow(), probe);
|
|
11673
|
+
writePendingOps(PENDING_OPS);
|
|
11674
|
+
MERGED = null;
|
|
11675
|
+
return { entry: probe, before: before };
|
|
11676
|
+
}
|
|
11677
|
+
/**
|
|
11678
|
+
* 送信時の仮置きを巻き戻す(断られた・通信が落ちた・何も変わらなかった)。**自分の nonce の行だけ**外し、
|
|
11679
|
+
* 他の送信中の仮置きには触らない。巻き戻したあとは、開いている一覧を描き直して元の姿に戻す。
|
|
11680
|
+
*/
|
|
11681
|
+
function rollbackOptimisticOp(opt) {
|
|
11682
|
+
if (!opt || !SITE.intake) return;
|
|
11683
|
+
PENDING_OPS = rollbackPendingOp(readPendingOps(), opt.entry, opt.before, FAILED_NONCES);
|
|
11684
|
+
if (opt.entry.nonce) FAILED_NONCES.push(opt.entry.nonce);
|
|
11685
|
+
writePendingOps(PENDING_OPS);
|
|
11686
|
+
MERGED = null;
|
|
11687
|
+
repaintOpenLists();
|
|
11688
|
+
refreshDataSources();
|
|
11689
|
+
syncAiLinks();
|
|
11690
|
+
}
|
|
11691
|
+
/**
|
|
11692
|
+
* 応答が成功したとき、自分の行を確定させる(送信中の印を外し、サーバが実測した欄を足す)。
|
|
11693
|
+
* 自分の行がもう無い(あとの操作に置き換えられた)なら何もしない。
|
|
11694
|
+
*/
|
|
11695
|
+
function settleOptimisticOp(opt, payload, targets, response) {
|
|
11696
|
+
if (!opt || !SITE.intake) return;
|
|
11697
|
+
var final = pendingOpOf(payload, {
|
|
11698
|
+
projectKey: SITE.intake.projectKey,
|
|
11699
|
+
now: Date.now(),
|
|
11700
|
+
generatedAt: rawFleetVal("generatedAt") || "",
|
|
11701
|
+
targets: targets || null,
|
|
11702
|
+
response: response || null,
|
|
11703
|
+
});
|
|
11704
|
+
PENDING_OPS = settlePendingOp(readPendingOps(), opt.entry.nonce, final);
|
|
11705
|
+
writePendingOps(PENDING_OPS);
|
|
11706
|
+
MERGED = null;
|
|
11707
|
+
}
|
|
11657
11708
|
/** 仮置きの行の印(「反映待ち」/「反映を確認できていません」)。印の無い行は null */
|
|
11658
11709
|
function pendingBadgeEl(row) {
|
|
11659
11710
|
var note = pendingRowNote(row, Date.now());
|
|
@@ -14383,16 +14434,25 @@ ${calendarAddUrl.toString()}
|
|
|
14383
14434
|
var next = !on;
|
|
14384
14435
|
// **ワークスペースは送らない。** この分岐に来るのは宣言の無い行だけで、
|
|
14385
14436
|
// サーバも ON では受け取らない(送れば断られる)
|
|
14386
|
-
|
|
14387
|
-
|
|
14437
|
+
// 行の見た目を「いまの向き」へ揃える。**送信した瞬間に呼ぶ(楽観更新・#968 E)**・成功でも呼ぶ(同じ値で冪等)・
|
|
14438
|
+
// 断られたら元の向きで呼んで戻す
|
|
14439
|
+
var apply = function (v) {
|
|
14440
|
+
on = v;
|
|
14388
14441
|
// 行の元データも合わせる。ライブの読み直しで同じ行が描き直されるまでの間、
|
|
14389
14442
|
// ここだけが「いまどちらか」を持っている
|
|
14390
|
-
s.notify =
|
|
14443
|
+
s.notify = v;
|
|
14391
14444
|
btn.textContent = labelOf();
|
|
14392
14445
|
var badge = badges.querySelector(".ds-notify");
|
|
14393
14446
|
// 並びは描画時と同じ(札の先頭)。後ろに足すと、押した行だけ順番が変わる
|
|
14394
14447
|
if (on && !badge) badges.insertBefore(notifyBadge(), badges.firstChild);
|
|
14395
14448
|
if (!on && badge) badges.removeChild(badge);
|
|
14449
|
+
};
|
|
14450
|
+
var before = on;
|
|
14451
|
+
await sendConfig({ op: "set-notify", ref: s.ref, value: next }, msg, btn, function () {
|
|
14452
|
+
apply(next);
|
|
14453
|
+
}, {
|
|
14454
|
+
onOptimistic: function () { apply(next); },
|
|
14455
|
+
onRevert: function () { apply(before); },
|
|
14396
14456
|
});
|
|
14397
14457
|
});
|
|
14398
14458
|
wrap.appendChild(btn);
|
|
@@ -15812,6 +15872,46 @@ ${calendarAddUrl.toString()}
|
|
|
15812
15872
|
staleNote: TARGETS_RACE_NOTE,
|
|
15813
15873
|
// 仮置き(#388)の鍵にドメインを入れるため、表示中の一覧を渡す(解除の payload にはキーしか無い)
|
|
15814
15874
|
pendingTargets: function () { return targets; },
|
|
15875
|
+
// 送信した瞬間に一覧を先に直す(応答の一覧が来れば、それが正本として上書きする)。
|
|
15876
|
+
// 返す値は送信ごとの控え(連続操作でも他の送信の控えと混ざらない)
|
|
15877
|
+
onOptimistic: function (sent) {
|
|
15878
|
+
var op = sent && sent.op;
|
|
15879
|
+
var next = null;
|
|
15880
|
+
if (op === "backlog-target-remove") {
|
|
15881
|
+
var key = String(sent.backlogProjectKey || "").toUpperCase();
|
|
15882
|
+
var dropped = false;
|
|
15883
|
+
next = targets.filter(function (t) {
|
|
15884
|
+
if (!dropped && String(t.projectKey || "").toUpperCase() === key) { dropped = true; return false; }
|
|
15885
|
+
return true;
|
|
15886
|
+
});
|
|
15887
|
+
if (!dropped) next = null;
|
|
15888
|
+
} else if (op === "backlog-target-add") {
|
|
15889
|
+
var tg = backlogTargetOf(sent.url);
|
|
15890
|
+
if (tg) {
|
|
15891
|
+
var exists = targets.some(function (t) {
|
|
15892
|
+
return String(t.domain || "").toLowerCase() === String(tg.domain || "").toLowerCase() && String(t.projectKey || "").toUpperCase() === String(tg.projectKey || "").toUpperCase();
|
|
15893
|
+
});
|
|
15894
|
+
if (!exists) next = targets.concat([{ domain: String(tg.domain), projectKey: String(tg.projectKey) }]);
|
|
15895
|
+
}
|
|
15896
|
+
}
|
|
15897
|
+
if (!next) return undefined;
|
|
15898
|
+
var tok = { before: targets, after: targetsGenerationOf(next) };
|
|
15899
|
+
targets = next;
|
|
15900
|
+
ensureKeySlots();
|
|
15901
|
+
paint();
|
|
15902
|
+
return tok;
|
|
15903
|
+
},
|
|
15904
|
+
// 断られた・通信が落ちた・何も変わらなかった: 他の操作が一覧を変えていなければ送信前へ戻し、変えていれば
|
|
15905
|
+
// 正本を読み直す(連続操作で 1 つの控えが他の送信を潰さない)
|
|
15906
|
+
onRevert: function (tok) {
|
|
15907
|
+
if (!tok) return;
|
|
15908
|
+
if (tok.after === targetsGenerationOf(targets)) {
|
|
15909
|
+
targets = tok.before;
|
|
15910
|
+
paint();
|
|
15911
|
+
} else {
|
|
15912
|
+
try { loadLiveFleet(true); } catch (e) {}
|
|
15913
|
+
}
|
|
15914
|
+
},
|
|
15815
15915
|
};
|
|
15816
15916
|
}
|
|
15817
15917
|
|
|
@@ -16229,7 +16329,7 @@ ${calendarAddUrl.toString()}
|
|
|
16229
16329
|
requiredNote: "フォルダのURLを入力してください。",
|
|
16230
16330
|
// 🔴 **追加の欄は入力と[追加]だけ(c-07)。** 以前は「アカウントを招待 → URL を貼る → 何が起きるか」の
|
|
16231
16331
|
// 手順(コピー行つき)を置いていたが、足してから読めなければ、その行の中が一手を出す
|
|
16232
|
-
// (DS_GATE_CAP.materials.unreachable:
|
|
16332
|
+
// (DS_GATE_CAP.materials.unreachable: 「このフォルダを〈取り込み用アカウント〉に閲覧者として共有してください。[コピー]」)。
|
|
16233
16333
|
// 「中身が 共有資料 にコピーされ Markdown 化される・止め方」はモーダル上部の説明文が 1 回だけ持つ。
|
|
16234
16334
|
build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
|
|
16235
16335
|
}));
|
|
@@ -16448,7 +16548,7 @@ ${calendarAddUrl.toString()}
|
|
|
16448
16548
|
requiredNote: "チャンネルのURLを入力してください。",
|
|
16449
16549
|
// 🔴 **追加の欄は入力と[追加]だけ(c-07)。** 以前は「/invite を貼る → リンクを貼る → 組織不明なら接続 →
|
|
16450
16550
|
// Gold 昇格の対象」の手順 4 行を置いていたが、足してから読めなければ、そのチャンネルの行の中が
|
|
16451
|
-
//
|
|
16551
|
+
// 理由に合った一手(招待と接続。slackRowHelpParts)を出す。
|
|
16452
16552
|
// 「毎晩の Gold 昇格の対象・止め方」「お客様の Slack は管理者の承認が要る」は、モーダル上部の説明文が 1 回だけ持つ。
|
|
16453
16553
|
// **選んだワークスペースを送る(#104)。** 空文字=社内ワークスペースなのでキーごと落とす
|
|
16454
16554
|
// (サーバ側は「未宣言・null・空」を区別しない約束で、無いことで既定を表す)
|
|
@@ -16574,6 +16674,22 @@ ${calendarAddUrl.toString()}
|
|
|
16574
16674
|
};
|
|
16575
16675
|
}
|
|
16576
16676
|
|
|
16677
|
+
/**
|
|
16678
|
+
* 二度押しだけを止める(語もスピナーも出さない)。**楽観更新の回の送信中**に使う(#968 E)——
|
|
16679
|
+
* 画面はもう先へ進んでいるので、「処理中…」で待たされていると読ませない。
|
|
16680
|
+
* @returns 元へ戻す関数。何度呼んでも 1 回しか効かない
|
|
16681
|
+
*/
|
|
16682
|
+
function lockQuietly(btn) {
|
|
16683
|
+
if (!btn) return function () {};
|
|
16684
|
+
btn.disabled = true;
|
|
16685
|
+
var used = false;
|
|
16686
|
+
return function unlock() {
|
|
16687
|
+
if (used) return;
|
|
16688
|
+
used = true;
|
|
16689
|
+
btn.disabled = false;
|
|
16690
|
+
};
|
|
16691
|
+
}
|
|
16692
|
+
|
|
16577
16693
|
/**
|
|
16578
16694
|
* 反映待ちのオーバーレイ+スピナー(#378・共通部品)。
|
|
16579
16695
|
*
|
|
@@ -16624,23 +16740,55 @@ ${calendarAddUrl.toString()}
|
|
|
16624
16740
|
* **例外が接続先の一覧(#127)。** 追加・削除の応答にはいまの一覧が必ず
|
|
16625
16741
|
* 添えられていて(成功も、重複で断られたときも、世代がずれていたときも)、これは
|
|
16626
16742
|
* スナップショットではなく**サーバが書いた直後の確定値**なので、その場で描き直してよい。
|
|
16627
|
-
* @param hooks { onTargets(list) → 画面が見ていた集合と違ったか, staleNote }
|
|
16743
|
+
* @param hooks { onTargets(list) → 画面が見ていた集合と違ったか, staleNote, onOptimistic(), onRevert() }
|
|
16628
16744
|
* onTargets … 応答に添えられた一覧を画面へ取り込む。**成否を問わず先に呼ぶ**——
|
|
16629
16745
|
* 断られたときこそ、画面を正しい状態へ追いつかせないと堂々巡りになる
|
|
16746
|
+
* onOptimistic … 送信した瞬間に呼ぶ(本当の楽観更新・#968 E)。呼び手が自分で直す画面(通知先の札・入力欄)を先に更新する
|
|
16747
|
+
* onRevert … 断られた・通信が落ちた・何も変わらなかったときに呼ぶ。onOptimistic で直したものを元へ戻す
|
|
16748
|
+
*
|
|
16749
|
+
* ## 楽観更新(cortex-engine #968 E)
|
|
16750
|
+
* 仮置き(notePendingOp)を**送信時**に書き、待ち表示(「保存中…」・「処理中…」)を出さずに一覧を先に更新する。
|
|
16751
|
+
* 失敗(エラー応答・通信失敗・何も変わらなかった)では仮置きを**巻き戻して**理由を出す。
|
|
16752
|
+
* 仮置きを作れない操作(切り出せない入力・仮置きの対象でない op)は、従来どおり待ち表示を出して応答を待つ。
|
|
16630
16753
|
*/
|
|
16631
16754
|
async function sendConfig(payload, msg, btn, onDone, hooks) {
|
|
16632
16755
|
var intake = SITE.intake;
|
|
16633
16756
|
if (!intake) return;
|
|
16634
|
-
//
|
|
16635
|
-
var
|
|
16757
|
+
// 送信時の仮置き。作れたときは待ち表示を出さず、一覧を先に更新する
|
|
16758
|
+
var opt = noteOptimisticOp(payload, hooks && typeof hooks.pendingTargets === "function" ? hooks.pendingTargets() : null);
|
|
16759
|
+
// 押している間はボタンごと「処理中…」にする(#222・beginBusy の項)。**楽観更新の回は語を変えない**
|
|
16760
|
+
// (二度押しだけ止める。待たされていると読ませない)
|
|
16761
|
+
var endBusy = opt ? lockQuietly(btn) : beginBusy(btn);
|
|
16636
16762
|
msg.className = "ds-toggle-msg";
|
|
16637
|
-
msg.textContent = "保存中…";
|
|
16763
|
+
msg.textContent = opt ? "" : "保存中…";
|
|
16764
|
+
// 巻き戻す口。**何度呼んでも 1 回しか効かない**(失敗の分岐が複数あるため)
|
|
16765
|
+
var reverted = false;
|
|
16766
|
+
var optToken;
|
|
16767
|
+
var revert = function () {
|
|
16768
|
+
if (reverted) return;
|
|
16769
|
+
reverted = true;
|
|
16770
|
+
if (opt) rollbackOptimisticOp(opt);
|
|
16771
|
+
if (hooks && typeof hooks.onRevert === "function") hooks.onRevert(optToken);
|
|
16772
|
+
};
|
|
16773
|
+
if (opt && opt.entry.action !== "update") {
|
|
16774
|
+
// 追加・解除は、開いている一覧を今すぐ描き直す(応答後の描き直しと同じ 3 つ)
|
|
16775
|
+
repaintOpenLists();
|
|
16776
|
+
refreshDataSources();
|
|
16777
|
+
syncAiLinks();
|
|
16778
|
+
}
|
|
16779
|
+
if (hooks && typeof hooks.onOptimistic === "function") optToken = hooks.onOptimistic(payload);
|
|
16780
|
+
// 応答が届いたか。届いたあとの描画の例外で「保存できなかった」と巻き戻さないための印
|
|
16781
|
+
var answered = false;
|
|
16638
16782
|
try {
|
|
16639
16783
|
var res = await callIntake("config", payload);
|
|
16784
|
+
answered = true;
|
|
16640
16785
|
var j = await res.json().catch(function () { return {}; });
|
|
16641
16786
|
// 一覧が添えられていれば、断られていても先に画面を追いつかせる(#127)
|
|
16642
16787
|
var raced = hooks && hooks.onTargets && Array.isArray(j.targets) ? hooks.onTargets(j.targets) : false;
|
|
16643
16788
|
if (!res.ok) {
|
|
16789
|
+
// **送信時の仮置きを巻き戻す(#968 E)。** 先に戻す——重複の確認の画面は入力欄の値を読むし、
|
|
16790
|
+
// 「既に登録されています」の分岐は下で仮置きを書き直す
|
|
16791
|
+
revert();
|
|
16644
16792
|
// **確認すれば覆せる断り方(2026-08-20・Slack の重複だけ)。**
|
|
16645
16793
|
//
|
|
16646
16794
|
// 全体定例のように複数案件に関係するチャンネルは実在するのに、
|
|
@@ -16728,9 +16876,19 @@ ${calendarAddUrl.toString()}
|
|
|
16728
16876
|
// する——続きの「一覧への反映には数分かかります(再読み込みで最新になります)」は、
|
|
16729
16877
|
// その場で「反映待ち」の行が出ている横では嘘(intake の文面は本便では触らない)。
|
|
16730
16878
|
// **changed:false では書かない**(何も変わっていないのに追加したと見せない)
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16879
|
+
// **何も変わらなかった回は、送信時の仮置きを巻き戻す**(変わっていないのに追加・解除したと見せない・#968 E)
|
|
16880
|
+
if (j.changed === false) revert();
|
|
16881
|
+
var pendingEntry = null;
|
|
16882
|
+
if (j.changed !== false) {
|
|
16883
|
+
var ptargets = hooks && typeof hooks.pendingTargets === "function" ? hooks.pendingTargets() : null;
|
|
16884
|
+
if (opt) {
|
|
16885
|
+
// 送信時に書いた自分の行を確定させる(あとの操作に置き換えられていれば何もしない)
|
|
16886
|
+
settleOptimisticOp(opt, payload, ptargets, j);
|
|
16887
|
+
pendingEntry = opt.entry;
|
|
16888
|
+
} else {
|
|
16889
|
+
pendingEntry = notePendingOp(payload, ptargets, j);
|
|
16890
|
+
}
|
|
16891
|
+
}
|
|
16734
16892
|
msg.className = "ds-toggle-msg is-ok";
|
|
16735
16893
|
msg.textContent = "";
|
|
16736
16894
|
msg.appendChild(document.createTextNode("✓ " + ((pendingEntry ? noteHeadline(j.message) : j.message) || "保存しました。")));
|
|
@@ -16738,7 +16896,8 @@ ${calendarAddUrl.toString()}
|
|
|
16738
16896
|
// 起動できなかった案件(権限・スタブ未配布)ではリンクを出さない——押しても何も無い
|
|
16739
16897
|
if (j.runsUrl) {
|
|
16740
16898
|
msg.appendChild(document.createTextNode(" "));
|
|
16741
|
-
|
|
16899
|
+
// **同期の一覧へのリンク**(run そのものは特定しない——応答は起動を待たない・#968 D)。起動できたかはその一覧で見る
|
|
16900
|
+
msg.appendChild(el("a", { href: j.runsUrl, target: "_blank", rel: "noopener", title: "GitHub Actions の同期の一覧を開きます(プロジェクトリポジトリへのアクセスが要ります)", text: "同期の一覧を見る →" }));
|
|
16742
16901
|
}
|
|
16743
16902
|
// **「反映の状況 →」のリンクは無くなった(#221・2026-08-25)。** 指していたのは
|
|
16744
16903
|
// 「画面を追いつかせるための fleet-status の実行」で、その起動ごと撤去した
|
|
@@ -16792,9 +16951,13 @@ ${calendarAddUrl.toString()}
|
|
|
16792
16951
|
// 第2引数は仮置きの結果(#388)。控えを出すかの判断に使う(addForm)。既存の受け側は見ない
|
|
16793
16952
|
if (onDone && j.changed !== false) onDone(j, { pending: pendingEntry });
|
|
16794
16953
|
} catch (e) {
|
|
16954
|
+
// 通信の失敗: 送信時の仮置きを巻き戻して理由を出す(#968 E)。サーバに届いていた可能性があるので、
|
|
16955
|
+
// 本物の登録を読み直す(保存できていれば行は live から出る)
|
|
16956
|
+
if (!answered) revert();
|
|
16795
16957
|
msg.className = "ds-toggle-msg is-error";
|
|
16796
16958
|
msg.textContent = "✗ 通信に失敗しました。";
|
|
16797
16959
|
endBusy();
|
|
16960
|
+
if (opt && !answered) { try { loadLiveFleet(true); } catch (e2) {} }
|
|
16798
16961
|
}
|
|
16799
16962
|
}
|
|
16800
16963
|
|
|
@@ -17100,8 +17263,26 @@ ${calendarAddUrl.toString()}
|
|
|
17100
17263
|
// 逆に接続先の一覧(Backlog)は onTargets を持つが確認は要らない——
|
|
17101
17264
|
// 共通に足すと、あちらの「他の人が先に変えていた」の判定と混ざる。
|
|
17102
17265
|
var h = opts.hooks ? opts.hooks() : null;
|
|
17103
|
-
|
|
17266
|
+
// **送信した瞬間に入力欄を空にし(楽観更新・#968 E)、断られたら元の値へ戻す**(打ち直させない)
|
|
17267
|
+
var sentMain = "";
|
|
17268
|
+
var sentName = "";
|
|
17104
17269
|
h = h || {};
|
|
17270
|
+
// 呼び手の hooks(接続先の一覧の楽観更新など)が既にあれば、その後ろに続ける
|
|
17271
|
+
var baseOptimistic = h.onOptimistic;
|
|
17272
|
+
var baseRevert = h.onRevert;
|
|
17273
|
+
h.onOptimistic = function (sentPayload) {
|
|
17274
|
+
sentMain = main.value;
|
|
17275
|
+
sentName = name ? name.value : "";
|
|
17276
|
+
main.value = "";
|
|
17277
|
+
if (name) name.value = "";
|
|
17278
|
+
return baseOptimistic ? baseOptimistic(sentPayload) : undefined;
|
|
17279
|
+
};
|
|
17280
|
+
h.onRevert = function (tok) {
|
|
17281
|
+
if (!main.value) main.value = sentMain;
|
|
17282
|
+
if (name && !name.value) name.value = sentName;
|
|
17283
|
+
if (baseRevert) baseRevert(tok);
|
|
17284
|
+
};
|
|
17285
|
+
if (!opts.duplicateConfirmLines) return h;
|
|
17105
17286
|
// **返り値が「進めてよいか」(Promise)。** 呼び出し側は必ず await すること——
|
|
17106
17287
|
// await を落とすと Promise は truthy なので**確認なしで「はい」扱いになる**。
|
|
17107
17288
|
// 2026-08-20 に window.confirm から画面内のモーダルに変えた(openConfirm)。
|
|
@@ -17442,12 +17623,15 @@ ${calendarAddUrl.toString()}
|
|
|
17442
17623
|
// 常設の接続ボタンを撤去したので、0 件の案件に顧客の Slack を足した人が接続へ進む入口はこの行だけ
|
|
17443
17624
|
// (#138 型の穴を塞ぐ)。まだ gate が付いていない行(仮置き・登録の反映だけが先に届いた行)でも、
|
|
17444
17625
|
// 印(orgUnknown)があれば出す。**印が無い行(測っていない)には何も出さない**
|
|
17445
|
-
|
|
17626
|
+
// 顧客の no_token(接続で直ると確かな行)も、この行のタイルの中に一手を出す(g-66)
|
|
17627
|
+
if (s.type === "slack" && s.gate === "no_token" && slackRowNeedsConnect(s)) {
|
|
17628
|
+
card.appendChild(restingHelpNode(slackRowHelpParts(s), "ds-gate-help ds-gate-resting"));
|
|
17629
|
+
} else if (s.type === "slack" && s.orgUnknown === true && !s.gate && SITE.intake) {
|
|
17446
17630
|
var pendingParts = slackRowHelpParts(s);
|
|
17447
17631
|
if (pendingParts && pendingParts.length) card.appendChild(restingHelpNode(pendingParts, "ds-gate-help ds-gate-resting"));
|
|
17448
17632
|
} else if (!(opts && opts.quietHelp) && showsRestingReason(s)) {
|
|
17449
17633
|
var restingParts = restingHelpPartsOf(s.type, s.gate, s);
|
|
17450
|
-
//
|
|
17634
|
+
// 空の配列は「行には何も出さない」。夜間の理由文にも落とさない
|
|
17451
17635
|
if (restingParts) { if (restingParts.length) card.appendChild(restingHelpNode(restingParts, "ds-gate-help ds-gate-resting")); }
|
|
17452
17636
|
else if (!(String(s.type).indexOf("github") === 0 && (s.gate === "unreachable" || s.gate === "no_token") && !githubOrgRefused(s))) {
|
|
17453
17637
|
// GitHub の読めない行は表が一手を決める(出さないと決めた行=確かめられなかった組織には、
|
package/package.json
CHANGED