@takagaki/cortex-decisions-viewer 0.12.103 → 0.12.105

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
@@ -2361,12 +2361,14 @@ async function build(opts) {
2361
2361
  // 走るのは latestGeneratedPaths と同じ **リポジトリルート**(process.cwd())——
2362
2362
  // Decisions ディレクトリで実行すると pathspec に一致せず常に空になる
2363
2363
  const aiHistory = (0, repo_1.collectAiChangeHistory)(process.cwd());
2364
+ // ストーリー(#901 V2-5)の日本時間の今日。日・週・月の3つで同じ日にそろえるため1回だけ計算する
2365
+ const storyToday = (0, story_1.todayJst)(new Date(opts.generatedAt));
2364
2366
  // ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
2365
2367
  const story = await (0, story_1.readStory)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null,
2366
2368
  // 動きのない日を埋める終点=ビルドの時刻の日本時間の日付(時計を注入できる)
2367
- (0, story_1.todayJst)(new Date(opts.generatedAt)));
2369
+ storyToday);
2368
2370
  // 週・月のあらすじ(#901)。週・月のファイルが無ければ null(キーごと出さない=今までと同じ画面)
2369
- const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, (0, story_1.todayJst)(new Date(opts.generatedAt)));
2371
+ const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, storyToday);
2370
2372
  // 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
2371
2373
  const projectIcon = await findProjectIcon(knowledgeRoot);
