@takagaki/cortex-decisions-viewer 0.4.80 → 0.4.81

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
@@ -137,9 +137,10 @@ async function readFleetSources(repoRoot) {
137
137
  name: String(s.name ?? ""),
138
138
  ref: s.ref != null ? String(s.ref) : undefined,
139
139
  url: s.url != null ? String(s.url) : undefined,
140
- gold: typeof s.gold === "boolean" ? s.gold : undefined,
141
- // goldState gold の理由。`gold: false` だけだと「意図的に外した」「宣言し忘れ」
142
- // exclude による除外」が同じ値になり、**正常な除外にまで警告を出して麻痺する**。
140
+ // **通すのは goldState だけ(`gold` の真偽値は落とす)。** slack は常に対象なので
141
+ // 真偽値は情報を持たず、github の除外は `excluded` の方が「正常な除外」だと
142
+ // 分かる(真偽値で見ていた頃は exclude 済みのリポにまで警告が付いた)。
143
+ // 画面はこの値で github の読み取り ON/OFF トグルの向きを決める。
143
144
  goldState: s.goldState != null ? String(s.goldState) : undefined,
144
145
  notify: typeof s.notify === "boolean" ? s.notify : undefined,
145
146
  gate: s.gate != null ? String(s.gate) : undefined,
@@ -149,7 +150,7 @@ async function readFleetSources(repoRoot) {
149
150
  origin: s.origin != null ? String(s.origin) : undefined,
150
151
  // どの Slack ワークスペースのチャンネルか(#104)。**列挙し忘れると静かに消える**
151
152
  // (このマッピングは素通しではなく明示の許可リスト)。落とすと、同じIDの
152
- // チャンネルを2つのワークスペースに持つ案件で Gold トグルが別の行を撃つ
153
+ // チャンネルを2つのワークスペースに持つ案件で「登録を解除」が別の行を消す
153
154
  workspace: s.workspace != null ? String(s.workspace) : undefined,
154
155
  }))
155
156
  : undefined;
