@takagaki/cortex-decisions-viewer 0.4.26 → 0.4.29

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.
Files changed (2) hide show
  1. package/dist/render.js +168 -3
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -19,6 +19,8 @@ function renderSite(site) {
19
19
  const safeTitle = escapeHtml(site.title);
20
20
  // 接続マップ(操作タブ最上部のSVG図)。ビルド時に生成し<template>で埋め込む。fleet情報が無ければ出さない
21
21
  const connMap = renderConnectionMap(site);
22
+ // チュートリアル(Homeタブ下部の「はじめに」セクション)。ボタンURLは既存の codeUrl を流用し、案件データ由来の値だけビルド時に埋める
23
+ const tutorialJson = JSON.stringify(buildTutorialData(site, codeUrl, repoSlug)).replace(/</g, "\\u003c");
22
24
  const githubBtn = site.repoBaseUrl
23
25
  ? `<a class="gh-btn" href="${escapeHtml(site.repoBaseUrl)}" target="_blank" rel="noopener" title="コンテキストリポジトリをGitHubで開く">${GITHUB_ICON}</a>`
24
26
  : "";
@@ -47,7 +49,8 @@ function renderSite(site) {
47
49
  <footer class="site-footer">
48
50
  <span id="footer-meta"></span>
49
51
  </footer>
50
- ${connMap ? `<template id="conn-map">${connMap}</template>\n` : ""}<script type="application/json" id="site-data">${dataJson}</script>
52
+ ${connMap ? `<template id="conn-map">${connMap}</template>\n` : ""}<script type="application/json" id="tutorial-data">${tutorialJson}</script>
53
+ <script type="application/json" id="site-data">${dataJson}</script>
51
54
  <script>${CLIENT_JS}</script>
52
55
  </body>
53
56
  </html>`;
@@ -160,6 +163,69 @@ function renderConnectionMap(site) {
160
163
  repo +
161
164
  `</svg>`);
162
165
  }
166
+ /**
167
+ * 「はじめに」セクション(Homeタブ下部)のチュートリアルデータをビルド時に生成する。
168
+ * 各チャレンジのプロンプトは固定文(ビューアコードに実案件名は残さない)。
169
+ * チャレンジ4だけは site.decisions から根拠付きの最新決定を選び、そのtitle/idを埋め込む。
170
+ */
171
+ function buildTutorialData(site, codeUrl, repoSlug) {
172
+ const c4 = tutorialChallenge4Prompt(site.decisions);
173
+ const specs = [
174
+ {
175
+ title: "全体像を知る",
176
+ desc: "プロジェクトの目的と現在地を掴む",
177
+ prompt: "このプロジェクトの全体像——目的・現状・主要な論点——を、蓄積されたコンテキストをもとに教えてください。",
178
+ },
179
+ {
180
+ title: "関係者を知る",
181
+ desc: "誰が何を担っているか",
182
+ prompt: "このプロジェクトの関係者(社内・顧客・関係会社)と、それぞれの役割を教えてください。",
183
+ },
184
+ {
185
+ title: "ドメインを可視化する",
186
+ desc: "案件の概念と関係を図にする",
187
+ prompt: "このプロジェクトの主要なドメイン概念(用語集)と、それらの関係性を mermaid 図で可視化してください。",
188
+ },
189
+ {
190
+ title: "決定の経緯を辿る",
191
+ desc: "なぜそう決まったかを根拠まで遡る",
192
+ prompt: c4,
193
+ },
194
+ {
195
+ title: "最近の動きを掴む",
196
+ desc: "直近のキャッチアップ",
197
+ prompt: "直近2週間の主要な動き・論点・未解決事項を、議事録や決定記録を横断してまとめてください。",
198
+ },
199
+ ];
200
+ return {
201
+ slug: repoSlug,
202
+ challenges: specs.map((s, i) => ({
203
+ n: i + 1,
204
+ title: s.title,
205
+ desc: s.desc,
206
+ url: codeUrl(s.prompt),
207
+ })),
208
+ };
209
+ }
210
+ /**
211
+ * チャレンジ4のプロンプトを決める。
212
+ * based_on を1件以上持つ決定のうちID最新 → 無ければ最新決定 → 決定ゼロなら汎用文。
213
+ * IDは YYYYMMDD-NNN 形式で文字列降順が新しい順。
214
+ */
215
+ function tutorialChallenge4Prompt(decisions) {
216
+ const generic = "このプロジェクトの重要な決定を1つ選び、なぜそう決まったのか、根拠になった会議や課題まで遡って経緯を説明してください。";
217
+ if (!decisions || !decisions.length)
218
+ return generic;
219
+ const byIdDesc = (a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
220
+ const withBasedOn = decisions
221
+ .filter((d) => (d.relations ?? []).some((r) => r.rel === "based_on"))
222
+ .sort(byIdDesc);
223
+ const picked = withBasedOn[0] ?? [...decisions].sort(byIdDesc)[0];
224
+ if (!picked)
225
+ return generic;
226
+ return (`このプロジェクトの決定「${picked.title}」(${picked.id})について、なぜこの結論に至ったのか、` +
227
+ "根拠になった会議や課題まで遡って経緯を説明してください。関連する未解決の論点があれば併せて教えてください。");
228
+ }
163
229
  const CSS = `
164
230
  :root {
165
231
  --bg: #f6f7f9;
@@ -426,6 +492,40 @@ a:hover { text-decoration: underline; }
426
492
  }
427
493
  .edit-btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
428
494
  .section-label { font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; margin: 24px 0 8px; }
495
+ /* ---- はじめに(チュートリアル・Homeタブ下部) ---- */
496
+ .tut-section { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--border); }
497
+ .tut-section-head { font-size: 18px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
498
+ .tut-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 2px 0 6px; }
499
+ .tut-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px; margin: 8px 0 16px; }
500
+ .tut-progress { font-size: 13px; font-weight: 700; color: var(--text); }
501
+ .tut-progress-bar { flex: 1 1 180px; height: 8px; border-radius: 999px; background: #e6eaf0; overflow: hidden; min-width: 120px; }
502
+ .tut-progress-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width .25s ease; }
503
+ .tut-celebrate { font-size: 13.5px; font-weight: 700; color: #1f7a45; }
504
+ .tut-list { display: flex; flex-direction: column; gap: 12px; }
505
+ .tut-card {
506
+ background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
507
+ padding: 16px 18px; box-shadow: var(--shadow); display: flex; align-items: flex-start; gap: 14px;
508
+ }
509
+ .tut-card.tut-done { border-color: #a7f3d0; background: #f4fdf8; }
510
+ .tut-num {
511
+ flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; background: var(--accent-weak);
512
+ color: var(--accent); font-weight: 700; font-size: 14px; display: flex; align-items: center; justify-content: center;
513
+ }
514
+ .tut-card.tut-done .tut-num { background: #d1fae5; color: #047857; }
515
+ .tut-body { flex: 1 1 auto; min-width: 0; }
516
+ .tut-title { font-size: 15.5px; font-weight: 700; color: var(--text); margin: 0 0 3px; }
517
+ .tut-desc { font-size: 13px; color: var(--muted); line-height: 1.7; margin: 0 0 12px; }
518
+ .tut-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 16px; }
519
+ .tut-btn {
520
+ display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 999px;
521
+ border: 1px solid var(--accent); background: var(--accent); color: #fff; font-size: 13px; font-weight: 600; white-space: nowrap;
522
+ }
523
+ .tut-btn:hover { background: #1d4ed8; text-decoration: none; }
524
+ .tut-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; user-select: none; }
525
+ .tut-check input { width: 15px; height: 15px; cursor: pointer; }
526
+ @media (max-width: 600px) {
527
+ .tut-actions { flex-direction: column; align-items: flex-start; }
528
+ }
429
529
  /* ---- データソースと自動化 ---- */
430
530
  .datasources { margin-top: 34px; }
431
531
  .datasources-head { font-size: 18px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
@@ -607,6 +707,11 @@ button.ops-card-btn:disabled { opacity: .6; cursor: default; }
607
707
  const CLIENT_JS = `
608
708
  (function () {
609
709
  var SITE = JSON.parse(document.getElementById("site-data").textContent);
710
+ // 「はじめに」セクションのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
711
+ var TUTORIAL = (function () {
712
+ var elx = document.getElementById("tutorial-data");
713
+ try { return elx ? JSON.parse(elx.textContent) : null; } catch (e) { return null; }
714
+ })();
610
715
 
611
716
  // ---------- インデックス構築 ----------
612
717
  var byId = {};
@@ -633,6 +738,7 @@ const CLIENT_JS = `
633
738
  // (ここで inline bodyHtml に適用すると検索テキストにHTMLが混ざるため適用しない)。
634
739
 
635
740
  // タブ: ホーム → Decisions → records/ を持つ各ディレクトリ(自動発見)
741
+ // オンボーディングのチュートリアル(5チャレンジ)は独立タブを廃止し、Homeタブ下部に統合した
636
742
  var TABS = [];
637
743
  if (SITE.home) TABS.push({ key: "home", label: "Home" });
638
744
  TABS.push({ key: "decisions", label: "Decisions", items: SITE.decisions });
@@ -650,6 +756,10 @@ const CLIENT_JS = `
650
756
  TABS.push({ key: "ops", label: "操作" });
651
757
  }
652
758
 
759
+ // 既定の着地タブ: home がある案件は Home、無ければ先頭タブ。
760
+ // URLに ?tab= があれば route() がそれを優先するので直リンクは不変。
761
+ var DEFAULT_TAB = SITE.home ? "home" : TABS[0].key;
762
+
653
763
  var app = document.getElementById("app");
654
764
  // ウィキリンク(本文中の [[用語]])クリックでSPA内遷移する
655
765
  app.addEventListener("click", function (ev) {
@@ -693,7 +803,7 @@ const CLIENT_JS = `
693
803
  return new URLSearchParams(location.search).get(name);
694
804
  }
695
805
  function tabUrl(tabKey) {
696
- return tabKey === TABS[0].key ? "?" : "?tab=" + encodeURIComponent(tabKey);
806
+ return tabKey === DEFAULT_TAB ? "?" : "?tab=" + encodeURIComponent(tabKey);
697
807
  }
698
808
  function go(id) {
699
809
  var url = id ? "?id=" + encodeURIComponent(id) : tabUrl(listState.tab);
@@ -702,7 +812,7 @@ const CLIENT_JS = `
702
812
  window.scrollTo(0, 0);
703
813
  }
704
814
 
705
- var listState = { tab: TABS[0].key, q: "", category: "", month: "", page: 1 };
815
+ var listState = { tab: DEFAULT_TAB, q: "", category: "", month: "", page: 1 };
706
816
 
707
817
  // 一覧のページネーション(描画コスト対策。1ページあたりの件数)
708
818
  var PAGE_SIZE = 30;
@@ -813,6 +923,59 @@ const CLIENT_JS = `
813
923
  return SITE.home && SITE.home.fm ? adoptionInfo(SITE.home.fm.adoption) : null;
814
924
  }
815
925
 
926
+ // ---------- はじめに(チュートリアル・Homeタブ下部に統合) ----------
927
+ // localStorageキーは案件別(repoSlug)。同一ブラウザで複数案件のビューアを見ても混ざらない
928
+ function tutStoreKey(n) { return "cortex-tutorial-done:" + ((TUTORIAL && TUTORIAL.slug) || "") + ":" + n; }
929
+ function tutIsDone(n) {
930
+ try { return localStorage.getItem(tutStoreKey(n)) === "1"; } catch (e) { return false; }
931
+ }
932
+ function tutSetDone(n, done) {
933
+ try { if (done) localStorage.setItem(tutStoreKey(n), "1"); else localStorage.removeItem(tutStoreKey(n)); } catch (e) {}
934
+ }
935
+ // Homeの本文の下に差し込むチュートリアルセクション(見出し+進捗+5チャレンジカード)
936
+ function renderTutorialSection() {
937
+ var challenges = (TUTORIAL && TUTORIAL.challenges) || [];
938
+ if (!challenges.length) return; // 最小データ(チュートリアル無し)では出さない
939
+
940
+ var sec = el("section", { class: "tut-section" });
941
+ sec.appendChild(el("h2", { class: "tut-section-head", text: "はじめに — このプロジェクトを掴む5ステップ" }));
942
+ sec.appendChild(el("p", { class: "tut-intro", text: "AIS Viewer と Claude Code を使ってこのプロジェクトを掴むための5ステップです。各ボタンを押すと、質問が入力済みの状態で Claude Code Web が開きます(このリポジトリへのアクセス権が必要です)。" }));
943
+
944
+ // 進捗ヘッダ({完了数}/5・バー・全完了で祝福)
945
+ var doneCount = challenges.filter(function (c) { return tutIsDone(c.n); }).length;
946
+ var progText = el("span", { class: "tut-progress", text: doneCount + "/" + challenges.length + " 完了" });
947
+ var fill = el("div", { class: "tut-progress-fill" });
948
+ fill.style.width = (challenges.length ? Math.round((doneCount / challenges.length) * 100) : 0) + "%";
949
+ var bar = el("div", { class: "tut-progress-bar" }, [fill]);
950
+ var celebrate = el("span", { class: "tut-celebrate" });
951
+ if (doneCount === challenges.length && challenges.length) celebrate.textContent = "🎉 全ステップ達成!オンボーディング完了です。";
952
+ sec.appendChild(el("div", { class: "tut-head" }, [progText, bar, celebrate]));
953
+
954
+ var list = el("div", { class: "tut-list" });
955
+ challenges.forEach(function (c) {
956
+ var done = tutIsDone(c.n);
957
+ var card = el("div", { class: "tut-card" + (done ? " tut-done" : "") });
958
+ var btn = el("a", { class: "tut-btn", href: c.url, target: "_blank", rel: "noopener", text: "🚀 挑戦する" });
959
+ var cb = el("input", { type: "checkbox" });
960
+ cb.checked = done;
961
+ var check = el("label", { class: "tut-check" }, [cb, el("span", { text: "完了" })]);
962
+ cb.addEventListener("change", function () {
963
+ tutSetDone(c.n, cb.checked);
964
+ // 進捗・祝福・カードの見た目を即時更新(Homeを再描画して反映)
965
+ renderList();
966
+ });
967
+ card.appendChild(el("div", { class: "tut-num", text: String(c.n) }));
968
+ card.appendChild(el("div", { class: "tut-body" }, [
969
+ el("h3", { class: "tut-title", text: c.title }),
970
+ el("p", { class: "tut-desc", text: c.desc }),
971
+ el("div", { class: "tut-actions" }, [btn, check]),
972
+ ]));
973
+ list.appendChild(card);
974
+ });
975
+ sec.appendChild(list);
976
+ app.appendChild(sec);
977
+ }
978
+
816
979
  // ---------- ホーム(着地画面: Markdownをそのまま表示) ----------
817
980
  function renderHome() {
818
981
  var h = SITE.home;
@@ -829,6 +992,8 @@ const CLIENT_JS = `
829
992
  var homeBody = el("div", { class: "body" });
830
993
  app.appendChild(homeBody);
831
994
  loadBody(h, homeBody);
995
+ // Markdown本文の下にオンボーディングのチュートリアルを差し込む
996
+ renderTutorialSection();
832
997
  }
833
998
 
834
999
  // ---------- データソースと自動化(fleet-status.json 由来。無ければ非表示) ----------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.4.26",
3
+ "version": "0.4.29",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,