2372
2374
  const site = {
@@ -2428,6 +2430,8 @@ async function build(opts) {
2428
2430
  // 載せる日が無い案件(帳簿なし・壊れ・shadow の日だけ・ファイルなし)はキーごと出さない
2429
2431
  story: story ?? undefined,
2430
2432
  storyDigest: storyDigest ?? undefined,
2433
+ // ストーリーのタブが出る案件だけ(日・週・月のどちらも無ければ出さない=空の器を通さない)
2434
+ storyToday: story || storyDigest ? storyToday : undefined,
2431
2435
  };
2432
2436
  // サムネイル(デザイン画面等)をサイトに同梱し、ノードのパスをサイト内相対に書き換える
2433
2437
  await node_fs_1.promises.mkdir(outAbs, { recursive: true });
@@ -573,17 +573,19 @@ function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
573
573
  * - 同じ活性度なら元の並び(日付降順 → タイトル昇順がそのまま残る)
574
574
  * - 用語集だけ(関係者の並びは `engagement.ts`・便 14b。Decisions と Rules は対象外)
575
575
  *
576
- * ## 強さの段(#790・engine #727)
576
+ * ## 強さの段(#790・engine #727・仕様書 Q5)
577
577
  *
578
- * 活性度で並べた**後に**、用語の強さ(場面の数 → 使った人の数 → 資料の下地の辞書式降順)を
579
- * かぶせる(`rankByStrength`)。🔴 **engine の目次(`scripts/memory-index.mjs` の `termRows`)の写し**
578
+ * 🔴 **活性度が主**(engine 仕様書 Q5・2026-10-01 オーナー)。強さ(使った人の数 → 場面の数 → 資料の下地の辞書式降順)は、
579
+ * 活性度で並べた**後に、活性度がちょうど同じ語どうしの順にだけ**使う(`breakTiesByStrength`)。栓 `term_gate` を on にしても
580
+ * 強さ主には並べ替えない。🔴 **engine の目次(`scripts/memory-index.mjs` の `termRows`)の写し**
580
581
  * ——画面と目次で同じ語の順番が食い違うと「目次の上から N 件を AI に読ませる」構想(#544)の
581
582
  * 土台が崩れるので、**目次と同じ並び**にする。一致は `glossary-activity-parity.test.mjs` の
582
583
  * `[強さ]`(engine を実際に呼んで焼いた表)が見る。
583
584
  * - 🔴 **帳簿の `state` が `"on"` のときだけ**(`readRederivationLedgers` がそれ以外を空にする)
584
585
  * - 強さを持つ語が 1 件も無ければ活性度の並びのまま(帳簿が無い案件は 1 バイトも触らない)
585
- * - 強さを知らない語は**末尾**へ、活性度の並びのまま。同点も活性度の並びのまま(安定)
586
- * - 🔴 活性度の帳簿が無く強さだけある案件でも並べる(活性度の段が「何もしない」でも強さの段は飛ばさない)
586
+ * - 🔴 活性度が違う語の順は 1 つも変えない。活性度を知らない語どうしも同点の 1 塊
587
+ * - 同点の塊の中で、強さを知らない語は塊の**末尾**へ。強さも同じなら活性度の並びのまま(安定)
588
+ * - 🔴 活性度の帳簿が無く強さだけある案件でも並べる(全部が「知らない」の 1 塊になり、その中を強さで並べる)
587
589
  *
588
590
  * ℹ️ `ledgers` と `statusOf` は**受け取り続ける**。`ledgers` は活性度のために要る。
589
591
  * `statusOf` は並びに使わなくなったが、**引数から落とすと「群分けを足し戻す」退行が
@@ -596,21 +598,18 @@ function rederivedBandOf(recordPath, status, ledgers, nowShaOf, verdict, miss) {
596
598
  function orderByActivity(records, pathOf, statusOf, ledgers, nowIso) {
597
599
  if (!records.length)
598
600
  return records;
599
- // 🔴 **強さの段は活性度の段の後に必ず通す**(活性度が 1 件も無い案件でも・上の「強さの段」)
600
- return rankByStrength(rankByActivity(records, pathOf, ledgers, nowIso), pathOf, ledgers);
601
+ // 🔴 **強さの段は活性度の段の後に必ず通す**(活性度が 1 件も無い案件でも・上の「強さの段」)。強さは同点の中の順だけ(Q5)
602
+ return breakTiesByStrength(rankByActivity(records, pathOf, ledgers, nowIso), pathOf, ledgers, nowIso);
603
+ }
604
+ /** 1 件の活性度(用語集の区画の外は「知らない」)。活性度の段と強さの段で同じ値を使う */
605
+ function activityOf(r, pathOf, ledgers, nowIso) {
606
+ const p = pathOf(r);
607
+ return ORDER_SECTION_RE.test(p) ? activityScore(ledgers.activity.get(p), nowIso) : null;
601
608
  }
602
609
  /** 活性度の段。活性度を持つ語が 1 件も無ければ**渡された配列をそのまま返す** */
603
610
  function rankByActivity(records, pathOf, ledgers, nowIso) {
604
611
  // この区画が並べ替えの対象か(用語集で、活性度の行が 1 件以上あるか)
605
- const scored = records.map((r, i) => {
606
- const p = pathOf(r);
607
- const inSection = ORDER_SECTION_RE.test(p);
608
- return {
609
- r: r,
610
- i: i,
611
- score: inSection ? activityScore(ledgers.activity.get(p), nowIso) : null,
612
- };
613
- });
612
+ const scored = records.map((r, i) => ({ r: r, i: i, score: activityOf(r, pathOf, ledgers, nowIso) }));
614
613
  if (!scored.some((s) => s.score != null))
615
614
  return records;
616
615
  return scored
@@ -627,26 +626,30 @@ function rankByActivity(records, pathOf, ledgers, nowIso) {
627
626
  .map((s) => s.r);
628
627
  }
629
628
  /**
630
- * 強さの段(#790)。**engine `scripts/glossary/activity.mjs` の `rankByStrength` の写し**——鍵・同点・
629
+ * 強さの段(#790・Q5)。**engine `scripts/glossary/activity.mjs` の `breakTiesByStrength` の写し**——塊の切り方・鍵・
631
630
  * 「知らない」の扱いを 1 つでも変えると、画面と目次で同じ語の順番が割れる。
632
631
  *
633
632
  * - 強さを持つ語が 1 件も無ければ**渡された配列をそのまま返す**(`state` が on でない案件はここ)
634
- * - 知らない語(帳簿に無い・用語集の区画の外)は末尾へ、渡された順のまま
635
- * - 鍵は `[場面の数, 使った人の数, 下地]` の辞書式降順。同点は渡された順(=活性度の並び・安定)
633
+ * - `records` は活性度の段を通した後の並び。**活性度がちょうど同じ語**(どちらも知らないも同じ)が隣り合う塊ごとに、
634
+ * 鍵 `[使った人の数, 場面の数, 下地]` の辞書式降順で並べ替える。塊の外へは 1 件も動かさない
635
+ * - 塊の中で知らない語(帳簿に無い・用語集の区画の外)は末尾へ、渡された順のまま。鍵も同じなら渡された順(安定)
636
636
  */
637
- function rankByStrength(records, pathOf, ledgers) {
637
+ function breakTiesByStrength(records, pathOf, ledgers, nowIso) {
638
638
  if (!ledgers.strength.size)
639
639
  return records;
640
640
  const scored = records.map((r, i) => {
641
641
  const p = pathOf(r);
642
- return { r: r, i: i, s: ORDER_SECTION_RE.test(p) ? (ledgers.strength.get(p) ?? null) : null };
642
+ return {
643
+ r: r,
644
+ i: i,
645
+ a: activityOf(r, pathOf, ledgers, nowIso),
646
+ s: ORDER_SECTION_RE.test(p) ? (ledgers.strength.get(p) ?? null) : null,
647
+ };
643
648
  });
644
649
  if (!scored.some((x) => x.s != null))
645
650
  return records;
646
- const key = (s) => [s.scenes, s.people, s.base ? 1 : 0];
647
- return scored
648
- .slice()
649
- .sort((a, b) => {
651
+ const key = (s) => [s.people, s.scenes, s.base ? 1 : 0];
652
+ const byStrength = (a, b) => {
650
653
  if ((a.s == null) !== (b.s == null))
651
654
  return a.s == null ? 1 : -1;
652
655
  if (a.s != null && b.s != null) {
@@ -657,8 +660,17 @@ function rankByStrength(records, pathOf, ledgers) {
657
660
  return kb[k] - ka[k];
658
661
  }
659
662
  return a.i - b.i;
660
- })
661
- .map((x) => x.r);
663
+ };
664
+ const out = [];
665
+ for (let start = 0; start < scored.length;) {
666
+ let end = start + 1;
667
+ while (end < scored.length && scored[end].a === scored[start].a)
668
+ end += 1;
669
+ for (const x of scored.slice(start, end).sort(byStrength))
670
+ out.push(x.r);
671
+ start = end;
672
+ }
673
+ return out;
662
674
  }
663
675
  /* ---------------------------------------------------------------------------
664
676
  * 決定の「その後」の帳簿(#705・engine #509 便 4)
package/dist/render.js CHANGED
@@ -4644,8 +4644,12 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4644
4644
  .container { max-width: 960px; margin: 0 auto; padding: 24px 20px 64px; }
4645
4645
 
4646
4646
  /* ---- tabs ---- */
4647
- .tabs { display: flex; gap: 6px; margin-bottom: 18px; border-bottom: 1px solid var(--border); }
4647
+ /* 🔴 幅が足りないときはタブの列だけが横にスクロールする(ページ全体は横スクロールさせない・分類B 2026-09-30)。
4648
+ overflow-x を visible 以外にすると overflow-y も auto 相当になり、.tab の margin-bottom: -1px と
4649
+ border-bottom の重なり(下の下線用の1px)が縦のスクロールバーとして見えかねないので、overflow-y は明示的に止める */
4650
+ .tabs { display: flex; gap: 6px; margin-bottom: 18px; border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden; }
4648
4651
  .tab {
4652
+ flex: 0 0 auto; white-space: nowrap;
4649
4653
  padding: 8px 16px; font-size: 14px; font-weight: 600; color: var(--muted);
4650
4654
  border: none; background: none; cursor: pointer;
4651
4655
  border-bottom: 2px solid transparent; margin-bottom: -1px;
@@ -4693,16 +4697,16 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4693
4697
  }
4694
4698
  .graph-reset:hover { background: rgba(30,40,80,0.9); border-color: rgba(160,180,230,0.7); }
4695
4699
 
4696
- /* ---- knowledge view switch ---- */
4697
- .view-switch {
4700
+ /* ---- knowledge view switch(ストーリーの日次/週次/月次タブも同じ見た目・分類B 2026-09-30) ---- */
4701
+ .view-switch, .story-sub-tabs {
4698
4702
  display: inline-flex; border: 1px solid rgba(255,255,255,0.75); border-radius: 999px; overflow: hidden;
4699
4703
  margin-bottom: 14px; background: rgba(255,255,255,0.45);
4700
4704
  -webkit-backdrop-filter: blur(10px) saturate(150%); backdrop-filter: blur(10px) saturate(150%);
4701
4705
  box-shadow: 0 2px 8px rgba(30,60,120,0.1), inset 0 1px 0 rgba(255,255,255,0.85);
4702
4706
  }
4703
- .view-btn { border: none; background: none; padding: 7px 18px; font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer; }
4704
- .view-btn.active { background: rgba(37,99,235,0.85); color: #fff; box-shadow: inset 0 1px 0 rgba(255,255,255,0.35); }
4705
- .view-btn:not(.active):hover { color: var(--accent); }
4707
+ .view-btn, .story-sub-tab { border: none; background: none; padding: 7px 18px; font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer; }
4708
+ .view-btn.active, .story-sub-tab.active { background: rgba(37,99,235,0.85); color: #fff; box-shadow: inset 0 1px 0 rgba(255,255,255,0.35); }
4709
+ .view-btn:not(.active):hover, .story-sub-tab:not(.active):hover { color: var(--accent); }
4706
4710
 
4707
4711
  /* ---- frontmatter panel ---- */
4708
4712
  .fm-panel { margin-top: 32px; }
@@ -4881,32 +4885,11 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4881
4885
  .story-card-edit { display: inline-block; margin-top: 8px; font-size: 12.5px; }
4882
4886
  .story-card .body h2 { font-size: 14px; margin: 14px 0 6px; }
4883
4887
  .story-card .body h3 { font-size: 13.5px; margin: 12px 0 6px; }
4884
- /* 週・月のあらすじ(#901)。週・月のファイルがある案件だけ使う */
4885
- .story-now { display: flex; flex-direction: column; gap: 14px; margin-bottom: 20px; }
4886
- .story-section-heading { margin: 4px 0 0; font-size: 15px; }
4888
+ /* 週・月のあらすじカード(#901)。週・月のファイルがある案件だけ使う */
4887
4889
  .story-empty { margin: 0; font-size: 13px; color: var(--muted); }
4888
4890
  .story-phase { margin: 0 0 8px; font-weight: 600; }
4889
4891
  .story-phase-label { display: inline-block; margin-right: 8px; font-size: 12px; color: var(--muted); font-weight: 400; }
4890
4892
  .story-digest-human h4 { font-size: 13.5px; margin: 12px 0 6px; }
4891
- .story-switch { display: flex; gap: 8px; margin: 0 0 12px; }
4892
- .story-switch-btn {
4893
- padding: 3px 16px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface);
4894
- color: inherit; font-size: 13px; cursor: pointer;
4895
- }
4896
- .story-switch-btn.active { background: #1d4ed8; border-color: #1d4ed8; color: #fff; font-weight: 600; }
4897
- .story-nav { display: flex; justify-content: space-between; margin: 0 0 10px; }
4898
- .story-nav-btn {
4899
- padding: 3px 12px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--surface);
4900
- color: inherit; font-size: 12.5px; cursor: pointer;
4901
- }
4902
- .story-nav-btn:disabled { opacity: 0.4; cursor: default; }
4903
- .story-browse { display: flex; flex-direction: column; gap: 12px; }
4904
- .story-days-fold { margin-top: 10px; }
4905
- .story-days-fold > summary, .story-week-fold > summary { cursor: pointer; font-size: 13px; font-weight: 600; }
4906
- .story-days-fold > .story-cards { margin-top: 10px; }
4907
- .story-week-fold { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 18px; background: var(--surface); }
4908
- .story-week-fold > .story-digest, .story-week-fold > .story-empty { margin-top: 10px; }
4909
- .story-month-pick { display: flex; align-items: center; gap: 8px; font-size: 13px; }
4910
4893
  /* ---- Home(案件の自己記述)---- */
4911
4894
  /* 未記入の警告。**画面でいちばん強い赤にする**——27案件中24案件が未記入のまま
4912
4895
  毎晩のAI生成が走っている状態で、控えめに出しても読まれない */
@@ -8421,6 +8404,10 @@ ${calendarAddUrl.toString()}
8421
8404
  return new URLSearchParams(location.search).get(name);
8422
8405
  }
8423
8406
  function tabUrl(tabKey) {
8407
+ // ストーリーは日次/週次/月次のサブタブを持つので、そのURL(storySubUrl)に任せる。
8408
+ // ここで素の "?tab=story" を返すと、他のタブへ移って戻ったときに URL だけ既定(日次)に戻り、
8409
+ // 画面(storySub は変えていない)と URL がずれる(分類B 2026-09-30)
8410
+ if (tabKey === "story") return storySubUrl(storySub);
8424
8411
  return tabKey === DEFAULT_TAB ? "?" : "?tab=" + encodeURIComponent(tabKey);
8425
8412
  }
8426
8413
  /** そのタブが立っているか(案件によっては操作タブが無い) */
@@ -8501,12 +8488,13 @@ ${calendarAddUrl.toString()}
8501
8488
  }
8502
8489
 
8503
8490
  // ---------- タブナビ ----------
8491
+ // 🔴 件数(「決定事項(466)」等)は出さない・文字は折り返さない(nowrap)。幅が足りなければ
8492
+ // タブの列だけが横にスクロールする(ページ全体は横にスクロールさせない・分類B 2026-09-30)
8504
8493
  function renderTabs() {
8505
8494
  if (TABS.length <= 1) return;
8506
8495
  var tabs = el("div", { class: "tabs" });
8507
8496
  TABS.forEach(function (t) {
8508
- var label = t.items ? t.label + "(" + t.items.length + ")" : t.label;
8509
- var b = el("button", { class: "tab" + (listState.tab === t.key ? " active" : ""), text: label });
8497
+ var b = el("button", { class: "tab" + (listState.tab === t.key ? " active" : ""), text: t.label });
8510
8498
  b.addEventListener("click", function () { switchTab(t.key); });
8511
8499
  tabs.appendChild(b);
8512
8500
  });
@@ -8544,17 +8532,13 @@ ${calendarAddUrl.toString()}
8544
8532
  text: "決定・用語・レポートを起点に、つながり(根拠・出典・リンク)を1列ずつ辿る表示です。行をクリックすると右に次の列が開きます。",
8545
8533
  },
8546
8534
  "story": {
8547
- text: "このプロジェクトの日々の動きを、1日ずつまとめた記録です。直したいことや足したいことは、その日の「人の追記」に書けます。",
8548
- },
8549
- "story-digest": {
8550
- text: "今週の日々と、週・月のあらすじです。直したいことや足したいことは、各カードの「人の追記」に書けます。",
8535
+ text: "このプロジェクトの日々の動きを、日次・週次・月次に分けてまとめた記録です。直したいことや足したいことは、各カードの「GitHub で追記する」から書けます。",
8551
8536
  },
8552
8537
  "dir:レポート": {
8553
8538
  text: "レポートの生成は終了し、履歴として凍結されています(レポートはSlack配信に一本化されました)。以下は過去に生成されたレコードです。",
8554
8539
  },
8555
8540
  };
8556
8541
  var infoKey = listState.tab === "graph" ? (knowledgeView === "map" ? "graph" : "explorer") : listState.tab;
8557
- if (infoKey === "story" && SITE.storyDigest) infoKey = "story-digest";
8558
8542
  var info = INFO[infoKey];
8559
8543
  if (!info) return;
8560
8544
  var bar = el("div", { class: "info-bar" }, [ el("span", { text: info.text }) ]);
@@ -8578,13 +8562,34 @@ ${calendarAddUrl.toString()}
8578
8562
  renderGenericList();
8579
8563
  }
8580
8564
 
8581
- // ---------- ストーリー(#901 V2-5)----------
8582
- // 日ごとのカード(新しい日が上=build が並べた順のまま)。本文は build の関所(marked → sanitize)を
8565
+ // ---------- ストーリー(#901 V2-5・日次/週次/月次タブへの分割は分類B 2026-09-30) ----------
8566
+ // 「日次」「週次」「月次」の3つのサブタブ。既定は日次。選んだサブタブは URL に添えて覚える
8567
+ // (?tab=story&sub=weekly のように・分類B 2026-09-30)。本文は build の関所(marked → sanitize)を
8583
8568
  // 通した HTML で、印のコメントと機械の語の行は build が除いてある。見出しは textContent で入れる
8584
- function renderStory() {
8585
- if (SITE.storyDigest) { renderStoryDigest(); return; }
8569
+ var STORY_SUB_DEFAULT = "daily";
8570
+ var STORY_SUBS = [["daily", "日次"], ["weekly", "週次"], ["monthly", "月次"]];
8571
+ var storySub = STORY_SUB_DEFAULT;
8572
+
8573
+ function storySubUrl(sub) {
8574
+ return sub === STORY_SUB_DEFAULT ? "?tab=story" : "?tab=story&sub=" + encodeURIComponent(sub);
8575
+ }
8576
+ function switchStorySub(sub) {
8577
+ if (storySub === sub) return;
8578
+ storySub = sub;
8579
+ history.replaceState(null, "", storySubUrl(sub));
8580
+ renderList();
8581
+ }
8582
+
8583
+ // 日ごとのカード(新しい日が上=build が並べた順のまま)。動きのない日は「◯月◯日は動きはありませんでした」の1行
8584
+ function renderStoryDaily() {
8585
+ var days = SITE.story || [];
8586
8586
  var list = el("div", { class: "story-cards" });
8587
- (SITE.story || []).forEach(function (d) {
8587
+ if (!days.length) {
8588
+ list.appendChild(el("p", { class: "story-empty", text: "日々の記録はまだありません" }));
8589
+ app.appendChild(list);
8590
+ return;
8591
+ }
8592
+ days.forEach(function (d) {
8588
8593
  // 動きのない日は薄い 1 行(日付+文言。畳んだ行は「9/10〜9/18」)
8589
8594
  if (d.quiet) {
8590
8595
  list.appendChild(el("div", { class: "story-quiet" }, [
@@ -8603,184 +8608,114 @@ ${calendarAddUrl.toString()}
8603
8608
  app.appendChild(list);
8604
8609
  }
8605
8610
 
8606
- // ---------- ストーリーの週・月(#901)----------
8607
- // 週・月のあらすじ(SITE.storyDigest)がある案件だけ。無い案件は上の renderStory のまま(今までと同じ画面)。
8608
- // 日のカードは SITE.story(build が並べた日)の「動きのある日」を週に配り直し、動きの無い日は 1 日 1 行で埋める。
8609
- // 本文は build の関所を通した HTML、見出し・局面は textContent で入れる
8610
- var STORY_WD = ["日", "月", "火", "水", "木", "金", "土"];
8611
- function storyMs(d) { return Date.parse(d + "T00:00:00Z"); }
8612
- function storyDay(ms) { return new Date(ms).toISOString().slice(0, 10); }
8613
- function storyMonday(d) { var ms = storyMs(d); return storyDay(ms - ((new Date(ms).getUTCDay() + 6) % 7) * 86400000); }
8614
- function storyMd(d) { return Number(d.slice(5, 7)) + "/" + Number(d.slice(8, 10)); }
8615
- function storyWeekLabel(mon) { return storyMd(mon) + "〜" + storyMd(storyDay(storyMs(mon) + 6 * 86400000)) + " の週"; }
8616
- function storyMonthLabel(m) { return m.slice(0, 4) + "年" + Number(m.slice(5, 7)) + "月"; }
8617
-
8618
- function renderStoryDigest() {
8619
- var dg = SITE.storyDigest;
8620
- var today = dg.today;
8621
- var pages = {};
8622
- var oldest = null;
8623
- (SITE.story || []).forEach(function (d) {
8624
- if (d.quiet) return;
8625
- pages[d.date] = d;
8626
- if (oldest === null || d.date < oldest) oldest = d.date;
8627
- });
8628
- var weekOf = {};
8629
- (dg.weeks || []).forEach(function (w) { weekOf[w.key] = w; });
8630
- var monthOf = {};
8631
- (dg.months || []).forEach(function (m) { monthOf[m.key] = m; });
8632
- var thisMon = storyMonday(today);
8633
- var lastMon = storyDay(storyMs(thisMon) - 7 * 86400000);
8634
-
8635
- // 日の行(新しい日が上)。from〜to のうち、動きのある日はカード・無い日は薄い 1 行。
8636
- // 載せた一番古い日より前は分からないので出さない(keepAll の「今週」だけは月曜から全部出す)
8637
- function dayRows(from, to, keepAll) {
8638
- var rows = [];
8639
- for (var ms = storyMs(to); ms >= storyMs(from); ms -= 86400000) {
8640
- var d = storyDay(ms);
8641
- var pg = pages[d];
8642
- if (pg) {
8643
- rows.push(el("section", { class: "story-card" }, [
8644
- el("h3", { class: "story-card-heading", text: pg.heading || pg.date }),
8645
- pg.bodyHtml ? el("div", { class: "body", html: pg.bodyHtml }) : null,
8646
- pg.editUrl ? el("a", { class: "story-card-edit", href: pg.editUrl, target: "_blank", rel: "noopener", text: "GitHub で追記する" }) : null,
8647
- ]));
8648
- } else if (keepAll || (oldest !== null && d >= oldest)) {
8649
- rows.push(el("div", { class: "story-quiet" }, [
8650
- el("span", { class: "story-quiet-date", text: d + "(" + STORY_WD[new Date(ms).getUTCDay()] + ")" }),
8651
- el("span", { text: " は動きはありませんでした" }),
8652
- ]));
8653
- }
8611
+ // 週・月のあらすじカード(局面・流れ・人の追記・GitHub のリンク)。見出し・局面は build が作った文字列(heading/phase)をそのまま使う
8612
+ function storyDigestCard(entry, phaseLabel) {
8613
+ var human = entry.humanHtml ? el("div", { class: "story-digest-human" }, [
8614
+ el("h4", { text: "人の追記" }),
8615
+ el("div", { class: "body", html: entry.humanHtml }),
8616
+ ]) : null;
8617
+ return el("section", { class: "story-card" }, [
8618
+ el("h3", { class: "story-card-heading", text: entry.heading }),
8619
+ entry.phase ? el("p", { class: "story-phase" }, [
8620
+ el("span", { class: "story-phase-label", text: phaseLabel }),
8621
+ el("span", { text: entry.phase }),
8622
+ ]) : null,
8623
+ entry.flowHtml ? el("div", { class: "body", html: entry.flowHtml }) : null,
8624
+ human,
8625
+ entry.editUrl ? el("a", { class: "story-card-edit", href: entry.editUrl, target: "_blank", rel: "noopener", text: "GitHub で追記する" }) : null,
8626
+ ]);
8627
+ }
8628
+
8629
+ // 週・月の抜けを埋めるための日付計算(UTC 固定で計算し YYYY-MM-DD/YYYY-MM の文字列を返す。
8630
+ // これらの文字列は桁がそろっているので、そのまま辞書式に比べれば新旧が分かる)
8631
+ function storyUtcMs(day) { return Date.parse(day + "T00:00:00Z"); }
8632
+ function storyDayOfMs(ms) { return new Date(ms).toISOString().slice(0, 10); }
8633
+ function storyMondayOf(day) {
8634
+ var dow = new Date(storyUtcMs(day)).getUTCDay(); // 0=日 … 6=土
8635
+ var back = dow === 0 ? 6 : dow - 1;
8636
+ return storyDayOfMs(storyUtcMs(day) - back * 86400000);
8637
+ }
8638
+ function storyPrevWeekKey(monday) { return storyDayOfMs(storyUtcMs(monday) - 7 * 86400000); }
8639
+ function storyPrevMonthKey(month) {
8640
+ var y = parseInt(month.slice(0, 4), 10);
8641
+ var m = parseInt(month.slice(5, 7), 10) - 1;
8642
+ if (m < 1) { m = 12; y -= 1; }
8643
+ return y + "-" + (m < 10 ? "0" + m : String(m));
8644
+ }
8645
+ function storyWeekLabel(monday) {
8646
+ var md = function (d) { var p = d.split("-"); return parseInt(p[1], 10) + "/" + parseInt(p[2], 10); };
8647
+ return md(monday) + "〜" + md(storyDayOfMs(storyUtcMs(monday) + 6 * 86400000)) + " の週";
8648
+ }
8649
+ // その案件でストーリーが分かっている一番古い日付(YYYY-MM-DD)。月・週・日のどれかに載っている
8650
+ // 一番古いものを使う(無ければ null=今の区切りだけ見る)。この日より前の区切りには案内を足さない
8651
+ function storyEarliestKnownDate() {
8652
+ var dates = [];
8653
+ ((SITE.storyDigest && SITE.storyDigest.months) || []).forEach(function (m) { dates.push(m.key + "-01"); });
8654
+ ((SITE.storyDigest && SITE.storyDigest.weeks) || []).forEach(function (w) { dates.push(w.key); });
8655
+ (SITE.story || []).forEach(function (d) { dates.push(d.date); });
8656
+ if (!dates.length) return null;
8657
+ return dates.reduce(function (a, b) { return b < a ? b : a; });
8658
+ }
8659
+
8660
+ // 週のあらすじを新しい週から順に。「今週」と「週が閉じたがまだあらすじが無い週」は見出し+案内の1行で埋め、
8661
+ // 欠けなく新しい週から並べる(その案件でストーリーが始まる前の週までは埋めない)
8662
+ function renderStoryWeekly() {
8663
+ var weeks = (SITE.storyDigest && SITE.storyDigest.weeks) || [];
8664
+ var list = el("div", { class: "story-cards" });
8665
+ var curMonday = SITE.storyToday ? storyMondayOf(SITE.storyToday) : null;
8666
+ if (curMonday) {
8667
+ var have = {};
8668
+ weeks.forEach(function (w) { have[w.key] = true; });
8669
+ var earliest = storyEarliestKnownDate();
8670
+ var earliestMonday = earliest ? storyMondayOf(earliest) : curMonday;
8671
+ for (var wk = curMonday; !have[wk] && wk >= earliestMonday; wk = storyPrevWeekKey(wk)) {
8672
+ list.appendChild(el("section", { class: "story-card" }, [
8673
+ el("h3", { class: "story-card-heading", text: storyWeekLabel(wk) }),
8674
+ el("p", { class: "story-empty", text: "週が閉じて 2 日後の夜に作ります" }),
8675
+ ]));
8654
8676
  }
8655
- return rows;
8656
- }
8657
- function pageCount(from, to) {
8658
- var n = 0;
8659
- for (var ms = storyMs(from); ms <= storyMs(to); ms += 86400000) if (pages[storyDay(ms)]) n++;
8660
- return n;
8661
8677
  }
8662
- // あらすじの本体(局面・流れ・人の追記・GitHub のリンク)。無ければ 1 行の案内
8663
- function digestBody(entry, phaseLabel, emptyText) {
8664
- if (!entry) return el("p", { class: "story-empty", text: emptyText });
8665
- var human = entry.humanHtml ? el("div", { class: "story-digest-human" }, [
8666
- el("h4", { text: "人の追記" }),
8667
- el("div", { class: "body", html: entry.humanHtml }),
8668
- ]) : null;
8669
- return el("div", { class: "story-digest" }, [
8670
- entry.phase ? el("p", { class: "story-phase" }, [
8671
- el("span", { class: "story-phase-label", text: phaseLabel }),
8672
- el("span", { text: entry.phase }),
8673
- ]) : null,
8674
- entry.flowHtml ? el("div", { class: "body", html: entry.flowHtml }) : null,
8675
- human,
8676
- entry.editUrl ? el("a", { class: "story-card-edit", href: entry.editUrl, target: "_blank", rel: "noopener", text: "GitHub で追記する" }) : null,
8677
- ]);
8678
- }
8679
- var WEEK_EMPTY = "あらすじはまだありません(週が閉じて 2 日後の夜に作ります)";
8680
- var MONTH_EMPTY = "あらすじはまだありません(月が閉じて 3 日後の夜に作ります)";
8681
- function weekEnd(mon) {
8682
- var sun = storyDay(storyMs(mon) + 6 * 86400000);
8683
- return sun < today ? sun : today;
8684
- }
8685
- function daysFold(mon) {
8686
- var rows = dayRows(mon, weekEnd(mon), mon === thisMon);
8687
- if (!rows.length) return null;
8688
- return el("details", { class: "story-days-fold" }, [
8689
- el("summary", { text: "この週の日々(" + pageCount(mon, weekEnd(mon)) + " 日)" }),
8690
- el("div", { class: "story-cards" }, rows),
8691
- ]);
8678
+ if (!weeks.length && !list.children.length) {
8679
+ list.appendChild(el("p", { class: "story-empty", text: "あらすじはまだありません(週が閉じて 2 日後の夜に作ります)" }));
8680
+ } else {
8681
+ weeks.forEach(function (w) { list.appendChild(storyDigestCard(w, "この週の局面")); });
8692
8682
  }
8683
+ app.appendChild(list);
8684
+ }
8693
8685
 
8694
- // 一番上: 今週の日々と先週のあらすじ
8695
- app.appendChild(el("section", { class: "story-now" }, [
8696
- el("h3", { class: "story-section-heading", text: "今週" }),
8697
- el("div", { class: "story-cards" }, dayRows(thisMon, today, true)),
8698
- el("h3", { class: "story-section-heading", text: "先週のあらすじ" }),
8699
- weekOf[lastMon]
8700
- ? el("section", { class: "story-card" }, [
8701
- el("h3", { class: "story-card-heading", text: storyWeekLabel(lastMon) }),
8702
- digestBody(weekOf[lastMon], "この週の局面", ""),
8703
- ])
8704
- : el("p", { class: "story-empty", text: "先週のあらすじはまだありません" }),
8705
- ]));
8706
-
8707
- // 週の並び(新しい週から): あらすじのある週・動きのある日のある週・先週。今週は上に出すので入れない
8708
- var monSet = {};
8709
- monSet[lastMon] = true;
8710
- Object.keys(weekOf).forEach(function (k) { if (k < thisMon) monSet[k] = true; });
8711
- Object.keys(pages).forEach(function (d) { var k = storyMonday(d); if (k < thisMon) monSet[k] = true; });
8712
- var mons = Object.keys(monSet).sort().reverse();
8713
- // 月の並び(新しい月から): あらすじのある月・動きのある日のある月
8714
- var mSet = {};
8715
- Object.keys(monthOf).forEach(function (k) { mSet[k] = true; });
8716
- Object.keys(pages).forEach(function (d) { mSet[d.slice(0, 7)] = true; });
8717
- var months = Object.keys(mSet).sort().reverse();
8718
-
8719
- var view = "week";
8720
- var weekAt = mons.indexOf(lastMon);
8721
- var monthAt = months.length ? months[0] : null;
8722
- var btnWeek = el("button", { class: "story-switch-btn", type: "button", text: "週" });
8723
- var btnMonth = el("button", { class: "story-switch-btn", type: "button", text: "月" });
8724
- var browse = el("div", { class: "story-browse" });
8725
- app.appendChild(el("div", { class: "story-switch" }, [btnWeek, btnMonth]));
8726
- app.appendChild(browse);
8727
-
8728
- function fillWeek() {
8729
- var mon = mons[weekAt];
8730
- var prev = el("button", { class: "story-nav-btn", type: "button", text: "← 前の週" });
8731
- var next = el("button", { class: "story-nav-btn", type: "button", text: "次の週 →" });
8732
- if (weekAt >= mons.length - 1) prev.disabled = true;
8733
- if (weekAt <= 0) next.disabled = true;
8734
- prev.addEventListener("click", function () { if (weekAt < mons.length - 1) { weekAt++; fill(); } });
8735
- next.addEventListener("click", function () { if (weekAt > 0) { weekAt--; fill(); } });
8736
- browse.appendChild(el("div", { class: "story-nav" }, [prev, next]));
8737
- browse.appendChild(el("section", { class: "story-card story-week" }, [
8738
- el("h3", { class: "story-card-heading", text: storyWeekLabel(mon) }),
8739
- digestBody(weekOf[mon], "この週の局面", WEEK_EMPTY),
8740
- daysFold(mon),
8741
- ]));
8742
- }
8743
- function fillMonth() {
8744
- if (monthAt === null) return;
8745
- var sel = el("select", { class: "story-month-select", "aria-label": "月を選ぶ" });
8746
- months.forEach(function (m) {
8747
- var o = el("option", { value: m, text: storyMonthLabel(m) });
8748
- if (m === monthAt) o.selected = true;
8749
- sel.appendChild(o);
8750
- });
8751
- sel.addEventListener("change", function () { monthAt = sel.value; fill(); });
8752
- browse.appendChild(el("label", { class: "story-month-pick" }, [el("span", { text: "月を選ぶ" }), sel]));
8753
- browse.appendChild(el("section", { class: "story-card story-month" }, [
8754
- el("h3", { class: "story-card-heading", text: storyMonthLabel(monthAt) }),
8755
- digestBody(monthOf[monthAt], "この月の局面", MONTH_EMPTY),
8756
- ]));
8757
- // その月にかかる週(新しい週から。今週も入れる)
8758
- var first = monthAt + "-01";
8759
- var mon = storyMonday(first);
8760
- var inMonth = [];
8761
- for (var ms = storyMs(mon); storyDay(ms).slice(0, 7) <= monthAt && storyDay(ms) <= today; ms += 7 * 86400000) {
8762
- var k = storyDay(ms);
8763
- if (weekOf[k] || pageCount(k, weekEnd(k)) > 0) inMonth.push(k);
8764
- }
8765
- inMonth.reverse().forEach(function (k) {
8766
- browse.appendChild(el("details", { class: "story-week-fold" }, [
8767
- el("summary", { text: storyWeekLabel(k) }),
8768
- digestBody(weekOf[k], "この週の局面", WEEK_EMPTY),
8769
- daysFold(k),
8686
+ // 月のあらすじを新しい月から順に。「今月」と「月が閉じたがまだあらすじが無い月」は見出し+案内の1行で埋め、
8687
+ // 欠けなく新しい月から並べる(その案件でストーリーが始まる前の月までは埋めない)
8688
+ function renderStoryMonthly() {
8689
+ var months = (SITE.storyDigest && SITE.storyDigest.months) || [];
8690
+ var list = el("div", { class: "story-cards" });
8691
+ var curMonth = SITE.storyToday ? SITE.storyToday.slice(0, 7) : null;
8692
+ if (curMonth) {
8693
+ var have = {};
8694
+ months.forEach(function (m) { have[m.key] = true; });
8695
+ var earliest = storyEarliestKnownDate();
8696
+ var earliestMonth = earliest ? earliest.slice(0, 7) : curMonth;
8697
+ for (var mo = curMonth; !have[mo] && mo >= earliestMonth; mo = storyPrevMonthKey(mo)) {
8698
+ list.appendChild(el("section", { class: "story-card" }, [
8699
+ el("h3", { class: "story-card-heading", text: monthLabel(mo) }),
8700
+ el("p", { class: "story-empty", text: "月が閉じて 3 日後の夜に作ります" }),
8770
8701
  ]));
8771
- });
8772
- }
8773
- function fill() {
8774
- browse.innerHTML = "";
8775
- btnWeek.className = "story-switch-btn" + (view === "week" ? " active" : "");
8776
- btnMonth.className = "story-switch-btn" + (view === "month" ? " active" : "");
8777
- btnWeek.setAttribute("aria-pressed", view === "week" ? "true" : "false");
8778
- btnMonth.setAttribute("aria-pressed", view === "month" ? "true" : "false");
8779
- if (view === "week") fillWeek(); else fillMonth();
8702
+ }
8780
8703
  }
8781
- btnWeek.addEventListener("click", function () { view = "week"; fill(); });
8782
- btnMonth.addEventListener("click", function () { view = "month"; fill(); });
8783
- fill();
8704
+ months.forEach(function (m) { list.appendChild(storyDigestCard(m, "この月の局面")); });
8705
+ app.appendChild(list);
8706
+ }
8707
+
8708
+ function renderStory() {
8709
+ var nav = el("div", { class: "story-sub-tabs" });
8710
+ STORY_SUBS.forEach(function (s) {
8711
+ var b = el("button", { class: "story-sub-tab" + (storySub === s[0] ? " active" : ""), type: "button", text: s[1] });
8712
+ b.addEventListener("click", function () { switchStorySub(s[0]); });
8713
+ nav.appendChild(b);
8714
+ });
8715
+ app.appendChild(nav);
8716
+ if (storySub === "weekly") renderStoryWeekly();
8717
+ else if (storySub === "monthly") renderStoryMonthly();
8718
+ else renderStoryDaily();
8784
8719
  }
8785
8720
 
8786
8721
  // 導入経緯(overview.adoption)の表示情報。Home見出し横のチップに出すラベルだけを返す。
@@ -21203,6 +21138,13 @@ ${calendarAddUrl.toString()}
21203
21138
  // 値が入っていて知らない語だったときは今までどおり動かさない(壊れた ?tab= で
21204
21139
  // 見ていた画面を飛ばさない・go() の行き先も ?tab= か ?id= のどちらかしか作らない)
21205
21140
  else if (tab === null) listState.tab = DEFAULT_TAB;
21141
+ // ストーリーのサブタブ(日次/週次/月次)。?sub= が無い・知らない語なら既定(日次)
21142
+ if (listState.tab === "story") {
21143
+ var sub = getParam("sub");
21144
+ var validSub = false;
21145
+ STORY_SUBS.forEach(function (s) { if (s[0] === sub) validSub = true; });
21146
+ storySub = validSub ? sub : STORY_SUB_DEFAULT;
21147
+ }
21206
21148
  renderList();
21207
21149
  }
21208
21150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.103",
3
+ "version": "0.12.105",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,