@takagaki/cortex-decisions-viewer 0.12.115 → 0.12.116

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 CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
39
+ exports.DECISION_FIRMNESS = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
40
40
  exports.sanitizeBody = sanitizeBody;
41
41
  exports.findProjectIcon = findProjectIcon;
42
42
  exports.readFleetSources = readFleetSources;
@@ -55,6 +55,9 @@ exports.bodyFileSlug = bodyFileSlug;
55
55
  exports.bodyStampId = bodyStampId;
56
56
  exports.bodyStampComment = bodyStampComment;
57
57
  exports.build = build;
58
+ exports.decisionFirmnessOf = decisionFirmnessOf;
59
+ exports.decisionTopicOf = decisionTopicOf;
60
+ exports.decidedByOf = decidedByOf;
58
61
  exports.createRefResolver = createRefResolver;
59
62
  const node_crypto_1 = require("node:crypto");
60
63
  const node_fs_1 = require("node:fs");
@@ -1523,6 +1526,16 @@ async function buildEvidenceBook(repoRoot, targetUrls, externalSources = []) {
1523
1526
  if (key && name && !book.names.has(key))
1524
1527
  book.names.set(key, name);
1525
1528
  };
1529
+ // 片付けで移した議事録のリンク(URL の鍵 → 「定例名 日付」・cortex-engine #968 R-6・A-R6-3)。表が無い・読めない案件は今どおり
1530
+ // (種類の既定の名前)。資料より先に載せる(同じ鍵に資料の題名が先に付かないように)
1531
+ try {
1532
+ const table = JSON.parse(await node_fs_1.promises.readFile(path.join(repoRoot, evidence_names_1.MOVED_MEETINGS_FILE), "utf8"));
1533
+ for (const [url, name] of (0, evidence_names_1.movedMeetingNamesOf)(table))
1534
+ putName(foldKeyOf(url), name);
1535
+ }
1536
+ catch {
1537
+ // 表が無い(片付けていない案件)・壊れている
1538
+ }
1526
1539
  // 課題(キー → 「キー 題名」)・Backlog のドキュメントと Wiki(URL の鍵 → 題名)
1527
1540
  const backlogDir = await findBacklogDir(repoRoot, "課題管理");
1528
1541
  for (const sub of BACKLOG_SECTIONS) {
@@ -2024,6 +2037,17 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
2024
2037
  addReferenceEdges(d.id, d.references);
2025
2038
  addLinkEdges(d.id, d.bodyLinks);
2026
2039
  }
