@takagaki/cortex-decisions-viewer 0.12.113 → 0.12.114

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
@@ -50,6 +50,7 @@ exports.collectMeetings = collectMeetings;
50
50
  exports.findBacklogDir = findBacklogDir;
51
51
  exports.figmaUrlFromRef = figmaUrlFromRef;
52
52
  exports.identifyEvidence = identifyEvidence;
53
+ exports.attachGlow = attachGlow;
53
54
  exports.bodyFileSlug = bodyFileSlug;
54
55
  exports.bodyStampId = bodyStampId;
55
56
  exports.bodyStampComment = bodyStampComment;
@@ -66,6 +67,7 @@ const evidence_names_1 = require("./evidence-names");
66
67
  const repo_1 = require("./repo");
67
68
  const engagement_1 = require("./engagement");
68
69
  const story_1 = require("./story");
70
+ const ranking_1 = require("./ranking");
69
71
  const rederivation_1 = require("./rederivation");
70
72
  // 直近のGold昇格で追加されたレコードのパス集合。レコード生成の各所から参照するのでモジュールスコープに置く
71
73
  // (引数で引き回すと呼び出し側の改修が広範囲になる)。build() の冒頭で1回だけ解決する。
@@ -1854,6 +1856,30 @@ function identifyEvidence(value, ctx, humanLabel) {
1854
1856
  kind: canon.kind,
1855
1857
  };
1856
1858
  }
