@takagaki/cortex-decisions-viewer 0.4.23 → 0.4.25

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
@@ -149,6 +149,7 @@ async function readFleetSources(repoRoot) {
149
149
  ? s.matchKeys.map((k) => String(k)).filter(Boolean)
150
150
  : undefined,
151
151
  driveSync: typeof s.driveSync === "boolean" ? s.driveSync : undefined,
152
+ enabled: typeof s.enabled === "boolean" ? s.enabled : undefined,
152
153
  }))
153
154
  : undefined;
154
155
  const pipelines = Array.isArray(obj.pipelines)
@@ -163,7 +164,18 @@ async function readFleetSources(repoRoot) {
163
164
  applicable: typeof p.applicable === "boolean" ? p.applicable : undefined,
164
165
  }))
165
166
  : undefined;
166
- return { externalSources, internalSources, pipelines };
167
+ // 能力→ツールの宣言マップ(例: { チャット: "slack", 開発: "none" })。"none" は未使用のグレー行表示に使う
168
+ let tools;
169
+ if (obj.tools != null && typeof obj.tools === "object" && !Array.isArray(obj.tools)) {
170
+ tools = {};
171
+ for (const [k, v] of Object.entries(obj.tools)) {
172
+ if (typeof v === "string")
173
+ tools[k] = v;
174
+ }
175
+ if (!Object.keys(tools).length)
176
+ tools = undefined;
177
+ }
178
+ return { externalSources, internalSources, pipelines, tools };
167
179
  }
168
180
  /** ディレクトリ内の .md を汎用レコードとして読み込む(存在しなければ空配列) */
