@takagaki/cortex-decisions-viewer 0.4.26 → 0.4.28
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/render.js +164 -4
- 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
|
+
// チュートリアル(「はじめに」タブ)。ボタン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="
|
|
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
|
+
* 「はじめに」タブのチュートリアルデータをビルド時に生成する。
|
|
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,38 @@ 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
|
+
/* ---- はじめに(チュートリアル) ---- */
|
|
496
|
+
.tut-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 2px 0 6px; }
|
|
497
|
+
.tut-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px; margin: 8px 0 16px; }
|
|
498
|
+
.tut-progress { font-size: 13px; font-weight: 700; color: var(--text); }
|
|
499
|
+
.tut-progress-bar { flex: 1 1 180px; height: 8px; border-radius: 999px; background: #e6eaf0; overflow: hidden; min-width: 120px; }
|
|
500
|
+
.tut-progress-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width .25s ease; }
|
|
501
|
+
.tut-celebrate { font-size: 13.5px; font-weight: 700; color: #1f7a45; }
|
|
502
|
+
.tut-list { display: flex; flex-direction: column; gap: 12px; }
|
|
503
|
+
.tut-card {
|
|
504
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
505
|
+
padding: 16px 18px; box-shadow: var(--shadow); display: flex; align-items: flex-start; gap: 14px;
|
|
506
|
+
}
|
|
507
|
+
.tut-card.tut-done { border-color: #a7f3d0; background: #f4fdf8; }
|
|
508
|
+
.tut-num {
|
|
509
|
+
flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; background: var(--accent-weak);
|
|
510
|
+
color: var(--accent); font-weight: 700; font-size: 14px; display: flex; align-items: center; justify-content: center;
|
|
511
|
+
}
|
|
512
|
+
.tut-card.tut-done .tut-num { background: #d1fae5; color: #047857; }
|
|
513
|
+
.tut-body { flex: 1 1 auto; min-width: 0; }
|
|
514
|
+
.tut-title { font-size: 15.5px; font-weight: 700; color: var(--text); margin: 0 0 3px; }
|
|
515
|
+
.tut-desc { font-size: 13px; color: var(--muted); line-height: 1.7; margin: 0 0 12px; }
|
|
516
|
+
.tut-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 16px; }
|
|
517
|
+
.tut-btn {
|
|
518
|
+
display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 999px;
|
|
519
|
+
border: 1px solid var(--accent); background: var(--accent); color: #fff; font-size: 13px; font-weight: 600; white-space: nowrap;
|
|
520
|
+
}
|
|
521
|
+
.tut-btn:hover { background: #1d4ed8; text-decoration: none; }
|
|
522
|
+
.tut-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; user-select: none; }
|
|
523
|
+
.tut-check input { width: 15px; height: 15px; cursor: pointer; }
|
|
524
|
+
@media (max-width: 600px) {
|
|
525
|
+
.tut-actions { flex-direction: column; align-items: flex-start; }
|
|
526
|
+
}
|
|
429
527
|
/* ---- データソースと自動化 ---- */
|
|
430
528
|
.datasources { margin-top: 34px; }
|
|
431
529
|
.datasources-head { font-size: 18px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
|
|
@@ -607,6 +705,11 @@ button.ops-card-btn:disabled { opacity: .6; cursor: default; }
|
|
|
607
705
|
const CLIENT_JS = `
|
|
608
706
|
(function () {
|
|
609
707
|
var SITE = JSON.parse(document.getElementById("site-data").textContent);
|
|
708
|
+
// 「はじめに」タブのチュートリアルデータ(ビルド時に埋め込み。ボタンURL・チャレンジ4の決定は解決済み)
|
|
709
|
+
var TUTORIAL = (function () {
|
|
710
|
+
var elx = document.getElementById("tutorial-data");
|
|
711
|
+
try { return elx ? JSON.parse(elx.textContent) : null; } catch (e) { return null; }
|
|
712
|
+
})();
|
|
610
713
|
|
|
611
714
|
// ---------- インデックス構築 ----------
|
|
612
715
|
var byId = {};
|
|
@@ -632,8 +735,10 @@ const CLIENT_JS = `
|
|
|
632
735
|
// 本文は bodies/<i>.html に外出しされ詳細表示時に fetch されるため、linkify は loadBody 側で適用する
|
|
633
736
|
// (ここで inline bodyHtml に適用すると検索テキストにHTMLが混ざるため適用しない)。
|
|
634
737
|
|
|
635
|
-
// タブ: ホーム → Decisions → records/ を持つ各ディレクトリ(自動発見)
|
|
738
|
+
// タブ: はじめに → ホーム → Decisions → records/ を持つ各ディレクトリ(自動発見)
|
|
636
739
|
var TABS = [];
|
|
740
|
+
// 新メンバー向けオンボーディング。最初に目に入るようタブ列の先頭に置く
|
|
741
|
+
if (TUTORIAL && TUTORIAL.challenges && TUTORIAL.challenges.length) TABS.push({ key: "tutorial", label: "はじめに" });
|
|
637
742
|
if (SITE.home) TABS.push({ key: "home", label: "Home" });
|
|
638
743
|
TABS.push({ key: "decisions", label: "Decisions", items: SITE.decisions });
|
|
639
744
|
(SITE.sections || []).forEach(function (sec) {
|
|
@@ -650,6 +755,11 @@ const CLIENT_JS = `
|
|
|
650
755
|
TABS.push({ key: "ops", label: "操作" });
|
|
651
756
|
}
|
|
652
757
|
|
|
758
|
+
// 既定の着地タブ: 「はじめに」はタブ列の先頭に置くが、初回着地は Home にする
|
|
759
|
+
// (毎回開く顧客・リピーターに自然。新規は先頭タブで気づける)。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 ===
|
|
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:
|
|
815
|
+
var listState = { tab: DEFAULT_TAB, q: "", category: "", month: "", page: 1 };
|
|
706
816
|
|
|
707
817
|
// 一覧のページネーション(描画コスト対策。1ページあたりの件数)
|
|
708
818
|
var PAGE_SIZE = 30;
|
|
@@ -792,6 +902,7 @@ const CLIENT_JS = `
|
|
|
792
902
|
renderTabs();
|
|
793
903
|
renderInfoBar();
|
|
794
904
|
|
|
905
|
+
if (listState.tab === "tutorial") { renderTutorial(); return; }
|
|
795
906
|
if (listState.tab === "home") { renderHome(); return; }
|
|
796
907
|
if (listState.tab === "decisions") { renderDecisionList(); return; }
|
|
797
908
|
if (listState.tab === "graph") { renderKnowledge(); return; }
|
|
@@ -813,6 +924,55 @@ const CLIENT_JS = `
|
|
|
813
924
|
return SITE.home && SITE.home.fm ? adoptionInfo(SITE.home.fm.adoption) : null;
|
|
814
925
|
}
|
|
815
926
|
|
|
927
|
+
// ---------- はじめに(チュートリアル) ----------
|
|
928
|
+
// localStorageキーは案件別(repoSlug)。同一ブラウザで複数案件のビューアを見ても混ざらない
|
|
929
|
+
function tutStoreKey(n) { return "cortex-tutorial-done:" + (TUTORIAL.slug || "") + ":" + n; }
|
|
930
|
+
function tutIsDone(n) {
|
|
931
|
+
try { return localStorage.getItem(tutStoreKey(n)) === "1"; } catch (e) { return false; }
|
|
932
|
+
}
|
|
933
|
+
function tutSetDone(n, done) {
|
|
934
|
+
try { if (done) localStorage.setItem(tutStoreKey(n), "1"); else localStorage.removeItem(tutStoreKey(n)); } catch (e) {}
|
|
935
|
+
}
|
|
936
|
+
function renderTutorial() {
|
|
937
|
+
countEl.textContent = "";
|
|
938
|
+
var challenges = TUTORIAL.challenges || [];
|
|
939
|
+
app.appendChild(el("p", { class: "tut-intro", text: "AIS Viewer と Claude Code を使ってこのプロジェクトを掴むための5ステップです。各ボタンを押すと、質問が入力済みの状態で Claude Code Web が開きます(このリポジトリへのアクセス権が必要です)。" }));
|
|
940
|
+
|
|
941
|
+
// 進捗ヘッダ({完了数}/5・バー・全完了で祝福)
|
|
942
|
+
var doneCount = challenges.filter(function (c) { return tutIsDone(c.n); }).length;
|
|
943
|
+
var progText = el("span", { class: "tut-progress", text: doneCount + "/" + challenges.length + " 完了" });
|
|
944
|
+
var fill = el("div", { class: "tut-progress-fill" });
|
|
945
|
+
fill.style.width = (challenges.length ? Math.round((doneCount / challenges.length) * 100) : 0) + "%";
|
|
946
|
+
var bar = el("div", { class: "tut-progress-bar" }, [fill]);
|
|
947
|
+
var celebrate = el("span", { class: "tut-celebrate" });
|
|
948
|
+
if (doneCount === challenges.length && challenges.length) celebrate.textContent = "🎉 全ステップ達成!オンボーディング完了です。";
|
|
949
|
+
var head = el("div", { class: "tut-head" }, [progText, bar, celebrate]);
|
|
950
|
+
app.appendChild(head);
|
|
951
|
+
|
|
952
|
+
var list = el("div", { class: "tut-list" });
|
|
953
|
+
challenges.forEach(function (c) {
|
|
954
|
+
var done = tutIsDone(c.n);
|
|
955
|
+
var card = el("div", { class: "tut-card" + (done ? " tut-done" : "") });
|
|
956
|
+
var btn = el("a", { class: "tut-btn", href: c.url, target: "_blank", rel: "noopener", text: "🚀 挑戦する" });
|
|
957
|
+
var cb = el("input", { type: "checkbox" });
|
|
958
|
+
cb.checked = done;
|
|
959
|
+
var check = el("label", { class: "tut-check" }, [cb, el("span", { text: "完了" })]);
|
|
960
|
+
cb.addEventListener("change", function () {
|
|
961
|
+
tutSetDone(c.n, cb.checked);
|
|
962
|
+
// 進捗・祝福・カードの見た目を即時更新(再描画で状態を反映)
|
|
963
|
+
renderList();
|
|
964
|
+
});
|
|
965
|
+
card.appendChild(el("div", { class: "tut-num", text: String(c.n) }));
|
|
966
|
+
card.appendChild(el("div", { class: "tut-body" }, [
|
|
967
|
+
el("h3", { class: "tut-title", text: c.title }),
|
|
968
|
+
el("p", { class: "tut-desc", text: c.desc }),
|
|
969
|
+
el("div", { class: "tut-actions" }, [btn, check]),
|
|
970
|
+
]));
|
|
971
|
+
list.appendChild(card);
|
|
972
|
+
});
|
|
973
|
+
app.appendChild(list);
|
|
974
|
+
}
|
|
975
|
+
|
|
816
976
|
// ---------- ホーム(着地画面: Markdownをそのまま表示) ----------
|
|
817
977
|
function renderHome() {
|
|
818
978
|
var h = SITE.home;
|
package/package.json
CHANGED