@takagaki/cortex-decisions-viewer 0.8.0 → 0.9.1
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 +760 -36
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REFLECT_OPS = exports.REFLECTING_LIMITS = void 0;
|
|
3
|
+
exports.UNSETTLED_LIMITS = exports.REFLECT_OPS = exports.REFLECTING_LIMITS = void 0;
|
|
4
4
|
exports.renderSite = renderSite;
|
|
5
5
|
exports.livePending = livePending;
|
|
6
6
|
exports.nextCheckDelay = nextCheckDelay;
|
|
7
7
|
exports.settledPending = settledPending;
|
|
8
|
+
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
9
|
+
exports.chatRowsToAdd = chatRowsToAdd;
|
|
10
|
+
exports.devRowsToAdd = devRowsToAdd;
|
|
11
|
+
exports.designRowsToAdd = designRowsToAdd;
|
|
12
|
+
exports.materialRowsToAdd = materialRowsToAdd;
|
|
13
|
+
exports.issueRowsToAdd = issueRowsToAdd;
|
|
14
|
+
exports.unsettledRegisteredCount = unsettledRegisteredCount;
|
|
15
|
+
exports.registeredRowsToAdd = registeredRowsToAdd;
|
|
16
|
+
exports.mergeRegisteredFleet = mergeRegisteredFleet;
|
|
17
|
+
exports.nextStateDelay = nextStateDelay;
|
|
8
18
|
exports.renderConnectionMap = renderConnectionMap;
|
|
9
19
|
/** SiteDataを単一の自己完結HTML(埋め込みデータ+CSS+JS)にレンダリングする */
|
|
10
20
|
function renderSite(site) {
|
|
@@ -183,6 +193,12 @@ const CM_TOOL_LABEL = {
|
|
|
183
193
|
function sourceNeedsAttention(s) {
|
|
184
194
|
if (s.gate === "ok")
|
|
185
195
|
return false;
|
|
196
|
+
// 🔴 **測っていない行を警告に数えない(段階1)。** 状態が無いのは「実測して分からなかった」
|
|
197
|
+
// ではなく「まだ誰も測っていない」——いま登録したばかりの行がこれになる。数えると
|
|
198
|
+
// **登録した直後に「⚠️ 1件に問題」と出て、タイルが警告色になる**(登録は成功している)。
|
|
199
|
+
// `unknown` は残す——あちらは測ったうえで分からなかった、という別の事実。
|
|
200
|
+
if (s.gate == null || s.gate === "")
|
|
201
|
+
return false;
|
|
186
202
|
if (s.goldState === "excluded")
|
|
187
203
|
return false;
|
|
188
204
|
return true;
|
|
@@ -472,6 +488,336 @@ function settledPending(list, presentByCap) {
|
|
|
472
488
|
}
|
|
473
489
|
return out;
|
|
474
490
|
}
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// 登録の即時値(registeredNow)— **まとめ表がまだ知らない行を足す**(段階1)
|
|
493
|
+
//
|
|
494
|
+
// 守るべき不変条件は3つ:
|
|
495
|
+
// 1. **足すだけ・置き換えない**(origin / goldState / notify / gate はまとめ表にしか無い)
|
|
496
|
+
// 2. **既知の欄だけ・葉は文字列だけ**(ライブ専用のキーなので build.ts の網にかからない)
|
|
497
|
+
// 3. **正規化しない**(engine が読むのは生値。揃えにいくと「画面には出るのに読まれない」行ができる)
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
/** 応答のキー(ASCII)→ 画面の能力名。**対応表はここ1つだけ** */
|
|
500
|
+
const REGISTERED_CAP_NAMES = {
|
|
501
|
+
chat: "チャット",
|
|
502
|
+
dev: "開発",
|
|
503
|
+
design: "デザイン",
|
|
504
|
+
materials: "共有資料",
|
|
505
|
+
issues: "課題管理",
|
|
506
|
+
};
|
|
507
|
+
/** 通してよい欄。**ここに無い欄は1つも通さない**(Lambda 側の列挙と同じ形) */
|
|
508
|
+
const REGISTERED_FIELDS = {
|
|
509
|
+
chat: ["ref", "name", "workspace"],
|
|
510
|
+
dev: ["ref", "type"],
|
|
511
|
+
design: ["key", "name"],
|
|
512
|
+
materials: ["id", "name"],
|
|
513
|
+
issues: ["domain", "projectKey"],
|
|
514
|
+
};
|
|
515
|
+
/** 欠けていたら行として読めない欄(「cm1.backlog.jp / 」のような半端な行を出さない) */
|
|
516
|
+
const REGISTERED_REQUIRED = {
|
|
517
|
+
chat: ["ref"],
|
|
518
|
+
dev: ["ref"],
|
|
519
|
+
design: ["key"],
|
|
520
|
+
materials: ["id"],
|
|
521
|
+
issues: ["domain", "projectKey"],
|
|
522
|
+
};
|
|
523
|
+
/** 開発の種別に許す値。**知らない値は落とす**(画面のラベル・振り分けが決まらない) */
|
|
524
|
+
const REGISTERED_DEV_TYPES = ["github-issues", "github-discussions"];
|
|
525
|
+
/**
|
|
526
|
+
* 応答の registeredNow を、画面が扱ってよい形へ畳む。**通らなかったものは黙って落とす。**
|
|
527
|
+
*
|
|
528
|
+
* 返すのは能力ごとの配列だけ(`asOf` は落とす——「◯◯時点」の出し分けは段階3で1箇所にする)。
|
|
529
|
+
* 1能力も読めなければ null。
|
|
530
|
+
*/
|
|
531
|
+
function sanitizeRegisteredNow(v) {
|
|
532
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
533
|
+
return null;
|
|
534
|
+
const out = {};
|
|
535
|
+
let any = false;
|
|
536
|
+
for (const cap in REGISTERED_FIELDS) {
|
|
537
|
+
if (!Object.prototype.hasOwnProperty.call(REGISTERED_FIELDS, cap))
|
|
538
|
+
continue;
|
|
539
|
+
const rows = v[cap];
|
|
540
|
+
if (!Array.isArray(rows))
|
|
541
|
+
continue; // 配列でなければキーごと落とす(0件と混ぜない)
|
|
542
|
+
const keep = [];
|
|
543
|
+
for (const r of rows) {
|
|
544
|
+
if (r == null || typeof r !== "object" || Array.isArray(r))
|
|
545
|
+
continue;
|
|
546
|
+
const item = {};
|
|
547
|
+
for (const f of REGISTERED_FIELDS[cap]) {
|
|
548
|
+
const fv = r[f];
|
|
549
|
+
if (typeof fv !== "string" || fv === "")
|
|
550
|
+
continue; // 葉は文字列だけ
|
|
551
|
+
item[f] = fv;
|
|
552
|
+
}
|
|
553
|
+
if (cap === "dev" && item.type && REGISTERED_DEV_TYPES.indexOf(item.type) < 0)
|
|
554
|
+
delete item.type;
|
|
555
|
+
let full = true;
|
|
556
|
+
for (const f of REGISTERED_REQUIRED[cap])
|
|
557
|
+
if (!item[f])
|
|
558
|
+
full = false;
|
|
559
|
+
if (full)
|
|
560
|
+
keep.push(item);
|
|
561
|
+
}
|
|
562
|
+
out[cap] = keep;
|
|
563
|
+
any = true;
|
|
564
|
+
}
|
|
565
|
+
return any ? out : null;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* チャットの行で、まとめ表がまだ知らないもの。
|
|
569
|
+
*
|
|
570
|
+
* 🔴 **鍵はチャンネルID+ワークスペース。** チャンネルIDはワークスペースの中でしか
|
|
571
|
+
* 一意でないので、IDだけで突き合わせると別ワークスペースの同IDを同じものと見て
|
|
572
|
+
* 行が出ない(#104 で同じ理由の事故がある)。
|
|
573
|
+
*/
|
|
574
|
+
function chatRowsToAdd(externals, rows) {
|
|
575
|
+
const have = {};
|
|
576
|
+
for (const s of externals || []) {
|
|
577
|
+
if (!s || s.type !== "slack" || !s.ref)
|
|
578
|
+
continue;
|
|
579
|
+
have[String(s.workspace || "") + "\t" + String(s.ref)] = true;
|
|
580
|
+
}
|
|
581
|
+
const out = [];
|
|
582
|
+
for (const r of rows || []) {
|
|
583
|
+
if (!r || !r.ref)
|
|
584
|
+
continue;
|
|
585
|
+
if (have[String(r.workspace || "") + "\t" + String(r.ref)])
|
|
586
|
+
continue;
|
|
587
|
+
const row = { type: "slack", name: r.name || r.ref, ref: r.ref };
|
|
588
|
+
if (r.workspace)
|
|
589
|
+
row.workspace = r.workspace;
|
|
590
|
+
out.push(row);
|
|
591
|
+
}
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* 開発の行で、まとめ表がまだ知らないもの。**鍵は `owner/repo` の生文字列。**
|
|
596
|
+
*
|
|
597
|
+
* **種別(issues / discussions)は登録ファイルの宣言をそのまま使う。** 無いときだけ
|
|
598
|
+
* Issues に倒す——ここで決め打ちすると、Discussions だけを登録した案件のラベルが嘘になる。
|
|
599
|
+
*/
|
|
600
|
+
function devRowsToAdd(externals, rows) {
|
|
601
|
+
const have = {};
|
|
602
|
+
for (const s of externals || []) {
|
|
603
|
+
if (!s || String(s.type || "").indexOf("github") !== 0 || !s.ref)
|
|
604
|
+
continue;
|
|
605
|
+
have[String(s.ref)] = true;
|
|
606
|
+
}
|
|
607
|
+
const out = [];
|
|
608
|
+
for (const r of rows || []) {
|
|
609
|
+
if (!r || !r.ref)
|
|
610
|
+
continue;
|
|
611
|
+
if (have[String(r.ref)])
|
|
612
|
+
continue;
|
|
613
|
+
out.push({ type: r.type || "github-issues", name: r.ref, ref: r.ref });
|
|
614
|
+
}
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
617
|
+
/** デザインの行で、まとめ表がまだ知らないもの(鍵はファイルキー) */
|
|
618
|
+
function designRowsToAdd(files, rows) {
|
|
619
|
+
const have = {};
|
|
620
|
+
for (const f of files || [])
|
|
621
|
+
if (f && f.key)
|
|
622
|
+
have[String(f.key)] = true;
|
|
623
|
+
const out = [];
|
|
624
|
+
for (const r of rows || []) {
|
|
625
|
+
if (!r || !r.key || have[String(r.key)])
|
|
626
|
+
continue;
|
|
627
|
+
// 名前は人が入力した別名。無ければキーを出す(既存の行と同じ扱い)
|
|
628
|
+
out.push({ key: r.key, name: r.name || r.key });
|
|
629
|
+
}
|
|
630
|
+
return out;
|
|
631
|
+
}
|
|
632
|
+
/** 共有資料の行で、まとめ表がまだ知らないもの(鍵はフォルダID) */
|
|
633
|
+
function materialRowsToAdd(ids, rows) {
|
|
634
|
+
const have = {};
|
|
635
|
+
for (const id of ids || [])
|
|
636
|
+
if (typeof id === "string" && id)
|
|
637
|
+
have[id] = true;
|
|
638
|
+
const out = [];
|
|
639
|
+
for (const r of rows || []) {
|
|
640
|
+
if (!r || !r.id || have[String(r.id)])
|
|
641
|
+
continue;
|
|
642
|
+
const row = { id: r.id };
|
|
643
|
+
if (r.name)
|
|
644
|
+
row.name = r.name;
|
|
645
|
+
out.push(row);
|
|
646
|
+
}
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 課題管理の行で、まとめ表がまだ知らないもの。
|
|
651
|
+
*
|
|
652
|
+
* **畳み方は `targetsGenerationOf`(サーバの `targetsGeneration`)と同じ**
|
|
653
|
+
* ——ドメインは小文字・キーは大文字。ここだけ別の畳み方にすると、同じ接続先が
|
|
654
|
+
* 二重に並ぶ(片方は削除ボタンを持たない)。
|
|
655
|
+
*/
|
|
656
|
+
function issueRowsToAdd(targets, rows) {
|
|
657
|
+
const key = (d, k) => String(d).toLowerCase() + "/" + String(k).toUpperCase();
|
|
658
|
+
const have = {};
|
|
659
|
+
for (const t of targets || [])
|
|
660
|
+
if (t && t.domain && t.projectKey)
|
|
661
|
+
have[key(t.domain, t.projectKey)] = true;
|
|
662
|
+
const out = [];
|
|
663
|
+
for (const r of rows || []) {
|
|
664
|
+
if (!r || !r.domain || !r.projectKey)
|
|
665
|
+
continue;
|
|
666
|
+
if (have[key(r.domain, r.projectKey)])
|
|
667
|
+
continue;
|
|
668
|
+
out.push({ domain: r.domain, projectKey: r.projectKey });
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
/** 同期ソース(internalSources)から、その能力の1件を取る */
|
|
673
|
+
function internalOf(list, kind) {
|
|
674
|
+
for (const s of list || [])
|
|
675
|
+
if (s && s.kind === kind)
|
|
676
|
+
return s;
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* まとめ表に足りない行を数える。**「未確定の列がある間は取り直す」の判断材料。**
|
|
681
|
+
*
|
|
682
|
+
* 数えるのは「登録ファイルには有るのに、まとめ表がまだ答えていない行」だけ。
|
|
683
|
+
* まとめ表が既に持っている行は、状態(gate)が無くても数えない——**取り直しても
|
|
684
|
+
* 変わらない**(夜間の実測が入るまで誰も測らない)ので、数えると15分間ずっと
|
|
685
|
+
* GitHub を叩き続けることになる。
|
|
686
|
+
*/
|
|
687
|
+
function unsettledRegisteredCount(view, reg) {
|
|
688
|
+
if (!reg)
|
|
689
|
+
return 0;
|
|
690
|
+
const rows = registeredRowsToAdd(view, reg);
|
|
691
|
+
return rows.externalSources.length + rows.design.length + rows.materials.length + rows.issues.length;
|
|
692
|
+
}
|
|
693
|
+
/** 足すべき行を能力ごとに求める(マージと件数の唯一の出どころ) */
|
|
694
|
+
function registeredRowsToAdd(view, reg) {
|
|
695
|
+
const v = view || {};
|
|
696
|
+
const r = reg || {};
|
|
697
|
+
const internals = v.internalSources || [];
|
|
698
|
+
const design = internalOf(internals, REGISTERED_CAP_NAMES.design);
|
|
699
|
+
const materials = internalOf(internals, REGISTERED_CAP_NAMES.materials);
|
|
700
|
+
const bt = v.backlogTarget;
|
|
701
|
+
const targets = bt && Array.isArray(bt.targets) ? bt.targets : bt ? [bt] : [];
|
|
702
|
+
return {
|
|
703
|
+
externalSources: chatRowsToAdd(v.externalSources, r.chat).concat(devRowsToAdd(v.externalSources, r.dev)),
|
|
704
|
+
design: designRowsToAdd(design && design.figmaFiles, r.design),
|
|
705
|
+
materials: materialRowsToAdd(materials && materials.driveFolderIds, r.materials),
|
|
706
|
+
issues: issueRowsToAdd(targets, r.issues),
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* まとめ表の3キー(externalSources / internalSources / backlogTarget)に、足りない行を足す。
|
|
711
|
+
*
|
|
712
|
+
* **足すだけ。** 既存の行・欄は1つも書き換えない。
|
|
713
|
+
*
|
|
714
|
+
* ⚠ 同期ソースの行がまだ無い能力(=夜間バッチが一度も走っていない案件)には、
|
|
715
|
+
* **その能力の行を作る**——作らないと、登録した Figma ファイル・Drive フォルダを
|
|
716
|
+
* 置く場所が画面に無い。作る行は `kind` と一覧だけで、最終同期・件数・ラベルは装わない。
|
|
717
|
+
* 共有資料だけは `driveState` が無いと一覧そのものが描かれないので、
|
|
718
|
+
* **登録ファイルを実際に読めたという根拠がある場合に限り** `on` を置く
|
|
719
|
+
* (まとめ表が持っている値は上書きしない)。
|
|
720
|
+
*/
|
|
721
|
+
function mergeRegisteredFleet(view, reg) {
|
|
722
|
+
const v = view || {};
|
|
723
|
+
const out = {
|
|
724
|
+
externalSources: v.externalSources,
|
|
725
|
+
internalSources: v.internalSources,
|
|
726
|
+
backlogTarget: v.backlogTarget,
|
|
727
|
+
};
|
|
728
|
+
if (!reg)
|
|
729
|
+
return out;
|
|
730
|
+
const add = registeredRowsToAdd(v, reg);
|
|
731
|
+
if (add.externalSources.length)
|
|
732
|
+
out.externalSources = (v.externalSources || []).concat(add.externalSources);
|
|
733
|
+
if (add.design.length || add.materials.length) {
|
|
734
|
+
const internals = (v.internalSources || []).slice();
|
|
735
|
+
const put = (kind, patch) => {
|
|
736
|
+
for (let i = 0; i < internals.length; i++) {
|
|
737
|
+
if (internals[i] && internals[i].kind === kind) {
|
|
738
|
+
internals[i] = Object.assign({}, internals[i], patch);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
internals.push(Object.assign({ kind: kind }, patch));
|
|
743
|
+
};
|
|
744
|
+
if (add.design.length) {
|
|
745
|
+
const cur = internalOf(internals, REGISTERED_CAP_NAMES.design);
|
|
746
|
+
put(REGISTERED_CAP_NAMES.design, { figmaFiles: ((cur && cur.figmaFiles) || []).concat(add.design) });
|
|
747
|
+
}
|
|
748
|
+
if (add.materials.length) {
|
|
749
|
+
const cur = internalOf(internals, REGISTERED_CAP_NAMES.materials);
|
|
750
|
+
const ids = ((cur && cur.driveFolderIds) || []).concat(add.materials.map((x) => x.id));
|
|
751
|
+
const names = Object.assign({}, (cur && cur.driveFolderNames) || {});
|
|
752
|
+
for (const x of add.materials)
|
|
753
|
+
if (x.name)
|
|
754
|
+
names[x.id] = x.name;
|
|
755
|
+
const patch = { driveFolderIds: ids, driveFolderNames: names };
|
|
756
|
+
// まとめ表が状態を持っていないときだけ置く(持っている値には触らない)
|
|
757
|
+
if (!cur || !cur.driveState)
|
|
758
|
+
patch.driveState = "on";
|
|
759
|
+
put(REGISTERED_CAP_NAMES.materials, patch);
|
|
760
|
+
}
|
|
761
|
+
out.internalSources = internals;
|
|
762
|
+
}
|
|
763
|
+
if (add.issues.length) {
|
|
764
|
+
const bt = v.backlogTarget;
|
|
765
|
+
const cur = bt && Array.isArray(bt.targets) ? bt.targets : bt && bt.domain && bt.projectKey ? [bt] : [];
|
|
766
|
+
const targets = cur.concat(add.issues);
|
|
767
|
+
// 単数の2つは**先頭の接続先と同じ値**(この配列を持たない読み手のために残っている欄)
|
|
768
|
+
out.backlogTarget = Object.assign({}, bt || {}, {
|
|
769
|
+
domain: targets[0].domain,
|
|
770
|
+
projectKey: targets[0].projectKey,
|
|
771
|
+
targets: targets,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
return out;
|
|
775
|
+
}
|
|
776
|
+
// ---------------------------------------------------------------------------
|
|
777
|
+
// 未確定の列がある間の取り直し(段階1の前提)
|
|
778
|
+
//
|
|
779
|
+
// 🔴 **これが無いと、登録したあと画面が二度と更新されない。** 60秒ごとの取り直しは
|
|
780
|
+
// これまで「反映中」の記録がある間だけ張られていたが、registeredNow が入ると
|
|
781
|
+
// 追加した瞬間に「もう出ている」と判定されて記録が消える。
|
|
782
|
+
//
|
|
783
|
+
// **上限とタブ非表示の扱いは、反映待ち(livePending / nextCheckDelay)と同じ考え方にする。**
|
|
784
|
+
// - 上限は**経過時間**で見る(回数カウンタで数えない)。バックグラウンドタブの
|
|
785
|
+
// スロットリングやスリープ復帰は、経過時間で見る限り自己修復する
|
|
786
|
+
// - **可視性のイベントは足さない。** 遅れて起きたタイマがその回で期限切れを落とすので、
|
|
787
|
+
// 非表示のあいだは自然に間隔が伸びるだけ。イベントを足すと、復帰の瞬間に
|
|
788
|
+
// 全タブが同時に叩く形を新しく作ることになる
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
/** 未確定の行があっても、取り直しを続ける上限(開始からの経過)。反映待ちと同じ15分 */
|
|
791
|
+
const UNSETTLED_TTL_MS = 15 * 60 * 1000;
|
|
792
|
+
/** 取り直しの間隔。60秒(op=state のキャッシュと同じ粒度) */
|
|
793
|
+
const UNSETTLED_POLL_MS = 60 * 1000;
|
|
794
|
+
/**
|
|
795
|
+
* 期限と間隔を**テストから固定するための入れ物**(REFLECTING_LIMITS と同じ作法)。
|
|
796
|
+
*
|
|
797
|
+
* ⚠ **定数そのものを export してはいけない**(CommonJS へ落ちるときに `exports.X` へ
|
|
798
|
+
* 書き換えられ、toString() で流し込んだ先には exports が無い)。
|
|
799
|
+
*/
|
|
800
|
+
exports.UNSETTLED_LIMITS = { ttlMs: UNSETTLED_TTL_MS, pollMs: UNSETTLED_POLL_MS };
|
|
801
|
+
/**
|
|
802
|
+
* 次に取り直すまでの ms。もう取り直す必要が無ければ null。
|
|
803
|
+
*
|
|
804
|
+
* @param count 未確定の行数(0 なら張らない)
|
|
805
|
+
* @param since 取り直しを始めた時刻(Date.now())
|
|
806
|
+
*
|
|
807
|
+
* 🔴 **経過は「上」だけでなく「下」も見る**(livePending と同じ理由)。端末の時計が
|
|
808
|
+
* 進んだ状態で始めて NTP で戻ると、`since` が未来になり**永久に期限が切れない**。
|
|
809
|
+
*/
|
|
810
|
+
function nextStateDelay(count, since, now) {
|
|
811
|
+
if (!(count > 0))
|
|
812
|
+
return null;
|
|
813
|
+
const age = now - since;
|
|
814
|
+
if (!Number.isFinite(age) || age < 0)
|
|
815
|
+
return null;
|
|
816
|
+
const left = UNSETTLED_TTL_MS - age;
|
|
817
|
+
if (left <= 0)
|
|
818
|
+
return null;
|
|
819
|
+
return left < UNSETTLED_POLL_MS ? left : UNSETTLED_POLL_MS;
|
|
820
|
+
}
|
|
475
821
|
function capabilityState(cap, ctx) {
|
|
476
822
|
// **能力→パイプラインの対応はここに持つ。** 呼び出し側に渡させると、
|
|
477
823
|
// クライアント側(この関数を .toString() で受け取る側)が同じ表を書き写すことになり、
|
|
@@ -1760,6 +2106,25 @@ var REFLECT_OPS = ${JSON.stringify(exports.REFLECT_OPS)};
|
|
|
1760
2106
|
${livePending.toString()}
|
|
1761
2107
|
${nextCheckDelay.toString()}
|
|
1762
2108
|
${settledPending.toString()}
|
|
2109
|
+
// 登録の即時値(registeredNow)の受け入れとマージ。**判定の実体はビルド時の定義**
|
|
2110
|
+
// ——画面側に書き写すと、許可リストが2枚になって列挙し忘れが静かに消える
|
|
2111
|
+
var REGISTERED_CAP_NAMES = ${JSON.stringify(REGISTERED_CAP_NAMES)};
|
|
2112
|
+
var REGISTERED_FIELDS = ${JSON.stringify(REGISTERED_FIELDS)};
|
|
2113
|
+
var REGISTERED_REQUIRED = ${JSON.stringify(REGISTERED_REQUIRED)};
|
|
2114
|
+
var REGISTERED_DEV_TYPES = ${JSON.stringify(REGISTERED_DEV_TYPES)};
|
|
2115
|
+
var UNSETTLED_TTL_MS = ${JSON.stringify(UNSETTLED_TTL_MS)};
|
|
2116
|
+
var UNSETTLED_POLL_MS = ${JSON.stringify(UNSETTLED_POLL_MS)};
|
|
2117
|
+
${sanitizeRegisteredNow.toString()}
|
|
2118
|
+
${chatRowsToAdd.toString()}
|
|
2119
|
+
${devRowsToAdd.toString()}
|
|
2120
|
+
${designRowsToAdd.toString()}
|
|
2121
|
+
${materialRowsToAdd.toString()}
|
|
2122
|
+
${issueRowsToAdd.toString()}
|
|
2123
|
+
${internalOf.toString()}
|
|
2124
|
+
${registeredRowsToAdd.toString()}
|
|
2125
|
+
${unsettledRegisteredCount.toString()}
|
|
2126
|
+
${mergeRegisteredFleet.toString()}
|
|
2127
|
+
${nextStateDelay.toString()}
|
|
1763
2128
|
// 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
1764
2129
|
var TUTORIAL = (function () {
|
|
1765
2130
|
var elx = document.getElementById("tutorial-data");
|
|
@@ -2316,6 +2681,19 @@ ${settledPending.toString()}
|
|
|
2316
2681
|
"no_token": { text: "トークン未設定", cls: "ds-gate-muted" },
|
|
2317
2682
|
"unknown": { text: "不明", cls: "ds-gate-muted" },
|
|
2318
2683
|
};
|
|
2684
|
+
/* ---------------------------------------------------------------------------
|
|
2685
|
+
* まだ誰も測っていない行(段階1)
|
|
2686
|
+
*
|
|
2687
|
+
* いま登録したばかりで、夜間の実測にも「接続を確認」にも掛かっていない状態。
|
|
2688
|
+
* **「未接続」とも「不明」とも書かない**——前者は測って繋がっていなかった、
|
|
2689
|
+
* 後者は測って分からなかった、という別の事実で、どちらも読み手に
|
|
2690
|
+
* 「直す必要がある」と読ませる(登録そのものは成功している)。
|
|
2691
|
+
*
|
|
2692
|
+
* 🔴 **DS_GATE には入れない。** あちらは**実測が返す語彙**で、判定側(slack-gate.mjs)と
|
|
2693
|
+
* 1対1で一致していることをテストが見張っている(足し忘れた判定が「不明」に落ちる事故の番人)。
|
|
2694
|
+
* 未測定は実測の結果ではないので、同じ表に混ぜるとその番人が意味を失う。
|
|
2695
|
+
* ------------------------------------------------------------------------- */
|
|
2696
|
+
var DS_GATE_PENDING = { text: "登録状況を確認中です", cls: "ds-gate-muted" };
|
|
2319
2697
|
/* ---------------------------------------------------------------------------
|
|
2320
2698
|
* no_token には**経路が2つある**(2026-08-18 の不具合報告)
|
|
2321
2699
|
*
|
|
@@ -2420,6 +2798,10 @@ ${settledPending.toString()}
|
|
|
2420
2798
|
// github-issues / github-discussions のどちらも同じ原因(組織単位の App)
|
|
2421
2799
|
if (String(s.type || "").indexOf("github") === 0) return DS_GATE_NO_TOKEN.github;
|
|
2422
2800
|
}
|
|
2801
|
+
// 🔴 **状態が無い行を「不明」に落とさない(段階1)。** 「不明」は実測して分からなかった
|
|
2802
|
+
// という意味で、まだ測っていない行に出すと嘘になる。**知らない値は従来どおり「不明」**
|
|
2803
|
+
// ——そちらは engine が新しい状態を足したのに画面が知らない、という別の事実
|
|
2804
|
+
if (s.gate == null || s.gate === "") return DS_GATE_PENDING;
|
|
2423
2805
|
return DS_GATE[s.gate] || DS_GATE["unknown"];
|
|
2424
2806
|
}
|
|
2425
2807
|
/**
|
|
@@ -3131,10 +3513,23 @@ ${settledPending.toString()}
|
|
|
3131
3513
|
*/
|
|
3132
3514
|
var LIVE_REALTIME = null;
|
|
3133
3515
|
/**
|
|
3134
|
-
*
|
|
3516
|
+
* いま登録ファイルに何が書いてあるか(op=state の registeredNow・段階1)。取得前は null。
|
|
3517
|
+
*
|
|
3518
|
+
* **まとめ表(fleet)とは別に持つ。** fleet は夜間バッチの成果物の射影で、こちらは
|
|
3519
|
+
* いまのリポジトリを読んだ結果。同じ入れ物に混ぜると、上に出す「◯◯時点」がどちらを
|
|
3520
|
+
* 指すのか説明できなくなる(realtime を別に持っているのと同じ理由)。
|
|
3521
|
+
*/
|
|
3522
|
+
var LIVE_REGISTERED = null;
|
|
3523
|
+
/** マージ済みの3キー(作り直しは setLive* だけが行う) */
|
|
3524
|
+
var MERGED = null;
|
|
3525
|
+
/** マージの対象になるキー。これ以外は素の値をそのまま返す */
|
|
3526
|
+
var MERGE_KEYS = { externalSources: 1, internalSources: 1, backlogTarget: 1 };
|
|
3527
|
+
|
|
3528
|
+
/**
|
|
3529
|
+
* 接続状況の素の値(まとめ表だけ)。ライブがあればライブ、無ければ静的ビルドの値。
|
|
3135
3530
|
* SITE 側はキー名が fleetGeneratedAt だけ異なる(他は fleet-status.json と同名)。
|
|
3136
3531
|
*/
|
|
3137
|
-
function
|
|
3532
|
+
function rawFleetVal(key) {
|
|
3138
3533
|
// **ライブが取れたら、そちらだけを見る(キー単位で静的へ落ちない)。**
|
|
3139
3534
|
// キーごとにフォールバックすると、たとえば externalSources はライブ・secretSources は
|
|
3140
3535
|
// 前日のビルド、という**2つの時点が混ざった画面**になる。上に出す「◯◯時点」は1つしか
|
|
@@ -3145,6 +3540,73 @@ ${settledPending.toString()}
|
|
|
3145
3540
|
return SITE[key === "generatedAt" ? "fleetGeneratedAt" : key];
|
|
3146
3541
|
}
|
|
3147
3542
|
|
|
3543
|
+
/**
|
|
3544
|
+
* 画面が読む値。**まとめ表がまだ知らない行を足したもの**を返す(段階1)。
|
|
3545
|
+
*
|
|
3546
|
+
* 🔴 **ここ1箇所で足す。** 一覧・能力タイル・接続マップの注記は全部この関数を通るので、
|
|
3547
|
+
* 足す場所を描画側に散らすと「タイルは1件・一覧は2件」のような食い違いが必ず出る。
|
|
3548
|
+
*/
|
|
3549
|
+
function fleetVal(key) {
|
|
3550
|
+
if (LIVE_REGISTERED && MERGE_KEYS[key]) {
|
|
3551
|
+
if (!MERGED) MERGED = mergeRegisteredFleet(rawView(), LIVE_REGISTERED);
|
|
3552
|
+
return MERGED[key];
|
|
3553
|
+
}
|
|
3554
|
+
return rawFleetVal(key);
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
/** まとめ表がまだ答えていない行の数(取り直しを続けるかの判断) */
|
|
3558
|
+
function unsettledNow() {
|
|
3559
|
+
return unsettledRegisteredCount(rawView(), LIVE_REGISTERED);
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
/** まとめ表(夜間の成果物)だけの3キー。**状態の判定には必ずこちらを渡す** */
|
|
3563
|
+
function rawView() {
|
|
3564
|
+
return {
|
|
3565
|
+
externalSources: rawFleetVal("externalSources"),
|
|
3566
|
+
internalSources: rawFleetVal("internalSources"),
|
|
3567
|
+
backlogTarget: rawFleetVal("backlogTarget"),
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
/**
|
|
3572
|
+
* まとめ表がまだ知らない行を持っている能力(段階1)。
|
|
3573
|
+
*
|
|
3574
|
+
* 🔴 **その能力の状態を「接続OK」「未接続」「未使用」のどれとも言わないため。**
|
|
3575
|
+
* 足した行を状態の判定に混ぜると「1行あるのだから繋がっている」と読んで**接続OK**に落ち、
|
|
3576
|
+
* 同じ画面で**タイルは接続OK・行は「登録状況を確認中です」**が並ぶ。かといって夜間の値を
|
|
3577
|
+
* そのまま出すと、**行はもう出ているのに「接続先が設定されていません」**と言う。
|
|
3578
|
+
* どちらも嘘なので、**行と同じ語**(登録状況を確認中です)に倒す。
|
|
3579
|
+
*/
|
|
3580
|
+
function pendingCaps() {
|
|
3581
|
+
if (!LIVE_REGISTERED) return {};
|
|
3582
|
+
var add = registeredRowsToAdd(rawView(), LIVE_REGISTERED);
|
|
3583
|
+
var out = {};
|
|
3584
|
+
for (var i = 0; i < add.externalSources.length; i++) out[externalCapability(add.externalSources[i].type)] = 1;
|
|
3585
|
+
if (add.design.length) out[REGISTERED_CAP_NAMES.design] = 1;
|
|
3586
|
+
if (add.materials.length) out[REGISTERED_CAP_NAMES.materials] = 1;
|
|
3587
|
+
if (add.issues.length) out[REGISTERED_CAP_NAMES.issues] = 1;
|
|
3588
|
+
return out;
|
|
3589
|
+
}
|
|
3590
|
+
/** その能力に、まとめ表がまだ知らない行があるか */
|
|
3591
|
+
function capPending(cap) { return !!pendingCaps()[cap]; }
|
|
3592
|
+
|
|
3593
|
+
/**
|
|
3594
|
+
* 能力の状態を決めるときに渡す値。**足した行は混ぜない**(誰も測っていないので状態を持たない)。
|
|
3595
|
+
*
|
|
3596
|
+
* ⚠ **これ単体を壊してもテストは赤くならない。** 画面に出る語は capPending が先に
|
|
3597
|
+
* 「登録状況を確認中です」へ倒すので、条件(足した行がある)が同じこの2つは常に一緒に効く。
|
|
3598
|
+
* それでも素の値を渡すのは、**判定関数に嘘の材料を渡さない**ためで、表示側の都合が
|
|
3599
|
+
* 変わったとき(例えば pending の語をやめたとき)に「接続OK」が復活しないようにする。
|
|
3600
|
+
*/
|
|
3601
|
+
function stateCtx() {
|
|
3602
|
+
return {
|
|
3603
|
+
capabilities: rawFleetVal("capabilities") || {},
|
|
3604
|
+
internals: rawFleetVal("internalSources") || [],
|
|
3605
|
+
externals: rawFleetVal("externalSources") || [],
|
|
3606
|
+
pipes: rawFleetVal("pipelines") || [],
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3148
3610
|
// ---------- 反映待ち(「⏳ 反映中」)----------
|
|
3149
3611
|
//
|
|
3150
3612
|
// ## 何のためか
|
|
@@ -3397,6 +3859,48 @@ ${settledPending.toString()}
|
|
|
3397
3859
|
reflectTimer = setTimeout(reflectTick, delay);
|
|
3398
3860
|
}
|
|
3399
3861
|
|
|
3862
|
+
// ---------- 未確定の行がある間の取り直し(段階1の前提)----------
|
|
3863
|
+
//
|
|
3864
|
+
// 🔴 **「反映中」の記録がある間だけ取り直す**のをやめ、**まとめ表がまだ答えていない
|
|
3865
|
+
// 行がある間**取り直す。registeredNow が入ると「反映中」は追加した瞬間に settle して
|
|
3866
|
+
// 消えるので、これが無いと**登録した直後に画面が二度と更新されなくなる。**
|
|
3867
|
+
//
|
|
3868
|
+
// 経路は増やさない(loadLiveFleet 1本)。取得の口が2つになると、上に出す
|
|
3869
|
+
// 「◯◯時点」がどちらの値を指すのか説明できなくなる。
|
|
3870
|
+
|
|
3871
|
+
/** 取り直しのタイマ(setTimeout の連鎖。setInterval は使わない) */
|
|
3872
|
+
var stateTimer = null;
|
|
3873
|
+
/** 取り直しを始めた時刻。**上限は経過時間で見る**(回数で数えない) */
|
|
3874
|
+
var stateSince = null;
|
|
3875
|
+
|
|
3876
|
+
/**
|
|
3877
|
+
* 取り直しの予約を、いまの状況に合わせ直す。
|
|
3878
|
+
*
|
|
3879
|
+
* 🔴 **未確定の行が無ければタイマを張らない。** テストは load の後に fetch を注入するので、
|
|
3880
|
+
* 無条件に張ると jsdom が閉じられず node --test が返らなくなる(反映待ちで実際に踏んだ形)。
|
|
3881
|
+
*/
|
|
3882
|
+
function refreshStateTimer() {
|
|
3883
|
+
var n = unsettledNow();
|
|
3884
|
+
if (!n) {
|
|
3885
|
+
if (stateTimer) { clearTimeout(stateTimer); stateTimer = null; }
|
|
3886
|
+
stateSince = null;
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
if (stateTimer) return;
|
|
3890
|
+
if (!SITE.intake || typeof fetch !== "function") return;
|
|
3891
|
+
if (stateSince == null) stateSince = Date.now();
|
|
3892
|
+
var delay = nextStateDelay(n, stateSince, Date.now());
|
|
3893
|
+
if (delay == null) return;
|
|
3894
|
+
stateTimer = setTimeout(stateTick, delay);
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
function stateTick() {
|
|
3898
|
+
stateTimer = null;
|
|
3899
|
+
// 応答が届いた時点で refreshStateTimer が呼ばれ、まだ足りなければ次を張る。
|
|
3900
|
+
// **期限に達していれば張られない**(無限に叩き続けない)
|
|
3901
|
+
loadLiveFleet();
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3400
3904
|
function reflectTick() {
|
|
3401
3905
|
reflectTimer = null;
|
|
3402
3906
|
var live = reflectingNow();
|
|
@@ -3460,6 +3964,9 @@ ${settledPending.toString()}
|
|
|
3460
3964
|
* 保存直後の15分間、異常が画面から消える——直したい誤読より悪い
|
|
3461
3965
|
*/
|
|
3462
3966
|
function openCapabilityModal(cap, items, note, noteIsUnset) {
|
|
3967
|
+
// **開いた能力を覚えて、その登録ファイルを読みに行く(段階1)。** 全能力を毎回
|
|
3968
|
+
// 読ませない代わりに、見ている能力だけを取りに行く
|
|
3969
|
+
noteOpenCap(cap);
|
|
3463
3970
|
// 見出しは押したタイルのツール名(Backlog・Google ドライブ…)。capToolLabel の説明を参照
|
|
3464
3971
|
var m = makeModal(capToolLabel(cap), true);
|
|
3465
3972
|
// **何が読み取られるのかを必ず書く。** 一覧に並ぶものと読み取られるものの関係は、
|
|
@@ -3479,6 +3986,8 @@ ${settledPending.toString()}
|
|
|
3479
3986
|
// ⚠ 引き直しでは能力の代表アダプタの状態を使う(openCapabilityModal は
|
|
3480
3987
|
// どのアダプタのタイルから開かれたかを知らない。カタログは今日どの能力も1個)
|
|
3481
3988
|
if (noteIsUnset) reflectMark(noteEl, cap, "desc", function () {
|
|
3989
|
+
// 足した行があるなら、この一文(接続先が設定されていません)はもう事実ではない
|
|
3990
|
+
if (capPending(cap)) return { text: "", hidden: true };
|
|
3482
3991
|
var capsNow = fleetVal("capabilities") || {};
|
|
3483
3992
|
var dNow = capabilityDisplayState(cap, capsNow);
|
|
3484
3993
|
var infoNow = capsNow[cap];
|
|
@@ -3567,11 +4076,18 @@ ${settledPending.toString()}
|
|
|
3567
4076
|
//
|
|
3568
4077
|
// **取得できたかで文言を変える。** 静的ビルドの値をそのまま「現在値」として見せると、
|
|
3569
4078
|
// まさにここが警告している誤読を自分で起こす。取りに行って失敗したことは隠さない。
|
|
4079
|
+
// 🔴 **足した行があるときは「次回の実行後」と言い切らない(段階1)。** 一覧には
|
|
4080
|
+
// 夜間の値といま読み取った登録が並んでいるので、「反映されるのは次回の実行後」は
|
|
4081
|
+
// **もう出ている行について嘘**になる。時刻を2つ出す形(どの行がいつの値か)は
|
|
4082
|
+
// 段階3で1箇所にまとめるので、ここでは**但し書きを1行足すだけ**にとどめる。
|
|
3570
4083
|
var asOf = fleetVal("generatedAt") ? dsFmtTime(fleetVal("generatedAt")) : "";
|
|
4084
|
+
var hasPending = Object.keys(pendingCaps()).length > 0;
|
|
3571
4085
|
if (asOf) {
|
|
3572
4086
|
var asOfText = LIVE_FLEET_FAILED
|
|
3573
4087
|
? "最新の接続状況を取得できませんでした。表示しているのは " + asOf + " 時点の情報です。"
|
|
3574
|
-
:
|
|
4088
|
+
: hasPending
|
|
4089
|
+
? "接続状況は " + asOf + " 時点の自動チェック結果です。いま登録した分も一覧に出していますが、読み取れているかどうかが分かるのは次回の自動チェックの後になります。"
|
|
4090
|
+
: "接続状況は " + asOf + " 時点の自動チェック結果です。設定を変えた場合、ここに反映されるのは次回の実行後になります。";
|
|
3575
4091
|
sec.appendChild(el("p", {
|
|
3576
4092
|
class: "datasources-asof" + (LIVE_FLEET_FAILED ? " is-stale" : ""),
|
|
3577
4093
|
text: asOfText,
|
|
@@ -3689,7 +4205,9 @@ ${settledPending.toString()}
|
|
|
3689
4205
|
col.appendChild(el("span", { class: "ds-drive-note", text: "Drive自動同期は未設定(手動で置いたファイルの変換のみ)" }));
|
|
3690
4206
|
}
|
|
3691
4207
|
// 未使用(enabled: false)はグレー行。アダプターの存在は見せつつ、この案件では設定されていないことを示す
|
|
3692
|
-
|
|
4208
|
+
// 🔴 **足した行がある能力では「未使用」と言わない(段階1)。** enabled は夜間の値で、
|
|
4209
|
+
// いま登録した行が一覧に出ているのに「この案件では設定されていません」と並ぶ
|
|
4210
|
+
var inactive = s.enabled === false && !capPending(s.kind);
|
|
3693
4211
|
if (inactive) {
|
|
3694
4212
|
// **反映待ちのあいだは「⏳ 反映中」に差し替える(消さない)。** 消すと
|
|
3695
4213
|
// .ds-inactive の灰色だけが残り、なぜ灰色なのかが読めなくなる
|
|
@@ -3702,7 +4220,7 @@ ${settledPending.toString()}
|
|
|
3702
4220
|
//(節ごとの作り直しはモーダルを閉じるまで走らない)
|
|
3703
4221
|
function (n) {
|
|
3704
4222
|
var srcNow = internalSourceOf(s.kind);
|
|
3705
|
-
var stillInactive = !!(srcNow && srcNow.enabled === false);
|
|
4223
|
+
var stillInactive = !!(srcNow && srcNow.enabled === false) && !capPending(s.kind);
|
|
3706
4224
|
if (!stillInactive) {
|
|
3707
4225
|
var card = n && n.closest ? n.closest(".ds-card") : null;
|
|
3708
4226
|
if (card) card.classList.remove("ds-inactive");
|
|
@@ -3788,19 +4306,8 @@ ${settledPending.toString()}
|
|
|
3788
4306
|
// 追加のときにワークスペースを選ばせるのをやめたので、
|
|
3789
4307
|
// 「どの組織のものか」「その組織とつながっているか」を画面が言う。
|
|
3790
4308
|
// 分け方は groupSlackByOrg(純関数)が全部持っている
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
return;
|
|
3794
|
-
}
|
|
3795
|
-
// **GitHub も組織ごとに分ける(2026-08-20・Slack からの横展開)。**
|
|
3796
|
-
// それまでは「リポジトリの一覧」と「組織ごとの接続状況」が**別々に並んで**おり、
|
|
3797
|
-
// どの行がどの組織のものか、行を見ても分からなかった。
|
|
3798
|
-
if (g.type === "__github__") {
|
|
3799
|
-
// **meta は行ごとに引く。** issues と discussions が混ざるので、
|
|
3800
|
-
// グループの type から1つに決められない
|
|
3801
|
-
githubRows(g.items).forEach(function (n) { pushCap("開発", n); });
|
|
3802
|
-
return;
|
|
3803
|
-
}
|
|
4309
|
+
// **一覧は下で1つの箱にまとめて出す**(開いたままでも差し替えられるように)
|
|
4310
|
+
if (g.type === "slack" || g.type === "__github__") return;
|
|
3804
4311
|
var cards = el("div", { class: "ds-list" });
|
|
3805
4312
|
g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
|
|
3806
4313
|
if (g.items.length >= 4) {
|
|
@@ -3828,6 +4335,50 @@ ${settledPending.toString()}
|
|
|
3828
4335
|
});
|
|
3829
4336
|
}
|
|
3830
4337
|
|
|
4338
|
+
// ---- チャットと開発の一覧(開いたままでも追いつく形)----
|
|
4339
|
+
//
|
|
4340
|
+
// 🔴 **行が1件も無くても箱を出す。** 立ち上げ直後の案件=**この仕組みが一番効く案件**は
|
|
4341
|
+
// ここが空だが、モーダルを開いたままチャンネルを足すのはまさにその案件。
|
|
4342
|
+
// 箱が無いと、足した行を入れる場所が画面に存在しない。
|
|
4343
|
+
//
|
|
4344
|
+
// **Slack は組織ごと(slackRows)・GitHub も組織ごと(githubRows)。**
|
|
4345
|
+
// それまでは「行の一覧」と「組織ごとの接続状況」が別々に並んでいて、
|
|
4346
|
+
// どの行がどの組織の話なのか読めなかった(2026-08-20 に揃えた)。
|
|
4347
|
+
function liveExternals(kind) {
|
|
4348
|
+
return (fleetVal("externalSources") || []).filter(function (s) {
|
|
4349
|
+
return kind === "slack" ? s.type === "slack" : String(s.type || "").indexOf("github") === 0;
|
|
4350
|
+
});
|
|
4351
|
+
}
|
|
4352
|
+
/** 行の見た目を決める値だけを並べる(時刻など毎回変わる値は入れない) */
|
|
4353
|
+
function externalsSig(kind, extra) {
|
|
4354
|
+
return JSON.stringify([liveExternals(kind), extra]);
|
|
4355
|
+
}
|
|
4356
|
+
/**
|
|
4357
|
+
* **空の箱を置いてよいのは、そこへ足せる案件だけ。** 投入の口を持たない構成
|
|
4358
|
+
* (素の Decisions ビュー)で行も無い能力に空の箱を積むと、押しても何も無いモーダルになる。
|
|
4359
|
+
*/
|
|
4360
|
+
function liveListWanted(kind) { return !!SITE.intake || liveExternals(kind).length > 0; }
|
|
4361
|
+
if (liveListWanted("slack")) pushCap("チャット", liveList(
|
|
4362
|
+
"チャット",
|
|
4363
|
+
function () { return externalsSig("slack", slackWorkspaces()); },
|
|
4364
|
+
function (box) {
|
|
4365
|
+
var items = liveExternals("slack");
|
|
4366
|
+
if (!items.length) return;
|
|
4367
|
+
var meta = DS_TYPE["slack"] || { icon: "🔗", label: "Slack" };
|
|
4368
|
+
slackRows(items, meta).forEach(function (n) { box.appendChild(n); });
|
|
4369
|
+
},
|
|
4370
|
+
));
|
|
4371
|
+
if (liveListWanted("github")) pushCap("開発", liveList(
|
|
4372
|
+
"開発",
|
|
4373
|
+
function () { return externalsSig("github", fleetVal("githubOwners") || null); },
|
|
4374
|
+
function (box) {
|
|
4375
|
+
var items = liveExternals("github");
|
|
4376
|
+
if (!items.length) return;
|
|
4377
|
+
// **meta は行ごとに引く**(issues と discussions でアイコンとラベルが違う)
|
|
4378
|
+
githubRows(items).forEach(function (n) { box.appendChild(n); });
|
|
4379
|
+
},
|
|
4380
|
+
));
|
|
4381
|
+
|
|
3831
4382
|
// **追加フォームはグループの外に置く。** グループは0件だと描かれず、4件以上だと
|
|
3832
4383
|
// 畳まれる(既定は閉)。中に入れると**新規案件(0件)=一番追加したい場面**で消える。
|
|
3833
4384
|
var adders = renderExternalAdders();
|
|
@@ -4133,10 +4684,13 @@ ${settledPending.toString()}
|
|
|
4133
4684
|
*/
|
|
4134
4685
|
function renderAdapterTile(cap, items, adapter, derived, ai) {
|
|
4135
4686
|
var meta = CAP_META[cap] || { icon: "🔗", desc: "" };
|
|
4687
|
+
// 🔴 **状態はまとめ表の素の値で決める(段階1)。** ここに足した行を混ぜると、
|
|
4688
|
+
// 登録直後の1行で「使っている」と判定され、誰も測っていないのに**接続OK**に落ちる
|
|
4689
|
+
// (同じ画面の行は「登録状況を確認中です」)。足した行の扱いは pendingSub が持つ
|
|
4136
4690
|
var st = derived === "unset" && capabilityDisplayState(cap, caps) !== "unset"
|
|
4137
4691
|
// カタログにあるが導出が答えていないアダプタ(今日は発生しない。#107 の前方互換)
|
|
4138
4692
|
? "idle"
|
|
4139
|
-
: capabilityState(cap,
|
|
4693
|
+
: capabilityState(cap, stateCtx());
|
|
4140
4694
|
// **ロゴはタイルの左に独立して置く。** 名前の行に入れると名前の高さに
|
|
4141
4695
|
// 縛られて小さくなる。ロゴで能力を見分けられるのが一覧の主な用途
|
|
4142
4696
|
var logo = ADAPTER_ICONS[adapter];
|
|
@@ -4150,7 +4704,12 @@ ${settledPending.toString()}
|
|
|
4150
4704
|
// ようにするのが目的だが、別のバッジを足すのではなく**この行を差し替える**
|
|
4151
4705
|
// ——抑制しないと、同じタイルの中で「⏳ 反映中」と「⦿ 未接続」が並んで矛盾する。
|
|
4152
4706
|
// **抑制するのは unset のときだけ**(off / broken は残す。R4 と同じ理由)
|
|
4153
|
-
|
|
4707
|
+
// まとめ表がまだ知らない行がある能力は、**行と同じ語**に倒す(未接続とも接続OKとも言わない)
|
|
4708
|
+
var pend = capPending(cap);
|
|
4709
|
+
var subEl = el("div", {
|
|
4710
|
+
class: "ds-tile-sub " + (pend ? DS_GATE_PENDING.cls : "ds-gate-" + st),
|
|
4711
|
+
text: pend ? DS_GATE_PENDING.text : tileSub(cap, st, derived, adapter, internals, sources),
|
|
4712
|
+
});
|
|
4154
4713
|
// **反映が届いたら、届いたスナップショットから引き直す。** 下地へ戻すと
|
|
4155
4714
|
// 「⏳ 反映中」→「⦿ 未接続」になる——データが正しくなったあとに嘘をつく。
|
|
4156
4715
|
// 状態の語も色(ds-gate-*)も、判定はタイルを作るときと同じ式で引く
|
|
@@ -4158,13 +4717,16 @@ ${settledPending.toString()}
|
|
|
4158
4717
|
var capsNow = fleetVal("capabilities") || {};
|
|
4159
4718
|
var insNow = fleetVal("internalSources") || [];
|
|
4160
4719
|
var srcsNow = fleetVal("externalSources") || [];
|
|
4161
|
-
var pipesNow = fleetVal("pipelines") || [];
|
|
4162
4720
|
var dNow = adapter === capabilityAdapter(cap, capsNow) ? capabilityDisplayState(cap, capsNow) : "unset";
|
|
4163
4721
|
var stNow = dNow === "unset" && capabilityDisplayState(cap, capsNow) !== "unset"
|
|
4164
4722
|
? "idle"
|
|
4165
|
-
: capabilityState(cap,
|
|
4166
|
-
|
|
4167
|
-
|
|
4723
|
+
: capabilityState(cap, stateCtx());
|
|
4724
|
+
var pendNow = capPending(cap);
|
|
4725
|
+
n.className = "ds-tile-sub " + (pendNow ? DS_GATE_PENDING.cls : "ds-gate-" + stNow);
|
|
4726
|
+
return {
|
|
4727
|
+
text: pendNow ? DS_GATE_PENDING.text : tileSub(cap, stNow, dNow, adapter, insNow, srcsNow),
|
|
4728
|
+
hidden: false,
|
|
4729
|
+
};
|
|
4168
4730
|
});
|
|
4169
4731
|
var body = el("div", { class: "ds-tile-body" }, [
|
|
4170
4732
|
el("div", { class: "ds-tile-name", text: (rawTool && (CM_TOOL_LABEL_C[rawTool] || rawTool)) || cap }),
|
|
@@ -4223,6 +4785,9 @@ ${settledPending.toString()}
|
|
|
4223
4785
|
body,
|
|
4224
4786
|
el("span", { class: "ds-tile-chev", text: "›" }),
|
|
4225
4787
|
]);
|
|
4788
|
+
// ⚠ **ここで足した行(段階1)を見て抑制しない。** unset の一文は開くときに引き直される
|
|
4789
|
+
// (openCapabilityModal の reflectMark の fresh 側が capPending を見る)ので二重になるうえ、
|
|
4790
|
+
// ここで抑えると off / broken まで消える——R4 で「消さない」と決めたもの
|
|
4226
4791
|
var modalNote = derived && derived !== "on" && capInfo && capInfo.detail
|
|
4227
4792
|
? CAP_STATE_TEXT[derived] + "(" + capInfo.detail + ")"
|
|
4228
4793
|
: "";
|
|
@@ -4917,8 +5482,23 @@ ${settledPending.toString()}
|
|
|
4917
5482
|
|
|
4918
5483
|
// **box を先に作る。** paint() は 0件の1行を box へ出し入れするので、
|
|
4919
5484
|
// 箱が無いと最初の描画で「未設定」の行が出ない
|
|
4920
|
-
var box = el("div", {}, [listEl]);
|
|
5485
|
+
var box = el("div", { "data-live-cap": REGISTERED_CAP_NAMES.issues }, [listEl]);
|
|
4921
5486
|
paint();
|
|
5487
|
+
/**
|
|
5488
|
+
* 取り直しで新しい値が届いたときの差し替え(段階1)。**一覧だけを描き直す。**
|
|
5489
|
+
*
|
|
5490
|
+
* モーダルを開いたまま接続先を足すのが普通の使い方なので、ここが無いと
|
|
5491
|
+
* 足した接続先は閉じるまで画面に出ない。paint() は鍵の欄を作り直さず入れ直すだけなので、
|
|
5492
|
+
* 入力中の鍵と「保存中…」は保たれる。
|
|
5493
|
+
*/
|
|
5494
|
+
box.__rebuild = function () {
|
|
5495
|
+
// 🔴 応答に添えられた一覧(adoptTargets)のほうが新しい。上書きしない
|
|
5496
|
+
if (adopted) return;
|
|
5497
|
+
var next = backlogTargetsNow(internalSourceOf(REGISTERED_CAP_NAMES.issues));
|
|
5498
|
+
if (targetsGenerationOf(next) === targetsGenerationOf(targets)) return;
|
|
5499
|
+
targets = next;
|
|
5500
|
+
paint();
|
|
5501
|
+
};
|
|
4922
5502
|
// 接続先がまだ無い案件の受け皿は、0件の1行の直後に置く(行が無いので中へ入れられない)
|
|
4923
5503
|
if (keySlots[""]) box.appendChild(keySlots[""]);
|
|
4924
5504
|
if (editable) {
|
|
@@ -5090,9 +5670,17 @@ ${settledPending.toString()}
|
|
|
5090
5670
|
// **ON/OFFのトグルは出さない。** フォルダを追加した時点で取り込みは始まる
|
|
5091
5671
|
// (applyAddDriveFolder が enabled を立てる)ので「始める」を押す機会が無い。
|
|
5092
5672
|
// 止めたいならフォルダを外す——登録そのものが意思表示、という会議と同じ考え方。
|
|
5093
|
-
|
|
5673
|
+
// **一覧は開いたままでも差し替わる箱に入れる(段階1)。** 追加は「モーダルを開いたまま」
|
|
5674
|
+
// 行われるので、ここが固定だと足したフォルダは閉じるまで画面に出ない
|
|
5675
|
+
var driveNow = function () { return internalSourceOf("共有資料") || s; };
|
|
5676
|
+
box.appendChild(liveList("共有資料", function () {
|
|
5677
|
+
var d = driveNow();
|
|
5678
|
+
return JSON.stringify([d.driveFolderIds || [], d.driveFolderNames || {}, d.driveState || ""]);
|
|
5679
|
+
}, function (listBox) {
|
|
5680
|
+
var src = driveNow();
|
|
5681
|
+
var folders = src.driveFolderIds || [];
|
|
5094
5682
|
// 名前は任意。**無ければIDを出す**(既存の案件は名前を持っていない)
|
|
5095
|
-
var fnames =
|
|
5683
|
+
var fnames = src.driveFolderNames || {};
|
|
5096
5684
|
if (folders.length) {
|
|
5097
5685
|
var dlist = el("div", { class: "ds-list" });
|
|
5098
5686
|
folders.forEach(function (id) {
|
|
@@ -5132,8 +5720,8 @@ ${settledPending.toString()}
|
|
|
5132
5720
|
}));
|
|
5133
5721
|
dlist.appendChild(row);
|
|
5134
5722
|
});
|
|
5135
|
-
|
|
5136
|
-
} else if (
|
|
5723
|
+
listBox.appendChild(dlist);
|
|
5724
|
+
} else if (src.driveState === "empty") {
|
|
5137
5725
|
// **0件を黙って見せない。** 追加フォームだけだと「まだ登録していない」のか
|
|
5138
5726
|
// 「読めていない」のか区別できない。
|
|
5139
5727
|
//
|
|
@@ -5141,8 +5729,9 @@ ${settledPending.toString()}
|
|
|
5141
5729
|
// driveFolderIds を「1件以上あるときだけ」出すので、キーの有無では
|
|
5142
5730
|
// 未取得(unset / broken)と0件を見分けられない。unset / broken は
|
|
5143
5731
|
// 上の分岐で先に返しているので、ここへ来る empty は「読めていて0件」だけ。
|
|
5144
|
-
|
|
5732
|
+
listBox.appendChild(el("p", { class: "ds-empty", text: "取り込み対象のフォルダはまだ登録されていません。" }));
|
|
5145
5733
|
}
|
|
5734
|
+
}));
|
|
5146
5735
|
box.appendChild(addForm({
|
|
5147
5736
|
title: "+ Google Drive のフォルダを追加",
|
|
5148
5737
|
placeholder: "https://drive.google.com/drive/folders/…",
|
|
@@ -5169,7 +5758,11 @@ ${settledPending.toString()}
|
|
|
5169
5758
|
if (s.kind === "デザイン" && usable("デザイン", "figma")) {
|
|
5170
5759
|
// 登録済みファイルを1件ずつ外せるようにする(6ファイル抱えている案件があり、
|
|
5171
5760
|
// 古いスプリントのファイルを外したいのが実際に起きる操作)。
|
|
5172
|
-
|
|
5761
|
+
// **一覧は開いたままでも差し替わる箱に入れる(段階1)。** 追加は「モーダルを開いたまま」
|
|
5762
|
+
// 行われるので、ここが固定だと足したファイルは閉じるまで画面に出ない
|
|
5763
|
+
var figsOf = function () { return (internalSourceOf("デザイン") || {}).figmaFiles || []; };
|
|
5764
|
+
box.appendChild(liveList("デザイン", function () { return JSON.stringify(figsOf()); }, function (listBox) {
|
|
5765
|
+
var figs = figsOf();
|
|
5173
5766
|
// **残り件数は「押した時点」で数える。** 一覧(figs)は夜間バッチのスナップショットで、
|
|
5174
5767
|
// このページを開いている間はもちろん、その日のうちに再読み込みしても更新されない。
|
|
5175
5768
|
// 描画時点の件数で判定すると、3件を1件ずつ外していったとき最後の1件でも警告が出ない。
|
|
@@ -5217,12 +5810,13 @@ ${settledPending.toString()}
|
|
|
5217
5810
|
}));
|
|
5218
5811
|
flist.appendChild(row);
|
|
5219
5812
|
});
|
|
5220
|
-
|
|
5813
|
+
listBox.appendChild(flist);
|
|
5221
5814
|
} else {
|
|
5222
5815
|
// **0件を黙って見せない。** 追加フォームだけだと「まだ登録していない」のか
|
|
5223
5816
|
// 「読めていない」のか区別できない(Driveは driveState で理由まで出している)
|
|
5224
|
-
|
|
5817
|
+
listBox.appendChild(el("p", { class: "ds-empty", text: "同期対象のFigmaファイルはまだ登録されていません。" }));
|
|
5225
5818
|
}
|
|
5819
|
+
}));
|
|
5226
5820
|
// **鍵の欄は一覧の直後に1つ(2026-08-21・オーナー指摘)。**
|
|
5227
5821
|
// Backlog と違い、Figma の鍵は**案件に1本**(ファイルごとではない)。
|
|
5228
5822
|
// 行ごとに出すと同じ欄が並ぶだけなので、一覧をひとまとまりと見て、その下に置く。
|
|
@@ -5482,6 +6076,12 @@ ${settledPending.toString()}
|
|
|
5482
6076
|
// - refreshUrl が無い(自己チェックを起こせなかった)ときも記録しない
|
|
5483
6077
|
// ——待っても来ないので「反映中」が嘘になる
|
|
5484
6078
|
if (j.changed !== false && j.refreshUrl) rememberReflecting(payload, j.refreshUrl);
|
|
6079
|
+
// **保存が効いたら、いまの登録を読み直す(段階1)。** 一覧はまとめ表(夜間バッチ)から
|
|
6080
|
+
// 描かれるので、保存しただけでは行が出ない。ここで読み直すと、登録ファイルの中身が
|
|
6081
|
+
// 返ってきて**足りない行として一覧に出る**——さらに「まとめ表がまだ答えていない行」が
|
|
6082
|
+
// できるので、まとめ表が追いつくまで取り直しが続く。
|
|
6083
|
+
// 取得中なら何もしない(応答後に catchUpOpenCaps が拾う)
|
|
6084
|
+
if (j.changed !== false) loadLiveFleet(true);
|
|
5485
6085
|
// **何も変わっていないなら「追加した」と見せない。** 既に同じものが入っていた場合
|
|
5486
6086
|
// サーバは 200 + changed:false を返す。ここを res.ok だけで判定すると
|
|
5487
6087
|
// 「変更はありません」と言いながら追加した行が出る
|
|
@@ -6116,6 +6716,51 @@ ${settledPending.toString()}
|
|
|
6116
6716
|
}
|
|
6117
6717
|
|
|
6118
6718
|
|
|
6719
|
+
/* ---------------------------------------------------------------------------
|
|
6720
|
+
* 開いたままのモーダルへ届ける(段階1)
|
|
6721
|
+
*
|
|
6722
|
+
* 🔴 **チャンネルやフォルダを足すのは「モーダルを開いたまま」。** 節ごとの差し替え
|
|
6723
|
+
* (refreshDataSources)はモーダルが開いている間は何もしない——下地を入れ替えると
|
|
6724
|
+
* 開いているモーダルの中身が切り離され、入力中の値も「保存中…」も消えるので、
|
|
6725
|
+
* **あのガードは外せない**。だから「反映中」と同じく、**その箇所だけを差し替える口**を置く。
|
|
6726
|
+
*
|
|
6727
|
+
* 差し替えるのは**レコードの一覧だけ**(追加フォーム・鍵の欄・結果メッセージは触らない)。
|
|
6728
|
+
* 課題管理の adoptTargets が先にこの形を採っている。
|
|
6729
|
+
*
|
|
6730
|
+
* 🔴 **中身が変わっていなければ何もしない。** 60秒ごとの取り直しのたびに作り直すと、
|
|
6731
|
+
* 「削除しました」の表示や取り消し線(.ds-removed)が黙って消える。
|
|
6732
|
+
* ------------------------------------------------------------------------- */
|
|
6733
|
+
|
|
6734
|
+
/**
|
|
6735
|
+
* 開いたままでも追いつく一覧。
|
|
6736
|
+
*
|
|
6737
|
+
* @param cap どの能力の一覧か(差し替えの対象を絞るためだけに使う)
|
|
6738
|
+
* @param sig いまのデータを表す文字列。**変わったときだけ**作り直す
|
|
6739
|
+
* @param build 中身を作る(box を渡すので、その中へ appendChild する)
|
|
6740
|
+
*/
|
|
6741
|
+
function liveList(cap, sig, build) {
|
|
6742
|
+
var box = el("div", { class: "ds-livelist", "data-live-cap": cap });
|
|
6743
|
+
box.__rebuild = function () {
|
|
6744
|
+
var next = sig();
|
|
6745
|
+
if (next === box.__sig) return;
|
|
6746
|
+
box.__sig = next;
|
|
6747
|
+
while (box.firstChild) box.removeChild(box.firstChild);
|
|
6748
|
+
build(box);
|
|
6749
|
+
};
|
|
6750
|
+
box.__rebuild();
|
|
6751
|
+
return box;
|
|
6752
|
+
}
|
|
6753
|
+
|
|
6754
|
+
/** 開いているモーダルの中の一覧だけを差し替える(下地は触らない) */
|
|
6755
|
+
function repaintOpenLists() {
|
|
6756
|
+
var overlay = app.querySelector(".modal-overlay");
|
|
6757
|
+
if (!overlay) return;
|
|
6758
|
+
var nodes = overlay.querySelectorAll("[data-live-cap]");
|
|
6759
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
6760
|
+
if (typeof nodes[i].__rebuild === "function") nodes[i].__rebuild();
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
|
|
6119
6764
|
/**
|
|
6120
6765
|
* 取得中フラグ。**完了しても寝かせたままにしない。**
|
|
6121
6766
|
* 一度 true のままだと、タブを移動して戻ってきても再取得せず、設定を変えた人が
|
|
@@ -6123,7 +6768,56 @@ ${settledPending.toString()}
|
|
|
6123
6768
|
* **操作タブに入り直したときだけ**もう一度取りに行く(ポーリングはしない)。
|
|
6124
6769
|
*/
|
|
6125
6770
|
var liveFleetRequested = false;
|
|
6126
|
-
|
|
6771
|
+
/**
|
|
6772
|
+
* この画面で開いた能力(op=state に送る識別子)。
|
|
6773
|
+
*
|
|
6774
|
+
* 🔴 **開いている能力の分だけ読む。** Lambda は指定された能力の登録ファイルだけを
|
|
6775
|
+
* 読む(ツリー1本+ファイルの本数)。全能力を毎回読ませると GitHub の枠を削る。
|
|
6776
|
+
*
|
|
6777
|
+
* **一度開いたら覚えておく(閉じても外さない)。** 外すと、次の取り直しで
|
|
6778
|
+
* その能力の行が応答から消え、**タイルの件数だけが行ったり来たりする**。
|
|
6779
|
+
*/
|
|
6780
|
+
var OPEN_CAPS = [];
|
|
6781
|
+
/** 画面の能力名 → op=state の識別子(対応表の実体は REGISTERED_CAP_NAMES) */
|
|
6782
|
+
function stateCapIdOf(cap) {
|
|
6783
|
+
for (var id in REGISTERED_CAP_NAMES) {
|
|
6784
|
+
if (Object.prototype.hasOwnProperty.call(REGISTERED_CAP_NAMES, id) && REGISTERED_CAP_NAMES[id] === cap) return id;
|
|
6785
|
+
}
|
|
6786
|
+
return null;
|
|
6787
|
+
}
|
|
6788
|
+
/** 取得中に開かれた能力があれば、もう一度だけ取りに行く */
|
|
6789
|
+
function catchUpOpenCaps() {
|
|
6790
|
+
if (OPEN_CAPS.length === liveFleetSentCaps.length) return;
|
|
6791
|
+
loadLiveFleet();
|
|
6792
|
+
}
|
|
6793
|
+
/** 能力を開いた(=その登録ファイルを読んでよい)。増えたときだけ取り直す */
|
|
6794
|
+
function noteOpenCap(cap) {
|
|
6795
|
+
var id = stateCapIdOf(cap);
|
|
6796
|
+
if (!id || OPEN_CAPS.indexOf(id) >= 0) return;
|
|
6797
|
+
OPEN_CAPS.push(id);
|
|
6798
|
+
loadLiveFleet();
|
|
6799
|
+
}
|
|
6800
|
+
/**
|
|
6801
|
+
* 直近の取得に載せた能力の一覧。
|
|
6802
|
+
*
|
|
6803
|
+
* **取得中に能力を開いた分を取りこぼさないため。** 取得中の loadLiveFleet は
|
|
6804
|
+
* 何もせずに戻るので、これが無いと「開いたのに、その能力だけ永久に読まれない」
|
|
6805
|
+
* (しかも次の取り直しは未確定の行が無いので張られない=二度と追いつかない)。
|
|
6806
|
+
*/
|
|
6807
|
+
var liveFleetSentCaps = [];
|
|
6808
|
+
/**
|
|
6809
|
+
* 次の取得で据え置きを迂回するか(保存が効いた直後の1回だけ)。
|
|
6810
|
+
*
|
|
6811
|
+
* 🔴 **通常のポーリングには付けない。** サーバの60秒の据え置きは、GitHub の枠を
|
|
6812
|
+
* 守るために効かせたままにする(枠は組織で共有していて、枯渇すると設定変更・
|
|
6813
|
+
* Amplifyビルド・Backlog同期が同時に止まる)。
|
|
6814
|
+
*
|
|
6815
|
+
* **取得中に保存が終わった分を取りこぼさない。** 取得中の loadLiveFleet は何もせずに
|
|
6816
|
+
* 戻るので、旗として残し、応答後にもう一度取りに行く
|
|
6817
|
+
*/
|
|
6818
|
+
var freshWanted = false;
|
|
6819
|
+
function loadLiveFleet(fresh) {
|
|
6820
|
+
if (fresh) freshWanted = true;
|
|
6127
6821
|
if (liveFleetRequested) return;
|
|
6128
6822
|
var intake = SITE.intake;
|
|
6129
6823
|
if (!intake) return;
|
|
@@ -6131,11 +6825,18 @@ ${settledPending.toString()}
|
|
|
6131
6825
|
// 素で呼ぶと描画中の ReferenceError になり、「画面が例外を出さずに描き切る」検査を割る。
|
|
6132
6826
|
if (typeof fetch !== "function") return;
|
|
6133
6827
|
liveFleetRequested = true;
|
|
6828
|
+
liveFleetSentCaps = OPEN_CAPS.slice();
|
|
6829
|
+
var useFresh = freshWanted;
|
|
6830
|
+
freshWanted = false;
|
|
6134
6831
|
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
6135
6832
|
fetch(pfx + "op=state&t=" + encodeURIComponent(intake.token), {
|
|
6136
6833
|
method: "POST",
|
|
6137
6834
|
headers: { "content-type": "application/json" },
|
|
6138
|
-
|
|
6835
|
+
// **開いている能力を伝える。** 知らない値は Lambda 側が黙って捨てる(断らない)ので、
|
|
6836
|
+
// 画面と Lambda の版が食い違っても接続状況の表示は消えない
|
|
6837
|
+
// **保存直後だけ据え置きを迂回させる。** これが無いと、サーバが保存前の答えを返し、
|
|
6838
|
+
// 足りない行が0件になって取り直しも張られない=手で再読み込みするまで追いつかない
|
|
6839
|
+
body: JSON.stringify(useFresh ? { caps: OPEN_CAPS, fresh: true } : { caps: OPEN_CAPS }),
|
|
6139
6840
|
})
|
|
6140
6841
|
// **非2xxも失敗として扱う。** ビューアとLambdaの版は独立に進むので、
|
|
6141
6842
|
// op=state 未対応のLambdaは 400 を返す。通信エラーだけを見ていると空表示になる。
|
|
@@ -6144,23 +6845,43 @@ ${settledPending.toString()}
|
|
|
6144
6845
|
// **fleet より先に読む。** fleet-status.json をまだ持たない案件(立ち上げ直後)は
|
|
6145
6846
|
// 下で throw するが、リアルタイム同期の可否は索引由来なので独立に決まる
|
|
6146
6847
|
LIVE_REALTIME = (j && j.realtime) || null;
|
|
6848
|
+
// 🔴 **registeredNow も fleet より先に読む。** まとめ表をまだ持たない案件
|
|
6849
|
+
// (=立ち上げ直後=この仕組みが一番効く案件)は下で throw するので、
|
|
6850
|
+
// ここより後に置くと**その案件だけ素通り**する
|
|
6851
|
+
LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
|
|
6852
|
+
MERGED = null;
|
|
6147
6853
|
if (!j || !j.fleet) throw new Error("no fleet");
|
|
6148
6854
|
LIVE_FLEET = normalizeFleet(j.fleet);
|
|
6149
6855
|
LIVE_FLEET_FAILED = false;
|
|
6150
6856
|
liveFleetRequested = false;
|
|
6857
|
+
MERGED = null;
|
|
6151
6858
|
refreshDataSources();
|
|
6152
6859
|
// **モーダルを開いたままでも「反映中」だけは追いつかせる。** refreshDataSources は
|
|
6153
6860
|
// モーダルが開いている間・入力中は何もしない(下地を入れ替えると開いているモーダルの
|
|
6154
6861
|
// 中身が切り離される)ので、局所的に差し替える口をここに置く
|
|
6155
6862
|
repaintReflect(document);
|
|
6863
|
+
repaintOpenLists();
|
|
6864
|
+
// まだ足りない行があるなら取り直しを続ける(無くなったら止める)
|
|
6865
|
+
refreshStateTimer();
|
|
6866
|
+
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
6867
|
+
if (freshWanted) loadLiveFleet();
|
|
6868
|
+
catchUpOpenCaps();
|
|
6156
6869
|
})
|
|
6157
6870
|
.catch(function () {
|
|
6158
6871
|
// 静的ビルドの値で描き続ける。ただし「古いかもしれない」ことは画面に出す
|
|
6159
6872
|
LIVE_FLEET = null;
|
|
6160
6873
|
LIVE_FLEET_FAILED = true;
|
|
6161
6874
|
liveFleetRequested = false;
|
|
6875
|
+
MERGED = null;
|
|
6162
6876
|
refreshDataSources();
|
|
6163
6877
|
repaintReflect(document);
|
|
6878
|
+
repaintOpenLists();
|
|
6879
|
+
// **失敗しても取り直しは続ける**(期限が来れば止まる)。ここで止めると、
|
|
6880
|
+
// 一時的な失敗のあと画面が二度と追いつかない
|
|
6881
|
+
refreshStateTimer();
|
|
6882
|
+
// 取得中に保存が効いていたら、迂回付きでもう一度取りに行く
|
|
6883
|
+
if (freshWanted) loadLiveFleet();
|
|
6884
|
+
catchUpOpenCaps();
|
|
6164
6885
|
});
|
|
6165
6886
|
}
|
|
6166
6887
|
|
|
@@ -6168,6 +6889,9 @@ ${settledPending.toString()}
|
|
|
6168
6889
|
renderConnMap();
|
|
6169
6890
|
renderDataSources();
|
|
6170
6891
|
loadLiveFleet();
|
|
6892
|
+
// **まとめ表がまだ答えていない行があれば取り直す。** 取得前は0件なので、
|
|
6893
|
+
// 実際に張られるのは応答が届いてから(loadLiveFleet が呼び直す)
|
|
6894
|
+
refreshStateTimer();
|
|
6171
6895
|
// **反映待ちがあれば自動で取り直す(再読み込みしても続く)。** 反映待ちが
|
|
6172
6896
|
// 1件も無ければタイマは張らない
|
|
6173
6897
|
scheduleReflectRefresh();
|