169
181
  async function loadGenericRecords(dirAbs, exclude, repoBaseUrl, branch) {
@@ -948,6 +960,7 @@ async function build(opts) {
948
960
  externalSources: fleet.externalSources,
949
961
  internalSources: fleet.internalSources,
950
962
  pipelines: fleet.pipelines,
963
+ tools: fleet.tools,
951
964
  };
952
965
  // サムネイル(デザイン画面等)をサイトに同梱し、ノードのパスをサイト内相対に書き換える
953
966
  await node_fs_1.promises.mkdir(outAbs, { recursive: true });
package/dist/render.js CHANGED
@@ -17,6 +17,8 @@ function renderSite(site) {
17
17
  // ※ 投入フォーム設定 site.intake は build.ts が --intake-url/--intake-token/--project-key から付与する。
18
18
  const dataJson = JSON.stringify(site).replace(/</g, "\\u003c");
19
19
  const safeTitle = escapeHtml(site.title);
20
+ // 接続マップ(操作タブ最上部のSVG図)。ビルド時に生成し<template>で埋め込む。fleet情報が無ければ出さない
21
+ const connMap = renderConnectionMap(site);
20
22
  const githubBtn = site.repoBaseUrl
21
23
  ? `<a class="gh-btn" href="${escapeHtml(site.repoBaseUrl)}" target="_blank" rel="noopener" title="コンテキストリポジトリをGitHubで開く">${GITHUB_ICON}</a>`
22
24
  : "";
@@ -45,7 +47,7 @@ function renderSite(site) {
45
47
  <footer class="site-footer">
46
48
  <span id="footer-meta"></span>
47
49
  </footer>
48
- <script type="application/json" id="site-data">${dataJson}</script>
50
+ ${connMap ? `<template id="conn-map">${connMap}</template>\n` : ""}<script type="application/json" id="site-data">${dataJson}</script>
49
51
  <script>${CLIENT_JS}</script>
50
52
  </body>
51
53
  </html>`;
@@ -58,6 +60,106 @@ function escapeHtml(s) {
58
60
  .replace(/>/g, "&gt;")
59
61
  .replace(/"/g, "&quot;");
60
62
  }
63
+ /** 接続マップの能力定義(表示順・絵文字は詳細カードの流儀を踏襲) */
64
+ const CM_CAPS = [
65
+ { cap: "課題管理", icon: "📋", pipe: /sync-backlog/ },
66
+ { cap: "会議", icon: "🎙", pipe: /ingest-minutes/ },
67
+ { cap: "共有資料", icon: "📁", pipe: /sync-materials/ },
68
+ { cap: "デザイン", icon: "🎨", pipe: /sync-designs|update-design-notes/ },
69
+ { cap: "チャット", icon: "💬", pipe: null },
70
+ { cap: "開発", icon: "🐙", pipe: null },
71
+ ];
72
+ /** ツールid→表示名(接続マップのノードラベル用) */
73
+ const CM_TOOL_LABEL = {
74
+ backlog: "Backlog",
75
+ jira: "Jira",
76
+ "google-meet": "Google Meet",
77
+ teams: "Teams",
78
+ "google-drive": "Google Drive",
79
+ box: "Box",
80
+ local: "ローカル",
81
+ figma: "Figma",
82
+ slack: "Slack",
83
+ github: "GitHub",
84
+ };
85
+ /**
86
+ * 接続マップ(データソースとの接続を視覚化したSVG)をビルド時に生成する。
87
+ * 上段に6アダプター(能力)ノード、下中央にリポジトリノード、間をベジェの破線で結ぶ。
88
+ * 状態: 健全=色付き+流れるアニメーション / 問題あり=黄+⚠️ / 未使用=グレー。
89
+ * fleet-status由来の情報が何も無ければ null(マップを出さない・後方互換)。
90
+ */
91
+ function renderConnectionMap(site) {
92
+ const internals = site.internalSources ?? [];
93
+ const externals = site.externalSources ?? [];
94
+ const pipes = site.pipelines ?? [];
95
+ const tools = site.tools ?? {};
96
+ if (!internals.length && !externals.length && !pipes.length && !Object.keys(tools).length) {
97
+ return null;
98
+ }
99
+ const slackExts = externals.filter((s) => s.type === "slack");
100
+ const ghExts = externals.filter((s) => s.type.startsWith("github"));
101
+ const nodes = [];
102
+ const links = [];
103
+ const W = 960;
104
+ const REPO_X = W / 2;
105
+ CM_CAPS.forEach((c, i) => {
106
+ const cx = 80 + i * 160;
107
+ const toolVal = tools[c.cap];
108
+ const internal = internals.find((s) => s.kind === c.cap);
109
+ const isLive = c.cap === "チャット" || c.cap === "開発";
110
+ const liveExts = c.cap === "チャット" ? slackExts : c.cap === "開発" ? ghExts : [];
111
+ // 使用中か: tools宣言がnone以外 / internalSourceが存在(enabled:false除く) / ライブ参照の実体がある
112
+ const used = (toolVal != null && toolVal !== "none") ||
113
+ (internal != null && internal.enabled !== false) ||
114
+ (isLive && liveExts.length > 0);
115
+ const off = toolVal === "none" || internal?.enabled === false || !used;
116
+ // 問題あり: ライブ参照はgate≠okが1件以上、同期ソースは対応パイプラインの直近runがfailure
117
+ const warn = !off &&
118
+ (isLive
119
+ ? liveExts.some((s) => s.gate !== "ok")
120
+ : pipes.some((p) => c.pipe != null && c.pipe.test(p.id) && p.applicable !== false && p.lastConclusion === "failure"));
121
+ const state = off ? "cm-off" : warn ? "cm-warn" : "cm-ok";
122
+ const toolName = toolVal && toolVal !== "none"
123
+ ? CM_TOOL_LABEL[toolVal] ?? toolVal
124
+ : internal
125
+ ? CM_TOOL_LABEL[internal.tool] ?? internal.tool
126
+ : isLive && liveExts.length
127
+ ? CM_TOOL_LABEL[c.cap === "チャット" ? "slack" : "github"]
128
+ : c.cap;
129
+ const label = `${c.icon} ${off && (toolVal === "none" || !toolName) ? c.cap : toolName}`;
130
+ // ノード下の補足: 未使用 / ライブ参照 / 最終同期(時刻はクライアントでローカルタイム整形するため data-ts で渡す)
131
+ let sub = "";
132
+ if (off) {
133
+ sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle">未使用</text>`;
134
+ }
135
+ else if (isLive) {
136
+ sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle">ライブ参照</text>`;
137
+ }
138
+ else if (internal?.lastSync) {
139
+ sub = `<text class="cm-sync" x="${cx}" y="60" text-anchor="middle" data-ts="${escapeHtml(internal.lastSync)}"></text>`;
140
+ }
141
+ const badge = warn ? `<text class="cm-badge" x="${cx + 62}" y="26" text-anchor="middle">⚠️</text>` : "";
142
+ // クリックでページ内の該当詳細カード(アンカー)へスクロールする
143
+ const anchor = `ds-${c.cap}`;
144
+ nodes.push(`<g class="cm-node ${state}" data-target="${anchor}" tabindex="0" role="link" aria-label="${escapeHtml(c.cap)}の詳細へ">` +
145
+ `<rect x="${cx - 74}" y="16" width="148" height="54" rx="10"></rect>` +
146
+ `<text class="cm-label" x="${cx}" y="40" text-anchor="middle">${escapeHtml(label)}</text>` +
147
+ sub +
148
+ badge +
149
+ `</g>`);
150
+ links.push(`<path class="cm-link ${state}" d="M ${cx} 72 C ${cx} 150, ${REPO_X} 160, ${REPO_X} 238"></path>`);
151
+ });
152
+ const repoTitle = site.title.length > 24 ? site.title.slice(0, 23) + "…" : site.title;
153
+ const repo = `<g class="cm-repo">` +
154
+ `<rect x="${REPO_X - 170}" y="240" width="340" height="52" rx="12"></rect>` +
155
+ `<text class="cm-repo-label" x="${REPO_X}" y="272" text-anchor="middle">📦 コンテキストリポジトリ(${escapeHtml(repoTitle)})</text>` +
156
+ `</g>`;
157
+ return (`<svg class="cm-svg" viewBox="0 0 ${W} 312" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="データソースとの接続マップ">` +
158
+ links.join("") +
159
+ nodes.join("") +
160
+ repo +
161
+ `</svg>`);
162
+ }
61
163
  const CSS = `
62
164
  :root {
63
165
  --bg: #f6f7f9;
@@ -360,6 +462,35 @@ a.ds-name:hover { text-decoration: underline; }
360
462
  .ds-group > .ds-list { margin-top: 8px; padding-left: 12px; border-left: 2px solid var(--border); }
361
463
  .badge.ds-sum-ok { background: #e9f7ef; color: #1f7a45; }
362
464
  .badge.ds-sum-warn { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
465
+ /* ---- 接続マップ(操作タブ最上部のSVG図) ---- */
466
+ .conn-map { overflow-x: auto; margin: 4px 0 20px; }
467
+ .conn-map .cm-svg { display: block; min-width: 760px; width: 100%; height: auto; font-family: inherit; }
468
+ .cm-node { cursor: pointer; outline: none; }
469
+ .cm-node rect { fill: var(--surface); stroke: var(--border); stroke-width: 1.5; }
470
+ .cm-node:hover rect, .cm-node:focus rect { stroke: var(--accent); }
471
+ .cm-node.cm-ok rect { stroke: var(--accent); }
472
+ .cm-node.cm-warn rect { fill: #fffbeb; stroke: #fcd34d; }
473
+ .cm-node.cm-off rect { fill: #f8f9fa; stroke: #d1d5db; stroke-dasharray: 4 3; }
474
+ .cm-label { font-size: 13.5px; font-weight: 600; fill: var(--text); }
475
+ .cm-node.cm-warn .cm-label { fill: #92400e; }
476
+ .cm-node.cm-off .cm-label { fill: #9ca3af; }
477
+ .cm-node.cm-off text { filter: grayscale(1); opacity: .8; }
478
+ .cm-sync { font-size: 10.5px; fill: var(--muted); font-variant-numeric: tabular-nums; }
479
+ .cm-node.cm-off .cm-sync { fill: #9ca3af; }
480
+ .cm-badge { font-size: 13px; }
481
+ .cm-link { fill: none; stroke-width: 1.6; stroke-dasharray: 6 6; stroke: #94a3b8; }
482
+ .cm-link.cm-ok { stroke: var(--accent); animation: cm-flow 1.1s linear infinite; }
483
+ .cm-link.cm-warn { stroke: #d97706; }
484
+ .cm-link.cm-off { stroke: #d1d5db; opacity: .6; }
485
+ @keyframes cm-flow { to { stroke-dashoffset: -24; } }
486
+ @media (prefers-reduced-motion: reduce) { .cm-link.cm-ok { animation: none; } }
487
+ .cm-repo rect { fill: var(--accent-weak); stroke: var(--accent); stroke-width: 1.5; }
488
+ .cm-repo-label { font-size: 14px; font-weight: 700; fill: #1e3a8a; }
489
+ /* 未使用(非活性)行: アダプターとして存在するが、この案件では設定されていないもの */
490
+ .ds-card.ds-inactive, .ds-pipe.ds-inactive { background: #f8f9fa; border-style: dashed; box-shadow: none; }
491
+ .ds-inactive .ds-icon { filter: grayscale(1); opacity: .55; }
492
+ .ds-inactive .ds-type, .ds-inactive .ds-name, .ds-inactive a.ds-name, .ds-inactive .ds-pipe-label { color: #9ca3af; }
493
+ .ds-inactive-note { font-size: 12px; color: #9ca3af; line-height: 1.6; flex: 0 0 auto; }
363
494
  .ds-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 0 0 auto; }
364
495
  .badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
365
496
  .badge.ds-excluded { background: #eceef1; color: #6b7280; }
@@ -511,8 +642,11 @@ const CLIENT_JS = `
511
642
  if (SITE.graph && SITE.graph.nodes.length > 0) {
512
643
  TABS.push({ key: "graph", label: "ナレッジグラフ" });
513
644
  }
