@takagaki/cortex-decisions-viewer 0.4.70 → 0.4.72

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
@@ -292,10 +292,28 @@ async function readFleetSources(repoRoot) {
292
292
  capabilities = out;
293
293
  }
294
294
  const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
295
+ // いま同期している Backlog の接続先(fleet-status.json トップレベル・#111)。
296
+ //
297
+ // **画面に現在値を出すために要る。** 「接続先を変更」フォームが入力欄だけだったため、
298
+ // 実機で「何もセットされていない」と読まれ、同じURLが貼り直された(#127)。
299
+ //
300
+ // **domain / projectKey しか通さない。** 生成側は configPath / sections / strays /
301
+ // features も持っているが、画面が使うのは接続先の2つだけ。素通しにすると、engine が
302
+ // このオブジェクトに足したものが顧客の画面データへ黙って流れる(Lambda 側の
303
+ // FLEET_PUBLIC_KEYS と同じ考え方で、こちらも許可リストにする)。
304
+ //
305
+ // **値が無い形は落とす。** 正本もミラーも持たない案件では `{ source: "secrets" }` だけが
306
+ // 来る。空の器を通すと、画面は「接続先の情報がある」と判断して空文字を並べることになる
307
+ const bt = obj.backlogTarget != null && typeof obj.backlogTarget === "object" && !Array.isArray(obj.backlogTarget)
308
+ ? obj.backlogTarget
309
+ : null;
310
+ const backlogTarget = bt && typeof bt.domain === "string" && bt.domain && typeof bt.projectKey === "string" && bt.projectKey
311
+ ? { domain: bt.domain, projectKey: bt.projectKey }
312
+ : undefined;
295
313
  const secretSources = obj.secretSources && typeof obj.secretSources === "object"
296
314
  ? obj.secretSources
297
315
  : undefined;
298
- return { externalSources, internalSources, pipelines, tools, capabilities, fleetGeneratedAt, secretSources, checks, githubOwners };
316
+ return { externalSources, internalSources, pipelines, tools, capabilities, fleetGeneratedAt, secretSources, checks, githubOwners, backlogTarget };
299
317
  }
300
318
  /** パスがディレクトリとして存在するか */
