@takagaki/cortex-decisions-viewer 0.12.165 → 0.12.167

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 +68 -30
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -5140,7 +5140,7 @@ a.pager-btn:hover { text-decoration: none; }
5140
5140
  使っていない)ので、暗色側の指定は置かない。押したあとの見た目は「取り消し線+無効化」 */
5141
5141
  .record-delete-btn {
5142
5142
  flex: 0 0 auto; cursor: pointer; white-space: nowrap;
5143
- padding: 8px 14px; border-radius: var(--radius);
5143
+ padding: 8px 18px; border-radius: 999px;
5144
5144
  border: 1px solid #fca5a5; background: #fff; color: #b91c1c;
5145
5145
  font-size: 13.5px; font-weight: 600;
5146
5146
  }
@@ -5153,7 +5153,8 @@ a.pager-btn:hover { text-decoration: none; }
5153
5153
  .record-delete-msg.is-error { color: #b91c1c; }
5154
5154
  /* 帯の中では1行を占める(ボタンの並びの下へ回り込ませる)。操作列では右寄せのまま畳む */
5155
5155
  .draft-note > .record-delete-msg { flex: 1 0 100%; }
5156
- .detail-actions .record-delete-msg { max-width: 320px; text-align: right; }
5156
+ /* ページ最下部の削除の口(レコード情報の下)。結果の行はボタンの下 */
5157
+ .record-delete-foot { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 24px 0 8px; }
5157
5158
  .deleted-note {
5158
5159
  display: flex; gap: 8px; align-items: flex-start; margin: 4px 0 16px;
5159
5160
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
@@ -6762,7 +6763,7 @@ ${story_1.splitByNameLinks.toString()}
6762
6763
  // **削除もここに並べる(2026-08-28 のオーナー裁定)。** AIが起票した記録に対して
6763
6764
  // 人がやることは「引き取る(確認済みにする)」か「要らないと言う(削除)」の二択で、
6764
6765
  // 同じ場面の同じ判断なので、口を離さない。**この帯は draft にしか出ない**ので、
6765
- // draft でない記録の削除の口は操作列の末尾(attachDeleteToEditRow)に置く。
6766
+ // draft でない記録の削除の口はページの最下部(appendDeleteFoot)に置く。
6766
6767
  function draftNote(rec) {
6767
6768
  if (!rec || !unconfirmed(rec)) return null;
6768
6769
  var path = promotableRecordPath(rec);
@@ -7075,7 +7076,7 @@ ${story_1.splitByNameLinks.toString()}
7075
7076
  }
7076
7077
  /** 筋の札(一覧の行)。札が無ければ null */
7077
7078
  function strandBadge(rec) {
7078
- return hasStrand(rec) ? el("span", { class: "badge strand", title: "筋", text: rec.strand }) : null;
7079
+ return hasStrand(rec) ? el("span", { class: "badge strand", title: "論点", text: rec.strand }) : null;
7079
7080
  }
7080
7081
  var FIRMNESS_CLASS = { "確定": "fixed", "仮": "tentative", "検討中": "open" };
7081
7082
  /** 固さの小札。語彙外は出さない(ビルド側でも捨てている・二重の網) */
@@ -18900,7 +18901,7 @@ ${story_1.splitByNameLinks.toString()}
18900
18901
  // 筋の札(#1094)。札を持つ決定が 1 件も無い案件では出さない
18901
18902
  var strandOpts = strandOptions(visibleRecords(SITE.decisions));
18902
18903
  if (strandOpts.length) {
18903
- var strandSel = buildSelect("すべての筋", strandOpts, listState.strand, function (v) { listState.strand = v; listState.page = 1; drawCards(); });
18904
+ var strandSel = buildSelect("すべての論点", strandOpts, listState.strand, function (v) { listState.strand = v; listState.page = 1; drawCards(); });
18904
18905
  strandSel.classList.add("strand-filter");
18905
18906
  filters.appendChild(strandSel);
18906
18907
  }
@@ -19218,6 +19219,9 @@ ${story_1.splitByNameLinks.toString()}
19218
19219
  //(acme.slack.com の acme の部分。fleet-status.json の slackWorkspaces[].domain)
19219
19220
  var LI_SLACK_USER = /^[UW][A-Z0-9]{2,20}$/;
19220
19221
  var LI_SLACK_DOMAIN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
19222
+ // エンタープライズの組織は classmethod.enterprise.slack.com のように「<組織>.enterprise」の形(台帳の domain には入らない)
19223
+ var LI_SLACK_ENT_DOMAIN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.enterprise$/i;
19224
+ var LI_SLACK_CHANNEL_HOST = /^https:\\/\\/([a-z0-9.-]{1,200})\\.slack\\.com\\//i;
19221
19225
  var LI_SLACK_TEAM = /^[TE][A-Z0-9]{2,20}$/;
19222
19226
  // Backlog のユーザー(engine は userId=ログイン名、無い相手は数値 id を入れる)と、
19223
19227
  // スペースのドメイン(fleet の backlogTarget。正本は 課題管理/issues-config.json)。
@@ -19273,8 +19277,9 @@ ${story_1.splitByNameLinks.toString()}
19273
19277
  * 場が team_id の形なのにどれとも一致しなければ**リンク無し**
19274
19278
  * 2. そうでなく、接続済みワークスペースが**ちょうど 1 つ**ならそれ
19275
19279
  * 3. それ以外(0 個・2 個以上)は**リンク無し**
19276
- * 🔴 **チャンネルの URL のホスト名から推測しない**(改名で変わるし、共有チャンネルでは
19277
- * 貼った人のホストになる。Slack の節と同じ作法)。**間違ったリンクは値だけより悪い**
19280
+ * 🔴 **チャンネルの URL のホストは、台帳にドメインが無いときの最後の手だけ**(liSlackChannelHost。
19281
+ * 登録されたチャンネルの全部が同じホストのときだけ採る。共有チャンネルでは貼った人のホストに
19282
+ * なりうるので、食い違えばリンク無し)。**間違ったリンクは値だけより悪い**
19278
19283
  */
19279
19284
  function liSlackDomain(scope) {
19280
19285
  var list = slackWorkspaces() || [];
@@ -19290,7 +19295,33 @@ ${story_1.splitByNameLinks.toString()}
19290
19295
  }
19291
19296
  if (!pick && list.length === 1) pick = list[0];
19292
19297
  var d = pick && typeof pick.domain === "string" ? pick.domain.trim() : "";
19293
- return LI_SLACK_DOMAIN.test(d) ? d : "";
19298
+ if (LI_SLACK_DOMAIN.test(d) || LI_SLACK_ENT_DOMAIN.test(d)) return d;
19299
+ // 台帳にドメインが無い(auth.test の url が点を含む組織のアドレスのとき等)ときは、
19300
+ // 案件に登録されたチャンネルの URL のホストから補う(liSlackChannelHost)
19301
+ return liSlackChannelHost(pick ? pick.teamId : "", !pick && list.length > 0);
19302
+ }
19303
+ /**
19304
+ * 登録済みの Slack チャンネル(fleet の externalSources)の URL から、ワークスペースのドメインを引く。
19305
+ *
19306
+ * 🔴 **登録されたチャンネルの URL が全部同じホストを指すときだけ**採る(共有チャンネルの
19307
+ * 貼り付け元のホストが混ざる案件では決められない=リンク無し)。ワークスペースが決まっているなら、
19308
+ * そのワークスペースのチャンネル(workspace が一致するもの)に絞る。**ワークスペースが複数あるのに
19309
+ * 決まっていない行(skipAll)は引かない**。ホストは slack.com の配下の https だけ
19310
+ */
19311
+ function liSlackChannelHost(teamId, skipAll) {
19312
+ if (skipAll) return "";
19313
+ var srcs = (fleetVal("externalSources") || []).filter(function (x) {
19314
+ return x && x.type === "slack" && typeof x.url === "string" && (!teamId || x.workspace === teamId);
19315
+ });
19316
+ var host = "";
19317
+ for (var i = 0; i < srcs.length; i++) {
19318
+ var m = LI_SLACK_CHANNEL_HOST.exec(srcs[i].url);
19319
+ var h = m ? m[1].toLowerCase() : "";
19320
+ if (!LI_SLACK_DOMAIN.test(h) && !LI_SLACK_ENT_DOMAIN.test(h)) return "";
19321
+ if (host && host !== h) return "";
19322
+ host = h;
19323
+ }
19324
+ return host;
19294
19325
  }
19295
19326
  /**
19296
19327
  * Backlog のユーザーのページを組むスペースのドメイン(決められなければ空文字)。
@@ -19528,7 +19559,7 @@ ${story_1.splitByNameLinks.toString()}
19528
19559
  rec.deleted = true;
19529
19560
  var title = app.querySelector(".detail-title");
19530
19561
  if (title) title.classList.add("is-deleted");
19531
- // **置き場は2つある**(draft の警告帯の中/上の操作列)。どちらに出ていても止める
19562
+ // **置き場は2つある**(draft の警告帯の中/ページの最下部)。どちらに出ていても止める
19532
19563
  var btns = app.querySelectorAll(".record-delete-btn");
19533
19564
  for (var b = 0; b < btns.length; b++) { btns[b].disabled = true; btns[b].textContent = "削除しました"; }
19534
19565
  // もう無い記録を直す口は押させない(AIで編集・GitHubで編集・確認済みにする)
@@ -19603,7 +19634,7 @@ ${story_1.splitByNameLinks.toString()}
19603
19634
  *
19604
19635
  * 呼び出し側は2つ:
19605
19636
  * - draftNote …… 未確認の記録。「確認済みにする」と並べて警告帯の中に置く
19606
- * - 詳細ページの操作列 …… 警告帯が出ない記録。置き場が他に無いのでここに寄せる
19637
+ * - 詳細ページの最下部(appendDeleteFoot) …… 警告帯が出ない記録
19607
19638
  */
19608
19639
  function buildDeleteControls(rec) {
19609
19640
  var path = deletableRecordPath(rec);
@@ -19623,19 +19654,18 @@ ${story_1.splitByNameLinks.toString()}
19623
19654
  return { btn: btn, msg: msg };
19624
19655
  }
19625
19656
  /**
19626
- * 警告帯が出ない記録の削除の口を、上の操作列(AIで編集・GitHubで編集の行)の末尾へ置く。
19657
+ * 警告帯が出ない記録の削除の口を、ページの一番下(「レコード情報(frontmatter)」の下・
19658
+ * 生成の行の上)へ置く。取り消せない操作を、高頻度の操作(AIで編集・GitHubで編集)の
19659
+ * 隣に置かない(2026-10-18 のオーナー指摘)。4 区画の詳細で同じ。
19627
19660
  *
19628
19661
  * **draft には出さない**——そちらは警告帯の中で「確認済みにする」と対になっている
19629
- * (2つ出すと、同じ操作の口がページに2か所できる)。取り消せない操作を高頻度の
19630
- * 操作の隣に置くことにはなるが、**警告帯の無い記録には他に置き場が無い**。
19631
- * 見た目は控えめな文字のボタンのままにして、隣の実体のあるボタンと重さを分ける。
19662
+ * (2つ出すと、同じ操作の口がページに2か所できる)。
19632
19663
  */
19633
- function attachDeleteToEditRow(rec, editRow) {
19634
- if (!rec || unconfirmed(rec)) return null;
19664
+ function appendDeleteFoot(rec) {
19665
+ if (!rec || unconfirmed(rec)) return;
19635
19666
  var del = buildDeleteControls(rec);
19636
- if (!del) return null;
19637
- editRow.appendChild(del.btn);
19638
- return del.msg; // 結果を出す行は操作列の下(呼び出し側が最後に並べる)
19667
+ if (!del) return;
19668
+ app.appendChild(el("div", { class: "record-delete-foot" }, [del.btn, del.msg]));
19639
19669
  }
19640
19670
  /** 詳細ページの末尾(本文・参照の後)に、つながっている ID・AIの変更履歴・frontmatter を並べる */
19641
19671
  function appendRecordFooter(rec) {
@@ -19646,6 +19676,7 @@ ${story_1.splitByNameLinks.toString()}
19646
19676
  if (history) app.appendChild(history);
19647
19677
  var fmp = renderFmPanel(rec.fm);
19648
19678
  if (fmp) app.appendChild(fmp);
19679
+ appendDeleteFoot(rec);
19649
19680
  // 開き直しても削除済みのまま見せる(印は rec が持っている)
19650
19681
  if (rec.deleted) markRecordDeleted(rec);
19651
19682
  }
@@ -19882,12 +19913,9 @@ ${story_1.splitByNameLinks.toString()}
19882
19913
  if (r.editUrl) {
19883
19914
  editRowR.appendChild(el("a", { class: "edit-btn", href: r.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
19884
19915
  }
19885
- // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
19886
- var delMsgR = attachDeleteToEditRow(r, editRowR);
19887
19916
  head.appendChild(el("div", { class: "detail-actions" }, [
19888
19917
  el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
19889
19918
  editRowR.children.length ? editRowR : null,
19890
- delMsgR,
19891
19919
  ]));
19892
19920
  app.appendChild(head);
19893
19921
 
@@ -20774,12 +20802,9 @@ ${story_1.splitByNameLinks.toString()}
20774
20802
  if (d.editUrl) {
20775
20803
  editRowD.appendChild(el("a", { class: "edit-btn", href: d.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
20776
20804
  }
20777
- // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
20778
- var delMsgD = attachDeleteToEditRow(d, editRowD);
20779
20805
  head.appendChild(el("div", { class: "detail-actions" }, [
20780
20806
  el("div", { class: "detail-actions-row" }, [ traceBtn ]),
20781
20807
  editRowD.children.length ? editRowD : null,
20782
- delMsgD,
20783
20808
  ]));
20784
20809
  app.appendChild(head);
20785
20810
 
@@ -20805,7 +20830,7 @@ ${story_1.splitByNameLinks.toString()}
20805
20830
 
20806
20831
  // 筋(#1094)。札の無い決定では何も作らない
20807
20832
  if (hasStrand(d)) {
20808
- app.appendChild(el("div", { class: "section-label", text: "筋" }));
20833
+ app.appendChild(el("div", { class: "section-label", text: "論点" }));
20809
20834
  app.appendChild(el("div", { class: "decision-strand" }, [el("span", { class: "chip", text: d.strand })]));
20810
20835
  }
20811
20836
 
@@ -21156,6 +21181,12 @@ ${story_1.splitByNameLinks.toString()}
21156
21181
  if (isHttp(target)) return el("a", { href: target, target: "_blank", rel: "noopener", text: target });
21157
21182
  return el("span", { text: target });
21158
21183
  }
21184
+ /** 値が http/https の URL 1 つだけ(前後の空白は落とす・空白を含むものは URL としない)なら、その URL。それ以外は空文字 */
21185
+ function fmHttpUrl(x) {
21186
+ if (typeof x !== "string") return "";
21187
+ var t = x.trim();
21188
+ return isHttp(t) && !/\\s/.test(t) ? t : "";
21189
+ }
21159
21190
  var FM_EMPTY_TEXT = "(未記入)";
21160
21191
  /** パネルの 1 値を文字にする。オブジェクト・配列は JSON([object Object] を出さない) */
21161
21192
  function fmScalarText(x) {
@@ -21211,14 +21242,21 @@ ${story_1.splitByNameLinks.toString()}
21211
21242
  cell.appendChild(line);
21212
21243
  });
21213
21244
  } else if (Array.isArray(v)) {
21214
- // 🔴 要素がオブジェクトなら JSON で出す(join に落とすと [object Object] になる)
21215
- cell.textContent = v.map(fmScalarText).join(" / ");
21245
+ // 🔴 要素がオブジェクトなら JSON で出す(join に落とすと [object Object] になる)。
21246
+ // http/https の URL だけの要素はリンクにする(新しいタブ・それ以外の綴りは文字のまま)
21247
+ v.forEach(function (x, i) {
21248
+ if (i > 0) cell.appendChild(document.createTextNode(" / "));
21249
+ var u = fmHttpUrl(x);
21250
+ cell.appendChild(u
21251
+ ? el("a", { href: u, target: "_blank", rel: "noopener", text: u })
21252
+ : el("span", { text: fmScalarText(x) }));
21253
+ });
21216
21254
  } else if (v && typeof v === "object") {
21217
21255
  cell.textContent = fmScalarText(v);
21218
21256
  } else if (k === "source") {
21219
21257
  cell.appendChild(fmTargetLink(String(v)));
21220
- } else if (isHttp(v)) {
21221
- cell.appendChild(el("a", { href: v, target: "_blank", rel: "noopener", text: v }));
21258
+ } else if (fmHttpUrl(v)) {
21259
+ cell.appendChild(el("a", { href: fmHttpUrl(v), target: "_blank", rel: "noopener", text: fmHttpUrl(v) }));
21222
21260
  } else {
21223
21261
  cell.textContent = String(v);
21224
21262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.165",
3
+ "version": "0.12.167",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,