@takagaki/cortex-decisions-viewer 0.12.164 → 0.12.166

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 +76 -35
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -2679,9 +2679,11 @@ function reflectedStateOf(rows, capId) {
2679
2679
  /**
2680
2680
  * ISO 時刻を「Gold の夜の日」の表示(`M/D`)にする(cortex-engine #968 設計 v1 §3)。
2681
2681
  *
2682
- * **engine の `nightDayOf`(−12 時間)と同じ切り方。** Gold は JST 22:00 に始まり日をまたいで
2683
- * 終わるので、素の日付だと同じ晩の記録が「9/28」と「9/29」に分かれる。12 時間引いてから
2684
- * JST の日付を読むと、22:00 に始まった晩はいつも開始した日の日付になる。
2682
+ * **engine の `NIGHT_CUTOFF_HOURS = 6`(`scripts/memory-v2/night-result.mjs`・日本時間 6 時)と
2683
+ * 同じ区切り。** Gold は JST 22:00 に始まり日をまたいで終わるので、素の日付だと同じ晩の記録が
2684
+ * 「9/28」と「9/29」に分かれる。6 時間引いてから JST の日付を読むと、JST 6:00 より前は前の日の
2685
+ * 晩、6:00 以降は当日の晩になる。🔴 区切りの数(6)はこの関数の `NIGHT_CUTOFF_HOURS` 1 か所だけに
2686
+ * 書く(engine と必ずそろえる。`.toString()` で渡すので関数の中に置く)。
2685
2687
  *
2686
2688
  * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
2687
2689
  * @returns `M/D`(ゼロ埋めなし)。読めない値は null
@@ -2691,10 +2693,11 @@ function jstNightLabel(iso) {
2691
2693
  if (!Number.isFinite(ms))
2692
2694
  return null;
2693
2695
  const JST_MS = 9 * 60 * 60 * 1000;
2694
- const d = new Date(ms - 12 * 60 * 60 * 1000 + JST_MS);
2696
+ const NIGHT_CUTOFF_HOURS = 6;
2697
+ const d = new Date(ms - NIGHT_CUTOFF_HOURS * 60 * 60 * 1000 + JST_MS);
2695
2698
  return (d.getUTCMonth() + 1) + "/" + d.getUTCDate();
2696
2699
  }
2697
- /** JST の暦日(`YYYY-MM-DD`)。`jstNightLabel` と違い −12 時間をしない(素の日付の比較用) */
2700
+ /** JST の暦日(`YYYY-MM-DD`)。`jstNightLabel` と違い夜の区切りで引かない(素の日付の比較用) */
2698
2701
  function jstCalendarDay(ms) {
2699
2702
  const JST_MS = 9 * 60 * 60 * 1000;
2700
2703
  const d = new Date(ms + JST_MS);
@@ -5137,7 +5140,7 @@ a.pager-btn:hover { text-decoration: none; }
5137
5140
  使っていない)ので、暗色側の指定は置かない。押したあとの見た目は「取り消し線+無効化」 */
5138
5141
  .record-delete-btn {
5139
5142
  flex: 0 0 auto; cursor: pointer; white-space: nowrap;
5140
- padding: 8px 14px; border-radius: var(--radius);
5143
+ padding: 8px 18px; border-radius: 999px;
5141
5144
  border: 1px solid #fca5a5; background: #fff; color: #b91c1c;
5142
5145
  font-size: 13.5px; font-weight: 600;
5143
5146
  }
@@ -5150,7 +5153,8 @@ a.pager-btn:hover { text-decoration: none; }
5150
5153
  .record-delete-msg.is-error { color: #b91c1c; }
5151
5154
  /* 帯の中では1行を占める(ボタンの並びの下へ回り込ませる)。操作列では右寄せのまま畳む */
5152
5155
  .draft-note > .record-delete-msg { flex: 1 0 100%; }
5153
- .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; }
5154
5158
  .deleted-note {
5155
5159
  display: flex; gap: 8px; align-items: flex-start; margin: 4px 0 16px;
5156
5160
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
@@ -6759,7 +6763,7 @@ ${story_1.splitByNameLinks.toString()}
6759
6763
  // **削除もここに並べる(2026-08-28 のオーナー裁定)。** AIが起票した記録に対して
6760
6764
  // 人がやることは「引き取る(確認済みにする)」か「要らないと言う(削除)」の二択で、
6761
6765
  // 同じ場面の同じ判断なので、口を離さない。**この帯は draft にしか出ない**ので、
6762
- // draft でない記録の削除の口は操作列の末尾(attachDeleteToEditRow)に置く。
6766
+ // draft でない記録の削除の口はページの最下部(appendDeleteFoot)に置く。
6763
6767
  function draftNote(rec) {
6764
6768
  if (!rec || !unconfirmed(rec)) return null;
6765
6769
  var path = promotableRecordPath(rec);
@@ -7072,7 +7076,7 @@ ${story_1.splitByNameLinks.toString()}
7072
7076
  }
7073
7077
  /** 筋の札(一覧の行)。札が無ければ null */
7074
7078
  function strandBadge(rec) {
7075
- 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;
7076
7080
  }
7077
7081
  var FIRMNESS_CLASS = { "確定": "fixed", "仮": "tentative", "検討中": "open" };
7078
7082
  /** 固さの小札。語彙外は出さない(ビルド側でも捨てている・二重の網) */
@@ -18897,7 +18901,7 @@ ${story_1.splitByNameLinks.toString()}
18897
18901
  // 筋の札(#1094)。札を持つ決定が 1 件も無い案件では出さない
18898
18902
  var strandOpts = strandOptions(visibleRecords(SITE.decisions));
18899
18903
  if (strandOpts.length) {
18900
- 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(); });
18901
18905
  strandSel.classList.add("strand-filter");
18902
18906
  filters.appendChild(strandSel);
18903
18907
  }