301
319
  async function isDirectory(dirAbs) {
@@ -1179,6 +1197,7 @@ async function build(opts) {
1179
1197
  capabilities: fleet.capabilities,
1180
1198
  checks: fleet.checks,
1181
1199
  githubOwners: fleet.githubOwners,
1200
+ backlogTarget: fleet.backlogTarget,
1182
1201
  };
1183
1202
  // サムネイル(デザイン画面等)をサイトに同梱し、ノードのパスをサイト内相対に書き換える
1184
1203
  await node_fs_1.promises.mkdir(outAbs, { recursive: true });
package/dist/render.js CHANGED
@@ -107,18 +107,19 @@ const ADAPTER_CATALOG = {
107
107
  "開発": ["github"],
108
108
  };
109
109
  /**
110
- * 導出された状態(5値)→ 画面に出す文言。**マップとタイルで同じ語を使う。**
110
+ * 導出された状態 画面に出す文言。**マップとタイルで同じ語を使う。**
111
111
  *
112
112
  * - `off` は「OFFに設定されています」。**接続へ誘わない**——意図的に止めたものに
113
113
  * 「接続しましょう」と出すのは、設定する必要が無いものを押させるのと同じ誤り。
114
- * - `unset` は「未接続」。こちらは入口を出してよい(まだ何も決めていない状態)。
115
- * - `empty` を `unset` に潰さない。設定ファイルはあるので「置き直す」ではなく「中身を足す」。
114
+ * - `unset` は「未接続」。こちらは入口を出してよい。
115
+ *
116
+ * **`empty` の行は無い。** 表示に入る前に `capabilityDisplayState` が unset へ畳むので、
117
+ * ここへ来ることはない(オーナー決定・2026-08-12。理由はその関数の項に書いてある)。
116
118
  */
117
119
  const CAP_STATE_TEXT = {
118
120
  unset: "未接続",
119
121
  off: "OFFに設定されています",
120
122
  broken: "設定に異常があります",
121
- empty: "設定はありますが、対象がありません",
122
123
  on: "接続OK",
123
124
  };
124
125
  /** 状態の印。CAP_STATE_TEXT の前に付ける(マップは付けない——⚠️バッジと二重になる) */
@@ -243,6 +244,27 @@ function capabilityRawState(cap, caps) {
243
244
  var s = c.state == null ? "" : String(c.state);
244
245
  return s ? s : null;
245
246
  }
247
+ /**
248
+ * **画面が使う状態。`empty` を `unset` に畳む。**(オーナー決定・2026-08-12)
249
+ *
250
+ * scaffold が設定ファイルを必ず作るので、「設定ファイルはある」は利用者にとって無情報。
251
+ * ダミーキーのまま置かれている=繋がっていない、が実態で、`unset` と `empty` の違いは
252
+ * **実装の都合**でしかない。利用者にとっての答えはどちらも「未接続」。
253
+ * 実機の受け入れテストで「設定はありますが、対象がありません」が何を指すのか伝わらず、
254
+ * 次の一手(=接続する)に繋がらなかった。
255
+ *
256
+ * **engine の状態機械は変えない。** `empty` は今後も導出されてくる(プレースホルダを
257
+ * 実キーと数えない判定は engine の責務で、そこを潰すと無駄な同期が走る #125①)。
258
+ * 畳むのは表示だけ——**この1関数だけ**が写像を持つので、「やっぱり区別したい」に
259
+ * なったらここの1行を消せば5値の表示へ戻る。
260
+ *
261
+ * 畳んだ先が unset なので、文言・接続の入口の案内・マップの線の有無・グレーの見た目は
262
+ * すべて unset と同一になる。`off` / `broken` の区別は従来どおり残す。
263
+ */
264
+ function capabilityDisplayState(cap, caps) {
265
+ var s = capabilityRawState(cap, caps);
266
+ return s === "empty" ? "unset" : s;
267
+ }
246
268
  /**
247
269
  * 貼られたURLから Backlog の接続先を読む。
248
270
  *
@@ -317,7 +339,11 @@ function capabilityState(cap, ctx) {
317
339
  : "ok";
318
340
  }
319
341
  // unset / empty / 未知の値。**未知を ok にも off にも寄せない**——どちらも嘘になる。
320
- // 「まだ繋がっていない」側に倒すのが安全(繋がっていないのに線を引かない)
342
+ // 「まだ繋がっていない」側に倒すのが安全(繋がっていないのに線を引かない)。
343
+ //
344
+ // ここは**生の状態**(capabilityRawState)で見ている。empty と unset は元々どちらも
345
+ // idle に落ちるので、表示側の写像(capabilityDisplayState が empty を unset に畳む)と
346
+ // 答えが割れることはない。**この2つを分けるなら、写像とここの両方を直すこと。**
321
347
  return "idle";
322
348
  }
323
349
  // ---- ここから下は capabilities を持たない古い fleet-status.json のための劣化表示 ----
@@ -406,7 +432,7 @@ function renderConnectionMap(site) {
406
432
  const idle = st === "idle";
407
433
  const warn = st === "warn";
408
434
  const state = "cm-" + st;
409
- const derived = capabilityRawState(c.cap, caps);
435
+ const derived = capabilityDisplayState(c.cap, caps);
410
436
  // **ラベルは能力名(会議・課題管理…)。ツール名は下に小さく置く。**
411
437
  // ツールは「その能力をいま何で実現しているか」という差し替え可能なアダプタで、
412
438
  // 主語ではない。ツール名を主にすると、Teams のように複数の能力に使われるものが
@@ -1157,8 +1183,24 @@ a.ds-name:hover { text-decoration: underline; }
1157
1183
  .ds-check-detail { font-size: 12px; color: var(--muted); }
1158
1184
  .ds-tile-desc { font-size: 12.5px; color: var(--muted); }
1159
1185
  .ds-tile-sub { font-size: 12.5px; margin-top: 2px; }
1160
- .ds-tile-chev { color: var(--muted); }
1186
+ .ds-tile-chev { color: var(--muted); flex: 0 0 auto; }
1161
1187
  .ds-tile-note { font-size: 12.5px; color: #a15c00; margin-top: 2px; }
1188
+ /* ---- 可変長の文字列は必ず折り返す(0.4.71) ----
1189
+ 実機の受け入れテストで、状態の文言がタイル幅を超えて溢れた(スシローのデザインタイル)。
1190
+ ここに出るのは engine の detail・チェックの直し方・チャンネル名・ファイルパスなど
1191
+ **長さが読めない**文字列で、増えるたびに個別に手当てしていたら必ず取りこぼす。
1192
+ **省略(ellipsis)ではなく折り返しで受ける**——何が足りないのかを伝える文なので、
1193
+ 途中で切ると画面の目的そのものが消える(名前だけの .ds-name は従来どおり省略のまま)。
1194
+ white-space: normal … button の中に置く行があるので UA 既定に委ねない
1195
+ overflow-wrap: anywhere … 空白の無い連なり(課題管理/issues-config.json・ID・URL)を切る
1196
+ min-width: 0 … flex の子が min-content より縮めるようにする(無いと折り返しても溢れる) */
1197
+ .ds-tile-name, .ds-tile-desc, .ds-tile-sub, .ds-tile-note,
1198
+ .ds-check-label, .ds-check-action, .ds-check-detail,
1199
+ .ds-pipe-label, .ds-pipe-todo, .ds-owner-state,
1200
+ .ds-empty, .sec-how, .modal-desc, .ds-toggle-msg,
1201
+ .ds-drive-note, .ds-inactive-note, .ds-legacy-chip, .ds-keys-code {
1202
+ min-width: 0; white-space: normal; overflow-wrap: anywhere;
1203
+ }
1162
1204
  .badge.ds-undeclared { background: #fff4e5; color: #9a6700; }
1163
1205
  .ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
1164
1206
  .ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
@@ -1216,6 +1258,11 @@ a.ds-name:hover { text-decoration: underline; }
1216
1258
  .ds-addform .sec-input { margin-bottom: 6px; }
1217
1259
  .sec-input-inline { width: auto; max-width: 220px; margin-bottom: 0; }
1218
1260
  .sec-how { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 0 0 10px; }
1261
+ /* いまの接続先(設定を変える口の直前に置く。値は本文色で、ラベルだけ弱める) */
1262
+ .ds-current { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin: 0 0 8px; font-size: 12.5px; }
1263
+ .ds-current-label { color: var(--muted); flex: 0 0 auto; }
1264
+ .ds-current-value { color: var(--text); font-weight: 600; word-break: break-all; }
1265
+ .ds-current-value.is-unset { color: var(--muted); font-weight: 400; }
1219
1266
  /* 入力欄のすぐ下。貼る直前に読める位置に置く */
1220
1267
  .sec-note { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 6px 0 0; }
1221
1268
  .sec-how p { margin: 0; }
@@ -1366,6 +1413,7 @@ var CAP_STATE_MARK = ${JSON.stringify(CAP_STATE_MARK)};
1366
1413
  ${externalCapability.toString()}
1367
1414
  ${capabilityAdapter.toString()}
1368
1415
  ${capabilityRawState.toString()}
1416
+ ${capabilityDisplayState.toString()}
1369
1417
  ${backlogTargetOf.toString()}
1370
1418
  ${sourceNeedsAttention.toString()}
1371
1419
  ${capabilityState.toString()}
@@ -2347,13 +2395,20 @@ ${capabilityState.toString()}
2347
2395
  }));
