superbee 0.0.1 → 0.1.1-pre.2
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/LICENSE +202 -0
- package/NOTICE +14 -0
- package/README.md +94 -7
- package/SKILL.md +453 -0
- package/dist/superbee.mjs +101701 -0
- package/package.json +38 -7
- package/references/recipes/claims/conventions/claim.md +39 -0
- package/references/recipes/claims/recipe.md +34 -0
- package/references/recipes/review-workflow/conventions/review-request.md +44 -0
- package/references/recipes/review-workflow/conventions/view.md +71 -0
- package/references/recipes/review-workflow/recipe.md +20 -0
- package/references/recipes/review-workflow/references/view-authoring-v0.md +324 -0
- package/references/recipes/review-workflow/views/review-workflow/reviews.html +27 -0
- package/references/recipes/review-workflow/views-registry/review-workflow-reviews.md +8 -0
- package/references/sample-bundle/concepts/index.md +4 -0
- package/references/sample-bundle/concepts/link-graph.md +25 -0
- package/references/sample-bundle/concepts/okf-alignment.md +26 -0
- package/references/sample-bundle/context-notes/cycle-okf-lite-vision.md +33 -0
- package/references/sample-bundle/context-notes/index.md +3 -0
- package/references/sample-bundle/index.md +8 -0
- package/references/sample-bundle/log.md +7 -0
- package/references/sample-bundle/references/index.md +3 -0
- package/references/sample-bundle/references/okf-spec.md +27 -0
- package/references/views/about.html +102 -0
- package/references/views/conventions/view.md +71 -0
- package/references/views/pulse.html +439 -0
- package/references/views/references/view-authoring-v0.md +324 -0
- package/references/views/roadmap.html +226 -0
- package/references/views/views-registry/about.md +12 -0
- package/references/views/views-registry/pulse.md +13 -0
- package/references/views/views-registry/roadmap.md +12 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Pulse</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #fafaf8; --panel: #ffffff; --ink: #1a1a1e; --muted: #71717a;
|
|
10
|
+
--line: #e4e4e7; --accent: #6d5ae6; --accent-soft: #ece9fc;
|
|
11
|
+
--glow: #f5f2ff; --chip: #f4f4f5; --ok: #16a34a; --warn: #d97706; --bad: #dc2626;
|
|
12
|
+
--mono: ui-monospace, "SF Mono", Menlo, monospace;
|
|
13
|
+
}
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #101014; --panel: #17171c; --ink: #ececf1; --muted: #8e8e98;
|
|
17
|
+
--line: #26262e; --accent: #9b8cf7; --accent-soft: #262040;
|
|
18
|
+
--glow: #1d1930; --chip: #222228;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; margin: 0; }
|
|
22
|
+
html, body { height: 100%; }
|
|
23
|
+
body {
|
|
24
|
+
font: 14px/1.5 -apple-system, "Segoe UI", system-ui, sans-serif;
|
|
25
|
+
background: var(--bg); color: var(--ink); display: flex; flex-direction: column;
|
|
26
|
+
}
|
|
27
|
+
header {
|
|
28
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 16px;
|
|
29
|
+
border-bottom: 1px solid var(--line); background: var(--panel); flex-wrap: wrap;
|
|
30
|
+
}
|
|
31
|
+
header h1 { font-size: 15px; font-weight: 650; letter-spacing: .01em; }
|
|
32
|
+
header .bundle { color: var(--muted); font-size: 12px; font-family: var(--mono); }
|
|
33
|
+
#search {
|
|
34
|
+
margin-left: auto; background: var(--chip); border: 1px solid var(--line); color: var(--ink);
|
|
35
|
+
border-radius: 8px; padding: 5px 10px; font-size: 13px; width: 200px; outline: none;
|
|
36
|
+
}
|
|
37
|
+
#search:focus { border-color: var(--accent); }
|
|
38
|
+
#follow {
|
|
39
|
+
border: 1px solid var(--line); background: var(--chip); color: var(--muted); border-radius: 999px;
|
|
40
|
+
padding: 5px 12px; font-size: 12px; cursor: pointer; user-select: none; white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
#follow.on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
|
|
43
|
+
#follow.on .dot { animation: blink 1.2s infinite; }
|
|
44
|
+
#follow .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 6px; }
|
|
45
|
+
@keyframes blink { 50% { opacity: .25; } }
|
|
46
|
+
#typebar { display: flex; gap: 6px; padding: 8px 16px; border-bottom: 1px solid var(--line); overflow-x: auto; background: var(--panel); }
|
|
47
|
+
.tchip {
|
|
48
|
+
font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--line);
|
|
49
|
+
background: var(--chip); color: var(--muted); cursor: pointer; white-space: nowrap;
|
|
50
|
+
}
|
|
51
|
+
.tchip.on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); font-weight: 600; }
|
|
52
|
+
main { flex: 1; display: flex; min-height: 0; }
|
|
53
|
+
#feed { flex: 0 0 44%; max-width: 560px; overflow-y: auto; border-right: 1px solid var(--line); padding: 8px 0 40px; }
|
|
54
|
+
.group-label {
|
|
55
|
+
position: sticky; top: 0; z-index: 1; font-size: 11px; font-weight: 700; text-transform: uppercase;
|
|
56
|
+
letter-spacing: .08em; color: var(--muted); background: var(--bg); padding: 10px 16px 4px;
|
|
57
|
+
}
|
|
58
|
+
.item {
|
|
59
|
+
display: block; width: 100%; text-align: left; border: 0; background: none; color: inherit; font: inherit;
|
|
60
|
+
padding: 9px 16px; cursor: pointer; border-left: 3px solid transparent;
|
|
61
|
+
}
|
|
62
|
+
.item:hover { background: var(--glow); }
|
|
63
|
+
.item.sel { background: var(--accent-soft); border-left-color: var(--accent); }
|
|
64
|
+
.item.fresh { animation: land 1.6s ease-out; }
|
|
65
|
+
@keyframes land { 0% { background: var(--accent-soft); transform: translateX(-4px); } 100% { background: none; transform: none; } }
|
|
66
|
+
.item .row1 { display: flex; align-items: baseline; gap: 8px; }
|
|
67
|
+
.item .title { font-weight: 570; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
68
|
+
.item .when { color: var(--muted); font-size: 11.5px; flex: none; }
|
|
69
|
+
.item .row2 { display: flex; align-items: center; gap: 8px; margin-top: 2px; font-size: 12px; color: var(--muted); }
|
|
70
|
+
.item .id { font-family: var(--mono); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
71
|
+
.dotc { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
|
|
72
|
+
.badge { font-size: 10.5px; font-weight: 650; padding: 1px 7px; border-radius: 999px; background: var(--chip); color: var(--muted); flex: none; }
|
|
73
|
+
.badge.st-todo { color: var(--muted); }
|
|
74
|
+
.badge.st-in_progress { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
|
|
75
|
+
.badge.st-blocked, .badge.st-canceled { color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, transparent); }
|
|
76
|
+
.badge.st-done, .badge.st-active { color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, transparent); }
|
|
77
|
+
#detail { flex: 1; overflow-y: auto; padding: 20px 28px 60px; }
|
|
78
|
+
#detail .empty { color: var(--muted); text-align: center; margin-top: 18vh; }
|
|
79
|
+
#detail .empty .big { font-size: 40px; margin-bottom: 12px; }
|
|
80
|
+
#crumbs { display: flex; gap: 4px; align-items: center; font-size: 12px; color: var(--muted); margin-bottom: 10px; flex-wrap: wrap; }
|
|
81
|
+
#crumbs button { border: 0; background: none; color: var(--accent); cursor: pointer; font: inherit; font-family: var(--mono); padding: 0; }
|
|
82
|
+
#crumbs .sep { opacity: .5; }
|
|
83
|
+
.doc h2 { font-size: 20px; margin-bottom: 2px; }
|
|
84
|
+
.doc .meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 8px 0 14px; }
|
|
85
|
+
.kv {
|
|
86
|
+
display: grid; grid-template-columns: max-content 1fr; gap: 3px 14px; font-size: 12.5px;
|
|
87
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 14px; margin-bottom: 16px;
|
|
88
|
+
}
|
|
89
|
+
.kv .k { color: var(--muted); font-family: var(--mono); }
|
|
90
|
+
.kv .v { word-break: break-word; }
|
|
91
|
+
.kv .v.ver { font-family: var(--mono); font-size: 11px; color: var(--muted); }
|
|
92
|
+
.body { max-width: 72ch; }
|
|
93
|
+
.body h1, .body h2, .body h3 { margin: 18px 0 6px; line-height: 1.25; }
|
|
94
|
+
.body h1 { font-size: 18px; } .body h2 { font-size: 16px; } .body h3 { font-size: 14.5px; }
|
|
95
|
+
.body p, .body ul, .body ol { margin: 8px 0; }
|
|
96
|
+
.body li { margin: 3px 0 3px 18px; }
|
|
97
|
+
.body code { font-family: var(--mono); font-size: 12.5px; background: var(--chip); border-radius: 4px; padding: 1px 5px; }
|
|
98
|
+
.body pre { background: var(--chip); border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; overflow-x: auto; margin: 10px 0; }
|
|
99
|
+
.body pre code { background: none; padding: 0; }
|
|
100
|
+
.body a.doclink { color: var(--accent); text-decoration: none; border-bottom: 1px dashed var(--accent); cursor: pointer; }
|
|
101
|
+
.body .extlink { color: var(--muted); border-bottom: 1px dotted var(--muted); cursor: help; }
|
|
102
|
+
.doc .live-note {
|
|
103
|
+
font-size: 12px; color: var(--accent); background: var(--accent-soft); border-radius: 8px;
|
|
104
|
+
padding: 6px 10px; margin-bottom: 12px; display: none;
|
|
105
|
+
}
|
|
106
|
+
footer { padding: 6px 16px; border-top: 1px solid var(--line); background: var(--panel); font-size: 11.5px; color: var(--muted); display: flex; gap: 14px; }
|
|
107
|
+
footer .keys span { font-family: var(--mono); background: var(--chip); border-radius: 4px; padding: 0 5px; margin: 0 2px; }
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<header>
|
|
112
|
+
<h1>⚡ Pulse</h1>
|
|
113
|
+
<span class="bundle" id="bundlename">connecting…</span>
|
|
114
|
+
<input id="search" type="search" placeholder="filter title or id…">
|
|
115
|
+
<button id="follow" title="Auto-open whatever changes"><span class="dot"></span>Follow live</button>
|
|
116
|
+
</header>
|
|
117
|
+
<div id="typebar"></div>
|
|
118
|
+
<main>
|
|
119
|
+
<div id="feed" tabindex="0"></div>
|
|
120
|
+
<div id="detail"><div class="empty"><div class="big">⚡</div>Click an item — or press <b>j</b>/<b>k</b> and <b>Enter</b>.<br>Toggle <b>Follow live</b> to ride along with the fleet.</div></div>
|
|
121
|
+
</main>
|
|
122
|
+
<footer>
|
|
123
|
+
<span class="keys"><span>j</span><span>k</span> move <span>⏎</span> open <span>esc</span> back</span>
|
|
124
|
+
<span id="stat"></span>
|
|
125
|
+
</footer>
|
|
126
|
+
<script>
|
|
127
|
+
(function () {
|
|
128
|
+
var PROTO = "v0", seq = 0, pending = {}, subs = [];
|
|
129
|
+
function send(type, extra) {
|
|
130
|
+
return new Promise(function (resolve, reject) {
|
|
131
|
+
var id = String(++seq);
|
|
132
|
+
pending[id] = { resolve: resolve, reject: reject };
|
|
133
|
+
var msg = { bridge: PROTO, id: id, type: type };
|
|
134
|
+
if (extra) for (var k in extra) msg[k] = extra[k];
|
|
135
|
+
parent.postMessage(msg, "*");
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function openPage(pageId) { parent.postMessage({ bridge: PROTO, type: "open-page", pageId: pageId }, "*"); }
|
|
139
|
+
function watch(refresh) {
|
|
140
|
+
if (typeof refresh !== "function") return Promise.reject(new TypeError("Bridge.watch requires a refresh function"));
|
|
141
|
+
var active = true, ready = false, running = false, queued = [];
|
|
142
|
+
function schedule(initial) {
|
|
143
|
+
running = true;
|
|
144
|
+
var batch = queued.splice(0);
|
|
145
|
+
return Promise.resolve().then(function () { return refresh(batch); }).then(function (value) {
|
|
146
|
+
running = false;
|
|
147
|
+
if (queued.length) void schedule(false);
|
|
148
|
+
return value;
|
|
149
|
+
}, function (err) {
|
|
150
|
+
running = false;
|
|
151
|
+
if (queued.length) void schedule(false);
|
|
152
|
+
if (initial) throw err;
|
|
153
|
+
console.error("Bridge.watch refresh failed", err);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function onChange(event) {
|
|
157
|
+
if (!active) return;
|
|
158
|
+
queued.push(event);
|
|
159
|
+
if (ready && !running) void schedule(false);
|
|
160
|
+
}
|
|
161
|
+
return window.Bridge.subscribe(onChange).then(function () {
|
|
162
|
+
ready = true;
|
|
163
|
+
return schedule(true);
|
|
164
|
+
}, function (err) {
|
|
165
|
+
active = false;
|
|
166
|
+
throw err;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
window.addEventListener("message", function (e) {
|
|
170
|
+
if (e.source !== window.parent) return;
|
|
171
|
+
var m = e.data;
|
|
172
|
+
if (!m || m.bridge !== PROTO) return;
|
|
173
|
+
if (m.type === "change") { subs.forEach(function (cb) { cb(m.event); }); return; }
|
|
174
|
+
var p = pending[m.id];
|
|
175
|
+
if (!p) return;
|
|
176
|
+
delete pending[m.id];
|
|
177
|
+
if (m.type === "error") p.reject(new Error((m.error && m.error.message) || "bridge error"));
|
|
178
|
+
else p.resolve(m.result);
|
|
179
|
+
});
|
|
180
|
+
window.Bridge = {
|
|
181
|
+
hello: function () { return send("hello"); },
|
|
182
|
+
query: function (params) { return send("query", { params: params }); },
|
|
183
|
+
read: function (docId) { return send("read", { docId: docId }); },
|
|
184
|
+
openPage: openPage,
|
|
185
|
+
subscribe: function (cb) { subs.push(cb); return send("subscribe"); },
|
|
186
|
+
watch: watch
|
|
187
|
+
};
|
|
188
|
+
})();
|
|
189
|
+
|
|
190
|
+
(function () {
|
|
191
|
+
"use strict";
|
|
192
|
+
var docs = [], selId = null, typeFilter = null, textFilter = "", follow = false;
|
|
193
|
+
var freshIds = new Set(), crumbs = [];
|
|
194
|
+
var PALETTE = ["#6d5ae6","#0891b2","#d97706","#16a34a","#dc2626","#c026d3","#4f46e5","#0d9488"];
|
|
195
|
+
var typeColor = {};
|
|
196
|
+
function colorFor(t) {
|
|
197
|
+
if (!typeColor[t]) typeColor[t] = PALETTE[Object.keys(typeColor).length % PALETTE.length];
|
|
198
|
+
return typeColor[t];
|
|
199
|
+
}
|
|
200
|
+
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; }); }
|
|
201
|
+
function progress(fm) { return fm.progress_status; }
|
|
202
|
+
function rel(ts) {
|
|
203
|
+
if (!ts) return "";
|
|
204
|
+
var d = Date.now() - new Date(ts).getTime();
|
|
205
|
+
if (isNaN(d)) return "";
|
|
206
|
+
if (d < 45e3) return "just now";
|
|
207
|
+
if (d < 36e5) return Math.round(d / 6e4) + "m ago";
|
|
208
|
+
if (d < 864e5) return Math.round(d / 36e5) + "h ago";
|
|
209
|
+
return Math.round(d / 864e5) + "d ago";
|
|
210
|
+
}
|
|
211
|
+
function groupOf(ts) {
|
|
212
|
+
if (!ts) return "Undated";
|
|
213
|
+
var d = Date.now() - new Date(ts).getTime();
|
|
214
|
+
if (d < 36e5) return "Last hour";
|
|
215
|
+
if (d < 864e5) return "Last 24 hours";
|
|
216
|
+
if (d < 7 * 864e5) return "This week";
|
|
217
|
+
return "Earlier";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- tiny markdown (headings, lists, fences, bold/italic/code, links) ----
|
|
221
|
+
function inline(s) {
|
|
222
|
+
s = esc(s);
|
|
223
|
+
s = s.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
224
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
|
|
225
|
+
s = s.replace(/(^|\W)\*([^*\s][^*]*)\*/g, "$1<i>$2</i>");
|
|
226
|
+
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, function (_m, text, href) {
|
|
227
|
+
if (/^https?:\/\//.test(href)) return '<span class="extlink" title="external: ' + esc(href) + ' (links out are disabled inside a sandboxed view)">' + text + ' ↗</span>';
|
|
228
|
+
return '<a class="doclink" data-href="' + esc(href) + '">' + text + "</a>";
|
|
229
|
+
});
|
|
230
|
+
return s;
|
|
231
|
+
}
|
|
232
|
+
function md(src, baseDir) {
|
|
233
|
+
var out = [], lines = String(src || "").split("\n"), inFence = false, fenceBuf = [], listBuf = [];
|
|
234
|
+
function flushList() { if (listBuf.length) { out.push("<ul>" + listBuf.map(function (li) { return "<li>" + inline(li) + "</li>"; }).join("") + "</ul>"); listBuf = []; } }
|
|
235
|
+
for (var i = 0; i < lines.length; i++) {
|
|
236
|
+
var L = lines[i];
|
|
237
|
+
if (/^```/.test(L)) { if (inFence) { out.push("<pre><code>" + esc(fenceBuf.join("\n")) + "</code></pre>"); fenceBuf = []; } inFence = !inFence; flushList(); continue; }
|
|
238
|
+
if (inFence) { fenceBuf.push(L); continue; }
|
|
239
|
+
var h = L.match(/^(#{1,3})\s+(.*)/);
|
|
240
|
+
if (h) { flushList(); out.push("<h" + h[1].length + ">" + inline(h[2]) + "</h" + h[1].length + ">"); continue; }
|
|
241
|
+
var li = L.match(/^\s*[-*]\s+(.*)/);
|
|
242
|
+
if (li) { listBuf.push(li[1]); continue; }
|
|
243
|
+
flushList();
|
|
244
|
+
if (L.trim() === "") continue;
|
|
245
|
+
out.push("<p>" + inline(L) + "</p>");
|
|
246
|
+
}
|
|
247
|
+
flushList();
|
|
248
|
+
if (inFence && fenceBuf.length) out.push("<pre><code>" + esc(fenceBuf.join("\n")) + "</code></pre>");
|
|
249
|
+
var div = document.createElement("div");
|
|
250
|
+
div.innerHTML = out.join("");
|
|
251
|
+
div.querySelectorAll("a.doclink").forEach(function (a) {
|
|
252
|
+
a.addEventListener("click", function (ev) {
|
|
253
|
+
ev.preventDefault();
|
|
254
|
+
var id = resolveDocId(a.getAttribute("data-href"), baseDir);
|
|
255
|
+
if (id) openDoc(id, { push: true });
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
return div;
|
|
259
|
+
}
|
|
260
|
+
function resolveDocId(href, baseDir) {
|
|
261
|
+
href = href.replace(/\.md$/i, "").replace(/^\.\//, "");
|
|
262
|
+
var parts;
|
|
263
|
+
if (href.startsWith("/")) parts = href.slice(1).split("/");
|
|
264
|
+
else {
|
|
265
|
+
parts = (baseDir ? baseDir.split("/") : []).filter(Boolean);
|
|
266
|
+
href.split("/").forEach(function (seg) {
|
|
267
|
+
if (seg === "..") parts.pop(); else if (seg !== "." && seg !== "") parts.push(seg);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return parts.join("/") || null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---- feed ----
|
|
274
|
+
var feedEl = document.getElementById("feed"), detailEl = document.getElementById("detail");
|
|
275
|
+
function visible() {
|
|
276
|
+
var rows = docs.slice();
|
|
277
|
+
if (typeFilter) rows = rows.filter(function (r) { return (r.frontmatter.type || "?") === typeFilter; });
|
|
278
|
+
if (textFilter) {
|
|
279
|
+
var q = textFilter.toLowerCase();
|
|
280
|
+
rows = rows.filter(function (r) { return (String(r.frontmatter.title || "") + " " + r.id).toLowerCase().indexOf(q) >= 0; });
|
|
281
|
+
}
|
|
282
|
+
rows.sort(function (a, b) { return String(b.frontmatter.timestamp || "").localeCompare(String(a.frontmatter.timestamp || "")); });
|
|
283
|
+
return rows;
|
|
284
|
+
}
|
|
285
|
+
function renderTypebar() {
|
|
286
|
+
var counts = {};
|
|
287
|
+
docs.forEach(function (r) { var t = r.frontmatter.type || "?"; counts[t] = (counts[t] || 0) + 1; });
|
|
288
|
+
var bar = document.getElementById("typebar");
|
|
289
|
+
bar.innerHTML = "";
|
|
290
|
+
var all = document.createElement("button");
|
|
291
|
+
all.className = "tchip" + (typeFilter ? "" : " on");
|
|
292
|
+
all.textContent = "All · " + docs.length;
|
|
293
|
+
all.onclick = function () { typeFilter = null; renderAll(); };
|
|
294
|
+
bar.appendChild(all);
|
|
295
|
+
Object.keys(counts).sort(function (a, b) { return counts[b] - counts[a]; }).forEach(function (t) {
|
|
296
|
+
var b = document.createElement("button");
|
|
297
|
+
b.className = "tchip" + (typeFilter === t ? " on" : "");
|
|
298
|
+
b.innerHTML = '<span class="dotc" style="background:' + colorFor(t) + ';margin-right:6px"></span>' + esc(t) + " · " + counts[t];
|
|
299
|
+
b.onclick = function () { typeFilter = (typeFilter === t ? null : t); renderAll(); };
|
|
300
|
+
bar.appendChild(b);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function renderFeed() {
|
|
304
|
+
var rows = visible(), lastGroup = null;
|
|
305
|
+
feedEl.innerHTML = "";
|
|
306
|
+
rows.forEach(function (r) {
|
|
307
|
+
var g = groupOf(r.frontmatter.timestamp);
|
|
308
|
+
if (g !== lastGroup) {
|
|
309
|
+
lastGroup = g;
|
|
310
|
+
var lab = document.createElement("div");
|
|
311
|
+
lab.className = "group-label"; lab.textContent = g;
|
|
312
|
+
feedEl.appendChild(lab);
|
|
313
|
+
}
|
|
314
|
+
var t = r.frontmatter.type || "?", st = progress(r.frontmatter);
|
|
315
|
+
var btn = document.createElement("button");
|
|
316
|
+
btn.className = "item" + (r.id === selId ? " sel" : "") + (freshIds.has(r.id) ? " fresh" : "");
|
|
317
|
+
btn.dataset.id = r.id;
|
|
318
|
+
btn.innerHTML =
|
|
319
|
+
'<div class="row1"><span class="dotc" style="background:' + colorFor(t) + '"></span>' +
|
|
320
|
+
'<span class="title">' + esc(r.frontmatter.title || r.id) + '</span>' +
|
|
321
|
+
'<span class="when">' + rel(r.frontmatter.timestamp) + "</span></div>" +
|
|
322
|
+
'<div class="row2"><span class="id">' + esc(r.id) + "</span>" +
|
|
323
|
+
(st ? '<span class="badge st-' + esc(st) + '">' + esc(st) + "</span>" : "") + "</div>";
|
|
324
|
+
btn.onclick = function () { openDoc(r.id, { push: false, reset: true }); };
|
|
325
|
+
feedEl.appendChild(btn);
|
|
326
|
+
});
|
|
327
|
+
document.getElementById("stat").textContent = rows.length + " of " + docs.length + " docs";
|
|
328
|
+
}
|
|
329
|
+
function renderAll() { renderTypebar(); renderFeed(); }
|
|
330
|
+
|
|
331
|
+
// ---- detail ----
|
|
332
|
+
function openDoc(id, opts) {
|
|
333
|
+
opts = opts || {};
|
|
334
|
+
if (opts.reset) crumbs = [];
|
|
335
|
+
if (opts.push && selId && selId !== id) crumbs.push(selId);
|
|
336
|
+
selId = id;
|
|
337
|
+
Bridge.read(id).then(function (doc) {
|
|
338
|
+
var fm = doc.frontmatter || {}, head = docs.find(function (r) { return r.id === id; });
|
|
339
|
+
var baseDir = id.indexOf("/") >= 0 ? id.slice(0, id.lastIndexOf("/")) : "";
|
|
340
|
+
detailEl.innerHTML = "";
|
|
341
|
+
var wrap = document.createElement("div"); wrap.className = "doc";
|
|
342
|
+
if (crumbs.length) {
|
|
343
|
+
var cr = document.createElement("div"); cr.id = "crumbs";
|
|
344
|
+
crumbs.forEach(function (cid) {
|
|
345
|
+
var b = document.createElement("button"); b.textContent = cid;
|
|
346
|
+
b.onclick = function () { var i = crumbs.indexOf(cid); crumbs = crumbs.slice(0, i); selId = null; openDoc(cid, { push: false }); };
|
|
347
|
+
cr.appendChild(b);
|
|
348
|
+
var s = document.createElement("span"); s.className = "sep"; s.textContent = "›"; cr.appendChild(s);
|
|
349
|
+
});
|
|
350
|
+
var here = document.createElement("span"); here.textContent = id; here.style.fontFamily = "var(--mono)";
|
|
351
|
+
cr.appendChild(here); wrap.appendChild(cr);
|
|
352
|
+
}
|
|
353
|
+
var live = document.createElement("div"); live.className = "live-note"; live.id = "livenote";
|
|
354
|
+
live.textContent = "⚡ this doc just changed under you — showing the fresh version";
|
|
355
|
+
wrap.appendChild(live);
|
|
356
|
+
var h = document.createElement("h2"); h.textContent = fm.title || id; wrap.appendChild(h);
|
|
357
|
+
var meta = document.createElement("div"); meta.className = "meta";
|
|
358
|
+
var t = fm.type || "?";
|
|
359
|
+
meta.innerHTML =
|
|
360
|
+
'<span class="badge" style="color:' + colorFor(t) + ';background:color-mix(in srgb,' + colorFor(t) + ' 12%, transparent)">' + esc(t) + "</span>" +
|
|
361
|
+
(progress(fm) ? '<span class="badge st-' + esc(progress(fm)) + '">' + esc(progress(fm)) + "</span>" : "") +
|
|
362
|
+
(fm.actor ? '<span class="badge">' + esc(fm.actor) + "</span>" : "") +
|
|
363
|
+
'<span class="badge">' + (rel(fm.timestamp) || "undated") + "</span>";
|
|
364
|
+
wrap.appendChild(meta);
|
|
365
|
+
var kv = document.createElement("div"); kv.className = "kv";
|
|
366
|
+
Object.keys(fm).sort().forEach(function (k) {
|
|
367
|
+
if (k === "title" || k === "type") return;
|
|
368
|
+
var kEl = document.createElement("span"); kEl.className = "k"; kEl.textContent = k;
|
|
369
|
+
var vEl = document.createElement("span"); vEl.className = "v";
|
|
370
|
+
vEl.textContent = typeof fm[k] === "object" ? JSON.stringify(fm[k]) : String(fm[k]);
|
|
371
|
+
kv.appendChild(kEl); kv.appendChild(vEl);
|
|
372
|
+
});
|
|
373
|
+
if (head) {
|
|
374
|
+
var kEl = document.createElement("span"); kEl.className = "k"; kEl.textContent = "version";
|
|
375
|
+
var vEl = document.createElement("span"); vEl.className = "v ver"; vEl.textContent = String(head.version).slice(0, 24) + "…";
|
|
376
|
+
kv.appendChild(kEl); kv.appendChild(vEl);
|
|
377
|
+
}
|
|
378
|
+
wrap.appendChild(kv);
|
|
379
|
+
var body = document.createElement("div"); body.className = "body";
|
|
380
|
+
body.appendChild(md(doc.body, baseDir));
|
|
381
|
+
wrap.appendChild(body);
|
|
382
|
+
detailEl.appendChild(wrap);
|
|
383
|
+
detailEl.scrollTop = 0;
|
|
384
|
+
renderFeed();
|
|
385
|
+
}).catch(function (err) {
|
|
386
|
+
detailEl.innerHTML = '<div class="empty">could not read <code>' + esc(id) + "</code><br>" + esc(err.message) + "</div>";
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ---- keyboard ----
|
|
391
|
+
document.addEventListener("keydown", function (e) {
|
|
392
|
+
if (e.target.tagName === "INPUT") { if (e.key === "Escape") e.target.blur(); return; }
|
|
393
|
+
var rows = visible().map(function (r) { return r.id; });
|
|
394
|
+
var i = rows.indexOf(selId);
|
|
395
|
+
if (e.key === "j") { var n = rows[Math.min(i + 1, rows.length - 1)] || rows[0]; if (n) openDoc(n, { reset: true }); }
|
|
396
|
+
else if (e.key === "k") { var p = rows[Math.max(i - 1, 0)] || rows[0]; if (p) openDoc(p, { reset: true }); }
|
|
397
|
+
else if (e.key === "Enter" && selId) openDoc(selId, { reset: true });
|
|
398
|
+
else if (e.key === "Escape") {
|
|
399
|
+
if (crumbs.length) { var back = crumbs.pop(); selId = null; openDoc(back, { push: false }); }
|
|
400
|
+
else { selId = null; renderFeed(); detailEl.innerHTML = '<div class="empty"><div class="big">⚡</div>Click an item to inspect it.</div>'; }
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// ---- filters ----
|
|
405
|
+
document.getElementById("search").addEventListener("input", function (e) { textFilter = e.target.value.trim(); renderFeed(); });
|
|
406
|
+
var followBtn = document.getElementById("follow");
|
|
407
|
+
followBtn.onclick = function () { follow = !follow; followBtn.classList.toggle("on", follow); };
|
|
408
|
+
|
|
409
|
+
// ---- boot + live ----
|
|
410
|
+
function refresh(changedIds) {
|
|
411
|
+
return Bridge.query({ limit: 200 }).then(function (res) {
|
|
412
|
+
docs = res.rows || [];
|
|
413
|
+
if (changedIds) { changedIds.forEach(function (id) { freshIds.add(id); }); setTimeout(function () { changedIds.forEach(function (id) { freshIds.delete(id); }); renderFeed(); }, 1800); }
|
|
414
|
+
renderAll();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
Bridge.hello().then(function (info) {
|
|
418
|
+
document.getElementById("bundlename").textContent = (info.bundle && (info.bundle.name || info.bundle.root)) || "";
|
|
419
|
+
}).catch(function (err) { console.error("Bridge.hello failed", err); });
|
|
420
|
+
Bridge.watch(function (events) {
|
|
421
|
+
var changed = [];
|
|
422
|
+
events.forEach(function (event) {
|
|
423
|
+
(event.changes || []).forEach(function (c) { changed.push(c.id); });
|
|
424
|
+
});
|
|
425
|
+
return refresh(changed.length ? changed : null).then(function () {
|
|
426
|
+
if (selId && changed.indexOf(selId) >= 0) {
|
|
427
|
+
openDoc(selId, { push: false });
|
|
428
|
+
setTimeout(function () { var n = document.getElementById("livenote"); if (n) n.style.display = "block"; }, 60);
|
|
429
|
+
}
|
|
430
|
+
if (follow && changed.length) openDoc(changed[0], { reset: true });
|
|
431
|
+
});
|
|
432
|
+
}).catch(function (err) {
|
|
433
|
+
detailEl.innerHTML = '<div class="empty">bridge failed: ' + esc(err.message) + "</div>";
|
|
434
|
+
});
|
|
435
|
+
setInterval(renderFeed, 30000); // keep relative times honest
|
|
436
|
+
})();
|
|
437
|
+
</script>
|
|
438
|
+
</body>
|
|
439
|
+
</html>
|