@@ -19215,6 +19219,9 @@ ${story_1.splitByNameLinks.toString()}
19215
19219
  //(acme.slack.com の acme の部分。fleet-status.json の slackWorkspaces[].domain)
19216
19220
  var LI_SLACK_USER = /^[UW][A-Z0-9]{2,20}$/;
19217
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;
19218
19225
  var LI_SLACK_TEAM = /^[TE][A-Z0-9]{2,20}$/;
19219
19226
  // Backlog のユーザー(engine は userId=ログイン名、無い相手は数値 id を入れる)と、
19220
19227
  // スペースのドメイン(fleet の backlogTarget。正本は 課題管理/issues-config.json)。
@@ -19270,8 +19277,9 @@ ${story_1.splitByNameLinks.toString()}
19270
19277
  * 場が team_id の形なのにどれとも一致しなければ**リンク無し**
19271
19278
  * 2. そうでなく、接続済みワークスペースが**ちょうど 1 つ**ならそれ
19272
19279
  * 3. それ以外(0 個・2 個以上)は**リンク無し**
19273
- * 🔴 **チャンネルの URL のホスト名から推測しない**(改名で変わるし、共有チャンネルでは
19274
- * 貼った人のホストになる。Slack の節と同じ作法)。**間違ったリンクは値だけより悪い**
19280
+ * 🔴 **チャンネルの URL のホストは、台帳にドメインが無いときの最後の手だけ**(liSlackChannelHost。
19281
+ * 登録されたチャンネルの全部が同じホストのときだけ採る。共有チャンネルでは貼った人のホストに
19282
+ * なりうるので、食い違えばリンク無し)。**間違ったリンクは値だけより悪い**
19275
19283
  */
19276
19284
  function liSlackDomain(scope) {
19277
19285
  var list = slackWorkspaces() || [];
@@ -19287,7 +19295,33 @@ ${story_1.splitByNameLinks.toString()}
19287
19295
  }
