create-dss-project 0.1.0
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/bin/create-dss-project.js +4 -0
- package/lib/index.js +80 -0
- package/lib/project-types.js +74 -0
- package/lib/prompts.js +42 -0
- package/lib/scaffold.js +169 -0
- package/package.json +30 -0
- package/template/.github/workflows/dashboard-build.yml +27 -0
- package/template/.github/workflows/template-lint.yml +71 -0
- package/template/CHANGELOG.md +43 -0
- package/template/CLAUDE.md +145 -0
- package/template/LICENSE +21 -0
- package/template/README.md +201 -0
- package/template/STATUS.md +34 -0
- package/template/context/competitor-snapshot.md +27 -0
- package/template/context/market-snapshot.md +32 -0
- package/template/context/pipeline-state.md +36 -0
- package/template/context/project-state.md +45 -0
- package/template/dashboard/CLAUDE.md +36 -0
- package/template/dashboard/DEPLOY.md +60 -0
- package/template/dashboard/build-data.js +395 -0
- package/template/dashboard/competitors.html +143 -0
- package/template/dashboard/css/styles.css +143 -0
- package/template/dashboard/data/.gitkeep +0 -0
- package/template/dashboard/decisions.html +132 -0
- package/template/dashboard/index.html +152 -0
- package/template/dashboard/js/app.js +59 -0
- package/template/dashboard/js/overview.js +50 -0
- package/template/dashboard/js/sidebar.js +62 -0
- package/template/dashboard/js/tailwind-config.js +52 -0
- package/template/dashboard/package-lock.json +351 -0
- package/template/dashboard/package.json +17 -0
- package/template/dashboard/pipeline.html +149 -0
- package/template/dashboard/research.html +215 -0
- package/template/dashboard/robots.txt +2 -0
- package/template/dashboard/scoring.html +187 -0
- package/template/dashboard/timeline.html +165 -0
- package/template/dashboard/vercel.json +5 -0
- package/template/dashboard/watch.js +57 -0
- package/template/data/.gitkeep +0 -0
- package/template/discovery/calls/.gitkeep +0 -0
- package/template/discovery/outreach/.gitkeep +0 -0
- package/template/discovery/prep/.gitkeep +0 -0
- package/template/docs/decks/.gitkeep +0 -0
- package/template/docs/executive-summary.md +104 -0
- package/template/docs/getting-started.md +274 -0
- package/template/docs/memos/evidence-grading.md +27 -0
- package/template/docs/memos/housekeeping-reference.md +101 -0
- package/template/docs/memos/reference-context.md +30 -0
- package/template/docs/output/project-activity.md +8 -0
- package/template/docs/output/status-blurb.md +4 -0
- package/template/docs/output/work-log.md +8 -0
- package/template/docs/skill-authoring-guide.md +212 -0
- package/template/memory/MEMORY.md +84 -0
- package/template/memory/decisions.md +13 -0
- package/template/memory/discovery.md +48 -0
- package/template/memory/research.md +33 -0
- package/template/memory/scoring.md +34 -0
- package/template/project.config.example.json +31 -0
- package/template/research/competitors/.gitkeep +0 -0
- package/template/research/market/.gitkeep +0 -0
- package/template/research/technical/.gitkeep +0 -0
- package/template/scripts/.gitkeep +0 -0
- package/template/scripts/build-cli-template.sh +32 -0
- package/template/scripts/health-check.sh +152 -0
- package/template/scripts/reset-to-template.sh +115 -0
- package/template/scripts/validate-placeholders.sh +47 -0
- package/template/skills/compare-options/SKILL.md +97 -0
- package/template/skills/critical-reasoning/SKILL.md +107 -0
- package/template/skills/decision/SKILL.md +75 -0
- package/template/skills/enrich-entity/SKILL.md +107 -0
- package/template/skills/health-check/SKILL.md +144 -0
- package/template/skills/onboard/SKILL.md +434 -0
- package/template/skills/outreach-sequence/SKILL.md +79 -0
- package/template/skills/pipeline-update/SKILL.md +90 -0
- package/template/skills/process-call/SKILL.md +96 -0
- package/template/skills/rebuild-snapshots/SKILL.md +88 -0
- package/template/skills/session-end/SKILL.md +120 -0
- package/template/skills/session-start/SKILL.md +93 -0
- package/template/skills/synthesise/SKILL.md +108 -0
- package/template/skills/weekly-report/SKILL.md +79 -0
- package/template/templates/call-notes.md +67 -0
- package/template/templates/call-prep.md +65 -0
- package/template/templates/entity-teardown.md +58 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="robots" content="noindex, nofollow">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="generator" content="DS Strategy Stack by DiffTheEnder (github.com/DiffTheEnder/DSS-Claude-Stack)">
|
|
8
|
+
<title>Research Hub — {{PROJECT_NAME}}</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Instrument+Serif&family=Syne:wght@600;700&display=swap" rel="stylesheet">
|
|
12
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
|
+
<script>
|
|
14
|
+
tailwind.config = {
|
|
15
|
+
darkMode: 'class',
|
|
16
|
+
theme: { extend: {
|
|
17
|
+
colors: {
|
|
18
|
+
cream: { 50:"#F7F4EF",100:"#f5f2ed",200:"#efe9e0",300:"#e8e0d4",400:"#E3DDD4",white:"#FEFCF9" },
|
|
19
|
+
warmgray: { 100:"#f0ebe3",300:"#d4c8b8",400:"#9B9183",500:"#7D7265",600:"#5C5044",700:"#4a3c2e",900:"#2C2418" },
|
|
20
|
+
warmdark: { 950:"#1A1612",900:"#242018",800:"#2C2720",700:"#3A3530",600:"#4a3f32" },
|
|
21
|
+
accent: { 50:"#F2F7F5",100:"#E3EEEA",200:"#C5D9D4",400:"#2DB892",500:"#0E9C82",600:"#0E7C6B",700:"#0B6358",800:"#084C44",900:"#053B35" },
|
|
22
|
+
},
|
|
23
|
+
fontFamily: { sans:['DM Sans','system-ui','sans-serif'], serif:['Instrument Serif','Georgia','serif'], syne:['Syne','sans-serif'] }
|
|
24
|
+
}}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
<link rel="stylesheet" href="css/styles.css">
|
|
28
|
+
</head>
|
|
29
|
+
<body class="bg-cream-50 dark:bg-warmdark-950 text-warmgray-900 dark:text-warmgray-100 min-h-screen flex">
|
|
30
|
+
|
|
31
|
+
<!-- Sidebar -->
|
|
32
|
+
<aside id="sidebar" class="sidebar flex flex-col shrink-0">
|
|
33
|
+
<a href="index.html" class="sidebar-brand" style="text-decoration:none;color:inherit">
|
|
34
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
35
|
+
<rect x="2" y="2" width="28" height="28" rx="8" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
|
36
|
+
<circle cx="16" cy="16" r="3" stroke="#2DB892" stroke-width="1.5"/>
|
|
37
|
+
<line x1="16" y1="6" x2="16" y2="12" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
38
|
+
<line x1="16" y1="20" x2="16" y2="26" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
39
|
+
<line x1="6" y1="16" x2="12" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
40
|
+
<line x1="20" y1="16" x2="26" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
41
|
+
</svg>
|
|
42
|
+
<div>
|
|
43
|
+
<div class="sidebar-title">{{PROJECT_NAME}}</div>
|
|
44
|
+
<div class="sidebar-subtitle">Strategy Dashboard</div>
|
|
45
|
+
</div>
|
|
46
|
+
</a>
|
|
47
|
+
<nav class="sidebar-nav">
|
|
48
|
+
<div class="sidebar-label">Core</div>
|
|
49
|
+
<a href="index.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 5v3l2 2"/></svg>Overview</a>
|
|
50
|
+
<a href="pipeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 4h12M2 8h8M2 12h5"/></svg>Pipeline</a>
|
|
51
|
+
<a href="competitors.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>Competitors</a>
|
|
52
|
+
<div class="sidebar-label" style="margin-top: 0.75rem">Strategy</div>
|
|
53
|
+
<a href="decisions.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 2v12M2 8h12"/><circle cx="8" cy="8" r="6"/></svg>Decisions</a>
|
|
54
|
+
<a href="scoring.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 13l3-4 3 2 4-6"/></svg>Scoring</a>
|
|
55
|
+
<a href="timeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3h12M2 8h12M2 13h12"/><circle cx="5" cy="3" r="1" fill="currentColor"/><circle cx="10" cy="8" r="1" fill="currentColor"/><circle cx="7" cy="13" r="1" fill="currentColor"/></svg>Timeline</a>
|
|
56
|
+
<a href="research.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="7" cy="7" r="5"/><path d="M11 11l3 3"/></svg>Research Hub</a>
|
|
57
|
+
</nav>
|
|
58
|
+
<div style="padding: 0.75rem; border-top: 1px solid rgba(255,248,240,0.06);">
|
|
59
|
+
<button id="dark-toggle" class="nav-link" style="width:100%; border:none; background:none; cursor:pointer">
|
|
60
|
+
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 1a7 7 0 100 14 5 5 0 010-10"/></svg>Toggle Theme
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
<div style="padding: 0.5rem 0.75rem; border-top: 1px solid rgba(255,248,240,0.06); text-align: center;">
|
|
64
|
+
<a href="https://github.com/DiffTheEnder/DSS-Claude-Stack" target="_blank" rel="noopener" style="font-size: 0.65rem; color: rgba(155,145,131,0.5); text-decoration: none;">Powered by DS Strategy Stack</a>
|
|
65
|
+
</div>
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
<!-- Main Content -->
|
|
69
|
+
<main class="flex-1 ml-[240px] p-8">
|
|
70
|
+
<header class="mb-8">
|
|
71
|
+
<h1 class="font-serif text-3xl mb-2">Research Hub</h1>
|
|
72
|
+
<p class="text-warmgray-400 text-sm">Index of all research files</p>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<!-- Summary cards -->
|
|
76
|
+
<div id="research-summary" class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
|
|
77
|
+
<!-- Populated by JS -->
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Research files table -->
|
|
81
|
+
<section class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 overflow-x-auto mb-6">
|
|
82
|
+
<table class="w-full text-sm">
|
|
83
|
+
<thead>
|
|
84
|
+
<tr class="border-b border-cream-300 dark:border-warmdark-700">
|
|
85
|
+
<th class="text-left p-3 font-semibold">File</th>
|
|
86
|
+
<th class="text-left p-3 font-semibold">Entity</th>
|
|
87
|
+
<th class="text-left p-3 font-semibold">Type</th>
|
|
88
|
+
<th class="text-left p-3 font-semibold">Last Modified</th>
|
|
89
|
+
<th class="text-left p-3 font-semibold">Evidence Grades</th>
|
|
90
|
+
</tr>
|
|
91
|
+
</thead>
|
|
92
|
+
<tbody id="research-body">
|
|
93
|
+
<!-- Populated by JS -->
|
|
94
|
+
</tbody>
|
|
95
|
+
</table>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<!-- Research gaps -->
|
|
99
|
+
<section id="research-gaps" class="hidden">
|
|
100
|
+
<h2 class="font-syne text-sm font-bold uppercase tracking-wider text-warmgray-400 mb-4">Research Gaps</h2>
|
|
101
|
+
<div id="gaps-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
102
|
+
<!-- Populated by JS -->
|
|
103
|
+
</div>
|
|
104
|
+
</section>
|
|
105
|
+
</main>
|
|
106
|
+
|
|
107
|
+
<script src="js/app.js"></script>
|
|
108
|
+
<script>
|
|
109
|
+
// Research Hub page logic — loads research.json
|
|
110
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
111
|
+
initDarkMode();
|
|
112
|
+
initSidebar();
|
|
113
|
+
initNav();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const data = await loadJSON('research.json');
|
|
117
|
+
const summaryEl = document.getElementById('research-summary');
|
|
118
|
+
const tbody = document.getElementById('research-body');
|
|
119
|
+
const gapsSection = document.getElementById('research-gaps');
|
|
120
|
+
const gapsGrid = document.getElementById('gaps-grid');
|
|
121
|
+
|
|
122
|
+
const files = data.files || data || [];
|
|
123
|
+
const gaps = data.gaps || [];
|
|
124
|
+
|
|
125
|
+
if ((!Array.isArray(files) || files.length === 0) && gaps.length === 0) {
|
|
126
|
+
summaryEl.innerHTML = '';
|
|
127
|
+
tbody.innerHTML = '<tr><td colspan="5" class="p-6 text-center text-warmgray-400">No research files yet. Use <code>/enrich-entity</code> to start building your research base.</td></tr>';
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Count evidence grades
|
|
132
|
+
const gradeCounts = { CONFIRMED: 0, SECONDARY: 0, INFERENCE: 0, ASSUMPTION: 0 };
|
|
133
|
+
files.forEach(f => {
|
|
134
|
+
const grades = f.evidenceGrades || f.evidence_grades || {};
|
|
135
|
+
Object.keys(grades).forEach(g => {
|
|
136
|
+
const key = g.toUpperCase();
|
|
137
|
+
if (gradeCounts.hasOwnProperty(key)) gradeCounts[key] += grades[g];
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Summary cards
|
|
142
|
+
const gradeColours = {
|
|
143
|
+
CONFIRMED: 'text-green-600 dark:text-green-400',
|
|
144
|
+
SECONDARY: 'text-blue-600 dark:text-blue-400',
|
|
145
|
+
INFERENCE: 'text-yellow-600 dark:text-yellow-400',
|
|
146
|
+
ASSUMPTION: 'text-red-500 dark:text-red-400'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
summaryEl.innerHTML = `
|
|
150
|
+
<div class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 p-4 text-center">
|
|
151
|
+
<div class="text-2xl font-bold">${files.length}</div>
|
|
152
|
+
<div class="text-xs text-warmgray-400 uppercase tracking-wider mt-1">Total Files</div>
|
|
153
|
+
</div>
|
|
154
|
+
${Object.entries(gradeCounts).map(([grade, count]) => `
|
|
155
|
+
<div class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 p-4 text-center">
|
|
156
|
+
<div class="text-2xl font-bold ${gradeColours[grade]}">${count}</div>
|
|
157
|
+
<div class="text-xs text-warmgray-400 uppercase tracking-wider mt-1">${grade}</div>
|
|
158
|
+
</div>
|
|
159
|
+
`).join('')}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
// Type badge colours
|
|
163
|
+
function typeBadge(type) {
|
|
164
|
+
const t = (type || '').toLowerCase();
|
|
165
|
+
if (t.includes('competitor')) return 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300';
|
|
166
|
+
if (t.includes('market')) return 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300';
|
|
167
|
+
if (t.includes('technical')) return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300';
|
|
168
|
+
return 'bg-warmgray-100 text-warmgray-500 dark:bg-warmdark-700 dark:text-warmgray-300';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Render files table
|
|
172
|
+
if (files.length === 0) {
|
|
173
|
+
tbody.innerHTML = '<tr><td colspan="5" class="p-6 text-center text-warmgray-400">No research files yet.</td></tr>';
|
|
174
|
+
} else {
|
|
175
|
+
files.forEach(f => {
|
|
176
|
+
const row = document.createElement('tr');
|
|
177
|
+
row.className = 'border-b border-cream-200 dark:border-warmdark-700 hover:bg-cream-100 dark:hover:bg-warmdark-700 transition-colors';
|
|
178
|
+
|
|
179
|
+
const grades = f.evidenceGrades || f.evidence_grades || {};
|
|
180
|
+
const gradeHtml = Object.entries(grades).map(([g, c]) =>
|
|
181
|
+
`<span class="inline-block px-1.5 py-0.5 rounded text-xs font-medium ${gradeColours[g.toUpperCase()] || ''} bg-cream-100 dark:bg-warmdark-700">${g}: ${c}</span>`
|
|
182
|
+
).join(' ');
|
|
183
|
+
|
|
184
|
+
row.innerHTML = `
|
|
185
|
+
<td class="p-3 font-medium font-mono text-xs">${f.file || f.filename || '—'}</td>
|
|
186
|
+
<td class="p-3">${f.entity || '—'}</td>
|
|
187
|
+
<td class="p-3"><span class="inline-block px-2 py-0.5 rounded text-xs font-medium ${typeBadge(f.type)}">${f.type || '—'}</span></td>
|
|
188
|
+
<td class="p-3 text-warmgray-500">${f.lastModified || f.last_modified || '—'}</td>
|
|
189
|
+
<td class="p-3">${gradeHtml || '—'}</td>
|
|
190
|
+
`;
|
|
191
|
+
tbody.appendChild(row);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Render research gaps
|
|
196
|
+
if (gaps.length > 0) {
|
|
197
|
+
gapsSection.classList.remove('hidden');
|
|
198
|
+
gaps.forEach(gap => {
|
|
199
|
+
const card = document.createElement('div');
|
|
200
|
+
card.className = 'bg-white dark:bg-warmdark-800 rounded-lg border border-dashed border-warmgray-300 dark:border-warmdark-600 p-4';
|
|
201
|
+
card.innerHTML = `
|
|
202
|
+
<p class="font-medium text-sm">${gap.entity || gap}</p>
|
|
203
|
+
<p class="text-xs text-warmgray-400 mt-1">${gap.reason || 'Missing research file'}</p>
|
|
204
|
+
`;
|
|
205
|
+
gapsGrid.appendChild(card);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} catch (e) {
|
|
210
|
+
console.warn('Could not load research data:', e.message);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
</script>
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="robots" content="noindex, nofollow">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="generator" content="DS Strategy Stack by DiffTheEnder (github.com/DiffTheEnder/DSS-Claude-Stack)">
|
|
8
|
+
<title>Scoring — {{PROJECT_NAME}}</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Instrument+Serif&family=Syne:wght@600;700&display=swap" rel="stylesheet">
|
|
12
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
|
+
<script>
|
|
14
|
+
tailwind.config = {
|
|
15
|
+
darkMode: 'class',
|
|
16
|
+
theme: { extend: {
|
|
17
|
+
colors: {
|
|
18
|
+
cream: { 50:"#F7F4EF",100:"#f5f2ed",200:"#efe9e0",300:"#e8e0d4",400:"#E3DDD4",white:"#FEFCF9" },
|
|
19
|
+
warmgray: { 100:"#f0ebe3",300:"#d4c8b8",400:"#9B9183",500:"#7D7265",600:"#5C5044",700:"#4a3c2e",900:"#2C2418" },
|
|
20
|
+
warmdark: { 950:"#1A1612",900:"#242018",800:"#2C2720",700:"#3A3530",600:"#4a3f32" },
|
|
21
|
+
accent: { 50:"#F2F7F5",100:"#E3EEEA",200:"#C5D9D4",400:"#2DB892",500:"#0E9C82",600:"#0E7C6B",700:"#0B6358",800:"#084C44",900:"#053B35" },
|
|
22
|
+
},
|
|
23
|
+
fontFamily: { sans:['DM Sans','system-ui','sans-serif'], serif:['Instrument Serif','Georgia','serif'], syne:['Syne','sans-serif'] }
|
|
24
|
+
}}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
<link rel="stylesheet" href="css/styles.css">
|
|
28
|
+
</head>
|
|
29
|
+
<body class="bg-cream-50 dark:bg-warmdark-950 text-warmgray-900 dark:text-warmgray-100 min-h-screen flex">
|
|
30
|
+
|
|
31
|
+
<!-- Sidebar -->
|
|
32
|
+
<aside id="sidebar" class="sidebar flex flex-col shrink-0">
|
|
33
|
+
<a href="index.html" class="sidebar-brand" style="text-decoration:none;color:inherit">
|
|
34
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
35
|
+
<rect x="2" y="2" width="28" height="28" rx="8" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
|
36
|
+
<circle cx="16" cy="16" r="3" stroke="#2DB892" stroke-width="1.5"/>
|
|
37
|
+
<line x1="16" y1="6" x2="16" y2="12" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
38
|
+
<line x1="16" y1="20" x2="16" y2="26" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
39
|
+
<line x1="6" y1="16" x2="12" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
40
|
+
<line x1="20" y1="16" x2="26" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
41
|
+
</svg>
|
|
42
|
+
<div>
|
|
43
|
+
<div class="sidebar-title">{{PROJECT_NAME}}</div>
|
|
44
|
+
<div class="sidebar-subtitle">Strategy Dashboard</div>
|
|
45
|
+
</div>
|
|
46
|
+
</a>
|
|
47
|
+
<nav class="sidebar-nav">
|
|
48
|
+
<div class="sidebar-label">Core</div>
|
|
49
|
+
<a href="index.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 5v3l2 2"/></svg>Overview</a>
|
|
50
|
+
<a href="pipeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 4h12M2 8h8M2 12h5"/></svg>Pipeline</a>
|
|
51
|
+
<a href="competitors.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>Competitors</a>
|
|
52
|
+
<div class="sidebar-label" style="margin-top: 0.75rem">Strategy</div>
|
|
53
|
+
<a href="decisions.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 2v12M2 8h12"/><circle cx="8" cy="8" r="6"/></svg>Decisions</a>
|
|
54
|
+
<a href="scoring.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 13l3-4 3 2 4-6"/></svg>Scoring</a>
|
|
55
|
+
<a href="timeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3h12M2 8h12M2 13h12"/><circle cx="5" cy="3" r="1" fill="currentColor"/><circle cx="10" cy="8" r="1" fill="currentColor"/><circle cx="7" cy="13" r="1" fill="currentColor"/></svg>Timeline</a>
|
|
56
|
+
<a href="research.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="7" cy="7" r="5"/><path d="M11 11l3 3"/></svg>Research Hub</a>
|
|
57
|
+
</nav>
|
|
58
|
+
<div style="padding: 0.75rem; border-top: 1px solid rgba(255,248,240,0.06);">
|
|
59
|
+
<button id="dark-toggle" class="nav-link" style="width:100%; border:none; background:none; cursor:pointer">
|
|
60
|
+
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 1a7 7 0 100 14 5 5 0 010-10"/></svg>Toggle Theme
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
<div style="padding: 0.5rem 0.75rem; border-top: 1px solid rgba(255,248,240,0.06); text-align: center;">
|
|
64
|
+
<a href="https://github.com/DiffTheEnder/DSS-Claude-Stack" target="_blank" rel="noopener" style="font-size: 0.65rem; color: rgba(155,145,131,0.5); text-decoration: none;">Powered by DS Strategy Stack</a>
|
|
65
|
+
</div>
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
<!-- Main Content -->
|
|
69
|
+
<main class="flex-1 ml-[240px] p-8">
|
|
70
|
+
<header class="mb-8">
|
|
71
|
+
<h1 class="font-serif text-3xl mb-2">Option Scoring</h1>
|
|
72
|
+
<p class="text-warmgray-400 text-sm">Data from <code>memory/scoring.md</code></p>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<!-- Recommended strategy card -->
|
|
76
|
+
<div id="recommendation-card" class="hidden mb-6">
|
|
77
|
+
<!-- Populated by JS -->
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Scoring comparison table -->
|
|
81
|
+
<section id="scoring-table-wrap" class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 overflow-x-auto">
|
|
82
|
+
<table class="w-full text-sm">
|
|
83
|
+
<thead id="scoring-head">
|
|
84
|
+
<!-- Populated by JS -->
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody id="scoring-body">
|
|
87
|
+
<!-- Populated by JS -->
|
|
88
|
+
</tbody>
|
|
89
|
+
</table>
|
|
90
|
+
</section>
|
|
91
|
+
</main>
|
|
92
|
+
|
|
93
|
+
<script src="js/app.js"></script>
|
|
94
|
+
<script>
|
|
95
|
+
// Scoring page logic — loads scoring.json
|
|
96
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
97
|
+
initDarkMode();
|
|
98
|
+
initSidebar();
|
|
99
|
+
initNav();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const data = await loadJSON('scoring.json');
|
|
103
|
+
const thead = document.getElementById('scoring-head');
|
|
104
|
+
const tbody = document.getElementById('scoring-body');
|
|
105
|
+
const recCard = document.getElementById('recommendation-card');
|
|
106
|
+
|
|
107
|
+
if (!data || (!data.options && !data.dimensions)) {
|
|
108
|
+
tbody.innerHTML = '<tr><td colspan="5" class="p-6 text-center text-warmgray-400">No options scored yet. Use <code>/compare-options</code> to evaluate strategic directions.</td></tr>';
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const options = data.options || [];
|
|
113
|
+
const dimensions = data.dimensions || [];
|
|
114
|
+
const recommendation = data.recommendation || '';
|
|
115
|
+
|
|
116
|
+
// Show recommendation card if present
|
|
117
|
+
if (recommendation) {
|
|
118
|
+
recCard.classList.remove('hidden');
|
|
119
|
+
recCard.innerHTML = `
|
|
120
|
+
<div class="bg-white dark:bg-warmdark-800 rounded-xl border-2 border-accent-400 p-5">
|
|
121
|
+
<h2 class="font-syne text-sm font-bold uppercase tracking-wider text-accent-500 mb-2">Recommended Strategy</h2>
|
|
122
|
+
<p class="text-base leading-relaxed">${recommendation}</p>
|
|
123
|
+
</div>
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (options.length === 0 || dimensions.length === 0) {
|
|
128
|
+
tbody.innerHTML = '<tr><td colspan="5" class="p-6 text-center text-warmgray-400">No options scored yet. Use <code>/compare-options</code> to evaluate strategic directions.</td></tr>';
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build header row: Dimension | Option1 | Option2 | ...
|
|
133
|
+
thead.innerHTML = '<tr class="border-b border-cream-300 dark:border-warmdark-700">' +
|
|
134
|
+
'<th class="text-left p-3 font-semibold">Dimension</th>' +
|
|
135
|
+
options.map(o => `<th class="text-left p-3 font-semibold">${o.name || o}</th>`).join('') +
|
|
136
|
+
'</tr>';
|
|
137
|
+
|
|
138
|
+
// Score cell colour
|
|
139
|
+
function scoreCellClass(score) {
|
|
140
|
+
const s = parseInt(score);
|
|
141
|
+
if (s === 5) return 'bg-accent-400 text-white';
|
|
142
|
+
if (s === 4) return 'bg-accent-200 text-accent-900 dark:text-accent-800';
|
|
143
|
+
if (s === 3) return 'bg-warmgray-300 text-warmgray-700';
|
|
144
|
+
if (s === 2) return 'bg-warmgray-400 text-white';
|
|
145
|
+
if (s === 1) return 'bg-red-500 text-white';
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Build dimension rows
|
|
150
|
+
dimensions.forEach(dim => {
|
|
151
|
+
const row = document.createElement('tr');
|
|
152
|
+
row.className = 'border-b border-cream-200 dark:border-warmdark-700';
|
|
153
|
+
const dimName = dim.name || dim;
|
|
154
|
+
let cells = `<td class="p-3 font-medium">${dimName}</td>`;
|
|
155
|
+
options.forEach(opt => {
|
|
156
|
+
const optName = opt.name || opt;
|
|
157
|
+
const score = dim.scores ? dim.scores[optName] : (data.scores ? data.scores[dimName + ':' + optName] : '');
|
|
158
|
+
const cls = scoreCellClass(score);
|
|
159
|
+
cells += `<td class="p-3 text-center"><span class="inline-block w-8 h-8 leading-8 rounded-md text-sm font-semibold ${cls}">${score || '—'}</span></td>`;
|
|
160
|
+
});
|
|
161
|
+
row.innerHTML = cells;
|
|
162
|
+
tbody.appendChild(row);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Build total row
|
|
166
|
+
const totalRow = document.createElement('tr');
|
|
167
|
+
totalRow.className = 'border-t-2 border-cream-400 dark:border-warmdark-600 font-bold';
|
|
168
|
+
let totalCells = '<td class="p-3 font-bold">Total</td>';
|
|
169
|
+
options.forEach(opt => {
|
|
170
|
+
const optName = opt.name || opt;
|
|
171
|
+
let total = 0;
|
|
172
|
+
dimensions.forEach(dim => {
|
|
173
|
+
const score = dim.scores ? dim.scores[optName] : (data.scores ? data.scores[(dim.name || dim) + ':' + optName] : 0);
|
|
174
|
+
total += parseInt(score) || 0;
|
|
175
|
+
});
|
|
176
|
+
totalCells += `<td class="p-3 text-center text-base">${total}</td>`;
|
|
177
|
+
});
|
|
178
|
+
totalRow.innerHTML = totalCells;
|
|
179
|
+
tbody.appendChild(totalRow);
|
|
180
|
+
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.warn('Could not load scoring data:', e.message);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
</script>
|
|
186
|
+
</body>
|
|
187
|
+
</html>
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="robots" content="noindex, nofollow">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="generator" content="DS Strategy Stack by DiffTheEnder (github.com/DiffTheEnder/DSS-Claude-Stack)">
|
|
8
|
+
<title>Timeline — {{PROJECT_NAME}}</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Instrument+Serif&family=Syne:wght@600;700&display=swap" rel="stylesheet">
|
|
12
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
|
+
<script>
|
|
14
|
+
tailwind.config = {
|
|
15
|
+
darkMode: 'class',
|
|
16
|
+
theme: { extend: {
|
|
17
|
+
colors: {
|
|
18
|
+
cream: { 50:"#F7F4EF",100:"#f5f2ed",200:"#efe9e0",300:"#e8e0d4",400:"#E3DDD4",white:"#FEFCF9" },
|
|
19
|
+
warmgray: { 100:"#f0ebe3",300:"#d4c8b8",400:"#9B9183",500:"#7D7265",600:"#5C5044",700:"#4a3c2e",900:"#2C2418" },
|
|
20
|
+
warmdark: { 950:"#1A1612",900:"#242018",800:"#2C2720",700:"#3A3530",600:"#4a3f32" },
|
|
21
|
+
accent: { 50:"#F2F7F5",100:"#E3EEEA",200:"#C5D9D4",400:"#2DB892",500:"#0E9C82",600:"#0E7C6B",700:"#0B6358",800:"#084C44",900:"#053B35" },
|
|
22
|
+
},
|
|
23
|
+
fontFamily: { sans:['DM Sans','system-ui','sans-serif'], serif:['Instrument Serif','Georgia','serif'], syne:['Syne','sans-serif'] }
|
|
24
|
+
}}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
<link rel="stylesheet" href="css/styles.css">
|
|
28
|
+
</head>
|
|
29
|
+
<body class="bg-cream-50 dark:bg-warmdark-950 text-warmgray-900 dark:text-warmgray-100 min-h-screen flex">
|
|
30
|
+
|
|
31
|
+
<!-- Sidebar -->
|
|
32
|
+
<aside id="sidebar" class="sidebar flex flex-col shrink-0">
|
|
33
|
+
<a href="index.html" class="sidebar-brand" style="text-decoration:none;color:inherit">
|
|
34
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
35
|
+
<rect x="2" y="2" width="28" height="28" rx="8" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
|
36
|
+
<circle cx="16" cy="16" r="3" stroke="#2DB892" stroke-width="1.5"/>
|
|
37
|
+
<line x1="16" y1="6" x2="16" y2="12" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
38
|
+
<line x1="16" y1="20" x2="16" y2="26" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
39
|
+
<line x1="6" y1="16" x2="12" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
40
|
+
<line x1="20" y1="16" x2="26" y2="16" stroke="#2DB892" stroke-width="1.5" stroke-linecap="round"/>
|
|
41
|
+
</svg>
|
|
42
|
+
<div>
|
|
43
|
+
<div class="sidebar-title">{{PROJECT_NAME}}</div>
|
|
44
|
+
<div class="sidebar-subtitle">Strategy Dashboard</div>
|
|
45
|
+
</div>
|
|
46
|
+
</a>
|
|
47
|
+
<nav class="sidebar-nav">
|
|
48
|
+
<div class="sidebar-label">Core</div>
|
|
49
|
+
<a href="index.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 5v3l2 2"/></svg>Overview</a>
|
|
50
|
+
<a href="pipeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 4h12M2 8h8M2 12h5"/></svg>Pipeline</a>
|
|
51
|
+
<a href="competitors.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>Competitors</a>
|
|
52
|
+
<div class="sidebar-label" style="margin-top: 0.75rem">Strategy</div>
|
|
53
|
+
<a href="decisions.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 2v12M2 8h12"/><circle cx="8" cy="8" r="6"/></svg>Decisions</a>
|
|
54
|
+
<a href="scoring.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 13l3-4 3 2 4-6"/></svg>Scoring</a>
|
|
55
|
+
<a href="timeline.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3h12M2 8h12M2 13h12"/><circle cx="5" cy="3" r="1" fill="currentColor"/><circle cx="10" cy="8" r="1" fill="currentColor"/><circle cx="7" cy="13" r="1" fill="currentColor"/></svg>Timeline</a>
|
|
56
|
+
<a href="research.html" class="nav-link"><svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="7" cy="7" r="5"/><path d="M11 11l3 3"/></svg>Research Hub</a>
|
|
57
|
+
</nav>
|
|
58
|
+
<div style="padding: 0.75rem; border-top: 1px solid rgba(255,248,240,0.06);">
|
|
59
|
+
<button id="dark-toggle" class="nav-link" style="width:100%; border:none; background:none; cursor:pointer">
|
|
60
|
+
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 1a7 7 0 100 14 5 5 0 010-10"/></svg>Toggle Theme
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
<div style="padding: 0.5rem 0.75rem; border-top: 1px solid rgba(255,248,240,0.06); text-align: center;">
|
|
64
|
+
<a href="https://github.com/DiffTheEnder/DSS-Claude-Stack" target="_blank" rel="noopener" style="font-size: 0.65rem; color: rgba(155,145,131,0.5); text-decoration: none;">Powered by DS Strategy Stack</a>
|
|
65
|
+
</div>
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
<!-- Main Content -->
|
|
69
|
+
<main class="flex-1 ml-[240px] p-8">
|
|
70
|
+
<header class="mb-8">
|
|
71
|
+
<h1 class="font-serif text-3xl mb-2">Project Timeline</h1>
|
|
72
|
+
<p class="text-warmgray-400 text-sm">Data from <code>docs/output/work-log.md</code></p>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<div id="timeline-container">
|
|
76
|
+
<!-- Populated by JS -->
|
|
77
|
+
</div>
|
|
78
|
+
</main>
|
|
79
|
+
|
|
80
|
+
<script src="js/app.js"></script>
|
|
81
|
+
<script>
|
|
82
|
+
// Timeline page logic — loads timeline.json
|
|
83
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
84
|
+
initDarkMode();
|
|
85
|
+
initSidebar();
|
|
86
|
+
initNav();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const entries = await loadJSON('timeline.json');
|
|
90
|
+
const container = document.getElementById('timeline-container');
|
|
91
|
+
|
|
92
|
+
if (!entries || entries.length === 0) {
|
|
93
|
+
container.innerHTML = '<div class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 p-6 text-center text-warmgray-400">No activity logged yet. Work will appear here after sessions.</div>';
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sort by date descending (most recent first)
|
|
98
|
+
entries.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
99
|
+
|
|
100
|
+
// Group by week
|
|
101
|
+
function getWeekLabel(dateStr) {
|
|
102
|
+
const d = new Date(dateStr);
|
|
103
|
+
const start = new Date(d);
|
|
104
|
+
start.setDate(d.getDate() - d.getDay() + 1); // Monday
|
|
105
|
+
const end = new Date(start);
|
|
106
|
+
end.setDate(start.getDate() + 6);
|
|
107
|
+
const fmt = (dt) => dt.toLocaleDateString('en-AU', { day: 'numeric', month: 'short' });
|
|
108
|
+
return 'Week of ' + fmt(start) + ' – ' + fmt(end);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Workstream badge colours (cycling through a palette)
|
|
112
|
+
const wsColours = [
|
|
113
|
+
'bg-accent-100 text-accent-700 dark:bg-accent-900 dark:text-accent-200',
|
|
114
|
+
'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
|
|
115
|
+
'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300',
|
|
116
|
+
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
|
|
117
|
+
'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300',
|
|
118
|
+
];
|
|
119
|
+
const wsMap = {};
|
|
120
|
+
let wsIdx = 0;
|
|
121
|
+
function getWsColour(ws) {
|
|
122
|
+
if (!ws) return wsColours[0];
|
|
123
|
+
if (!wsMap[ws]) { wsMap[ws] = wsColours[wsIdx % wsColours.length]; wsIdx++; }
|
|
124
|
+
return wsMap[ws];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let currentWeek = '';
|
|
128
|
+
let html = '';
|
|
129
|
+
|
|
130
|
+
entries.forEach(entry => {
|
|
131
|
+
const week = getWeekLabel(entry.date);
|
|
132
|
+
if (week !== currentWeek) {
|
|
133
|
+
currentWeek = week;
|
|
134
|
+
html += `<h3 class="font-syne text-sm font-bold uppercase tracking-wider text-warmgray-400 mt-6 mb-3 first:mt-0">${week}</h3>`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const outputs = entry.outputs || entry.output_files || [];
|
|
138
|
+
const outputHtml = outputs.length > 0
|
|
139
|
+
? '<div class="mt-2 flex flex-wrap gap-1">' + outputs.map(f => `<span class="text-xs bg-cream-200 dark:bg-warmdark-700 text-warmgray-500 dark:text-warmgray-300 px-1.5 py-0.5 rounded font-mono">${f}</span>`).join('') + '</div>'
|
|
140
|
+
: '';
|
|
141
|
+
|
|
142
|
+
html += `
|
|
143
|
+
<div class="relative pl-6 pb-6 border-l-2 border-cream-300 dark:border-warmdark-600 last:pb-0">
|
|
144
|
+
<div class="absolute left-[-5px] top-1 w-2 h-2 rounded-full bg-accent-400"></div>
|
|
145
|
+
<div class="bg-white dark:bg-warmdark-800 rounded-xl border border-cream-300 dark:border-warmdark-700 p-4">
|
|
146
|
+
<div class="flex items-center gap-2 mb-1.5">
|
|
147
|
+
<span class="text-xs text-warmgray-400">${entry.date || ''}</span>
|
|
148
|
+
${entry.workstream ? '<span class="inline-block px-2 py-0.5 rounded text-xs font-medium ' + getWsColour(entry.workstream) + '">' + entry.workstream + '</span>' : ''}
|
|
149
|
+
</div>
|
|
150
|
+
<p class="text-sm leading-relaxed">${entry.description || entry.text || ''}</p>
|
|
151
|
+
${outputHtml}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
container.innerHTML = html;
|
|
158
|
+
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.warn('Could not load timeline data:', e.message);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* File watcher — rebuilds dashboard JSON when source files change.
|
|
4
|
+
* Watches: docs/, research/, discovery/, data/, memory/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
12
|
+
const WATCH_DIRS = [
|
|
13
|
+
path.join(PROJECT_ROOT, 'docs'),
|
|
14
|
+
path.join(PROJECT_ROOT, 'research'),
|
|
15
|
+
path.join(PROJECT_ROOT, 'discovery'),
|
|
16
|
+
path.join(PROJECT_ROOT, 'data'),
|
|
17
|
+
path.join(PROJECT_ROOT, 'memory'),
|
|
18
|
+
];
|
|
19
|
+
const WATCH_FILES = [
|
|
20
|
+
path.join(PROJECT_ROOT, 'CLAUDE.md'),
|
|
21
|
+
path.join(PROJECT_ROOT, 'STATUS.md'),
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
let debounce = null;
|
|
25
|
+
|
|
26
|
+
function rebuild() {
|
|
27
|
+
if (debounce) return;
|
|
28
|
+
debounce = setTimeout(() => {
|
|
29
|
+
debounce = null;
|
|
30
|
+
console.log(`\n[${new Date().toLocaleTimeString()}] Change detected — rebuilding...`);
|
|
31
|
+
try {
|
|
32
|
+
execSync('node build-data.js', { cwd: __dirname, stdio: 'inherit' });
|
|
33
|
+
console.log('Rebuild complete. Refresh browser to see changes.\n');
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.error('Rebuild failed:', e.message);
|
|
36
|
+
}
|
|
37
|
+
}, 500);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log('Watching for changes in project files...');
|
|
41
|
+
console.log('Press Ctrl+C to stop.\n');
|
|
42
|
+
|
|
43
|
+
for (const dir of WATCH_DIRS) {
|
|
44
|
+
if (fs.existsSync(dir)) {
|
|
45
|
+
fs.watch(dir, { recursive: true }, (event, filename) => {
|
|
46
|
+
if (filename && (filename.endsWith('.md') || filename.endsWith('.csv'))) {
|
|
47
|
+
rebuild();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const file of WATCH_FILES) {
|
|
54
|
+
if (fs.existsSync(file)) {
|
|
55
|
+
fs.watchFile(file, { interval: 1000 }, rebuild);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
File without changes
|