@takagaki/cortex-decisions-viewer 0.10.1 → 0.10.3

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 CHANGED
@@ -166,6 +166,10 @@ async function readFleetSources(repoRoot) {
166
166
  goldState: s.goldState != null ? String(s.goldState) : undefined,
167
167
  notify: typeof s.notify === "boolean" ? s.notify : undefined,
168
168
  gate: s.gate != null ? String(s.gate) : undefined,
169
+ // gate が missing_scope のときに Slack が名指しした不足スコープ(段B)。
170
+ // **列挙し忘れると静かに消える**(この対応は素通しではなく明示の許可リスト)。
171
+ // 落とすと「権限が足りません」とだけ出て、何を頼めばよいか画面から分からない
172
+ needed: s.needed != null ? String(s.needed) : undefined,
169
173
  // どこから来たか(derived / explicit / both)。**削除を出してよいかの判断に要る。**
170
174
  // 自動導出されたものは登録ファイルから消しても翌日また現れるので、
171
175
  // explicit のときだけ削除ボタンを出す。
package/dist/render.js CHANGED
@@ -601,7 +601,20 @@ function sanitizeStateNow(v) {
601
601
  /**
602
602
  * ストアの「届くか」を行に重ねる。**まとめ表より新しいときだけ**——古い確認結果で
603
603
  * 新しい実測を潰さない(時刻の比較は ISO 文字列の辞書順で足りる)。
604
- * 対象はチャットの行だけ(段A。確認ボタンが書くのがチャットだけのため)。
604
+ *
605
+ * 対象はチャットと開発の行(段B。夜間チェックがこの2つを書く)。
606
+ *
607
+ * 🔴 **突き合わせの鍵は能力ごとに違う。**
608
+ * - chat … ワークスペース + チャンネルID。チャンネルIDはワークスペースの中でしか
609
+ * 一意でないので、IDだけで突き合わせると別ワークスペースの同IDを同じものと見る
610
+ * - dev … owner/repo の生文字列(devRowsToAdd と同じ)。**ワークスペースは持たない。**
611
+ * 同じリポジトリの github-issues と github-discussions は**同じ行**を指すので、
612
+ * 2つの行に同じ判定が載るのが正しい(書く側が SK で dedupe している)
613
+ *
614
+ * **needed(足りないスコープ)も載せる。** 載せないと、夜間が missing_scope を
615
+ * 書いた行だけスコープ名が消え、「権限が足りません」とだけ出て何を直せばよいか
616
+ * 分からない状態に戻る。上書きするときは空でも必ず載せる——夜間の古い needed が
617
+ * 新しい判定の横に残ると、直したはずのスコープ名がいつまでも出る
605
618
  */
606
619
  function overlayStateOnFleet(view, rows, generatedAt) {
607
620
  const v = view || {};
@@ -609,21 +622,33 @@ function overlayStateOnFleet(view, rows, generatedAt) {
609
622
  return v;
610
623
  const byKey = {};
611
624
  for (const r of rows) {
612
- if (r.cap !== "chat" || r.question !== "reachable")
625
+ if (r.question !== "reachable")
613
626
  continue;
614
- byKey[String(r.workspace || "") + "\t" + String(r.ref)] = r;
627
+ if (r.cap === "chat")
628
+ byKey["chat\t" + String(r.workspace || "") + "\t" + String(r.ref)] = r;
629
+ else if (r.cap === "dev")
630
+ byKey["dev\t" + String(r.ref)] = r;
615
631
  }
616
632
  let changed = false;
617
633
  const externals = v.externalSources.map((s) => {
618
- if (!s || s.type !== "slack" || !s.ref)
634
+ if (!s || !s.ref)
619
635
  return s;
620
- const hit = byKey[String(s.workspace || "") + "\t" + String(s.ref)];
636
+ let hit = null;
637
+ if (s.type === "slack")
638
+ hit = byKey["chat\t" + String(s.workspace || "") + "\t" + String(s.ref)];
639
+ else if (String(s.type || "").indexOf("github") === 0)
640
+ hit = byKey["dev\t" + String(s.ref)];
621
641
  if (!hit)
622
642
  return s;
623
643
  if (generatedAt && hit.at <= generatedAt)
624
644
  return s;
625
645
  changed = true;
626
- return Object.assign({}, s, { gate: hit.status, gateAt: hit.at, gateBy: hit.by });
646
+ return Object.assign({}, s, {
647
+ gate: hit.status,
648
+ gateAt: hit.at,
649
+ gateBy: hit.by,
650
+ needed: hit.needed || "",
651
+ });
627
652
  });
628
653
  return changed ? Object.assign({}, v, { externalSources: externals }) : v;
629
654
  }
@@ -2087,6 +2112,12 @@ a.ds-name:hover { text-decoration: underline; }
2087
2112
  .ds-keyrow { flex: 1 1 100%; min-width: 0; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
2088
2113
  /* 一覧の直後に置くとき(Figma)は、行の中ではないので区切り線を持たない */
2089
2114
  .ds-keyrow-list { margin-top: 10px; padding-top: 0; border-top: none; }
2115
+ /* 🔴 **hidden を効かせる(2026-08-22)。** 鍵の欄は接続先が0件のあいだ hidden 属性で
2116
+ 畳む(作らないのではなく隠す——理由は syncKeyCardVisibility の項)。ところが
2117
+ .ops-card は display: flex、.ds-keyrow は flex アイテムを名乗るので、
2118
+ UA 既定の [hidden] { display: none } が**負ける**。畳んだつもりで出たままになる。
2119
+ ⚠ CSS は jsdom から見えないので、この行はテストでは守れない(実機で見ること)。 */
2120
+ [hidden] { display: none !important; }
2090
2121
  /* 行の中に入った鍵カードは、カードの枠を持たない(行の一部に見せる) */
2091
2122
  .ops-card.ds-keycard { border: none; box-shadow: none; padding: 0; background: transparent; gap: 6px; }
2092
2123
  .ops-card.ds-keycard > .ops-card-head { gap: 8px; }
@@ -3930,6 +3961,113 @@ ${nextStateDelay.toString()}
3930
3961
  return null;
3931
3962
  }
3932
3963
 
3964
+ /** 鍵の種別 → その鍵が効く能力。**3つの口が共有する対応表**(下の不変条件を参照) */
3965
+ var SECRET_CAP = { figma: "デザイン", backlog: "課題管理" };
3966
+
3967
+ /**
3968
+ * 「この画面の鍵の欄に貼ってください」と案内しているチェックの id → その鍵が効く能力。
3969
+ *
3970
+ * **欄が出ていないときに出すと、存在しない場所を指す案内になる。** 最後の接続先を
3971
+ * 外した直後(登録は0件・夜間のチェックは古いまま)に実際にそうなっていた。
3972
+ * **id をここ1箇所に集める**——散らすと片方だけ直る。
3973
+ */
3974
+ var KEY_POINTING_CHECKS = { backlog_secrets: "課題管理", figma_token: "デザイン" };
3975
+
3976
+ /**
3977
+ * 応答に添えられた接続先の一覧を採ったときの件数(Backlog の adoptTargets)。
3978
+ *
3979
+ * **鍵の欄の出し入れだけがこれを見る。** まとめ表(fleetVal)が新しい接続先を知るのは
3980
+ * 次の取り直しの後なので、これが無いと「行はその場で出るのに、鍵の欄だけ遅れて出る」。
3981
+ * 一覧そのものの正本は adoptTargets が持つローカルの targets で、ここは写しにしない
3982
+ *(件数しか持たないのは、判断に要るのが件数だけだから)。
3983
+ * **節を作り直すときに捨てる**(buildDataSources の先頭)——作り直しはスナップショットから
3984
+ * 描き直すことなので、そこから先は素直にスナップショットを信じる。
3985
+ */
3986
+ var ADOPTED_TARGET_COUNT = null;
3987
+
3988
+ /**
3989
+ * 鍵(APIキー/トークン)が意味を持つ接続先の数(オーナー決定 2026-08-22)。
3990
+ * **0件なら、鍵の入力欄もタイルの「⚠ トークンが未設定です」も出さない**——
3991
+ * 繋ぐ先が無いのに鍵だけ求めるのは順番の逆な案内になる。最初の一歩は
3992
+ * 「+ 接続先を追加」に揃える(2026-08-12 の「未接続でも欄を出す」B判断を上書き)。
3993
+ *
3994
+ * 🔴 **口は3つ。全部この関数を通す**(0.4.71 からの不変条件を 2026-08-22 に拡張):
3995
+ * 1. 鍵の入力カードの表示/非表示(syncKeyCardVisibility)
3996
+ * 2. タイルの「⚠ トークンが未設定です」(tokenRelevant)
3997
+ * 3. 鍵の欄を指すチェック行(KEY_POINTING_CHECKS)
3998
+ * 別々に書くと「欄はあるのに未設定と言われない」「言われるのに欄が無い」
3999
+ * 「無い欄を指す案内だけが出る」のどれかが起きる(3つとも実機で出た形)。
4000
+ */
4001
+ function keyTargetCount(cap) {
4002
+ if (cap === "課題管理") {
4003
+ // 応答で採った一覧のほうが新しい(まとめ表が追いつくのは次の取り直しから)
4004
+ if (ADOPTED_TARGET_COUNT != null) return ADOPTED_TARGET_COUNT;
4005
+ return backlogTargetsNow(internalSourceOf("課題管理")).length;
4006
+ }
4007
+ if (cap === "デザイン") {
4008
+ var d = internalSourceOf("デザイン");
4009
+ return d && d.figmaFiles ? d.figmaFiles.length : 0;
4010
+ }
4011
+ return 1;
4012
+ }
4013
+
4014
+ /**
4015
+ * この画面がいま持っている鍵カード(種別つき)。secretCard が作るたびに載せ、
4016
+ * buildDataSources が節を作り直すたびに入れ替える。
4017
+ *
4018
+ * **DOM を検索しないのはなぜか。** カードは buildDataSources の中で作られるが、
4019
+ * 置かれるのは能力モーダルの中身(=タイルを押すまで文書に入らない切り離されたノード)。
4020
+ * 組み立て直後に document から探しても**1枚も見つからない**。
4021
+ */
4022
+ var KEY_CARDS = [];
4023
+
4024
+ /**
4025
+ * 鍵の欄と、その受け皿の出し入れ(オーナー決定 2026-08-22)。
4026
+ *
4027
+ * 🔴 **カードは常に作る。隠すだけにする。** 生成そのものを止めると、
4028
+ * **0件でモーダルを開いたまま最初の接続先を足しても欄が現れない**——
4029
+ * カードを作るのは buildDataSources のループ1回きりで、そのあとの描き直し
4030
+ *(paint / liveList)は**鍵の欄を作り直さない**(入力中の値と「保存中…」を守るため)。
4031
+ * 節ごとの差し替え(refreshDataSources)もモーダルが開いている間は何もしない。
4032
+ * つまり**閉じて開き直すまで出ない**。表示の切り替えなら描き直しのたびに当て直せる。
4033
+ *
4034
+ * **受け皿ごと隠す。** .ds-keyrow は margin-top / padding-top / border-top を持つので、
4035
+ * カードだけ畳むと**理由の無い余白と区切り線**が残る(レビューで実測)。
4036
+ * 判定は「中に見えているカードが1枚も無いか」——OFF にした能力で受け皿だけが
4037
+ * 残る形(カードを作らずに return する経路)も同じ理由でここに拾われる。
4038
+ *
4039
+ * ⚠ **3つの口のうち、描き直しに追随するのはこのカードだけ。** タイルの注記
4040
+ *(tokenRelevant)とチェック行(allChecks)は**節を組み立てるときに固定**される。
4041
+ * したがって**モーダルを開いたまま最後の1件を外した瞬間だけ**、鍵の欄は消えるのに
4042
+ * タイル側は古いまま(「トークンが未設定です」や鍵の欄を指す案内が残る)になりうる。
4043
+ * 閉じれば節が作り直されて揃う。タイルはオーバーレイの裏で読めないので実害が小さいと
4044
+ * 判断して、ここは合わせていない——**揃えるなら3つとも同じ描き直しに載せること**
4045
+ *(注記だけを動的にすると、判定が1箇所という不変条件のほうが先に壊れる)。
4046
+ */
4047
+ function syncKeyCardVisibility() {
4048
+ var shown = {};
4049
+ for (var k in SECRET_CAP) {
4050
+ if (Object.prototype.hasOwnProperty.call(SECRET_CAP, k)) shown[k] = keyTargetCount(SECRET_CAP[k]) > 0;
4051
+ }
4052
+ for (var i = 0; i < KEY_CARDS.length; i++) {
4053
+ // **対応表に無い種別は隠さない**(keyTargetCount の既定と同じく出す側に倒す)。
4054
+ // 3つ目の鍵が足されて分岐を書き忘れた場合は key-card-visibility の [全種0件] が落ちる
4055
+ KEY_CARDS[i].el.hidden = shown[KEY_CARDS[i].kind] === false;
4056
+ }
4057
+ eachKeySlot(function (slot) {
4058
+ slot.hidden = !slot.querySelector("[data-secret-kind]:not([hidden])");
4059
+ });
4060
+ }
4061
+
4062
+ /** いま在る鍵の受け皿(.ds-keyrow)を順に渡す。置き場の正本は KEY_SLOTS */
4063
+ function eachKeySlot(fn) {
4064
+ var slots = KEY_SLOTS.backlog || {};
4065
+ for (var d in slots) {
4066
+ if (Object.prototype.hasOwnProperty.call(slots, d) && slots[d]) fn(slots[d]);
4067
+ }
4068
+ if (KEY_SLOTS.figma) fn(KEY_SLOTS.figma);
4069
+ }
4070
+
3933
4071
  /**
3934
4072
  * 能力ごとの「**スナップショットが答えている集合**」。
3935
4073
  *
@@ -4282,6 +4420,10 @@ ${nextStateDelay.toString()}
4282
4420
 
4283
4421
  function buildDataSources() {
4284
4422
  KEY_SLOTS = { backlog: {}, backlogNamed: false, figma: null };
4423
+ // 鍵カードも受け皿と同じく作り直す(前回の切り離されたカードを掴んだままにしない)
4424
+ KEY_CARDS = [];
4425
+ // 作り直しはスナップショットから描き直すこと。応答で採った件数はここで捨てる
4426
+ ADOPTED_TARGET_COUNT = null;
4285
4427
  var internals = fleetVal("internalSources") || [];
4286
4428
  var sources = fleetVal("externalSources") || [];
4287
4429
  var pipes = fleetVal("pipelines") || [];
@@ -4678,7 +4820,7 @@ ${nextStateDelay.toString()}
4678
4820
  // 連携の鍵は、その能力のところへ置く(画面下まで往復させない)
4679
4821
  // **GitHub(「external-sources」)はここに無い。** 読み取りトークンは廃止し、
4680
4822
  // 読み取りは GitHub App に一本化した。**この表に戻すと入力欄が復活する**
4681
- var SECRET_CAP = { figma: "デザイン", backlog: "課題管理" };
4823
+ // (対応表そのものは SECRET_CAP。3つの口で共有するので上に置いてある)
4682
4824
  (SITE.secretKinds || []).forEach(function (k) {
4683
4825
  if (!SECRET_FORMS[k]) return;
4684
4826
  // **意図的にOFFにした能力にだけ、入力欄を出さない(0.4.71)。** 使わないと決めたものに
@@ -4698,6 +4840,11 @@ ${nextStateDelay.toString()}
4698
4840
  }));
4699
4841
  return;
4700
4842
  }
4843
+ // 🔴 **接続先が0件でも、ここでは return しない(オーナー決定 2026-08-22 の実装注意)。**
4844
+ // 「0件なら鍵の欄を出さない」のは決まりだが、**生成を止めると直せなくなる**——
4845
+ // モーダルを開いたまま最初の接続先を足しても、欄は閉じて開き直すまで現れない
4846
+ //(作るのはこのループ1回きりで、以後の描き直しは鍵の欄を作り直さない)。
4847
+ // カードは常に作り、出し入れは syncKeyCardVisibility が hidden で行う
4701
4848
  // **Backlog は接続先のスペースごとにカードを出す(2026-08-20・#127)。**
4702
4849
  // キーはスペースごとに別なので、欄が1つだと**どちらの鍵を入れたのか
4703
4850
  // 誰にも分からない**まま片方の棚に入る(入れたのに動かない、が起きる)。
@@ -4780,8 +4927,16 @@ ${nextStateDelay.toString()}
4780
4927
  figma_token: "デザイン", figma_inventory: "デザイン",
4781
4928
  dev_sources_registered: "開発", dev_issues_derived: "開発",
4782
4929
  };
4930
+ // 🔴 **鍵の欄を指すチェックは、鍵の欄が出ているときだけ出す(オーナー決定 2026-08-22)。**
4931
+ // 「この画面の『Backlog のAPIキー』に貼ってください」は、接続先が0件で欄を畳んでいる
4932
+ // あいだは**存在しない欄を指す案内**になる(最後の接続先を外した直後に実際に出た。
4933
+ // 夜間のチェックは古いままなので、登録が0件になっても missing が残っている)。
4934
+ // **ここで落とすので、タイルの注記と「セットアップの残り」の両方から消える**
4935
+ // ——片方だけで畳むと、落ちた行がもう片方に湧いて出る(loose の拾い直しに落ちるため)
4783
4936
  var allChecks = (fleetVal("checks") || []).filter(function (c) {
4784
- return c != null && c.status === "missing";
4937
+ if (c == null || c.status !== "missing") return false;
4938
+ var kc = KEY_POINTING_CHECKS[c.id];
4939
+ return !(kc && keyTargetCount(kc) === 0);
4785
4940
  });
4786
4941
  // **タイルに出せたものを覚えておく。** 振り分け先の能力タイルが立たない案件
4787
4942
  //(tools に宣言が無い・行も無い)では出す場所が無くなり、項目が**黙って消える**。
@@ -5005,7 +5160,8 @@ ${nextStateDelay.toString()}
5005
5160
  // 「入力欄はあるのに未設定だと言われない」「言われるのに入力欄が無い」のどちらかが起きる。
5006
5161
  // 0.4.70 は unset で入力欄を畳んでいたので注記も抑えていたが、案A で欄を出すようにしたため揃えた
5007
5162
  // 導出が無いデータでは判断材料が無いので、注記を出す側(=入力欄と揃う側)に倒す
5008
- var tokenRelevant = derived != null ? derived !== "off" : true;
5163
+ // 接続先0件の抑止は入力欄と**同じ関数**を通す(keyTargetCount 🔴 不変条件)
5164
+ var tokenRelevant = (derived != null ? derived !== "off" : true) && keyTargetCount(cap) > 0;
5009
5165
  if (tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !((fleetVal("secretSources") || {})[sk])) {
5010
5166
  body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
5011
5167
  }
@@ -5127,6 +5283,8 @@ ${nextStateDelay.toString()}
5127
5283
  sec.appendChild(todo);
5128
5284
  }
5129
5285
 
5286
+ // 組み立てたカードに、いまの件数で表示/非表示を当てる(詳細は syncKeyCardVisibility)
5287
+ syncKeyCardVisibility();
5130
5288
  return sec;
5131
5289
  }
5132
5290
  function renderDataSources() {
@@ -5353,6 +5511,12 @@ ${nextStateDelay.toString()}
5353
5511
  // 古い「X分前に確認」を残さない(下に出るいまの結果と、時刻が2つ並ぶ)
5354
5512
  var oldAt = card.querySelector(".ds-gate-at");
5355
5513
  if (oldAt) oldAt.remove();
5514
+ // 🔴 **古いスコープ名の札も必ず消してから出し直す。** 行には夜間の判定に添えた
5515
+ // 札が出ていることがあり(段B)、置き換えた「接続OK」の横に古いスコープ名が
5516
+ // 残ると、同じ行が2つのことを言う——この節が何度も直してきた形そのもの。
5517
+ // 押し直すたびに札が増えていくのも、まとめてここで断つ
5518
+ var oldScopes = card.querySelectorAll(".ds-probe-scope");
5519
+ for (var oi = 0; oi < oldScopes.length; oi++) oldScopes[oi].remove();
5356
5520
  // 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
5357
5521
  // 本文へ落とすと、いちばん要る情報が説明文に埋もれる
5358
5522
  if (j.gate === "missing_scope") {
@@ -5677,6 +5841,10 @@ ${nextStateDelay.toString()}
5677
5841
  // **出し入れのたびに反映中を当て直す。** 外している間の描き直しはこの行に届かない
5678
5842
  // ので、戻すときに当てないと古い文言のまま出る
5679
5843
  applyReflect(emptyEl, reflectingNow());
5844
+ // 🔴 **鍵の欄の出し入れは、描き直しのたびに当て直す(2026-08-22)。** 最初の1件を
5845
+ // 足した瞬間に欄が現れ、最後の1件を外した瞬間に消えるのはここが効いているから。
5846
+ // 節ごとの差し替え(refreshDataSources)はモーダルが開いている間は何もしない
5847
+ syncKeyCardVisibility();
5680
5848
  }
5681
5849
 
5682
5850
  /**
@@ -5694,6 +5862,9 @@ ${nextStateDelay.toString()}
5694
5862
  if (x && x.domain && x.projectKey) next.push({ domain: String(x.domain), projectKey: String(x.projectKey) });
5695
5863
  }
5696
5864
  targets = next;
5865
+ // **鍵の欄の判断もこの一覧に従わせる(2026-08-22)。** keyTargetCount はまとめ表を
5866
+ // 見るので、ここで教えないと「行はその場で出るのに鍵の欄だけ次の取り直しまで出ない」
5867
+ ADOPTED_TARGET_COUNT = targets.length;
5697
5868
  paint();
5698
5869
  return targetsGenerationOf(targets) !== before;
5699
5870
  }
@@ -5722,9 +5893,14 @@ ${nextStateDelay.toString()}
5722
5893
  var dom = String(targets[ti].domain || "").toLowerCase();
5723
5894
  if (dom && domains.indexOf(dom) === -1) domains.push(dom);
5724
5895
  }
5725
- // **0件の案件にも1つ出す。** 「接続先を足してからでないと鍵を入れられない」形にすると、
5726
- // 立ち上げ直後の案件が最初の一歩を踏めない(#138 で直したのと同じ穴)。
5896
+ // **0件の案件にも受け皿は1つ作る。ただし中身は隠れている(2026-08-22)。**
5897
+ // オーナー決定で「接続先が0件なら鍵の欄は出さない」になったが、**作らないのではなく
5898
+ // 隠す**——受け皿ごと無いと、モーダルを開いたまま最初の1件を足したときに
5899
+ // 鍵の欄を置く場所が無く、閉じて開き直すまで現れない(syncKeyCardVisibility の項)。
5900
+ // 受け皿は0件のあいだ hidden なので、区切り線と余白も出ない。
5727
5901
  // 空文字のキーは「どのスペースとも結び付いていない受け皿」で、行の中には入らない
5902
+ //(足した接続先のドメインに対応するスロットは無いので、鍵の欄はこの受け皿に
5903
+ // 残ったまま一覧の下に現れる。paint() はこのキーを移動の対象から外している)
5728
5904
  if (!domains.length) domains.push("");
5729
5905
  for (var di = 0; di < domains.length; di++) keySlots[domains[di]] = el("div", { class: "ds-keyrow" });
5730
5906
  KEY_SLOTS.backlog = keySlots;
@@ -6088,7 +6264,7 @@ ${nextStateDelay.toString()}
6088
6264
  // how / note)。全文をここに移すと二重になり、片方だけ直る形になる
6089
6265
  // ——カードは接続マップとタイルの注記と条件を揃えているので、動かすと壊れやすい。
6090
6266
  hint: [
6091
- "Figma でトークンを発行します(読み取りだけ。スコープ2つ)。手順は上の「Figma のトークン」の欄にあります。",
6267
+ "Figma でトークンを発行します(読み取りだけ。スコープ2つ)。手順は「Figma のトークン」の欄にあります(ファイルを追加すると上に出ます)。",
6092
6268
  "発行したアカウントを、読み取りたい Figma ファイルに閲覧者として招待します(招待が無いと、トークンが正しくても読めません)。",
6093
6269
  "そのファイルのURLを下の欄に貼って「追加」を押します。",
6094
6270
  "追加したファイルは、画面名と Figma へのリンクが毎晩同期されます(画像は取り込まれません)。",
@@ -6794,6 +6970,17 @@ ${nextStateDelay.toString()}
6794
6970
  badges,
6795
6971
  gateEl,
6796
6972
  ]);
6973
+ // 足りないスコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
6974
+ //
6975
+ // **押した瞬間の結果と同じ見た目にする**(probeSection が同じ class を付けている)。
6976
+ // 出どころは2つあるが、読む人にとっては同じ情報:
6977
+ // - 夜間チェックが書いた needed(fleet-status.json 経由)
6978
+ // - 状態ストアの行が上書きした needed(overlayStateOnFleet 経由)
6979
+ // ここが無いと「権限が足りません」とだけ出て、**どのスコープを頼めばよいか
6980
+ // 画面から分からない**——押して確かめた人にしか分からない状態に戻る
6981
+ if (s.gate === "missing_scope" && s.needed) {
6982
+ card.appendChild(el("code", { class: "ds-probe-scope", text: String(s.needed) }));
6983
+ }
6797
6984
  if (gateAtEl) card.appendChild(gateAtEl);
6798
6985
  // チャンネルの登録解除。書き込み経路(intake Lambda)がある案件でだけ出す。
6799
6986
  // **Slack だけ。** github は登録ファイルが別(dev-sources.json)で、下のトグルと
@@ -7001,6 +7188,9 @@ ${nextStateDelay.toString()}
7001
7188
  box.__sig = next;
7002
7189
  while (box.firstChild) box.removeChild(box.firstChild);
7003
7190
  build(box);
7191
+ // 一覧が変わった=接続先の件数が変わったかもしれない。鍵の欄を当て直す
7192
+ // (Figma の鍵の欄は一覧の**外**にあるので、ここで見ないと取り残される)
7193
+ syncKeyCardVisibility();
7004
7194
  };
7005
7195
  box.__rebuild();
7006
7196
  return box;
@@ -7521,7 +7711,7 @@ ${nextStateDelay.toString()}
7521
7711
  // 押すまで見えないと成立しない
7522
7712
  // - .sec-preferred … 「誰のアカウントで発行するのか」を**手順より先に**伝える行。
7523
7713
  // 未登録のときだけ出る=これから貼る人にだけ出るので、畳むと存在理由が消える
7524
- return el("div", { class: "ops-card" + (inRow ? " ds-keycard" : ""), "data-secret-kind": kind }, [
7714
+ var card = el("div", { class: "ops-card" + (inRow ? " ds-keycard" : ""), "data-secret-kind": kind }, [
7525
7715
  el("div", { class: "ops-card-head" }, [
7526
7716
  el("span", { class: "ops-card-icon", text: meta.icon }),
7527
7717
  el("h3", { class: "ops-card-title", text: cardTitle }),
@@ -7542,6 +7732,11 @@ ${nextStateDelay.toString()}
7542
7732
  // 説明が落ちるのは、いちばん落としてはいけない種類の欠落
7543
7733
  el("p", { class: "sec-note", text: "入れた値はここには表示されません(保存だけできます)。保存先は案件ごとに分かれているので、他の案件には影響しません。" }),
7544
7734
  ]);
7735
+ // **作った鍵カードは1枚残らずここに載せる。** 出し入れ(接続先が0件のときに畳む)は
7736
+ // DOM 検索ではなくこの控えで回す——カードは能力モーダルの中身で、タイルを押すまで
7737
+ // 文書に入っていないため(syncKeyCardVisibility の項)
7738
+ KEY_CARDS.push({ kind: kind, el: card });
7739
+ return card;
7545
7740
  }
7546
7741
 
7547
7742
  // ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,