@takagaki/cortex-decisions-viewer 0.12.32 → 0.12.33

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 +115 -56
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -16,6 +16,7 @@ exports.overlayStateOnFleet = overlayStateOnFleet;
16
16
  exports.chatRowsToAdd = chatRowsToAdd;
17
17
  exports.gchatRowsToAdd = gchatRowsToAdd;
18
18
  exports.devRowsToAdd = devRowsToAdd;
19
+ exports.markExcludedPendingRows = markExcludedPendingRows;
19
20
  exports.designRowsToAdd = designRowsToAdd;
20
21
  exports.materialRowsToAdd = materialRowsToAdd;
21
22
  exports.issuesRefOf = issuesRefOf;
@@ -1011,6 +1012,44 @@ function devRowsToAdd(externals, rows) {
1011
1012
  }
1012
1013
  return out;
1013
1014
  }
1015
+ /**
1016
+ * 仮置きで足した GitHub の行に、「読み取り停止中」の印を写す(#409 の後追い)。
1017
+ *
1018
+ * 🔴 **仮置きの行は印を持たない。** `pendingExternalRow` が作れるのは payload から
1019
+ * 切り出せる値だけで、その repo が `exclude` に居るかは登録ファイル側にしかない。
1020
+ *
1021
+ * それが問題になるのはこの一続きのときだけ:
1022
+ * 読み取りを止めた repo を**もう一度追加すると、サーバは「既に登録されています」と断る**
1023
+ *(`sources` には残っているため)。#409 で画面はその断りを事実として受け取り行を出すように
1024
+ * なったが、**印が無いので「読み取り中の行」として並ぶ**——止めたはずのものが動いて
1025
+ * 見えるので、**止め忘れたと思ってもう一度止めに行く**ことになる。まとめ表にも
1026
+ * 即時読み取りの `dev` にもその行は居ない(除外は `devExcluded` に分かれている)ので、
1027
+ * 他のどこからも印は付かない。
1028
+ *
1029
+ * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
1030
+ */
1031
+ function markExcludedPendingRows(view, reg) {
1032
+ const v = view || {};
1033
+ const rows = v.externalSources;
1034
+ if (!reg || !Array.isArray(reg.devExcluded) || !Array.isArray(rows))
1035
+ return v;
1036
+ const excluded = {};
1037
+ for (const r of reg.devExcluded)
1038
+ if (r && r.ref)
1039
+ excluded[String(r.ref).toLowerCase()] = true;
1040
+ let changed = false;
1041
+ const next = rows.map(function (s) {
1042
+ if (!s || s.pending !== true || !s.ref || s.goldState)
1043
+ return s;
1044
+ if (String(s.type || "").indexOf("github") !== 0)
1045
+ return s;
1046
+ if (excluded[String(s.ref).toLowerCase()] !== true)
1047
+ return s;
1048
+ changed = true;
1049
+ return Object.assign({}, s, { goldState: "excluded" });
1050
+ });
1051
+ return changed ? Object.assign({}, v, { externalSources: next }) : v;
1052
+ }
1014
1053
  /** デザインの行で、まとめ表がまだ知らないもの(鍵はファイルキー) */