2040
+ // 決めた人の線(決定 → 関係者・#1094)。relations には書かれないので `decided_by[].member` から引く。
2041
+ // 🔴 相手の関係者がグラフに無ければ引かない(宛先の無い点を作らない=classifyTarget を通さない)
2042
+ for (const d of decisions) {
2043
+ for (const who of d.decidedBy ?? []) {
2044
+ if (!("member" in who))
2045
+ continue;
2046
+ const node = nodes.get(who.member);
2047
+ if (node && node.group === "member")
2048
+ edges.push({ from: d.id, to: who.member, rel: "decided_by" });
2049
+ }
2050
+ }
2027
2051
  if (home) {
2028
2052
  addRelationEdges(home.id, home.relations);
2029
2053
  addSourceEdge(home.id, home.source);
@@ -2705,6 +2729,10 @@ async function parseDecision(raw, fileName, repoRelDir, repoBaseUrl, branch, ref
2705
2729
  const references = referencesRaw.map((r) => refs.resolve(r));
2706
2730
  const relations = toRelations(data.relations);
2707
2731
  const followupHuman = (0, rederivation_1.humanFollowupOf)(data.followup);
2732
+ // 記憶 v2 の任意の欄(#1094)。**欄が無い・語彙外の記録ではキーごと載せない**(埋め込みの形を今と同じに保つ)
2733
+ const firmness = decisionFirmnessOf(data.firmness);
2734
+ const topic = decisionTopicOf(data.topic);
2735
+ const decidedBy = decidedByOf(data.decided_by);
2708
2736
  // 本文先頭の H1 はタイトルと重複するため除去(詳細画面ではタイトルを別途表示する)
2709
2737
  const bodyHtml = await renderRecordBody(content);
2710
2738
  const filePath = repoRelDir ? `${repoRelDir}/${fileName}` : fileName;
@@ -2735,8 +2763,61 @@ async function parseDecision(raw, fileName, repoRelDir, repoBaseUrl, branch, ref
2735
2763
  fmErrors: validateFm(data),
2736
2764
  // 担当者の答え(#705)。**語彙外・形が違うものはキーごと載せない**
2737
2765
  ...(followupHuman ? { followupHuman: followupHuman } : {}),
2766
+ ...(firmness ? { firmness } : {}),
2767
+ ...(topic ? { topic } : {}),
2768
+ ...(decidedBy ? { decidedBy } : {}),
2738
2769
  };
2739
2770
  }
2771
+ /**
2772
+ * 記憶 v2 の決定の新しい欄の読み手(#1094)。
2773
+ *
2774
+ * 🔴 **写し。正本は cortex-engine `scripts/gold/decision-v2-fields.mjs` の `decisionV2FieldsOf`**。
2775
+ * 同じバイト列の表 `test/fixtures/decision-v2-fields-contract.json` を両リポに置き、
2776
+ * `test/supersedes-view.test.mjs` の「[契約] …」が全ケースを流す。規則を変えるときは engine の正本・この写し・表を同じ日に直す
2777
+ */
2778
+ /** 決定の固さの語彙(並びも正本と同じ) */
2779
+ exports.DECISION_FIRMNESS = ["確定", "仮", "検討中"];
2780
+ /** `decided_by` の要素が持ってよい鍵 */
2781
+ const DECIDED_BY_KEYS = ["member", "name", "url"];
2782
+ const MEMBER_ID_RE = /^member:\S/;
2783
+ const DECIDED_BY_URL_RE = /^https?:\/\/\S+$/;
2784
+ const isText = (v) => typeof v === "string" && v.trim() !== "";
2785
+ /** frontmatter の `firmness` → 語彙の 1 語。語彙外・文字列でないものは undefined(綴りはそのまま比べる) */
2786
+ function decisionFirmnessOf(value) {
2787
+ return exports.DECISION_FIRMNESS.includes(value) ? value : undefined;
2788
+ }
2789
+ /** frontmatter の `topic` → 論点の札の名前(前後の空白を落とす)。空・文字列でないものは undefined */
2790
+ function decisionTopicOf(value) {
2791
+ return isText(value) ? value.trim() : undefined;
2792
+ }
2793
+ /** `decided_by` の要素 1 つ。読めなければ null(文字列・知らない鍵・member と name/url の同居・member: で始まらない・空の name・http(s) でない url) */
2794
+ function decidedByEntryOf(entry) {
2795
+ if (entry == null || typeof entry !== "object" || Array.isArray(entry))
2796
+ return null;
2797
+ const o = entry;
2798
+ if (Object.keys(o).some((k) => !DECIDED_BY_KEYS.includes(k)))
2799
+ return null;
2800
+ const has = (k) => Object.prototype.hasOwnProperty.call(o, k);
2801
+ if (has("member")) {
2802
+ if (has("name") || has("url"))
2803
+ return null;
2804
+ return typeof o.member === "string" && MEMBER_ID_RE.test(o.member) ? { member: o.member } : null;
2805
+ }
2806
+ if (!has("name") || !isText(o.name))
2807
+ return null;
2808
+ if (o.url == null)
2809
+ return { name: o.name };
2810
+ return typeof o.url === "string" && DECIDED_BY_URL_RE.test(o.url) ? { name: o.name, url: o.url } : null;
2811
+ }
2812
+ /**
2813
+ * frontmatter の `decided_by` → 決めた人の並び。🔴 形の崩れた要素は**飛ばす**。
2814
+ * 欄がリストでなければ undefined(無いものとして扱い、旧い `deciders` を出す)。空のリストは []
2815
+ */
2816
+ function decidedByOf(value) {
2817
+ if (!Array.isArray(value))
2818
+ return undefined;
2819
+ return value.map(decidedByEntryOf).filter((e) => e !== null);
2820
+ }
2740
2821
  /**
2741
2822
  * 解決した参照 → 書かれていた値(#968 tools 1)。つながり画面は点の鍵をこの値から決める
2742
2823
  * (`site-data` に載せない——機械の語を画面の材料に増やさない)
@@ -13,9 +13,10 @@
13
13
  * 対応表で URL に引けるものは同じ鍵に寄せる——今の ID と新しい URL の根拠が混ざっても 1 つの点になる。
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.emptyEvidenceBook = void 0;
16
+ exports.MOVED_MEETINGS_FILE = exports.emptyEvidenceBook = void 0;
17
17
  exports.minuteFoldKey = minuteFoldKey;
18
18
  exports.minuteLabel = minuteLabel;
19
+ exports.movedMeetingNamesOf = movedMeetingNamesOf;
19
20
  exports.minuteRefOfPath = minuteRefOfPath;
20
21
  exports.slackChannelOfLabel = slackChannelOfLabel;
21
22
  exports.backlogTailOf = backlogTailOf;
@@ -55,6 +56,34 @@ function minuteLabel(id) {
55
56
  const d = m[2];
56
57
  return `${lastSegment(m[1])} ${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`;
57
58
  }
59
+ /**
60
+ * 片付けで移した会議の表(engine が目次と同じコミットで置く・cortex-engine #968 R-6・A-R6-3)。
61
+ * 写しを片付けた案件だけが持つ。無ければ今どおり(移した議事録のリンクは種類の既定の名前で出る)
62
+ */
63
+ exports.MOVED_MEETINGS_FILE = ".cortex/mirror-moved-meetings.json";
64
+ /**
65
+ * 移した会議の表 → `[移した URL, 会議の名前(「定例名 日付」)]` の並び。形が違う表・行は読まない(名前を当て推量しない)。
66
+ * 名前は `minuteLabel`(`minute:` の根拠と同じ 1 本)で作る——同じ会議が `minute:` で書かれても URL で書かれても同じ名前になる
67
+ */
68
+ function movedMeetingNamesOf(table) {
69
+ if (!table || typeof table !== "object")
70
+ return [];
71
+ const t = table;
72
+ if (t.version !== 1 || !Array.isArray(t.meetings))
73
+ return [];
74
+ const out = [];
75
+ for (const row of t.meetings) {
76
+ if (!row || typeof row !== "object")
77
+ continue;
78
+ const { url, ref } = row;
79
+ if (typeof url !== "string" || !/^https:\/\/\S+$/.test(url) || typeof ref !== "string")
80
+ continue;
81
+ const name = minuteLabel(ref);
82
+ if (name)
83
+ out.push([url, name]);
84
+ }
85
+ return out;
86
+ }
58
87
  /**
59
88
  * 議事録の写しのパス(`会議/{…}/{定例名}/{YYYYMMDD}/{…}minutes{…}.md`)→ `minute:{定例名}:{YYYYMMDD}`。
60
89
  * `buildTargetUrlMap` の `minute:` の組み方と同じ規約(定例名は日付の 1 つ上の段)
package/dist/render.js CHANGED
@@ -5314,6 +5314,15 @@ a.pager-btn:hover { text-decoration: none; }
5314
5314
  .followup-section { margin: 14px 0 4px; }
5315
5315
  /* 置き換え(#699 F3)。印は「その後」の置き換え済みと同じ色(同じ意味なので) */
5316
5316
  .badge.retired { background: #f5f5f4; color: #57534e; border: 1px solid #d6d3d1; }
5317
+ /* 論点・固さ・「今」(#1094)。欄の無い記録には付かない */
5318
+ .badge.now { background: #1d4ed8; color: #fff; }
5319
+ .card.is-now { border-left: 3px solid #1d4ed8; }
5320
+ .card.is-faded { opacity: .55; }
5321
+ .badge.topic { background: #f5f3ff; color: #5b21b6; border: 1px solid #ddd6fe; font-weight: 500; }
5322
+ .badge.firmness-fixed { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; }
5323
+ .badge.firmness-tentative { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
5324
+ .badge.firmness-open { background: #f8fafc; color: #475569; border: 1px dashed #cbd5e1; }
5325
+ .decision-topic { margin-bottom: 4px; }
5317
5326
  .supersede-section { margin: 14px 0 4px; }
5318
5327
  .supersede-list { list-style: none; margin: 0 0 8px; padding: 0; }
5319
5328
  .supersede-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; font-size: 13.5px; line-height: 1.7; padding: 2px 0; }
@@ -7360,6 +7369,110 @@ ${calendarAddUrl.toString()}
7360
7369
  return box;
7361
7370
  }
7362
7371
 
7372
+ /* -------------------------------------------------------------------------
7373
+ * 論点・固さ・決めた人(記憶 v2・cortex-engine #1094)
7374
+ * -------------------------------------------------------------------------
7375
+ * 欄(topic / firmness / decidedBy)は**ビルド側で語彙・形を確かめてある**。欄の無い記録では
7376
+ * キーごと無いので、画面は有無だけを見る。🔴 **欄の無い記録だけの案件では要素を 1 つも作らない**
7377
+ * (絞り込み・「今」・固さの札・決めた人の節が出ない=今と同じ DOM)。
7378
+ * 「置き換えられた」の判定は isRetired(ビルドが supersedes.ts で組んだ答え)をそのまま使う。
7379
+ */
7380
+ function hasTopic(rec) {
7381
+ return !!(rec && typeof rec.topic === "string" && rec.topic);
7382
+ }
7383
+ /** 論点の札の一覧(札を持つ決定が無ければ空) */
7384
+ function topicOptions(items) {
7385
+ var seen = {};
7386
+ var out = [];
7387
+ (items || []).forEach(function (d) {
7388
+ if (!hasTopic(d) || Object.prototype.hasOwnProperty.call(seen, d.topic)) return;
7389
+ seen[d.topic] = true;
7390
+ out.push(d.topic);
7391
+ });
7392
+ return out.sort(function (a, b) { return a.localeCompare(b, "ja"); });
7393
+ }
7394
+ /** 新しい順(決定日の降順 → ID の降順)。日付の無いものは最後 */
7395
+ function newestDecisionFirst(a, b) {
7396
+ if (a.date !== b.date) {
7397
+ if (!a.date) return 1;
7398
+ if (!b.date) return -1;
7399
+ return a.date < b.date ? 1 : -1;
7400
+ }
7401
+ return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
7402
+ }
7403
+ /**
7404
+ * 札ごとの「今」の決定(一番新しく、置き換えられていない 1 件)の ID の集合。
7405
+ * 削除済みは数えない(一覧と同じ材料)
7406
+ */
7407
+ function nowDecisionIds() {
7408
+ var best = {};
7409
+ visibleRecords(SITE.decisions).forEach(function (d) {
7410
+ if (!hasTopic(d) || isRetired(d)) return;
7411
+ var cur = best[d.topic];
7412
+ if (!cur || newestDecisionFirst(d, cur) < 0) best[d.topic] = d;
7413
+ });
7414
+ var ids = {};
7415
+ Object.keys(best).forEach(function (t) { ids[best[t].id] = true; });
7416
+ return ids;
7417
+ }
7418
+ function isNowDecision(rec) {
7419
+ return !!(rec && rec._kind === "decision" && hasTopic(rec) && nowDecisionIds()[rec.id]);
7420
+ }
7421
+ function nowBadge(rec) {
7422
+ return isNowDecision(rec) ? el("span", { class: "badge now", title: "この論点で今有効な決定", text: "今" }) : null;
7423
+ }
7424
+ /** 論点の札(一覧の行)。札が無ければ null */
7425
+ function topicBadge(rec) {
7426
+ return hasTopic(rec) ? el("span", { class: "badge topic", title: "論点", text: rec.topic }) : null;
7427
+ }
7428
+ var FIRMNESS_CLASS = { "確定": "fixed", "仮": "tentative", "検討中": "open" };
7429
+ /** 固さの小札。語彙外は出さない(ビルド側でも捨てている・二重の網) */
7430
+ function firmnessBadge(rec) {
7431
+ var f = rec && rec.firmness;
7432
+ if (typeof f !== "string" || !Object.prototype.hasOwnProperty.call(FIRMNESS_CLASS, f)) return null;
7433
+ return el("span", { class: "badge firmness firmness-" + FIRMNESS_CLASS[f], title: "固さ", text: f });
7434
+ }
7435
+ /** 置き換えられた決定を薄く出すか(論点の札を持つ決定だけ。欄の無い記録の見た目は変えない) */
7436
+ function fadedClass(rec) {
7437
+ return hasTopic(rec) && isRetired(rec) ? " is-faded" : "";
7438
+ }
7439
+ /** 決めた人 1 人の札。関係者が名簿にあればそのページへ、{name, url} は url があれば元の URL へ */
7440
+ function decidedByChip(who) {
7441
+ if (!who || typeof who !== "object") return null;
7442
+ if (typeof who.member === "string") {
7443
+ var rec = byId[who.member];
7444
+ var fallback = who.member.slice("member:".length);
7445
+ if (rec && rec._kind === "generic") {
7446
+ var a = el("a", { class: "chip decided-by-member", href: "?id=" + encodeURIComponent(who.member), text: humanTitle(rec.title) || fallback });
7447
+ a.addEventListener("click", function (ev) {
7448
+ if (ev.defaultPrevented || ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return;
7449
+ ev.preventDefault();
7450
+ go(who.member);
7451
+ });
7452
+ return a;
7453
+ }
7454
+ return el("span", { class: "chip", text: fallback });
7455
+ }
7456
+ if (typeof who.name !== "string" || !who.name) return null;
7457
+ var raw = typeof who.url === "string" ? who.url.toLowerCase() : "";
7458
+ var url = raw.indexOf("http://") === 0 || raw.indexOf("https://") === 0 ? who.url : "";
7459
+ if (url) return el("a", { class: "chip decided-by-link", href: url, target: "_blank", rel: "noopener", text: who.name });
7460
+ return el("span", { class: "chip", text: who.name });
7461
+ }
7462
+ /** 検索に使う決めた人の表記(decidedBy があればそちら、無ければ deciders) */
7463
+ function deciderWords(d) {
7464
+ if (Array.isArray(d.decidedBy) && d.decidedBy.length) {
7465
+ return d.decidedBy.map(function (w) {
7466
+ if (w && typeof w.member === "string") {
7467
+ var r = byId[w.member];
7468
+ return r && r.title ? r.title : w.member.slice("member:".length);
7469
+ }
7470
+ return w && typeof w.name === "string" ? w.name : "";
7471
+ }).join(" ");
7472
+ }
7473
+ return (d.deciders || []).join(" ");
7474
+ }
7475
+
7363
7476
  // 未確認が1件も無い一覧では絞り込みを出さない
7364
7477
  function hasUnverified(items) {
7365
7478
  return (items || []).some(function (r) { return machineOwned(confirmPathOf(r), r.rstatus); });
@@ -8680,7 +8793,7 @@ ${calendarAddUrl.toString()}
8680
8793
  }
8681
8794
  }
8682
8795
 
8683
- var listState = { tab: DEFAULT_TAB, q: "", category: "", month: "", status: "", page: 1 };
8796
+ var listState = { tab: DEFAULT_TAB, q: "", category: "", month: "", status: "", topic: "", page: 1 };
8684
8797
 
8685
8798
  // 一覧のページネーション(描画コスト対策。1ページあたりの件数)
8686
8799
  var PAGE_SIZE = 30;
@@ -19304,6 +19417,13 @@ ${calendarAddUrl.toString()}
19304
19417
  if (statusOptsD.length) {
19305
19418
  filters.appendChild(buildSelect("すべての状態", statusOptsD, listState.status, function (v) { listState.status = v; listState.page = 1; drawCards(); }, statusLabel));
19306
19419
  }
19420
+ // 論点の札(#1094)。札を持つ決定が 1 件も無い案件では出さない
19421
+ var topicOpts = topicOptions(visibleRecords(SITE.decisions));
19422
+ if (topicOpts.length) {
19423
+ var topicSel = buildSelect("すべての論点", topicOpts, listState.topic, function (v) { listState.topic = v; listState.page = 1; drawCards(); });
19424
+ topicSel.classList.add("topic-filter");
19425
+ filters.appendChild(topicSel);
19426
+ }
19307
19427
  app.appendChild(filters);
19308
19428
 
19309
19429
  var addBarD = aiAddBar("Decisions");
@@ -19335,16 +19455,25 @@ ${calendarAddUrl.toString()}
19335
19455
  var q = listState.q.trim().toLowerCase();
19336
19456
  // 削除済みは絞り込みの手前で落とす(分母も一緒に減るので「12/13件」が残らない)
19337
19457
  var all = visibleRecords(SITE.decisions);
19338
- return all.filter(function (d) {
19458
+ var out = all.filter(function (d) {
19339
19459
  if (listState.month && d.date.slice(0, 7) !== listState.month) return false;
19340
19460
  if (listState.category && d.category !== listState.category) return false;
19341
19461
  if (!matchesStatusFilter(d, !!SITE.decisionFollowup)) return false;
19462
+ if (!matchesTopicFilter(d)) return false;
19342
19463
  if (q) {
19343
- var hay = (d.title + " " + d.summary + " " + d.searchText + " " + d.deciders.join(" ")).toLowerCase();
19464
+ var hay = (d.title + " " + d.summary + " " + d.searchText + " " + deciderWords(d)).toLowerCase();
19344
19465
  if (hay.indexOf(q) === -1) return false;
19345
19466
  }
19346
19467
  return true;
19347
19468
  });
19469
+ // 同じ論点の中は新しい順(決定日の降順 → ID の降順)
19470
+ if (listState.topic) out.sort(newestDecisionFirst);
19471
+ return out;
19472
+ }
19473
+ /** 論点の絞り込み(#1094)。選んでいなければ全件、選んだら同じ札の決定だけ */
19474
+ function matchesTopicFilter(d) {
19475
+ if (!listState.topic) return true;
19476
+ return hasTopic(d) && d.topic === listState.topic;
19348
19477
  }
19349
19478
 
19350
19479
  function drawCards() {
@@ -19367,6 +19496,7 @@ ${calendarAddUrl.toString()}
19367
19496
 
19368
19497
  pg.slice.forEach(function (d) {
19369
19498
  var meta = el("div", { class: "card-meta" }, [
19499
+ nowBadge(d),
19370
19500
  draftBadge(d),
19371
19501
  proposalBadge(d),
19372
19502
  followupBadge(d),
@@ -19374,13 +19504,15 @@ ${calendarAddUrl.toString()}
19374
19504
  latestBadge(d),
19375
19505
  el("span", { class: "mono", text: d.date || "----" }),
19376
19506
  catBadge(d.category),
19507
+ firmnessBadge(d),
19508
+ topicBadge(d),
19377
19509
  ]);
19378
19510
  var eb = fmErrorBadge(d);
19379
19511
  if (eb) meta.appendChild(eb);
19380
19512
  var traceD = el("a", { class: "mc-trace", href: "?tab=graph", text: "🕸 つながり" });
19381
19513
  traceD.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); openExplorerFor(d); });
19382
19514
  meta.appendChild(traceD);
19383
- var card = el("div", { class: "card", title: d.id, "data-record-id": d.id }, [
19515
+ var card = el("div", { class: "card" + fadedClass(d) + (isNowDecision(d) ? " is-now" : ""), title: d.id, "data-record-id": d.id }, [
19384
19516
  meta,
19385
19517
  // 消したレコードは一覧でも取り消し線(次のビルドまで一覧には残る)
19386
19518
  cardTitleLink(d, d.deleted ? " is-deleted" : ""),
@@ -20404,6 +20536,9 @@ ${calendarAddUrl.toString()}
20404
20536
  // 話題の線(決定・ルール・関係者 → 用語・engine #551 の about)。名前の一致で機械が付けた手がかりなので、
20405
20537
  // 根拠の線と同じく細く薄い「ゆるいつながり」として描き、凡例は日本語の 1 行(栓 about_relations の on の条件)
20406
20538
  about: { color: "#ddd6fe", label: "話題", kind: "topic" },
20539
+ // 決めた人の線(決定 → 関係者・#1094)。relations ではなく decided_by から引く。
20540
+ // 🔴 凡例には**この線がグラフにあるときだけ**出す(欄の無い案件の凡例を変えない)
20541
+ decided_by: { color: "#f472b6", label: "決めた人", kind: "gold", onlyWhenPresent: true },
20407
20542
  };
20408
20543
  var WEAK_RELS = {};
20409
20544
  var REL_COLOR = {};
@@ -20471,7 +20606,10 @@ ${calendarAddUrl.toString()}
20471
20606
  // 線(関係)の色凡例。グラフ内の有無に関わらず全種別を、Goldどうし/根拠の2群で表示する
20472
20607
  [["gold", "線(Goldどうし):"], ["evidence", "線(根拠):"], ["topic", "線(話題):"]].forEach(function (pair) {
20473
20608
  var kind = pair[0], heading = pair[1];
20474
- var keys = Object.keys(REL_META).filter(function (r) { return REL_META[r].kind === kind; });
20609
+ var keys = Object.keys(REL_META).filter(function (r) {
20610
+ if (REL_META[r].kind !== kind) return false;
20611
+ return !REL_META[r].onlyWhenPresent || graph.edges.some(function (e) { return e.rel === r; });
20612
+ });
20475
20613
  if (!keys.length) return;
20476
20614
  legend.appendChild(el("span", { class: "sep", text: heading }));
20477
20615
  keys.forEach(function (r) {
@@ -21019,6 +21157,7 @@ ${calendarAddUrl.toString()}
21019
21157
  relates_to: "rgba(148,163,184,0.6)", supersedes: "rgba(248,113,113,0.9)",
21020
21158
  reference: "rgba(180,196,230,0.32)", source: "rgba(180,196,230,0.32)", link: "rgba(180,196,230,0.22)",
21021
21159
  about: "rgba(196,181,253,0.22)",
21160
+ decided_by: "rgba(244,114,182,0.75)",
21022
21161
  };
21023
21162
  // 画面に入っている範囲(世界座標)。両端が同じ側の外にある線は描かない
21024
21163
  var em = 4 / view.scale; // 線の太さの分の余白(画面で 4px)
@@ -21270,12 +21409,14 @@ ${calendarAddUrl.toString()}
21270
21409
  app.appendChild(head);
21271
21410
 
21272
21411
  var meta = el("div", { class: "detail-meta" }, [
21412
+ nowBadge(d),
21273
21413
  draftBadge(d),
21274
21414
  proposalBadge(d),
21275
21415
  retiredBadge(d),
21276
21416
  latestBadge(d),
21277
21417
  el("span", { class: "mono", text: d.date || "----" }),
21278
21418
  catBadge(d.category),
21419
+ firmnessBadge(d),
21279
21420
  ]);
21280
21421
  meta.title = d.id;
21281
21422
  meta.setAttribute("data-record-id", d.id);
@@ -21295,7 +21436,18 @@ ${calendarAddUrl.toString()}
21295
21436
  app.appendChild(el("div", { class: "summary-box", text: d.summary }));
21296
21437
  }
21297
21438
 
21298
- if (d.deciders && d.deciders.length) {
21439
+ // 論点(#1094)。札の無い決定では何も作らない
21440
+ if (hasTopic(d)) {
21441
+ app.appendChild(el("div", { class: "section-label", text: "論点" }));
21442
+ app.appendChild(el("div", { class: "decision-topic" }, [el("span", { class: "chip", text: d.topic })]));
21443
+ }
21444
+
21445
+ // 決めた人(#1094)。decided_by に読めた人がいればそちらを出し、いなければ(欄が無い・空・全部崩れ)今どおり deciders
21446
+ var whoChips = Array.isArray(d.decidedBy) ? d.decidedBy.map(decidedByChip).filter(Boolean) : [];
21447
+ if (whoChips.length) {
21448
+ app.appendChild(el("div", { class: "section-label", text: "決めた人" }));
21449
+ app.appendChild(el("div", { class: "deciders decided-by" }, whoChips));
21450
+ } else if (d.deciders && d.deciders.length) {
21299
21451
  app.appendChild(el("div", { class: "section-label", text: "決定者" }));
21300
21452
  app.appendChild(el("div", { class: "deciders" }, d.deciders.map(function (p) {
21301
21453
  return el("span", { class: "chip", text: p });
@@ -21344,6 +21496,7 @@ ${calendarAddUrl.toString()}
21344
21496
  based_on: "根拠", derived_from: "由来", relates_to: "関連",
21345
21497
  supersedes: "置換", superseded_by: "置換先", reference: "参照", source: "出典", link: "リンク",
21346
21498
  about: "話題",
21499
+ decided_by: "決めた人",
21347
21500
  };
21348
21501
  var explorerState = { folder: null, path: [], q: "", sx: 0, autoRight: false };
21349
21502
  // 索引もグラフ本体と同じ材料(削除済みを除いたもの)から作る。別々に間引くと、
@@ -21356,7 +21509,7 @@ ${calendarAddUrl.toString()}
21356
21509
  (adjDir[e.from] = adjDir[e.from] || []).push({ id: e.to, rel: e.rel, dir: "out" });
21357
21510
  (adjDir[e.to] = adjDir[e.to] || []).push({ id: e.from, rel: e.rel, dir: "in" });
21358
21511
  });
21359
- var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "source", "reference", "link", "about"];
21512
+ var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "decided_by", "source", "reference", "link", "about"];
21360
21513
 
21361
21514
  function recordText(rec) {
21362
21515
  if (!rec) return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.115",
3
+ "version": "0.12.116",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,