@tenonhq/dovetail-dashboard 0.0.13 → 0.0.15
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/package.json +3 -2
- package/public/app.js +47 -1
- package/public/claude-plans.css +643 -0
- package/public/claude-plans.html +61 -0
- package/public/claude-plans.js +445 -0
- package/public/index.html +1 -0
- package/public/styles.css +26 -0
- package/server.js +259 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/* /claude-plans page logic.
|
|
2
|
+
* - Fetches initial state from REST.
|
|
3
|
+
* - Subscribes to /api/claude-plans/stream (SSE) for live updates.
|
|
4
|
+
* - Renders markdown via marked + DOMPurify, Mermaid via mermaid.run().
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
if (window.mermaid && typeof window.mermaid.initialize === "function") {
|
|
11
|
+
window.mermaid.initialize({ startOnLoad: false, theme: "default", securityLevel: "strict" });
|
|
12
|
+
}
|
|
13
|
+
if (window.marked && typeof window.marked.setOptions === "function") {
|
|
14
|
+
window.marked.setOptions({ breaks: true, gfm: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var state = {
|
|
18
|
+
plans: new Map(), // slug -> plan
|
|
19
|
+
artifacts: new Map(), // slug -> Map<artifactSlug, artifact>
|
|
20
|
+
selectedSlug: null,
|
|
21
|
+
activeTab: "plan"
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
var els = {
|
|
25
|
+
storage: document.getElementById("cp-storage"),
|
|
26
|
+
list: document.getElementById("cp-list"),
|
|
27
|
+
count: document.getElementById("cp-count"),
|
|
28
|
+
railEmpty: document.getElementById("cp-rail-empty"),
|
|
29
|
+
detailEmpty: document.getElementById("cp-detail-empty"),
|
|
30
|
+
detailBody: document.getElementById("cp-detail-body"),
|
|
31
|
+
detailTitle: document.getElementById("cp-detail-title"),
|
|
32
|
+
detailStatus: document.getElementById("cp-detail-status"),
|
|
33
|
+
detailStamp: document.getElementById("cp-detail-stamp"),
|
|
34
|
+
artifactCount: document.getElementById("cp-artifact-count"),
|
|
35
|
+
planPanel: document.getElementById("cp-tab-plan"),
|
|
36
|
+
artifactsPanel: document.getElementById("cp-tab-artifacts"),
|
|
37
|
+
tabs: document.querySelectorAll(".cp-tab")
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function sortedPlans() {
|
|
41
|
+
return Array.from(state.plans.values()).sort(function (a, b) {
|
|
42
|
+
return (b.updated_at || "").localeCompare(a.updated_at || "");
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sortedArtifacts(slug) {
|
|
47
|
+
var map = state.artifacts.get(slug);
|
|
48
|
+
if (!map) return [];
|
|
49
|
+
return Array.from(map.values()).sort(function (a, b) {
|
|
50
|
+
return (a.created_at || "").localeCompare(b.created_at || "");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fmtTime(iso) {
|
|
55
|
+
if (!iso) return "";
|
|
56
|
+
var d = new Date(iso);
|
|
57
|
+
if (isNaN(d.getTime())) return iso;
|
|
58
|
+
return d.toLocaleString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function showError(msg) {
|
|
62
|
+
var el = document.createElement("div");
|
|
63
|
+
el.style.cssText = "position:fixed;bottom:16px;right:16px;background:var(--danger);color:#fff;padding:8px 14px;border-radius:4px;font-size:13px;z-index:9999";
|
|
64
|
+
el.textContent = msg;
|
|
65
|
+
document.body.appendChild(el);
|
|
66
|
+
setTimeout(function () { el.remove(); }, 4000);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderMarkdown(md, target) {
|
|
70
|
+
if (!window.marked || !window.DOMPurify) {
|
|
71
|
+
target.textContent = md;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
var html = window.marked.parse(md || "");
|
|
75
|
+
target.innerHTML = window.DOMPurify.sanitize(html);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderMermaid(source, target) {
|
|
79
|
+
target.classList.add("cp-mermaid");
|
|
80
|
+
target.textContent = "";
|
|
81
|
+
if (!window.mermaid || typeof window.mermaid.render !== "function") {
|
|
82
|
+
target.textContent = source;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
var id = "mmd-" + Math.random().toString(36).slice(2, 10);
|
|
86
|
+
try {
|
|
87
|
+
window.mermaid.render(id, source).then(
|
|
88
|
+
function (out) { target.innerHTML = out.svg; },
|
|
89
|
+
function (err) {
|
|
90
|
+
target.classList.remove("cp-mermaid");
|
|
91
|
+
target.classList.add("cp-mermaid-error");
|
|
92
|
+
target.textContent = "mermaid error: " + (err && err.message ? err.message : String(err));
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
target.classList.remove("cp-mermaid");
|
|
97
|
+
target.classList.add("cp-mermaid-error");
|
|
98
|
+
target.textContent = "mermaid error: " + err.message;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ─── Copy helpers ─────────────────────────────────────────────────────────── */
|
|
103
|
+
|
|
104
|
+
function fallbackCopy(text) {
|
|
105
|
+
var ta = document.createElement("textarea");
|
|
106
|
+
ta.value = text;
|
|
107
|
+
ta.style.cssText = "position:fixed;left:-9999px;top:-9999px;opacity:0";
|
|
108
|
+
document.body.appendChild(ta);
|
|
109
|
+
ta.select();
|
|
110
|
+
try { document.execCommand("copy"); } catch (_) {}
|
|
111
|
+
ta.remove();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function makeCopyBtn(label, getText) {
|
|
115
|
+
var btn = document.createElement("button");
|
|
116
|
+
btn.className = "cp-copy-btn";
|
|
117
|
+
btn.textContent = label;
|
|
118
|
+
btn.addEventListener("click", function (e) {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
var text = getText();
|
|
121
|
+
var flash = function () {
|
|
122
|
+
btn.textContent = "Copied!";
|
|
123
|
+
btn.classList.add("cp-copy-btn--copied");
|
|
124
|
+
setTimeout(function () {
|
|
125
|
+
btn.textContent = label;
|
|
126
|
+
btn.classList.remove("cp-copy-btn--copied");
|
|
127
|
+
}, 1500);
|
|
128
|
+
};
|
|
129
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
130
|
+
navigator.clipboard.writeText(text).then(flash).catch(function () {
|
|
131
|
+
fallbackCopy(text);
|
|
132
|
+
flash();
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
fallbackCopy(text);
|
|
136
|
+
flash();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return btn;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addTabsCopyAll(plan, artifacts) {
|
|
143
|
+
var existing = document.getElementById("cp-tabs-copy-all");
|
|
144
|
+
if (existing) existing.remove();
|
|
145
|
+
|
|
146
|
+
var btn = makeCopyBtn("Copy All", function () {
|
|
147
|
+
if (state.activeTab === "artifacts") {
|
|
148
|
+
return artifacts.map(function (a) {
|
|
149
|
+
return "# " + a.title + "\n\n" + a.content;
|
|
150
|
+
}).join("\n\n---\n\n");
|
|
151
|
+
}
|
|
152
|
+
return plan.content_md && plan.content_md.trim()
|
|
153
|
+
? plan.content_md
|
|
154
|
+
: els.planPanel.innerText.trim();
|
|
155
|
+
});
|
|
156
|
+
btn.id = "cp-tabs-copy-all";
|
|
157
|
+
btn.style.marginLeft = "auto";
|
|
158
|
+
var tabsEl = document.querySelector(".cp-tabs");
|
|
159
|
+
if (tabsEl) tabsEl.appendChild(btn);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function addPlanSectionCopyBtns() {
|
|
163
|
+
var structured = els.planPanel.querySelector(".cp-structured");
|
|
164
|
+
if (!structured) return;
|
|
165
|
+
var children = Array.from(structured.children);
|
|
166
|
+
children.forEach(function (child) {
|
|
167
|
+
child.classList.add("cp-c-copy-wrap");
|
|
168
|
+
var group = document.createElement("div");
|
|
169
|
+
group.className = "cp-copy-btn-group";
|
|
170
|
+
var btn = makeCopyBtn("Copy", (function (el) {
|
|
171
|
+
return function () { return el.innerText.trim(); };
|
|
172
|
+
})(child));
|
|
173
|
+
group.appendChild(btn);
|
|
174
|
+
child.appendChild(group);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
function renderRail() {
|
|
181
|
+
var plans = sortedPlans();
|
|
182
|
+
els.count.textContent = String(plans.length);
|
|
183
|
+
if (plans.length === 0) {
|
|
184
|
+
els.railEmpty.style.display = "block";
|
|
185
|
+
els.list.innerHTML = "";
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
els.railEmpty.style.display = "none";
|
|
189
|
+
els.list.innerHTML = "";
|
|
190
|
+
plans.forEach(function (plan) {
|
|
191
|
+
var artifactCount = (state.artifacts.get(plan.slug) || new Map()).size;
|
|
192
|
+
var li = document.createElement("li");
|
|
193
|
+
li.className = "cp-list-item" + (plan.slug === state.selectedSlug ? " active" : "");
|
|
194
|
+
li.tabIndex = 0;
|
|
195
|
+
li.dataset.slug = plan.slug;
|
|
196
|
+
li.innerHTML =
|
|
197
|
+
'<div class="cp-list-row">' +
|
|
198
|
+
' <span class="cp-list-title"></span>' +
|
|
199
|
+
' <span class="cp-status-pill cp-status-' + plan.status + '">' + plan.status + '</span>' +
|
|
200
|
+
'</div>' +
|
|
201
|
+
'<div class="cp-list-meta">' +
|
|
202
|
+
' <span class="cp-list-meta-slug"></span>' +
|
|
203
|
+
' <span class="cp-artifact-badge">' + artifactCount + ' artifact' + (artifactCount === 1 ? '' : 's') + '</span>' +
|
|
204
|
+
'</div>';
|
|
205
|
+
li.querySelector(".cp-list-title").textContent = plan.title;
|
|
206
|
+
li.querySelector(".cp-list-meta-slug").textContent = plan.slug;
|
|
207
|
+
li.addEventListener("click", function () { selectPlan(plan.slug); });
|
|
208
|
+
li.addEventListener("keydown", function (e) {
|
|
209
|
+
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); selectPlan(plan.slug); }
|
|
210
|
+
});
|
|
211
|
+
var delBtn = document.createElement("button");
|
|
212
|
+
delBtn.className = "cp-list-delete";
|
|
213
|
+
delBtn.title = "Delete plan";
|
|
214
|
+
delBtn.textContent = "×";
|
|
215
|
+
delBtn.addEventListener("click", function (e) {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
if (!delBtn.dataset.confirm) {
|
|
218
|
+
delBtn.dataset.confirm = "1";
|
|
219
|
+
delBtn.textContent = "?";
|
|
220
|
+
setTimeout(function () {
|
|
221
|
+
delBtn.textContent = "×";
|
|
222
|
+
delete delBtn.dataset.confirm;
|
|
223
|
+
}, 2000);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
fetch("/api/claude-plans/" + encodeURIComponent(plan.slug), { method: "DELETE" })
|
|
227
|
+
.then(function (r) {
|
|
228
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
229
|
+
removePlan(plan.slug);
|
|
230
|
+
})
|
|
231
|
+
.catch(function () {
|
|
232
|
+
delBtn.textContent = "×";
|
|
233
|
+
delete delBtn.dataset.confirm;
|
|
234
|
+
showError("Failed to delete plan. Try again.");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
li.querySelector(".cp-list-row").appendChild(delBtn);
|
|
238
|
+
els.list.appendChild(li);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderDetail() {
|
|
243
|
+
if (!state.selectedSlug || !state.plans.has(state.selectedSlug)) {
|
|
244
|
+
els.detailEmpty.style.display = "block";
|
|
245
|
+
els.detailBody.hidden = true;
|
|
246
|
+
var orphan = document.getElementById("cp-tabs-copy-all");
|
|
247
|
+
if (orphan) orphan.remove();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
var plan = state.plans.get(state.selectedSlug);
|
|
251
|
+
var artifacts = sortedArtifacts(state.selectedSlug);
|
|
252
|
+
|
|
253
|
+
els.detailEmpty.style.display = "none";
|
|
254
|
+
els.detailBody.hidden = false;
|
|
255
|
+
els.detailTitle.textContent = plan.title;
|
|
256
|
+
els.detailStatus.textContent = plan.status;
|
|
257
|
+
els.detailStatus.className = "cp-status-pill cp-status-" + plan.status;
|
|
258
|
+
els.detailStamp.textContent = "updated " + fmtTime(plan.updated_at);
|
|
259
|
+
els.artifactCount.textContent = String(artifacts.length);
|
|
260
|
+
|
|
261
|
+
var existingPrBadge = document.getElementById("cp-pr-badge");
|
|
262
|
+
if (existingPrBadge) existingPrBadge.remove();
|
|
263
|
+
if (plan.pr_url) {
|
|
264
|
+
var prBadge = document.createElement("a");
|
|
265
|
+
prBadge.id = "cp-pr-badge";
|
|
266
|
+
prBadge.className = "cp-pr-badge";
|
|
267
|
+
prBadge.href = plan.pr_url;
|
|
268
|
+
prBadge.target = "_blank";
|
|
269
|
+
prBadge.rel = "noopener noreferrer";
|
|
270
|
+
prBadge.textContent = plan.pr_title
|
|
271
|
+
? "PR #" + plan.pr_number + " — " + plan.pr_title
|
|
272
|
+
: "PR #" + (plan.pr_number || "");
|
|
273
|
+
els.detailStamp.insertAdjacentElement("afterend", prBadge);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (plan.content_html) {
|
|
277
|
+
els.planPanel.innerHTML = window.DOMPurify
|
|
278
|
+
? window.DOMPurify.sanitize(plan.content_html)
|
|
279
|
+
: plan.content_html;
|
|
280
|
+
} else {
|
|
281
|
+
renderMarkdown(plan.content_md, els.planPanel);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
addPlanSectionCopyBtns();
|
|
285
|
+
addTabsCopyAll(plan, artifacts);
|
|
286
|
+
|
|
287
|
+
els.artifactsPanel.innerHTML = "";
|
|
288
|
+
if (artifacts.length === 0) {
|
|
289
|
+
var empty = document.createElement("div");
|
|
290
|
+
empty.className = "cp-detail-empty";
|
|
291
|
+
empty.style.padding = "40px 0";
|
|
292
|
+
empty.textContent = "No artifacts yet. Call push_artifact or push_diagram from Claude.";
|
|
293
|
+
els.artifactsPanel.appendChild(empty);
|
|
294
|
+
} else {
|
|
295
|
+
artifacts.forEach(function (artifact) {
|
|
296
|
+
var card = document.createElement("div");
|
|
297
|
+
card.className = "cp-artifact-card";
|
|
298
|
+
var head = document.createElement("div");
|
|
299
|
+
head.className = "cp-artifact-head";
|
|
300
|
+
var title = document.createElement("span");
|
|
301
|
+
title.className = "cp-artifact-title";
|
|
302
|
+
title.textContent = artifact.title;
|
|
303
|
+
var kind = document.createElement("span");
|
|
304
|
+
kind.className = "cp-kind-pill";
|
|
305
|
+
kind.textContent = artifact.kind;
|
|
306
|
+
head.appendChild(title);
|
|
307
|
+
head.appendChild(kind);
|
|
308
|
+
|
|
309
|
+
var copyGroup = document.createElement("div");
|
|
310
|
+
copyGroup.className = "cp-copy-btn-group cp-copy-btn-group--artifact";
|
|
311
|
+
copyGroup.appendChild(makeCopyBtn("Copy", (function (content) {
|
|
312
|
+
return function () { return content; };
|
|
313
|
+
})(artifact.content)));
|
|
314
|
+
head.appendChild(copyGroup);
|
|
315
|
+
|
|
316
|
+
card.appendChild(head);
|
|
317
|
+
|
|
318
|
+
var body = document.createElement("div");
|
|
319
|
+
if (artifact.kind === "mermaid") renderMermaid(artifact.content, body);
|
|
320
|
+
else renderMarkdown(artifact.content, body);
|
|
321
|
+
card.appendChild(body);
|
|
322
|
+
els.artifactsPanel.appendChild(card);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function setActiveTab(tab) {
|
|
328
|
+
state.activeTab = tab;
|
|
329
|
+
els.tabs.forEach(function (btn) {
|
|
330
|
+
btn.classList.toggle("active", btn.dataset.tab === tab);
|
|
331
|
+
});
|
|
332
|
+
els.planPanel.hidden = tab !== "plan";
|
|
333
|
+
els.artifactsPanel.hidden = tab !== "artifacts";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function selectPlan(slug) {
|
|
337
|
+
state.selectedSlug = slug;
|
|
338
|
+
renderRail();
|
|
339
|
+
renderDetail();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
els.tabs.forEach(function (btn) {
|
|
343
|
+
btn.addEventListener("click", function () { setActiveTab(btn.dataset.tab); });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
function upsertPlan(plan) {
|
|
347
|
+
state.plans.set(plan.slug, plan);
|
|
348
|
+
renderRail();
|
|
349
|
+
if (state.selectedSlug === plan.slug) renderDetail();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function removePlan(slug) {
|
|
353
|
+
state.plans.delete(slug);
|
|
354
|
+
state.artifacts.delete(slug);
|
|
355
|
+
if (state.selectedSlug === slug) state.selectedSlug = null;
|
|
356
|
+
renderRail();
|
|
357
|
+
renderDetail();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function upsertArtifact(artifact) {
|
|
361
|
+
var bucket = state.artifacts.get(artifact.plan_slug);
|
|
362
|
+
if (!bucket) {
|
|
363
|
+
bucket = new Map();
|
|
364
|
+
state.artifacts.set(artifact.plan_slug, bucket);
|
|
365
|
+
}
|
|
366
|
+
bucket.set(artifact.slug, artifact);
|
|
367
|
+
renderRail();
|
|
368
|
+
if (state.selectedSlug === artifact.plan_slug) renderDetail();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function removeArtifact(planSlug, slug) {
|
|
372
|
+
var bucket = state.artifacts.get(planSlug);
|
|
373
|
+
if (!bucket) return;
|
|
374
|
+
bucket.delete(slug);
|
|
375
|
+
renderRail();
|
|
376
|
+
if (state.selectedSlug === planSlug) renderDetail();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function loadInitial() {
|
|
380
|
+
try {
|
|
381
|
+
var res = await fetch("/api/claude-plans");
|
|
382
|
+
var data = await res.json();
|
|
383
|
+
if (data && Array.isArray(data.plans)) {
|
|
384
|
+
data.plans.forEach(function (p) { state.plans.set(p.slug, p); });
|
|
385
|
+
}
|
|
386
|
+
if (data && data.storage) els.storage.textContent = data.storage;
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.warn("[claude-plans] failed to load initial state:", err);
|
|
389
|
+
}
|
|
390
|
+
// Preload artifacts for visible plans
|
|
391
|
+
var slugs = Array.from(state.plans.keys());
|
|
392
|
+
await Promise.all(slugs.map(function (slug) {
|
|
393
|
+
return fetch("/api/claude-plans/" + encodeURIComponent(slug))
|
|
394
|
+
.then(function (r) { return r.json(); })
|
|
395
|
+
.then(function (data) {
|
|
396
|
+
if (data && Array.isArray(data.artifacts)) {
|
|
397
|
+
var bucket = new Map();
|
|
398
|
+
data.artifacts.forEach(function (a) { bucket.set(a.slug, a); });
|
|
399
|
+
state.artifacts.set(slug, bucket);
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
.catch(function () { /* ignore */ });
|
|
403
|
+
}));
|
|
404
|
+
renderRail();
|
|
405
|
+
if (!state.selectedSlug) {
|
|
406
|
+
var sorted = sortedPlans();
|
|
407
|
+
if (sorted.length > 0) selectPlan(sorted[0].slug);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function startStream() {
|
|
412
|
+
var es = new EventSource("/api/claude-plans/stream");
|
|
413
|
+
es.addEventListener("plan:upsert", function (e) {
|
|
414
|
+
try { upsertPlan(JSON.parse(e.data).plan); } catch (_) {}
|
|
415
|
+
});
|
|
416
|
+
es.addEventListener("plan:delete", function (e) {
|
|
417
|
+
try { removePlan(JSON.parse(e.data).slug); } catch (_) {}
|
|
418
|
+
});
|
|
419
|
+
es.addEventListener("artifact:upsert", function (e) {
|
|
420
|
+
try { upsertArtifact(JSON.parse(e.data).artifact); } catch (_) {}
|
|
421
|
+
});
|
|
422
|
+
es.addEventListener("artifact:delete", function (e) {
|
|
423
|
+
try {
|
|
424
|
+
var payload = JSON.parse(e.data);
|
|
425
|
+
removeArtifact(payload.plan_slug, payload.slug);
|
|
426
|
+
} catch (_) {}
|
|
427
|
+
});
|
|
428
|
+
es.addEventListener("plan:focus", function (e) {
|
|
429
|
+
try {
|
|
430
|
+
var data = JSON.parse(e.data);
|
|
431
|
+
if (data.slug) {
|
|
432
|
+
selectPlan(data.slug);
|
|
433
|
+
var item = els.list.querySelector("[data-slug=\"" + data.slug + "\"]");
|
|
434
|
+
if (item) item.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
435
|
+
}
|
|
436
|
+
} catch (_) {}
|
|
437
|
+
});
|
|
438
|
+
es.onerror = function () { /* EventSource auto-reconnects */ };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
442
|
+
setActiveTab("plan");
|
|
443
|
+
loadInitial().then(startStream);
|
|
444
|
+
});
|
|
445
|
+
})();
|
package/public/index.html
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
</div>
|
|
15
15
|
<div class="header-right">
|
|
16
16
|
<button class="btn-refresh" id="refresh-btn" title="Refresh scopes and update sets">Refresh</button>
|
|
17
|
+
<a class="btn-refresh" href="/claude-plans" title="Claude plans and diagrams pushed via MCP">Claude</a>
|
|
17
18
|
<div class="active-task-chip" id="active-task-chip" style="display:none;"></div>
|
|
18
19
|
<div class="instance-badge" id="instance-badge">Loading...</div>
|
|
19
20
|
</div>
|
package/public/styles.css
CHANGED
|
@@ -717,6 +717,11 @@ header h1 span {
|
|
|
717
717
|
border-color: var(--danger);
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
.toast.warn {
|
|
721
|
+
border-color: #c88c2a;
|
|
722
|
+
color: #e8b86d;
|
|
723
|
+
}
|
|
724
|
+
|
|
720
725
|
@keyframes slideIn {
|
|
721
726
|
from {
|
|
722
727
|
opacity: 0;
|
|
@@ -817,3 +822,24 @@ header h1 span {
|
|
|
817
822
|
color: var(--text-muted);
|
|
818
823
|
white-space: nowrap;
|
|
819
824
|
}
|
|
825
|
+
|
|
826
|
+
.recent-edit-dismiss {
|
|
827
|
+
background: none;
|
|
828
|
+
border: none;
|
|
829
|
+
color: var(--text-muted);
|
|
830
|
+
font-size: 16px;
|
|
831
|
+
line-height: 1;
|
|
832
|
+
padding: 0 2px;
|
|
833
|
+
cursor: pointer;
|
|
834
|
+
opacity: 0;
|
|
835
|
+
transition: opacity 0.15s, color 0.15s;
|
|
836
|
+
flex-shrink: 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.recent-edit-row:hover .recent-edit-dismiss {
|
|
840
|
+
opacity: 1;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.recent-edit-dismiss:hover {
|
|
844
|
+
color: var(--danger);
|
|
845
|
+
}
|