@nanocollective/roster 0.1.0-alpha.45 → 0.1.0-alpha.46
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/docs/portal.md
CHANGED
|
@@ -96,6 +96,10 @@ Where the portal opens. An Ask box at the top, then these, in order.
|
|
|
96
96
|
|
|
97
97
|
Answers go out as you, with the staff member's `@handle`, so they act on them straight away.
|
|
98
98
|
When nothing is waiting, it says so.
|
|
99
|
+
- **Unread.** Anything on a staff member's own tracker, or elsewhere, with activity you have not
|
|
100
|
+
seen: a peer's ask, a status issue, their own work. Unread comes from your GitHub
|
|
101
|
+
notifications, as on Trackers. Cards and rows anywhere on Home carry the same blue mark, and
|
|
102
|
+
opening one marks it read here and on GitHub.
|
|
99
103
|
- **Working now.** Each staff member: what they are running and what started it (the daily run,
|
|
100
104
|
a follow-on, answering an issue, a peer's ask), for how long, with a link to the log. When
|
|
101
105
|
they are idle, how their last run ended. Peer and follow-on runs today are counted against
|
package/package.json
CHANGED
|
@@ -283,6 +283,10 @@ dialog.sheet{border:1px solid var(--border-strong);border-radius:var(--r-lg);bac
|
|
|
283
283
|
color:var(--ink);padding:0;width:min(860px,calc(100vw - 32px));max-height:calc(100vh - 48px);
|
|
284
284
|
box-shadow:var(--shadow-lg)}
|
|
285
285
|
dialog.sheet[open]{animation:dlgin .18s var(--ease)}
|
|
286
|
+
/* Pinned to the window. The graph's own `.sheet` panel is position:absolute, and that rule
|
|
287
|
+
reached this dialog too, so one opened on a scrolled page sat at the top of the document,
|
|
288
|
+
half off the screen. */
|
|
289
|
+
dialog.sheet{position:fixed}
|
|
286
290
|
dialog.sheet::backdrop{background:rgba(0,0,0,.35);backdrop-filter:blur(8px)}
|
|
287
291
|
.sheetbody{padding:22px 24px 26px;overflow:auto;max-height:calc(100vh - 50px)}
|
|
288
292
|
|
|
@@ -295,6 +299,12 @@ dialog.sheet.side .sheetbody{max-height:100vh;height:100%;box-sizing:border-box;
|
|
|
295
299
|
@media (prefers-reduced-motion:reduce){dialog.sheet.side[open]{animation:none}}
|
|
296
300
|
.sidebar-x{position:sticky;top:0;display:flex;justify-content:flex-end;margin:-6px -6px 4px;z-index:1}
|
|
297
301
|
dialog.sheet.side .viewer{border:0;box-shadow:none;padding:0;min-height:0;background:none}
|
|
302
|
+
/* The title and its actions stay in view while a long thread scrolls under them. The close
|
|
303
|
+
button floats over the header's right edge, so the header leaves room for it. */
|
|
304
|
+
dialog.sheet.side .thead{position:sticky;top:-18px;z-index:1;background:var(--bg);
|
|
305
|
+
padding-top:18px;margin-top:-18px;padding-right:44px}
|
|
306
|
+
dialog.sheet.side .sidebar-x{position:sticky;top:0;height:0;margin:0;z-index:2;overflow:visible}
|
|
307
|
+
dialog.sheet.side .sidebar-x .iconbtn{margin-top:-4px}
|
|
298
308
|
.sheettitle{margin:0 0 14px;font:600 18px var(--sans);letter-spacing:-.02em}
|
|
299
309
|
.sheetloading{display:grid;place-items:center;min-height:260px}
|
|
300
310
|
.spinner{width:22px;height:22px;border-radius:50%;border:2px solid var(--line);border-top-color:var(--accent);
|
|
@@ -67,6 +67,14 @@
|
|
|
67
67
|
.homeclosed .meta{margin-top:3px;line-height:1.5;font-family:var(--sans);font-size:12px}
|
|
68
68
|
.homerest{margin-top:26px}
|
|
69
69
|
|
|
70
|
+
/* Activity you have not seen, as Trackers marks it: a blue edge and a bold title. Opening the
|
|
71
|
+
thread marks it read, here and on GitHub. */
|
|
72
|
+
.homecard.unread{box-shadow:inset 3px 0 0 var(--blue)}
|
|
73
|
+
.homerow.unread{box-shadow:inset 3px 0 0 var(--blue);background:color-mix(in srgb,var(--blue) 6%,transparent)}
|
|
74
|
+
.unread .homeopen{font-weight:700}
|
|
75
|
+
.unread .homeopen::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
|
|
76
|
+
background:var(--blue);margin:0 7px 1px 0;vertical-align:middle}
|
|
77
|
+
|
|
70
78
|
.homereports{display:flex;flex-direction:column;gap:14px;padding:14px 16px}
|
|
71
79
|
.homereport{display:grid;grid-template-columns:190px 1fr;gap:12px;font-size:13px;line-height:1.5}
|
|
72
80
|
.homereport p{margin:0 0 3px;color:var(--ink-dim)}
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* staff a staff member's own work: their status issue, a peer's ask, their own issues
|
|
8
8
|
* elsewhere draft pull requests, and product or ops issues nobody has put on a person
|
|
9
9
|
*
|
|
10
|
+
* `unread` is not a place: it lists what in `staff` and `elsewhere` has activity you have not
|
|
11
|
+
* seen (from your GitHub notifications), since those two are otherwise off Home. Items in the
|
|
12
|
+
* other places carry their own unread mark.
|
|
13
|
+
*
|
|
10
14
|
* Closed items only matter when a staff member closed them today: that is "Closed today", with
|
|
11
15
|
* Reopen beside each, because staff now close things without asking. */
|
|
12
16
|
|
|
@@ -45,7 +49,15 @@ export function sortHome(items, staff, humans, live = [], now = new Date()) {
|
|
|
45
49
|
for (const r of l.running ?? []) if (s && r.issue) working.add(`${s.brain}#${r.issue}`);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
const out = {
|
|
52
|
+
const out = {
|
|
53
|
+
reports: [],
|
|
54
|
+
needs: [],
|
|
55
|
+
requests: [],
|
|
56
|
+
staff: [],
|
|
57
|
+
elsewhere: [],
|
|
58
|
+
unread: [],
|
|
59
|
+
closedToday: [],
|
|
60
|
+
};
|
|
49
61
|
const today = dayOf(now);
|
|
50
62
|
|
|
51
63
|
for (const item of items) {
|
|
@@ -92,6 +104,9 @@ export function sortHome(items, staff, humans, live = [], now = new Date()) {
|
|
|
92
104
|
out.needs.sort(oldest);
|
|
93
105
|
out.requests.sort((a, b) => b.item.updatedAt.localeCompare(a.item.updatedAt));
|
|
94
106
|
out.closedToday.sort((a, b) => b.item.closedAt.localeCompare(a.item.closedAt));
|
|
107
|
+
out.unread = [...out.staff, ...out.elsewhere]
|
|
108
|
+
.filter((e) => e.item.unread)
|
|
109
|
+
.sort((a, b) => b.item.updatedAt.localeCompare(a.item.updatedAt));
|
|
95
110
|
return out;
|
|
96
111
|
}
|
|
97
112
|
|
|
@@ -73,6 +73,11 @@ export function viewHome(m) {
|
|
|
73
73
|
if (h.needs.length) for (const e of h.needs) page.append(needCard(e));
|
|
74
74
|
else page.append(el("p", { className: "homeempty", textContent: "Nothing needs you." }));
|
|
75
75
|
|
|
76
|
+
if (h.unread.length) {
|
|
77
|
+
page.append(section("Unread", h.unread.length));
|
|
78
|
+
for (const e of h.unread) page.append(unreadRow(e));
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
page.append(section("Working now"));
|
|
77
82
|
page.append(working(staff));
|
|
78
83
|
|
|
@@ -147,7 +152,11 @@ function needCard(e) {
|
|
|
147
152
|
const { item, kind } = e;
|
|
148
153
|
/* The whole card opens the thread; the one quick action on it does not. Answering in words
|
|
149
154
|
happens in the thread, where what was asked is in front of you. */
|
|
150
|
-
const card = el("div", {
|
|
155
|
+
const card = el("div", {
|
|
156
|
+
className: "card homecard" + (item.unread ? " unread" : ""),
|
|
157
|
+
tabIndex: 0,
|
|
158
|
+
role: "button",
|
|
159
|
+
});
|
|
151
160
|
card.onclick = (ev) => {
|
|
152
161
|
if (!ev.target.closest("button, a, textarea")) openThread(item);
|
|
153
162
|
};
|
|
@@ -350,6 +359,24 @@ function poll() {
|
|
|
350
359
|
|
|
351
360
|
const STATUS = { waiting: "Waiting", working: "Being worked on", answered: "Answered" };
|
|
352
361
|
|
|
362
|
+
const WHY = {
|
|
363
|
+
status: "Status",
|
|
364
|
+
peer: "Peer ask",
|
|
365
|
+
own: "Their work",
|
|
366
|
+
draft: "Draft",
|
|
367
|
+
contributor: "Issue",
|
|
368
|
+
other: "Issue",
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/** Something on a staff member's tracker, or elsewhere, with activity you have not seen. */
|
|
372
|
+
function unreadRow(e) {
|
|
373
|
+
return clickable(e.item, "homerow", [
|
|
374
|
+
el("span", { className: "chip", textContent: WHY[e.why] ?? "Issue" }),
|
|
375
|
+
openLink(e.item),
|
|
376
|
+
el("span", { className: "meta", textContent: whereOf(e) + " · " + ago(e.item.updatedAt) }),
|
|
377
|
+
]);
|
|
378
|
+
}
|
|
379
|
+
|
|
353
380
|
function requestRow(e) {
|
|
354
381
|
return clickable(e.item, "homerow", [
|
|
355
382
|
el("span", { className: "chip " + (e.status === "answered" ? "cool" : e.status === "working" ? "warm" : ""), textContent: STATUS[e.status] }),
|
|
@@ -360,7 +387,11 @@ function requestRow(e) {
|
|
|
360
387
|
|
|
361
388
|
/** A row that opens its thread from anywhere on it, except a button inside it. */
|
|
362
389
|
function clickable(item, className, kids) {
|
|
363
|
-
const row = el(
|
|
390
|
+
const row = el(
|
|
391
|
+
"div",
|
|
392
|
+
{ className: className + " homeclick" + (item.unread ? " unread" : ""), tabIndex: 0, role: "button" },
|
|
393
|
+
kids,
|
|
394
|
+
);
|
|
364
395
|
row.onclick = (ev) => {
|
|
365
396
|
if (!ev.target.closest("button, a")) openThread(item);
|
|
366
397
|
};
|