1859
+ /**
1860
+ * 順位のファイルが持つ 4 区画の置き場(`Cortex/<区画>/records/` の下か)。分類(frontmatter の `type`)では決めない——
1861
+ * engine は置き場で順位を付けるので、`type` が無い・違う記録だけが一律の大きさで目立つ、を防ぐ
1862
+ */
1863
+ const RANKED_DIR_RE = /(?:^|\/)(?:Decisions|Rules|Glossary|Members)\/(?:records\/)?[^/]+$/;
1864
+ /**
1865
+ * 記録の点に、目次と同じ順位から決めた明るさ(`glow`・0〜1)を付ける(engine #551・オーナー決定 10/1)。
1866
+ *
1867
+ * 順位は engine の書いたファイル(`ranking.ts`)をそのまま読み、ここでは並べ替えない。
1868
+ * - 4 区画(決定・ルール・用語・関係者)の記録で順位に載っていないもの(目次に出さない割り振りの決定・順位を書いた夜より
1869
+ * 後に足された記録)は**一番暗い 0**(目次に載らない=一番下。一律の大きさのままだと中位より目立ってしまう)
1870
+ * - 根拠の点・Home・4 区画の外の記録には付けない(今までどおりの見た目)
1871
+ */
1872
+ function attachGlow(nodes, glow, recordRelPaths) {
1873
+ for (const n of nodes) {
1874
+ if (!n.recordId)
1875
+ continue;
1876
+ const rel = recordRelPaths.get(n.recordId);
1877
+ if (rel === undefined || !RANKED_DIR_RE.test(rel))
1878
+ continue;
1879
+ const g = glow.get(rel);
1880
+ n.glow = g === undefined ? 0 : g;
1881
+ }
1882
+ }
1857
1883
  /**
1858
1884
  * 全レコードのfrontmatter(relations / source / references)と本文リンクから関係グラフを構築する(engine #551・分類A 10/1)。
1859
1885
  *
@@ -2395,12 +2421,21 @@ async function build(opts) {
2395
2421
  // 関与の帳簿(便 14b・#709)。関係者の一覧を目次と同じ「関わりの多い順」に並べる材料。
2396
2422
  // **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
2397
2423
  const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
2424
+ // 目次と同じ順位(engine #551)。**読めないなら `null`** で、つながり画面の点は今までどおり一律
2425
+ const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot);
2426
+ // 記録の id → リポジトリルート相対のパス(順位のファイルの鍵と突き合わせる。`repoRoot` 起点・cwd 起点にしない)
2427
+ const recordRelPaths = new Map();
2428
+ const relOf = (dir, fileName) => {
2429
+ const rel = path.relative(repoRoot, dir).split(path.sep).join("/");
2430
+ return rel ? `${rel}/${fileName}` : fileName;
2431
+ };
2398
2432
  const decisions = [];
2399
2433
  const rawDates = new Map();
2400
2434
  for (const fileName of mdFiles) {
2401
2435
  const raw = await node_fs_1.promises.readFile(path.join(dirAbs, fileName), "utf8");
2402
2436
  const rec = await parseDecision(raw, fileName, repoRelDir, repo.baseUrl, repo.branch, refs);
2403
2437
  decisions.push(rec);
2438
+ recordRelPaths.set(rec.id, relOf(dirAbs, fileName));
2404
2439
  // 置き換えの逆引きの決定日(書かれたままの文字列・SiteData には載せない)
2405
2440
  const rd = rawFrontmatterDate(raw);
2406
2441
  if (rd !== undefined)
@@ -2475,6 +2510,9 @@ async function build(opts) {
2475
2510
  // **点と線は同じなのに地図の絵が毎晩組み変わる**(cortex-context で 1414/1414 の
2476
2511
  // 座標が動くのを実測)。並びが効くのは一覧だけ、という約束をここで固定する
2477
2512
  graphSections.push({ name: e.name, records: records });
2513
+ for (const r of records)
2514
+ if (!recordRelPaths.has(r.id))
2515
+ recordRelPaths.set(r.id, relOf(recordsDir, r.fileName));
2478
2516
  }
2479
2517
  }
2480
2518
  sections.sort((a, b) => a.name.localeCompare(b.name, "ja"));
@@ -2514,6 +2552,9 @@ async function build(opts) {
2514
2552
  }
2515
2553
  const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl, evidenceBook);
2516
2554
  computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
2555
+ // 点の明るさと大きさ(engine #551)。順位のファイルが読めない案件では 1 点も付かない(今までと同じ画面)
2556
+ if (memoryRanking)
2557
+ attachGlow(graph.nodes, (0, ranking_1.glowByPath)(memoryRanking), recordRelPaths);
2517
2558
  // データソースと自動化(fleet-status.json 由来・上で 1 回だけ読んだもの)。無ければ undefined でセクション非表示。
2518
2559
  // すでにある会議(投入モーダルの「会議名」の選択肢)。intake の有無に関わらず載せる——
2519
2560
  // 出し分けは表示側の仕事で、ここで絞ると「投入の口を後から付けたら選択肢が空」になる
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MEMORY_RANKING_SECTIONS = exports.MEMORY_RANKING_VERSION = exports.MEMORY_RANKING_PATH = void 0;
7
+ exports.parseMemoryRanking = parseMemoryRanking;
8
+ exports.readMemoryRanking = readMemoryRanking;
9
+ exports.glowByPath = glowByPath;
10
+ /**
11
+ * つながり画面の点の明るさと大きさ(cortex-engine #551・オーナー決定 2026-10-01)。
12
+ *
13
+ * ## 🔴 順位の決め方を画面に持たない
14
+ *
15
+ * 「光の強さ(大きさ)は目次(`Cortex/Index.md`)と同じ順位で変える。順位は全件ぶんを目次と同じ 1 つの決め方で出し、
16
+ * 画面はそれを読む(決め方を画面側に写さない)」。だからここは engine が書いた `.cortex/memory-ranking.json`
17
+ * (engine `scripts/memory-ranking.mjs`・目次の書き手が目次と同じコミットで書く)を**読むだけ**で、並べ替えない。
18
+ * 決めるのは「区画の中で何番目か」を明るさ(0〜1)にする写し方だけ(一番上=1・一番下=0・間は等間隔)。
19
+ *
20
+ * ## 🔴 ファイルが無い・壊れている・版が違う案件は「今までと 1 バイトも同じ画面」
21
+ *
22
+ * 読めなければ `null` を返し、点に `glow` を付けない(画面は今までどおり一律の大きさ・光り)。
23
+ * 読めた案件で 4 区画の記録なのに載っていないもの(目次に出さない割り振りの決定・順位を書いた夜より後に足された記録)は
24
+ * 一番暗い 0 にする(`build.ts` の `attachGlow`)。Home・区画の外の記録・根拠の点には付けない。
25
+ */
26
+ const promises_1 = __importDefault(require("node:fs/promises"));
27
+ const node_path_1 = __importDefault(require("node:path"));
28
+ /** 置き場(engine `scripts/memory-ranking.mjs` の `RANKING_PATH`) */
29
+ exports.MEMORY_RANKING_PATH = ".cortex/memory-ranking.json";
30
+ /** 形の版(engine の `RANKING_VERSION`。違ったら読まない) */
31
+ exports.MEMORY_RANKING_VERSION = 1;
32
+ /** 載る区画(engine の `RANKING_SECTIONS`) */
33
+ exports.MEMORY_RANKING_SECTIONS = ["Decisions", "Rules", "Glossary", "Members"];
34
+ /** 中身を読む(投げない)。形が違う・版が違う → `null` */
35
+ function parseMemoryRanking(text) {
36
+ try {
37
+ const parsed = JSON.parse(text);
38
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
39
+ return null;
40
+ const o = parsed;
41
+ if (o.version !== exports.MEMORY_RANKING_VERSION)
42
+ return null;
43
+ const r = o.ranking;
44
+ if (!r || typeof r !== "object" || Array.isArray(r))
45
+ return null;
46
+ const out = {};
47
+ for (const key of exports.MEMORY_RANKING_SECTIONS) {
48
+ const list = r[key];
49
+ if (!Array.isArray(list))
50
+ return null;
51
+ out[key] = list.filter((p) => typeof p === "string" && p !== "");
52
+ }
53
+ return out;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /** 案件リポから読む。無い → `null`(黙る)・壊れている → `null`(build のログに 1 行) */
60
+ async function readMemoryRanking(repoRoot) {
61
+ let text;
62
+ try {
63
+ text = await promises_1.default.readFile(node_path_1.default.join(repoRoot, exports.MEMORY_RANKING_PATH), "utf8");
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ const parsed = parseMemoryRanking(text);
69
+ if (!parsed)
70
+ console.warn(`${node_path_1.default.basename(exports.MEMORY_RANKING_PATH)}: 読めないので、つながり画面の点は一律の明るさで出します`);
71
+ return parsed;
72
+ }
73
+ /**
74
+ * 記録のパス → 明るさ(0〜1)。区画の中の位置だけで決める(一番上=1・一番下=0・1 件だけの区画は 1)。
75
+ * 小数は 3 桁に丸める(画面のデータを太らせない)。
76
+ */
77
+ function glowByPath(ranking) {
78
+ const out = new Map();
79
+ for (const key of exports.MEMORY_RANKING_SECTIONS) {
80
+ const list = ranking[key];
81
+ const last = list.length - 1;
82
+ list.forEach((p, i) => {
83
+ if (out.has(p))
84
+ return;
85
+ out.set(p, last <= 0 ? 1 : Math.round((1 - i / last) * 1000) / 1000);
86
+ });
87
+ }
88
+ return out;
89
+ }
package/dist/render.js CHANGED
@@ -4789,6 +4789,12 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4789
4789
  background: rgba(13,20,45,0.75); color: #cdd8f2; font-size: 12.5px; font-weight: 600; cursor: pointer;
4790
4790
  }
4791
4791
  .graph-reset:hover { background: rgba(30,40,80,0.9); border-color: rgba(160,180,230,0.7); }
4792
+ /* 期間で光らせる(engine #551・オーナー決定 10/1)。キーワードの検索の下の 1 行 */
4793
+ .graph-period { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; font-size: 13px; color: var(--muted); }
4794
+ .graph-period select, .graph-period input[type="date"] { padding: 5px 8px; border: 1px solid var(--border); border-radius: 8px; font: inherit; color: var(--text); background: var(--surface); }
4795
+ .graph-period button { padding: 4px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: inherit; cursor: pointer; font: inherit; }
4796
+ .graph-period button:disabled, .graph-period input:disabled { opacity: 0.45; cursor: default; }
4797
+ .graph-period-text { color: var(--text); font-weight: 600; }
4792
4798
 
4793
4799
  /* ---- knowledge view switch(ストーリーの日次/週次/月次タブも同じ見た目・分類B 2026-09-30) ---- */
4794
4800
  .view-switch, .story-sub-tabs {
@@ -4984,6 +4990,7 @@ a.pager-btn:hover { text-decoration: none; }
4984
4990
  .story-card {
4985
4991
  border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px; background: var(--surface);
4986
4992
  }
4993
+ .story-card-focus { border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.25); }
4987
4994
  .story-quiet { margin: -6px 0; padding: 0 18px; font-size: 12.5px; color: var(--muted); }
4988
4995
  .story-quiet-date { font-variant-numeric: tabular-nums; }
4989
4996
  .story-card-heading { margin: 0 0 8px; font-size: 15px; }
@@ -8536,6 +8543,7 @@ ${calendarAddUrl.toString()}
8536
8543
  // 文中のリンクは「押したら進む=戻れる」に見えるので、そちらだけ積む
8537
8544
  function switchTab(tabKey, push) {
8538
8545
  listState.tab = tabKey;
8546
+ storyFocus = null;
8539
8547
  listState.page = 1;
8540
8548
  if (push) history.pushState({}, "", tabUrl(tabKey));
8541
8549
  else history.replaceState(null, "", tabUrl(tabKey));
@@ -8817,6 +8825,8 @@ ${calendarAddUrl.toString()}
8817
8825
  var STORY_SUB_DEFAULT = "daily";
8818
8826
  var STORY_SUBS = [["daily", "日次"], ["weekly", "週次"], ["monthly", "月次"]];
8819
8827
  var storySub = STORY_SUB_DEFAULT;
8828
+ // 記録の詳細の「この日のストーリー」から着いたときに目立たせる日(日次)・週の月曜(週次)。URL の ?day= / ?week=(engine #551)
8829
+ var storyFocus = null;
8820
8830
 
8821
8831
  function storySubUrl(sub) {
8822
8832
  return sub === STORY_SUB_DEFAULT ? "?tab=story" : "?tab=story&sub=" + encodeURIComponent(sub);
@@ -8824,6 +8834,7 @@ ${calendarAddUrl.toString()}
8824
8834
  function switchStorySub(sub) {
8825
8835
  if (storySub === sub) return;
8826
8836
  storySub = sub;
8837
+ storyFocus = null;
8827
8838
  history.replaceState(null, "", storySubUrl(sub));
8828
8839
  renderList();
8829
8840
  }
@@ -8846,7 +8857,7 @@ ${calendarAddUrl.toString()}
8846
8857
  ]));
8847
8858
  return;
8848
8859
  }