19288
19296
  if (!pick && list.length === 1) pick = list[0];
19289
19297
  var d = pick && typeof pick.domain === "string" ? pick.domain.trim() : "";
19290
- return LI_SLACK_DOMAIN.test(d) ? d : "";
19298
+ if (LI_SLACK_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;
19291
19325
  }
19292
19326
  /**
19293
19327
  * Backlog のユーザーのページを組むスペースのドメイン(決められなければ空文字)。
@@ -19525,7 +19559,7 @@ ${story_1.splitByNameLinks.toString()}
19525
19559
  rec.deleted = true;
19526
19560
  var title = app.querySelector(".detail-title");
19527
19561
  if (title) title.classList.add("is-deleted");
19528
- // **置き場は2つある**(draft の警告帯の中/上の操作列)。どちらに出ていても止める
19562
+ // **置き場は2つある**(draft の警告帯の中/ページの最下部)。どちらに出ていても止める
19529
19563
  var btns = app.querySelectorAll(".record-delete-btn");
19530
19564
  for (var b = 0; b < btns.length; b++) { btns[b].disabled = true; btns[b].textContent = "削除しました"; }
19531
19565
  // もう無い記録を直す口は押させない(AIで編集・GitHubで編集・確認済みにする)
@@ -19600,7 +19634,7 @@ ${story_1.splitByNameLinks.toString()}
19600
19634
  *
19601
19635
  * 呼び出し側は2つ:
19602
19636
  * - draftNote …… 未確認の記録。「確認済みにする」と並べて警告帯の中に置く
19603
- * - 詳細ページの操作列 …… 警告帯が出ない記録。置き場が他に無いのでここに寄せる
19637
+ * - 詳細ページの最下部(appendDeleteFoot) …… 警告帯が出ない記録
19604
19638
  */
19605
19639
  function buildDeleteControls(rec) {
19606
19640
  var path = deletableRecordPath(rec);
@@ -19620,19 +19654,18 @@ ${story_1.splitByNameLinks.toString()}
19620
19654
  return { btn: btn, msg: msg };
19621
19655
  }
19622
19656
  /**
19623
- * 警告帯が出ない記録の削除の口を、上の操作列(AIで編集・GitHubで編集の行)の末尾へ置く。
19657
+ * 警告帯が出ない記録の削除の口を、ページの一番下(「レコード情報(frontmatter)」の下・
19658
+ * 生成の行の上)へ置く。取り消せない操作を、高頻度の操作(AIで編集・GitHubで編集)の
19659
+ * 隣に置かない(2026-10-18 のオーナー指摘)。4 区画の詳細で同じ。
19624
19660
  *
19625
19661
  * **draft には出さない**——そちらは警告帯の中で「確認済みにする」と対になっている
19626
- * (2つ出すと、同じ操作の口がページに2か所できる)。取り消せない操作を高頻度の
19627
- * 操作の隣に置くことにはなるが、**警告帯の無い記録には他に置き場が無い**。
19628
- * 見た目は控えめな文字のボタンのままにして、隣の実体のあるボタンと重さを分ける。
19662
+ * (2つ出すと、同じ操作の口がページに2か所できる)。
19629
19663
  */