package/dist/render.js CHANGED
@@ -160,15 +160,26 @@ const CM_TOOL_LABEL = {
160
160
  * **同じ判定を使う**ためにここに置く。片方だけ直すと、同じ画面でマップが⚠️・直下が「接続OK」になる。
161
161
  *
162
162
  * 判定の考え方:
163
- * - 明示的に Gold 対象から外したもの(`gold === false`)は、読めなくて当たり前なので警告しない。
164
- * ただし通知先(`notify`)に使っているなら、届いていないこと自体が問題なので拾う。
165
- * - `gold` が無い(旧 fleet-status.json)ときは**警告する側に倒す**。「不明」を「対象外」と扱うと、
166
- * 本当に壊れているものが黙って隠れる(フェイルオープン)。
163
+ * - 明示的に読み取りから外したもの(`goldState === "excluded"`)は、読めなくて当たり前なので
164
+ * 警告しない。ただし通知先(`notify`)に使っているなら、届いていないこと自体が問題なので拾う。
165
+ * - それ以外は**警告する側に倒す**。「不明」を「対象外」と扱うと、本当に壊れているものが
166
+ * 黙って隠れる(フェイルオープン)。
167
+ *
168
+ * **`excluded` は github だけの状態。** `dev-sources.json` の `exclude` による除外で、
169
+ * slack 側の状態ではない——`channels.json` に載っているチャンネルは例外なく全て Gold 昇格の
170
+ * 対象になったので、slack は常に「読めるべきもの」。判定を `gold === false` に置いたままだと、
171
+ * engine が github の除外リポに付ける `gold: false` を slack と同じ意味で読むことになり、
172
+ * **exclude 済みの github リポに警告が付く**(除外は正常な状態)。
173
+ *
174
+ * **`notify` は見ない。** 以前は「除外されていても通知先なら繋がっている必要がある」として
175
+ * 例外にしていたが、`excluded` が github 専用になった今、engine が `notify` を付けるのは
176
+ * slack だけ(resolve-external-sources)なので**この節は決して成立しない**。残しておくと、
177
+ * 読み手には現役の分岐に見えて「github の通知先」という存在しない状態を想定させる。
167
178
  */
168
179
  function sourceNeedsAttention(s) {
169
180
  if (s.gate === "ok")
170
181
  return false;
171
- if (s.gold === false && s.notify !== true)
182
+ if (s.goldState === "excluded")
172
183
  return false;
173
184
  return true;
174
185
  }
@@ -1192,13 +1203,6 @@ a.ds-name:hover { text-decoration: underline; }
1192
1203
  .ds-inactive .ds-type, .ds-inactive .ds-name, .ds-inactive a.ds-name, .ds-inactive .ds-pipe-label { color: #9ca3af; }
1193
1204
  .ds-inactive-note { font-size: 12px; color: #9ca3af; line-height: 1.6; flex: 0 0 auto; }
1194
1205
  .ds-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 0 0 auto; }
1195
- .badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
1196
- .badge.ds-excluded { background: #eceef1; color: #6b7280; }
1197
- /* 保存は済んでいるが、接続状況の再計算(1〜2分)がまだのもの。**確定と同じ見た目にしない**
1198
- ——リロードで消えたときに「戻された」と読まれるため、点線で区別して (反映中) を添える。
1199
- **幅と色まで指定する。** border-style だけだと、border を持たない .ds-excluded 側は
1200
- 初期値の medium(≈3px)/currentColor に解決され、押した向きによって太さ3倍・色が別物になる */
1201
- .badge.is-pending { border: 1px dashed currentColor; opacity: 0.85; }
1202
1206
  /* 能力モーダルは一覧(17チャンネル・19リポジトリ)を入れるので広く取る。
1203
1207
  既定の480pxでは、チャンネル名+バッジ2つ+トグルが1行に収まらず折り返す。
1204
1208
  画面の広さいっぱいまで使う——この画面は一覧を読むためのもので、
@@ -1252,7 +1256,6 @@ a.ds-name:hover { text-decoration: underline; }
1252
1256
  .ds-drive-note, .ds-inactive-note, .ds-legacy-chip, .ds-keys-code {
1253
1257
  min-width: 0; white-space: normal; overflow-wrap: anywhere;
1254
1258
  }
1255
- .badge.ds-undeclared { background: #fff4e5; color: #9a6700; }
1256
1259
  .ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
1257
1260
  .ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
1258
1261
  .ds-toggle-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
@@ -2169,9 +2172,12 @@ ${capabilityState.toString()}
2169
2172
  */
2170
2173
  function openCapabilityModal(cap, items, note) {
2171
2174
  var m = makeModal(cap, true);
2172
- // **凡例を必ず入れる。** goldState の4状態は凡例があって初めて読める
2173
- if (cap === "チャット" || cap === "開発") {
2174
- m.modal.appendChild(el("p", { class: "modal-desc", text: "「Gold昇格の読み取り対象」のものだけを毎晩の Gold 昇格が読み取ります。「除外」は意図的に外したもの、「未宣言(対象外)」はまだ判断されていないもので、どちらも読み取りません。" }));
2175
+ // **何が読み取られるのかを必ず書く。** 一覧に並ぶものと読み取られるものの関係は、
2176
+ // 書いていないと分からない(チャットは「載っていれば全部」、開発はリポジトリ単位でOFFできる)
2177
+ if (cap === "チャット") {
2178
+ m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるチャンネルは、例外なく全て毎晩の Gold 昇格が読み取り、顧客も見る Decision・用語集の材料になります。読み取りを止めるには「登録を解除」してください。" }));
2179
+ } else if (cap === "開発") {
2180
+ m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるリポジトリの Issues を毎晩の Gold 昇格が読み取ります。リポジトリ単位で読み取りを止められます(止めたものは読み取られません)。" }));
2175
2181
  }
2176
2182
  // **なぜこの状態なのかを、押した先でも読めるようにする(#125②)。** タイルは常時出す
2177
2183
  // ようになったので、未接続の能力を押したときに中身が空になる場面が増えた。
@@ -2697,7 +2703,7 @@ ${capabilityState.toString()}
2697
2703
  }
2698
2704
  // 読み取り範囲。**この画面は顧客も見る。何をGoldに読んでいるかの開示が主眼**
2699
2705
  //
2700
- // **繋がっているときだけ出す。** 「未使用」と「2リポジトリ/2件をGoldに使用」が
2706
+ // **繋がっているときだけ出す。** 「未使用」と読み取り範囲の内訳が
2701
2707
  // 同じ行に並ぶと読めない。tools を none に倒しても externalSources は翌朝の
2702
2708
  // 再生成まで残るので、使うのをやめた宣言の直後に必ず通る。
2703
2709
  // 「⦿ 未接続 Driveフォルダ 0件」も同様(最終同期を出さないのと同じ理由)
@@ -2705,10 +2711,11 @@ ${capabilityState.toString()}
2705
2711
  if (!connected) {
2706
2712
  // 状態だけ出す
2707
2713
  } else if (live.length) {
2708
- // 単位は分かるものだけ。知らない種別に勝手な単位を付けない
2714
+ // 単位は分かるものだけ。知らない種別に勝手な単位を付けない。
2715
+ // **「N件をGoldに使用」の内訳は出さない。** チャンネルは載っていれば全部が対象で、
2716
+ // github の除外は行のトグルが持つ(件数の内訳は Gold トグルがあった頃の名残)
2709
2717
  var unit = cap === "チャット" ? "チャンネル" : cap === "開発" ? "リポジトリ" : "件";
2710
- var gold = live.filter(function (s) { return s.gold === true; }).length;
2711
- parts.push(live.length + unit + "/" + gold + "件をGoldに使用");
2718
+ parts.push(live.length + unit);
2712
2719
  } else if (cap === "共有資料" && adapter === "google-drive" && internal && internal.driveFolderIds) {
2713
2720
  // **そのタイルのアダプタを見る。** 宣言(tools)を見ていた頃は、local の案件
2714
2721
  //(gdo・三菱)で「ローカル」と名乗ったタイルが「Driveフォルダ 0件」と語っていた。
@@ -2871,9 +2878,9 @@ ${capabilityState.toString()}
2871
2878
  }
2872
2879
  }
2873
2880
 
2874
- // 注記は全能力の後ろに1つ。**Gold の4状態は凡例があって初めて読める。**
2881
+ // 注記は全能力の後ろに1つ。**何が Gold の材料になるのかを1文で言う。**
2875
2882
  if (anyCap) {
2876
- sec.appendChild(el("p", { class: "ds-note", text: "「Gold昇格の読み取り対象」のものだけを毎晩の Gold 昇格が読み取ります。「除外」は意図的に外したもの、「未宣言(対象外)」はまだ判断されていないもので、どちらも読み取りません(チャンネル・リポジトリ単位で切り替えられます)。" }));
2883
+ sec.appendChild(el("p", { class: "ds-note", text: "登録されている Slack チャンネルと GitHub リポジトリは、毎晩の Gold 昇格が読み取り、顧客も見る Decision・用語集の材料になります。チャンネルは登録を解除すると、リポジトリは読み取りを止めると、以後は読み取られません。" }));
2877
2884
  }
2878
2885
 
2879
2886
  if (pipes.length) {
@@ -2948,65 +2955,46 @@ ${capabilityState.toString()}
2948
2955
  // 宣言を撤去したいま出す条件が無い。未接続の能力は能力タイル(常時表示)が状態と入口を持つ。
2949
2956
  // 外部ソース1件のカード(グループ化表示・展開表示の両方から使う)
2950
2957
  /**
2951
- * Gold昇格の切り替えボタン。押すと設定ファイル(channels.json)が書き換わる。
2958
+ * チャンネルの登録を解除するボタン(remove-channel)。押すと channels.json から行が消える。
2959
+ *
2960
+ * 注: ここは埋め込みJSのテンプレートリテラル内。**コメントでもバッククォートを使わない**
2961
+ *(リテラルが閉じて構文エラーになる。実際にそれで壊した)。
2962
+ *
2963
+ * **これが画面から読み取りを止める唯一の手段。** チャンネル単位の Gold トグルは撤去した
2964
+ * (載っているチャンネルは例外なく全て Gold 昇格の対象)。事故(社内限定の会話が顧客可視の
2965
+ * Gold へ流れ始めた)に画面から手を打てないと、git で channels.json を直接編集するしかない。
2952
2966
  *
2953
- * **広げる方向(OFF→ON)には確認を出す。** そのチャンネルの内容をAIが読んで、顧客も見る
2954
- * Decision・用語集に蒸留するようになる。社内限定のチャンネルをONにすると、顧客が見られない
2955
- * 会話が顧客可視の層に出てくる(実際にその事故が起きている)。狭める方向は安全なので確認しない。
2967
+ * **確認文に不可逆性を必ず書く。** 止まるのは今後の読み取りだけで、**既に Gold 層へ
2968
+ * 入った内容は消えない**(案件リポの git 履歴に残る)。「読み取りを止めます」だけだと
2969
+ * 「入ってしまった内容も消える」と読まれ、事故対応の判断を誤らせる。
2956
2970
  *
2957
- * 保存しても画面はすぐには変わらない(接続状況は夜間バッチのスナップショット)。
2958
- * それを伝えないと「保存できていない」と受け取られるので、応答のメッセージをそのまま出す。
2971
+ * 削除の作法は既存の removeButton(Figma・GitHub の削除)に揃える——確認は向きに
2972
+ * 関わらず必ず出し、成功しても一覧からは消さずに行へ取り消し線を引く(一覧は夜間
2973
+ * スナップショットなので、翌朝まで残るのが正しい状態)。
2959
2974
  */
2960
- function goldToggle(s) {
2961
- // **共通部品(stateToggle)に載せる。** 以前はここだけ変更者名をダイアログで聞いており、
2962
- // 同じカードの中で2つの流儀が並んでいた。送信・エラー表示・向きの反転も個別実装で、
2963
- // 片方だけ直したときにずれる状態だった。
2964
- // **控えはボタンにも効かせる。** バッジだけに効かせると、リロード後に同じカードの中で
2965
- // バッジが「対象です」・ボタンが「始めますか?」と逆を主張する。しかもその状態で
2966
- // ボタンの表示どおりに押すと、サーバは既に同じ値なので changed:false を返し、
2967
- // ボタンが永久に反転しない——**15分(TTL切れ)まで止められなくなる**。
2968
- // stateToggle の doc が要件として書いている「戻す操作を必ず残す」を破る。
2969
- var pendingNow = pendingGoldFor(s);
2970
- return stateToggle({
2971
- on: pendingNow ? pendingNow.value : s.gold === true,
2972
- labelOn: "Gold昇格を止める",
2973
- labelOff: "Gold昇格を始める",
2974
- confirmOn: [
2975
- "「" + (s.name || s.ref) + "」の内容を、顧客も見る Decision・用語集に蒸留するようになります。",
2976
- "",
2977
- "社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。",
2978
- "顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ有効にしてください。",
2979
- "",
2980
- "※ 今夜の蒸留(22時ごろ)までに止めれば取り込まれません。一度生成された記録は自動では消えません。",
2981
- "",
2982
- "有効にしますか?",
2983
- ],
2984
- build: function (next) {
2975
+ function removeChannelButton(s, card) {
2976
+ return removeButton({
2977
+ label: "登録を解除",
2978
+ confirmLines: function () {
2979
+ return [
2980
+ "「" + (s.name || s.ref) + "」を、Gold 昇格の読み取り対象から外します。",
2981
+ "",
2982
+ "次回の Gold 昇格(毎晩22時ごろ)から、このチャンネルは読み取られなくなります。",
2983
+ "",
2984
+ "※ 既に Gold 層に入った内容は消えません(Decision・用語集はそのまま残ります)。",
2985
+ "",
2986
+ "解除しますか?",
2987
+ ];
2988
+ },
2989
+ build: function () {
2985
2990
  // **どのワークスペースのチャンネルかを必ず送る(#104)。** 落とすと、サーバ側は
2986
2991
  // 既定ワークスペースの同じIDのエントリを探しに行き、「対象チャンネルが
2987
- // 見つかりません」か、最悪**別のチャンネルを Gold に上げる**
2988
- var payload = { op: "set-channel-gold", ref: s.ref, value: next };
2992
+ // 見つかりません」か、最悪**別のワークスペースの同じIDのチャンネルを消す**
2993
+ var payload = { op: "remove-channel", ref: s.ref };
2989
2994
  if (s.workspace) payload.workspace = s.workspace;
2990
2995
  return payload;
2991
2996
  },
2992
- // 保存できた直後に、この行のバッジだけを「反映中」に差し替える。
2993
- // 一覧そのものは触らない(結果メッセージや他行の状態を巻き込まないため)。
2994
- pending: function (next, toggleEl) {
2995
- // **表示済みの値に戻したなら「反映中」ではない。** ON→OFF と押し直した場合、
2996
- // 画面が出している値と一致するので待つものが無い。ここで消さないと、
2997
- // 何も保留していないのに (反映中) が残り、リロードで消えて不可解に見える。
2998
- var back = next === (s.gold === true);
2999
- // 控えの鍵もワークスペース込み(#104。pendingKeyOf の doc)
3000
- if (back) clearPendingGold(pendingKeyOf(s));
3001
- else markPendingGold(pendingKeyOf(s), next);
3002
- var card = toggleEl && toggleEl.parentNode;
3003
- var badges = card && card.querySelector(".ds-badges");
3004
- if (!badges) return;
3005
- var b = badges.querySelector(".badge.ds-gold, .badge.ds-excluded, .badge.ds-undeclared");
3006
- var spec = pendingBadgeSpec(next, back);
3007
- if (b) { b.className = spec.cls; b.textContent = spec.text; }
3008
- else badges.insertBefore(el("span", { class: spec.cls, text: spec.text }), badges.firstChild);
3009
- },
2997
+ onDone: function () { card.classList.add("ds-removed"); },
3010
2998
  });
3011
2999
  }
3012
3000
 
@@ -3631,10 +3619,10 @@ ${capabilityState.toString()}
3631
3619
  // 「会議はTeams・連絡はSlack」という普通の構成を表せない。teams を宣言した案件では
3632
3620
  // フォームごと消え、**Slackを渡されているのに追加できない**状態になっていた。
3633
3621
  //
3634
- // 読む側(resolve-external-sources.mjs)も Slack tools ゲートを外してある。
3635
- // Gold昇格の本来のゲートは**チャンネル単位の gold: true**(追加直後は未宣言=読まれない。
3636
- // 上げたければ追加後にトグルを押す)。そこは変えていないので、宣言なしで顧客可視のGold
3637
- // 流れることはない。
3622
+ // **追加そのものが Gold 昇格の意思決定になった。** チャンネル単位の gold: true という
3623
+ // 二段目のゲートは撤去され、channels.json に載っているチャンネルは例外なく全て
3624
+ // 毎晩の Gold 昇格が読む。だから**確認はここに要る**——重い話(顧客可視の層へ出る)を
3625
+ // 伝える場所が他に無くなった。撤去した Gold トグルの確認文の趣旨をここへ移している。
3638
3626
  //
3639
3627
  // 注: ここは埋め込みJSのテンプレートリテラル内。**コメントでもバッククォートを使わない**
3640
3628
  //(リテラルが閉じて構文エラーになる。実際にそれで壊した)。
@@ -3648,22 +3636,27 @@ ${capabilityState.toString()}
3648
3636
  requiredNote: "チャンネルのURLを入力してください。",
3649
3637
  // **招待の話を先頭に置く。** Slack の仕様で、ボットは**公開チャンネルでも**招待されるまで
3650
3638
  // 中身を1件も読めない(conversations.history が not_in_channel を返す・2026-08-16 実測+公式ドキュメント)。
3651
- // 以前は「名前が取得できない」を主に書いていたが、読めない方が重い話なので順序を入れ替えた。
3652
- hint: "先に Slack でそのチャンネルを開き、下のコマンドを貼って Cortex を招待してください。公開チャンネルでも招待が必要で、招待していないと中身を読めません。そのあと、チャンネル名を右クリック →「リンクをコピー」で出るURLを下に貼ります。追加しただけでは Gold 昇格には載りません(載せるには各チャンネルの「Gold昇格を始める」を押します)。招待前に登録すると名前は「名前未取得」になり、招待後にGold昇格をONにすると実名に変わります。",
3639
+ // そのあとに「追加=Gold 昇格の対象」を置く(手順と、その手順の重さ)。
3640
+ hint: "先に Slack でそのチャンネルを開き、下のコマンドを貼って Cortex を招待してください。公開チャンネルでも招待が必要で、招待していないと中身を読めません。そのあと、チャンネル名を右クリック →「リンクをコピー」で出るURLを下に貼ります。追加したチャンネルは毎晩の Gold 昇格の対象になり、顧客も見る Decision・用語集の材料になります(読み取りを止めるには、追加した行の「登録を解除」を押します)。招待前に登録すると名前は「名前未取得」になるので、招待してから追加するのがおすすめです。",
3653
3641
  copyText: "/invite @Cortex",
3654
- // **「Goldに載る」と書かない。** 追加だけでは載らないので嘘になる。
3655
- // 確認する理由は「中央Botで読みに行く」「他案件の取り違え」。
3642
+ // 撤去した Gold トグルの確認文の趣旨をここへ移した。**追加が昇格の意思決定そのもの**
3643
+ // なので、社内限定の会話が顧客可視の層に出てしまう話はここで伝えるしかない
3644
+ // (実際にその事故が起きている)。あわせて他案件の取り違えも確認させる。
3656
3645
  confirmLines: function (v) {
3657
3646
  return [
3658
- "このチャンネルを、Cortex が読みに行く対象に追加します。",
3659
- "",
3660
- "貼ったURLがこの案件のものか、もう一度確認してください。",
3647
+ "このチャンネルの内容を、顧客も見る Decision・用語集に蒸留するようになります。",
3661
3648
  "",
3662
3649
  v,
3663
3650
  "",
3651
+ "社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。",
3652
+ "顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ追加してください。",
3653
+ "",
3654
+ "貼ったURLがこの案件のものか、もう一度確認してください。",
3655
+ "",
3664
3656
  // Slack の仕様(公開チャンネルでも招待が要る)を、最後にもう一度置く。
3665
3657
  // 「追加したのに何も取り込まれない」の原因のほとんどがこれ。
3666
3658
  "※ Slack でこのチャンネルに Cortex を招待していないと、中身は読めません。",
3659
+ "※ 今夜の蒸留(22時ごろ)までに登録を解除すれば取り込まれません。一度生成された記録は自動では消えません。",
3667
3660
  "",
3668
3661
  "追加しますか?",
3669
3662
  ];
@@ -3707,20 +3700,15 @@ ${capabilityState.toString()}
3707
3700
 
3708
3701
  // ---------- 設定の変更(?op=config)の共通部品 ----------
3709
3702
  //
3710
- // 確認 → 送信 → 結果表示、までを1箇所に置く。3つの操作で書き写すと、
3711
- // 片方だけ直したときにずれる(既存の goldToggle で使っている作法をそのまま一般化したもの)。
3703
+ // 確認 → 送信 → 結果表示、までを1箇所に置く。操作ごとに書き写すと、
3704
+ // 片方だけ直したときにずれる(トグル・削除・追加のどれも同じ経路を通す)。
3712
3705
 
3713
3706
  /**
3714
3707
  * サーバへ設定変更を送る。**一覧は書き換えない**
3715
3708
  * (接続状況は夜間バッチのスナップショットなので、「もう効いている」と誤解させないため)。
3716
3709
  * 呼び出し側は onDone で自分のボタンの向きを反転させる。
3717
3710
  *
3718
- * **例外はバッジの先出し。** opts.pending を持つ操作(いまは Gold の切り替えだけ)は、
3719
- * 押した内容を「(反映中)」として先に出す。出してよいのは (1) 保存の成否がサーバで
3720
- * 確定していて推測が入らない (2) **確定した表示と見た目で区別できる**、の両方を満たす
3721
- * ときだけ。区別しないと、リロードで消えたときに「戻された」と読まれる。
3722
- *
3723
- * **もう1つの例外が接続先の一覧(#127)。** 追加・削除の応答にはいまの一覧が必ず
3711
+ * **例外が接続先の一覧(#127)。** 追加・削除の応答にはいまの一覧が必ず
3724
3712
  * 添えられていて(成功も、重複で断られたときも、世代がずれていたときも)、これは
3725
3713
  * スナップショットではなく**サーバが書いた直後の確定値**なので、その場で描き直してよい。
3726
3714
  * @param hooks { onTargets(list) → 画面が見ていた集合と違ったか, staleNote }
@@ -3796,9 +3784,7 @@ ${capabilityState.toString()}
3796
3784
 
3797
3785
  /**
3798
3786
  * ON/OFF のトグル。**戻す操作を必ず残す**(誤って有効にしたとき翌朝まで止められないのは許容できない)。
3799
- * @param opts { on, labelOn, labelOff, confirmOn, build(next), pending(next, wrapEl) }
3800
- * pending … 保存できた直後に、その行のバッジだけを「(反映中)」へ差し替える任意のフック。
3801
- * 持たない操作は従来どおりボタンの向きだけを反転する(バッジは触らない)。
3787
+ * @param opts { on, labelOn, labelOff, confirmOn, build(next) }
3802
3788
  */
3803
3789
  function stateToggle(opts) {
3804
3790
  var wrap = el("div", { class: "ds-toggle" });
@@ -3817,11 +3803,9 @@ ${capabilityState.toString()}
3817
3803
  if (next && opts.confirmOn && !confirmDanger(opts.confirmOn)) { msg.textContent = ""; return; }
3818
3804
  sendConfig(opts.build(next), msg, btn, function () {
3819
3805
  // **押した内容をボタンに反映し、逆向きの操作を残す。**
3806
+ // **カードごと作り直さない。** 上の msg に出した結果表示が消えてしまう
3820
3807
  on = next;
3821
3808
  paint();
3822
- // バッジは「反映中」として先出しする(opts.pending がある操作だけ)。
3823
- // **カードごと作り直さない。** 上の msg に出した結果表示が消えてしまう
3824
- if (opts.pending) opts.pending(next, wrap);
3825
3809
  });
3826
3810
  });
3827
3811
  wrap.appendChild(btn);
@@ -3949,123 +3933,14 @@ ${capabilityState.toString()}
3949
3933
  }
3950
3934
 
3951
3935
  /* ---------------------------------------------------------------------------
3952
- * Gold切り替えの「反映中」表示。
3953
- *
3954
- * 押しても一覧のバッジが変わるのは fleet-status.yml が完走したあと(1〜2分)。
3955
- * その間バッジが古いままだと「押したのに何も起きない」に見える。
3936
+ * Gold 切り替えの「(画面反映待ち)」バッジ(pendingGold 一式)は**撤去した**。
3956
3937
  *
3957
- * **推測ではない。** Lambda は書き込む前に Bot の在籍を確認し、ダメなら 400 を返すので、
3958
- * 200 が返った時点で channels.json は確実に書き換わっている。ここで出すのは
3959
- * 「確定した事実の先出し」であって楽観的な当て推量ではない。
3938
+ * チャンネル単位の Gold トグルが無くなったので、先出しする値が無い。残っている
3939
+ * sessionStorage(cortex.pendingGold:案件キー)は読み手が消えるだけなので無害。
3960
3940
  *
3961
- * **確定したバッジと同じ見た目にはしない。** 30秒後にリロードした人には、付けたはずの
3962
- * ラベルが消えて見える——「変えたはずなのに違う値が出ている」という、この画面が
3963
- * 繰り返し警告してきた誤読そのものになる。だから (反映中) を付けて区別し、
3964
- * さらに sessionStorage に持たせてリロードしても残す。
3965
- *
3966
- * 消えるのは「fleet-status が追いついたとき」か「TTL 切れ」。TTL が無いと、
3967
- * ワークフローが落ちている案件で永久に「反映中」が残り、嘘の表示になる。
3941
+ * **削除の結果は行の取り消し線で示す**(removeButton の作法)。一覧は夜間
3942
+ * スナップショットから描かれるので、消したはずの行が翌朝まで残るのが正しい状態。
3968
3943
  * ------------------------------------------------------------------------- */
3969
- // **キーは案件別にする。** Slackの共有チャンネルは複数案件のCortexに同時に載りうるので、
3970
- // 案件をまたぐキーにすると、案件Aで押した「反映中」が案件Bの同じ行に出る
3971
- // (チュートリアルの既読キーが同じ理由で案件別になっている)。
3972
- var PENDING_KEY = "cortex.pendingGold:" + ((SITE.intake && SITE.intake.projectKey) || "");
3973
- var PENDING_TTL_MS = 15 * 60 * 1000;
3974
- /**
3975
- * 保存する形: { "<チャンネルID>": { value: boolean, at: number } }
3976
- * at が無い/0 のエントリは期限切れとして落とす。
3977
- * **型では守れない**(このコードは render.ts のテンプレートリテラル内で、tsc が見ない)。
3978
- * 守るのはテストの仕事。
3979
- */
3980
-
3981
- /**
3982
- * 先出しバッジの見た目。**1箇所に集約する**——文言とクラスを2箇所に書くと片方だけ直る。
3983
- *
3984
- * **「反映中」とは書かない。** この画面の既存語彙では「反映」が2つの意味で使われていて、
3985
- * 短いほう(数分)には必ず「一覧への」が付いている(サーバ文言「次回の Gold 昇格
3986
- * (毎晩22時ごろ)から反映されます。一覧への反映には数分かかります」)。修飾なしの
3987
- * 「反映中」だと、22時の蒸留の話と読めてしまう。
3988
- *
3989
- * とくに OFF 方向で害が大きい。**止めた時点で蒸留に対しては完全に効いている**
3990
- * (Slack はミラーせず Gold 昇格が直接読むため)。待っているのは画面だけなので、
3991
- * 「除外(反映中)」は「まだ除外されていない」と逆に読める。
3992
- */
3993
- function pendingBadgeSpec(value, settled) {
3994
- return {
3995
- cls: "badge " + (value ? "ds-gold" : "ds-excluded") + (settled ? "" : " is-pending"),
3996
- text: (value ? "Gold昇格の読み取り対象" : "除外") + (settled ? "" : "(画面反映待ち)"),
3997
- };
3998
- }
3999
-
4000
- function readPending() {
4001
- try {
4002
- var raw = sessionStorage.getItem(PENDING_KEY);
4003
- if (!raw) return {};
4004
- var obj = JSON.parse(raw);
4005
- if (!obj || typeof obj !== "object") return {};
4006
- var now = Date.now();
4007
- var out = {};
4008
- for (var k in obj) {
4009
- if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
4010
- var e = obj[k];
4011
- if (e && typeof e === "object" && now - (e.at || 0) < PENDING_TTL_MS) out[k] = e;
4012
- }
4013
- return out;
4014
- } catch (_) {
4015
- // sessionStorage が使えない環境(プライベートモード等)でも画面は出す
4016
- return {};
4017
- }
4018
- }
4019
- function writePending(map) {
4020
- try { sessionStorage.setItem(PENDING_KEY, JSON.stringify(map)); } catch (_) {}
4021
- }
4022
- function clearPendingGold(key) {
4023
- if (!key) return;
4024
- var map = readPending();
4025
- if (!(key in map)) return;
4026
- delete map[key];
4027
- writePending(map);
4028
- }
4029
- /**
4030
- * 控えの鍵。**チャンネルIDだけでは足りない(#104)。**
4031
- * IDはワークスペースの中でしか一意でないので、同じIDのチャンネルを2つのワークスペースに
4032
- * 持つ案件では、片方で押した「反映中」が**もう片方の行にも出る**。
4033
- */
4034
- function pendingKeyOf(s) {
4035
- if (!s || !s.ref) return "";
4036
- return s.workspace ? s.workspace + "\t" + s.ref : s.ref;
4037
- }
4038
- /** 押した内容を控える。fleet-status が追いつくまでの間だけ効く */
4039
- function markPendingGold(key, value) {
4040
- if (!key) return;
4041
- var map = readPending();
4042
- map[key] = { value: value === true, at: Date.now() };
4043
- writePending(map);
4044
- }
4045
- /**
4046
- * その行の「反映中」を引く。**実データが追いついていたら控えを捨てる。**
4047
- * 捨てないと、正しくなった後も (反映中) が残り続ける。
4048
- */
4049
- function pendingGoldFor(s) {
4050
- var key = pendingKeyOf(s);
4051
- if (!key) return null;
4052
- var map = readPending();
4053
- var e = map[key];
4054
- if (!e) return null;
4055
- // **「値が一致したら捨てる」だけでは足りない。** 別の人が直後に逆を押して、
4056
- // その結果で追いついた場合、値が一致しないので控えが残り、TTL切れまで
4057
- // 実際とは違う値を出し続ける。**押した時点より新しいスナップショットが来たら、
4058
- // 値が何であれ控えは用済み**——サーバの答えのほうが新しいので、そちらを信じる。
4059
- var snapshot = fleetVal("generatedAt");
4060
- var snapAt = snapshot ? Date.parse(snapshot) : NaN;
4061
- var stale = !isNaN(snapAt) && snapAt >= (e.at || 0);
4062
- if (s.gold === e.value || stale) {
4063
- delete map[s.ref];
4064
- writePending(map);
4065
- return null;
4066
- }
4067
- return e;
4068
- }
4069
3944
 
4070
3945
  /**
4071
3946
  * 登録の由来の表示(#118)。値は fleet-status.json の origin
@@ -4112,16 +3987,9 @@ ${capabilityState.toString()}
4112
3987
 
4113
3988
  function extSourceCard(s, meta) {
4114
3989
  var badges = el("div", { class: "ds-badges" });
4115
- var pending = pendingGoldFor(s);
4116
- // 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
4117
- // 判断されていないだけのものが「判断済み」に見えてしまう。
4118
- if (pending) {
4119
- var spec = pendingBadgeSpec(pending.value, false);
4120
- badges.appendChild(el("span", { class: spec.cls, text: spec.text }));
4121
- }
4122
- else if (s.gold === true) badges.appendChild(el("span", { class: "badge ds-gold", text: "Gold昇格の読み取り対象" }));
4123
- else if (s.goldState === "undeclared") badges.appendChild(el("span", { class: "badge ds-undeclared", text: "未宣言(対象外)" }));
4124
- else if (s.gold === false) badges.appendChild(el("span", { class: "badge ds-excluded", text: "除外" }));
3990
+ // **Gold 対象かのバッジは持たない。** 一覧に並ぶ Slack チャンネルは例外なく全て対象で、
3991
+ // 行ごとに「対象です」と書いても情報が増えない。github の除外は行のトグル
3992
+ // (「読み取りを始める/止める」)が向きで示す。
4125
3993
  if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
4126
3994
  // **どのワークスペースのチャンネルかを出す(#104)。** 同じチャンネル名が
4127
3995
  // 2つのワークスペースに並びうるので、これが無いと「どっちの #general か」が読めない。
@@ -4136,8 +4004,10 @@ ${capabilityState.toString()}
4136
4004
  // 登録の由来(#118)。**「登録から削除」が出る/出ないの理由がこれ。** 導出されたリポは
4137
4005
  // 登録ファイルから消しても翌日また現れるので削除を出していないが、画面には
4138
4006
  // 「同じ見た目の行なのに、片方にだけ削除がある」としか映らず、毎回考え直すことになる。
4139
- // **GitHub の行だけに出す。** 由来で出し分けているのはここだけだから——Slack
4140
- // channels.json 由来の derived だが、あちらに削除は無く、由来を見せても打つ手が増えない。
4007
+ // **GitHub の行だけに出す。** ORIGIN_HELP の文面がリポジトリと .gitmodules に
4008
+ // 特化していて、Slack に出すと嘘になる。Slack も由来で「登録を解除」を出し分ける
4009
+ // ようになった(explicit には出さない)が、Slack で explicit なのは登録ファイルに
4010
+ // 直接書いた例外的な行だけで、バッジのために文面をもう一組増やすには釣り合わない。
4141
4011
  // #119 以降、新しいデータの github は必ず explicit(詳細は ORIGIN_LABEL)
4142
4012
  if (String(s.type).indexOf("github") === 0 && ORIGIN_LABEL[s.origin]) {
4143
4013
  badges.appendChild(el("span", {
@@ -4156,10 +4026,16 @@ ${capabilityState.toString()}
4156
4026
  badges,
4157
4027
  el("span", { class: "ds-gate " + gate.cls, text: gate.text }),
4158
4028
  ]);
4159
- // Gold昇格の切り替え。書き込み経路(intake Lambda)がある案件でだけ出す。
4160
- // Slack以外は読み手が導出しないので出さない(押せても効かないボタンは出さない)。
4161
- if (SITE.intake && s.type === "slack" && s.ref) {
4162
- card.appendChild(goldToggle(s));
4029
+ // チャンネルの登録解除。書き込み経路(intake Lambda)がある案件でだけ出す。
4030
+ // **Slack だけ。** github は登録ファイルが別(dev-sources.json)で、下のトグルと
4031
+ // 「登録から削除」が担う。
4032
+ //
4033
+ // **explicit(=dev-sources.json だけに登録された行)には出さない。** 解除が書き換える
4034
+ // のは channels.json なので、そこに無い行は押しても必ず 404 になる。GitHub 側が
4035
+ // 同じ事故(押しても戻ってくる削除)を origin で避けているのと同じ理由で、向きが逆
4036
+ // ——Slack は channels.json 由来が derived なので、出すのは derived と both。
4037
+ if (SITE.intake && s.type === "slack" && s.ref && s.origin !== "explicit") {
4038
+ card.appendChild(removeChannelButton(s, card));
4163
4039
  }
4164
4040
  // GitHubリポの読み取り ON/OFF。OFF は exclude で表現される(導出されたリポも確実に落ちる)。
4165
4041
  if (SITE.intake && s.type.indexOf("github") === 0 && s.ref) {
@@ -4803,8 +4679,8 @@ ${capabilityState.toString()}
4803
4679
  * モーダルを app に載せて、中へフォーカスを移す。
4804
4680
  *
4805
4681
  * **「最初の focusable」に載せてはいけない。** 一覧の先頭に来るのは
4806
- * 「Gold昇格を止める」「読み取りを止める」「このファイルを外す」といった
4807
- * 設定を書き換えるボタンで、しかも**狭める向きは意図的に確認を出さない**
4682
+ * 「読み取りを止める」「このファイルを外す」といった設定を書き換えるボタンで、
4683
+ * しかも**狭める向きは意図的に確認を出さない**
4808
4684
  * (stateToggle の confirmOn は広げる向きだけ)。タイルを Enter で開いて
4809
4685
  * もう一度 Enter を押しただけで、確認なしに設定ファイルが書き換わる。
4810
4686
  * 移す先は閉じるボタン——押しても何も壊れないものにする。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.80",
3
+ "version": "0.4.81",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,