@takagaki/cortex-decisions-viewer 0.12.13 → 0.12.15
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 +7 -274
- package/dist/render.js +385 -175
- package/dist/repo.js +347 -0
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -2080,9 +2080,15 @@ a:hover { text-decoration: underline; }
|
|
|
2080
2080
|
background: #fffbeb; border: 1px solid #fcd34d; border-radius: 8px;
|
|
2081
2081
|
padding: 10px 14px; margin: 12px 0 4px; font-size: 13.5px; color: #92400e; line-height: 1.7;
|
|
2082
2082
|
}
|
|
2083
|
+
/* 警告帯に並ぶ二択(確認済みにする/削除)。AIが起票した記録に人がやることは
|
|
2084
|
+
「引き取る」か「要らないと言う」のどちらかなので、同じ場所に並べる */
|
|
2085
|
+
.draft-note-actions {
|
|
2086
|
+
flex: 0 0 auto; align-self: center; margin-left: auto;
|
|
2087
|
+
display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
|
2088
|
+
}
|
|
2083
2089
|
/* 警告帯に並べる「確認済みにする」。帯と同じ系統の色で、押すものだと分かる濃さにする */
|
|
2084
2090
|
.promote-btn {
|
|
2085
|
-
flex: 0 0 auto;
|
|
2091
|
+
flex: 0 0 auto; cursor: pointer; white-space: nowrap;
|
|
2086
2092
|
padding: 6px 14px; border-radius: 999px; border: 1px solid #b45309;
|
|
2087
2093
|
background: #b45309; color: #fff; font-size: 13px; font-weight: 600;
|
|
2088
2094
|
}
|
|
@@ -2108,10 +2114,11 @@ a:hover { text-decoration: underline; }
|
|
|
2108
2114
|
padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
|
2109
2115
|
}
|
|
2110
2116
|
.home-grounding > .home-grounding-title:first-child { margin-top: 0; }
|
|
2111
|
-
/* ---- レコード詳細の「AI
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2117
|
+
/* ---- レコード詳細の「AIによる最近の変更」(リポジトリの git 履歴 由来)----
|
|
2118
|
+
カードは詳細ページの提案(.proposals)と同系色にする——どちらも
|
|
2119
|
+
「AIがやったこと・言っていることを読む」もので種類が同じ。
|
|
2120
|
+
出どころは 0.12.15 で週次整理レポートから git 履歴に変えた(レポートは毎回全置換で、
|
|
2121
|
+
翌週の実行で前の履歴が消えたため) */
|
|
2115
2122
|
.ai-history { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--border); }
|
|
2116
2123
|
.ah-summary { cursor: pointer; font-size: 13.5px; font-weight: 700; color: var(--muted); }
|
|
2117
2124
|
.ah-item {
|
|
@@ -2122,34 +2129,51 @@ a:hover { text-decoration: underline; }
|
|
|
2122
2129
|
.ah-line { font-size: 13px; color: var(--muted); line-height: 1.8; word-break: break-word; }
|
|
2123
2130
|
.ah-meta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 10px; font-size: 13px; }
|
|
2124
2131
|
.ah-commit { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
.ah-
|
|
2128
|
-
.ah-
|
|
2129
|
-
/*
|
|
2130
|
-
.ah-
|
|
2132
|
+
/* 変わった行のかたまり(消えた行/増えた行)。**ファイル全体の前後ではない**ので、
|
|
2133
|
+
ラベルを必ず添える(.ah-diff-label) */
|
|
2134
|
+
.ah-diff { margin-top: 10px; }
|
|
2135
|
+
.ah-diff-label { font-size: 12.5px; font-weight: 700; color: var(--muted); margin-bottom: 4px; }
|
|
2136
|
+
/* 何を見せていて何を見せられないのかの断り。節の末尾に置く */
|
|
2137
|
+
.ah-source { font-size: 12px; color: var(--muted); line-height: 1.7; margin: 12px 0 0; }
|
|
2138
|
+
/* 差分は数千字になりうる。折りたたみの中でも高さを縛って、開いたときにページ全体を押し流さない */
|
|
2139
|
+
.ah-diff-text {
|
|
2131
2140
|
margin: 8px 0 0; padding: 10px 12px; max-height: 320px; overflow: auto;
|
|
2132
2141
|
background: #fff; border: 1px solid var(--border); border-radius: 8px;
|
|
2133
2142
|
font-size: 12.5px; line-height: 1.7; white-space: pre-wrap; word-break: break-word;
|
|
2134
2143
|
}
|
|
2135
2144
|
/* ---- レコードの削除(?op=delete-record)----
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2145
|
+
置き場は2つ(2026-08-28 のオーナー裁定)。**未確認の記録は警告帯の中**で
|
|
2146
|
+
「確認済みにする」と二択に、**警告帯が出ない記録は上の操作列の末尾**に置く。
|
|
2147
|
+
|
|
2148
|
+
**2026-08-29 のオーナーFBで、控えめな文字ボタンから同格のボタンへ変えた。**
|
|
2149
|
+
下線だけの文字は隣の「確認済みにする」より一段下に見え、二択のうち片方だけが
|
|
2150
|
+
本物のボタンに見えていた——選ばせたいのは対等な二択なので、寸法をそろえる。
|
|
2151
|
+
**配色は背景白・赤文字**(取り消せない操作であることは、塗りではなく赤で示す。
|
|
2152
|
+
帯と同じ濃さで塗ると「押してほしいほう」に見える)。
|
|
2153
|
+
なお、このビューアに**ダークテーマは無い**(:root の1組だけで prefers-color-scheme を
|
|
2154
|
+
使っていない)ので、暗色側の指定は置かない。押したあとの見た目は「取り消し線+無効化」 */
|
|
2140
2155
|
.record-delete-btn {
|
|
2141
|
-
|
|
2142
|
-
|
|
2156
|
+
flex: 0 0 auto; cursor: pointer; white-space: nowrap;
|
|
2157
|
+
padding: 8px 14px; border-radius: var(--radius);
|
|
2158
|
+
border: 1px solid #fca5a5; background: #fff; color: #b91c1c;
|
|
2159
|
+
font-size: 13.5px; font-weight: 600;
|
|
2143
2160
|
}
|
|
2144
|
-
|
|
2145
|
-
.record-delete-btn
|
|
2146
|
-
.record-delete-
|
|
2161
|
+
/* 警告帯の中では「確認済みにする」(.promote-btn)と寸法・丸みをそろえ、二択を同格に見せる */
|
|
2162
|
+
.draft-note .record-delete-btn { padding: 6px 14px; border-radius: 999px; font-size: 13px; }
|
|
2163
|
+
.record-delete-btn:hover { background: #fef2f2; border-color: #b91c1c; }
|
|
2164
|
+
.record-delete-btn:disabled { cursor: default; opacity: 0.6; }
|
|
2165
|
+
.record-delete-msg { font-size: 13px; }
|
|
2166
|
+
.record-delete-msg:empty { display: none; }
|
|
2147
2167
|
.record-delete-msg.is-error { color: #b91c1c; }
|
|
2168
|
+
/* 帯の中では1行を占める(ボタンの並びの下へ回り込ませる)。操作列では右寄せのまま畳む */
|
|
2169
|
+
.draft-note > .record-delete-msg { flex: 1 0 100%; }
|
|
2170
|
+
.detail-actions .record-delete-msg { max-width: 320px; text-align: right; }
|
|
2148
2171
|
.deleted-note {
|
|
2149
|
-
display: flex; gap: 8px; align-items: flex-start; margin
|
|
2150
|
-
background: #
|
|
2172
|
+
display: flex; gap: 8px; align-items: flex-start; margin: 4px 0 16px;
|
|
2173
|
+
background: #fff; border: 1px solid var(--border); border-radius: 8px;
|
|
2151
2174
|
padding: 10px 12px; font-size: 13px; line-height: 1.8; color: var(--muted);
|
|
2152
2175
|
}
|
|
2176
|
+
.draft-note > .deleted-note { flex: 1 0 100%; margin-bottom: 0; }
|
|
2153
2177
|
/* 消したものは、消えたと分かる形で残す(一覧・詳細の両方) */
|
|
2154
2178
|
.is-deleted { text-decoration: line-through; color: var(--muted); }
|
|
2155
2179
|
/* 消したレコードでは、直す口(AIで編集・GitHubで編集)を押させない */
|
|
@@ -2677,6 +2701,14 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
2677
2701
|
/* 取り込みの条件は箇条書きで出す(ANDであることを読み飛ばせないように) */
|
|
2678
2702
|
.sec-how-list { margin: 4px 0; padding-left: 1.3em; }
|
|
2679
2703
|
.sec-how-list li { margin: 2px 0; }
|
|
2704
|
+
/* 会議の主催者向けの選択肢(Meet Recordings フォルダの共有・2026-08-29 オーナー承認)。
|
|
2705
|
+
**折りたたまない。** 週次整理の節が「誰も見ない」で撤去された経緯があるので、
|
|
2706
|
+
知ってほしい選択肢を <details> の中に隠さない。従来の bot 招待の説明の下に、
|
|
2707
|
+
枠で囲った別の話として置く(差し替えではなく追加であることを見た目でも示す) */
|
|
2708
|
+
.mt-share { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 8px 0 0; background: #fff; }
|
|
2709
|
+
.mt-share-title { font-size: 12.5px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
|
|
2710
|
+
/* 共有すると何が起きるかの1行。**説明文より強く出す**(.ds-scope-note と同じ理由) */
|
|
2711
|
+
.mt-share-note { color: var(--text); margin-top: 6px; }
|
|
2680
2712
|
/* 0件を黙って見せない(追加フォームだけだと「未登録」か「読めていない」か区別できない) */
|
|
2681
2713
|
.ds-empty { color: var(--muted); font-size: 12.5px; margin: 4px 0 10px; }
|
|
2682
2714
|
.sec-input { width: 100%; box-sizing: border-box; font-size: 13px; padding: 7px 10px; margin-bottom: 8px; border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); }
|
|
@@ -3078,6 +3110,11 @@ ${deriveCapTile.toString()}
|
|
|
3078
3110
|
// **確認済みにできる記録には、その場で上げるボタンを並べる**(下の promotableRecordPath)。
|
|
3079
3111
|
// ボタンが出せないとき(受け口が未設定・パスが解決できない)は、これまでどおり
|
|
3080
3112
|
// 「frontmatter を直してください」の案内に倒す——押せない画面で押す話をしない
|
|
3113
|
+
//
|
|
3114
|
+
// **削除もここに並べる(2026-08-28 のオーナー裁定)。** AIが起票した記録に対して
|
|
3115
|
+
// 人がやることは「引き取る(確認済みにする)」か「要らないと言う(削除)」の二択で、
|
|
3116
|
+
// 同じ場面の同じ判断なので、口を離さない。**この帯は draft にしか出ない**ので、
|
|
3117
|
+
// 人が確認済みにした記録には削除の口ごと現れない(守りはサーバ側にもある)。
|
|
3081
3118
|
function draftNote(rec) {
|
|
3082
3119
|
if (!rec || rec.rstatus !== "draft") return null;
|
|
3083
3120
|
var path = promotableRecordPath(rec);
|
|
@@ -3087,13 +3124,19 @@ ${deriveCapTile.toString()}
|
|
|
3087
3124
|
? "この内容はAIが自動生成したものです(人間未確認)。内容を確認し、直すところが無ければ「確認済みにする」を押してください。直したいところがあれば「AIで編集」から修正してください。"
|
|
3088
3125
|
: "この内容はAIが自動生成したものです(人間未確認)。必ず内容を確認し、適切な内容に直したうえで frontmatter の status を active に変更してください。" }),
|
|
3089
3126
|
]);
|
|
3127
|
+
var actions = el("div", { class: "draft-note-actions" });
|
|
3090
3128
|
if (path) {
|
|
3091
3129
|
var btn = el("button", { class: "promote-btn", type: "button", text: "確認済みにする" });
|
|
3092
3130
|
btn.addEventListener("click", function () {
|
|
3093
3131
|
openPromoteModal(path, function () { markPromoted(rec, note); });
|
|
3094
3132
|
});
|
|
3095
|
-
|
|
3133
|
+
actions.appendChild(btn);
|
|
3096
3134
|
}
|
|
3135
|
+
var del = buildDeleteControls(rec);
|
|
3136
|
+
if (del) actions.appendChild(del.btn);
|
|
3137
|
+
if (actions.children.length) note.appendChild(actions);
|
|
3138
|
+
// 断られた理由・削除した旨は帯の中に出す(口と結果を離さない)
|
|
3139
|
+
if (del) note.appendChild(del.msg);
|
|
3097
3140
|
return note;
|
|
3098
3141
|
}
|
|
3099
3142
|
|
|
@@ -4079,9 +4122,35 @@ ${deriveCapTile.toString()}
|
|
|
4079
4122
|
ul.appendChild(el("li", { text: "会議名に上の目印が入っている" }));
|
|
4080
4123
|
wrap.appendChild(ul);
|
|
4081
4124
|
wrap.appendChild(el("p", { text: "どちらか一方だけでは取り込まれません。" }));
|
|
4125
|
+
wrap.appendChild(meetingFolderShare());
|
|
4082
4126
|
return wrap;
|
|
4083
4127
|
}
|
|
4084
4128
|
|
|
4129
|
+
/**
|
|
4130
|
+
* 主催者向けの選択肢: 「Meet Recordings」フォルダを1回共有すると、以後は招待が要らない。
|
|
4131
|
+
*
|
|
4132
|
+
* **上の bot 招待の説明は残したまま足す(2026-08-29 オーナー承認)。** 招待方式は
|
|
4133
|
+
* 参加するだけの会議・他社主催の会議で今も唯一の手段なので、置き換えではない。
|
|
4134
|
+
*
|
|
4135
|
+
* **手順の細目は書かない。** 正本は apps-script/docs/meet-recordings-sharing.md で、
|
|
4136
|
+
* 「マイドライブの直下しか見ない」「メモを移すと対象外になる」等はそちらが持つ。
|
|
4137
|
+
* 画面が担うのは**選択肢があることと、共有すると何が起きるかの1行**まで
|
|
4138
|
+
*(同じ説明を2箇所で維持すると、必ず片方が古くなる)。
|
|
4139
|
+
*
|
|
4140
|
+
* 🔴 **注意の1行を落とさない。** フォルダごと共有するので、目印の無い会議
|
|
4141
|
+
*(1on1・面談)のメモも bot から**見える状態にはなる**。取り込まれないことと
|
|
4142
|
+
* 見えないことは別なので、両方を1行で正直に書く。
|
|
4143
|
+
*/
|
|
4144
|
+
// 注: ここは埋め込みJSのテンプレートリテラル内。コメントでもバッククォートを使わない
|
|
4145
|
+
function meetingFolderShare() {
|
|
4146
|
+
var box = el("div", { class: "mt-share" });
|
|
4147
|
+
box.appendChild(el("div", { class: "mt-share-title", text: "毎回の招待をやめる方法(会議の主催者向け・任意)" }));
|
|
4148
|
+
box.appendChild(el("p", { text: "自分の Google ドライブにある「Meet Recordings」フォルダを、下のアカウントに「閲覧者」で1回だけ共有すると、以後あなたが主催する会議は招待が要らなくなります。会議名の目印はこれまでどおり必要です。" }));
|
|
4149
|
+
box.appendChild(copyRow(DRIVE_INGEST_ACCOUNT));
|
|
4150
|
+
box.appendChild(el("p", { class: "mt-share-note", text: "共有すると、そのフォルダに入る会議メモは bot から見える状態になります(実際に取り込まれるのは、会議名に目印が入っている会議だけです)。" }));
|
|
4151
|
+
return box;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4085
4154
|
/**
|
|
4086
4155
|
* Slack の承認画面へ進むボタン(2026-08-20 に接続カードから切り出した)。
|
|
4087
4156
|
*
|
|
@@ -10161,7 +10230,7 @@ ${deriveCapTile.toString()}
|
|
|
10161
10230
|
filters.appendChild(buildSelect("すべての期間", SITE.months, listState.month, function (v) { listState.month = v; listState.page = 1; drawCards(); }, monthLabel));
|
|
10162
10231
|
}
|
|
10163
10232
|
filters.appendChild(buildSelect("すべてのカテゴリー", SITE.categories, listState.category, function (v) { listState.category = v; listState.page = 1; drawCards(); }));
|
|
10164
|
-
var statusOptsD = statusFilterOptions(SITE.decisions);
|
|
10233
|
+
var statusOptsD = statusFilterOptions(visibleRecords(SITE.decisions));
|
|
10165
10234
|
if (statusOptsD.length) {
|
|
10166
10235
|
filters.appendChild(buildSelect("すべての状態", statusOptsD, listState.status, function (v) { listState.status = v; listState.page = 1; drawCards(); }, statusLabel));
|
|
10167
10236
|
}
|
|
@@ -10189,7 +10258,9 @@ ${deriveCapTile.toString()}
|
|
|
10189
10258
|
|
|
10190
10259
|
function drawCards() {
|
|
10191
10260
|
var q = listState.q.trim().toLowerCase();
|
|
10192
|
-
|
|
10261
|
+
// 削除済みは絞り込みの手前で落とす(分母も一緒に減るので「12/13件」が残らない)
|
|
10262
|
+
var all = visibleRecords(SITE.decisions);
|
|
10263
|
+
var items = all.filter(function (d) {
|
|
10193
10264
|
if (listState.month && d.date.slice(0, 7) !== listState.month) return false;
|
|
10194
10265
|
if (listState.category && d.category !== listState.category) return false;
|
|
10195
10266
|
if (!matchesStatusFilter(d)) return false;
|
|
@@ -10204,14 +10275,14 @@ ${deriveCapTile.toString()}
|
|
|
10204
10275
|
wrap.innerHTML = "";
|
|
10205
10276
|
|
|
10206
10277
|
if (!items.length) {
|
|
10207
|
-
countEl.textContent = "0 / " +
|
|
10278
|
+
countEl.textContent = "0 / " + all.length + " 件";
|
|
10208
10279
|
wrap.appendChild(el("div", { class: "empty", text: "該当する決定はありません" }));
|
|
10209
10280
|
return;
|
|
10210
10281
|
}
|
|
10211
10282
|
|
|
10212
10283
|
var pg = paginate(items);
|
|
10213
10284
|
countEl.textContent =
|
|
10214
|
-
items.length + " / " +
|
|
10285
|
+
items.length + " / " + all.length + " 件" +
|
|
10215
10286
|
(pg.pages > 1 ? "(" + pg.page + "/" + pg.pages + "ページ)" : "");
|
|
10216
10287
|
|
|
10217
10288
|
pg.slice.forEach(function (d) {
|
|
@@ -10246,7 +10317,8 @@ ${deriveCapTile.toString()}
|
|
|
10246
10317
|
function renderGenericList() {
|
|
10247
10318
|
var tab = null;
|
|
10248
10319
|
TABS.forEach(function (t) { if (t.key === listState.tab) tab = t; });
|
|
10249
|
-
|
|
10320
|
+
// 削除済みは描画の手前で落とす(件数の分母・絞り込みの選択肢もこれ1本で揃う)
|
|
10321
|
+
var items = visibleRecords(tab && tab.items ? tab.items : []);
|
|
10250
10322
|
|
|
10251
10323
|
var filters = el("div", { class: "filters" });
|
|
10252
10324
|
var search = el("input", { type: "search", placeholder: "キーワード検索" });
|
|
@@ -10314,49 +10386,47 @@ ${deriveCapTile.toString()}
|
|
|
10314
10386
|
}
|
|
10315
10387
|
|
|
10316
10388
|
/* ---------------------------------------------------------------------------
|
|
10317
|
-
* AI
|
|
10389
|
+
* AIによる最近の変更(リポジトリの git 履歴 由来)
|
|
10318
10390
|
* ---------------------------------------------------------------------------
|
|
10319
|
-
*
|
|
10391
|
+
* 夜間のパイプラインは、記録を勝手に起票し、draft の記録を勝手に書き換える。
|
|
10320
10392
|
* **黙って書き換えられた記録を作らないための面**がこれ——AIが何をしたかを、
|
|
10321
10393
|
* 変えられた記録自身のページに畳んで置く。
|
|
10322
10394
|
*
|
|
10323
|
-
*
|
|
10324
|
-
*
|
|
10325
|
-
*
|
|
10326
|
-
*
|
|
10395
|
+
* 🔴 **出どころを週次整理レポートから git 履歴に変えた(0.12.15)。**
|
|
10396
|
+
* レポート(.cortex/weekly-curation-report.json)は毎回全置換で、翌週の実行で
|
|
10397
|
+
* 差し替わると**前の履歴が画面から消えた**——オーナーが実際に踏んだ(前日に見た変更が
|
|
10398
|
+
* 翌日には無くなっていた)。1週間で消える履歴は、この節の目的を果たさない。
|
|
10399
|
+
* git のコミットは消えないので、ビルド時にそこから作る(repo.ts の collectAiChangeHistory)。
|
|
10400
|
+
*
|
|
10401
|
+
* **取れないものは取れないと言う。** git には「なぜそう直したか」の根拠(引用)が
|
|
10402
|
+
* 残らないので、出せるのは**変わった行・コミットメッセージ・日時**まで。
|
|
10403
|
+
* 節の末尾でそれを1行断る(根拠があるように見せない)。
|
|
10327
10404
|
*
|
|
10328
|
-
*
|
|
10329
|
-
|
|
10405
|
+
* **人のコミットは混ぜない。** 受け口の Lambda は人の操作の代理でコミットするので
|
|
10406
|
+
*(「Gold確認: …」「レコード削除: …」)、bot 名義だけで拾うと人の操作がAIの仕業として
|
|
10407
|
+
* 出る。切り分けは repo.ts の isAiCommit(ビルド時)。
|
|
10330
10408
|
*
|
|
10331
10409
|
* 🔴 **値はすべて textContent 経由で入れる**(el の text オプション)。
|
|
10332
|
-
*
|
|
10333
|
-
* innerHTML
|
|
10410
|
+
* 差分の中身・コミットメッセージは**リポジトリの生テキスト**で、記録の本文がそのまま
|
|
10411
|
+
* 入る。innerHTML に渡すと記録の中身が画面のコードになる。
|
|
10334
10412
|
*/
|
|
10335
10413
|
|
|
10336
|
-
/**
|
|
10337
|
-
function
|
|
10338
|
-
var
|
|
10339
|
-
if (!
|
|
10414
|
+
/** そのレコードに対するAIの変更(新しい順・ビルド側で最大5件に絞ってある) */
|
|
10415
|
+
function aiHistoryEntries(rec) {
|
|
10416
|
+
var map = SITE.aiHistory;
|
|
10417
|
+
if (!map) return [];
|
|
10340
10418
|
var p = repoPathOf(rec);
|
|
10341
10419
|
if (!p) return [];
|
|
10342
|
-
return
|
|
10343
|
-
// 統合は**勝者のページ**に出す(敗者はファイルごと消えていて、開く先が無い)。
|
|
10344
|
-
// 精製と未知の kind は path で照合する——読めない kind でも
|
|
10345
|
-
// 「この記録に何かをした」ことは出す(読めないまま消えるより、読めないまま出す)
|
|
10346
|
-
if (act.winner && act.loser) return !!act.winner.path && act.winner.path === p;
|
|
10347
|
-
return !!act.path && act.path === p;
|
|
10348
|
-
});
|
|
10420
|
+
return map[p] || [];
|
|
10349
10421
|
}
|
|
10350
10422
|
/**
|
|
10351
|
-
*
|
|
10352
|
-
*
|
|
10353
|
-
* Date に通さず ISO の字面(YYYY-MM-DD)から切り出す——閲覧者のタイムゾーンで
|
|
10354
|
-
* 日付がずれると、レポート自身が書いた実行日と食い違う(CI の UTC でも同じずれが出る)
|
|
10423
|
+
* 見出しに出す日付(YYYY/M/D)。**Date に通さない**——閲覧者のタイムゾーンで日付がずれると、
|
|
10424
|
+
* コミット自身が持っている日付と食い違う。読めない値は素のまま出す
|
|
10355
10425
|
*/
|
|
10356
|
-
function
|
|
10426
|
+
function ahDate(iso) {
|
|
10357
10427
|
var m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(String(iso));
|
|
10358
10428
|
if (!m) return String(iso);
|
|
10359
|
-
return parseInt(m[2], 10) + "/" + parseInt(m[3], 10);
|
|
10429
|
+
return m[1] + "/" + parseInt(m[2], 10) + "/" + parseInt(m[3], 10);
|
|
10360
10430
|
}
|
|
10361
10431
|
// コミットへのリンク。repoBaseUrl が無ければ短縮SHAだけ出す(どの変更かは残る)
|
|
10362
10432
|
function ahCommitEl(sha) {
|
|
@@ -10368,96 +10438,53 @@ ${deriveCapTile.toString()}
|
|
|
10368
10438
|
target: "_blank", rel: "noopener", text: "コミット " + short,
|
|
10369
10439
|
});
|
|
10370
10440
|
}
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
el("span", { class: "proposal-value", text: chg.before || "(未記入)" }),
|
|
10381
|
-
]),
|
|
10382
|
-
el("div", { class: "proposal-row proposal-row-new" }, [
|
|
10383
|
-
el("span", { class: "proposal-label", text: "変更後" }),
|
|
10384
|
-
el("span", { class: "proposal-value", text: chg.after || "(未記入)" }),
|
|
10385
|
-
]),
|
|
10386
|
-
]),
|
|
10441
|
+
/**
|
|
10442
|
+
* 変わった行のかたまり。**「消えた行/増えた行」と書く。**
|
|
10443
|
+
* 出しているのは差分(-U0)であってファイル全体の前後ではないので、
|
|
10444
|
+
* 「変更前」だけだと**記録の全文が載っていると誤読される**
|
|
10445
|
+
*/
|
|
10446
|
+
function ahDiffEl(label, text) {
|
|
10447
|
+
return el("div", { class: "ah-diff" }, [
|
|
10448
|
+
el("div", { class: "ah-diff-label", text: label }),
|
|
10449
|
+
el("pre", { class: "ah-diff-text", text: text }),
|
|
10387
10450
|
]);
|
|
10388
10451
|
}
|
|
10389
|
-
function ahEvidenceEl(list) {
|
|
10390
|
-
var ul = el("ul", { class: "proposal-evidence" });
|
|
10391
|
-
list.forEach(function (evi) {
|
|
10392
|
-
// 引用が無くても出典は出す(公開範囲フィルタで引用だけ落ちることがある)
|
|
10393
|
-
var li = el("li", {}, [el("span", { class: "proposal-source", text: evi.source })]);
|
|
10394
|
-
if (evi.quote) li.appendChild(el("blockquote", { class: "proposal-quote", text: evi.quote }));
|
|
10395
|
-
ul.appendChild(li);
|
|
10396
|
-
});
|
|
10397
|
-
return ul;
|
|
10398
|
-
}
|
|
10399
|
-
// 汎用行(未知の kind)。**捨てずに「名前: 値」で並べる**——engine が先に kind を
|
|
10400
|
-
// 増やしても「何かがあったこと」は画面に残る
|
|
10401
|
-
function ahGenericRow(row) {
|
|
10402
|
-
var box = el("div", { class: "ah-item" }, [el("div", { class: "ah-item-head", text: row.kind })]);
|
|
10403
|
-
var pairs = [["対象", row.title], ["種別", row.type], ["理由", row.reason],
|
|
10404
|
-
["なぜ", row.why], ["状態", row.status],
|
|
10405
|
-
["一致度", row.score != null ? String(row.score) : ""]];
|
|
10406
|
-
pairs.forEach(function (kv) {
|
|
10407
|
-
if (!kv[1]) return;
|
|
10408
|
-
box.appendChild(el("div", { class: "ah-line", text: kv[0] + ": " + kv[1] }));
|
|
10409
|
-
});
|
|
10410
|
-
var commit = ahCommitEl(row.commit);
|
|
10411
|
-
if (commit) box.appendChild(el("div", { class: "ah-meta" }, [commit]));
|
|
10412
|
-
return box;
|
|
10413
|
-
}
|
|
10414
10452
|
/** AIがこの記録にしたこと1件 */
|
|
10415
|
-
function
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10453
|
+
function ahEntryEl(e) {
|
|
10454
|
+
var what = e.kind === "add" ? "AIがこの記録を作成しました" : "AIがこの記録を書き換えました";
|
|
10455
|
+
var box = el("div", { class: "ah-item" }, [
|
|
10456
|
+
el("div", { class: "ah-item-head", text: ahDate(e.at) + " " + what }),
|
|
10457
|
+
]);
|
|
10458
|
+
if (e.subject) box.appendChild(el("div", { class: "ah-line", text: e.subject }));
|
|
10459
|
+
if (e.kind !== "add") {
|
|
10460
|
+
if (e.before) box.appendChild(ahDiffEl("変更前(消えた行)", e.before));
|
|
10461
|
+
if (e.after) box.appendChild(ahDiffEl("変更後(増えた行)", e.after));
|
|
10462
|
+
// **空欄で済ませない。** 差分を取り出せなかった(浅い clone・巨大な差分)のか、
|
|
10463
|
+
// 何も変わっていないのかを、読む人が区別できないままにしない
|
|
10464
|
+
if (!e.before && !e.after) {
|
|
10465
|
+
box.appendChild(el("div", { class: "ah-line", text: "変わった行をここに出せませんでした。コミットを開くと中身を確認できます。" }));
|
|
10422
10466
|
}
|
|
10423
|
-
if (
|
|
10424
|
-
|
|
10425
|
-
if (commit) box.appendChild(el("div", { class: "ah-meta" }, [commit]));
|
|
10426
|
-
if (act.loser.body) {
|
|
10427
|
-
// **全文は畳んでおく。** 数千字になりうるので開いたまま並べるとページが読めなくなる。
|
|
10428
|
-
// それでも捨てないのは、消えた記録を復元できる形で残すのがこの節の役目だから
|
|
10429
|
-
box.appendChild(el("details", { class: "ah-body" }, [
|
|
10430
|
-
el("summary", { class: "ah-body-summary", text: "取り込んで削除した記録の全文" }),
|
|
10431
|
-
el("pre", { class: "ah-body-text", text: act.loser.body }),
|
|
10432
|
-
]));
|
|
10467
|
+
if (e.truncated) {
|
|
10468
|
+
box.appendChild(el("div", { class: "ah-line", text: "※ 長いので途中までを出しています。全部はコミットで見られます。" }));
|
|
10433
10469
|
}
|
|
10434
|
-
return box;
|
|
10435
10470
|
}
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
]);
|
|
10440
|
-
act.changes.forEach(function (chg) { refined.appendChild(ahChangeEl(chg)); });
|
|
10441
|
-
if (act.evidence && act.evidence.length) {
|
|
10442
|
-
refined.appendChild(el("div", { class: "section-label", text: "根拠" }));
|
|
10443
|
-
refined.appendChild(ahEvidenceEl(act.evidence));
|
|
10444
|
-
}
|
|
10445
|
-
var refineCommit = ahCommitEl(act.commit);
|
|
10446
|
-
if (refineCommit) refined.appendChild(el("div", { class: "ah-meta" }, [refineCommit]));
|
|
10447
|
-
return refined;
|
|
10448
|
-
}
|
|
10449
|
-
return ahGenericRow(act);
|
|
10471
|
+
var commit = ahCommitEl(e.commit);
|
|
10472
|
+
if (commit) box.appendChild(el("div", { class: "ah-meta" }, [commit]));
|
|
10473
|
+
return box;
|
|
10450
10474
|
}
|
|
10451
10475
|
/** 「AIによる最近の変更」の折りたたみ。該当が0件なら null(節ごと出さない) */
|
|
10452
10476
|
function buildAiHistory(rec) {
|
|
10453
|
-
var
|
|
10454
|
-
if (!
|
|
10455
|
-
var wc = SITE.weeklyCuration;
|
|
10456
|
-
var when = wc.ranAt ? aiHistoryWhen(wc.ranAt) + " の週次整理" : "週次整理";
|
|
10477
|
+
var list = aiHistoryEntries(rec);
|
|
10478
|
+
if (!list.length) return null;
|
|
10457
10479
|
var det = el("details", { class: "ai-history" }, [
|
|
10458
|
-
el("summary", { class: "ah-summary", text: "AIによる最近の変更(" +
|
|
10480
|
+
el("summary", { class: "ah-summary", text: "AIによる最近の変更(" + list.length + "件)" }),
|
|
10459
10481
|
]);
|
|
10460
|
-
|
|
10482
|
+
list.forEach(function (e) { det.appendChild(ahEntryEl(e)); });
|
|
10483
|
+
// 🔴 何を見せていて、何を見せられないのかを最後に断る
|
|
10484
|
+
det.appendChild(el("p", {
|
|
10485
|
+
class: "ah-source",
|
|
10486
|
+
text: "この一覧はリポジトリの変更履歴(コミット)から作っています。AIがなぜそう直したかの根拠は履歴に残らないため、変わった行とコミットだけを出しています。件数が多いときは新しいほうから5件までです。",
|
|
10487
|
+
}));
|
|
10461
10488
|
return det;
|
|
10462
10489
|
}
|
|
10463
10490
|
|
|
@@ -10490,9 +10517,10 @@ ${deriveCapTile.toString()}
|
|
|
10490
10517
|
/**
|
|
10491
10518
|
* 確認の文面。**何が起きるかを具体に書く**(消えるだけなのか、除外リストにも載るのか)。
|
|
10492
10519
|
*
|
|
10493
|
-
*
|
|
10494
|
-
*
|
|
10495
|
-
*
|
|
10520
|
+
* **status による出し分けはもう無い。** 2026-08-28 のオーナー裁定は最終的に
|
|
10521
|
+
* 「消せるのは draft だけ」へ落ち着いた(確認済みの記録に重い警告を出して消させる、
|
|
10522
|
+
* ではなく、口ごと出さない)。判定は draftNote の出し分けとサーバの門にあるので、
|
|
10523
|
+
* ここは「何が起きるか」だけを書く。
|
|
10496
10524
|
*/
|
|
10497
10525
|
function deleteConfirmLines(rec, path) {
|
|
10498
10526
|
var lines = ["このレコードを削除しますか?", rec.title];
|
|
@@ -10500,10 +10528,9 @@ ${deriveCapTile.toString()}
|
|
|
10500
10528
|
if (label) {
|
|
10501
10529
|
lines.push("削除すると、夜間のAIが同じ内容を再登録しないよう、" + label + "の除外リストにも追記されます。");
|
|
10502
10530
|
}
|
|
10503
|
-
if (rec.rstatus === "active") lines.push("これは人が確認済みにした記録です。");
|
|
10504
10531
|
return lines;
|
|
10505
10532
|
}
|
|
10506
|
-
async function sendDeleteRecord(path, msg, btn, onOk) {
|
|
10533
|
+
async function sendDeleteRecord(rec, path, msg, btn, onOk) {
|
|
10507
10534
|
var intake = SITE.intake;
|
|
10508
10535
|
if (!intake) return;
|
|
10509
10536
|
function setError(text) {
|
|
@@ -10534,6 +10561,8 @@ ${deriveCapTile.toString()}
|
|
|
10534
10561
|
// **なっている**ので、失敗として見せると「押しても消えない」堂々巡りになる。
|
|
10535
10562
|
// **符号で見る**(promote の ALREADY_ACTIVE と同じ理由)
|
|
10536
10563
|
if (res.ok || (res.status === 409 && j.code === "ALREADY_DELETED")) {
|
|
10564
|
+
// このブラウザでは即座に消えたことにする(次のビルドまで SiteData は古い)
|
|
10565
|
+
rememberDeleted(rec, path);
|
|
10537
10566
|
endBusy();
|
|
10538
10567
|
onOk();
|
|
10539
10568
|
return;
|
|
@@ -10559,52 +10588,220 @@ ${deriveCapTile.toString()}
|
|
|
10559
10588
|
rec.deleted = true;
|
|
10560
10589
|
var title = app.querySelector(".detail-title");
|
|
10561
10590
|
if (title) title.classList.add("is-deleted");
|
|
10562
|
-
|
|
10563
|
-
|
|
10591
|
+
// **置き場は2つある**(draft の警告帯の中/上の操作列)。どちらに出ていても止める
|
|
10592
|
+
var btns = app.querySelectorAll(".record-delete-btn");
|
|
10593
|
+
for (var b = 0; b < btns.length; b++) { btns[b].disabled = true; btns[b].textContent = "削除しました"; }
|
|
10564
10594
|
// もう無い記録を直す口は押させない(AIで編集・GitHubで編集・確認済みにする)
|
|
10565
10595
|
var links = app.querySelectorAll(".detail-actions a.ai-edit-btn, .detail-actions a.edit-btn");
|
|
10566
10596
|
for (var i = 0; i < links.length; i++) disableDetailLink(links[i]);
|
|
10567
10597
|
var promote = app.querySelector(".draft-note .promote-btn");
|
|
10568
10598
|
if (promote) promote.disabled = true;
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10599
|
+
if (app.querySelector(".deleted-note")) return;
|
|
10600
|
+
var note = el("div", { class: "deleted-note" }, [
|
|
10601
|
+
el("span", { text: "🗑" }),
|
|
10602
|
+
el("span", { text: "削除しました。次のビルド(数分後)でサイトから消えます。" }),
|
|
10603
|
+
]);
|
|
10604
|
+
// 押した口の近くに出す。警告帯があるならその中、無ければ見出しの直下
|
|
10605
|
+
var banner = app.querySelector(".draft-note");
|
|
10606
|
+
if (banner) { banner.appendChild(note); return; }
|
|
10607
|
+
var meta = app.querySelector(".detail-meta");
|
|
10608
|
+
if (meta && meta.parentNode) meta.parentNode.insertBefore(note, meta.nextSibling);
|
|
10576
10609
|
}
|
|
10577
10610
|
/**
|
|
10578
|
-
*
|
|
10579
|
-
*
|
|
10611
|
+
* 人の確認印が付いた記録か。**3値の意味は infra の HUMAN_CONFIRMED_STATUSES と同じ**
|
|
10612
|
+
* (active = 人が確認済みにした印/superseded = active を経た記録の差し替え後/
|
|
10613
|
+
* inactive = 人が退場を記した印)。ここは「押しても断られるボタンを出さない」ための
|
|
10614
|
+
* 出し分けで、**守りはサーバ側**(promote の PROMOTABLE_PATH と同じ関係)。
|
|
10580
10615
|
*/
|
|
10581
|
-
function
|
|
10616
|
+
function humanConfirmedStatus(rec) {
|
|
10617
|
+
var s = rec && rec.rstatus;
|
|
10618
|
+
return s === "active" || s === "superseded" || s === "inactive";
|
|
10619
|
+
}
|
|
10620
|
+
/**
|
|
10621
|
+
* 削除の口(ボタンと、その結果を出す行)。**置き場は呼び出し側が決める。**
|
|
10622
|
+
*
|
|
10623
|
+
* 出さないのは**人の確認印が付いた記録だけ**(2026-08-28 の裁定変更)。原則は
|
|
10624
|
+
* 「人間の承認が挟まったものは削除の選択肢を出さない」で、逆に status が無い記録・
|
|
10625
|
+
* frontmatter が壊れた記録は**画面から片付けたい掃除の主対象**なので消せる。
|
|
10626
|
+
*
|
|
10627
|
+
* 呼び出し側は2つ:
|
|
10628
|
+
* - draftNote …… 未確認の記録。「確認済みにする」と並べて警告帯の中に置く
|
|
10629
|
+
* - 詳細ページの操作列 …… 警告帯が出ない記録。置き場が他に無いのでここに寄せる
|
|
10630
|
+
*/
|
|
10631
|
+
function buildDeleteControls(rec) {
|
|
10632
|
+
if (humanConfirmedStatus(rec)) return null;
|
|
10582
10633
|
var path = deletableRecordPath(rec);
|
|
10583
10634
|
if (!path) return null;
|
|
10584
|
-
|
|
10585
|
-
|
|
10635
|
+
// **語尾の「…」は 2026-08-29 のオーナーFBで外した。** 二択の相方(「確認済みにする」)と
|
|
10636
|
+
// 語の形が揃わず、片方だけが下書きのように見えていた。押した先に確認モーダルが必ず出ることは
|
|
10637
|
+
// 語尾ではなく操作そのもの(confirmDanger を await する下の行)で担保している
|
|
10638
|
+
var btn = el("button", { class: "record-delete-btn", type: "button", text: "削除する" });
|
|
10586
10639
|
var msg = el("div", { class: "record-delete-msg" });
|
|
10587
10640
|
btn.addEventListener("click", async function () {
|
|
10588
10641
|
// **確認を await する。** 落とすと Promise が truthy で確認なしに消える
|
|
10589
10642
|
if (!(await confirmDanger(deleteConfirmLines(rec, path), { okLabel: "削除する", returnTo: btn }))) return;
|
|
10590
|
-
await sendDeleteRecord(path, msg, btn, function () { markRecordDeleted(rec); });
|
|
10643
|
+
await sendDeleteRecord(rec, path, msg, btn, function () { markRecordDeleted(rec); });
|
|
10591
10644
|
});
|
|
10592
|
-
|
|
10593
|
-
wrap.appendChild(msg);
|
|
10594
|
-
return wrap;
|
|
10645
|
+
return { btn: btn, msg: msg };
|
|
10595
10646
|
}
|
|
10596
|
-
/**
|
|
10647
|
+
/**
|
|
10648
|
+
* 警告帯が出ない記録の削除の口を、上の操作列(AIで編集・GitHubで編集の行)の末尾へ置く。
|
|
10649
|
+
*
|
|
10650
|
+
* **draft には出さない**——そちらは警告帯の中で「確認済みにする」と対になっている
|
|
10651
|
+
* (2つ出すと、同じ操作の口がページに2か所できる)。取り消せない操作を高頻度の
|
|
10652
|
+
* 操作の隣に置くことにはなるが、**警告帯の無い記録には他に置き場が無い**。
|
|
10653
|
+
* 見た目は控えめな文字のボタンのままにして、隣の実体のあるボタンと重さを分ける。
|
|
10654
|
+
*/
|
|
10655
|
+
function attachDeleteToEditRow(rec, editRow) {
|
|
10656
|
+
if (!rec || rec.rstatus === "draft") return null;
|
|
10657
|
+
var del = buildDeleteControls(rec);
|
|
10658
|
+
if (!del) return null;
|
|
10659
|
+
editRow.appendChild(del.btn);
|
|
10660
|
+
return del.msg; // 結果を出す行は操作列の下(呼び出し側が最後に並べる)
|
|
10661
|
+
}
|
|
10662
|
+
/** 詳細ページの末尾(本文・提案・frontmatter の後)に、AIの変更履歴と frontmatter を並べる */
|
|
10597
10663
|
function appendRecordFooter(rec) {
|
|
10598
10664
|
var history = buildAiHistory(rec);
|
|
10599
10665
|
if (history) app.appendChild(history);
|
|
10600
10666
|
var fmp = renderFmPanel(rec.fm);
|
|
10601
10667
|
if (fmp) app.appendChild(fmp);
|
|
10602
|
-
var del = buildDeleteSection(rec);
|
|
10603
|
-
if (del) app.appendChild(del);
|
|
10604
10668
|
// 開き直しても削除済みのまま見せる(印は rec が持っている)
|
|
10605
10669
|
if (rec.deleted) markRecordDeleted(rec);
|
|
10606
10670
|
}
|
|
10607
10671
|
|
|
10672
|
+
/* ---------------------------------------------------------------------------
|
|
10673
|
+
* 削除の即時反映(トゥームストーン)
|
|
10674
|
+
* ---------------------------------------------------------------------------
|
|
10675
|
+
* 静的サイトなので、削除の commit が入っても**次のビルドまで SiteData は古いまま**。
|
|
10676
|
+
* 何もしないと「消したのに、一覧にもつながりにもまだ居る」ように見える。
|
|
10677
|
+
* そこで削除した記録をこのブラウザに覚えておき、描画の手前で間引く。
|
|
10678
|
+
*
|
|
10679
|
+
* **覚えるのは押した本人のブラウザだけ。** 他の閲覧者には次のビルドまで見えたまま
|
|
10680
|
+
* (2026-08-28 の裁定で了解済みの範囲)。サーバに人ごとの表示状態を持たせない。
|
|
10681
|
+
*
|
|
10682
|
+
* **いつまでも隠し続けない。** 隠しっぱなしにすると、同じ記録を作り直したのに
|
|
10683
|
+
* 「作ったはずのものが出てこない」状態が永久に続く(破棄の条件は applyDeleteTombstones)。
|
|
10684
|
+
*/
|
|
10685
|
+
function deletedStoreKey() { return "cortex-deleted:" + REPO_SLUG; }
|
|
10686
|
+
/** 「削除より後に始まったビルドか」を判断するときの余裕(下の tombstoneStale) */
|
|
10687
|
+
var TOMBSTONE_MARGIN_MS = 30 * 60 * 1000;
|
|
10688
|
+
|
|
10689
|
+
function readTombstones() {
|
|
10690
|
+
try {
|
|
10691
|
+
var raw = localStorage.getItem(deletedStoreKey());
|
|
10692
|
+
var list = raw ? JSON.parse(raw) : [];
|
|
10693
|
+
return Object.prototype.toString.call(list) === "[object Array]" ? list : [];
|
|
10694
|
+
} catch (e) { return []; }
|
|
10695
|
+
}
|
|
10696
|
+
function writeTombstones(list) {
|
|
10697
|
+
try { localStorage.setItem(deletedStoreKey(), JSON.stringify(list)); } catch (e) {}
|
|
10698
|
+
}
|
|
10699
|
+
/**
|
|
10700
|
+
* 削除できたことを覚える。**同じパスは足さない**(押し直しで配列が伸びない)。
|
|
10701
|
+
*
|
|
10702
|
+
* gen0(削除操作をした画面のビルド時刻)も残す。破棄の判定をできるだけ
|
|
10703
|
+
* **サーバ時刻同士の比較**にするため(下の tombstoneStale)。
|
|
10704
|
+
*/
|
|
10705
|
+
function rememberDeleted(rec, path) {
|
|
10706
|
+
var list = readTombstones();
|
|
10707
|
+
for (var i = 0; i < list.length; i++) {
|
|
10708
|
+
if (list[i] && list[i].path === path) return;
|
|
10709
|
+
}
|
|
10710
|
+
list.push({ path: path, id: (rec && rec.id) || "", at: Date.now(), gen0: SITE.generatedAt || "" });
|
|
10711
|
+
writeTombstones(list);
|
|
10712
|
+
}
|
|
10713
|
+
/**
|
|
10714
|
+
* ビルド時刻の文字列(cli.ts が入れる "YYYY-MM-DD HH:mm UTC")をミリ秒に直す。
|
|
10715
|
+
* 読めなければ NaN——呼び出し側は**隠し続ける側に倒す**(嘘の再表示より安全)。
|
|
10716
|
+
*/
|
|
10717
|
+
function siteTimeMs(s) {
|
|
10718
|
+
if (typeof s !== "string" || !s) return NaN;
|
|
10719
|
+
return Date.parse(s.replace(" UTC", "Z").replace(" ", "T"));
|
|
10720
|
+
}
|
|
10721
|
+
/**
|
|
10722
|
+
* このトゥームストーンはもう用済みか(=削除より後のビルドにも記録が残っている)。
|
|
10723
|
+
*
|
|
10724
|
+
* **generatedAt はコミット時点ではない。** 削除コミットより前の状態から始まった
|
|
10725
|
+
* ビルドが削除の後に完成すると、generatedAt は削除時刻より新しいのに削除は
|
|
10726
|
+
* 入っていない。ビルドの所要時間とクライアント時計のずれを見て 30 分の余裕を取る。
|
|
10727
|
+
* 副作用は「同じ記録を作り直した直後だけ、本人のブラウザで最大30分見えない」。
|
|
10728
|
+
*
|
|
10729
|
+
* **主軸はサーバ時刻同士の比較**(現行の generatedAt と、削除した画面の gen0)。
|
|
10730
|
+
* クライアントの時計が大きく狂っていても機能が丸ごとは死なない(進んだ時計なら
|
|
10731
|
+
* 再表示が遅れるだけ)。
|
|
10732
|
+
*/
|
|
10733
|
+
function tombstoneStale(t, nowMs) {
|
|
10734
|
+
if (isNaN(nowMs)) return false;
|
|
10735
|
+
var byAt = typeof t.at === "number" && nowMs > t.at + TOMBSTONE_MARGIN_MS;
|
|
10736
|
+
var gen0 = siteTimeMs(t.gen0);
|
|
10737
|
+
// gen0 を持たない古い形式のエントリは at だけで見る(後方互換・安全側)
|
|
10738
|
+
if (isNaN(gen0)) return byAt;
|
|
10739
|
+
return byAt && nowMs > gen0 + TOMBSTONE_MARGIN_MS;
|
|
10740
|
+
}
|
|
10741
|
+
/** トゥームストーンの相手を SITE から探す(id で引き、無ければパスで照合) */
|
|
10742
|
+
function findTombstoned(t) {
|
|
10743
|
+
if (t.id && byId[t.id]) return byId[t.id];
|
|
10744
|
+
if (!t.path) return null;
|
|
10745
|
+
var pool = (SITE.decisions || []).slice();
|
|
10746
|
+
(SITE.sections || []).forEach(function (sec) { pool = pool.concat(sec.records || []); });
|
|
10747
|
+
for (var i = 0; i < pool.length; i++) {
|
|
10748
|
+
if (repoPathOf(pool[i]) === t.path) return pool[i];
|
|
10749
|
+
}
|
|
10750
|
+
return null;
|
|
10751
|
+
}
|
|
10752
|
+
/**
|
|
10753
|
+
* ページを開いたときに1回だけ走らせる。**残すのは「まだ SiteData に居て、かつ
|
|
10754
|
+
* 削除より後のビルドでは消えているはず」のものだけ**:
|
|
10755
|
+
*
|
|
10756
|
+
* 1. 相手が SiteData に居ない → ビルドに反映済み。エントリを捨てる
|
|
10757
|
+
* 2. 居るが、削除より後のビルドにも居る → 作り直された(または revert)。
|
|
10758
|
+
* 捨てて**表示に戻す**(隠し続けると「作り直したのに出ない」になる)
|
|
10759
|
+
* 3. それ以外 → 削除済みの印を立てる
|
|
10760
|
+
*/
|
|
10761
|
+
function applyDeleteTombstones() {
|
|
10762
|
+
var list = readTombstones();
|
|
10763
|
+
if (!list.length) return;
|
|
10764
|
+
var nowMs = siteTimeMs(SITE.generatedAt);
|
|
10765
|
+
var kept = [];
|
|
10766
|
+
for (var i = 0; i < list.length; i++) {
|
|
10767
|
+
var t = list[i];
|
|
10768
|
+
if (!t || typeof t.path !== "string" || !t.path) continue;
|
|
10769
|
+
var rec = findTombstoned(t);
|
|
10770
|
+
if (!rec) continue;
|
|
10771
|
+
if (tombstoneStale(t, nowMs)) continue;
|
|
10772
|
+
rec.deleted = true;
|
|
10773
|
+
kept.push(t);
|
|
10774
|
+
}
|
|
10775
|
+
if (kept.length !== list.length) writeTombstones(kept);
|
|
10776
|
+
}
|
|
10777
|
+
applyDeleteTombstones();
|
|
10778
|
+
|
|
10779
|
+
/** 一覧に出すレコード。**描画の手前で間引く**——件数表示も絞り込みの選択肢も自然に揃う */
|
|
10780
|
+
function visibleRecords(items) {
|
|
10781
|
+
return (items || []).filter(function (r) { return !r.deleted; });
|
|
10782
|
+
}
|
|
10783
|
+
/**
|
|
10784
|
+
* 削除済みを除いたグラフ。**消費側で1行ずつ摘まむのではなく、ここ1箇所で作る**
|
|
10785
|
+
* ——グラフ・エクスプローラー・件数表示が別々に間引くと、片方だけ消え残る。
|
|
10786
|
+
*/
|
|
10787
|
+
function graphWithoutDeleted() {
|
|
10788
|
+
var g = SITE.graph || {};
|
|
10789
|
+
var nodes = g.nodes || [];
|
|
10790
|
+
var edges = g.edges || [];
|
|
10791
|
+
var gone = {};
|
|
10792
|
+
var any = false;
|
|
10793
|
+
nodes.forEach(function (n) {
|
|
10794
|
+
var rec = n.recordId ? byId[n.recordId] : null;
|
|
10795
|
+
if (rec && rec.deleted) { gone[n.id] = true; any = true; }
|
|
10796
|
+
});
|
|
10797
|
+
if (!any) return { nodes: nodes, edges: edges };
|
|
10798
|
+
return {
|
|
10799
|
+
nodes: nodes.filter(function (n) { return !gone[n.id]; }),
|
|
10800
|
+
// 削除した点に触れる辺も落とす(残すと相手のいない線が浮く)
|
|
10801
|
+
edges: edges.filter(function (e) { return !gone[e.from] && !gone[e.to]; }),
|
|
10802
|
+
};
|
|
10803
|
+
}
|
|
10804
|
+
|
|
10608
10805
|
// ---------- 汎用詳細 ----------
|
|
10609
10806
|
function renderGenericDetail(r) {
|
|
10610
10807
|
app.innerHTML = "";
|
|
@@ -10624,9 +10821,12 @@ ${deriveCapTile.toString()}
|
|
|
10624
10821
|
if (r.editUrl) {
|
|
10625
10822
|
editRowR.appendChild(el("a", { class: "edit-btn", href: r.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
10626
10823
|
}
|
|
10824
|
+
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
10825
|
+
var delMsgR = attachDeleteToEditRow(r, editRowR);
|
|
10627
10826
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
10628
10827
|
el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
|
|
10629
10828
|
editRowR.children.length ? editRowR : null,
|
|
10829
|
+
delMsgR,
|
|
10630
10830
|
]));
|
|
10631
10831
|
app.appendChild(head);
|
|
10632
10832
|
|
|
@@ -10658,16 +10858,17 @@ ${deriveCapTile.toString()}
|
|
|
10658
10858
|
// ---------- グラフ(force-directed / canvas) ----------
|
|
10659
10859
|
// Gold層(Cortexのレコード)だけ色付け。それ以外(課題・議事録・資料・外部リンク)は一律グレー
|
|
10660
10860
|
/* 濃紺の背景に小さな点で出るので、Gold 4区画+Home は明るい色で互いに離す
|
|
10661
|
-
*
|
|
10662
|
-
* 2026-08-
|
|
10861
|
+
* (決定事項=青・用語集=緑・メンバー=ピンク・ルール=黄・Home=オレンジ。
|
|
10862
|
+
* レポートは黄が近いのでシアンへ)。**Home とルールは 2026-08-29 のオーナーFBで
|
|
10863
|
+
* 入れ替えた**——案件の顔である Home のほうが強い色(オレンジ)を持つ。
|
|
10663
10864
|
* 凡例の表記は日本語に統一する(区画名は画面のタブ名と同じ言葉を使う) */
|
|
10664
10865
|
var GROUP_STYLE = {
|
|
10665
10866
|
decision: { color: "#60a5fa", label: "決定事項" },
|
|
10666
10867
|
term: { color: "#4ade80", label: "用語集" },
|
|
10667
10868
|
member: { color: "#f472b6", label: "メンバー" },
|
|
10668
|
-
rule: { color: "#
|
|
10869
|
+
rule: { color: "#fde047", label: "ルール" },
|
|
10669
10870
|
report: { color: "#22d3ee", label: "レポート" },
|
|
10670
|
-
overview: { color: "#
|
|
10871
|
+
overview: { color: "#fb923c", label: "Home" },
|
|
10671
10872
|
record: { color: "#0e7490", label: "その他レコード" },
|
|
10672
10873
|
silver: { color: "#9ca3af", label: "コンテキスト(課題・議事録・資料・リンク等)" },
|
|
10673
10874
|
bronze: { color: "#9ca3af", label: "コンテキスト(課題・議事録・資料・リンク等)" },
|
|
@@ -10682,7 +10883,9 @@ ${deriveCapTile.toString()}
|
|
|
10682
10883
|
var graphState = { showWeak: true, q: "" };
|
|
10683
10884
|
|
|
10684
10885
|
function renderGraph() {
|
|
10685
|
-
|
|
10886
|
+
// 削除済みを除いたグラフを1本作り、件数・凡例・描画の全部をこれで賄う
|
|
10887
|
+
var graph = graphWithoutDeleted();
|
|
10888
|
+
countEl.textContent = "点 " + graph.nodes.length + " / つながり " + graph.edges.length;
|
|
10686
10889
|
|
|
10687
10890
|
var filters = el("div", { class: "filters" });
|
|
10688
10891
|
var search = el("input", { type: "search", placeholder: "キーワードで探す(決定・用語・レポートは本文の中まで探します。見つかった点とそのつながりが光ります)" });
|
|
@@ -10699,7 +10902,7 @@ ${deriveCapTile.toString()}
|
|
|
10699
10902
|
var legend = el("div", { class: "graph-legend" });
|
|
10700
10903
|
var legendShown = {};
|
|
10701
10904
|
Object.keys(GROUP_STYLE).forEach(function (g) {
|
|
10702
|
-
var anyNode =
|
|
10905
|
+
var anyNode = graph.nodes.some(function (n) { return n.group === g; });
|
|
10703
10906
|
if (!anyNode || legendShown[GROUP_STYLE[g].label]) return;
|
|
10704
10907
|
legendShown[GROUP_STYLE[g].label] = true;
|
|
10705
10908
|
var item = el("span", {}, [el("span", { class: "dot" }), document.createTextNode(GROUP_STYLE[g].label)]);
|
|
@@ -10728,19 +10931,19 @@ ${deriveCapTile.toString()}
|
|
|
10728
10931
|
legend.appendChild(toggle);
|
|
10729
10932
|
app.appendChild(legend);
|
|
10730
10933
|
|
|
10731
|
-
startGraph(canvas, search);
|
|
10934
|
+
startGraph(canvas, search, graph);
|
|
10732
10935
|
}
|
|
10733
10936
|
|
|
10734
|
-
function startGraph(canvas, searchInput) {
|
|
10735
|
-
var allEdges =
|
|
10937
|
+
function startGraph(canvas, searchInput, graph) {
|
|
10938
|
+
var allEdges = graph.edges.filter(function (e) {
|
|
10736
10939
|
return graphState.showWeak || !WEAK_RELS[e.rel];
|
|
10737
10940
|
});
|
|
10738
10941
|
// 弱い辺を消したとき、レコード以外の孤立ノードは隠す
|
|
10739
10942
|
var used = {};
|
|
10740
10943
|
allEdges.forEach(function (e) { used[e.from] = true; used[e.to] = true; });
|
|
10741
10944
|
// ビルド時に算出済みの座標(n.x/n.y)があればそれを使う=ウォームアップ不要で開いた瞬間に整列済み。
|
|
10742
|
-
var preLaidOut =
|
|
10743
|
-
var nodes =
|
|
10945
|
+
var preLaidOut = graph.nodes.some(function (n) { return typeof n.x === "number"; });
|
|
10946
|
+
var nodes = graph.nodes
|
|
10744
10947
|
.filter(function (n) { return n.recordId || used[n.id]; })
|
|
10745
10948
|
.map(function (n, i) {
|
|
10746
10949
|
return { id: n.id, label: n.label, group: n.group, recordId: n.recordId, url: n.url,
|
|
@@ -11291,9 +11494,12 @@ ${deriveCapTile.toString()}
|
|
|
11291
11494
|
if (d.editUrl) {
|
|
11292
11495
|
editRowD.appendChild(el("a", { class: "edit-btn", href: d.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
11293
11496
|
}
|
|
11497
|
+
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
11498
|
+
var delMsgD = attachDeleteToEditRow(d, editRowD);
|
|
11294
11499
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
11295
11500
|
el("div", { class: "detail-actions-row" }, [ traceBtn ]),
|
|
11296
11501
|
editRowD.children.length ? editRowD : null,
|
|
11502
|
+
delMsgD,
|
|
11297
11503
|
]));
|
|
11298
11504
|
app.appendChild(head);
|
|
11299
11505
|
|
|
@@ -11362,10 +11568,13 @@ ${deriveCapTile.toString()}
|
|
|
11362
11568
|
supersedes: "置換", reference: "参照", source: "出典", link: "リンク",
|
|
11363
11569
|
};
|
|
11364
11570
|
var explorerState = { folder: null, path: [], q: "", sx: 0, autoRight: false };
|
|
11571
|
+
// 索引もグラフ本体と同じ材料(削除済みを除いたもの)から作る。別々に間引くと、
|
|
11572
|
+
// つながり画面から消した点がエクスプローラーの列にだけ残る
|
|
11573
|
+
var explorerGraph = graphWithoutDeleted();
|
|
11365
11574
|
var nodeById = {};
|
|
11366
|
-
|
|
11575
|
+
explorerGraph.nodes.forEach(function (n) { nodeById[n.id] = n; });
|
|
11367
11576
|
var adjDir = {};
|
|
11368
|
-
|
|
11577
|
+
explorerGraph.edges.forEach(function (e) {
|
|
11369
11578
|
(adjDir[e.from] = adjDir[e.from] || []).push({ id: e.to, rel: e.rel, dir: "out" });
|
|
11370
11579
|
(adjDir[e.to] = adjDir[e.to] || []).push({ id: e.from, rel: e.rel, dir: "in" });
|
|
11371
11580
|
});
|
|
@@ -11382,9 +11591,10 @@ ${deriveCapTile.toString()}
|
|
|
11382
11591
|
// Gold層のフォルダ(1列目)。検索ヒット表示と使い分ける
|
|
11383
11592
|
var goldGroups = [];
|
|
11384
11593
|
if (SITE.home) goldGroups.push({ name: "Home", ids: [SITE.home.id] });
|
|
11385
|
-
|
|
11594
|
+
// 削除済みは列にも件数にも出さない(一覧・グラフと同じ扱いに揃える)
|
|
11595
|
+
goldGroups.push({ name: "Decisions", ids: visibleRecords(SITE.decisions).map(function (d) { return d.id; }) });
|
|
11386
11596
|
(SITE.sections || []).forEach(function (sec) {
|
|
11387
|
-
goldGroups.push({ name: sec.name, ids: sec.records.map(function (r) { return r.id; }) });
|
|
11597
|
+
goldGroups.push({ name: sec.name, ids: visibleRecords(sec.records).map(function (r) { return r.id; }) });
|
|
11388
11598
|
});
|
|
11389
11599
|
|
|
11390
11600
|
function renderExplorer() {
|