1015
1054
  function designRowsToAdd(files, rows) {
1016
1055
  const have = {};
@@ -3604,6 +3643,7 @@ ${sanitizeRegisteredNow.toString()}
3604
3643
  ${chatRowsToAdd.toString()}
3605
3644
  ${gchatRowsToAdd.toString()}
3606
3645
  ${devRowsToAdd.toString()}
3646
+ ${markExcludedPendingRows.toString()}
3607
3647
  ${designRowsToAdd.toString()}
3608
3648
  ${materialRowsToAdd.toString()}
3609
3649
  ${issueRowsToAdd.toString()}
@@ -4053,6 +4093,58 @@ ${deriveCapTile.toString()}
4053
4093
  function codeUrl(prompt) {
4054
4094
  return claudeCodeUrl(prompt, REPO_SLUG, fleetVal("externalSources") || []);
4055
4095
  }
4096
+ /**
4097
+ * 🔴 **「1件も登録されていない」と「確かめられなかった」を分ける(#409)。**
4098
+ *
4099
+ * サーバは登録ファイルを読めなかった能力を、**キーごと応答から落とす**
4100
+ *(「読めなかった」を「0件」と混ぜないため。intake の buildRegisteredNow)。
4101
+ * ところが画面はそれを「足す行が無い」としか受け取っていなかったので、
4102
+ * **確かめられなかった結果が「登録されていません」として出ていた**。
4103
+ * オーナーはそれを見て未登録だと読み、追加して「既に登録されています」と断られた。
4104
+ *
4105
+ * 🔴 **能力ごとのキーの有無で判定してはいけない(実装レビューの指摘)。** サーバは
4106
+ * 「読めなかった」ときだけでなく **「そのファイルがまだ無い」ときにも同じくキーを落とす**
4107
+ * (読んだ本文が null でも undefined でも、文字列でなければ同じ枝に落ちる)。
4108
+ * キーの有無で見ると、**その能力をまだ使っていない案件**——つまり立ち上げ直後の、
4109
+ * この画面が一番よく見られる状態——に**永久に消えない誤警報**が出る
4110
+ * (「再読み込みしてください」と言われても、設定ファイルは再読み込みでは生えない)。
4111
+ * しかも製品としては、その状態は「登録されていません」と言い切ると決めてある
4112
+ * (engine の capabilities: ファイルの有無は実装の都合で、利用者にはどちらも「まだ登録していない」)。
4113
+ *
4114
+ * **だから応答まるごとで見る。** 登録ファイルの一覧(registeredNow)と
4115
+ * 能力の状態(capabilitiesNow)は**サーバの同じ return から出る**ので、
4116
+ * **両方とも無いときだけ**が「そもそも読みに行けなかった」——今回の症状の
4117
+ * 3経路(ツリー取得の失敗・ツリーの打ち切り・応答そのものの失敗)は全部これに当たる。
4118
+ *
4119
+ * ⚠ この粒度では、**ファイル1本だけが読めなかった回**(8秒の打ち切り・単発の失敗)は
4120
+ * 拾えない。そちらはサーバがログを出す経路なので、まず記録から追えるようにしてある。
4121
+ */
4122
+ function registrationUnreadable() {
4123
+ return liveAnswered && LIVE_REGISTERED === null && LIVE_CAPS === null;
4124
+ }
4125
+ /**
4126
+ * 一覧に添える1行。**帯は症状・行は一手・繰り返さない。**
4127
+ * 応答がまだ届いていない間は出さない(確かめる前に「確かめられなかった」と言わない)。
4128
+ *
4129
+ * **0件のときは一覧の代わり**——空の一覧そのものを出さない(オーナーが踏んだのは
4130
+ * 「空の一覧を見て未登録だと読んだ」ことなので)。
4131
+ *
4132
+ * 🔴 **行があるときも黙らない。** まとめ表が行を持っていれば一覧は出るが、
4133
+ * **その日足したばかりの行だけが欠けたまま、完全な一覧に見える**——利用者の読み違い方は
4134
+ * 0件のときと同じで、「自分が足したものは登録されていない」と読む。行は本物なので消さず、
4135
+ * **欠けているかもしれないことだけを添える**。
4136
+ *
4137
+ * ⚠ 「表示は前の晩の内容です」とは言わない。一覧にはまとめ表(前の晩)だけでなく
4138
+ * **このブラウザが覚えている反映待ちの行も混ざる**ので、言い切ると画面自身が嘘をつく。
4139
+ * 利用者が知りたいのは日付ではなく「自分が足したものが抜けているかどうか」。
4140
+ */
4141
+ function unverifiedListNote(noun, hasRows) {
4142
+ if (!registrationUnreadable()) return null;
4143
+ var head = "登録されている" + noun + "を確認できませんでした。";
4144
+ return el("div", { class: "ds-note", text: hasRows
4145
+ ? head + "最近足したものが抜けているかもしれません。再読み込みしてください。"
4146
+ : head + "再読み込みしてください。" });
4147
+ }
4056
4148
  // AI 導線の a はここでだけ作る。href を決めてよいのは codeUrl だけ(#404)。
4057
4149
  // 指示文を属性に残すのは、接続状況の応答が届いたときに href を組み直すため(syncAiLinks)
4058
4150
  function aiLink(cls, prompt, text) {
@@ -6499,7 +6591,7 @@ ${deriveCapTile.toString()}
6499
6591
  // 状態ストアの答えは常に勝つ(まとめ表は gate を焼き込まない。overlayStateOnFleet の項)
6500
6592
  if (!MERGED) {
6501
6593
  MERGED = overlayStateOnFleet(
6502
- applyPendingOps(mergeRegisteredFleet(rawView(), LIVE_REGISTERED), pend),
6594
+ markExcludedPendingRows(applyPendingOps(mergeRegisteredFleet(rawView(), LIVE_REGISTERED), pend), LIVE_REGISTERED),
6503
6595
  LIVE_STATE,
6504
6596
  );
6505
6597
  }
@@ -7431,44 +7523,6 @@ ${deriveCapTile.toString()}
7431
7523
  * (素の Decisions ビュー)で行も無い能力に空の箱を積むと、押しても何も無いモーダルになる。
7432
7524
  */
7433
7525
  function liveListWanted(kind) { return !!SITE.intake || liveExternals(kind).length > 0; }
7434
- /**
7435
- * 🔴 **「1件も登録されていない」と「確かめられなかった」を分ける(#409)。**
7436
- *
7437
- * サーバは登録ファイルを読めなかった能力を、**キーごと応答から落とす**
7438
- *(「読めなかった」を「0件」と混ぜないため。intake の buildRegisteredNow)。
7439
- * ところが画面はそれを「足す行が無い」としか受け取っていなかったので、
7440
- * **確かめられなかった結果が「登録されていません」として出ていた**。
7441
- * オーナーはそれを見て未登録だと読み、追加して「既に登録されています」と断られた。
7442
- *
7443
- * 🔴 **能力ごとのキーの有無で判定してはいけない(実装レビューの指摘)。** サーバは
7444
- * 「読めなかった」ときだけでなく **「そのファイルがまだ無い」ときにも同じくキーを落とす**
7445
- * (読んだ本文が null でも undefined でも、文字列でなければ同じ枝に落ちる)。
7446
- * キーの有無で見ると、**その能力をまだ使っていない案件**——つまり立ち上げ直後の、
7447
- * この画面が一番よく見られる状態——に**永久に消えない誤警報**が出る
7448
- * (「再読み込みしてください」と言われても、設定ファイルは再読み込みでは生えない)。
7449
- * しかも製品としては、その状態は「登録されていません」と言い切ると決めてある
7450
- * (engine の capabilities: ファイルの有無は実装の都合で、利用者にはどちらも「まだ登録していない」)。
7451
- *
7452
- * **だから応答まるごとで見る。** 登録ファイルの一覧(registeredNow)と
7453
- * 能力の状態(capabilitiesNow)は**サーバの同じ return から出る**ので、
7454
- * **両方とも無いときだけ**が「そもそも読みに行けなかった」——今回の症状の
7455
- * 3経路(ツリー取得の失敗・ツリーの打ち切り・応答そのものの失敗)は全部これに当たる。
7456
- *
7457
- * ⚠ この粒度では、**ファイル1本だけが読めなかった回**(8秒の打ち切り・単発の失敗)は
7458
- * 拾えない。そちらはサーバがログを出す経路なので、まず記録から追えるようにしてある。
7459
- */
7460
- function registrationUnreadable() {
7461
- return liveAnswered && LIVE_REGISTERED === null && LIVE_CAPS === null;
7462
- }
7463
- /**
7464
- * 空の一覧の代わりに出す1行。**帯は症状・行は一手・繰り返さない。**
7465
- * 空の一覧そのものを出さない——今回オーナーが踏んだのは「空の一覧を見て未登録だと読んだ」ことなので。
7466
- * 応答がまだ届いていない間は出さない(確かめる前に「確かめられなかった」と言わない)。
7467
- */
7468
- function unverifiedListNote(noun) {
7469
- if (!registrationUnreadable()) return null;
7470
- return el("div", { class: "ds-note", text: "登録されている" + noun + "を確認できませんでした。再読み込みしてください。" });
7471
- }
7472
7526
  // **箱にはアダプタのタグを付ける(#153)。** pushCap の無タグは「どのタイルにも出す」
7473
7527
  // 既定なので、タグを忘れると Slack の一覧が Google Chat モーダルに丸ごと出る
7474
7528
  if (liveListWanted("slack")) pushCap("チャット", liveList(
@@ -7476,11 +7530,10 @@ ${deriveCapTile.toString()}
7476
7530
  function () { return externalsSig("slack", slackWorkspaces()); },
7477
7531
  function (box) {
7478
7532
  var items = liveExternals("slack");
7479
- if (!items.length) {
7480
- var noteSlack = unverifiedListNote("チャンネル");
7481
- if (noteSlack) box.appendChild(noteSlack);
7482
- return;
7483
- }
7533
+ // 注記は**行より前**に置く(0件なら一覧の代わり・行があれば添え書き)
7534
+ var noteSlack = unverifiedListNote("チャンネル", items.length > 0);
7535
+ if (noteSlack) box.appendChild(noteSlack);
7536
+ if (!items.length) return;
7484
7537
  var meta = DS_TYPE["slack"] || { icon: "🔗", label: "Slack" };
7485
7538
  slackRows(items, meta).forEach(function (n) { box.appendChild(n); });
7486
7539
  },
@@ -7492,11 +7545,10 @@ ${deriveCapTile.toString()}
7492
7545
  function () { return externalsSig("google-chat", null); },
7493
7546
  function (box) {
7494
7547
  var items = liveExternals("google-chat");
7495
- if (!items.length) {
7496
- var noteGchat = unverifiedListNote("スペース");
7497
- if (noteGchat) box.appendChild(noteGchat);
7498
- return;
7499
- }
7548
+ // 注記は**行より前**に置く(0件なら一覧の代わり・行があれば添え書き)
7549
+ var noteGchat = unverifiedListNote("スペース", items.length > 0);
7550
+ if (noteGchat) box.appendChild(noteGchat);
7551
+ if (!items.length) return;
7500
7552
  var meta = DS_TYPE["google-chat"] || { icon: "💬", label: "Google Chat" };
7501
7553
  items.forEach(function (s) { box.appendChild(extSourceCard(s, meta)); });
7502
7554
  },
@@ -7506,11 +7558,10 @@ ${deriveCapTile.toString()}
7506
7558
  function () { return externalsSig("github", githubOwnersView() || null); },
7507
7559
  function (box) {
7508
7560
  var items = liveExternals("github");
7509
- if (!items.length) {
7510
- var noteDev = unverifiedListNote("リポジトリ");
7511
- if (noteDev) box.appendChild(noteDev);
7512
- return;
7513
- }
7561
+ // 注記は**行より前**に置く(0件なら一覧の代わり・行があれば添え書き)
7562
+ var noteDev = unverifiedListNote("リポジトリ", items.length > 0);
7563
+ if (noteDev) box.appendChild(noteDev);
7564
+ if (!items.length) return;
7514
7565
  // **meta は行ごとに引く**(issues と discussions でアイコンとラベルが違う)
7515
7566
  githubRows(items).forEach(function (n) { box.appendChild(n); });
7516
7567
  },
@@ -9512,10 +9563,14 @@ ${deriveCapTile.toString()}
9512
9563
  box.appendChild(liveList("共有資料", function () {
9513
9564
  var d = driveNow();
9514
9565
  // 仮置きの印(drivePending)も署名に入れる——外れたときに印だけ残さない(#388)
9515
- return JSON.stringify([d.driveFolderIds || [], d.driveFolderNames || {}, d.driveState || "", d.drivePending || {}]);
9566
+ // 「確かめられたか」も混ぜる(#409。混ぜないと注記の要否が変わっても作り直されない)
9567
+ return JSON.stringify([d.driveFolderIds || [], d.driveFolderNames || {}, d.driveState || "", d.drivePending || {}, registrationUnreadable()]);
9516
9568
  }, function (listBox) {
9517
9569
  var src = driveNow();
9518
9570
  var folders = src.driveFolderIds || [];
9571
+ // 兄弟の穴を一緒に塞ぐ(#409)。ここも同じ registeredNow から行を足している
9572
+ var noteDrive = unverifiedListNote("フォルダ", folders.length > 0);
9573
+ if (noteDrive) listBox.appendChild(noteDrive);
9519
9574
  // 名前は任意。**無ければIDを出す**(既存の案件は名前を持っていない)
9520
9575
  var fnames = src.driveFolderNames || {};
9521
9576
  if (folders.length) {
@@ -9651,9 +9706,13 @@ ${deriveCapTile.toString()}
9651
9706
  // **署名に取り込みの状態も混ぜる(段C第二弾)。** 一覧のJSONだけだと、応答が返る前に
9652
9707
  // モーダルを開いた人には取り込みが1件も出ない(designIngestSigOf の項に経緯)
9653
9708
  figGroup.appendChild(liveList("デザイン", function () {
9654
- return JSON.stringify(figsOf()) + "|" + designIngestSigOf(figsOf());
9709
+ // 「確かめられたか」も混ぜる(#409。混ぜないと注記の要否が変わっても作り直されない)
9710
+ return JSON.stringify(figsOf()) + "|" + designIngestSigOf(figsOf()) + "|" + registrationUnreadable();
9655
9711
  }, function (listBox) {
9656
9712
  var figs = figsOf();
9713
+ // 兄弟の穴を一緒に塞ぐ(#409)。ここも同じ registeredNow から行を足している
9714
+ var noteFig = unverifiedListNote("ファイル", figs.length > 0);
9715
+ if (noteFig) listBox.appendChild(noteFig);
9657
9716
  // **一覧から消えた鍵は「外し済み」から落とす**(figsRemoved の項に経緯)。
9658
9717
  // サーバが削除を認めた印なので、ここから先に同じ鍵が現れたら**貼り直された別の登録**
9659
9718
  var figKeys = figs.map(function (f) { return f.key; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.32",
3
+ "version": "0.12.33",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,