514
- // Cortex自体を操作するアクションの受け皿(取り込み等)。今後この種の機能が増えたらここに集約する。
515
- if (SITE.intake) {
645
+ // Cortex自体を操作するアクションと、接続・自動化の情報の受け皿(取り込み・接続マップ・データソース)。
646
+ // 投入フォーム設定 or fleet-status由来の接続情報のどちらかがあれば出す。
647
+ var HAS_FLEET = !!((SITE.internalSources || []).length || (SITE.externalSources || []).length ||
648
+ (SITE.pipelines || []).length || SITE.tools);
649
+ if (SITE.intake || HAS_FLEET) {
516
650
  TABS.push({ key: "ops", label: "操作" });
517
651
  }
518
652
 
@@ -695,7 +829,6 @@ const CLIENT_JS = `
695
829
  var homeBody = el("div", { class: "body" });
696
830
  app.appendChild(homeBody);
697
831
  loadBody(h, homeBody);
698
- renderDataSources();
699
832
  }
700
833
 
701
834
  // ---------- データソースと自動化(fleet-status.json 由来。無ければ非表示) ----------
@@ -735,10 +868,12 @@ const CLIENT_JS = `
735
868
  function renderDataSources() {
736
869
  var internals = SITE.internalSources || [];
737
870
  var sources = SITE.externalSources || [];
738
- // tools宣言上この案件の対象外(applicable: false)のパイプラインは載せない
739
- // (スキップ正常終了のrunが成功表示に見えるため)。フィールド無しは従来どおり表示する。
740
- var pipes = (SITE.pipelines || []).filter(function (p) { return p.applicable !== false; });
741
- if (!internals.length && !sources.length && !pipes.length) return; // すべて無ければセクションごと出さない
871
+ var pipes = SITE.pipelines || [];
872
+ // 能力→ツールの宣言(例: チャット: "none")。"none" の能力は「未使用」のグレー行で存在だけ見せる
873
+ var tools = SITE.tools || {};
874
+ var chatOff = tools["チャット"] === "none";
875
+ var devOff = tools["開発"] === "none";
876
+ if (!internals.length && !sources.length && !pipes.length && !chatOff && !devOff) return; // すべて無ければセクションごと出さない
742
877
 
743
878
  var sec = el("section", { class: "datasources" });
744
879
  sec.appendChild(el("h2", { class: "datasources-head", text: "データソースと自動化" }));
@@ -763,13 +898,19 @@ const CLIENT_JS = `
763
898
  if (s.driveSync === false) {
764
899
  col.appendChild(el("span", { class: "ds-drive-note", text: "Drive自動同期は未設定(手動で置いたファイルの変換のみ)" }));
765
900
  }
766
- var card = el("div", { class: "ds-card" }, [
901
+ // 未使用(enabled: false)はグレー行。アダプターの存在は見せつつ、この案件では設定されていないことを示す
902
+ var inactive = s.enabled === false;
903
+ if (inactive) {
904
+ col.appendChild(el("span", { class: "ds-inactive-note", text: "未使用(この案件では設定されていません)" }));
905
+ }
906
+ // id は接続マップのノードclickの飛び先(アンカー)
907
+ var card = el("div", { class: "ds-card" + (inactive ? " ds-inactive" : ""), id: "ds-" + (s.kind || "") }, [
767
908
  el("div", { class: "ds-card-main" }, [
768
909
  el("span", { class: "ds-icon", text: DS_KIND_ICON[s.kind] || "📁" }),
769
910
  el("span", { class: "ds-type", text: s.kind || "" }),
770
911
  col,
771
912
  ]),
772
- s.lastSync && dsFmtTime(s.lastSync)
913
+ !inactive && s.lastSync && dsFmtTime(s.lastSync)
773
914
  ? el("span", { class: "ds-sync", text: "最終同期: " + dsFmtTime(s.lastSync) })
774
915
  : null,
775
916
  ]);
@@ -779,7 +920,7 @@ const CLIENT_JS = `
779
920
  sec.appendChild(el("p", { class: "ds-note", text: "これらの更新は毎晩の Gold 昇格が読み取ります。" }));
780
921
  }
781
922
 
782
- if (sources.length) {
923
+ if (sources.length || chatOff || devOff) {
783
924
  sec.appendChild(el("div", { class: "ds-sub", text: "外部ソース(ライブ参照)" }));
784
925
  // ツール(type)別にグループ化し、多いグループ(4件以上)は折りたたむ
785
926
  var groups = [];
@@ -791,8 +932,16 @@ const CLIENT_JS = `
791
932
  });
792
933
  var UNIT = { "slack": "チャンネル", "github-issues": "リポジトリ", "github-discussions": "リポジトリ" };
793
934
  var wrap = el("div", { class: "ds-list" });
935
+ // チャット未使用: Slackグループの位置(先頭)に「存在するが未設定」のグレー行を出す
936
+ if (chatOff) wrap.appendChild(inactiveSourceRow("💬", "チャット", "ds-チャット"));
937
+ var chatAnchored = chatOff;
938
+ var devAnchored = devOff;
794
939
  groups.forEach(function (g) {
795
940
  var meta = DS_TYPE[g.type] || { icon: "🔗", label: g.type || "その他" };
941
+ // 接続マップのノードclickの飛び先(アンカー)。チャット=Slack群の先頭・開発=GitHub群の先頭
942
+ var anchorId = null;
943
+ if (g.type === "slack" && !chatAnchored) { anchorId = "ds-チャット"; chatAnchored = true; }
944
+ if (g.type.indexOf("github") === 0 && !devAnchored) { anchorId = "ds-開発"; devAnchored = true; }
796
945
  var cards = el("div", { class: "ds-list" });
797
946
  g.items.forEach(function (s) { cards.appendChild(extSourceCard(s, meta)); });
798
947
  if (g.items.length >= 4) {
@@ -805,11 +954,16 @@ const CLIENT_JS = `
805
954
  ? el("span", { class: "badge ds-sum-ok", text: "接続OK" })
806
955
  : el("span", { class: "badge ds-sum-warn", text: "⚠️ " + bad + "件に問題" }),
807
956
  ]);
808
- wrap.appendChild(el("details", { class: "ds-group" }, [sum, cards]));
957
+ var det = el("details", { class: "ds-group" }, [sum, cards]);
958
+ if (anchorId) det.id = anchorId;
959
+ wrap.appendChild(det);
809
960
  } else {
961
+ if (anchorId && cards.firstChild) cards.firstChild.id = anchorId;
810
962
  while (cards.firstChild) wrap.appendChild(cards.firstChild);
811
963
  }