2348
2396
  return;
2349
2397
  }
2350
- // **未接続(unset)の能力には入力欄を出さない(#125②)。** 鍵は「繋いだものを読むため」の
2351
- // ものなので、繋ぐ先が無い状態で入れても効かない。何も出さないと「鍵はどこで入れるのか」が
2352
- // 分からなくなるので、順序だけを1行で伝える
2353
- if (capabilityRawState(SECRET_CAP[k], caps) === "unset") {
2398
+ // **意図的にOFFにした能力にだけ、入力欄を出さない(0.4.71)。** 使わないと決めたものに
2399
+ // 鍵を求めるのは、設定する必要が無いものに設定を促す偽の警告と同じ。
2400
+ // 何も出さないと「トークンはどこで入れるのか」が分からなくなるので、理由を1行だけ残す
2401
+ // (上の GitHub の畳み方と同じ流儀)。
2402
+ //
2403
+ // **未接続(unset・畳んだ empty)には出す。** 0.4.70 では「繋ぐ先が無い状態で鍵を
2404
+ // 入れても効かない」として畳んでいたが、engine の checks は「この画面の入力欄に
2405
+ // 貼ってください」と案内しており、**入力欄が無いのは案内の嘘**だった(cortex-context の
2406
+ // デザインで実際にそうなっていた)。「未接続=これから繋げる入口」なら、その入口に
2407
+ // 最初の一歩を置けるほうが整合する。broken も出す——鍵を貼り直して直る場合がある
2408
+ if (capabilityDisplayState(SECRET_CAP[k], caps) === "off") {
2354
2409
  pushCap(SECRET_CAP[k], el("p", {
2355
- class: "sec-how",
2356
- text: "この能力はまだ接続されていません。接続すると、ここに鍵の入力欄が出ます。",
2410
+ class: "sec-how ds-all-connected",
2411
+ text: "この能力はOFFに設定されています。使う場合は設定ファイルで有効にしてください(鍵の登録はそのあとで構いません)。",
2357
2412
  }));
2358
2413
  return;
2359
2414
  }
@@ -2403,7 +2458,7 @@ ${capabilityState.toString()}
2403
2458
  meeting_minutes: "会議",
2404
2459
  channels_json: "チャット",
2405
2460
  figma_token: "デザイン", figma_inventory: "デザイン",
2406
- submodules: "開発", dev_issues_derived: "開発",
2461
+ dev_sources_registered: "開発", dev_issues_derived: "開発",
2407
2462
  };