8849
- list.appendChild(el("section", { class: "story-card" }, [
8860
+ list.appendChild(el("section", { class: "story-card", "data-story-key": d.date }, [
8850
8861
  el("h3", { class: "story-card-heading", text: d.heading || d.date }),
8851
8862
  d.bodyHtml ? el("div", { class: "body", html: d.bodyHtml }) : null,
8852
8863
  // その日のファイルを GitHub で開く(表示の文字にファイル名を出さない)。URL が無い案件は出さない
@@ -8862,7 +8873,7 @@ ${calendarAddUrl.toString()}
8862
8873
  el("h4", { text: "人の追記" }),
8863
8874
  el("div", { class: "body", html: entry.humanHtml }),
8864
8875
  ]) : null;
8865
- return el("section", { class: "story-card" }, [
8876
+ return el("section", { class: "story-card", "data-story-key": entry.key }, [
8866
8877
  el("h3", { class: "story-card-heading", text: entry.heading }),
8867
8878
  entry.phase ? el("p", { class: "story-phase" }, [
8868
8879
  el("span", { class: "story-phase-label", text: phaseLabel }),
@@ -8964,6 +8975,53 @@ ${calendarAddUrl.toString()}
8964
8975
  if (storySub === "weekly") renderStoryWeekly();
8965
8976
  else if (storySub === "monthly") renderStoryMonthly();
8966
8977
  else renderStoryDaily();
8978
+ // 記録の詳細から着いた日(週)のカードを目立たせて、そこまで送る
8979
+ if (storyFocus) {
8980
+ var focused = null;
8981
+ app.querySelectorAll(".story-card[data-story-key]").forEach(function (c) {
8982
+ if (!focused && c.getAttribute("data-story-key") === storyFocus) focused = c;
8983
+ });
8984
+ if (focused) {
8985
+ focused.classList.add("story-card-focus");
8986
+ if (typeof focused.scrollIntoView === "function") focused.scrollIntoView({ block: "start" });
8987
+ }
8988
+ }
8989
+ }
8990
+
8991
+ /**
8992
+ * 記録の日付から「その日のストーリー」の行き先を決める(engine #551・オーナー決定 10/1)。
8993
+ * その日の日次のカード(動きのあった日)があれば日次へ、無ければその日を含む週のあらすじへ。どちらも無ければ null(リンクを出さない)
8994
+ */
8995
+ function storyTargetOf(date) {
8996
+ if (!date || !/^\\d{4}-\\d{2}-\\d{2}$/.test(date) || !hasTab("story")) return null;
8997
+ var days = SITE.story || [];
8998
+ for (var i = 0; i < days.length; i++) {
8999
+ if (!days[i].quiet && days[i].date === date) return { sub: "daily", key: date, href: "?tab=story&day=" + encodeURIComponent(date) };
9000
+ }
9001
+ var weeks = (SITE.storyDigest && SITE.storyDigest.weeks) || [];
9002
+ var monday = storyMondayOf(date);
9003
+ for (var j = 0; j < weeks.length; j++) {
9004
+ if (weeks[j].key === monday) return { sub: "weekly", key: monday, href: "?tab=story&sub=weekly&week=" + encodeURIComponent(monday) };
9005
+ }
9006
+ return null;
9007
+ }
9008
+ /** 記録の詳細の「この日のストーリー」(行き先が無ければ null) */
9009
+ function storyLinkFor(rec) {
9010
+ var target = storyTargetOf(rec && rec.date);
9011
+ if (!target) return null;
9012
+ var a = el("a", { class: "trace-btn story-link-btn", href: target.href, text: target.sub === "daily" ? "📖 この日のストーリー" : "📖 この週のストーリー" });
9013
+ a.addEventListener("click", function (ev) {
9014
+ // 新しいタブで開く操作(修飾キー・中ボタン)はブラウザに任せる(href は本物の URL)
9015
+ if (ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || (typeof ev.button === "number" && ev.button !== 0)) return;
9016
+ ev.preventDefault();
9017
+ listState.tab = "story";
9018
+ storySub = target.sub;
9019
+ storyFocus = target.key;
9020
+ lastWasDetail = false;
9021
+ history.pushState({}, "", target.href);
9022
+ renderList();
9023
+ });
9024
+ return a;
8967
9025
  }
8968
9026
 
8969
9027
  // 導入経緯(overview.adoption)の表示情報。Home見出し横のチップに出すラベルだけを返す。
@@ -20275,7 +20333,7 @@ ${calendarAddUrl.toString()}
20275
20333
  // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
20276
20334
  var delMsgR = attachDeleteToEditRow(r, editRowR);
20277
20335
  head.appendChild(el("div", { class: "detail-actions" }, [
20278
- el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
20336
+ el("div", { class: "detail-actions-row" }, [ traceBtn2, storyLinkFor(r) ]),
20279
20337
  editRowR.children.length ? editRowR : null,
20280
20338
  delMsgR,
20281
20339
  ]));
@@ -20353,7 +20411,32 @@ ${calendarAddUrl.toString()}
20353
20411
  REL_COLOR[r] = REL_META[r].color;
20354
20412
  if (REL_META[r].kind === "evidence" || REL_META[r].kind === "topic") WEAK_RELS[r] = true;
20355
20413
  });
20356
- var graphState = { showWeak: true, q: "" };
20414
+ // period: 期間で光らせる(engine #551)。unit は all(光らせない)/day/week、day は基準の日(YYYY-MM-DD)
20415
+ var graphState = { showWeak: true, q: "", period: { unit: "all", day: null } };
20416
+
20417
+ /** 期間の範囲(両端を含む YYYY-MM-DD)。「すべて」・日付が無いときは null。週は月曜〜日曜(ストーリーの週と同じ区切り) */
20418
+ function graphPeriodRange(period) {
20419
+ if (!period || !period.day || !/^\\d{4}-\\d{2}-\\d{2}$/.test(period.day)) return null;
20420
+ if (period.unit === "day") return { from: period.day, to: period.day };
20421
+ if (period.unit === "week") {
20422
+ var monday = storyMondayOf(period.day);
20423
+ return { from: monday, to: storyDayOfMs(storyUtcMs(monday) + 6 * 86400000) };
20424
+ }
20425
+ return null;
20426
+ }
20427
+ /** 期間の見出し(「10/1」「9/28〜10/4 の週」) */
20428
+ function graphPeriodLabel(period) {
20429
+ var range = graphPeriodRange(period);
20430
+ if (!range) return "";
20431
+ if (period.unit === "week") return storyWeekLabel(range.from);
20432
+ var p = range.from.split("-");
20433
+ return parseInt(p[1], 10) + "/" + parseInt(p[2], 10) + " の日";
20434
+ }
20435
+ /** 記録の日付(生まれた・変わった日)がその期間に入るか。日付の無い記録(関係者など)は入らない */
20436
+ function recordInPeriod(rec, range) {
20437
+ var d = rec && rec.date;
20438
+ return !!(d && /^\\d{4}-\\d{2}-\\d{2}$/.test(d) && d >= range.from && d <= range.to);
20439
+ }
20357
20440
 
20358
20441
  function renderGraph() {
20359
20442
  // 削除済みを除いたグラフを1本作り、件数・凡例・描画の全部をこれで賄う
@@ -20365,6 +20448,8 @@ ${calendarAddUrl.toString()}
20365
20448
  search.value = graphState.q;
20366
20449
  filters.appendChild(search);
20367
20450
  app.appendChild(filters);
20451
+ var periodCtl = buildGraphPeriod(graph);
20452
+ app.appendChild(periodCtl.row);
20368
20453
 
20369
20454
  var wrap = el("div", { class: "graph-wrap" });
20370
20455
  var canvas = el("canvas", { class: "graph-canvas" });
@@ -20405,10 +20490,67 @@ ${calendarAddUrl.toString()}
20405
20490
  legend.appendChild(toggle);
20406
20491
  app.appendChild(legend);
20407
20492
 
20408
- startGraph(canvas, search, graph);
20493
+ startGraph(canvas, search, graph, periodCtl);
20494
+ }
20495
+
20496
+ /**
20497
+ * 期間で光らせるつまみ(engine #551・オーナー決定 10/1)。「すべて/日/週」と基準の日・前後の送り。
20498
+ * 選ぶと、記録の日付(生まれた・変わった日)がその期間に入る記録と、そのつながりだけが光る。基準の日の既定は、点の中で一番新しい記録の日
20499
+ */
20500
+ function buildGraphPeriod(graph) {
20501
+ var latest = "";
20502
+ graph.nodes.forEach(function (n) {
20503
+ var rec = n.recordId ? byId[n.recordId] : null;
20504
+ if (rec && rec.date && /^\\d{4}-\\d{2}-\\d{2}$/.test(rec.date) && rec.date > latest) latest = rec.date;
20505
+ });
20506
+ // 日付を持つ記録が 1 件も無い案件は、ビルドの日(日本時間)を基準にする(「日」「週」を選んでも何も起きない、を防ぐ)
20507
+ if (!graphState.period.day) graphState.period.day = latest || SITE.storyToday || (SITE.generatedAt ? String(SITE.generatedAt).slice(0, 10) : null);
20508
+ var row = el("div", { class: "graph-period" });
20509
+ var unit = el("select", { class: "graph-period-unit", "aria-label": "期間の区切り" });
20510
+ [["all", "すべて"], ["day", "日"], ["week", "週"]].forEach(function (o) {
20511
+ var opt = el("option", { value: o[0], text: o[1] });
20512
+ if (graphState.period.unit === o[0]) opt.selected = true;
20513
+ unit.appendChild(opt);
20514
+ });
20515
+ var day = el("input", { type: "date", class: "graph-period-day", "aria-label": "基準の日" });
20516
+ if (graphState.period.day) day.value = graphState.period.day;
20517
+ var prev = el("button", { type: "button", class: "graph-period-prev", text: "◀ 前", title: "前の期間" });
20518
+ var next = el("button", { type: "button", class: "graph-period-next", text: "次 ▶", title: "次の期間" });
20519
+ var label = el("span", { class: "graph-period-text" });
20520
+ row.appendChild(el("span", { text: "期間で光らせる:" }));
20521
+ [unit, day, prev, next, label].forEach(function (c) { row.appendChild(c); });
20522
+ var listeners = [];
20523
+ var shown = null; // 今の期間で光っている記録の数(つながり画面が数えて渡す)
20524
+ function paint() {
20525
+ var off = graphState.period.unit === "all";
20526
+ day.disabled = off; prev.disabled = off; next.disabled = off;
20527
+ label.textContent = off ? "" : graphPeriodLabel(graphState.period) + (shown === null ? "" : "に生まれた・変わった記録 " + shown + " 件");
20528
+ }
20529
+ function sync() {
20530
+ shown = null;
20531
+ paint();
20532
+ listeners.forEach(function (f) { f(); });
20533
+ }
20534
+ function step(sign) {
20535
+ if (!graphState.period.day) return;
20536
+ var days = graphState.period.unit === "week" ? 7 : 1;
20537
+ graphState.period.day = storyDayOfMs(storyUtcMs(graphState.period.day) + sign * days * 86400000);
20538
+ day.value = graphState.period.day;
20539
+ sync();
20540
+ }
20541
+ unit.addEventListener("change", function () { graphState.period.unit = unit.value; sync(); });
20542
+ day.addEventListener("change", function () { if (/^\\d{4}-\\d{2}-\\d{2}$/.test(day.value)) { graphState.period.day = day.value; sync(); } });
20543
+ prev.addEventListener("click", function () { step(-1); });
20544
+ next.addEventListener("click", function () { step(1); });
20545
+ sync();
20546
+ return {
20547
+ row: row,
20548
+ onChange: function (f) { listeners.push(f); },
20549
+ showCount: function (n) { shown = n; paint(); },
20550
+ };
20409
20551
  }
20410
20552
 
20411
- function startGraph(canvas, searchInput, graph) {
20553
+ function startGraph(canvas, searchInput, graph, periodCtl) {
20412
20554
  var allEdges = graph.edges.filter(function (e) {
20413
20555
  return graphState.showWeak || !WEAK_RELS[e.rel];
20414
20556
  });
@@ -20421,6 +20563,7 @@ ${calendarAddUrl.toString()}
20421
20563
  .filter(function (n) { return n.recordId || used[n.id]; })
20422
20564
  .map(function (n, i) {
20423
20565
  return { id: n.id, label: n.label, group: n.group, recordId: n.recordId, url: n.url,
20566
+ glow: typeof n.glow === "number" ? n.glow : null,
20424
20567
  x: typeof n.x === "number" ? n.x : 0, y: typeof n.y === "number" ? n.y : 0,
20425
20568
  vx: 0, vy: 0, phase: i * 2.399 };
20426
20569
  });
@@ -20462,9 +20605,11 @@ ${calendarAddUrl.toString()}
20462
20605
 
20463
20606
  // 検索: 一致ノード(matched)が光り、その隣接(lit)は残し、他は減光する
20464
20607
  var matched = {}, lit = {}, searching = false;
20608
+ // 期間(engine #551)を選んでいるときは、その期間に生まれた・変わった記録だけが一致になる(キーワードと両方なら両方を満たす記録)
20465
20609
  function applySearch() {
20466
20610
  var q = graphState.q.trim().toLowerCase();
20467
- searching = q.length > 0;
20611
+ var range = graphPeriodRange(graphState.period.unit === "all" ? null : graphState.period);
20612
+ searching = q.length > 0 || !!range;
20468
20613
  matched = {}; lit = {};
20469
20614
  if (!searching) {
20470
20615
  countEl.textContent = "点 " + nodes.length + " / つながり " + edges.length;
@@ -20472,12 +20617,16 @@ ${calendarAddUrl.toString()}
20472
20617
  }
20473
20618
  var count = 0;
20474
20619
  nodes.forEach(function (n) {
20475
- if (n.search.indexOf(q) !== -1) { matched[n.id] = true; lit[n.id] = true; count++; }
20620
+ if (q && n.search.indexOf(q) === -1) return;
20621
+ if (range && !recordInPeriod(n.recordId ? byId[n.recordId] : null, range)) return;
20622
+ matched[n.id] = true; lit[n.id] = true; count++;
20476
20623
  });
20477
20624
  edges.forEach(function (e) {
20478
20625
  if (matched[e.a.id] || matched[e.b.id]) { lit[e.a.id] = true; lit[e.b.id] = true; }
20479
20626
  });
20480
20627
  countEl.textContent = count + " 件ヒット(つながりも一緒に光っています)";
20628
+ // 期間を選んでいるときは、つまみの行に「◯◯に生まれた・変わった記録 N 件」(キーワードと両方なら両方を満たす数)
20629
+ if (periodCtl && range) periodCtl.showCount(count);
20481
20630
  }
20482
20631
  applySearch();
20483
20632
  if (searchInput) {
@@ -20486,6 +20635,7 @@ ${calendarAddUrl.toString()}
20486
20635
  applySearch();
20487
20636
  });
20488
20637
  }
20638
+ if (periodCtl) periodCtl.onChange(applySearch);
20489
20639
 
20490
20640
  var dpr = window.devicePixelRatio || 1;
20491
20641
  var ctx = canvas.getContext("2d");
@@ -20668,8 +20818,20 @@ ${calendarAddUrl.toString()}
20668
20818
  if (alpha > 0.003) alpha *= 0.995;
20669
20819
  }
20670
20820
 
20821
+ // 記録の点の大きさ: 目次と同じ順位から決めた明るさ(glow・0〜1・engine #551)があれば 6〜12、無ければ今までどおり一律 9
20671
20822
  function nodeRadius(n) {
20672
- return n.recordId ? 9 : 6;
20823
+ if (!n.recordId) return 6;
20824
+ if (n.glow === null) return 9;
20825
+ var r = 6 + 6 * n.glow;
20826
+ // 一致した点は少なくとも今までの一律の大きさ(9)にする(順位の低い点でも見つけられるように)
20827
+ return matched[n.id] ? Math.max(r, 9) : r;
20828
+ }
20829
+ // 記録の点の明るさの係数(glow が無ければ 1=今までどおり)。目次の上位ほど明るく、下位は沈む
20830
+ // 🔴 検索・期間で一致した点とホバー中の点は順位で沈めない(その日に足されたばかりで順位に載っていない記録が、
20831
+ // 「この期間の記録」を選んだのに一番暗く小さく出る、を防ぐ)
20832
+ function glowAlpha(n) {
20833
+ if (matched[n.id] || n === hovered) return 1;
20834
+ return n.recordId && n.glow !== null ? 0.4 + 0.6 * n.glow : 1;
20673
20835
  }
20674
20836
 
20675
20837
  function draw() {
@@ -20817,7 +20979,7 @@ ${calendarAddUrl.toString()}
20817
20979
  var isGold = !!n.recordId;
20818
20980
  // Gold以外の星はゆっくり瞬く
20819
20981
  var tw = isGold ? 1 : 0.72 + 0.28 * (0.5 + 0.5 * Math.sin(now * 0.0017 + n.phase));
20820
- ctx.globalAlpha = alphaN * tw;
20982
+ ctx.globalAlpha = alphaN * tw * glowAlpha(n);
20821
20983
  var r0 = nodeRadius(n);
20822
20984
  var fill = isGold ? style.color : "#c9d6f2";
20823
20985
  // 検索一致ノードとホバー中のノードは金色のリングで発光させる
@@ -20842,7 +21004,7 @@ ${calendarAddUrl.toString()}
20842
21004
  ctx.shadowColor =
20843
21005
  introFlash > 0.01 ? "#bcd0ff" : isGold ? style.color : "#9fb4e8";
20844
21006
  ctx.shadowBlur =
20845
- (isGold ? 16 : 7) * (dim ? 0.3 : 1) * (inChain ? 1.7 : 1) * (1 + introFlash * 1.8);
21007
+ (isGold ? (n.glow === null || matched[n.id] ? Math.max(16, n.glow === null ? 16 : 6 + 22 * n.glow) : 6 + 22 * n.glow) : 7) * (dim ? 0.3 : 1) * (inChain ? 1.7 : 1) * (1 + introFlash * 1.8);
20846
21008
  ctx.beginPath();
20847
21009
  ctx.arc(n.x, n.y, r0, 0, Math.PI * 2);
20848
21010
  ctx.fillStyle = fill;
@@ -20979,7 +21141,7 @@ ${calendarAddUrl.toString()}
20979
21141
  // 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
20980
21142
  var delMsgD = attachDeleteToEditRow(d, editRowD);
20981
21143
  head.appendChild(el("div", { class: "detail-actions" }, [
20982
- el("div", { class: "detail-actions-row" }, [ traceBtn ]),
21144
+ el("div", { class: "detail-actions-row" }, [ traceBtn, storyLinkFor(d) ]),
20983
21145
  editRowD.children.length ? editRowD : null,
20984
21146
  delMsgD,
20985
21147
  ]));
@@ -21493,6 +21655,8 @@ ${calendarAddUrl.toString()}
21493
21655
  var validSub = false;
21494
21656
  STORY_SUBS.forEach(function (s) { if (s[0] === sub) validSub = true; });
21495
21657
  storySub = validSub ? sub : STORY_SUB_DEFAULT;
21658
+ var focusKey = storySub === "weekly" ? getParam("week") : storySub === "daily" ? getParam("day") : null;
21659
+ storyFocus = focusKey && /^\\d{4}-\\d{2}-\\d{2}$/.test(focusKey) ? focusKey : null;
21496
21660
  }
21497
21661
  renderList();
21498
21662
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.113",
3
+ "version": "0.12.114",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,