812
964
  });
965
+ // 開発未使用: GitHubグループの位置(末尾)にグレー行を出す
966
+ if (devOff) wrap.appendChild(inactiveSourceRow("🐙", "GitHub", "ds-開発"));
813
967
  sec.appendChild(wrap);
814
968
  }
815
969
 
@@ -817,6 +971,15 @@ const CLIENT_JS = `
817
971
  sec.appendChild(el("div", { class: "ds-sub", text: "自動化パイプライン" }));
818
972
  var plist = el("div", { class: "ds-pipes" });
819
973
  pipes.forEach(function (p) {
974
+ // tools宣言上この案件の対象外(applicable: false)はグレー行。✅❌や最終成功は出さない
975
+ // (スキップ正常終了のrunが成功表示に見えるため)。存在自体は見せる
976
+ if (p.applicable === false) {
977
+ plist.appendChild(el("div", { class: "ds-pipe ds-inactive" }, [
978
+ el("span", { class: "ds-pipe-label", text: p.label || p.id || "" }),
979
+ el("span", { class: "ds-inactive-note", text: "未使用" }),
980
+ ]));
981
+ return;
982
+ }
820
983
  var t = p.lastSuccess ? dsFmtTime(p.lastSuccess) : "";
821
984
  var children = [];
822
985
  if (p.lastConclusion === "success") {
@@ -833,6 +996,17 @@ const CLIENT_JS = `
833
996
 