2408
2463
  var allChecks = (fleetVal("checks") || []).filter(function (c) {
2409
2464
  return c != null && c.status === "missing";
@@ -2525,7 +2580,7 @@ ${capabilityState.toString()}
2525
2580
  // その状態を割り当て、残りは未接続——「知らない」を「繋がっている」に寄せない
2526
2581
  var active = capabilityAdapter(cap, caps);
2527
2582
  adapters.forEach(function (a, ai) {
2528
- renderAdapterTile(cap, items, a, a === active ? capabilityRawState(cap, caps) : "unset", ai);
2583
+ renderAdapterTile(cap, items, a, a === active ? capabilityDisplayState(cap, caps) : "unset", ai);
2529
2584
  });
2530
2585
  } else if (items.length || declared) {
2531
2586
  // 旧データ: アダプタのタイルは宣言か行があるときだけ。手動投入だけが理由でここへ来た
@@ -2545,7 +2600,7 @@ ${capabilityState.toString()}
2545
2600
  */
2546
2601
  function renderAdapterTile(cap, items, adapter, derived, ai) {
2547
2602
  var meta = CAP_META[cap] || { icon: "🔗", desc: "" };
2548
- var st = derived === "unset" && capabilityRawState(cap, caps) !== "unset"
2603
+ var st = derived === "unset" && capabilityDisplayState(cap, caps) !== "unset"
2549
2604
  // カタログにあるが導出が答えていないアダプタ(今日は発生しない。#107 の前方互換)
2550
2605
  ? "idle"
2551
2606
  : capabilityState(cap, { tools: tools, capabilities: caps, internals: internals, externals: sources, pipes: pipes });
@@ -2564,10 +2619,16 @@ ${capabilityState.toString()}
2564
2619
  el("div", { class: "ds-tile-sub ds-gate-" + st, text: tileSub(cap, st, derived, adapter) }),
2565
2620
  ]);
2566
2621
  // **なぜその状態なのかを添える(#125②)。** engine が書いた一文(detail)で、
2567
- // 「未接続」だけでは分からない「何が無いのか」を出す。on のときは
2568
- // 読み取り範囲(件数)が既に同じ行にあるので添えない
2622
+ // 「何が無いのか」を出す。on のときは読み取り範囲(件数)が既に同じ行にある。
2623
+ //
2624
+ // **未接続(unset・畳んだ empty)はタイル表面に出さない(0.4.71)。** 未接続の答えは
2625
+ // 「まだ繋がっていない」の一言で足り、files が空です/figma.json がありません、の
2626
+ // 区別は押した先で読めばよい実装の話。表面に置くと一覧が理由文で埋まるうえ、
2627
+ // 実機で幅を超えて溢れた。off(何件残して止めたか)と broken(異常)は
2628
+ // 押さずに気づけないと困るので、表面に残す
2569
2629
  var capInfo = caps[cap];
2570
- if (derived && derived !== "on" && capInfo && capInfo.detail && adapter === capabilityAdapter(cap, caps)) {
2630
+ var detailOnTile = derived === "off" || derived === "broken";
2631
+ if (detailOnTile && capInfo && capInfo.detail && adapter === capabilityAdapter(cap, caps)) {
2571
2632
  body.appendChild(el("div", { class: "ds-tile-note ds-tile-todo", text: capInfo.detail }));
2572
2633
  }
2573
2634
  // トークン未設定は**状態とは別軸**。状態機械に混ぜると判定が1箇所でなくなる。
@@ -2583,10 +2644,13 @@ ${capabilityState.toString()}
2583
2644
  // 案件に未設定の警告を出すと、直す必要が無いものを直させることになる。
2584
2645
  // 読めていない組織があるときは、この下の checks とタイルの状態(warn)が拾う
2585
2646
  var tokenIsFallback = sk === "external-sources" && !githubTokenNeeded();
2586
- // **#125②: 導出があるときは導出で判断する。** OFF(意図的に止めた)と unset(未接続で、
2587
- // 鍵の入力欄そのものを案内文に畳んである)には出さない——入れる先が無い、あるいは
2588
- // 入れる必要が無いものについて設定を促す偽の警告になる
2589
- var tokenRelevant = derived != null ? derived !== "off" && derived !== "unset" : tools[cap] !== "none";
2647
+ // **#125②: 導出があるときは導出で判断する。** OFF(意図的に止めた)にだけ出さない
2648
+ // ——入れる必要が無いものについて設定を促す偽の警告になる。
2649
+ //
2650
+ // **注記の条件は、入力欄を出す条件と同じにする(0.4.71)。** 別々にすると
2651
+ // 「入力欄はあるのに未設定だと言われない」「言われるのに入力欄が無い」のどちらかが起きる。
2652
+ // 0.4.70 は unset で入力欄を畳んでいたので注記も抑えていたが、案A で欄を出すようにしたため揃えた
2653
+ var tokenRelevant = derived != null ? derived !== "off" : tools[cap] !== "none";
2590
2654
  if (!tokenIsFallback && tokenRelevant && sk && (SITE.secretKinds || []).indexOf(sk) >= 0 && !((fleetVal("secretSources") || {})[sk])) {
2591
2655
  body.appendChild(el("div", { class: "ds-tile-note", text: "⚠ トークンが未設定です" }));
2592
2656
  }
@@ -2780,6 +2844,34 @@ ${capabilityState.toString()}
2780
2844
  });
