@tenonhq/dovetail-dashboard 0.0.22 → 0.0.24
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 +1 -1
- package/public/claude-plans.css +135 -0
- package/public/claude-plans.html +21 -0
- package/public/claude-plans.js +143 -8
package/package.json
CHANGED
package/public/claude-plans.css
CHANGED
|
@@ -1123,3 +1123,138 @@
|
|
|
1123
1123
|
background: #14384a;
|
|
1124
1124
|
color: #b6e8ff;
|
|
1125
1125
|
}
|
|
1126
|
+
|
|
1127
|
+
/* ─── Topics cloud ─────────────────────────────────────────────────────────── */
|
|
1128
|
+
|
|
1129
|
+
.cp-topics {
|
|
1130
|
+
margin: 0 24px 12px;
|
|
1131
|
+
background: var(--surface-raised);
|
|
1132
|
+
border: 1px solid var(--border);
|
|
1133
|
+
border-radius: var(--radius-md, 8px);
|
|
1134
|
+
box-shadow: var(--shadow-1, 0 1px 2px rgba(0,0,0,0.04));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.cp-topics-summary {
|
|
1138
|
+
display: flex;
|
|
1139
|
+
align-items: center;
|
|
1140
|
+
gap: 8px;
|
|
1141
|
+
padding: 10px 14px;
|
|
1142
|
+
cursor: pointer;
|
|
1143
|
+
list-style: none;
|
|
1144
|
+
user-select: none;
|
|
1145
|
+
font-size: 13px;
|
|
1146
|
+
color: var(--fg);
|
|
1147
|
+
}
|
|
1148
|
+
.cp-topics-summary::-webkit-details-marker { display: none; }
|
|
1149
|
+
.cp-topics-summary:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
|
|
1150
|
+
|
|
1151
|
+
.cp-topics-caret {
|
|
1152
|
+
display: inline-block;
|
|
1153
|
+
transition: transform 0.15s ease;
|
|
1154
|
+
color: var(--fg-muted);
|
|
1155
|
+
font-size: 12px;
|
|
1156
|
+
width: 12px;
|
|
1157
|
+
}
|
|
1158
|
+
.cp-topics:not([open]) .cp-topics-caret { transform: rotate(-90deg); }
|
|
1159
|
+
|
|
1160
|
+
.cp-topics-title {
|
|
1161
|
+
font-weight: 600;
|
|
1162
|
+
letter-spacing: 0.02em;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.cp-topics-count {
|
|
1166
|
+
display: inline-flex;
|
|
1167
|
+
align-items: center;
|
|
1168
|
+
justify-content: center;
|
|
1169
|
+
min-width: 22px;
|
|
1170
|
+
height: 20px;
|
|
1171
|
+
padding: 0 6px;
|
|
1172
|
+
background: var(--bg-muted, var(--surface));
|
|
1173
|
+
color: var(--fg-muted);
|
|
1174
|
+
border: 1px solid var(--border);
|
|
1175
|
+
border-radius: 999px;
|
|
1176
|
+
font-size: 11px;
|
|
1177
|
+
font-weight: 600;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.cp-topics-clear {
|
|
1181
|
+
margin-left: auto;
|
|
1182
|
+
background: transparent;
|
|
1183
|
+
border: 1px solid var(--border);
|
|
1184
|
+
border-radius: 6px;
|
|
1185
|
+
padding: 3px 10px;
|
|
1186
|
+
font-size: 11px;
|
|
1187
|
+
color: var(--fg-muted);
|
|
1188
|
+
cursor: pointer;
|
|
1189
|
+
}
|
|
1190
|
+
.cp-topics-clear:hover {
|
|
1191
|
+
background: var(--bg-muted, var(--surface));
|
|
1192
|
+
color: var(--fg);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.cp-topics-cloud {
|
|
1196
|
+
display: flex;
|
|
1197
|
+
flex-wrap: wrap;
|
|
1198
|
+
gap: 6px 8px;
|
|
1199
|
+
padding: 10px 14px 14px;
|
|
1200
|
+
border-top: 1px solid var(--border-subtle, var(--border));
|
|
1201
|
+
line-height: 1.3;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.cp-topics-empty {
|
|
1205
|
+
color: var(--fg-muted);
|
|
1206
|
+
font-size: 12px;
|
|
1207
|
+
font-style: italic;
|
|
1208
|
+
padding: 2px 0;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.cp-topic-chip {
|
|
1212
|
+
display: inline-flex;
|
|
1213
|
+
align-items: baseline;
|
|
1214
|
+
gap: 6px;
|
|
1215
|
+
padding: 4px 10px;
|
|
1216
|
+
background: var(--surface);
|
|
1217
|
+
color: var(--fg);
|
|
1218
|
+
border: 1px solid var(--border);
|
|
1219
|
+
border-radius: 999px;
|
|
1220
|
+
font-family: inherit;
|
|
1221
|
+
font-weight: 500;
|
|
1222
|
+
letter-spacing: 0.01em;
|
|
1223
|
+
cursor: pointer;
|
|
1224
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
1225
|
+
}
|
|
1226
|
+
.cp-topic-chip:hover {
|
|
1227
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--surface));
|
|
1228
|
+
border-color: var(--accent);
|
|
1229
|
+
}
|
|
1230
|
+
.cp-topic-chip:focus-visible {
|
|
1231
|
+
outline: none;
|
|
1232
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.cp-topic-chip--active {
|
|
1236
|
+
background: var(--accent);
|
|
1237
|
+
color: var(--accent-fg);
|
|
1238
|
+
border-color: var(--accent);
|
|
1239
|
+
}
|
|
1240
|
+
.cp-topic-chip--active:hover {
|
|
1241
|
+
background: var(--accent-hover, var(--accent));
|
|
1242
|
+
color: var(--accent-fg);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.cp-topic-chip-count {
|
|
1246
|
+
display: inline-flex;
|
|
1247
|
+
align-items: center;
|
|
1248
|
+
justify-content: center;
|
|
1249
|
+
min-width: 18px;
|
|
1250
|
+
padding: 0 5px;
|
|
1251
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
1252
|
+
border-radius: 999px;
|
|
1253
|
+
font-size: 10px;
|
|
1254
|
+
font-weight: 700;
|
|
1255
|
+
line-height: 1.6;
|
|
1256
|
+
letter-spacing: 0;
|
|
1257
|
+
}
|
|
1258
|
+
.cp-topic-chip--active .cp-topic-chip-count {
|
|
1259
|
+
background: color-mix(in srgb, var(--accent-fg) 20%, transparent);
|
|
1260
|
+
}
|
package/public/claude-plans.html
CHANGED
|
@@ -36,6 +36,27 @@
|
|
|
36
36
|
</div>
|
|
37
37
|
</header>
|
|
38
38
|
|
|
39
|
+
<details class="cp-topics" id="cp-topics" open>
|
|
40
|
+
<summary class="cp-topics-summary">
|
|
41
|
+
<span class="cp-topics-caret" aria-hidden="true">▾</span>
|
|
42
|
+
<span class="cp-topics-title">Topics</span>
|
|
43
|
+
<span class="cp-topics-count" id="cp-topics-count">0</span>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
class="cp-topics-clear"
|
|
47
|
+
id="cp-topics-clear"
|
|
48
|
+
hidden
|
|
49
|
+
title="Clear topic filter"
|
|
50
|
+
aria-label="Clear topic filter"
|
|
51
|
+
>Clear filter</button>
|
|
52
|
+
</summary>
|
|
53
|
+
<div class="cp-topics-cloud" id="cp-topics-cloud" role="group" aria-label="Plan topics">
|
|
54
|
+
<span class="cp-topics-empty" id="cp-topics-empty">
|
|
55
|
+
No topics yet. Push a plan and topics will appear here.
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
</details>
|
|
59
|
+
|
|
39
60
|
<main class="cp-layout">
|
|
40
61
|
<aside class="cp-rail" id="cp-rail">
|
|
41
62
|
<div class="cp-rail-header">
|
package/public/claude-plans.js
CHANGED
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
prompts: new Map(), // slug -> Map<promptSlug, prompt>
|
|
37
37
|
selectedSlug: null,
|
|
38
38
|
activeTab: "plan",
|
|
39
|
-
query: ""
|
|
39
|
+
query: "", // lowercased search query; "" means show all
|
|
40
|
+
topicFilter: null // active topic label, null = no topic filter
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
var els = {
|
|
@@ -58,9 +59,24 @@
|
|
|
58
59
|
planPanel: document.getElementById("cp-tab-plan"),
|
|
59
60
|
artifactsPanel: document.getElementById("cp-tab-artifacts"),
|
|
60
61
|
promptsPanel: document.getElementById("cp-tab-prompts"),
|
|
61
|
-
tabs: document.querySelectorAll(".cp-tab")
|
|
62
|
+
tabs: document.querySelectorAll(".cp-tab"),
|
|
63
|
+
topics: document.getElementById("cp-topics"),
|
|
64
|
+
topicsCloud: document.getElementById("cp-topics-cloud"),
|
|
65
|
+
topicsCount: document.getElementById("cp-topics-count"),
|
|
66
|
+
topicsEmpty: document.getElementById("cp-topics-empty"),
|
|
67
|
+
topicsClear: document.getElementById("cp-topics-clear")
|
|
62
68
|
};
|
|
63
69
|
|
|
70
|
+
function planMatchesTopic(plan, topic) {
|
|
71
|
+
if (!topic) return true;
|
|
72
|
+
var cats = plan.categories;
|
|
73
|
+
if (!cats || !cats.length) return false;
|
|
74
|
+
for (var i = 0; i < cats.length; i++) {
|
|
75
|
+
if (cats[i] === topic) return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
// True when plan matches q across title, slug, status, plan markdown/html,
|
|
65
81
|
// pr title, and every artifact title + content. Lets you find a plan by any
|
|
66
82
|
// snippet that appears anywhere in or attached to it.
|
|
@@ -96,9 +112,121 @@
|
|
|
96
112
|
|
|
97
113
|
function sortedPlans() {
|
|
98
114
|
var q = state.query;
|
|
115
|
+
var topic = state.topicFilter;
|
|
99
116
|
var all = allPlansSorted();
|
|
100
|
-
if (!q) return all;
|
|
101
|
-
return all.filter(function (p) {
|
|
117
|
+
if (!q && !topic) return all;
|
|
118
|
+
return all.filter(function (p) {
|
|
119
|
+
return planMatchesQuery(p, q) && planMatchesTopic(p, topic);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ─── Topics aggregation + render ──────────────────────────────────────────
|
|
124
|
+
* Counts topic occurrences across all plans (not just filtered ones) so the
|
|
125
|
+
* cloud reflects the full corpus. Click a chip to filter the rail by that
|
|
126
|
+
* topic; click the active chip again to clear.
|
|
127
|
+
*
|
|
128
|
+
* Counts are computed client-side from plan.categories arrays — no new
|
|
129
|
+
* server endpoint or SSE event. The existing plan:upsert stream rerenders
|
|
130
|
+
* the cloud automatically because renderRail() now also calls renderTopics().
|
|
131
|
+
*/
|
|
132
|
+
function aggregateTopics() {
|
|
133
|
+
var counts = new Map();
|
|
134
|
+
state.plans.forEach(function (plan) {
|
|
135
|
+
var cats = plan.categories;
|
|
136
|
+
if (!cats || !cats.length) return;
|
|
137
|
+
for (var i = 0; i < cats.length; i++) {
|
|
138
|
+
var label = cats[i];
|
|
139
|
+
counts.set(label, (counts.get(label) || 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
var rows = [];
|
|
143
|
+
counts.forEach(function (count, label) {
|
|
144
|
+
rows.push({ label: label, count: count });
|
|
145
|
+
});
|
|
146
|
+
rows.sort(function (a, b) {
|
|
147
|
+
if (b.count !== a.count) return b.count - a.count;
|
|
148
|
+
return a.label.localeCompare(b.label);
|
|
149
|
+
});
|
|
150
|
+
return rows;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// sqrt compression keeps a 30-count topic from dwarfing a 2-count one.
|
|
154
|
+
function topicFontSize(count, max) {
|
|
155
|
+
if (max <= 1) return 14;
|
|
156
|
+
var ratio = Math.sqrt(count) / Math.sqrt(max);
|
|
157
|
+
return Math.round(12 + ratio * 12);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderTopics() {
|
|
161
|
+
if (!els.topicsCloud) return;
|
|
162
|
+
var rows = aggregateTopics();
|
|
163
|
+
if (els.topicsCount) els.topicsCount.textContent = String(rows.length);
|
|
164
|
+
if (els.topicsClear) els.topicsClear.hidden = !state.topicFilter;
|
|
165
|
+
if (rows.length === 0) {
|
|
166
|
+
els.topicsCloud.innerHTML = "";
|
|
167
|
+
if (els.topicsEmpty) {
|
|
168
|
+
els.topicsCloud.appendChild(els.topicsEmpty);
|
|
169
|
+
els.topicsEmpty.style.display = "inline";
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (els.topicsEmpty) els.topicsEmpty.style.display = "none";
|
|
174
|
+
els.topicsCloud.innerHTML = "";
|
|
175
|
+
var max = rows[0].count;
|
|
176
|
+
rows.forEach(function (row) {
|
|
177
|
+
var chip = document.createElement("button");
|
|
178
|
+
chip.type = "button";
|
|
179
|
+
var isActive = state.topicFilter === row.label;
|
|
180
|
+
chip.className = "cp-topic-chip" + (isActive ? " cp-topic-chip--active" : "");
|
|
181
|
+
chip.style.fontSize = topicFontSize(row.count, max) + "px";
|
|
182
|
+
chip.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
183
|
+
chip.title = row.count + " plan" + (row.count === 1 ? "" : "s");
|
|
184
|
+
var labelEl = document.createElement("span");
|
|
185
|
+
labelEl.className = "cp-topic-chip-label";
|
|
186
|
+
labelEl.textContent = row.label;
|
|
187
|
+
var countEl = document.createElement("span");
|
|
188
|
+
countEl.className = "cp-topic-chip-count";
|
|
189
|
+
countEl.textContent = String(row.count);
|
|
190
|
+
chip.appendChild(labelEl);
|
|
191
|
+
chip.appendChild(countEl);
|
|
192
|
+
chip.addEventListener("click", function () {
|
|
193
|
+
applyTopicFilter(state.topicFilter === row.label ? null : row.label);
|
|
194
|
+
});
|
|
195
|
+
els.topicsCloud.appendChild(chip);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function applyTopicFilter(label) {
|
|
200
|
+
state.topicFilter = label;
|
|
201
|
+
renderTopics();
|
|
202
|
+
renderRail();
|
|
203
|
+
if (label) {
|
|
204
|
+
var visible = sortedPlans();
|
|
205
|
+
var stillVisible = state.selectedSlug &&
|
|
206
|
+
visible.some(function (p) { return p.slug === state.selectedSlug; });
|
|
207
|
+
if (!stillVisible && visible.length > 0) selectPlan(visible[0].slug);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setupTopics() {
|
|
212
|
+
if (els.topicsClear) {
|
|
213
|
+
els.topicsClear.addEventListener("click", function (e) {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
applyTopicFilter(null);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (els.topics) {
|
|
220
|
+
try {
|
|
221
|
+
var stored = localStorage.getItem("cp-topics-collapsed");
|
|
222
|
+
if (stored === "1") els.topics.open = false;
|
|
223
|
+
} catch (_) {}
|
|
224
|
+
els.topics.addEventListener("toggle", function () {
|
|
225
|
+
try {
|
|
226
|
+
localStorage.setItem("cp-topics-collapsed", els.topics.open ? "0" : "1");
|
|
227
|
+
} catch (_) {}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
102
230
|
}
|
|
103
231
|
|
|
104
232
|
function sortedArtifacts(slug) {
|
|
@@ -296,9 +424,10 @@
|
|
|
296
424
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
297
425
|
|
|
298
426
|
function renderRail() {
|
|
427
|
+
renderTopics();
|
|
299
428
|
var plans = sortedPlans();
|
|
300
429
|
var totalPlans = state.plans.size;
|
|
301
|
-
var isFiltering = !!state.query;
|
|
430
|
+
var isFiltering = !!state.query || !!state.topicFilter;
|
|
302
431
|
els.count.textContent = isFiltering
|
|
303
432
|
? plans.length + " / " + totalPlans
|
|
304
433
|
: String(totalPlans);
|
|
@@ -307,7 +436,12 @@
|
|
|
307
436
|
if (isFiltering && totalPlans > 0) {
|
|
308
437
|
els.railEmpty.style.display = "none";
|
|
309
438
|
els.railNoMatch.hidden = false;
|
|
310
|
-
if (els.railNoMatchQ)
|
|
439
|
+
if (els.railNoMatchQ) {
|
|
440
|
+
var pieces = [];
|
|
441
|
+
if (state.query) pieces.push('"' + state.query + '"');
|
|
442
|
+
if (state.topicFilter) pieces.push("topic “" + state.topicFilter + "”");
|
|
443
|
+
els.railNoMatchQ.textContent = pieces.join(" + ");
|
|
444
|
+
}
|
|
311
445
|
} else {
|
|
312
446
|
els.railEmpty.style.display = "block";
|
|
313
447
|
els.railNoMatch.hidden = true;
|
|
@@ -411,9 +545,9 @@
|
|
|
411
545
|
resumeBtn.id = "cp-resume-btn";
|
|
412
546
|
resumeBtn.className = "cp-resume-btn";
|
|
413
547
|
resumeBtn.textContent = "Resume";
|
|
414
|
-
resumeBtn.title = "Copy /resume command to clipboard";
|
|
548
|
+
resumeBtn.title = "Copy /resume-claude-plans command to clipboard";
|
|
415
549
|
resumeBtn.addEventListener("click", function () {
|
|
416
|
-
var cmd = "/resume " + plan.slug;
|
|
550
|
+
var cmd = "/resume-claude-plans " + plan.slug;
|
|
417
551
|
var finish = function () { showToast("Copied! Paste into Claude."); };
|
|
418
552
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
419
553
|
navigator.clipboard.writeText(cmd).then(finish).catch(function () {
|
|
@@ -800,6 +934,7 @@
|
|
|
800
934
|
document.addEventListener("DOMContentLoaded", function () {
|
|
801
935
|
setupTheme();
|
|
802
936
|
setupSearch();
|
|
937
|
+
setupTopics();
|
|
803
938
|
setActiveTab("plan");
|
|
804
939
|
loadInitial().then(startStream);
|
|
805
940
|
});
|