@takagaki/cortex-decisions-viewer 0.12.5 → 0.12.7

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 +174 -4
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -2064,10 +2064,26 @@ a:hover { text-decoration: underline; }
2064
2064
  .badge.latest { background: #dcfce7; color: #166534; border: 1px solid #86efac; }
2065
2065
  .badge.error { background: #fee2e2; color: #b91c1c; border: 1px solid #fca5a5; }
2066
2066
  .draft-note {
2067
- display: flex; gap: 8px; align-items: flex-start;
2067
+ display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start;
2068
2068
  background: #fffbeb; border: 1px solid #fcd34d; border-radius: 8px;
2069
2069
  padding: 10px 14px; margin: 12px 0 4px; font-size: 13.5px; color: #92400e; line-height: 1.7;
2070
2070
  }
2071
+ /* 警告帯に並べる「確認済みにする」。帯と同じ系統の色で、押すものだと分かる濃さにする */
2072
+ .promote-btn {
2073
+ flex: 0 0 auto; align-self: center; margin-left: auto; cursor: pointer; white-space: nowrap;
2074
+ padding: 6px 14px; border-radius: 999px; border: 1px solid #b45309;
2075
+ background: #b45309; color: #fff; font-size: 13px; font-weight: 600;
2076
+ }
2077
+ .promote-btn:hover { background: #92400e; }
2078
+ .promote-btn:disabled { opacity: .6; cursor: default; }
2079
+ /* 何を上げようとしているか(リポジトリ上のパス)。リンクではないので控えめな色にする */
2080
+ .promote-target { font-size: 12.5px; word-break: break-all; color: var(--muted); margin: 0 0 12px; }
2081
+ /* 押したあとの帯。draft(黄・要対応)と入れ替わるので、済んだと分かる緑にする */
2082
+ .promoted-note {
2083
+ display: flex; gap: 8px; align-items: flex-start;
2084
+ background: #ecfdf5; border: 1px solid #a7f3d0; border-radius: 8px;
2085
+ padding: 10px 14px; margin: 12px 0 4px; font-size: 13.5px; color: #047857; line-height: 1.7;
2086
+ }
2071
2087
  .adoption-note {
2072
2088
  display: flex; gap: 8px; align-items: flex-start;
2073
2089
  background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px;
@@ -2913,13 +2929,136 @@ ${deriveCapTile.toString()}
2913
2929
  function latestBadge(rec) {
2914
2930
  return rec && rec.isLatest ? el("span", { class: "badge latest", text: "最新の生成" }) : null;
2915
2931
  }
2916
- // 詳細ページの警告。バッジより強く「そのまま信じないでほしい」と伝える
2932
+ // 詳細ページの警告。バッジより強く「そのまま信じないでほしい」と伝える。
2933
+ // **確認済みにできる記録には、その場で上げるボタンを並べる**(下の promotableRecordPath)。
2934
+ // ボタンが出せないとき(受け口が未設定・パスが解決できない)は、これまでどおり
2935
+ // 「frontmatter を直してください」の案内に倒す——押せない画面で押す話をしない
2917
2936
  function draftNote(rec) {
2918
2937
  if (!rec || rec.rstatus !== "draft") return null;
2919
- return el("div", { class: "draft-note" }, [
2938
+ var path = promotableRecordPath(rec);
2939
+ var note = el("div", { class: "draft-note" }, [
2920
2940
  el("span", { text: "⚠️" }),
2921
- el("span", { text: "この内容はAIが自動生成したものです(人間未確認)。必ず内容を確認し、適切な内容に直したうえで frontmatter の status を active に変更してください。" }),
2941
+ el("span", { text: path
2942
+ ? "この内容はAIが自動生成したものです(人間未確認)。内容を確認し、直すところが無ければ「確認済みにする」を押してください。直したいところがあれば「AIで編集」から修正してください。"
2943
+ : "この内容はAIが自動生成したものです(人間未確認)。必ず内容を確認し、適切な内容に直したうえで frontmatter の status を active に変更してください。" }),
2922
2944
  ]);
2945
+ if (path) {
2946
+ var btn = el("button", { class: "promote-btn", type: "button", text: "確認済みにする" });
2947
+ btn.addEventListener("click", function () {
2948
+ openPromoteModal(path, function () { markPromoted(rec, note); });
2949
+ });
2950
+ note.appendChild(btn);
2951
+ }
2952
+ return note;
2953
+ }
2954
+
2955
+ // ---------- Gold の確認済み昇格(status: draft → active) ----------
2956
+ //
2957
+ // 内容がそのまま正しいとき、**編集せずに所有権だけ引き取る**ための最短経路。
2958
+ // 直すところがあるなら「AIで編集」が別にあるので、ここは「そのままでよい」専用。
2959
+
2960
+ // 受け口が受け付けるパスの形。**サーバ側にも同じ門がある**(logic.mjs の isPromotablePath)。
2961
+ // ここは「押せないボタンを出さない」ための出し分けで、**守りはサーバ側**
2962
+ var PROMOTABLE_PATH = /^Cortex\\/(Decisions|Glossary|Members|Rules)\\/records\\/[^/]+\\.md$/;
2963
+ function promotableRecordPath(rec) {
2964
+ if (!SITE.intake) return "";
2965
+ var p = repoPathOf(rec);
2966
+ if (!p || !PROMOTABLE_PATH.test(p)) return "";
2967
+ // 区画の規約・雛形はレコードではない(一覧にも出ないが、念のため出し分けでも弾く)
2968
+ var base = p.slice(p.lastIndexOf("/") + 1).toLowerCase();
2969
+ return base === "readme.md" || base === "template.md" ? "" : p;
2970
+ }
2971
+
2972
+ // 確認者名は同じブラウザで覚えておく(**案件別**。同一ブラウザで複数案件を見ても混ざらない)
2973
+ function verifierStoreKey() { return "cortex-verifier-name:" + REPO_SLUG; }
2974
+ function rememberedVerifier() {
2975
+ try { return localStorage.getItem(verifierStoreKey()) || ""; } catch (e) { return ""; }
2976
+ }
2977
+
2978
+ /**
2979
+ * 確認の一手間を挟む。**押した瞬間に上げない。**
2980
+ *
2981
+ * 上げると「人間が確認したもの」としてAIの自動更新の対象から外れる=意味の重い操作で、
2982
+ * 元に戻すにはリポジトリを直接編集するしかない。誤爆の余地を残さない。
2983
+ */
2984
+ function openPromoteModal(path, onDone) {
2985
+ var mk = makeModal("この記録を確認済みにする", false);
2986
+ var modal = mk.modal;
2987
+ modal.appendChild(el("p", { class: "modal-desc", text: "内容を確認したものとして、この記録を「確認済み」に上げます。以後この記録は人間が持つものとして扱われ、AIによる自動更新の対象から外れます。" }));
2988
+ modal.appendChild(el("div", { class: "promote-target mono", text: path }));
2989
+ var nameInput = el("input", { type: "text", class: "modal-name", placeholder: "確認した人として記録に残ります(省略できます)" });
2990
+ nameInput.value = rememberedVerifier();
2991
+ modal.appendChild(fieldRow("お名前(任意)", nameInput));
2992
+ var status = el("div", { class: "modal-status" });
2993
+ var cancelBtn = el("button", { class: "modal-cancel", type: "button", text: "閉じる" });
2994
+ var okBtn = el("button", { class: "ops-card-btn", type: "button", text: "確認済みにする" });
2995
+ modal.appendChild(status);
2996
+ modal.appendChild(el("div", { class: "modal-actions" }, [cancelBtn, okBtn]));
2997
+ mk.overlay.appendChild(modal);
2998
+ showModal(mk.overlay, modal);
2999
+ cancelBtn.addEventListener("click", function () { mk.close(); });
3000
+ okBtn.addEventListener("click", function () {
3001
+ sendPromote(path, nameInput.value, status, okBtn, function () { mk.close(); onDone(); });
3002
+ });
3003
+ }
3004
+
3005
+ async function sendPromote(path, name, status, btn, onOk) {
3006
+ var intake = SITE.intake;
3007
+ if (!intake) return;
3008
+ function setStatus(msg, isError) {
3009
+ status.className = "modal-status" + (isError ? " modal-status-error" : "");
3010
+ status.textContent = msg;
3011
+ }
3012
+ // 押している間はボタンごと語を差し替える(#222・beginBusy の項)
3013
+ var endBusy = beginBusy(btn, "確認中…");
3014
+ setStatus("", false);
3015
+ var who = String(name || "").trim();
3016
+ var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
3017
+ var res, j;
3018
+ try {
3019
+ res = await fetch(pfx + "op=promote&t=" + encodeURIComponent(intake.token), {
3020
+ method: "POST",
3021
+ headers: { "content-type": "application/json" },
3022
+ body: JSON.stringify({ projectKey: intake.projectKey, path: path, submitter: who }),
3023
+ });
3024
+ j = await res.json().catch(function () { return {}; });
3025
+ } catch (e) {
3026
+ endBusy();
3027
+ setStatus("✗ サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。", true);
3028
+ return;
3029
+ }
3030
+ // 🔴 **「既に確認済みです」は成功として扱う。** この画面のデータは静的ビルド時点のもので、
3031
+ // 誰かが先に上げていれば古いまま未確認に見える。押した人が望んだ状態には**なっている**ので、
3032
+ // 失敗として見せると「押しても直らない」堂々巡りになる。
3033
+ // **符号で見る。** 409 を一律に成功にすると、status が draft でない別の状態
3034
+ // (superseded 等)で断られたときまで「確認済みにしました」と嘘をつく
3035
+ if (res.ok || (res.status === 409 && j.code === "ALREADY_ACTIVE")) {
3036
+ if (who) { try { localStorage.setItem(verifierStoreKey(), who); } catch (e2) {} }
3037
+ endBusy();
3038
+ onOk();
3039
+ return;
3040
+ }
3041
+ endBusy();
3042
+ setStatus("✗ " + (j.message || "確認済みにできませんでした。時間をおいて再度お試しください。"), true);
3043
+ }
3044
+
3045
+ /**
3046
+ * 押した結果を画面に映す。
3047
+ *
3048
+ * **次のビルドまで SiteData は古いまま。** 昇格の commit 自体が Amplify の再ビルドを
3049
+ * 誘発するので数分で追いつくが、その間も押した人には済んだものとして見せる。
3050
+ */
3051
+ function markPromoted(rec, note) {
3052
+ // **元データも直す。** 一覧へ戻って開き直しても未確認に戻らないようにする
3053
+ rec.rstatus = "active";
3054
+ var host = note.parentNode;
3055
+ if (!host) return;
3056
+ var badge = host.querySelector(".detail-meta .badge.draft");
3057
+ if (badge && badge.parentNode) badge.parentNode.removeChild(badge);
3058
+ host.replaceChild(el("div", { class: "promoted-note" }, [
3059
+ el("span", { text: "✅" }),
3060
+ el("span", { text: "この記録を確認済みにしました。一覧などの表示は、数分後に画面が作り直されたときに反映されます。" }),
3061
+ ]), note);
2923
3062
  }
2924
3063
  // 未確認の絞り込み(フィルタのセレクトの選択肢は draft の1つだけ)
2925
3064
  function statusLabel(v) { return v === "draft" ? "未確認のみ" : v; }
@@ -8300,6 +8439,14 @@ ${deriveCapTile.toString()}
8300
8439
 
8301
8440
  function renderOps() {
8302
8441
  renderConnMap();
8442
+ // 🔴 **先読みが飛行中なら、いま作った図を確認中へ倒す(#264)。**
8443
+ // 先読み(起動時の loadLiveFleet)は操作タブを開く前に走るので、
8444
+ // 確認中へ倒す口(loadLiveFleet の connMapConfirmingShown の1回)は
8445
+ // **conn-map の DOM が無い時点で使い切られている**——ここで呼び直さないと、
8446
+ // 応答が届く前に操作タブを開いた人には**焼いた状態が断言されたまま**出る
8447
+ // (#256 で消した瞬きが、先読みの経路だけで戻ってくる)。
8448
+ // タイル側は描画時に liveConfirming() を評価する作りなので追加対応は要らない。
8449
+ if (liveConfirming()) showConfirmingConnMap();
8303
8450
  renderDataSources();
8304
8451
  loadLiveFleet();
8305
8452
  // **まとめ表がまだ答えていない行があれば取り直す。** 取得前は0件なので、
@@ -8900,6 +9047,11 @@ ${deriveCapTile.toString()}
8900
9047
  if (capConfigState(cap)) continue;
8901
9048
  var node = wrap.querySelector('[data-target="ds-' + cap + '"]');
8902
9049
  if (!node) continue;
9050
+ // 🔴 **すでに退避してある能力は二度と触らない(#264)。** 先読みの経路では
9051
+ // ここが1回の描画で2回呼ばれうる(renderOps の飛行中分岐 → loadLiveFleet の
9052
+ // 初回1回)。上書きすると **退避に「cm-pending」が入り**、失敗・見切りで
9053
+ // 戻したときに確認中の表示が焼いた値として復活する=確認中で固まる。
9054
+ if (node.hasAttribute("data-cm-baked-class")) continue;
8903
9055
  node.setAttribute("data-cm-baked-class", node.getAttribute("class") || "");
8904
9056
  node.setAttribute("class", "cm-node cm-pending");
8905
9057
  var sync = node.querySelector(".cm-sync");
@@ -10554,5 +10706,23 @@ ${deriveCapTile.toString()}
10554
10706
 
10555
10707
  window.addEventListener("popstate", route);
10556
10708
  route();
10709
+ /**
10710
+ * 接続状況の先読み(#264)。**開いたタブに関係なく、起動時に1回だけ取りに行く。**
10711
+ *
10712
+ * op=state は 0.12.0 から常に全能力を要求するので(ALL_STATE_CAPS の項)、
10713
+ * この1回で操作タブが必要とする材料は全部揃う——操作タブを開いた時点で
10714
+ * LIVE_STATE が既にあり、「確認中…」を待たずに確定表示が出る。
10715
+ *
10716
+ * 🔴 **route() の後に置く(renderList / renderOps 側にフックしない)。**
10717
+ * renderList はタブを切り替えるたびに走るので、あちらに置くと「起動時に1回」
10718
+ * ではなくなる。しかも **popstate はこの行を通らない**(listener は route だけ)
10719
+ * ので、戻る/進むで先読みが何度も走ることもない。
10720
+ *
10721
+ * 取りに行かない案件(intake 無し・fetch 無し)で確認中が出たまま固まる心配は
10722
+ * ない——判定は loadLiveFleet 内の liveConfirmPossible が持つ。多重fetchも
10723
+ * 既存の liveFleetRequested の再入防止が守る(?tab=ops で開いた場合は
10724
+ * renderOps 側の呼び出しが先に飛んでいるので、ここは何もせずに戻る)。
10725
+ */
10726
+ loadLiveFleet();
10557
10727
  })();
10558
10728
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.5",
3
+ "version": "0.12.7",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,