2781
2845
  }
2782
2846
 
2847
+ /**
2848
+ * いま同期している Backlog の接続先(#127)。
2849
+ *
2850
+ * **2つの経路から引く。** 正本は fleet の backlogTarget(engine が正本
2851
+ * issues-config.json → ミラーの順で解決した値)だが、**このキーを載せているのは
2852
+ * 新しい engine だけ**で、艦隊の多くは @v1 のまま持っていない。無いときは
2853
+ * 内部ソースの url(同じ値から機械導出されたプロジェクトURL)を解析して補う。
2854
+ * ここを backlogTarget だけにすると、**設定済みの案件に「未設定」と出す**
2855
+ * ——今回直している誤読(何もセットされていないと読んで貼り直す)を、
2856
+ * 画面の側からもう一度作ることになる。
2857
+ *
2858
+ * **解析できないときは黙らない。** 「未設定」と明示する。行ごと消すと、
2859
+ * 「表示が無い=まだ何も無い」なのか「出し忘れ」なのかが読み手に区別できない。
2860
+ */
2861
+ function currentBacklogTargetLine(s) {
2862
+ var t = fleetVal("backlogTarget");
2863
+ if (!t || !t.domain || !t.projectKey) t = (s && s.url && backlogTargetOf(s.url)) || null;
2864
+ var p = el("p", { class: "ds-current" });
2865
+ p.appendChild(el("span", { class: "ds-current-label", text: "現在の接続先" }));
2866
+ p.appendChild(
2867
+ el("span", {
2868
+ class: t ? "ds-current-value" : "ds-current-value is-unset",
2869
+ text: t ? t.domain + " / " + t.projectKey : "未設定",
2870
+ }),
2871
+ );
2872
+ return p;
2873
+ }
2874
+
2783
2875
  /**
2784
2876
  * リポジトリ内の情報源に足す操作(会議の取り込みON/OFF・Driveフォルダ追加とON/OFF・Figmaファイル追加)。
2785
2877
  *
@@ -2836,6 +2928,13 @@ ${capabilityState.toString()}
2836
2928
  // 「プロジェクトキー」「プロジェクト名」の区別が付かず取り違える。
2837
2929
  // 実データでも cm1.backlog.jp と jleague.backlog.jp が混在している
2838
2930
  if (s.kind === "課題管理" && usable("課題管理", "backlog")) {
2931
+ // **いまどこに繋がっているかを、フォームより先に出す(#127)。**
2932
+ //
2933
+ // 実機で踏んだ欠陥: この節は入力欄と placeholder しか無く、保存済みの接続先が
2934
+ // 画面のどこにも出ていなかった。保存直後に出る楽観的なチップはリロードで消えるので、
2935
+ // **利用者は「何もセットされていない」と読み、同じURLを貼り直した**。
2936
+ // 設定を変える口の隣には、いまの値が必ず要る。
2937
+ box.appendChild(currentBacklogTargetLine(s));
2839
2938
  // **設定ファイルが無い案件にはフォームを出さない。** 書き込み先が無いので
2840
2939
  // 貼っても 404 で終わる。三菱電機は tools が backlog なのに
2841
2940
  // backlog-settings.json が1つも無い(固定IP経由の同期で、UIからの投入は
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.70",
3
+ "version": "0.4.72",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,