834
997
  app.appendChild(sec);
835
998
  }
999
+ // 未使用能力のプレースホルダ行(アダプターとして存在するが、この案件では設定されていないもの)
1000
+ function inactiveSourceRow(icon, typeLabel, anchorId) {
1001
+ return el("div", { class: "ds-card ds-inactive", id: anchorId }, [
1002
+ el("div", { class: "ds-card-main" }, [
1003
+ el("span", { class: "ds-icon", text: icon }),
1004
+ el("span", { class: "ds-type", text: typeLabel }),
1005
+ el("span", { class: "ds-name", text: "未使用" }),
1006
+ ]),
1007
+ el("span", { class: "ds-inactive-note", text: "この案件では設定されていません" }),
1008
+ ]);
1009
+ }
836
1010
  // 外部ソース1件のカード(グループ化表示・展開表示の両方から使う)
837
1011
  function extSourceCard(s, meta) {
838
1012
  var badges = el("div", { class: "ds-badges" });
@@ -851,14 +1025,39 @@ const CLIENT_JS = `
851
1025
  ]);
852
1026
  }
853
1027
 
854
- // ---------- 操作タブ(Cortex自体への操作。まずは取り込み。今後この受け皿に機能を足す) ----------
1028
+ // ---------- 操作タブ(接続マップ・データソースと自動化・取り込み。今後この受け皿に機能を足す) ----------
855
1029
  function renderOps() {
856
- app.appendChild(el("p", { class: "ops-intro", text: "Cortex にコンテキストを取り込む操作です。ファイルを選ぶかテキストを貼って送信すると、自動で取り込まれます(数分で反映)。" }));
857
- app.appendChild(el("div", { class: "section-label", text: "取り込み" }));
858
- var grid = el("div", { class: "ops-grid" });
859
- grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
860
- grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
861
- app.appendChild(grid);
1030
+ renderConnMap();
1031
+ renderDataSources();
1032
+ if (SITE.intake) {
1033
+ app.appendChild(el("p", { class: "ops-intro", text: "Cortex にコンテキストを取り込む操作です。ファイルを選ぶかテキストを貼って送信すると、自動で取り込まれます(数分で反映)。" }));
1034
+ app.appendChild(el("div", { class: "section-label", text: "取り込み" }));
1035
+ var grid = el("div", { class: "ops-grid" });
1036
+ grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
1037
+ grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
1038
+ app.appendChild(grid);
1039
+ }
1040
+ }
1041
+
1042
+ // ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
1043
+ function renderConnMap() {
1044
+ var tpl = document.getElementById("conn-map");
1045
+ if (!tpl) return; // fleet情報が無い(旧fleet-status等)→ マップなし
1046
+ var wrap = el("div", { class: "conn-map" });
1047
+ wrap.appendChild(tpl.content.cloneNode(true));
1048
+ // 最終同期の時刻はブラウザのローカルタイムで整形(詳細カードの表示と揃える)
1049
+ wrap.querySelectorAll("[data-ts]").forEach(function (t) {
1050
+ var s = dsFmtTime(t.getAttribute("data-ts"));
1051
+ if (s) t.textContent = "最終同期 " + s;
1052
+ });
1053
+ // ノードclickで、下の該当詳細カード(アンカー)へスクロール
1054
+ wrap.addEventListener("click", function (ev) {
1055
+ var g = ev.target && ev.target.closest ? ev.target.closest("[data-target]") : null;
1056
+ if (!g) return;
1057
+ var target = document.getElementById(g.getAttribute("data-target"));
1058
+ if (target && target.scrollIntoView) target.scrollIntoView({ behavior: "smooth", block: "center" });
1059
+ });
1060
+ app.appendChild(wrap);
862
1061
  }
863
1062
  function opsCard(icon, title, desc, kind, btnLabel) {
864
1063
  var btn = el("button", { class: "ops-card-btn", text: "+ " + btnLabel });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,