19630
- function attachDeleteToEditRow(rec, editRow) {
19631
- if (!rec || unconfirmed(rec)) return null;
19664
+ function appendDeleteFoot(rec) {
19665
+ if (!rec || unconfirmed(rec)) return;
19632
19666
  var del = buildDeleteControls(rec);
19633
- if (!del) return null;
19634
- editRow.appendChild(del.btn);
19635
- return del.msg; // 結果を出す行は操作列の下(呼び出し側が最後に並べる)
19667
+ if (!del) return;
19668
+ app.appendChild(el("div", { class: "record-delete-foot" }, [del.btn, del.msg]));
19636
19669
  }
19637
19670
  /** 詳細ページの末尾(本文・参照の後)に、つながっている ID・AIの変更履歴・frontmatter を並べる */
19638
19671
  function appendRecordFooter(rec) {
@@ -19643,6 +19676,7 @@ ${story_1.splitByNameLinks.toString()}
19643
19676
  if (history) app.appendChild(history);
19644
19677
  var fmp = renderFmPanel(rec.fm);
19645
19678
  if (fmp) app.appendChild(fmp);
19679
+ appendDeleteFoot(rec);
19646
19680
  // 開き直しても削除済みのまま見せる(印は rec が持っている)
19647
19681
  if (rec.deleted) markRecordDeleted(rec);
19648
19682
  }
@@ -19879,12 +19913,9 @@ ${story_1.splitByNameLinks.toString()}
19879
19913
  if (r.editUrl) {
19880
19914
  editRowR.appendChild(el("a", { class: "edit-btn", href: r.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
19881
19915
  }
19882
- // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
19883
- var delMsgR = attachDeleteToEditRow(r, editRowR);
19884
19916
  head.appendChild(el("div", { class: "detail-actions" }, [
19885
19917
  el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
19886
19918
  editRowR.children.length ? editRowR : null,
19887
- delMsgR,
19888
19919
  ]));
19889
19920
  app.appendChild(head);
19890
19921
 
@@ -20771,12 +20802,9 @@ ${story_1.splitByNameLinks.toString()}
20771
20802
  if (d.editUrl) {
20772
20803
  editRowD.appendChild(el("a", { class: "edit-btn", href: d.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
20773
20804
  }
20774
- // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
20775
- var delMsgD = attachDeleteToEditRow(d, editRowD);
20776
20805
  head.appendChild(el("div", { class: "detail-actions" }, [
20777
20806
  el("div", { class: "detail-actions-row" }, [ traceBtn ]),
20778
20807
  editRowD.children.length ? editRowD : null,
20779
- delMsgD,
20780
20808
  ]));
20781
20809
  app.appendChild(head);
20782
20810
 
@@ -20802,7 +20830,7 @@ ${story_1.splitByNameLinks.toString()}
20802
20830
 
20803
20831
  // 筋(#1094)。札の無い決定では何も作らない
20804
20832
  if (hasStrand(d)) {
20805
- app.appendChild(el("div", { class: "section-label", text: "筋" }));
20833
+ app.appendChild(el("div", { class: "section-label", text: "論点" }));
20806
20834
  app.appendChild(el("div", { class: "decision-strand" }, [el("span", { class: "chip", text: d.strand })]));
20807
20835
  }
20808
20836
 
@@ -21153,6 +21181,12 @@ ${story_1.splitByNameLinks.toString()}
21153
21181
  if (isHttp(target)) return el("a", { href: target, target: "_blank", rel: "noopener", text: target });
21154
21182
  return el("span", { text: target });
21155
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
+ }
21156
21190
  var FM_EMPTY_TEXT = "(未記入)";
21157
21191
  /** パネルの 1 値を文字にする。オブジェクト・配列は JSON([object Object] を出さない) */
21158
21192
  function fmScalarText(x) {
@@ -21208,14 +21242,21 @@ ${story_1.splitByNameLinks.toString()}
21208
21242
  cell.appendChild(line);
21209
21243
  });
21210
21244
  } else if (Array.isArray(v)) {
21211
- // 🔴 要素がオブジェクトなら JSON で出す(join に落とすと [object Object] になる)
21212
- 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
+ });
21213
21254
  } else if (v && typeof v === "object") {
21214
21255
  cell.textContent = fmScalarText(v);
21215
21256
  } else if (k === "source") {
21216
21257
  cell.appendChild(fmTargetLink(String(v)));
21217
- } else if (isHttp(v)) {
21218
- 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) }));
21219
21260
  } else {
21220
21261
  cell.textContent = String(v);
21221
21262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.164",
3
+ "version": "0.12.166",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,