@nanocollective/roster 0.1.0-alpha.29 → 0.1.0-alpha.30
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/dist/cli.js
CHANGED
|
@@ -37,6 +37,8 @@ function httpStatus(stderr) {
|
|
|
37
37
|
return m ? Number(m[1]) : void 0;
|
|
38
38
|
}
|
|
39
39
|
function tidy(stderr) {
|
|
40
|
+
if (/rate limit/i.test(stderr))
|
|
41
|
+
return "GitHub's API limit for this hour is used up. It resets within the hour.";
|
|
40
42
|
const msg = /"message"\s*:\s*"([^"]+)"/.exec(stderr);
|
|
41
43
|
if (msg) return msg[1];
|
|
42
44
|
const first = stderr.split("\n").map((l) => l.trim()).filter(Boolean)[0] ?? "unknown error";
|
|
@@ -4255,6 +4257,7 @@ function buildExport(ws, org, parseYaml, opts = {}) {
|
|
|
4255
4257
|
statusIssue: manifest.status_issue,
|
|
4256
4258
|
schedule: entry.schedule ?? manifest.schedule,
|
|
4257
4259
|
mention: manifest.mention,
|
|
4260
|
+
icon: manifest.icon ? String(manifest.icon) : void 0,
|
|
4258
4261
|
bots: [manifest.bot, manifest.public_bot].filter(Boolean).map(botName),
|
|
4259
4262
|
soloBots: [],
|
|
4260
4263
|
sharedBots: [],
|
|
@@ -5645,6 +5648,9 @@ async function fetchInbox(repos) {
|
|
|
5645
5648
|
}
|
|
5646
5649
|
})
|
|
5647
5650
|
);
|
|
5651
|
+
const reasons = errors.map((e) => e.slice(e.indexOf(": ") + 2));
|
|
5652
|
+
if (errors.length > 1 && reasons.every((r) => r === reasons[0]))
|
|
5653
|
+
errors.splice(0, errors.length, reasons[0]);
|
|
5648
5654
|
items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
5649
5655
|
return { items, errors };
|
|
5650
5656
|
}
|
|
@@ -5802,6 +5808,8 @@ function rollup(state) {
|
|
|
5802
5808
|
return "pending";
|
|
5803
5809
|
}
|
|
5804
5810
|
function short4(e) {
|
|
5811
|
+
const stderr = e?.stderr;
|
|
5812
|
+
if (stderr) return tidy(String(stderr));
|
|
5805
5813
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5806
5814
|
const line = msg.split("\n").find((l) => l.trim() && !/^\s*$/.test(l)) ?? msg;
|
|
5807
5815
|
return line.length > 200 ? line.slice(0, 199) + "\u2026" : line;
|
package/docs/export.md
CHANGED
|
@@ -35,6 +35,7 @@ that reads it gets the same view without reimplementing the memory grammar.
|
|
|
35
35
|
| `statusIssue` | pinned issue number |
|
|
36
36
|
| `schedule` | cron |
|
|
37
37
|
| `mention` | what wakes them |
|
|
38
|
+
| `icon` | the icon set by `icon:` in staff.yaml, if any |
|
|
38
39
|
| `bots[]` | every App login, `[bot]` suffix stripped |
|
|
39
40
|
| `soloBots[]` | identities unique to this staff member |
|
|
40
41
|
| `sharedBots[]` | identities shared with others |
|
package/docs/staff-yaml.md
CHANGED
|
@@ -59,6 +59,7 @@ labels:
|
|
|
59
59
|
| `handle` | yes | Must match `org.yaml`. The composer looks them up by the `org.yaml` one, so a mismatch composes the wrong brain. |
|
|
60
60
|
| `name` | yes | Role name in prose. |
|
|
61
61
|
| `mention` | yes | What wakes them, as in `@cto`. The caller's condition tests for this string. |
|
|
62
|
+
| `icon` | no | The icon the portal shows for them: `code`, `megaphone`, `life-buoy`, `compass`, `brush`, `bug`, `server`, `book-open`, `bar-chart`, `users` or `person`. Unset, it is picked from the role. |
|
|
62
63
|
| `brain` | yes | `owner/name` of this repo. Without it nothing can check secrets, labels or runs. |
|
|
63
64
|
| `status_issue` | yes in practice | Number of the pinned status issue. The prompts reference it, so a run cannot compose without it. `roster hire --apply` writes it. |
|
|
64
65
|
|
package/package.json
CHANGED
|
@@ -157,3 +157,15 @@ main{padding:36px 44px 96px;max-width:1220px;overflow-x:hidden}
|
|
|
157
157
|
font:600 14px var(--sans);cursor:pointer}
|
|
158
158
|
.hirecard:hover{color:var(--ink);border-color:var(--accent)}
|
|
159
159
|
.hirecard::before{content:"+";font-size:22px;line-height:1}
|
|
160
|
+
|
|
161
|
+
/* The inbox toolbar: one height, one line. */
|
|
162
|
+
.inboxbar{align-items:center}
|
|
163
|
+
.inboxbar > *{height:34px;box-sizing:border-box}
|
|
164
|
+
.inboxbar .modes{margin:0;padding:3px;align-items:stretch}
|
|
165
|
+
.inboxbar .mode{display:flex;align-items:center}
|
|
166
|
+
.inboxbar .iconbtn{width:34px;display:grid;place-items:center}
|
|
167
|
+
.inboxbar .ghbtn{display:flex;align-items:center}
|
|
168
|
+
/* Staff and issue icons in the sidebar. */
|
|
169
|
+
.nav.inboxlink{display:flex;align-items:center;gap:8px}
|
|
170
|
+
.nav.inboxlink .ic,.staffrow .ic{--ic:14px;color:var(--ink-faint);flex:none}
|
|
171
|
+
.nav.inboxlink[aria-current="true"] .ic{color:var(--accent)}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { getOrg } from "./api.js";
|
|
4
4
|
import { $, el, store } from "./dom.js";
|
|
5
|
-
import { icon, iconHTML } from "./icons.js";
|
|
5
|
+
import { icon, iconHTML, staffIcon } from "./icons.js";
|
|
6
6
|
import { installLightbox } from "./lightbox.js";
|
|
7
7
|
import { setPeople } from "./md.js";
|
|
8
8
|
import {
|
|
@@ -268,6 +268,7 @@ function paintSidebar() {
|
|
|
268
268
|
const dot = el("span", { className: "readydot", hidden: true });
|
|
269
269
|
head.append(
|
|
270
270
|
icon("chevron", "caret"),
|
|
271
|
+
icon(staffIcon(s), "ic"),
|
|
271
272
|
el("span", { className: "t", textContent: s.name }),
|
|
272
273
|
dot,
|
|
273
274
|
el("span", { className: "hh", textContent: s.handle }),
|
|
@@ -322,20 +323,22 @@ function paintInboxLinks() {
|
|
|
322
323
|
const sub = $("#inboxsub");
|
|
323
324
|
if (!sub) return;
|
|
324
325
|
sub.replaceChildren();
|
|
326
|
+
// Inbox itself is everything. Under it: each staff member, then the product repos' issues.
|
|
325
327
|
const links = [
|
|
326
|
-
[
|
|
327
|
-
["
|
|
328
|
-
["decision", "", "Decisions"],
|
|
329
|
-
...S.data.staff.map((s) => ["", s.handle, s.name]),
|
|
328
|
+
...S.data.staff.map((s) => [s.handle, s.name, staffIcon(s)]),
|
|
329
|
+
["@issues", "Issues", "issue-open"],
|
|
330
330
|
];
|
|
331
|
-
for (const [
|
|
332
|
-
const b = el("button", { className: "nav subnav inboxlink",
|
|
333
|
-
|
|
331
|
+
for (const [who, label, glyph] of links) {
|
|
332
|
+
const b = el("button", { className: "nav subnav inboxlink" }, [
|
|
333
|
+
icon(glyph, "ic"),
|
|
334
|
+
el("span", { className: "t", textContent: label }),
|
|
335
|
+
]);
|
|
334
336
|
b.dataset.who = who;
|
|
335
337
|
b.onclick = () => {
|
|
336
338
|
S.view = "inbox";
|
|
337
|
-
|
|
338
|
-
S.inboxStaff = who;
|
|
339
|
+
// A second click on the one you are on goes back to everything.
|
|
340
|
+
S.inboxStaff = S.inboxStaff === who ? "" : who;
|
|
341
|
+
S.inboxFilter = "";
|
|
339
342
|
S.inboxOpen = null;
|
|
340
343
|
render();
|
|
341
344
|
};
|
|
@@ -347,10 +350,7 @@ function markSidebar() {
|
|
|
347
350
|
const sub = $("#inboxsub");
|
|
348
351
|
if (sub) sub.hidden = S.view !== "inbox";
|
|
349
352
|
for (const b of document.querySelectorAll(".inboxlink")) {
|
|
350
|
-
const on =
|
|
351
|
-
S.view === "inbox" &&
|
|
352
|
-
b.dataset.filter === (S.inboxFilter || "") &&
|
|
353
|
-
b.dataset.who === (S.inboxStaff || "");
|
|
353
|
+
const on = S.view === "inbox" && b.dataset.who === (S.inboxStaff || "");
|
|
354
354
|
b.setAttribute("aria-current", String(on));
|
|
355
355
|
}
|
|
356
356
|
for (const b of document.querySelectorAll(".nav")) {
|
|
@@ -13,6 +13,26 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const PATHS = {
|
|
16
|
+
"code":
|
|
17
|
+
"<polyline points=\"16 18 22 12 16 6\" /> <polyline points=\"8 6 2 12 8 18\" />",
|
|
18
|
+
"megaphone":
|
|
19
|
+
"<path d=\"m3 11 18-5v12L3 14v-3z\" /> <path d=\"M11.6 16.8a3 3 0 1 1-5.8-1.6\" />",
|
|
20
|
+
"life-buoy":
|
|
21
|
+
"<circle cx=\"12\" cy=\"12\" r=\"10\" /> <path d=\"m4.93 4.93 4.24 4.24\" /> <path d=\"m14.83 9.17 4.24-4.24\" /> <path d=\"m14.83 14.83 4.24 4.24\" /> <path d=\"m9.17 14.83-4.24 4.24\" /> <circle cx=\"12\" cy=\"12\" r=\"4\" />",
|
|
22
|
+
"compass":
|
|
23
|
+
"<circle cx=\"12\" cy=\"12\" r=\"10\" /> <polygon points=\"16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76\" />",
|
|
24
|
+
"brush":
|
|
25
|
+
"<path d=\"m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08\" /> <path d=\"M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z\" />",
|
|
26
|
+
"bug":
|
|
27
|
+
"<path d=\"m8 2 1.88 1.88\" /> <path d=\"M14.12 3.88 16 2\" /> <path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\" /> <path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\" /> <path d=\"M12 20v-9\" /> <path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\" /> <path d=\"M6 13H2\" /> <path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\" /> <path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\" /> <path d=\"M22 13h-4\" /> <path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\" />",
|
|
28
|
+
"server":
|
|
29
|
+
"<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\" /> <rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\" /> <line x1=\"6\" x2=\"6.01\" y1=\"6\" y2=\"6\" /> <line x1=\"6\" x2=\"6.01\" y1=\"18\" y2=\"18\" />",
|
|
30
|
+
"book-open":
|
|
31
|
+
"<path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\" /> <path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\" />",
|
|
32
|
+
"bar-chart":
|
|
33
|
+
"<line x1=\"12\" x2=\"12\" y1=\"20\" y2=\"10\" /> <line x1=\"18\" x2=\"18\" y1=\"20\" y2=\"4\" /> <line x1=\"6\" x2=\"6\" y1=\"20\" y2=\"16\" />",
|
|
34
|
+
"users":
|
|
35
|
+
"<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" /> <circle cx=\"9\" cy=\"7\" r=\"4\" /> <path d=\"M22 21v-2a4 4 0 0 0-3-3.87\" /> <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />",
|
|
16
36
|
"check":
|
|
17
37
|
"<path d=\"M20 6 9 17l-5-5\" />",
|
|
18
38
|
"chevron":
|
|
@@ -96,6 +116,23 @@ export function icon(name, cls = "") {
|
|
|
96
116
|
return span.firstChild ?? span;
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
/* A staff member's icon: `icon:` in their staff.yaml, or one picked from their role. */
|
|
120
|
+
const ROLE_ICONS = {
|
|
121
|
+
cto: "code",
|
|
122
|
+
cmo: "megaphone",
|
|
123
|
+
support: "life-buoy",
|
|
124
|
+
pm: "compass",
|
|
125
|
+
designer: "brush",
|
|
126
|
+
qa: "bug",
|
|
127
|
+
devops: "server",
|
|
128
|
+
writer: "book-open",
|
|
129
|
+
analyst: "bar-chart",
|
|
130
|
+
community: "users",
|
|
131
|
+
};
|
|
132
|
+
export function staffIcon(s) {
|
|
133
|
+
return s?.icon && hasIcon(s.icon) ? s.icon : (ROLE_ICONS[s?.handle] ?? "person");
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
export function hasIcon(name) {
|
|
100
137
|
return Object.hasOwn(PATHS, name);
|
|
101
138
|
}
|
|
@@ -150,7 +150,7 @@ function inboxScreen(m, opts) {
|
|
|
150
150
|
};
|
|
151
151
|
controls.push(newBtn);
|
|
152
152
|
}
|
|
153
|
-
m.append(el("div", { className: "row", style: "margin-bottom:16px" }, controls));
|
|
153
|
+
m.append(el("div", { className: "row inboxbar", style: "margin-bottom:16px" }, controls));
|
|
154
154
|
|
|
155
155
|
const split = el("div", { className: "split" });
|
|
156
156
|
const list = el("div", { className: "tree" });
|
|
@@ -232,12 +232,15 @@ function inboxScreen(m, opts) {
|
|
|
232
232
|
|
|
233
233
|
// The sidebar's filters are the inbox's. Pending work has no control for them, so it ignores them.
|
|
234
234
|
const staffPick = opts.prs ? "" : S.inboxStaff;
|
|
235
|
-
|
|
235
|
+
// "Issues" is the product repos: every repo a staff member works in.
|
|
236
|
+
const products = new Set(S.data.staff.flatMap((s) => s.worksIn ?? []));
|
|
237
|
+
const issuesOnly = staffPick === "@issues";
|
|
238
|
+
const whoseStaff = staffPick && !issuesOnly ? S.data.staff.find((s) => s.handle === staffPick) : null;
|
|
236
239
|
/* The two screens partition the org rather than overlap on it. Pending work is every pull
|
|
237
240
|
request; the inbox is everything else. An inbox that also listed the PRs said the same
|
|
238
241
|
thing twice and made the badge beside it a number you could not act on. */
|
|
239
242
|
const mine = S.inbox.items
|
|
240
|
-
.filter((i) => belongsTo(i, whoseStaff))
|
|
243
|
+
.filter((i) => (issuesOnly ? products.has(i.repo) : belongsTo(i, whoseStaff)))
|
|
241
244
|
.filter((i) => (i.kind === "pr") === !!opts.prs);
|
|
242
245
|
const isOpen = (i) => i.state === "OPEN";
|
|
243
246
|
const scoped = mine.filter(
|