@nanocollective/roster 0.1.0-alpha.2 → 0.1.0-alpha.21
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/README.md +70 -84
- package/dist/cli.js +4775 -2495
- package/docs/README.md +19 -11
- package/docs/agents.md +328 -13
- package/docs/architecture.md +13 -5
- package/docs/charters/cmo.md +69 -0
- package/docs/charters/cto.md +71 -0
- package/docs/charters/support.md +60 -0
- package/docs/commands.md +95 -11
- package/docs/concepts.md +64 -12
- package/docs/cost.md +39 -3
- package/docs/developing.md +16 -21
- package/docs/doctor-codes.md +21 -6
- package/docs/export.md +2 -1
- package/docs/extending.md +13 -4
- package/docs/getting-started.md +121 -80
- package/docs/images/brain.jpg +0 -0
- package/docs/images/org.jpg +0 -0
- package/docs/images/prompt.jpg +0 -0
- package/docs/images/setup-org.jpg +0 -0
- package/docs/images/setup-plan.jpg +0 -0
- package/docs/images/staff.jpg +0 -0
- package/docs/manual-steps.md +94 -101
- package/docs/memory.md +29 -8
- package/docs/org-yaml.md +76 -11
- package/docs/portal.md +261 -47
- package/docs/prompts.md +77 -11
- package/docs/security.md +51 -7
- package/docs/session-workflow.md +51 -21
- package/docs/staff-yaml.md +16 -7
- package/docs/troubleshooting.md +23 -20
- package/docs/upgrading.md +9 -3
- package/docs/writing-a-charter.md +33 -17
- package/package.json +1 -1
- package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
- package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
- package/templates/brain/CHARTER.md +3 -3
- package/templates/brain/README.md +1 -0
- package/templates/brain/log/decisions.md +3 -0
- package/templates/brain/staff.yaml +0 -1
- package/templates/brain/strategy/ideas.md +7 -0
- package/templates/ops/.github/workflows/session.yaml +117 -40
- package/templates/ops/agents.mjs +127 -8
- package/templates/ops/compose.mjs +77 -7
- package/templates/ops/inflight.mjs +157 -0
- package/templates/ops/org/operating.md +21 -7
- package/templates/ops/org/voice.md +9 -0
- package/templates/ops/prompts/_identity.md +8 -1
- package/templates/ops/prompts/_inflight.md +14 -0
- package/templates/ops/prompts/_paths.md +2 -1
- package/templates/ops/prompts/daily.md +16 -7
- package/templates/ops/prompts/mention.md +18 -2
- package/templates/ops/run-record.mjs +144 -0
- package/templates/portal/css/base.css +238 -64
- package/templates/portal/css/brain.css +30 -20
- package/templates/portal/css/diff.css +15 -10
- package/templates/portal/css/graph.css +12 -7
- package/templates/portal/css/health.css +32 -11
- package/templates/portal/css/inbox.css +117 -14
- package/templates/portal/css/layout.css +93 -41
- package/templates/portal/css/markdown.css +57 -15
- package/templates/portal/css/runs.css +13 -0
- package/templates/portal/css/setup.css +83 -39
- package/templates/portal/index.html +24 -3
- package/templates/portal/js/api.js +65 -4
- package/templates/portal/js/app.js +112 -12
- package/templates/portal/js/dialog.js +94 -4
- package/templates/portal/js/dom.js +25 -0
- package/templates/portal/js/icons.js +8 -1
- package/templates/portal/js/lightbox.js +273 -0
- package/templates/portal/js/md.js +23 -6
- package/templates/portal/js/mdedit.js +84 -0
- package/templates/portal/js/mention.js +264 -0
- package/templates/portal/js/refresh.js +136 -6
- package/templates/portal/js/state.js +55 -8
- package/templates/portal/js/views/app.js +23 -5
- package/templates/portal/js/views/checklist.js +29 -10
- package/templates/portal/js/views/credential.js +98 -0
- package/templates/portal/js/views/docs.js +94 -4
- package/templates/portal/js/views/files.js +58 -14
- package/templates/portal/js/views/graph.js +1 -1
- package/templates/portal/js/views/health.js +178 -37
- package/templates/portal/js/views/inbox.js +938 -98
- package/templates/portal/js/views/memory.js +16 -1
- package/templates/portal/js/views/org.js +124 -104
- package/templates/portal/js/views/orgedit.js +213 -0
- package/templates/portal/js/views/paste.js +33 -7
- package/templates/portal/js/views/prompt.js +61 -67
- package/templates/portal/js/views/repos.js +20 -15
- package/templates/portal/js/views/runonce.js +94 -0
- package/templates/portal/js/views/runs.js +165 -0
- package/templates/portal/js/views/setup.js +311 -83
- package/templates/portal/js/views/staff.js +139 -22
- package/templates/portal/js/yaml.js +134 -0
- package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
- package/templates/ops/prompts/pr-mention.md +0 -57
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
/* The inbox list, and the thread beside it. */
|
|
2
2
|
|
|
3
|
+
/* One row per item, with a hairline between them. Without it a list of two-line titles is a
|
|
4
|
+
wall of sentences and you have to work out where one item ends — which is most of what made
|
|
5
|
+
the inbox hard to scan. The line is dropped either side of the selected row, whose own
|
|
6
|
+
background is already doing the separating. */
|
|
3
7
|
.irow{display:block;width:100%;text-align:left;background:none;border:0;cursor:pointer;
|
|
4
|
-
padding:
|
|
5
|
-
|
|
8
|
+
padding:11px 12px;border-radius:var(--r);color:var(--ink-dim);
|
|
9
|
+
border-top:1px solid transparent;transition:background .12s var(--ease)}
|
|
10
|
+
.irow + .irow{border-top-color:var(--line)}
|
|
11
|
+
.irow[aria-current="true"],.irow[aria-current="true"] + .irow{border-top-color:transparent}
|
|
12
|
+
.irow:hover{background:var(--hover)}
|
|
13
|
+
.irow:hover + .irow,.irow:hover{border-top-color:transparent}
|
|
6
14
|
.irow:focus{outline:none}
|
|
7
|
-
.irow:focus-visible{
|
|
8
|
-
.irow[aria-current="true"]{background:var(--
|
|
9
|
-
.ititle{font:13.5px/1.4 var(--sans);color:var(--ink);display:-webkit-box;-webkit-line-clamp:2;
|
|
15
|
+
.irow:focus-visible{box-shadow:inset 0 0 0 1px var(--accent-dim)}
|
|
16
|
+
.irow[aria-current="true"]{background:var(--accent-soft)}
|
|
17
|
+
.ititle{font:500 13.5px/1.4 var(--sans);letter-spacing:-.005em;color:var(--ink);display:-webkit-box;-webkit-line-clamp:2;
|
|
10
18
|
-webkit-box-orient:vertical;overflow:hidden}
|
|
11
19
|
.irow[aria-current="true"] .ititle{color:var(--accent)}
|
|
20
|
+
/* The rest of the row follows the title. The project label kept the flat grey it has when
|
|
21
|
+
nothing is selected, which read as a piece of the row that had not got the message. */
|
|
22
|
+
.irow[aria-current="true"] .repo{color:color-mix(in srgb,var(--accent) 72%,var(--ink-dim));
|
|
23
|
+
border-right-color:color-mix(in srgb,var(--accent) 30%,var(--line))}
|
|
24
|
+
.irow[aria-current="true"] .num,.irow[aria-current="true"] .when{
|
|
25
|
+
color:color-mix(in srgb,var(--accent) 45%,var(--ink-faint))}
|
|
12
26
|
.dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--warn);
|
|
13
27
|
margin-right:6px;vertical-align:2px}
|
|
14
28
|
.imeta{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:5px}
|
|
15
|
-
.imeta .repo{font:10px var(--sans);letter-spacing:.
|
|
29
|
+
.imeta .repo{font:600 10px var(--sans);letter-spacing:.06em;text-transform:uppercase;
|
|
16
30
|
color:var(--ink-faint);border-right:1px solid var(--line);padding-right:7px}
|
|
17
31
|
.imeta .num{font:10.5px var(--mono);color:var(--ink-faint)}
|
|
18
32
|
.imeta .when{font:10.5px var(--sans);color:var(--ink-faint);margin-left:auto}
|
|
19
33
|
.ck{display:inline-flex;--ic:13px}
|
|
34
|
+
.cc{display:inline-flex;align-items:center;gap:3px;--ic:12px;font:10.5px var(--mono);
|
|
35
|
+
color:var(--ink-faint)}
|
|
36
|
+
.irow[aria-current="true"] .cc{color:color-mix(in srgb,var(--accent) 45%,var(--ink-faint))}
|
|
20
37
|
/* Closed work reads as finished at a glance, so it can share a list with open work without
|
|
21
38
|
competing with it. */
|
|
22
39
|
.irow.shut .ititle{color:var(--ink-dim)}
|
|
@@ -24,14 +41,99 @@
|
|
|
24
41
|
.ist{display:inline-flex;--ic:12px}
|
|
25
42
|
.ist.closed{color:var(--derived)} .ist.merged{color:var(--derived)}
|
|
26
43
|
.ck.passing{color:var(--accent)} .ck.failing{color:var(--err)} .ck.pending{color:var(--warn)}
|
|
44
|
+
/* Filled rather than outlined like the label chips beside it. A branch that no longer applies
|
|
45
|
+
to its base is the one state where a row can show green checks and still not be mergeable,
|
|
46
|
+
so it has to win the row it is in. */
|
|
47
|
+
.chip.conflict{color:var(--err);border-color:color-mix(in srgb,var(--err) 50%,transparent);
|
|
48
|
+
background:color-mix(in srgb,var(--err) 14%,transparent)}
|
|
27
49
|
|
|
28
50
|
/* ---------- thread ---------- */
|
|
29
|
-
.thead{border-bottom:1px solid var(--line);padding-bottom:
|
|
30
|
-
.
|
|
31
|
-
|
|
51
|
+
.thead{border-bottom:1px solid var(--line);padding-bottom:16px}
|
|
52
|
+
.thead h3{margin:8px 0 0;font:600 19px/1.3 var(--sans);letter-spacing:-.02em;color:var(--ink)}
|
|
53
|
+
/* The same fact as the chip, said in full, once, at the top of the thread it applies to.
|
|
54
|
+
Tight to the title and a clear gap above the buttons: it is a statement about this pull
|
|
55
|
+
request, not a caption on the controls. At an even 10/16 it read as the latter. */
|
|
56
|
+
.tconflict{margin:4px 0 0;font:12.5px/1.5 var(--sans);color:var(--err)}
|
|
57
|
+
/* A bottom margin here would collapse against the row's own 16px and change nothing, so the
|
|
58
|
+
extra room is put on the row. General sibling, not adjacent: labels can sit between them. */
|
|
59
|
+
.tconflict ~ .tacts{margin-top:24px}
|
|
60
|
+
.tlabels{margin-top:10px}
|
|
32
61
|
.cmt{border-top:1px solid var(--line);padding:16px 0}
|
|
33
62
|
.cmt.first{border-top:0}
|
|
34
|
-
.
|
|
63
|
+
/* The header already draws a line under itself. Whatever the reversed thread puts first must
|
|
64
|
+
not draw a second one right under it. */
|
|
65
|
+
.lead{border-top:0}
|
|
66
|
+
/* Reactions. Small, under the comment they belong to, the way GitHub puts them — an agent's
|
|
67
|
+
👀 is an acknowledgement and it should be readable at a glance rather than counted. */
|
|
68
|
+
.reacts{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
|
|
69
|
+
.react{font:11px var(--sans);border:1px solid var(--line);border-radius:999px;padding:2px 9px;
|
|
70
|
+
color:var(--ink-dim);background:var(--panel-2);cursor:default}
|
|
71
|
+
/* Reply, close and merge, beside the title rather than under the last comment. A thread is
|
|
72
|
+
read newest-first and answered from the top, so nothing here is worth scrolling to. */
|
|
73
|
+
.tacts{margin-top:16px;gap:8px}
|
|
74
|
+
.tacts select{min-height:28px;padding:4px 8px;font-size:12px}
|
|
75
|
+
/* The way out of the screen, not one of the things you do on it. */
|
|
76
|
+
.tacts .out{margin-left:auto}
|
|
77
|
+
|
|
78
|
+
/* ---------- pull requests ----------
|
|
79
|
+
Conversation, commits, files. Three views of one thing, so they are tabs on the thread
|
|
80
|
+
rather than three places to navigate to. */
|
|
81
|
+
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);margin:2px 0 0}
|
|
82
|
+
.tab{background:none;border:0;border-bottom:2px solid transparent;cursor:pointer;
|
|
83
|
+
font:500 12.5px var(--sans);color:var(--ink-faint);padding:9px 12px;margin-bottom:-1px;
|
|
84
|
+
transition:color .15s var(--ease),border-color .15s var(--ease)}
|
|
85
|
+
.tab:hover{color:var(--ink)}
|
|
86
|
+
.tab[aria-current="true"]{color:var(--ink);border-bottom-color:var(--accent)}
|
|
87
|
+
.tab:focus-visible{box-shadow:inset 0 0 0 1px var(--accent-dim);border-radius:6px 6px 0 0}
|
|
88
|
+
|
|
89
|
+
.prcommit{display:flex;gap:10px;align-items:baseline;padding:8px 0;
|
|
90
|
+
border-top:1px solid var(--line);font-size:12.5px}
|
|
91
|
+
.prcommit:first-of-type{border-top:0}
|
|
92
|
+
.prcommit .sha{font:11.5px var(--mono);color:var(--ink-faint);flex:none;text-decoration:none}
|
|
93
|
+
.prcommit .sha:hover{color:var(--accent)}
|
|
94
|
+
.prcommit .subj{color:var(--ink);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
|
|
95
|
+
white-space:nowrap}
|
|
96
|
+
.prcommit .meta{flex:none}
|
|
97
|
+
|
|
98
|
+
/* ---------- writing: labels and attachments ----------
|
|
99
|
+
The label set is small and knowable, so it is a row of toggles rather than a text box that
|
|
100
|
+
quietly accepts a typo as a new label. Pressed state carries the same tones the inbox rows
|
|
101
|
+
use, so a `decision` chip is the same colour wherever you meet it. */
|
|
102
|
+
.labelpick{margin-top:12px}
|
|
103
|
+
.labelpick .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
|
104
|
+
.chip.pick{background:none;cursor:pointer;padding:3px 9px;font-size:11px;color:var(--ink-dim)}
|
|
105
|
+
.chip.pick:hover{border-color:var(--ink-faint);color:var(--ink)}
|
|
106
|
+
.chip.pick:focus-visible{box-shadow:0 0 0 3px var(--accent-soft);border-color:var(--accent)}
|
|
107
|
+
.chip.pick[aria-pressed="true"]{background:var(--accent-soft);
|
|
108
|
+
border-color:var(--accent-dim);color:var(--accent)}
|
|
109
|
+
|
|
110
|
+
/* ---------- the @ list ----------
|
|
111
|
+
A mention is the mechanism: a staff member's workflow gates on their handle, so a misspelled
|
|
112
|
+
one is a message nobody is woken by. The roster is small and already on the page, so typing
|
|
113
|
+
`@` offers it — only the entries a mention actually reaches, and each says what it does.
|
|
114
|
+
|
|
115
|
+
The height is capped well under a dialog's, because it is placed inside one and a dialog
|
|
116
|
+
scrolls its own box: anything taller is not long, it is cut off. */
|
|
117
|
+
.mentions{position:absolute;z-index:30;min-width:280px;max-width:min(420px,90%);max-height:196px;
|
|
118
|
+
overflow-y:auto;background:var(--bg-elev);border:1px solid var(--border-strong);border-radius:var(--r);
|
|
119
|
+
padding:4px;box-shadow:var(--shadow-lg)}
|
|
120
|
+
.mrow{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;width:100%;text-align:left;
|
|
121
|
+
background:none;border:0;border-radius:6px;padding:6px 8px;cursor:pointer;font:inherit}
|
|
122
|
+
.mrow:hover{background:var(--hover)}
|
|
123
|
+
.mrow[aria-selected="true"]{background:var(--accent-soft)}
|
|
124
|
+
.mrow .mat{font:12px var(--mono);color:var(--accent)}
|
|
125
|
+
.mrow .mname{font:12.5px var(--sans);color:var(--ink);overflow:hidden;text-overflow:ellipsis;
|
|
126
|
+
white-space:nowrap}
|
|
127
|
+
.mrow .mnote{grid-column:1/-1;font:10.5px var(--sans);color:var(--ink-faint)}
|
|
128
|
+
.mrow.human .mat{color:var(--ruled)}
|
|
129
|
+
/* The measuring copy of a textarea, for finding the caret. Never seen. */
|
|
130
|
+
.mmirror{position:absolute;visibility:hidden;white-space:pre-wrap;word-wrap:break-word;
|
|
131
|
+
pointer-events:none;top:0;left:0}
|
|
132
|
+
|
|
133
|
+
.attach{display:flex;align-items:center;gap:10px;margin-top:9px;flex-wrap:wrap}
|
|
134
|
+
.attach .ghbtn{display:inline-flex;align-items:center;gap:6px;--ic:13px}
|
|
135
|
+
/* The whole textarea is the drop target, because that is where a person aims. */
|
|
136
|
+
textarea.dropping{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 7%,transparent)}
|
|
35
137
|
|
|
36
138
|
/* ---------- timeline events ----------
|
|
37
139
|
Not comments, so not comment-shaped: a marker, a line of prose, and whatever it points at.
|
|
@@ -51,14 +153,15 @@
|
|
|
51
153
|
.tev .twhen{color:var(--ink-faint);font-size:11px;white-space:nowrap}
|
|
52
154
|
.tev.closed .tico{color:var(--err)}
|
|
53
155
|
.tev.reopened .tico,.tev.merged .tico{color:var(--accent)}
|
|
54
|
-
.tev.cross-referenced .tico{color:var(--
|
|
156
|
+
.tev.cross-referenced .tico{color:var(--ruled)}
|
|
55
157
|
|
|
56
158
|
/* An item this thread points at, as a row you can open here rather than a link that throws
|
|
57
159
|
you out to GitHub. Open and closed read at a glance, which is the whole reason to show it. */
|
|
58
160
|
.xref{display:flex;gap:8px;align-items:baseline;width:100%;text-align:left;background:none;
|
|
59
|
-
border:1px solid var(--line);border-radius:
|
|
60
|
-
cursor:pointer;color:var(--ink-dim);font:12.5px var(--sans)
|
|
61
|
-
|
|
161
|
+
border:1px solid var(--line);border-radius:var(--r);padding:7px 11px;margin-top:6px;
|
|
162
|
+
cursor:pointer;color:var(--ink-dim);font:12.5px var(--sans);background:var(--bg-elev);
|
|
163
|
+
transition:border-color .15s var(--ease),background .15s var(--ease),color .15s var(--ease)}
|
|
164
|
+
.xref:hover{border-color:var(--border-strong);background:var(--surface-2);color:var(--ink)}
|
|
62
165
|
.xref .st{flex:none;display:inline-flex;--ic:14px;position:relative;top:2px}
|
|
63
166
|
.xref .st.open{color:var(--accent)} .xref .st.closed{color:var(--derived)}
|
|
64
167
|
.xref .st.merged{color:var(--derived)} .xref .st.draft{color:var(--ink-faint)}
|
|
@@ -1,86 +1,138 @@
|
|
|
1
1
|
/* The page frame: sidebar, main column, and the tree-beside-viewer split every screen uses. */
|
|
2
2
|
|
|
3
|
-
.app{display:grid;grid-template-columns:
|
|
3
|
+
.app{display:grid;grid-template-columns:252px 1fr;min-height:100vh}
|
|
4
4
|
/* ---------- sidebar ---------- */
|
|
5
|
-
.side{background:var(--
|
|
6
|
-
position:sticky;top:0;height:100vh;overflow-y:auto;display:flex;flex-direction:column
|
|
7
|
-
|
|
5
|
+
.side{background:var(--side);border-right:1px solid var(--line);padding:16px 10px 12px;
|
|
6
|
+
position:sticky;top:0;height:100vh;overflow-y:auto;display:flex;flex-direction:column;
|
|
7
|
+
gap:1px}
|
|
8
|
+
.brand{padding:4px 10px 16px;font:600 15px/1.2 var(--sans);letter-spacing:-.02em;
|
|
9
|
+
display:grid;grid-template-columns:auto 1fr;column-gap:10px;align-items:center}
|
|
10
|
+
/* The tab icon, in the corner it belongs in. Drawn rather than fetched so it takes the
|
|
11
|
+
theme's accent and costs no request. */
|
|
12
|
+
.brand .mark{grid-row:1/3;display:inline-block;width:24px;height:24px;border-radius:7px;
|
|
13
|
+
background:var(--accent-fill)}
|
|
8
14
|
.brand small{display:block;color:var(--ink-faint);font-weight:400;font-size:11.5px;
|
|
9
|
-
letter-spacing
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
letter-spacing:0;margin-top:2px;overflow:hidden;text-overflow:ellipsis;
|
|
16
|
+
white-space:nowrap}
|
|
17
|
+
.refreshall{display:flex;align-items:center;gap:8px;width:100%;margin:0 0 12px;
|
|
18
|
+
background:var(--hover);border:1px solid transparent;color:var(--ink-dim);
|
|
19
|
+
border-radius:8px;padding:6px 10px;cursor:pointer;font:11.5px var(--sans);text-align:left;
|
|
20
|
+
transition:color .15s var(--ease),background .15s var(--ease)}
|
|
21
|
+
.refreshall:hover{color:var(--ink);background:var(--active)}
|
|
14
22
|
.refreshall .sync{display:inline-flex;--ic:13px;flex:none}
|
|
15
23
|
.refreshall.spin .sync{animation:spin .9s linear infinite}
|
|
16
24
|
|
|
17
|
-
.sect{padding:
|
|
25
|
+
.sect{padding:16px 10px 6px;font:600 11px var(--sans);letter-spacing:.08em;
|
|
18
26
|
text-transform:uppercase;color:var(--ink-faint)}
|
|
27
|
+
/* The Staff heading is also the fold for the list under it, so it is a button — and has to
|
|
28
|
+
go on reading as a heading rather than as a control. The caret and the left padding
|
|
29
|
+
are the staff rows' own, so the two carets line up in one column. */
|
|
30
|
+
.sectfold{display:flex;align-items:center;gap:7px;width:100%;background:none;border:0;
|
|
31
|
+
border-radius:6px;cursor:pointer;text-align:left;font:600 11px var(--sans);letter-spacing:.08em;
|
|
32
|
+
text-transform:uppercase;color:var(--ink-faint);margin-top:12px;
|
|
33
|
+
transition:color .15s var(--ease)}
|
|
34
|
+
.sectfold:hover{color:var(--ink-dim)}
|
|
35
|
+
.sectfold .caret{display:inline-flex;--ic:12px;flex:none;transition:transform .15s var(--ease)}
|
|
36
|
+
.sectfold[aria-expanded="false"] .caret{transform:rotate(-90deg)}
|
|
37
|
+
/* The count is a number, not a heading: mono, and none of the heading's letterspacing. */
|
|
38
|
+
.sectfold .n{font:10.5px var(--mono);letter-spacing:0;text-transform:none;color:var(--ink-faint);
|
|
39
|
+
font-variant-numeric:tabular-nums}
|
|
19
40
|
/* Flex, not block with a float. The icons are block-level SVGs and the trailing count has to
|
|
20
41
|
sit hard right; `float` does neither of those inside a flex row, and did neither of them
|
|
21
42
|
outside one once the icons arrived. */
|
|
22
|
-
.nav{display:flex;align-items:center;width:100%;text-align:left;background:none;
|
|
23
|
-
color:var(--ink-dim);font:
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
.nav
|
|
27
|
-
.nav
|
|
28
|
-
.nav
|
|
29
|
-
.nav.
|
|
43
|
+
.nav{position:relative;display:flex;align-items:center;width:100%;text-align:left;background:none;
|
|
44
|
+
border:0;border-radius:7px;color:var(--ink-dim);font:500 13.5px var(--sans);padding:6px 10px;
|
|
45
|
+
cursor:pointer;min-height:32px;
|
|
46
|
+
transition:background .15s var(--ease),color .15s var(--ease)}
|
|
47
|
+
.nav:hover{color:var(--ink);background:var(--hover)}
|
|
48
|
+
.nav:focus-visible{box-shadow:0 0 0 2px var(--accent-soft),inset 0 0 0 1px var(--accent-dim)}
|
|
49
|
+
.nav[aria-current="true"]{color:var(--ink);background:var(--sel)}
|
|
50
|
+
.nav .ic{color:var(--ink-faint);margin-right:10px;--ic:15px;flex:none;
|
|
51
|
+
transition:color .15s var(--ease)}
|
|
52
|
+
.nav:hover .ic{color:var(--ink-dim)}
|
|
53
|
+
.nav[aria-current="true"] .ic{color:var(--accent)}
|
|
54
|
+
.nav .n{margin-left:auto;padding-left:10px;color:var(--ink-faint);font:500 10.5px var(--mono);
|
|
55
|
+
font-variant-numeric:tabular-nums}
|
|
56
|
+
.nav .n:not(:empty):not(.wait){padding:1px 7px;border-radius:999px;margin-left:auto;
|
|
57
|
+
background:var(--sel);color:var(--ink-dim);min-width:20px;
|
|
58
|
+
text-align:center}
|
|
59
|
+
.nav .n.wait{margin-left:auto}
|
|
60
|
+
.nav.top{margin-bottom:0}
|
|
30
61
|
|
|
31
62
|
/* Staff are rows that unfold into their own views, rather than a dropdown plus a list you
|
|
32
63
|
have to connect for yourself. The nesting is the hierarchy, said out loud. */
|
|
33
|
-
.staffrow{display:flex;align-items:center;gap:7px;font:
|
|
34
|
-
padding-right:
|
|
35
|
-
.staffrow .caret{color:var(--ink-faint);--ic:12px;flex:none;transition:transform .
|
|
64
|
+
.staffrow{display:flex;align-items:center;gap:7px;font:500 13px var(--sans);color:var(--ink);
|
|
65
|
+
padding-right:10px}
|
|
66
|
+
.staffrow .caret{color:var(--ink-faint);--ic:12px;flex:none;transition:transform .15s var(--ease)}
|
|
36
67
|
.staffrow[aria-expanded="false"] .caret{transform:rotate(-90deg)}
|
|
37
68
|
.staffrow .t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
38
69
|
.staffrow .hh{font:10.5px var(--mono);color:var(--ink-faint);flex:none}
|
|
39
70
|
.staffrow[aria-selected="true"]{color:var(--ink)}
|
|
40
|
-
.staffviews{padding-
|
|
41
|
-
/* `subnav`, not `sub`: `.sub` is the page subtitle and carries a
|
|
71
|
+
.staffviews{position:relative;padding:2px 0 6px;margin-left:12px}
|
|
72
|
+
/* `subnav`, not `sub`: `.sub` is the page subtitle and carries a bottom margin, which
|
|
42
73
|
these rows inherited as a gap you could park a car in. */
|
|
43
|
-
.nav.subnav{padding:
|
|
74
|
+
.nav.subnav{padding:4px 10px 4px 16px;font-size:12.5px;min-height:28px}
|
|
44
75
|
.nav.subnav .n{font-size:10px}
|
|
45
|
-
.sidefoot{margin-top:auto;padding-top:14px}
|
|
76
|
+
.sidefoot{margin-top:auto;padding-top:14px;border-top:1px solid var(--line)}
|
|
46
77
|
.sidefoot .nav{font-size:12.5px;color:var(--ink-faint)}
|
|
47
78
|
.sidefoot .nav:hover{color:var(--ink-dim)}
|
|
48
79
|
|
|
49
80
|
/* ---------- main ---------- */
|
|
50
|
-
main{padding:
|
|
81
|
+
main{padding:36px 44px 96px;max-width:1220px;overflow-x:hidden}
|
|
51
82
|
|
|
52
83
|
/* ---------- tree beside viewer ---------- */
|
|
53
84
|
.split{display:grid;grid-template-columns:314px 1fr;gap:16px;align-items:start}
|
|
54
85
|
@media (max-width:900px){.split{grid-template-columns:1fr}}
|
|
55
|
-
.tree{background:var(--panel);border:1px solid var(--line);border-radius:
|
|
56
|
-
max-height:74vh;overflow:auto}
|
|
57
|
-
.tdir{font:600 11px var(--mono);color:var(--ink-faint);padding:
|
|
58
|
-
.tdir.first{padding-top:
|
|
86
|
+
.tree{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:6px;
|
|
87
|
+
max-height:74vh;overflow:auto;box-shadow:var(--shadow-sm)}
|
|
88
|
+
.tdir{font:600 11px var(--mono);color:var(--ink-faint);padding:9px 8px 4px;letter-spacing:.02em}
|
|
89
|
+
.tdir.first{padding-top:3px}
|
|
59
90
|
.tfile{display:flex;align-items:baseline;gap:10px;width:100%;text-align:left;background:none;
|
|
60
|
-
border:0;color:var(--ink-dim);font:12.5px var(--mono);padding:
|
|
61
|
-
cursor:pointer}
|
|
62
|
-
.tfile:hover{background:var(--
|
|
63
|
-
.tfile[aria-current="true"]{background:var(--
|
|
91
|
+
border:0;color:var(--ink-dim);font:12.5px var(--mono);padding:5px 8px;border-radius:6px;
|
|
92
|
+
cursor:pointer;transition:background .12s var(--ease),color .12s var(--ease)}
|
|
93
|
+
.tfile:hover{background:var(--hover);color:var(--ink)}
|
|
94
|
+
.tfile[aria-current="true"]{background:var(--accent-soft);color:var(--accent)}
|
|
64
95
|
.tfile span.t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
65
96
|
.tfile i{color:var(--ink-faint);font-style:normal;font-size:10.5px;flex:none;
|
|
66
97
|
font-variant-numeric:tabular-nums}
|
|
67
98
|
.tfile:focus{outline:none}
|
|
68
|
-
.tfile:focus-visible{
|
|
99
|
+
.tfile:focus-visible{box-shadow:inset 0 0 0 1px var(--accent-dim)}
|
|
100
|
+
|
|
101
|
+
/* A search result: the page, and the lines the words were actually found in. Without the lines
|
|
102
|
+
this is a filter on titles, which is the thing that was not good enough. */
|
|
103
|
+
.tfile.thit{align-items:flex-start;padding:8px}
|
|
104
|
+
.tfile.thit .t{white-space:normal;overflow:visible}
|
|
105
|
+
.thit .hitname{display:block;font:13px var(--sans);color:var(--ink)}
|
|
106
|
+
.thit[aria-current="true"] .hitname{color:var(--accent)}
|
|
107
|
+
.thit .hitline{display:block;font:11.5px/1.5 var(--sans);color:var(--ink-faint);margin-top:3px;
|
|
108
|
+
overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
|
109
|
+
.thit .hitline em{font-style:normal;color:var(--ink);background:color-mix(in srgb,var(--accent) 22%,transparent);
|
|
110
|
+
border-radius:3px;padding:0 2px}
|
|
69
111
|
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
/* No height cap: a file grows the page, so there is one scrollbar, not one inside another. */
|
|
113
|
+
.viewer{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:22px 26px;
|
|
114
|
+
min-height:340px;min-width:0;box-shadow:var(--shadow-sm)}
|
|
72
115
|
.viewer pre{margin:0;white-space:pre-wrap;word-break:break-word;font:12.5px/1.65 var(--mono);color:var(--ink-dim)}
|
|
73
116
|
/* Source keeps its own line breaks and scrolls, rather than wrapping into soup. */
|
|
74
117
|
.viewer pre.code{white-space:pre;word-break:normal;overflow-x:auto}
|
|
75
118
|
.viewer img{max-width:100%;border-radius:6px;background:#fff}
|
|
119
|
+
/* A recording in a brain gets the same room a screenshot does. Black rather than white
|
|
120
|
+
behind it: letterboxing should read as the video's own edges, not as a broken image. */
|
|
121
|
+
.viewer video.player,.viewer audio.player{max-width:100%;width:100%;border-radius:6px;background:#000}
|
|
122
|
+
.viewer audio.player{background:none}
|
|
76
123
|
.viewer table{border-collapse:collapse;font:12px var(--mono);width:100%}
|
|
77
124
|
.viewer td,.viewer th{border:1px solid var(--line);padding:4px 8px;text-align:left;white-space:nowrap}
|
|
78
|
-
.viewer th{position:sticky;top:0;background:var(--panel-2);color:var(--ink)}
|
|
125
|
+
.viewer th{position:sticky;top:0;background:var(--panel-2);color:var(--ink);font-weight:600}
|
|
79
126
|
|
|
80
127
|
.gallery{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
|
|
81
|
-
.gallery figure{margin:0}
|
|
82
|
-
.gallery img{width:100%;aspect-ratio:4/3;object-fit:contain;background:var(--panel-2);
|
|
83
|
-
border:1px solid var(--line);cursor:pointer
|
|
128
|
+
.gallery figure{margin:0;position:relative}
|
|
129
|
+
.gallery img,.gallery video{width:100%;aspect-ratio:4/3;object-fit:contain;background:var(--panel-2);
|
|
130
|
+
border-radius:var(--r);border:1px solid var(--line);cursor:pointer;display:block;
|
|
131
|
+
transition:border-color .15s var(--ease),transform .15s var(--ease)}
|
|
132
|
+
.gallery img:hover,.gallery video:hover{border-color:var(--border-strong)}
|
|
133
|
+
/* A still frame and a photograph look identical in a grid, so the video says which it is. */
|
|
134
|
+
.playmark{position:absolute;top:8px;right:8px;display:inline-flex;padding:4px;border-radius:50%;
|
|
135
|
+
background:color-mix(in srgb,#000 55%,transparent);color:#fff;--ic:12px;pointer-events:none}
|
|
84
136
|
.gallery figcaption{font:10.5px var(--mono);color:var(--ink-faint);margin-top:4px;
|
|
85
137
|
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
86
138
|
|
|
@@ -1,46 +1,62 @@
|
|
|
1
1
|
/* One renderer, two sizes: .cbody is a comment in a thread, .doc is a file being read.
|
|
2
2
|
Everything shared lives on .md so a fix reaches both. */
|
|
3
3
|
|
|
4
|
-
.md{line-height:1.
|
|
4
|
+
.md{line-height:1.7}
|
|
5
5
|
.md>:first-child{margin-top:0}
|
|
6
6
|
.md p{margin:0 0 10px}
|
|
7
7
|
.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{color:var(--ink);font-weight:600;line-height:1.3;
|
|
8
|
-
margin:
|
|
9
|
-
.md h1{font-size:
|
|
8
|
+
margin:26px 0 10px;letter-spacing:-.02em}
|
|
9
|
+
.md h1{font-size:22px} .md h2{font-size:17.5px} .md h3{font-size:15px}
|
|
10
10
|
.md h4,.md h5,.md h6{font-size:13.5px;color:var(--ink-dim)}
|
|
11
|
-
.md h1,.md h2{padding-bottom:
|
|
11
|
+
.md h1,.md h2{padding-bottom:8px;border-bottom:1px solid var(--line)}
|
|
12
12
|
.md ul,.md ol{margin:0 0 10px;padding-left:20px}
|
|
13
13
|
.md li{margin:3px 0}
|
|
14
14
|
.md li>ul,.md li>ol{margin:4px 0 0}
|
|
15
15
|
.md ul.tasks{list-style:none;padding-left:1px}
|
|
16
16
|
.md .box{color:var(--ink-faint);margin-right:6px;display:inline-flex;--ic:14px;
|
|
17
17
|
vertical-align:-3px}
|
|
18
|
-
.md code{font:12px var(--mono);color:var(--
|
|
19
|
-
padding:1px
|
|
20
|
-
.md pre{background:var(--
|
|
21
|
-
padding:
|
|
18
|
+
.md code{font:12px var(--mono);color:var(--ink);background:color-mix(in srgb,var(--ink) 7%,transparent);
|
|
19
|
+
padding:1px 5px;border-radius:5px;word-break:break-word}
|
|
20
|
+
.md pre{background:var(--surface-2);border:0;border-radius:var(--r);
|
|
21
|
+
padding:13px 15px;font:12px/1.62 var(--mono);margin:10px 0;color:var(--ink-dim);
|
|
22
22
|
overflow-x:auto;white-space:pre}
|
|
23
|
-
.md pre code{background:none;padding:0;color:inherit;font-size:inherit}
|
|
24
|
-
.md a{color:var(--
|
|
25
|
-
|
|
23
|
+
.md pre code{background:none;border:0;padding:0;color:inherit;font-size:inherit}
|
|
24
|
+
.md a{color:var(--accent);text-decoration:underline;text-decoration-color:var(--accent-dim);
|
|
25
|
+
text-underline-offset:3px;text-decoration-thickness:1px}
|
|
26
|
+
.md a:hover{text-decoration-color:var(--accent)}
|
|
27
|
+
.md blockquote{margin:12px 0;padding:2px 0 2px 14px;border-left:3px solid var(--line);color:var(--ink-dim)}
|
|
26
28
|
.md hr{border:0;border-top:1px solid var(--line);margin:16px 0}
|
|
27
29
|
.md img{max-width:100%;border-radius:6px;background:var(--panel-2)}
|
|
28
30
|
.md .ctable{overflow-x:auto;margin:10px 0;max-width:100%}
|
|
29
31
|
.md table{border-collapse:collapse;font-size:12.5px;width:100%}
|
|
30
|
-
.md th,.md td{border:1px solid var(--line);padding:
|
|
31
|
-
.md th{background:var(--panel-2);color:var(--ink);font-weight:600;white-space:nowrap
|
|
32
|
+
.md th,.md td{border:1px solid var(--line);padding:7px 10px;text-align:left;vertical-align:top}
|
|
33
|
+
.md th{background:var(--panel-2);color:var(--ink);font-weight:600;white-space:nowrap;
|
|
34
|
+
font-size:11.5px;letter-spacing:.02em}
|
|
32
35
|
.md td{color:var(--ink-dim)}
|
|
33
36
|
.md td b,.md td strong{color:var(--ink)}
|
|
34
37
|
.cbody{margin-top:8px;font-size:13.5px;max-width:74ch}
|
|
35
38
|
.cbody h1{font-size:17px} .cbody h2{font-size:15.5px} .cbody h3{font-size:14px}
|
|
36
39
|
.doc{font-size:14px;max-width:80ch}
|
|
37
40
|
|
|
41
|
+
/* ---------- YAML ----------
|
|
42
|
+
org.yaml and staff.yaml are the two files here anybody reads closely, and flat grey made
|
|
43
|
+
finding a key in one a matter of reading every line. Structure is what YAML has that prose
|
|
44
|
+
does not; these five colours are it, and nothing else is coloured. Tokens from the theme,
|
|
45
|
+
so light and dark both work without a second palette. */
|
|
46
|
+
.yk{color:var(--ruled)} /* keys */
|
|
47
|
+
.ys{color:var(--accent)} /* quoted strings */
|
|
48
|
+
.yn{color:var(--derived)} /* numbers */
|
|
49
|
+
.yb{color:var(--derived)} /* true, false, null */
|
|
50
|
+
.yc{color:var(--ink-faint);font-style:italic} /* comments */
|
|
51
|
+
.yp,.yd{color:var(--ink-faint)} /* colons, brackets, list dashes */
|
|
52
|
+
pre.yaml{color:var(--ink)}
|
|
53
|
+
|
|
38
54
|
/* A reference to another issue or PR, as a chip rather than a raw link: half of a status
|
|
39
55
|
issue is #113, and a wall of blue URLs is the thing that makes them unreadable. */
|
|
40
|
-
.ref{display:inline-block;font:11.5px var(--mono);color:var(--
|
|
56
|
+
.ref{display:inline-block;font:11.5px var(--mono);color:var(--ruled);background:var(--panel-2);
|
|
41
57
|
border:1px solid var(--line);border-radius:20px;padding:0 7px;text-decoration:none;
|
|
42
58
|
white-space:nowrap;line-height:1.65}
|
|
43
|
-
.ref:hover{border-color:var(--
|
|
59
|
+
.ref:hover{border-color:var(--ruled);background:color-mix(in srgb,var(--ruled) 14%,transparent)}
|
|
44
60
|
.ref.pr{color:var(--measured)}
|
|
45
61
|
.ref.pr:hover{border-color:var(--measured);background:color-mix(in srgb,var(--measured) 14%,transparent)}
|
|
46
62
|
|
|
@@ -52,3 +68,29 @@
|
|
|
52
68
|
.at:hover{color:var(--ink);border-color:var(--ink-faint)}
|
|
53
69
|
.at.you{color:var(--derived);border-color:color-mix(in srgb,var(--derived) 40%,transparent)}
|
|
54
70
|
.at.you:hover{background:color-mix(in srgb,var(--derived) 14%,transparent);border-color:var(--derived)}
|
|
71
|
+
|
|
72
|
+
/* A screenshot on a doc page.
|
|
73
|
+
Bordered and inset so it reads as a figure rather than the page bleeding into another one:
|
|
74
|
+
these are pictures of this same UI, and without a frame the two run together. */
|
|
75
|
+
.md img{display:block;max-width:100%;height:auto;margin:14px 0;
|
|
76
|
+
border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow)}
|
|
77
|
+
.md img+em,.md p>img+em{display:block}
|
|
78
|
+
|
|
79
|
+
/* ---------- highlighted editors (mdedit.js) ---------- */
|
|
80
|
+
|
|
81
|
+
.hled{position:relative}
|
|
82
|
+
.hled .hlback{position:absolute;inset:0;margin:0;pointer-events:none;overflow:hidden;
|
|
83
|
+
white-space:pre-wrap;overflow-wrap:break-word;word-break:normal;color:var(--ink);
|
|
84
|
+
border-color:transparent !important;background:transparent !important}
|
|
85
|
+
.hled .hlfront{position:relative;color:transparent;background:transparent;caret-color:var(--ink);
|
|
86
|
+
-webkit-text-fill-color:transparent}
|
|
87
|
+
.hled .hlfront::selection{background:var(--accent-soft)}
|
|
88
|
+
.mdh{color:var(--ruled);font-weight:700}
|
|
89
|
+
.mdb{font-weight:700}
|
|
90
|
+
.mdi{font-style:italic}
|
|
91
|
+
.mdc,.mdcode,.mdfence{color:var(--derived)}
|
|
92
|
+
.mdq{color:var(--ink-dim);font-style:italic}
|
|
93
|
+
.mdl{color:var(--accent)}
|
|
94
|
+
.mdk{color:var(--accent)}
|
|
95
|
+
.mdu{color:var(--ink-faint)}
|
|
96
|
+
.mdt{color:var(--ruled)}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* The Runs screen: a spend strip, then one table per staff member. */
|
|
2
|
+
|
|
3
|
+
.runsum{margin-bottom:6px}
|
|
4
|
+
.hsect i.warn{color:var(--warn)}
|
|
5
|
+
.runtable{border-collapse:collapse;width:100%;margin-top:8px;font:12px var(--mono);
|
|
6
|
+
font-variant-numeric:tabular-nums}
|
|
7
|
+
.runtable th{text-align:left;font:600 10.5px var(--sans);letter-spacing:.06em;text-transform:uppercase;
|
|
8
|
+
color:var(--ink-faint);padding:6px 10px 6px 0;border-bottom:1px solid var(--line)}
|
|
9
|
+
.runtable td{padding:7px 10px 7px 0;border-bottom:1px solid var(--line);color:var(--ink-dim);
|
|
10
|
+
white-space:nowrap}
|
|
11
|
+
.runtable td:last-child,.runtable th:last-child{text-align:right;padding-right:0}
|
|
12
|
+
/* The table is wider than a phone; it scrolls inside its section, never the page. */
|
|
13
|
+
.hbody.runs{overflow-x:auto}
|