@tenonhq/dovetail-dashboard 0.0.35 → 0.0.37
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 -3
- package/public/claude-plans.html +42 -1
- package/public/cp-todo-panel.js +291 -0
- package/public/cp-todo.css +103 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenonhq/dovetail-dashboard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"description": "Update Set Dashboard for Dovetail",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"postpublish": "npm run version:bump"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@tenonhq/dovetail-claude-plans": "
|
|
13
|
-
"@tenonhq/dovetail-todo": "
|
|
12
|
+
"@tenonhq/dovetail-claude-plans": "~0.0.21",
|
|
13
|
+
"@tenonhq/dovetail-todo": "~0.0.1",
|
|
14
14
|
"axios": "^1.5.1",
|
|
15
15
|
"axios-cookiejar-support": "^4.0.7",
|
|
16
16
|
"chokidar": "^3.6.0",
|
package/public/claude-plans.html
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
<link rel="stylesheet" href="/tokens.css" />
|
|
19
19
|
<link rel="stylesheet" href="/styles.css" />
|
|
20
20
|
<link rel="stylesheet" href="/claude-plans.css" />
|
|
21
|
+
<link rel="stylesheet" href="/todos.css" />
|
|
22
|
+
<link rel="stylesheet" href="/cp-todo.css" />
|
|
21
23
|
</head>
|
|
22
24
|
<body class="cp-body">
|
|
23
25
|
<header>
|
|
@@ -33,7 +35,10 @@
|
|
|
33
35
|
<a class="cp-nav-link" href="/prompt-lints">Prompt Lints
|
|
34
36
|
<span class="cp-lints-badge" id="cp-lints-badge" title="Prompts needing review" hidden>0</span>
|
|
35
37
|
→</a>
|
|
36
|
-
<
|
|
38
|
+
<button class="cp-nav-link cp-todo-toggle" id="cp-todo-toggle" type="button"
|
|
39
|
+
aria-expanded="false" aria-controls="cp-todo-drawer"
|
|
40
|
+
title="Open TODO priorities panel">TODO
|
|
41
|
+
<span class="cp-todo-badge" id="cp-todo-badge" hidden>0</span></button>
|
|
37
42
|
<button class="cp-theme-toggle" id="cp-theme-toggle" type="button"
|
|
38
43
|
aria-label="Toggle dark mode" title="Toggle dark mode"></button>
|
|
39
44
|
<span class="cp-storage" id="cp-storage" title="storage root"></span>
|
|
@@ -141,6 +146,42 @@
|
|
|
141
146
|
</section>
|
|
142
147
|
</main>
|
|
143
148
|
|
|
149
|
+
<!-- TODO side panel: a drawer hosting @tenonhq/dovetail-todo's priority list,
|
|
150
|
+
open/closed from the header TODO button. Backed by the same /api/todos
|
|
151
|
+
endpoints + SSE stream as the standalone /todos page. -->
|
|
152
|
+
<aside class="cp-todo-drawer" id="cp-todo-drawer" aria-hidden="true" aria-label="TODO priorities">
|
|
153
|
+
<div class="cp-todo-drawer-header">
|
|
154
|
+
<span class="cp-todo-drawer-title">TODO</span>
|
|
155
|
+
<span class="td-summary" id="cpt-summary">—</span>
|
|
156
|
+
<button class="cp-todo-close" id="cp-todo-close" type="button"
|
|
157
|
+
title="Close (Esc)" aria-label="Close TODO panel">×</button>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="cp-todo-drawer-body">
|
|
160
|
+
<form class="td-add" id="cpt-add-form" autocomplete="off">
|
|
161
|
+
<input type="text" id="cpt-input" class="td-input" maxlength="280"
|
|
162
|
+
placeholder="Add a priority… (Enter = bottom, Shift+Enter = top)">
|
|
163
|
+
<button type="submit" class="td-add-btn" id="cpt-add-btn">Add</button>
|
|
164
|
+
</form>
|
|
165
|
+
<div class="td-toolbar">
|
|
166
|
+
<div class="td-toolbar-actions">
|
|
167
|
+
<label class="td-toggle-done">
|
|
168
|
+
<input type="checkbox" id="cpt-hide-done"> Hide done
|
|
169
|
+
</label>
|
|
170
|
+
<button class="td-clear-btn" id="cpt-clear-done" type="button"
|
|
171
|
+
title="Remove all completed items">Clear done</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
<ul class="td-list" id="cpt-list" aria-label="Priority TODO list"></ul>
|
|
175
|
+
<div class="td-empty" id="cpt-empty" style="display:none;">
|
|
176
|
+
Nothing here yet. Add your first priority above — the top of the list is what you work on next.
|
|
177
|
+
</div>
|
|
178
|
+
<p class="td-hint">Drag a row by its handle to reprioritize. Top item is next.</p>
|
|
179
|
+
</div>
|
|
180
|
+
</aside>
|
|
181
|
+
|
|
182
|
+
<div class="toast-container" id="toast-container"></div>
|
|
183
|
+
|
|
184
|
+
<script src="/cp-todo-panel.js"></script>
|
|
144
185
|
<script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
|
|
145
186
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.5/dist/purify.min.js"></script>
|
|
146
187
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.6/dist/mermaid.min.js"></script>
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/* Claude Plans — embedded TODO side panel.
|
|
2
|
+
* A self-contained port of todos.js scoped to the drawer on the claude-plans
|
|
3
|
+
* page: open/close toggle (persisted), priority list with add/toggle/edit/
|
|
4
|
+
* remove/drag-reorder over the same /api/todos REST surface, live SSE refresh,
|
|
5
|
+
* and a header badge of the open (not-done) count. It deliberately does NOT
|
|
6
|
+
* touch the theme toggle — claude-plans.js already owns that.
|
|
7
|
+
*/
|
|
8
|
+
(function () {
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
var drawer = document.getElementById("cp-todo-drawer");
|
|
12
|
+
var toggleBtn = document.getElementById("cp-todo-toggle");
|
|
13
|
+
var closeBtn = document.getElementById("cp-todo-close");
|
|
14
|
+
var badge = document.getElementById("cp-todo-badge");
|
|
15
|
+
if (!drawer || !toggleBtn) return;
|
|
16
|
+
|
|
17
|
+
var listEl = document.getElementById("cpt-list");
|
|
18
|
+
var emptyEl = document.getElementById("cpt-empty");
|
|
19
|
+
var summaryEl = document.getElementById("cpt-summary");
|
|
20
|
+
var form = document.getElementById("cpt-add-form");
|
|
21
|
+
var input = document.getElementById("cpt-input");
|
|
22
|
+
var addBtn = document.getElementById("cpt-add-btn");
|
|
23
|
+
var hideDoneEl = document.getElementById("cpt-hide-done");
|
|
24
|
+
var clearDoneBtn = document.getElementById("cpt-clear-done");
|
|
25
|
+
|
|
26
|
+
var OPEN_KEY = "cp-todo-open";
|
|
27
|
+
var items = []; // current ordered items from the server
|
|
28
|
+
var dragId = null; // id of the row being dragged
|
|
29
|
+
var isEditing = false; // suppress SSE re-render while editing text
|
|
30
|
+
|
|
31
|
+
// ── Open/close ─────────────────────────────────────────────────────────────
|
|
32
|
+
function isOpen() { return drawer.classList.contains("cp-todo-open"); }
|
|
33
|
+
// `silent` skips focus moves — used on the initial persisted-open restore so a
|
|
34
|
+
// page load doesn't yank focus into the panel (and hijack the "/" shortcut).
|
|
35
|
+
function setOpen(open, silent) {
|
|
36
|
+
var wasOpen = isOpen();
|
|
37
|
+
drawer.classList.toggle("cp-todo-open", open);
|
|
38
|
+
drawer.setAttribute("aria-hidden", open ? "false" : "true");
|
|
39
|
+
toggleBtn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
40
|
+
try { localStorage.setItem(OPEN_KEY, open ? "1" : "0"); } catch (e) {}
|
|
41
|
+
if (silent) return;
|
|
42
|
+
if (open) {
|
|
43
|
+
if (input) input.focus();
|
|
44
|
+
} else if (wasOpen && drawer.contains(document.activeElement)) {
|
|
45
|
+
// Don't strand keyboard focus inside the now-aria-hidden subtree.
|
|
46
|
+
toggleBtn.focus();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
toggleBtn.addEventListener("click", function () { setOpen(!isOpen()); });
|
|
50
|
+
if (closeBtn) closeBtn.addEventListener("click", function () { setOpen(false); });
|
|
51
|
+
document.addEventListener("keydown", function (e) {
|
|
52
|
+
if (e.key === "Escape" && isOpen() && !isEditing) setOpen(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function toast(message, kind) {
|
|
56
|
+
var c = document.getElementById("toast-container");
|
|
57
|
+
if (!c) { if (kind === "error") console.warn(message); return; }
|
|
58
|
+
var t = document.createElement("div");
|
|
59
|
+
t.className = "toast" + (kind ? " toast-" + kind : "");
|
|
60
|
+
t.textContent = message;
|
|
61
|
+
c.appendChild(t);
|
|
62
|
+
setTimeout(function () { t.remove(); }, 3200);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function api(method, url, body) {
|
|
66
|
+
var opts = { method: method, headers: { "Content-Type": "application/json" } };
|
|
67
|
+
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
68
|
+
return fetch(url, opts).then(function (res) {
|
|
69
|
+
return res.json().then(function (data) {
|
|
70
|
+
if (!res.ok) throw new Error((data && data.error) || ("HTTP " + res.status));
|
|
71
|
+
return data;
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function visibleItems() {
|
|
77
|
+
if (hideDoneEl && hideDoneEl.checked) {
|
|
78
|
+
return items.filter(function (it) { return !it.done; });
|
|
79
|
+
}
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function updateBadge() {
|
|
84
|
+
if (!badge) return;
|
|
85
|
+
var open = items.filter(function (it) { return !it.done; }).length;
|
|
86
|
+
badge.textContent = String(open);
|
|
87
|
+
badge.hidden = open === 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function render() {
|
|
91
|
+
updateBadge();
|
|
92
|
+
if (isEditing) return;
|
|
93
|
+
listEl.innerHTML = "";
|
|
94
|
+
var shown = visibleItems();
|
|
95
|
+
var doneCount = items.filter(function (it) { return it.done; }).length;
|
|
96
|
+
summaryEl.textContent =
|
|
97
|
+
items.length === 0
|
|
98
|
+
? "No items"
|
|
99
|
+
: items.length + " item" + (items.length === 1 ? "" : "s") + " · " + doneCount + " done";
|
|
100
|
+
emptyEl.style.display = items.length === 0 ? "block" : "none";
|
|
101
|
+
|
|
102
|
+
shown.forEach(function (it, idx) {
|
|
103
|
+
listEl.appendChild(renderItem(it, idx));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderItem(it, idx) {
|
|
108
|
+
var li = document.createElement("li");
|
|
109
|
+
li.className = "td-item" + (it.done ? " td-done" : "");
|
|
110
|
+
li.setAttribute("data-id", it.id);
|
|
111
|
+
|
|
112
|
+
var handle = document.createElement("span");
|
|
113
|
+
handle.className = "td-handle";
|
|
114
|
+
handle.textContent = "⠿";
|
|
115
|
+
handle.title = "Drag to reprioritize";
|
|
116
|
+
handle.setAttribute("draggable", "true");
|
|
117
|
+
wireDrag(handle, li, it);
|
|
118
|
+
li.appendChild(handle);
|
|
119
|
+
|
|
120
|
+
var rank = document.createElement("span");
|
|
121
|
+
rank.className = "td-rank";
|
|
122
|
+
rank.textContent = String(idx + 1);
|
|
123
|
+
li.appendChild(rank);
|
|
124
|
+
|
|
125
|
+
var check = document.createElement("input");
|
|
126
|
+
check.type = "checkbox";
|
|
127
|
+
check.className = "td-check";
|
|
128
|
+
check.checked = it.done;
|
|
129
|
+
check.title = it.done ? "Mark not done" : "Mark done";
|
|
130
|
+
check.addEventListener("change", function () {
|
|
131
|
+
api("PATCH", "/api/todos/" + encodeURIComponent(it.id), { done: check.checked })
|
|
132
|
+
.then(function (r) { applyList(r.list); })
|
|
133
|
+
.catch(function (e) { toast(e.message, "error"); check.checked = it.done; });
|
|
134
|
+
});
|
|
135
|
+
li.appendChild(check);
|
|
136
|
+
|
|
137
|
+
var text = document.createElement("span");
|
|
138
|
+
text.className = "td-text";
|
|
139
|
+
text.textContent = it.text;
|
|
140
|
+
text.title = "Double-click to edit";
|
|
141
|
+
text.addEventListener("dblclick", function () { beginEdit(li, text, it); });
|
|
142
|
+
li.appendChild(text);
|
|
143
|
+
|
|
144
|
+
var del = document.createElement("button");
|
|
145
|
+
del.className = "td-del";
|
|
146
|
+
del.type = "button";
|
|
147
|
+
del.textContent = "×";
|
|
148
|
+
del.title = "Delete";
|
|
149
|
+
del.addEventListener("click", function () {
|
|
150
|
+
api("DELETE", "/api/todos/" + encodeURIComponent(it.id))
|
|
151
|
+
.then(function (r) { applyList(r.list); })
|
|
152
|
+
.catch(function (e) { toast(e.message, "error"); });
|
|
153
|
+
});
|
|
154
|
+
li.appendChild(del);
|
|
155
|
+
|
|
156
|
+
return li;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function beginEdit(li, text, it) {
|
|
160
|
+
isEditing = true;
|
|
161
|
+
var editor = document.createElement("input");
|
|
162
|
+
editor.type = "text";
|
|
163
|
+
editor.className = "td-text-edit";
|
|
164
|
+
editor.maxLength = 280;
|
|
165
|
+
editor.value = it.text;
|
|
166
|
+
li.replaceChild(editor, text);
|
|
167
|
+
editor.focus();
|
|
168
|
+
editor.setSelectionRange(editor.value.length, editor.value.length);
|
|
169
|
+
|
|
170
|
+
function commit() {
|
|
171
|
+
var next = editor.value.trim();
|
|
172
|
+
isEditing = false;
|
|
173
|
+
if (!next || next === it.text) { render(); return; }
|
|
174
|
+
api("PATCH", "/api/todos/" + encodeURIComponent(it.id), { text: next })
|
|
175
|
+
.then(function (r) { applyList(r.list); })
|
|
176
|
+
.catch(function (e) { toast(e.message, "error"); render(); });
|
|
177
|
+
}
|
|
178
|
+
function cancel() { isEditing = false; render(); }
|
|
179
|
+
|
|
180
|
+
editor.addEventListener("blur", commit);
|
|
181
|
+
editor.addEventListener("keydown", function (e) {
|
|
182
|
+
if (e.key === "Enter") { e.preventDefault(); editor.blur(); }
|
|
183
|
+
else if (e.key === "Escape") { e.preventDefault(); editor.removeEventListener("blur", commit); cancel(); }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Drag-and-drop reorder ─────────────────────────────────────────────────
|
|
188
|
+
function wireDrag(handle, li, it) {
|
|
189
|
+
handle.addEventListener("dragstart", function (e) {
|
|
190
|
+
dragId = it.id;
|
|
191
|
+
li.classList.add("td-dragging");
|
|
192
|
+
if (e.dataTransfer) { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", it.id); }
|
|
193
|
+
});
|
|
194
|
+
handle.addEventListener("dragend", function () {
|
|
195
|
+
dragId = null;
|
|
196
|
+
li.classList.remove("td-dragging");
|
|
197
|
+
Array.prototype.forEach.call(listEl.children, function (c) { c.classList.remove("td-drop-target"); });
|
|
198
|
+
});
|
|
199
|
+
li.addEventListener("dragover", function (e) {
|
|
200
|
+
if (dragId === null || dragId === it.id) return;
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
203
|
+
li.classList.add("td-drop-target");
|
|
204
|
+
});
|
|
205
|
+
li.addEventListener("dragleave", function () { li.classList.remove("td-drop-target"); });
|
|
206
|
+
li.addEventListener("drop", function (e) {
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
li.classList.remove("td-drop-target");
|
|
209
|
+
if (dragId === null || dragId === it.id) return;
|
|
210
|
+
reorder(dragId, it.id);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Move dragged id to the slot occupied by targetId, then persist full order.
|
|
215
|
+
function reorder(sourceId, targetId) {
|
|
216
|
+
var order = items.map(function (i) { return i.id; });
|
|
217
|
+
var from = order.indexOf(sourceId);
|
|
218
|
+
var to = order.indexOf(targetId);
|
|
219
|
+
if (from === -1 || to === -1) return;
|
|
220
|
+
order.splice(from, 1);
|
|
221
|
+
order.splice(to, 0, sourceId);
|
|
222
|
+
// Optimistic: reorder local items immediately for snappy UX.
|
|
223
|
+
var byId = {};
|
|
224
|
+
items.forEach(function (i) { byId[i.id] = i; });
|
|
225
|
+
items = order.map(function (id) { return byId[id]; });
|
|
226
|
+
render();
|
|
227
|
+
api("POST", "/api/todos/reorder", { ids: order })
|
|
228
|
+
.then(function (r) { applyList(r.list); })
|
|
229
|
+
.catch(function (e) { toast(e.message, "error"); load(); });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function applyList(list) {
|
|
233
|
+
if (list && Array.isArray(list.items)) {
|
|
234
|
+
items = list.items;
|
|
235
|
+
render();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── Add ───────────────────────────────────────────────────────────────────
|
|
240
|
+
form.addEventListener("submit", function (e) { e.preventDefault(); submitAdd("bottom"); });
|
|
241
|
+
input.addEventListener("keydown", function (e) {
|
|
242
|
+
if (e.key === "Enter" && e.shiftKey) { e.preventDefault(); submitAdd("top"); }
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
function submitAdd(position) {
|
|
246
|
+
var text = input.value.trim();
|
|
247
|
+
if (!text) return;
|
|
248
|
+
addBtn.disabled = true;
|
|
249
|
+
api("POST", "/api/todos", { text: text, position: position })
|
|
250
|
+
.then(function (r) { input.value = ""; applyList(r.list); })
|
|
251
|
+
.catch(function (e) { toast(e.message, "error"); })
|
|
252
|
+
.then(function () { addBtn.disabled = false; input.focus(); });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (hideDoneEl) hideDoneEl.addEventListener("change", render);
|
|
256
|
+
if (clearDoneBtn) {
|
|
257
|
+
clearDoneBtn.addEventListener("click", function () {
|
|
258
|
+
api("POST", "/api/todos/clear-done")
|
|
259
|
+
.then(function (r) { applyList(r.list); toast("Cleared " + r.removed + " completed", "success"); })
|
|
260
|
+
.catch(function (e) { toast(e.message, "error"); });
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── Load + live updates ─────────────────────────────────────────────────────
|
|
265
|
+
function load() {
|
|
266
|
+
api("GET", "/api/todos")
|
|
267
|
+
.then(function (r) { items = (r.list && r.list.items) || []; render(); })
|
|
268
|
+
.catch(function (e) { toast(e.message, "error"); });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function connectStream() {
|
|
272
|
+
try {
|
|
273
|
+
var es = new EventSource("/api/todos/stream");
|
|
274
|
+
es.addEventListener("todos:update", function (ev) {
|
|
275
|
+
try {
|
|
276
|
+
var data = JSON.parse(ev.data);
|
|
277
|
+
if (data && data.list) applyList(data.list);
|
|
278
|
+
} catch (e) {}
|
|
279
|
+
});
|
|
280
|
+
es.onerror = function () { /* browser auto-reconnects */ };
|
|
281
|
+
} catch (e) { /* SSE unsupported — panel still works via manual actions */ }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Restore persisted open state (default closed). Silent: no focus steal.
|
|
285
|
+
var saved = "0";
|
|
286
|
+
try { saved = localStorage.getItem(OPEN_KEY) || "0"; } catch (e) {}
|
|
287
|
+
setOpen(saved === "1", true);
|
|
288
|
+
|
|
289
|
+
load();
|
|
290
|
+
connectStream();
|
|
291
|
+
})();
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Claude Plans — embedded TODO side panel.
|
|
3
|
+
A right-anchored drawer you can open/close from the header. Inner list rows
|
|
4
|
+
reuse the .td-* classes from todos.css; this file owns only the drawer shell,
|
|
5
|
+
the header toggle button, and the open/close animation. Tokens: tokens.css.
|
|
6
|
+
============================================================================ */
|
|
7
|
+
|
|
8
|
+
/* ── Header toggle button ─────────────────────────────────────────────────── */
|
|
9
|
+
.cp-todo-toggle {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 6px;
|
|
13
|
+
background: transparent;
|
|
14
|
+
border: none;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
font: inherit;
|
|
17
|
+
}
|
|
18
|
+
.cp-todo-toggle[aria-expanded="true"] {
|
|
19
|
+
color: var(--brand);
|
|
20
|
+
}
|
|
21
|
+
.cp-todo-badge {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
min-width: 18px;
|
|
26
|
+
height: 18px;
|
|
27
|
+
padding: 0 5px;
|
|
28
|
+
font-family: var(--font-mono);
|
|
29
|
+
font-size: 11px;
|
|
30
|
+
font-weight: var(--weight-semibold);
|
|
31
|
+
line-height: 1;
|
|
32
|
+
color: var(--accent-fg);
|
|
33
|
+
background: var(--accent);
|
|
34
|
+
border-radius: 9px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── Drawer shell ─────────────────────────────────────────────────────────── */
|
|
38
|
+
.cp-todo-drawer {
|
|
39
|
+
/* Anchored below the 64px sticky header (z-index 50) and kept beneath it
|
|
40
|
+
(z-index 40) so the header's nav, theme toggle, and TODO button stay
|
|
41
|
+
clickable while the drawer is open. */
|
|
42
|
+
position: fixed;
|
|
43
|
+
top: 64px;
|
|
44
|
+
right: 0;
|
|
45
|
+
height: calc(100vh - 64px);
|
|
46
|
+
width: min(400px, 92vw);
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
background: var(--surface-raised);
|
|
50
|
+
border-left: 1px solid var(--border);
|
|
51
|
+
box-shadow: var(--shadow-3);
|
|
52
|
+
transform: translateX(100%);
|
|
53
|
+
transition: transform var(--dur-2) var(--ease-out);
|
|
54
|
+
z-index: 40;
|
|
55
|
+
will-change: transform;
|
|
56
|
+
}
|
|
57
|
+
.cp-todo-drawer.cp-todo-open {
|
|
58
|
+
transform: translateX(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.cp-todo-drawer-header {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--space-3);
|
|
65
|
+
padding: var(--space-4) var(--space-5);
|
|
66
|
+
border-bottom: 1px solid var(--border);
|
|
67
|
+
flex: 0 0 auto;
|
|
68
|
+
}
|
|
69
|
+
.cp-todo-drawer-title {
|
|
70
|
+
font-weight: var(--weight-semibold);
|
|
71
|
+
font-size: 15px;
|
|
72
|
+
color: var(--fg);
|
|
73
|
+
}
|
|
74
|
+
.cp-todo-drawer-header .td-summary {
|
|
75
|
+
flex: 1;
|
|
76
|
+
}
|
|
77
|
+
.cp-todo-close {
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
background: transparent;
|
|
80
|
+
border: none;
|
|
81
|
+
color: var(--fg-subtle);
|
|
82
|
+
font-size: 22px;
|
|
83
|
+
line-height: 1;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
padding: 2px 8px;
|
|
86
|
+
border-radius: var(--radius-sm);
|
|
87
|
+
transition: color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
|
|
88
|
+
}
|
|
89
|
+
.cp-todo-close:hover {
|
|
90
|
+
color: var(--fg);
|
|
91
|
+
background: var(--surface);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.cp-todo-drawer-body {
|
|
95
|
+
flex: 1 1 auto;
|
|
96
|
+
overflow-y: auto;
|
|
97
|
+
padding: var(--space-5);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Inner list reuses todos.css (.td-*). Pull the trailing hint in a touch. */
|
|
101
|
+
.cp-todo-drawer-body .td-hint {
|
|
102
|
+
margin-top: var(--space-5);
|
|
103
|
+
}
|