@takagaki/cortex-decisions-viewer 0.12.214 → 0.12.215

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.
Files changed (2) hide show
  1. package/dist/render.js +201 -262
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -2336,8 +2336,8 @@ const SECRET_NOW_STATES = ["registered", "missing", "unknown"];
2336
2336
  /**
2337
2337
  * `secretsNow`(鍵が登録されているか・列2)の許可リスト。
2338
2338
  *
2339
- * 応答の1行は `{ adapter, kind, state, source, space? }`。画面が使うのは kind / state /
2340
- * space だけで、**adapter は使わない**(鍵カードは種別で引く)。source は「どの棚が
2339
+ * 応答の1行は `{ adapter, kind, state, source, space?, savedAt? }`。画面が使うのは kind / state /
2340
+ * space / savedAt だけで、**adapter は使わない**(鍵カードは種別で引く)。source は「どの棚が
2341
2341
  * 答えたか」の内部語なので、画面に出す語へ畳むのは読み出し側(`secretStateFromNow`)。
2342
2342
  */
2343
2343
  function sanitizeSecretsNow(v) {
@@ -2359,6 +2359,9 @@ function sanitizeSecretsNow(v) {
2359
2359
  // 揃えないと**入っている鍵を「未登録」と言う**(`issuesRefOf` と同じ理由)
2360
2360
  if (typeof r.space === "string" && r.space)
2361
2361
  row.space = r.space.toLowerCase();
2362
+ // 保存日時(受付が Secrets Manager の LastChangedDate から載せる ISO)。日付として読める形だけ通す(札の「M/D に保存」の材料・便 KU)
2363
+ if (typeof r.savedAt === "string" && /^\d{4}-\d{2}-\d{2}T[\d:.]+(Z|[+-]\d{2}:?\d{2})$/.test(r.savedAt) && !isNaN(new Date(r.savedAt).getTime()))
2364
+ row.savedAt = r.savedAt;
2362
2365
  out.push(row);
2363
2366
  }
2364
2367
  return out.length ? out : null;
@@ -2393,7 +2396,7 @@ function secretStateFromNow(rows, kind, space) {
2393
2396
  hit = mine[0];
2394
2397
  }
2395
2398
  if (hit.state === "registered")
2396
- return { known: "yes", src: "Secrets Manager" };
2399
+ return hit.savedAt ? { known: "yes", src: "Secrets Manager", savedAt: hit.savedAt } : { known: "yes", src: "Secrets Manager" };
2397
2400
  if (hit.state === "missing")
2398
2401
  return { known: "no", src: "" };
2399
2402
  return null;
@@ -5734,7 +5737,6 @@ a.ds-tile:hover { text-decoration: none; }
5734
5737
  .ds-more { margin-top: 14px; }
5735
5738
  .ds-more > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
5736
5739
  .ds-more[open] > summary { margin-bottom: 8px; }
5737
- .ds-more-key { margin: 6px 0 0; font-size: 12.5px; color: var(--text); display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
5738
5740
  /* 行の状態に添える「次に誰が何をするか」(2026-08-18)。**必ず自分の行を占める**
5739
5741
  (flex: 1 1 100%)——状態バッジの横に押し込むと、いちばん読ませたい一文が
5740
5742
  カード幅に負けて省略される。状態の語より弱く、しかし読める大きさで出す */
@@ -5794,7 +5796,10 @@ a.ds-tile:hover { text-decoration: none; }
5794
5796
  .ops-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 4px 0 8px; }
5795
5797
  .ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
5796
5798
  .sec-state { font-size: 12.5px; font-weight: 600; margin: 0 0 8px; color: var(--muted); }
5797
- .sec-state.is-managed { color: #1a7f37; }
5799
+ .sec-state.is-managed, .sec-state.is-ok { color: #1a7f37; }
5800
+ .sec-state.is-failed { color: #b42318; }
5801
+ .sec-state.is-checking { color: var(--muted); font-weight: 400; font-style: italic; }
5802
+ .sec-saved { margin: 6px 0 0; }
5798
5803
  .sec-state.is-none { color: var(--muted); font-weight: 400; }
5799
5804
  /* 登録されているか分からない(engine がスペース別の状況をまだ出していない)。
5800
5805
  **未登録と同じ見た目にしない**——断定していないことが色でも分かるようにする */
@@ -5830,16 +5835,6 @@ a.ds-tile:hover { text-decoration: none; }
5830
5835
  .ops-card.ds-keycard > .ops-card-head { gap: 8px; }
5831
5836
  .ops-card.ds-keycard .ops-card-icon { font-size: 16px; }
5832
5837
  .ops-card.ds-keycard .ops-card-title { font-size: 13px; }
5833
- /* 押すまで入力欄を出さない(2026-08-21・オーナー指摘)。summary はボタンに見せる */
5834
- .sec-input-fold { border: none; margin: 0; }
5835
- .sec-input-fold > summary {
5836
- cursor: pointer; display: inline-block; list-style: none;
5837
- padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border);
5838
- background: #fff; color: var(--text); font-size: 12.5px; font-weight: 600;
5839
- }
5840
- .sec-input-fold > summary::-webkit-details-marker { display: none; }
5841
- .sec-input-fold > summary:hover { border-color: var(--accent); color: var(--accent); }
5842
- .sec-input-fold[open] > summary { margin-bottom: 8px; }
5843
5838
  .ds-addform { border: 1px dashed var(--border); border-radius: 8px; padding: 12px 14px; margin-top: 10px; background: #fff; }
5844
5839
  .ds-addform-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
5845
5840
  .ds-addform .sec-input { margin-bottom: 6px; }
@@ -10156,7 +10151,7 @@ ${story_1.splitByNameLinks.toString()}
10156
10151
  // 🔴 **Backlog の行の一手(c-07 の 2 本目)。** 行の中身(そのスペースのキーの有無・同期経路・
10157
10152
  // スペースの数)で決まるので関数で持つ。row は { domain, projectKey, gate }。
10158
10153
  // DS_GATE_CAP.issues は札(APIキーが未登録です)だけを持ち、help を持たせない(一手の置き場は 1 か所)。
10159
- // 部品の型は 文字列 / { copy, bare } / { act: "backlog-key" | "backlog-route-on", label, space }
10154
+ // 部品の型は 文字列 / { copy, bare } / { act: "backlog-route-on", label }
10160
10155
  backlog: {
10161
10156
  no_token: {
10162
10157
  resting: function (row) { return backlogRowHelpParts(row); },
@@ -10298,21 +10293,16 @@ ${story_1.splitByNameLinks.toString()}
10298
10293
  /**
10299
10294
  * 行の一手に置くボタン(c-07)。**押せないボタンは作らない**(null)。
10300
10295
  * - "connect" … [接続する]。Slack の承認画面へ送る(slackConnectInline)。投入の口が無ければ null
10301
- * - "figma-key" … [トークンを入れる]/[トークンを入れ直す]。下の「Figma のトークン」の欄の入力へ移る
10302
- * - "backlog-key" … [入れる]/[入れ直す]。**そのスペースの**鍵カードを開いて入力へ移る(arg=スペース)。
10303
- * 受け皿が無いスペースには作らない(別のスペースの欄へ落とさない)
10296
+ * 🔴 **鍵を入れるボタンは作らない(便 KU)。** 鍵の欄は接続先の行の中に最初から見えている(secretCard)ので、
10297
+ * 「入れる」の入口を行の一手に重ねない(同じ鍵に[入れる]が 3 か所あった)
10304
10298
  * - "backlog-route-on" … [切り替える]。同期経路を固定 IP 経由にする(setBacklogRoute)
10305
10299
  * @param label 省略時は既定の語
10306
- * @param arg 部品が要る材料(backlog-key のスペース)
10300
+ * @param arg 部品が要る材料(今は使う一手が無い)
10307
10301
  */
10308
10302
  function actNode(act, label, arg) {
10309
10303
  if (!SITE.intake) return null;
10310
10304
  if (act === "connect") return slackConnectInline(label || "接続する");
10311
- // Backlog の行の一手(c-07 の 2 本目)。arg はスペース(ドメイン)。**受け皿が無ければ作らない**
10312
- if (act === "backlog-key") return backlogKeyJump(arg, label || "入れる");
10313
10305
  if (act === "backlog-route-on") return backlogRouteOnButton(label || "切り替える");
10314
- // 鍵の欄(KEY_SLOTS.figma)が無い構成では、移る先が無いので出さない
10315
- if (act === "figma-key") return secretKindsShown().indexOf("figma") >= 0 ? figmaKeyJump(label || "トークンを入れる") : null;
10316
10306
  return null;
10317
10307
  }
10318
10308
  /**
@@ -10488,13 +10478,12 @@ ${story_1.splitByNameLinks.toString()}
10488
10478
  },
10489
10479
  issues: { no_token: { text: "APIキーが未登録です", cls: "ds-gate-muted" } },
10490
10480
  design: {
10491
- // 🔴 **鍵が無い行の一手は、行の中の 1 行(c-07)。** [トークンを入れる]が下の「Figma のトークン」の欄の入力へ移る
10481
+ // 🔴 **鍵が無い行の一手は、行の中の 1 行(c-07)。** 一手の文が下の「Figma のトークン」の欄を指す(ボタンは無い・便 KU)
10492
10482
  no_token: {
10493
10483
  text: "トークンが未登録です",
10494
10484
  cls: "ds-gate-muted",
10495
- help: "Figma のトークンを入れてください。",
10485
+ help: "Figma のトークンを、下の欄に入れてください。",
10496
10486
  helpLink: { href: FIGMA_SETTINGS_URL, label: "Figma の設定でトークンを発行する →" },
10497
- act: "figma-key",
10498
10487
  },
10499
10488
  // 「アクセス不可」では、トークンの失効なのか(403)ファイルを共有していないのか(404)が
10500
10489
  // 画面から見分けられない(理由が文字の中だけ=機械の語に依存した分岐は書かない・c-07)。
@@ -10505,9 +10494,7 @@ ${story_1.splitByNameLinks.toString()}
10505
10494
  unreachable: {
10506
10495
  text: "ファイルを読めません",
10507
10496
  cls: "ds-gate-warn",
10508
- help: "このファイルに、トークンを発行したアカウントを閲覧者として招待してください(トークンが切れているときは入れ直してください)。",
10509
- act: "figma-key",
10510
- actLabel: "トークンを入れ直す",
10497
+ help: "このファイルに、トークンを発行したアカウントを閲覧者として招待してください(トークンが切れているときは、下の欄に入れ直してください)。",
10511
10498
  },
10512
10499
  },
10513
10500
  };
@@ -11109,24 +11096,6 @@ ${story_1.splitByNameLinks.toString()}
11109
11096
  return wrap;
11110
11097
  }
11111
11098
 
11112
- /**
11113
- * [トークンを入れる]/[トークンを入れ直す](Figma の行の一手・c-07)。
11114
- * 一覧の直後にある「Figma のトークン」の欄(KEY_SLOTS.figma)の入力へ移って focus する。
11115
- * **欄は 0 件のあいだ畳まれている**が、行がある=ファイルが 1 件以上あるので畳まれていない。
11116
- * 入力が見つからなければ何もしない(鍵の欄を作る口が無い構成)。
11117
- */
11118
- function figmaKeyJump(label) {
11119
- var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "トークンを入れる" });
11120
- btn.addEventListener("click", function () {
11121
- var slot = KEY_SLOTS.figma;
11122
- var input = slot && slot.querySelector("input, textarea");
11123
- if (!input) return;
11124
- try { if (input.scrollIntoView) input.scrollIntoView({ block: "center" }); } catch (e) { /* 動かせなくても focus は試す */ }
11125
- input.focus();
11126
- });
11127
- return btn;
11128
- }
11129
-
11130
11099
  /** 台帳から、そのワークスペースの接続状態を引く(無ければ null=断定しない) */
11131
11100
  function tokenStateOf(workspaces, teamId) {
11132
11101
  var list = workspaces || [];
@@ -12425,32 +12394,15 @@ ${story_1.splitByNameLinks.toString()}
12425
12394
  // **対応表に無い種別は隠さない**(keyTargetCount の既定と同じく出す側に倒す)。
12426
12395
  // 3つ目の鍵が足されて分岐を書き忘れた場合は key-card-visibility の [全種0件] が落ちる
12427
12396
  var hide = shown[KEY_CARDS[i].kind] === false;
12428
- // 🔴 **Backlog の行の中のカードは、0 件の判定(上)に加えて「見せる理由があるとき」だけ見せる**
12429
- //(c-07 の 2 本目)。0 件が勝つ——0 件のあいだは理由があっても出さない(順序の逆な案内になる)
12430
- if (!hide && KEY_CARDS[i].compact) hide = !backlogKeyCardWanted(KEY_CARDS[i]);
12431
12397
  KEY_CARDS[i].el.hidden = hide;
12432
- if (!hide && KEY_CARDS[i].compact && KEY_CARDS[i].refresh) KEY_CARDS[i].refresh();
12398
+ // 🔴 札は見せる瞬間に当て直す(作ったときの答えは古いことがある。取り直しが後から届く・#227)
12399
+ if (!hide && KEY_CARDS[i].refresh) KEY_CARDS[i].refresh();
12433
12400
  }
12434
12401
  eachKeySlot(function (slot) {
12435
12402
  slot.hidden = !slot.querySelector("[data-secret-kind]:not([hidden])");
12436
12403
  });
12437
12404
  }
12438
12405
 
12439
- /**
12440
- * Backlog の行の中の鍵カードを、いま見せるか(c-07 の 2 本目・スペースごとの判定)。次の 4 つのどれか:
12441
- * ① キーが要る(backlogKeyNeededForSpace=登録状況が「未登録」・足した直後で答えがまだ無い等)
12442
- * ② 一手の[入れる]/[入れ直す]・「その他」の[入れ直す]が押された(forced)
12443
- * ③ 触り始めた(開いた・値がある・結果の文がある)。🔴 **保存直後の「✓ 保存しました」と入力中の値を畳まない**(#223)
12444
- * 使えているスペースのカードは、ふだん隠れている(DOM には常に在る=入力中の値・結果は消えない)。
12445
- */
12446
- function backlogKeyCardWanted(ent) {
12447
- if (ent.forced) return true;
12448
- if (ent.fold && ent.fold.open) return true;
12449
- if (ent.input && ent.input.value !== "") return true;
12450
- if (ent.msg && ent.msg.textContent !== "") return true;
12451
- return backlogKeyNeededForSpace(ent.space);
12452
- }
12453
-
12454
12406
  /** いま在る鍵の受け皿(.ds-keyrow)を順に渡す。置き場の正本は KEY_SLOTS */
12455
12407
  function eachKeySlot(fn) {
12456
12408
  var slots = KEY_SLOTS.backlog || {};
@@ -13046,6 +12998,7 @@ ${story_1.splitByNameLinks.toString()}
13046
12998
  KEY_CARDS = [];
13047
12999
  // Backlog の節の行の控え・結果の帯・描き直しの口も作り直す(切り離された古い節を掴んだままにしない)
13048
13000
  BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
13001
+ FIGMA_UI = { keys: [] };
13049
13002
  // 作り直しはスナップショットから描き直すこと。応答で採った件数はここで捨てる
13050
13003
  ADOPTED_TARGET_COUNT = null;
13051
13004
  var internals = fleetVal("internalSources") || [];
@@ -14501,6 +14454,7 @@ ${story_1.splitByNameLinks.toString()}
14501
14454
  MERGED = null;
14502
14455
  repaintOpenLists();
14503
14456
  refreshDataSources();
14457
+ syncKeyCardVisibility(); // 鍵の札(接続OK/失敗)も同じ結果で当て直す
14504
14458
  }
14505
14459
  // 本物の行を 1 回だけ取りに行く(据え置きのまま・「reloadLiveState」の項)
14506
14460
  reloadLiveState();
@@ -15452,7 +15406,7 @@ ${story_1.splitByNameLinks.toString()}
15452
15406
  }
15453
15407
  var ipLabel = ip ? "固定の送信元 IP(" + ip + ")" : "固定の送信元 IP";
15454
15408
  if (keyState === "no") {
15455
- return "同期は" + ipLabel + "から行います。お客様にこの IP の許可を依頼し、接続先の行の「API キーを入れてください」から登録すると同期が始まります。";
15409
+ return "同期は" + ipLabel + "から行います。お客様にこの IP の許可を依頼し、接続先の行の下の欄に API キーを貼って[保存して接続を確認]を押すと同期が始まります。";
15456
15410
  }
15457
15411
  // 2 文に収める(実装レビューの指摘)。押した直後の帯(サーバの文面)も「許可を依頼」を言うが、
15458
15412
  // 帯は数秒で読み終える結果で、この行は次に開いたときも残る状態——役目が違うので両方に置く
@@ -15551,7 +15505,7 @@ ${story_1.splitByNameLinks.toString()}
15551
15505
  * 部品の置き場:
15552
15506
  * - 一手 … DS_GATE_RESTING_HELP.backlog → backlogRowHelpParts(行の中身で決まる)
15553
15507
  * - キーの欄 … これまでと同じスペースごとの受け皿(KEY_SLOTS.backlog・#223)に同じカード。
15554
- * 変えるのは見え方だけ(syncKeyCardVisibility の backlogKeyCardWanted)
15508
+ * 変えるのは見え方だけ(syncKeyCardVisibility)
15555
15509
  * - 同期経路の切り替え … setBacklogRoute に 1 本化。入口はタイルの一手・[接続を確認]の結果・「その他」のスイッチ
15556
15510
  * - 札(事実)… 経路札「固定 IP 経由」・リアルタイム札(appendBacklogChips)
15557
15511
  * ------------------------------------------------------------------------- */
@@ -15562,6 +15516,8 @@ ${story_1.splitByNameLinks.toString()}
15562
15516
  * 🔴 **鍵の置き場の正本は KEY_SLOTS。ここは行の控えだけ**(#223 の「節の中に受け皿の控えを持たない」は守る)。
15563
15517
  */
15564
15518
  var BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
15519
+ /** Figma の鍵が効くファイル(鍵カードの札が引く。一覧を描くときに入れ替える) */
15520
+ var FIGMA_UI = { keys: [] };
15565
15521
 
15566
15522
  /** いま見えている行のスペース(小文字・重複なし) */
15567
15523
  function backlogDomainsNow() {
@@ -15614,27 +15570,13 @@ ${story_1.splitByNameLinks.toString()}
15614
15570
  return false;
15615
15571
  }
15616
15572
 
15617
- /** そのスペースの鍵カードを見せるか(行のどれかが要るか。行が無ければ要らない) */
15618
- function backlogKeyNeededForSpace(space) {
15619
- var domains = backlogDomainsNow();
15620
- var dom = space || (domains.length === 1 ? domains[0] : "");
15621
- if (!dom) return false;
15622
- var rows = BACKLOG_UI.rows || [];
15623
- for (var i = 0; i < rows.length; i++) {
15624
- if (String(rows[i].domain || "").toLowerCase() !== dom) continue;
15625
- var r = reachableStateOf("issues", issuesRefOf(rows[i].domain, rows[i].projectKey));
15626
- if (backlogKeyNeeded(dom, r ? r.status : null)) return true;
15627
- }
15628
- return false;
15629
- }
15630
-
15631
15573
  /**
15632
15574
  * Backlog の行に出す一手のパーツ(無ければ null)。**スペースをまたがない**(ボタンはその行のスペースの受け皿だけを引く)。
15633
15575
  *
15634
- * キーが要る → 「API キーを入れてください。[入れる]」(複数スペースならスペース名つき)
15635
- * 読めない・通常経路 → 「読めません。API キーを入れ直してください。[入れ直す] Backlog に IP 制限があるなら、固定 IP 経由で読んでください。[切り替える]」
15636
- * 読めない・固定 IP → 「お客様に固定の送信元 IP(…)の許可を依頼してください。[コピー] 許可済みなら、API キーを入れ直してください。[入れ直す]」
15637
- * 読めない・経路不明 → 「読めません。API キーを入れ直してください。[入れ直す]」(切り替えが押せるか分からないので[切り替える]は出さない)
15576
+ * キーが要る → 「API キーを、下の欄に入れてください。」(複数スペースならスペース名つき)
15577
+ * 読めない・通常経路 → 「読めません。下の欄に API キーを入れ直してください。 Backlog に IP 制限があるなら、固定 IP 経由で読んでください。[切り替える]」
15578
+ * 読めない・固定 IP → 「お客様に固定の送信元 IP(…)の許可を依頼してください。[コピー] 許可済みなら、下の欄に API キーを入れ直してください。」
15579
+ * 読めない・経路不明 → 「読めません。下の欄に API キーを入れ直してください。」(切り替えが押せるか分からないので[切り替える]は出さない)
15638
15580
  *
15639
15581
  * 🔴 **理由の語(HTTP の番号等)には依存しない。** 401・403・404 は画面から見分けられないので、読めない行は広い方の 1 行。
15640
15582
  * IP 制限と確定できるのは[接続を確認]の応答の reason だけ(appendBacklogProbeSwitch)。
@@ -15646,10 +15588,10 @@ ${story_1.splitByNameLinks.toString()}
15646
15588
  var dom = String(row.domain || "").toLowerCase();
15647
15589
  var multi = backlogDomainsNow().length > 1;
15648
15590
  if (backlogKeyNeeded(dom, row.gate)) {
15649
- return [(multi ? dom + " の " : "") + "API キーを入れてください。", { act: "backlog-key", space: dom, label: "入れる" }];
15591
+ // 🔴 鍵の欄は接続先の行の中に最初から見えている(便 KU)。ここは指す文だけでボタンは置かない
15592
+ return [(multi ? dom + " の " : "") + "API キーを、下の欄に入れてください。"];
15650
15593
  }
15651
15594
  if (row.gate !== "unreachable") return null;
15652
- var reenter = { act: "backlog-key", space: dom, label: "入れ直す" };
15653
15595
  var route = LIVE_BACKLOG_ROUTE;
15654
15596
  if (route === "fixed-ip") {
15655
15597
  // IP が取れていなければ IP も[コピー]も出さない(嘘の値を書かない・#516)
@@ -15657,9 +15599,9 @@ ${story_1.splitByNameLinks.toString()}
15657
15599
  var head = ip
15658
15600
  ? ["お客様に固定の送信元 IP(" + ip + ")の許可を依頼してください。", { copy: ip, bare: true }]
15659
15601
  : ["お客様に固定の送信元 IP の許可を依頼してください。"];
15660
- return head.concat([" 許可済みなら、API キーを入れ直してください。", reenter]);
15602
+ return head.concat([" 許可済みなら、下の欄に API キーを入れ直してください。"]);
15661
15603
  }
15662
- var base = ["読めません。API キーを入れ直してください。", reenter];
15604
+ var base = ["読めません。下の欄に API キーを入れ直してください。"];
15663
15605
  if (route === "runner") {
15664
15606
  // 切り替えは案件全体の経路(スペースごとではない)。複数スペースでは強めに言う
15665
15607
  var scope = multi
@@ -15690,54 +15632,6 @@ ${story_1.splitByNameLinks.toString()}
15690
15632
  return appendReachableReason(row, "issues", ref);
15691
15633
  }
15692
15634
 
15693
- /** そのスペースの鍵の受け皿(無ければ null)。名無しの受け皿は、スペースが 1 つ以下のあいだだけ代わりを務める(ensureKeySlots の項) */
15694
- function backlogKeySlotFor(space) {
15695
- var slots = KEY_SLOTS.backlog || {};
15696
- var d = String(space || "").toLowerCase();
15697
- if (d && slots[d]) return slots[d];
15698
- if (slots[""] && backlogDomainsNow().length <= 1) return slots[""];
15699
- return null;
15700
- }
15701
-
15702
- /** そのスペースの鍵カードの控え(無ければ null)。**ほかのスペースのカードへ落とさない** */
15703
- function backlogKeyEntryFor(space) {
15704
- var d = String(space || "").toLowerCase();
15705
- var list = [];
15706
- for (var i = 0; i < KEY_CARDS.length; i++) {
15707
- if (KEY_CARDS[i].kind === "backlog" && KEY_CARDS[i].compact) list.push(KEY_CARDS[i]);
15708
- }
15709
- for (var j = 0; j < list.length; j++) if (d && list[j].space === d) return list[j];
15710
- if (backlogDomainsNow().length <= 1) {
15711
- for (var k = 0; k < list.length; k++) if (list[k].space === "") return list[k];
15712
- }
15713
- return null;
15714
- }
15715
-
15716
- /** そのスペースの鍵カードを見せて、入力へ移る([入れる]・[入れ直す])。見つからなければ false */
15717
- function openBacklogKeyCard(space) {
15718
- var ent = backlogKeyEntryFor(space);
15719
- if (!ent) return false;
15720
- ent.forced = true;
15721
- ent.fold.open = true;
15722
- syncKeyCardVisibility();
15723
- try { if (ent.input.scrollIntoView) ent.input.scrollIntoView({ block: "center" }); } catch (e) { /* 動かせなくても focus は試す */ }
15724
- ent.input.focus();
15725
- return true;
15726
- }
15727
-
15728
- /**
15729
- * [入れる]/[入れ直す]。**そのスペースの受け皿が無ければ作らない**(別のスペースの欄へ移さない)。
15730
- * 🔴 **作る条件は受け皿(KEY_SLOTS)だけ。カードの控えは押した時点で引く**——節は鍵カードより先に組み立てられる
15731
- *(buildDataSources の順序)ので、行を描く時点ではカードがまだ無い。受け皿は節が先に作っている
15732
- */
15733
- function backlogKeyJump(space, label) {
15734
- if (secretKindsShown().indexOf("backlog") < 0) return null;
15735
- if (!backlogKeySlotFor(space)) return null;
15736
- var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "入れる" });
15737
- btn.addEventListener("click", function () { openBacklogKeyCard(space); });
15738
- return btn;
15739
- }
15740
-
15741
15635
  /**
15742
15636
  * 同期経路の切り替え(固定 IP 経由 ⇄ 通常)。**スイッチ(「その他」)とタイルの[切り替える]が同じこの関数を呼ぶ**
15743
15637
  * ——操作を 2 本書かない(op・値・確認なし・応答の route を正とする・changed:false の取り直しは 1 つだけ持つ)。
@@ -16881,7 +16775,7 @@ ${story_1.splitByNameLinks.toString()}
16881
16775
  // 🔴 **追加の欄は URL の入力と[追加]だけ(c-07 の 2 本目)。** 手順の文は置かない(hint を渡さない)。
16882
16776
  // 外した情報の行き先:
16883
16777
  // - 「起票先は変わりません」「毎晩同期します」「外すには」… 上部の説明文(openCapabilityModal)
16884
- // - 「スペースごとに別のAPIキー」… 足した行の一手「◯◯ の API キーを入れてください」と、「その他」のスペースごとの行
16778
+ // - 「スペースごとに別のAPIキー」… 足した行の中のスペースごとの鍵の欄(札つき)
16885
16779
  // - 「専用 URL の登録が要ります」… 追加した行のリアルタイム札(未受信)と、一覧の下の一手[設定する]
16886
16780
  build: function (url) {
16887
16781
  return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
@@ -16940,21 +16834,6 @@ ${story_1.splitByNameLinks.toString()}
16940
16834
  box.appendChild(rtBox);
16941
16835
 
16942
16836
  var routeWrap = backlogRouteSwitch();
16943
- var keyRows = liveList("backlog-key-rows", function () {
16944
- var ds = backlogDomainsNow();
16945
- return JSON.stringify(ds.map(function (d) { return [d, secretStateOf("backlog", d, ds.length).known]; }));
16946
- }, function (b) {
16947
- var ds = backlogDomainsNow();
16948
- ds.forEach(function (d) {
16949
- var st = secretStateOf("backlog", d, ds.length);
16950
- var word = st.known === "yes" ? "登録済みです" : st.known === "no" ? "未登録です" : "確認中です";
16951
- // ボタンはそのスペースの鍵カードを開く(受け皿が無ければ作らない)
16952
- b.appendChild(el("p", { class: "ds-more-key" }, [
16953
- el("span", { text: d + ":API キーは" + word }),
16954
- backlogKeyJump(d, st.known === "no" ? "入れる" : "入れ直す"),
16955
- ]));
16956
- });
16957
- });
16958
16837
  var rtMore = liveList("backlog-realtime-more", function () {
16959
16838
  var st = backlogRealtimeState();
16960
16839
  return JSON.stringify([st ? [st.total, st.live] : null, backlogFactsSigOf()]);
@@ -16970,7 +16849,6 @@ ${story_1.splitByNameLinks.toString()}
16970
16849
  moreEl = el("details", { class: "ds-more" }, [
16971
16850
  el("summary", { text: "その他" }),
16972
16851
  routeWrap,
16973
- keyRows,
16974
16852
  rtMore,
16975
16853
  ]);
16976
16854
  box.appendChild(moreEl);
@@ -16984,7 +16862,6 @@ ${story_1.splitByNameLinks.toString()}
16984
16862
  moreEl.hidden = !(keyTargetCount("課題管理") > 0 || LIVE_BACKLOG_ROUTE === "fixed-ip");
16985
16863
  rtBox.__rebuild();
16986
16864
  if (routeWrap.__live) routeWrap.__live.__rebuild();
16987
- keyRows.__rebuild();
16988
16865
  rtMore.__rebuild();
16989
16866
  }
16990
16867
  }
@@ -17328,6 +17205,7 @@ ${story_1.splitByNameLinks.toString()}
17328
17205
  // サーバが削除を認めた印なので、ここから先に同じ鍵が現れたら**貼り直された別の登録**
17329
17206
  var figKeys = figs.map(function (f) { return f.key; });
17330
17207
  figsRemoved = figsRemoved.filter(function (k) { return figKeys.indexOf(k) >= 0; });
17208
+ FIGMA_UI.keys = figKeys.filter(function (k) { return figsRemoved.indexOf(k) < 0; });
17331
17209
  // **残り件数は「押した時点」で数える。** 一覧(figs)は夜間バッチのスナップショットで、
17332
17210
  // このページを開いている間はもちろん、その日のうちに再読み込みしても更新されない。
17333
17211
  // 描画時点の件数で判定すると、3件を1件ずつ外していったとき最後の1件でも警告が出ない。
@@ -19875,9 +19753,8 @@ ${story_1.splitByNameLinks.toString()}
19875
19753
  var stateSpace = (opts && opts.stateSpace) || "";
19876
19754
  var spaceCount = (opts && opts.spaceCount) || 0;
19877
19755
  var inRow = !!(opts && opts.inRow);
19878
- // 🔴 **Backlog の行の中のカードは新しい見え方(c-07 の 2 本目)。** 見出しを持たず(行の中なので「何の鍵か」は
19879
- // 一手の文が言う)、ふだんは隠れていて、キーが無いと分かったとき・[入れる]/[入れ直す]を押したとき・
19880
- // 触り始めた後だけ見える(syncKeyCardVisibility)。旧カード(行の外・intake 無しの案件)と Figma は従来どおり。
19756
+ // 🔴 **Backlog の行の中のカードは見出しを持たない**(行の中なので「何の鍵か」は札と欄が言う)。
19757
+ // 接続先が 1 件以上あるあいだは常に見える(0 件のときだけ syncKeyCardVisibility が畳む)。
19881
19758
  var compact = kind === "backlog" && inRow;
19882
19759
  /**
19883
19760
  * 鍵がどこまで効くのか(#229)。**置き場所だけでは伝わらないので、言葉にする。**
@@ -19892,11 +19769,9 @@ ${story_1.splitByNameLinks.toString()}
19892
19769
  // **いま登録されているか**を先に出す。入れ直したのに反映されていない状態が
19893
19770
  // 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
19894
19771
  // 保存の直後は前日の値のまま。時刻は上の節に出している)。
19895
- var st = secretStateOf(kind, stateSpace, spaceCount);
19896
19772
  var cardTitle = meta.title;
19897
- // 🔴 **「登録済み」は「読めている」を意味しない。** 鍵が入っていても、そのアカウントから
19898
- // プロジェクトが見えなければ同期は失敗する。**「接続OK」「同期OK」とは書かないこと**
19899
- //(同期できているかのバッジは、夜間処理が接続先ごとの成否を保存してからの別作業)。
19773
+ // 🔴 **「登録済み」は「読めている」を意味しない。** 「接続OK」を札に出してよいのは、そのスペース(Figma は全ファイル)の
19774
+ // 行の「届くか」がすべて ok のときだけ(statusOf)。測っていなければ「登録済み」止まり。
19900
19775
  //
19901
19776
  // **前方一致で見る。** #105 の移行期は「Secrets Manager(旧名)」も来る。
19902
19777
  // 完全一致にすると、旧名で動いている案件だけが repo secret と同じ見た目になり、
@@ -19907,27 +19782,140 @@ ${story_1.splitByNameLinks.toString()}
19907
19782
  // 同じ 1 語にし、**生の値は data-src に置く**(判定の is-managed は生の値のまま)。
19908
19783
  // 知らない値が来ても生で出さない——project-wording.test.mjs が画面の語を見張る
19909
19784
  var state = el("p", { class: "sec-state" });
19910
- applySecretState(state, st);
19911
-
19912
- var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
19785
+ // 🔴 **鍵の窓は 1 か所・欄は最初から見えている・ボタンは 1 つ(便 KU)。** 以前は[入れる]で開く畳み+[保存]+別の[接続を確認]の 3 段で、
19786
+ // 同じ鍵の[入れる]が行の一手・「その他」・畳みの 3 か所にあった。いまは 状態の札 → 伏せ字の欄(空のまま)→ [保存して接続を確認]。
19787
+ // 保存した値は読み戻さない(書き込み専用)。登録済みでも欄は空で、入れ直すときだけ貼る(伏せ字を欄に入れて伏せ字ごと保存する事故を避ける)
19788
+ var cap = kind === "backlog" ? "issues" : "design";
19789
+ var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off", spellcheck: "false" });
19913
19790
  var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
19914
19791
  var msg = el("div", { class: "ds-toggle-msg" });
19792
+ var savedNote = el("p", { class: "sec-note sec-saved" });
19793
+ // 札が読む材料の控え。checking=保存〜確認の最中、savedHere=この画面で保存した(正本 secretsNow は最大 10 秒古い答えを返すので、registered を返すまで「登録済み」以上に固定する)
19794
+ var ent = { kind: kind, el: null, compact: compact, space: stateSpace, input: input, msg: msg, checking: false, savedHere: false };
19795
+
19796
+ function spaceCountNow() { return kind === "backlog" ? backlogDomainsNow().length : spaceCount; }
19797
+ /** この鍵が効く接続先の ref(Backlog=そのスペースの行・Figma=一覧のファイル)。登録済みの一覧だけを引く=孤児の行を混ぜない */
19798
+ function refsNow() {
19799
+ var out = [];
19800
+ if (kind === "backlog") {
19801
+ var ds = backlogDomainsNow();
19802
+ var dom = stateSpace || (ds.length === 1 ? ds[0] : "");
19803
+ var rows = BACKLOG_UI.rows || [];
19804
+ for (var i = 0; i < rows.length; i++) {
19805
+ if (dom && String(rows[i].domain || "").toLowerCase() === dom) out.push(issuesRefOf(rows[i].domain, rows[i].projectKey));
19806
+ }
19807
+ } else {
19808
+ out = (FIGMA_UI.keys || []).slice();
19809
+ }
19810
+ return out;
19811
+ }
19812
+ /** 接続の確認の口があるか。組み直した案件(表)も同じ口(受付 op=probe の direct・同期を起こさずその場で 1 本叩く)で確かめる */
19813
+ function probeOk() { return !!SITE.intake && !!dispatchProbeTypeOf(cap); }
19814
+ /** 「M/D に保存」。受付の savedAt(Secrets Manager の LastChangedDate)から出す。ブラウザには覚えない(端末が変わると消えるため) */
19815
+ function savedDate(st) {
19816
+ var d = st && st.savedAt ? new Date(st.savedAt) : null;
19817
+ return d && !isNaN(d.getTime()) ? (d.getMonth() + 1) + "/" + d.getDate() : "";
19818
+ }
19819
+ function secretNow() {
19820
+ var s = secretStateOf(kind, stateSpace, spaceCountNow());
19821
+ if (ent.savedHere) {
19822
+ if (s.known === "yes") ent.savedHere = false; // 正本が追いついた
19823
+ else return { known: "yes", src: "" };
19824
+ }
19825
+ return s;
19826
+ }
19827
+ /**
19828
+ * 札 1 つの決め方。**未登録/確認中/接続OK〈時刻〉/失敗〈理由の一言〉/登録済み**。
19829
+ * 「失敗」の一言は行の札の語(dsGateForCap)をそのまま使う。測っていない行・鍵が無かった頃の古い行(no_token)は数えず、
19830
+ * 一部だけ測れているときも「接続OK」と言い切らない(登録済みに倒す)。
19831
+ */
19832
+ function statusOf(st) {
19833
+ if (ent.checking) return ["is-checking", "確認中"];
19834
+ if (st.known === "no") return ["is-none", "未登録"];
19835
+ if (st.known !== "yes") return ["is-unknown", "確認中"];
19836
+ var refs = refsNow(), bad = null, ok = 0, latest = "";
19837
+ for (var i = 0; i < refs.length; i++) {
19838
+ var r = reachableStateOf(cap, refs[i]);
19839
+ if (!r || r.status === "no_token") continue;
19840
+ if (r.status === "ok") { ok++; if (String(r.at || "") > latest) latest = String(r.at || ""); }
19841
+ else if (!bad) bad = r;
19842
+ }
19843
+ if (bad) return ["is-failed", "失敗(" + dsGateForCap(cap, bad.status).text + ")"];
19844
+ if (ok && ok === refs.length) {
19845
+ var t = dsFmtTime(latest);
19846
+ return ["is-ok", "接続OK" + (t ? "(" + t + ")" : "")];
19847
+ }
19848
+ return null; // 登録済み
19849
+ }
19850
+ function paint() {
19851
+ var st = secretNow();
19852
+ var s = statusOf(st);
19853
+ if (s) {
19854
+ // 取得元は札の語に関わらず data-src に置く(判定の is-managed は生の値の前方一致・#841)
19855
+ var srcRaw = st.known === "yes" ? (st.src || "") : "";
19856
+ state.className = "sec-state " + s[0] + (srcRaw.indexOf("Secrets Manager") === 0 ? " is-managed" : "");
19857
+ if (srcRaw) state.setAttribute("data-src", srcRaw); else state.removeAttribute("data-src");
19858
+ state.textContent = s[1];
19859
+ } else {
19860
+ applySecretState(state, st);
19861
+ }
19862
+ var reg = st.known === "yes";
19863
+ var d = savedDate(st);
19864
+ savedNote.hidden = !reg;
19865
+ savedNote.textContent = reg ? "登録済み" + (d ? "(" + d + " に保存)" : "") + "。入れ直すときだけ貼ってください" : "";
19866
+ if (!btn.disabled) btn.textContent = probeOk() ? "保存して接続を確認" : "保存";
19867
+ if (preferred) {
19868
+ var want = st.known === "no";
19869
+ if (want && !preferred.parentNode && card) card.insertBefore(preferred, input);
19870
+ else if (!want && preferred.parentNode) preferred.parentNode.removeChild(preferred);
19871
+ }
19872
+ }
19873
+ /** 保存のあとの接続の確認。結果は行と同じ形で置く(applyProbeResult)。返すのは添える 1 文(無ければ空) */
19874
+ async function checkAfterSave() {
19875
+ var type = dispatchProbeTypeOf(cap);
19876
+ // Figma は回数制限(1 分あたりの枠)に収まるよう先頭の 2 ファイルだけ。残りは各行の[接続を確認]
19877
+ var refs = refsNow().slice(0, kind === "figma" ? 2 : 20);
19878
+ for (var i = 0; i < refs.length; i++) {
19879
+ // 無応答で札が「確認中」のまま固まらないよう、1 本ごとに 15 秒で見切る
19880
+ var ctrl = typeof AbortController === "function" ? new AbortController() : null;
19881
+ var giveUp = ctrl ? setTimeout(function () { ctrl.abort(); }, 15000) : null;
19882
+ try {
19883
+ var res = await callIntake("probe", { type: type, ref: refs[i] }, ctrl ? { signal: ctrl.signal } : undefined);
19884
+ var j = await res.json().catch(function () { return {}; });
19885
+ if (giveUp != null) clearTimeout(giveUp);
19886
+ if (res.ok && j.gate) {
19887
+ applyProbeResult(cap, refs[i], "", { gate: j.gate, checkedAt: j.checkedAt, needed: "", detail: j.detail || "" });
19888
+ } else if (!res.ok) {
19889
+ return "接続の確認はできませんでした:" + ((j && j.message) || "少し待ってからもう一度お試しください。");
19890
+ } else {
19891
+ return "接続の確認は少し後に札へ出ます。";
19892
+ }
19893
+ } catch (e) {
19894
+ if (giveUp != null) clearTimeout(giveUp);
19895
+ return "接続の確認は通信に失敗しました。";
19896
+ }
19897
+ }
19898
+ return "";
19899
+ }
19915
19900
 
19916
19901
  btn.addEventListener("click", async function () {
19917
19902
  var value = input.value;
19918
- if (!value.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ トークンを入力してください。"; return; }
19903
+ if (!value.trim()) {
19904
+ msg.className = "ds-toggle-msg";
19905
+ // 登録済みで空のまま押しても何も変えない(置き換えるのは、貼ったときだけ)
19906
+ msg.textContent = secretNow().known === "yes" ? "何も変えていません。入れ直すときだけ値を貼ってください。" : "";
19907
+ if (secretNow().known !== "yes") { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ トークンを入力してください。"; }
19908
+ return;
19909
+ }
19919
19910
 
19920
- // 押している間はボタンごと「処理中…」にする(#222・beginBusy の項)
19911
+ // 保存〜確認の全体を 1 回の busy で囲む(#222・beginBusy の項)
19921
19912
  var endBusy = beginBusy(btn);
19922
19913
  msg.className = "ds-toggle-msg";
19923
19914
  msg.textContent = "保存中…";
19924
19915
  var intake = SITE.intake;
19925
19916
  try {
19926
19917
  // **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
19927
- // **Backlog はスペースだけ名乗る(#138)。** 名前を組み立てるのはサーバで、
19928
- // こちらが渡すのは「どのスペースの鍵か」という材料だけ。
19929
- // **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
19930
- // 貼った鍵が「スペースを特定できません」で断られる
19918
+ // **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に貼った鍵が「スペースを特定できません」で断られる
19931
19919
  var res = await callIntake("secret", secretPayload(intake, kind, value, cardSpace));
19932
19920
  var j = await res.json().catch(function () { return {}; });
19933
19921
  if (!res.ok) {
@@ -19937,30 +19925,38 @@ ${story_1.splitByNameLinks.toString()}
19937
19925
  return;
19938
19926
  }
19939
19927
  msg.className = "ds-toggle-msg is-ok";
19940
- // 🔴 **「保存中…」を消してから成功を書く(#220)。** ここが抜けていて、実機の
19941
- // 画面に「保存中…✓ 保存しました。同期を開始しました。」と**2つの状態が同居**して
19942
- // いた。appendChild は足すだけなので、消す一文が要る(sendConfig 側には在った)。
19943
- // **語を書く前に必ずここを通すこと**——次に文面を足す人が同じ形で踏む
19928
+ // 🔴 **「保存中…」を消してから成功を書く(#220)。** appendChild は足すだけなので、消す一文が要る
19944
19929
  msg.textContent = "";
19945
19930
  msg.appendChild(document.createTextNode("✓ " + (j.message || "保存しました。")));
19946
- // 起動できたときだけ、実行状況への導線を出す(鍵を入れた直後に効いたか確かめられる)
19947
19931
  if (j.runsUrl) {
19948
19932
  msg.appendChild(document.createTextNode(" "));
19949
19933
  msg.appendChild(el("a", { href: j.runUrl || j.runsUrl, target: "_blank", rel: "noopener", title: "GitHub Actions の実行ログを開きます(プロジェクトリポジトリへのアクセスが要ります)", text: "実行状況を見る →" })); // その run を特定できていればそれ、無ければ一覧
19950
19934
  }
19951
19935
  // 入れた値は画面に残さない(肩越しに見えるのを防ぐ)
19952
19936
  input.value = "";
19953
- endBusy();
19954
- // 🔴 **Backlog の行の中のカードは、保存したら行の一手を当て直す**(「API キーを入れてください」を
19955
- // 登録状況の正本に合わせて消す・c-07 の 2 本目)。正本(secretsNow)は取り直しで届く
19956
- if (compact) {
19957
- if (typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
19958
- loadLiveFleet(true);
19937
+ ent.savedHere = true;
19938
+ var willCheck = probeOk() && refsNow().length > 0;
19939
+ ent.checking = willCheck;
19940
+ paint();
19941
+ // 🔴 Backlog の行の一手は登録状況の正本に合わせて当て直す。正本(secretsNow)は取り直しで届く
19942
+ if (compact && typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
19943
+ if (willCheck) {
19944
+ var note = await checkAfterSave();
19945
+ if (note) msg.appendChild(document.createTextNode(" " + note));
19959
19946
  }
19947
+ ent.checking = false;
19948
+ endBusy();
19949
+ paint();
19950
+ loadLiveFleet(true);
19960
19951
  } catch (e) {
19961
- msg.className = "ds-toggle-msg is-error";
19962
- msg.textContent = "✗ 通信に失敗しました。";
19952
+ ent.checking = false;
19953
+ // 保存が済んだあとの不調を「通信に失敗」と言わない(貼り直しに誘導しない)
19954
+ if (!ent.savedHere) {
19955
+ msg.className = "ds-toggle-msg is-error";
19956
+ msg.textContent = "✗ 通信に失敗しました。";
19957
+ }
19963
19958
  endBusy();
19959
+ paint();
19964
19960
  }
19965
19961
  });
19966
19962
 
@@ -19972,21 +19968,17 @@ ${story_1.splitByNameLinks.toString()}
19972
19968
  meta.note ? el("p", { class: "sec-how", text: meta.note }) : null,
19973
19969
  meta.selfServe ? el("p", { class: "sec-how", text: meta.selfServe.note }) : null,
19974
19970
  ].filter(Boolean);
19975
- // **「畳んである中に何があるか」を summary に書く。** ただの「詳細」だと、
19976
- // 発行のしかたを探している人がここを開く手掛かりを持てない(rt カードと同じ流儀)
19971
+ // **「畳んである中に何があるか」を summary に書く。**
19977
19972
  var guidanceBox = el("details", { class: "sec-fold" }, [el("summary", { text: "発行のしかた" })]);
19978
19973
  for (var gi = 0; gi < guidance.length; gi++) guidanceBox.appendChild(guidance[gi]);
19979
- // **手順の動画+画像(#547・#1353)。** 文章(guidance)の下に足す。
19980
- // meta.guide を持つ種別(Figma)はカルーセル1つに乗せる。meta.guide も meta.shots も
19981
- // 持たない種別(Backlog)ではこの節は素通りする
19974
+ // **手順の動画+画像(#547・#1353)。** meta.guide を持つ種別(Figma)はカルーセル1つに乗せる
19982
19975
  if (meta.guide && meta.guideInModal) {
19983
19976
  // モーダルの上に出している種別(Backlog・Figma)は、ここには重ねない
19984
19977
  } else if (meta.guide) {
19985
19978
  var carousel = buildGuideCarousel(kind, meta.guide);
19986
19979
  if (carousel) guidanceBox.appendChild(carousel);
19987
19980
  } else if (meta.shots) {
19988
- // 後方互換(旧形)。今のところ参照するエントリは無い——将来 Google Chat/Meet を
19989
- // この口に載せ替えるまでの踏み板として残す
19981
+ // 後方互換(旧形)
19990
19982
  for (var si = 0; si < meta.shots.length; si++) {
19991
19983
  var shot = meta.shots[si];
19992
19984
  guidanceBox.appendChild(el("p", { class: "sec-how", text: shot.heading }));
@@ -19994,86 +19986,33 @@ ${story_1.splitByNameLinks.toString()}
19994
19986
  }
19995
19987
  }
19996
19988
 
19997
- // **押すまで入力欄を出さない(2026-08-21・オーナー指摘)。**
19998
- // 「今のAPIキーのセクション、欄があると思うんだけど…押すとフォームだけ出てくる」。
19999
- //
20000
- // 🔴 **前回とは向きが逆。** 前回は「.sec-input は畳まない」と決めていたが、
20001
- // 鍵の欄がレコードの行の下へ移り、**行に常時ぶら下がる入力欄**になったため、
20002
- // 一覧の見通しを取り戻す側へ倒した。**状態(.sec-state)は畳まない**ので、
20003
- // 「保存したのに反映されていない」は開かなくても読める。
20004
- //
20005
- // ⚠ **DOM には常に置く**(押すまで生成しない作りにしない)。ライブ差し替えは
20006
- // input のノードを直接触るし、テストも card.querySelector("input.sec-input") で掴む。
20007
- // ⚠ **<summary> は button ではない**ので、「保存」を掴む既存の取り方は変わらない。
20008
- var inputFold = el("details", { class: "sec-input-fold" }, [
20009
- // Backlog の行の中のカードは、一手の語([入れる]・[入れ直す])と揃える。Figma は「登録する」のまま
20010
- el("summary", { class: "sec-input-summary", text: st.known === "yes" ? "入れ直す" : (compact ? "入れる" : "登録する") }),
20011
- ]);
20012
- inputFold.appendChild(input);
20013
- inputFold.appendChild(el("div", { class: "ds-toggle" }, [btn, msg]));
20014
- // 🔴 **値の扱いの 1 文は、開いた中に残す**(顧客に鍵の貼り付けを求める画面でいちばん落としてはいけない説明・#101)。
20015
- // 短くした版(行の中のカード)。「他のプロジェクトには影響しません」は仕組みの事実で、利用者が選ぶものではないので外す
20016
- if (compact) inputFold.appendChild(el("p", { class: "sec-note", text: "入れた値は保存だけでき、あとから表示されません。" }));
20017
-
20018
- // ライブ取得後に状態の行だけを差し替えるための目印(カードごと作り直すと入力が消える)
20019
- //
20020
- // **並びは「状態 → 貼る(畳む) → 畳んだ手順」。**
20021
- //
20022
- // 🔴 **.sec-state / .sec-preferred は <details> の外に置くこと。**
20023
- // - .sec-state … 保存したのに反映されていない状態が分かる唯一の手段。
20024
- // 押すまで見えないと成立しない
20025
- // - .sec-preferred … 「誰のアカウントで発行するのか」を**手順より先に**伝える行。
20026
- // 未登録のときだけ出る=これから貼る人にだけ出るので、畳むと存在理由が消える
19989
+ // 🔴 **.sec-state / .sec-preferred は常に見える場所に置く。** 札は「保存したのに反映されていない」が分かる唯一の手段。
19990
+ // .sec-preferred は「誰のアカウントで発行するのか」を手順より先に伝える行(未登録のときだけ出る)
20027
19991
  var preferred = meta.selfServe ? el("p", { class: "sec-preferred", text: meta.selfServe.text }) : null;
20028
19992
  var card = el("div", { class: "ops-card" + (inRow ? " ds-keycard" : ""), "data-secret-kind": kind }, [
20029
- // 行の中の Backlog カードは見出しを持たない(何の鍵かは一手の文が言う)
19993
+ // 行の中の Backlog カードは見出しを持たない(何の鍵かは札と欄が言う)
20030
19994
  compact ? null : el("div", { class: "ops-card-head" }, [
20031
19995
  el("span", { class: "ops-card-icon", text: meta.icon }),
20032
19996
  el("h3", { class: "ops-card-title", text: cardTitle }),
20033
19997
  ]),
20034
- // **desc は無い種別がある(2026-08-21)。** el() は text をそのまま textContent へ
20035
- // 代入するので、無条件に出すと画面に文字列 undefined が出る
20036
19998
  meta.desc ? el("p", { class: "ops-card-desc", text: meta.desc }) : null,
20037
- // **鍵の効く範囲は、登録状況より先に言う。** 「登録済み」と読んだ後では
20038
- // 「どのファイルの話か」を確かめ直すことになる(#229)
19999
+ // **鍵の効く範囲は、登録状況より先に言う。** (#229)
20039
20000
  scopeNote ? el("p", { class: "sec-scope", text: scopeNote }) : null,
20040
20001
  state,
20041
- // **未登録のときだけ、先に試すべき手を出す。** 既に動いているカードに
20042
- // 「まずこれをやれ」と出すと、直す必要が無いものを触らせることになる。
20043
- // **「確認中」にも出さない**——入っているかもしれないものを触らせない
20044
- st.known === "no" ? preferred : null,
20045
- inputFold,
20002
+ input,
20003
+ el("div", { class: "ds-toggle" }, [btn, msg]),
20004
+ savedNote,
20005
+ // 🔴 **値の扱いの 1 文は常に出す**(顧客に鍵の貼り付けを求める画面でいちばん落としてはいけない説明・#101)
20006
+ el("p", { class: "sec-note", text: compact
20007
+ ? "入れた値は保存だけでき、あとから表示されません。"
20008
+ : "入れた値はここには表示されません(保存だけできます)。保存先はプロジェクトごとに分かれているので、他のプロジェクトには影響しません。" }),
20046
20009
  guidanceBox,
20047
- // **扱いの説明はカードに持たせる。** 節の見出しの下に置いていたときは、
20048
- // #101 でカードだけがモーダルへ移り、説明だけが画面から消えた。
20049
- // 顧客にトークンの貼り付けを求める画面で、貼った値がどう扱われるかの
20050
- // 説明が落ちるのは、いちばん落としてはいけない種類の欠落
20051
- // 行の中の Backlog カードは、同じ 1 文を開いた中(上の inputFold)に短くして持つ
20052
- compact ? null : el("p", { class: "sec-note", text: "入れた値はここには表示されません(保存だけできます)。保存先はプロジェクトごとに分かれているので、他のプロジェクトには影響しません。" }),
20053
20010
  ]);
20054
- // **作った鍵カードは1枚残らずここに載せる。** 出し入れ(接続先が0件のときに畳む)は
20055
- // DOM 検索ではなくこの控えで回す——カードは能力モーダルの中身で、タイルを押すまで
20056
- // 文書に入っていないため(syncKeyCardVisibility の項)。
20057
- // compact(Backlog の行の中)のカードは、見え方の規則(backlogKeyCardWanted)が要る材料を一緒に持つ:
20058
- // space=状態を引くスペース(行のドメイン。名無しの受け皿は空文字)・forced=一手・「その他」から開かれた・
20059
- // fold/input/msg=触り始めたかの判定と、入力への移動・refresh=見せる瞬間の状態の当て直し
20060
- var entry = { kind: kind, el: card, compact: compact, space: stateSpace, forced: false, fold: inputFold, input: input, msg: msg };
20061
- if (compact) {
20062
- entry.refresh = function () {
20063
- // 作ったときの答えは古いことがある(取り直しが後から届く)。見せる瞬間に状態の行と summary と
20064
- // 「自分で繋げばよい」の 1 行を当て直す(入れた直後に前の答えのまま出さない・#227)
20065
- var now = secretStateOf(kind, stateSpace, backlogDomainsNow().length);
20066
- applySecretState(state, now);
20067
- var sum = inputFold.querySelector(".sec-input-summary");
20068
- if (sum) sum.textContent = now.known === "yes" ? "入れ直す" : "入れる";
20069
- if (preferred) {
20070
- var want = now.known === "no";
20071
- if (want && !preferred.parentNode) card.insertBefore(preferred, inputFold);
20072
- else if (!want && preferred.parentNode) preferred.parentNode.removeChild(preferred);
20073
- }
20074
- };
20075
- }
20076
- KEY_CARDS.push(entry);
20011
+ ent.el = card;
20012
+ ent.refresh = paint;
20013
+ paint();
20014
+ // **作った鍵カードは1枚残らずここに載せる。** 出し入れ(接続先が0件のときに畳む)は DOM 検索ではなくこの控えで回す
20015
+ KEY_CARDS.push(ent);
20077
20016
  return card;
20078
20017
  }
20079
20018
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.214",
3
+ "version": "0.12.215",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,