@takagaki/cortex-decisions-viewer 0.12.0 → 0.12.2

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 +137 -4
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -517,9 +517,14 @@ const REGISTERED_CAP_NAMES = {
517
517
  materials: "共有資料",
518
518
  issues: "課題管理",
519
519
  };
520
- /** 通してよい欄。**ここに無い欄は1つも通さない**(Lambda 側の列挙と同じ形) */
520
+ /**
521
+ * 通してよい欄。**ここに無い欄は1つも通さない**(Lambda 側の列挙と同じ形)
522
+ *
523
+ * 🔴 **`orgUnknown` は真偽値**(他の欄は全部文字列)。**この1欄だけの例外**で、
524
+ * 篩の側にも `=== true` の分岐がある——`sanitizeRegisteredNow` を参照
525
+ */
521
526
  const REGISTERED_FIELDS = {
522
- chat: ["ref", "name", "workspace"],
527
+ chat: ["ref", "name", "workspace", "orgUnknown"],
523
528
  dev: ["ref", "type"],
524
529
  design: ["key", "name"],
525
530
  materials: ["id", "name"],
@@ -559,6 +564,17 @@ function sanitizeRegisteredNow(v) {
559
564
  const item = {};
560
565
  for (const f of REGISTERED_FIELDS[cap]) {
561
566
  const fv = r[f];
567
+ // 🔴 **真偽値を通すのは orgUnknown だけ**(2026-08-25)。葉を文字列に限った篩が
568
+ // この印を落としており、**追加した直後のチャンネルから「Slack ワークスペースに
569
+ // 接続する」の導線が消えていた**(顧客Slackを繋ぐ唯一の入口)。
570
+ // **`=== true` に限る。** 文字列の "true" や 1 を真と読むと、意図しない値が
571
+ // 「組織不明」の案内を呼び、直すものが無い人に接続を勧めることになる。
572
+ // 例外はこの1欄だけ——他の欄まで真偽値を通すと、許可リストが篩の意味を失う
573
+ if (f === "orgUnknown") {
574
+ if (fv === true)
575
+ item[f] = true;
576
+ continue;
577
+ }
562
578
  if (typeof fv !== "string" || fv === "")
563
579
  continue; // 葉は文字列だけ
564
580
  item[f] = fv;
@@ -668,6 +684,10 @@ function overlayStateOnFleet(view, rows, generatedAt) {
668
684
  * 🔴 **鍵はチャンネルID+ワークスペース。** チャンネルIDはワークスペースの中でしか
669
685
  * 一意でないので、IDだけで突き合わせると別ワークスペースの同IDを同じものと見て
670
686
  * 行が出ない(#104 で同じ理由の事故がある)。
687
+ *
688
+ * 🔴 **`orgUnknown` の印をここで落とさない(2026-08-25)。** 落とすと、この関数が作る行
689
+ * (=追加した直後の主役)が `groupSlackByOrg` で社内の組に落ち、
690
+ * **「Slack ワークスペースに接続する」が画面から消える**——顧客Slackを繋ぐ唯一の入口。
671
691
  */
672
692
  function chatRowsToAdd(externals, rows) {
673
693
  const have = {};
@@ -685,6 +705,8 @@ function chatRowsToAdd(externals, rows) {
685
705
  const row = { type: "slack", name: r.name || r.ref, ref: r.ref };
686
706
  if (r.workspace)
687
707
  row.workspace = r.workspace;
708
+ if (r.orgUnknown === true)
709
+ row.orgUnknown = true;
688
710
  out.push(row);
689
711
  }
690
712
  return out;
@@ -2438,6 +2460,10 @@ a.ds-name:hover { text-decoration: underline; }
2438
2460
  min-width: 0; white-space: normal; overflow-wrap: anywhere;
2439
2461
  }
2440
2462
  .ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
2463
+ /* 常設の接続の入口に添える「なぜ押すのか」(0.12.2)。**必ず自分の行を占める**
2464
+ (flex: 1 1 100%)——.ds-toggle は横並びなので、隣の「誰が承認できるか」の説明と
2465
+ 横に並ぶと、どちらも半分の幅に潰れる(.ds-gate-help と同じ理由) */
2466
+ .ds-connect-lead { flex: 1 1 100%; }
2441
2467
  .ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
2442
2468
  .ds-toggle-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
2443
2469
  .ds-toggle-btn:disabled { opacity: .6; cursor: default; }
@@ -2507,6 +2533,12 @@ a.ds-name:hover { text-decoration: underline; }
2507
2533
  .ds-probe-at { font-size: 11.5px; color: var(--muted); }
2508
2534
  .ds-probe-help { margin: 6px 0 0; font-size: 12px; line-height: 1.7; color: #6b7280;
2509
2535
  min-width: 0; white-space: normal; overflow-wrap: anywhere; }
2536
+ /* 「アクセス不可」の原因の候補(0.12.2)。**箇条書きにする**——候補が3つあるので、
2537
+ 一続きの文章にすると読む人が「自分がどれなのか」を自分で切り出すことになる
2538
+ (組織不明の案内が同じ形で並べているのと同じ理由)。
2539
+ 色と大きさは .ds-probe-help を併せて付けることで他の本文に揃える */
2540
+ .ds-probe-why { padding-left: 1.25em; list-style: disc; }
2541
+ .ds-probe-why li { margin: 2px 0; }
2510
2542
  /* Slack が名指しした不足スコープ。**そのまま読める形で出す**(言い換えると検索できない) */
2511
2543
  .ds-probe-scope { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: #fef3c7;
2512
2544
  border: 1px solid #fde68a; border-radius: 6px; padding: 1px 6px; user-select: all; }
@@ -3625,8 +3657,17 @@ ${deriveCapTile.toString()}
3625
3657
  * お客様の Slack なら、クラスメソッドの人間は押せないことが多い
3626
3658
  * (その Slack の管理者にお願いすることになる)。それを書かないと、
3627
3659
  * 押しても進まない人が「壊れている」と読む。
3660
+ *
3661
+ * ## 常設の入口として置くときは lead を渡す(0.12.2)
3662
+ *
3663
+ * 「組織不明」の案内の中では、直前の箇条書きが**なぜ接続するのか**を言っている。
3664
+ * 追加フォームの後に単独で置くときは**その文脈が無い**ので、押す理由が画面から
3665
+ * 読み取れない——下の説明文は「誰が承認できるか」の話しかしていない。
3666
+ * 1行だけ添える口を開けておく。**見出しは付けない**(節を増やすと一覧が遠くなる)。
3667
+ *
3668
+ * @param lead 省略可。押す理由の1行(案内の中から呼ぶときは渡さない)
3628
3669
  */
3629
- function slackConnectButton() {
3670
+ function slackConnectButton(lead) {
3630
3671
  var wrap = el("div", { class: "ds-toggle" });
3631
3672
  // **「この」を付けない(2026-08-20・オーナー指摘)。**
3632
3673
  // どのワークスペースを指すのか読み取れない——そもそも**指定できない**
@@ -3664,6 +3705,9 @@ ${deriveCapTile.toString()}
3664
3705
  btn.disabled = false;
3665
3706
  }
3666
3707
  });
3708
+ // **lead は「なぜ押すのか」・下の説明は「誰が押せるのか」。** 別のことを言っているので
3709
+ // 1文にまとめない(まとめると、どちらの話なのか読み取れない長文になる)
3710
+ if (lead) wrap.appendChild(el("p", { class: "ds-group-note ds-connect-lead", text: lead }));
3667
3711
  wrap.appendChild(el("p", {
3668
3712
  class: "ds-group-note",
3669
3713
  text: "ボタンを押すと Slack の承認画面が開きます。承認できるのは、その Slack の管理者だけです"
@@ -5419,6 +5463,40 @@ ${deriveCapTile.toString()}
5419
5463
  // 読み取れていない組として現れ、そこに接続ボタンが出る。
5420
5464
  // 0件なら繋ぐ相手もまだ無いので、入口が要る場面にならない。
5421
5465
  pushCap("チャット", adders.querySelector('[data-adder="slack"]'));
5466
+ /* -------------------------------------------------------------------------
5467
+ * 🔴 接続の入口を常設する(0.12.2・実オンボーディングで踏んだ)
5468
+ * -------------------------------------------------------------------------
5469
+ * 直前の版まで、このボタンは**「組織不明」の案内の中にしか無かった**。
5470
+ * 2026-08-20 に独立した節をやめたときの理屈は「チャンネルを追加すれば
5471
+ * 読み取れていない組として現れ、そこに接続ボタンが出る」だったが、
5472
+ * **印が付かない経路で登録された行には、そこへ行く道が1本も無い**:
5473
+ *
5474
+ * - channels.json に手で書き足した行は orgUnknown を持たない
5475
+ * (印を載せるのはサーバの登録経路だけ)ので、お客様の Slack の
5476
+ * チャンネルなのに「クラスメソッド」の並びに落ちる
5477
+ * - Slack コネクトの共有チャンネルは **URL がどちらの組織のドメインにもなりうる**
5478
+ * ので、そもそも分類が当てにならない。「クラスメソッド」に分類された行の
5479
+ * 正しい対処が接続(OAuth)のこともある(実測: 招待しようとしたら
5480
+ * 「組織外のユーザー」で断られた)
5481
+ *
5482
+ * **お客様の Slack かどうかは、行の状態から決まらない。** だから入口は
5483
+ * 行の状態と無関係に置く——**組織不明の組が1つも無くても出る**。
5484
+ *
5485
+ * ⚠ **「組織不明」の案内の中のボタンは残す。** あの文脈では、直前の箇条書きが
5486
+ * 「なぜ接続するのか」を言っており、次の一手として正しい。同じ関数を2箇所から
5487
+ * 呼ぶだけで、押したときの動きは1つ(承認の口も1つ)。
5488
+ *
5489
+ * **置き場所は追加フォームの後。** フォームの手順3が「組織不明と出たら…接続する」と
5490
+ * 言っているので、フォームより前に出すと読む順と操作の順が食い違う。
5491
+ *
5492
+ * **投入の口が無い構成では出さない。** 承認の開始は intake Lambda を叩くので、
5493
+ * 無い案件では押しても始まらない(renderExternalAdders も同じ条件で空を返す)。
5494
+ * ----------------------------------------------------------------------- */
5495
+ if (SITE.intake) {
5496
+ pushCap("チャット", slackConnectButton(
5497
+ "お客様の Slack や、Slack コネクトで共有されたチャンネルを読むには、先にワークスペースの接続が要ります。",
5498
+ ));
5499
+ }
5422
5500
  // **組織ごとの接続状況は、行と一体にした(2026-08-20・Slack からの横展開)。**
5423
5501
  //
5424
5502
  // それまでは「リポジトリの一覧」と「組織ごとの接続状況」が**別々に並んで**おり、
@@ -6103,6 +6181,59 @@ ${deriveCapTile.toString()}
6103
6181
  return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
6104
6182
  }
6105
6183
 
6184
+ /**
6185
+ * 「アクセス不可」の次の一手(0.12.2・実オンボーディングで踏んだ)。
6186
+ *
6187
+ * ## なぜ2つの導線を併記するのか
6188
+ *
6189
+ * それまでは「見つからないか、アーカイブされています。不要なら登録を解除」だけを
6190
+ * 出していた。**この行がまさに、まだ読めていない主役の行**なのに、
6191
+ * 生きているチャンネルを読めるようにする道が1本も書かれていなかった。
6192
+ *
6193
+ * 原因は**行の分類では絞れない**:
6194
+ *
6195
+ * - 非公開チャンネルは、招待されるまで「存在しない」と返る(招待で直る)
6196
+ * - Slack コネクトの共有チャンネルは、**URL がどちらの組織のドメインにもなりうる**。
6197
+ * だから「クラスメソッド」に分類された行の正しい対処が
6198
+ * **ワークスペース接続(OAuth)** のこともある
6199
+ * (実測: 招待しようとしたら「組織外のユーザー」で断られた)
6200
+ *
6201
+ * **どちらかに賭けて片方だけ出すと、外したほうの人は詰まる。** 招待だけを出すと
6202
+ * 何度招待しても直らないものを繰り返させ、接続だけを出すと招待で済む人に
6203
+ * 「直らない処方」を渡す。**両方出して、読む人に選ばせる**のが正しい
6204
+ * (組織不明の案内が同じ判断をしている)。
6205
+ *
6206
+ * ## 社内/顧客で出し分けない
6207
+ *
6208
+ * no_token は出し分けている(社内は画面から設定できるものが本当に無い)が、
6209
+ * こちらは**分類そのものが当てにならない**のが問題の本体なので、
6210
+ * 同じ理屈を持ち込むと直したい誤読を画面の側から作り直すことになる。
6211
+ */
6212
+ function slackUnreachableHelpNodes() {
6213
+ var out = [];
6214
+ out.push(el("p", { class: "ds-probe-help", text: "Cortex からこのチャンネルが見えませんでした。次のどれかです。" }));
6215
+ var ul = el("ul", { class: "ds-probe-help ds-probe-why" });
6216
+ // **押したあと何をするかまで書く。** 直したあと表示が変わらないと「効かなかった」と
6217
+ // 読まれる——この画面の一覧は夜間のスナップショットなので、その場で確かめる手段は
6218
+ // 「接続を確認」をもう一度押すこと以外に無い
6219
+ ul.appendChild(el("li", {
6220
+ text: "非公開チャンネル … 下のコマンドをそのチャンネルに貼って Cortex を招待してから、もう一度「接続を確認」を押してください",
6221
+ }));
6222
+ ul.appendChild(el("li", {
6223
+ text: "お客様の Slack、または Slack コネクトで共有されたチャンネル … "
6224
+ + "「クラスメソッド」と出ている行でもこちらのことがあります。"
6225
+ + "下の「Slack ワークスペースに接続する」から接続してから、もう一度「接続を確認」を押してください",
6226
+ }));
6227
+ ul.appendChild(el("li", {
6228
+ text: "アーカイブ・削除された、URL が違う … 「登録を解除」を押してください",
6229
+ }));
6230
+ out.push(ul);
6231
+ // **手で打たせない**(copyRow のコメント)。招待の候補を出したなら、
6232
+ // そのコマンドも同じ場所に無いと、貼る文字列を探しに行くことになる
6233
+ out.push(copyRow("/invite @Cortex"));
6234
+ return out;
6235
+ }
6236
+
6106
6237
  /**
6107
6238
  * 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
6108
6239
  *
@@ -6146,7 +6277,9 @@ ${deriveCapTile.toString()}
6146
6277
  : "「Slack ワークスペースに接続する」から接続してください。")];
6147
6278
  }
6148
6279
  if (r.gate === "unreachable") {
6149
- return [p("チャンネルが見つからないか、アーカイブされています。不要なら「登録を解除」してください。")];
6280
+ // **ここだけ本文が2つ以上になる(0.12.2)。** 原因が3つあり、しかも
6281
+ // 行の分類では絞れないので、候補と導線を並べる(関数側に理由がある)
6282
+ return slackUnreachableHelpNodes();
6150
6283
  }
6151
6284
  // unknown。**生のエラーは隠さない**——「不明」だけだと切り分けが始められない
6152
6285
  return [p("Slack の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。")];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,