k0-board 0.4.0 → 0.5.0
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/.claude/skills/k0-changelog/SKILL.md +2 -2
- package/.claude/skills/k0-discuss/SKILL.md +105 -0
- package/.claude/skills/k0-discuss/references/rounds.md +280 -0
- package/.claude/skills/k0-epic/SKILL.md +196 -0
- package/.claude/skills/k0-links/.claude-plugin/plugin.json +6 -0
- package/.claude/skills/k0-links/hooks/hooks.json +1 -0
- package/.claude/skills/k0-links/hooks/links.js +128 -0
- package/.claude/skills/k0-links/hooks/register.js +204 -0
- package/.claude/skills/k0-next/SKILL.md +82 -0
- package/.claude/skills/k0-order/SKILL.md +123 -0
- package/.claude/skills/k0-plan/SKILL.md +156 -0
- package/.claude/skills/k0-split/SKILL.md +169 -0
- package/.claude/skills/k0-story/SKILL.md +108 -0
- package/.claude/skills/k0-ultracode/SKILL.md +251 -0
- package/.claude/skills/k0-ultracode/references/agent-brief.md +71 -0
- package/.claude/skills/k0-ultracode/references/modes.md +49 -0
- package/.claude/skills/k0-verify/SKILL.md +219 -0
- package/.claude/skills/k0-whatsnew/SKILL.md +95 -0
- package/.claude/skills/k0-work/SKILL.md +212 -0
- package/CHANGELOG.md +361 -1
- package/README.md +931 -111
- package/bin/k0-board.js +79 -11
- package/menubar/K0MenuBar.swift +35 -5
- package/package.json +13 -1
- package/platform/contract.js +30 -5
- package/platform/darwin/terminal.js +230 -56
- package/platform/index.js +2 -1
- package/platform/linux/index.js +3 -0
- package/platform/linux/tray.py +10 -10
- package/platform/shared/command.js +19 -3
- package/platform/shared/tmux.js +32 -14
- package/platform/win32/index.js +5 -2
- package/platform/win32/terminal.js +24 -8
- package/platform/win32/tray.ps1 +13 -13
- package/server/backlog.js +618 -0
- package/server/changelog.js +13 -13
- package/server/db.js +1800 -146
- package/server/files.js +60 -21
- package/server/git.js +53 -14
- package/server/idle.js +15 -13
- package/server/index.js +1251 -186
- package/server/launcher.js +122 -14
- package/server/machine.js +10 -1
- package/server/mirror.js +1048 -0
- package/server/prices.js +59 -0
- package/server/projects.js +57 -12
- package/server/servers.js +22 -1
- package/server/sessions.js +5 -5
- package/server/settings.js +25 -2
- package/server/update.js +324 -0
- package/server/usage.js +642 -0
- package/server/watcher.js +67 -13
- package/server/worktree.js +600 -0
- package/server/writer.js +98 -26
- package/web/awake.js +62 -0
- package/web/base.css +4 -0
- package/web/board.css +396 -16
- package/web/board.js +1182 -152
- package/web/changelog.js +3 -3
- package/web/files.js +73 -16
- package/web/index.html +109 -12
- package/web/list.css +536 -0
- package/web/list.js +1015 -0
- package/web/mentions.js +27 -9
- package/web/recency.js +10 -10
- package/web/units.js +1 -1
- package/web/usage.css +169 -0
- package/web/usage.html +78 -0
- package/web/usage.js +447 -0
- package/web/view.js +22 -3
- package/web/whatsnew.css +211 -0
- package/web/whatsnew.html +49 -0
- package/web/whatsnew.js +339 -0
package/web/board.css
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* The house variables — colours, fonts, scale — live in base.css, which belongs to both
|
|
2
2
|
pages. From here down there is only the board. */
|
|
3
3
|
@import url('/base.css');
|
|
4
|
+
/* And the list, which is the other half of the dashboard: the same stories as rows. It is a file
|
|
5
|
+
of its own because it is a view of its own — the board reaches it through one line in `render` —
|
|
6
|
+
and because it carries the whole of the panel that opens beside a row. */
|
|
7
|
+
@import url('/list.css');
|
|
4
8
|
|
|
5
9
|
/* The page no longer scrolls: it is the board that moves underneath the view. */
|
|
6
10
|
body {
|
|
@@ -32,24 +36,32 @@ body {
|
|
|
32
36
|
letter-spacing: 1px;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/* The pills, the shape of the view and the repository: one group on the bar, because they are one
|
|
40
|
+
question. Two of them say what you are looking at and the third says what shape it comes in, and
|
|
41
|
+
a switch that changed the shape from the other end of the bar would not look like it belonged to
|
|
42
|
+
the same answer. */
|
|
43
|
+
.filterbar { display: flex; align-items: center; gap: 12px; flex: 1; flex-wrap: wrap; }
|
|
44
|
+
|
|
35
45
|
/* The legend IS the filter: click a status and it leaves the board. */
|
|
36
|
-
|
|
46
|
+
/* Seven pills is the widest thing on the bar by a long way, and the bar has to end up on one row:
|
|
47
|
+
what is trimmed here is the space around the words and never the words, which are the legend. */
|
|
48
|
+
.filters { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
37
49
|
.filters button {
|
|
38
50
|
display: flex;
|
|
39
51
|
align-items: center;
|
|
40
|
-
gap:
|
|
52
|
+
gap: 6px;
|
|
41
53
|
border: 1px solid var(--line);
|
|
42
54
|
background: #fffdf7;
|
|
43
55
|
border-radius: 999px;
|
|
44
|
-
padding: 5px
|
|
45
|
-
font-size: .
|
|
56
|
+
padding: 5px 11px 5px 8px;
|
|
57
|
+
font-size: .8rem;
|
|
46
58
|
font-weight: 600;
|
|
47
59
|
color: var(--ink);
|
|
48
60
|
cursor: pointer;
|
|
49
61
|
}
|
|
50
62
|
.filters button i {
|
|
51
|
-
width:
|
|
52
|
-
height:
|
|
63
|
+
width: 12px;
|
|
64
|
+
height: 12px;
|
|
53
65
|
border-radius: 4px;
|
|
54
66
|
flex: none;
|
|
55
67
|
border: 1px solid rgba(0, 0, 0, .12);
|
|
@@ -81,6 +93,11 @@ body {
|
|
|
81
93
|
/* `display: flex` beats the browser's `[hidden]`: until there is a sample, it hides here. */
|
|
82
94
|
.gauge[hidden] { display: none; }
|
|
83
95
|
.gauge:hover { border-color: #bdb6a5; background: #fff; }
|
|
96
|
+
/* RAM over CPU rather than side by side, with the closing time under them. The bar is one row again
|
|
97
|
+
and the four mode buttons have gone two by two: the gauge gives back the width it was taking by
|
|
98
|
+
going up instead of along, and written across it was over two hundred pixels of a bar that had
|
|
99
|
+
none to give. The dot stays where it was — the box is what became three lines, not the chip. */
|
|
100
|
+
.gauge .nums { display: flex; flex-direction: column; line-height: 1.15; }
|
|
84
101
|
.gauge i {
|
|
85
102
|
width: 9px;
|
|
86
103
|
height: 9px;
|
|
@@ -89,9 +106,9 @@ body {
|
|
|
89
106
|
background: var(--calm);
|
|
90
107
|
}
|
|
91
108
|
/* That k0 is looking after the memory for you is worth knowing once and then forgetting: it sits
|
|
92
|
-
on the same chip,
|
|
93
|
-
read and this is only there so the closing is never a surprise. */
|
|
94
|
-
.gauge .idle { font-weight: 500; opacity: .6;
|
|
109
|
+
on the same chip, under the numbers and lighter than them, because the numbers are what you came
|
|
110
|
+
to read and this is only there so the closing is never a surprise. */
|
|
111
|
+
.gauge .idle { font-weight: 500; opacity: .6; white-space: nowrap; }
|
|
95
112
|
.gauge.warn i { background: var(--warn); }
|
|
96
113
|
.gauge.bad i { background: var(--alert); }
|
|
97
114
|
/* Solid red only when it is critical: the one moment it is allowed to interrupt. */
|
|
@@ -113,6 +130,50 @@ body {
|
|
|
113
130
|
.new svg,
|
|
114
131
|
.log svg { width: 17px; height: 17px; display: block; }
|
|
115
132
|
|
|
133
|
+
/* The plus opens a little menu, so it has to have something to hang off. */
|
|
134
|
+
.new-wrap { position: relative; flex: none; display: flex; }
|
|
135
|
+
|
|
136
|
+
/* Two entries and no more: the two things k0 can make. It is a menu and not a second dialog
|
|
137
|
+
because the choice is one word long — and the sentence under each word is the whole difference
|
|
138
|
+
between them, since one is typed here and the other is talked through in a terminal. */
|
|
139
|
+
.new-wrap .menu {
|
|
140
|
+
position: absolute;
|
|
141
|
+
z-index: 6;
|
|
142
|
+
top: calc(100% + 6px);
|
|
143
|
+
left: 0;
|
|
144
|
+
min-width: 230px;
|
|
145
|
+
padding: 5px;
|
|
146
|
+
background: var(--paper);
|
|
147
|
+
border: 1px solid var(--line);
|
|
148
|
+
border-radius: 11px;
|
|
149
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, .18);
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 2px;
|
|
153
|
+
}
|
|
154
|
+
.new-wrap .menu[hidden] { display: none; }
|
|
155
|
+
.new-wrap .menu button {
|
|
156
|
+
display: block;
|
|
157
|
+
width: 100%;
|
|
158
|
+
border: none;
|
|
159
|
+
background: none;
|
|
160
|
+
border-radius: 7px;
|
|
161
|
+
padding: 8px 11px;
|
|
162
|
+
text-align: left;
|
|
163
|
+
font-size: .95rem;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
color: var(--ink);
|
|
166
|
+
}
|
|
167
|
+
.new-wrap .menu button:hover { background: #f0ede3; border: none; }
|
|
168
|
+
.new-wrap .menu button em {
|
|
169
|
+
display: block;
|
|
170
|
+
margin-top: 2px;
|
|
171
|
+
font-style: normal;
|
|
172
|
+
font-size: .76rem;
|
|
173
|
+
font-weight: 400;
|
|
174
|
+
color: var(--muted);
|
|
175
|
+
}
|
|
176
|
+
|
|
116
177
|
/* The way out to the ChangeLog. It is a link, not a button, because it goes somewhere — and it
|
|
117
178
|
is drawn quieter than the plus, because starting work matters more than reading about it. */
|
|
118
179
|
.log {
|
|
@@ -121,15 +182,127 @@ body {
|
|
|
121
182
|
color: var(--muted);
|
|
122
183
|
}
|
|
123
184
|
.log:hover { background: #fff; border-color: #c9c3b4; color: var(--ink); }
|
|
185
|
+
/* `display: flex` beats the browser's `[hidden]`, the same trap the gauge and the What's New mark
|
|
186
|
+
fall into. The door to the backlog is drawn hidden and put on the bar only when the server says
|
|
187
|
+
there is a backlog behind the board; without this it would sit there whatever the answer was,
|
|
188
|
+
leading to a page whose whole content is the sentence "The k0 backlog is switched off." */
|
|
189
|
+
.log[hidden] { display: none; }
|
|
190
|
+
|
|
191
|
+
/* ── The mark that says k0 moved ──────────────────────────────────────
|
|
192
|
+
It sits against the name and it is a dot, not a badge with a number on it: there is nothing to
|
|
193
|
+
count. It only exists at all when the server says so — after a version jump, or when npm has
|
|
194
|
+
something newer — so it is never one more thing on the bar that means nothing today. */
|
|
195
|
+
.whatsnew {
|
|
196
|
+
flex: none;
|
|
197
|
+
margin-left: -6px;
|
|
198
|
+
align-self: flex-start;
|
|
199
|
+
padding: 4px;
|
|
200
|
+
display: flex;
|
|
201
|
+
border-radius: 999px;
|
|
202
|
+
}
|
|
203
|
+
/* `display: flex` beats the browser's `[hidden]`, the same trap the gauge falls into: the mark
|
|
204
|
+
ships hidden and is shown only when the server has something to say. Without this the dot sits
|
|
205
|
+
against the name for ever on a board where nothing is new, and with no sentence on it either —
|
|
206
|
+
the title is only written when there is one. */
|
|
207
|
+
.whatsnew[hidden] { display: none; }
|
|
208
|
+
.whatsnew span {
|
|
209
|
+
width: 7px;
|
|
210
|
+
height: 7px;
|
|
211
|
+
border-radius: 999px;
|
|
212
|
+
background: var(--warn);
|
|
213
|
+
display: block;
|
|
214
|
+
}
|
|
215
|
+
.whatsnew:hover span { transform: scale(1.3); }
|
|
216
|
+
|
|
217
|
+
/* ── Kanban or List ───────────────────────────────────────────────────
|
|
218
|
+
One control with two halves, not two buttons: the same stories as a board, or the same stories as
|
|
219
|
+
rows. It stands inside the filter group because it is the same kind of control as the pills next
|
|
220
|
+
to it — how you are looking at what you have already chosen to see — and it is on the bar at all
|
|
221
|
+
only where there is a backlog behind the board. */
|
|
222
|
+
.columns {
|
|
223
|
+
flex: none;
|
|
224
|
+
display: flex;
|
|
225
|
+
gap: 2px;
|
|
226
|
+
padding: 2px;
|
|
227
|
+
border: 1px solid var(--line);
|
|
228
|
+
border-radius: 999px;
|
|
229
|
+
background: var(--paper);
|
|
230
|
+
}
|
|
231
|
+
/* `display: flex` beats the browser's `[hidden]`, the same trap the gauge and the What's New mark
|
|
232
|
+
fall into: without this the switch stands on the bar of a board that has no backlog behind it. */
|
|
233
|
+
.columns[hidden] { display: none; }
|
|
234
|
+
.columns button {
|
|
235
|
+
border: none;
|
|
236
|
+
background: none;
|
|
237
|
+
border-radius: 999px;
|
|
238
|
+
padding: 4px 10px;
|
|
239
|
+
font-size: .8rem;
|
|
240
|
+
font-weight: 600;
|
|
241
|
+
color: var(--muted);
|
|
242
|
+
}
|
|
243
|
+
.columns button:hover { background: rgba(0, 0, 0, .05); border: none; }
|
|
244
|
+
/* The lit half is dark, the same ink as the primary button in a dialog. Two greys would read as
|
|
245
|
+
two buttons waiting to be pressed rather than as one answer already given. */
|
|
246
|
+
.columns button[aria-checked="true"] { background: var(--ink); color: var(--paper); }
|
|
247
|
+
.columns button[aria-checked="true"]:hover { background: var(--ink); }
|
|
248
|
+
|
|
249
|
+
/* And which repository, which now serves both views: the list has no columns to be repositories,
|
|
250
|
+
and on the board it narrows what is already there.
|
|
251
|
+
|
|
252
|
+
Drawn here rather than left to the browser. It was a `<select>`, which on macOS opens the
|
|
253
|
+
system's own list in the system's own type in the middle of the screen — a panel from another
|
|
254
|
+
application landing on top of a board made of paper — and which cannot hold the search that
|
|
255
|
+
makes a list of twenty repositories usable at all. It is the same picker the new-story dialog
|
|
256
|
+
has, so there is one way of choosing a repository in k0 and not two. */
|
|
257
|
+
.repo-filter { flex: none; }
|
|
258
|
+
.repo-filter > button {
|
|
259
|
+
font: inherit;
|
|
260
|
+
font-size: .82rem;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
color: var(--ink);
|
|
263
|
+
background: var(--paper);
|
|
264
|
+
border: 1px solid var(--line);
|
|
265
|
+
border-radius: 999px;
|
|
266
|
+
padding: 5px 12px;
|
|
267
|
+
/* A repository with a long name must not be what pushes the bar onto a second row: past this it
|
|
268
|
+
is cut with an ellipsis, and the whole name is still in the list. */
|
|
269
|
+
max-width: 160px;
|
|
270
|
+
overflow: hidden;
|
|
271
|
+
text-overflow: ellipsis;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
}
|
|
275
|
+
.repo-filter > button:hover { border-color: #bdb6a5; }
|
|
276
|
+
.repo-filter > button[aria-expanded="true"] { border-color: #bdb6a5; background: var(--bg); }
|
|
277
|
+
/* Wider than the button it hangs off: the button is cut short on purpose and the list is where the
|
|
278
|
+
whole name is read. */
|
|
279
|
+
.repo-filter .picker-list { left: 0; right: auto; min-width: 260px; }
|
|
280
|
+
.repo-filter .picker-list input {
|
|
281
|
+
display: block;
|
|
282
|
+
width: 100%;
|
|
283
|
+
font: inherit;
|
|
284
|
+
font-size: .9rem;
|
|
285
|
+
padding: 7px 10px;
|
|
286
|
+
margin-bottom: 5px;
|
|
287
|
+
border: 1px solid var(--line);
|
|
288
|
+
border-radius: 7px;
|
|
289
|
+
background: var(--paper);
|
|
290
|
+
}
|
|
291
|
+
.repo-filter .picker-list input:focus { outline: none; border-color: #bdb6a5; }
|
|
124
292
|
|
|
125
293
|
/* ── The four modes ───────────────────────────────────────────────────
|
|
126
294
|
At the end of the bar, a row of four: how awake the machine has to stay. Switched off they are
|
|
127
295
|
pale outlines like the statuses filtered away; the lit one takes the colour of its mode. The
|
|
128
296
|
colour is not decoration: from across the room — which is what driving mode is for — you do not
|
|
129
297
|
focus on the icon, but you do see the colour. */
|
|
298
|
+
/* Two by two rather than four across: the bar was going to a second row, and of everything on it
|
|
299
|
+
these four are the ones that lose the least by being stacked — they are a scale you glance at,
|
|
300
|
+
not a sentence you read. The columns are `auto` and not a width, because driving mode makes each
|
|
301
|
+
button 46px and a fixed column would clip them. */
|
|
130
302
|
.modes {
|
|
131
303
|
flex: none;
|
|
132
|
-
display:
|
|
304
|
+
display: grid;
|
|
305
|
+
grid-template-columns: repeat(2, auto);
|
|
133
306
|
gap: 3px;
|
|
134
307
|
}
|
|
135
308
|
.modes button {
|
|
@@ -177,6 +350,58 @@ body {
|
|
|
177
350
|
not to make: an amber ring around it, and the title says the rest. */
|
|
178
351
|
.modes button[data-partial] { box-shadow: 0 0 0 2px #e0a72e; }
|
|
179
352
|
|
|
353
|
+
/* ── Inside an epic ───────────────────────────────────────────────────
|
|
354
|
+
A strip under the bar, on the page only once you have gone into a lane. It says the four things
|
|
355
|
+
there are to say about standing in one: which epic, where it lives, how far it has got, and the
|
|
356
|
+
way back out. The colour is the epic's own — the hue is worked out from its key, the same one
|
|
357
|
+
the label on every note below is drawn in — so the strip and the notes under it are visibly one
|
|
358
|
+
thing rather than two that happen to be on screen together. */
|
|
359
|
+
.lane {
|
|
360
|
+
flex: none;
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 12px;
|
|
364
|
+
padding: 9px 22px;
|
|
365
|
+
background: hsl(var(--h) 48% 95%);
|
|
366
|
+
border-bottom: 1px solid hsl(var(--h) 34% 84%);
|
|
367
|
+
}
|
|
368
|
+
.lane[hidden] { display: none; }
|
|
369
|
+
.lane .key {
|
|
370
|
+
flex: none;
|
|
371
|
+
font: 700 .8rem/1 var(--mono);
|
|
372
|
+
color: hsl(var(--h) 52% 30%);
|
|
373
|
+
}
|
|
374
|
+
.lane .name {
|
|
375
|
+
font: 600 1.4rem/1 var(--hand);
|
|
376
|
+
color: var(--ink);
|
|
377
|
+
min-width: 0;
|
|
378
|
+
overflow: hidden;
|
|
379
|
+
text-overflow: ellipsis;
|
|
380
|
+
white-space: nowrap;
|
|
381
|
+
}
|
|
382
|
+
.lane .repo { flex: none; font-size: .8rem; color: var(--muted); }
|
|
383
|
+
/* Done over total and no weighting: eleven stories with one finished is a bar one eleventh along,
|
|
384
|
+
however big that one was. Anything else is an estimate with a progress bar drawn round it. */
|
|
385
|
+
.lane .bar {
|
|
386
|
+
flex: none;
|
|
387
|
+
width: 160px;
|
|
388
|
+
height: 6px;
|
|
389
|
+
border-radius: 999px;
|
|
390
|
+
background: hsl(var(--h) 28% 87%);
|
|
391
|
+
overflow: hidden;
|
|
392
|
+
}
|
|
393
|
+
.lane .bar i {
|
|
394
|
+
display: block;
|
|
395
|
+
height: 100%;
|
|
396
|
+
background: hsl(var(--h) 52% 46%);
|
|
397
|
+
transition: width .3s ease;
|
|
398
|
+
}
|
|
399
|
+
.lane .count { flex: none; font-size: .8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
400
|
+
.lane .spacer { flex: 1; }
|
|
401
|
+
/* Words and not an ✕: coming back to the whole board is not closing something, and there is
|
|
402
|
+
nothing here to close. */
|
|
403
|
+
.lane button { flex: none; padding: 5px 13px; font-size: .82rem; font-weight: 600; }
|
|
404
|
+
|
|
180
405
|
/* ── The board ───────────────────────────────────────────────────────── */
|
|
181
406
|
/* The viewport is the fixed window, the board is the content sliding behind it: one transform on
|
|
182
407
|
.board, and no scrollbar anywhere. */
|
|
@@ -279,6 +504,8 @@ body {
|
|
|
279
504
|
.column > h2 .fold[disabled] { opacity: .12; cursor: default; }
|
|
280
505
|
.column > h2 .fold[disabled]:hover { opacity: .12; background: none; }
|
|
281
506
|
|
|
507
|
+
/* Where the state columns used to be there is now a view of its own: see list.css. */
|
|
508
|
+
|
|
282
509
|
.empty {
|
|
283
510
|
position: absolute;
|
|
284
511
|
inset: 0;
|
|
@@ -376,9 +603,26 @@ body {
|
|
|
376
603
|
.postit.BACKLOG { background: var(--BACKLOG); }
|
|
377
604
|
.postit.COMPLETED { background: var(--COMPLETED); opacity: .6; }
|
|
378
605
|
.postit.COMPLETED .title { text-decoration: line-through; }
|
|
379
|
-
/* Grinding away: the faintest breath, no blinking dots.
|
|
380
|
-
|
|
381
|
-
|
|
606
|
+
/* Grinding away: the faintest breath, no blinking dots.
|
|
607
|
+
The breath is a ring on a layer of its own, and the only thing moving is that layer's opacity,
|
|
608
|
+
which the browser composites without redrawing anything. It used to animate `box-shadow` on the
|
|
609
|
+
post-it itself — a blurred shadow, recomputed and repainted sixty times a second, for every
|
|
610
|
+
working post-it, in every open tab, for as long as the board was on screen. On a machine already
|
|
611
|
+
under pressure that was the most expensive thing k0 did, and none of it was in k0's own process:
|
|
612
|
+
the bill arrived under the browser's name. The ring sits just inside the edge rather than just
|
|
613
|
+
outside it, because the post-it clips what overflows it. */
|
|
614
|
+
.postit.WORKING::after, .postit.PLANNING::after {
|
|
615
|
+
content: '';
|
|
616
|
+
position: absolute;
|
|
617
|
+
inset: 0;
|
|
618
|
+
border-radius: inherit;
|
|
619
|
+
box-shadow: inset 0 0 0 3px rgba(59, 130, 246, .22);
|
|
620
|
+
pointer-events: none;
|
|
621
|
+
opacity: 0;
|
|
622
|
+
will-change: opacity;
|
|
623
|
+
animation: breathe 2.6s ease-in-out infinite;
|
|
624
|
+
}
|
|
625
|
+
@keyframes breathe { 50% { opacity: 1; } }
|
|
382
626
|
|
|
383
627
|
/* The repository's name and, beside it, how it is doing: the two read together. */
|
|
384
628
|
.postit .head {
|
|
@@ -423,8 +667,11 @@ body {
|
|
|
423
667
|
font-style: italic;
|
|
424
668
|
color: rgba(0, 0, 0, .56);
|
|
425
669
|
margin-top: 6px;
|
|
670
|
+
/* The notes are written one per line and have to read that way: `- OMI` under `- Locato`, not
|
|
671
|
+
`- OMI - Locato` run together. Three lines, like the prompt, so two notes that wrap both show. */
|
|
672
|
+
white-space: pre-wrap;
|
|
426
673
|
display: -webkit-box;
|
|
427
|
-
-webkit-line-clamp:
|
|
674
|
+
-webkit-line-clamp: 3;
|
|
428
675
|
-webkit-box-orient: vertical;
|
|
429
676
|
overflow: hidden;
|
|
430
677
|
}
|
|
@@ -451,6 +698,104 @@ body {
|
|
|
451
698
|
.postit .since { margin-left: auto; }
|
|
452
699
|
.postit .dead { font-style: italic; }
|
|
453
700
|
|
|
701
|
+
/* ── What the backlog adds to a note ──────────────────────────────────
|
|
702
|
+
Four marks and no more: what the story is called, the epic it belongs to, the star, and whether
|
|
703
|
+
it is waiting on something. Everything else a story has — its decisions, its rounds, its plan —
|
|
704
|
+
is a page and not a note, and a post-it that tried to carry it would stop being a post-it. With
|
|
705
|
+
the backlog off none of them is drawn and the note is the note it has always been. */
|
|
706
|
+
|
|
707
|
+
/* `K42` is the name the story keeps for ever; `1.12` is where it is sitting in the tree today and
|
|
708
|
+
moves the moment the tree does. Two different facts, so two weights — said in one breath they
|
|
709
|
+
would read as one long name. */
|
|
710
|
+
.postit .key {
|
|
711
|
+
flex: none;
|
|
712
|
+
font-family: var(--mono);
|
|
713
|
+
font-size: .72rem;
|
|
714
|
+
font-weight: 700;
|
|
715
|
+
line-height: 1;
|
|
716
|
+
color: rgba(0, 0, 0, .6);
|
|
717
|
+
cursor: default;
|
|
718
|
+
}
|
|
719
|
+
.postit .key em {
|
|
720
|
+
font-style: normal;
|
|
721
|
+
font-weight: 500;
|
|
722
|
+
color: rgba(0, 0, 0, .36);
|
|
723
|
+
margin-left: 4px;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* The epic, in the epic's colour, and the way into it. The hue is on the element itself, worked
|
|
727
|
+
out from the key: nothing stores a colour, so there is nothing to keep in step with an epic
|
|
728
|
+
that gets renamed or moved. */
|
|
729
|
+
.postit .epic {
|
|
730
|
+
flex: 0 1 auto;
|
|
731
|
+
min-width: 0;
|
|
732
|
+
padding: 2px 8px;
|
|
733
|
+
border: none;
|
|
734
|
+
border-radius: 999px;
|
|
735
|
+
background: hsl(var(--h) 58% 88%);
|
|
736
|
+
color: hsl(var(--h) 55% 26%);
|
|
737
|
+
font-size: .72rem;
|
|
738
|
+
font-weight: 700;
|
|
739
|
+
line-height: 1.4;
|
|
740
|
+
overflow: hidden;
|
|
741
|
+
text-overflow: ellipsis;
|
|
742
|
+
white-space: nowrap;
|
|
743
|
+
}
|
|
744
|
+
.postit .epic:hover { background: hsl(var(--h) 58% 81%); border: none; }
|
|
745
|
+
|
|
746
|
+
/* Waiting on something that is not done. It warns and it never stops: what it is waiting for is
|
|
747
|
+
in the title, and the button that starts the story asks once and then goes ahead. */
|
|
748
|
+
.postit .waits {
|
|
749
|
+
flex: none;
|
|
750
|
+
display: inline-flex;
|
|
751
|
+
align-items: center;
|
|
752
|
+
gap: 2px;
|
|
753
|
+
color: var(--alert);
|
|
754
|
+
font-weight: 700;
|
|
755
|
+
font-variant-numeric: tabular-nums;
|
|
756
|
+
cursor: default;
|
|
757
|
+
}
|
|
758
|
+
.postit .waits svg { width: 13px; height: 13px; display: block; }
|
|
759
|
+
|
|
760
|
+
/* The flag: the mark and the target both, at the bottom right, mirroring the pencil at the top.
|
|
761
|
+
|
|
762
|
+
It is invisible until the pointer is on the note, and then it is a faint outline — an outline
|
|
763
|
+
sitting on every note all the time is a mark that is everywhere and therefore marks nothing. Set,
|
|
764
|
+
it stays on whether or not anybody is looking, and the note takes a border and its key and name
|
|
765
|
+
take the same colour: the point of flagging something is to find it again from across the room,
|
|
766
|
+
and a fifteen-pixel glyph cannot be seen from there. */
|
|
767
|
+
.postit .flag {
|
|
768
|
+
position: absolute;
|
|
769
|
+
right: 6px;
|
|
770
|
+
bottom: 6px;
|
|
771
|
+
width: 24px;
|
|
772
|
+
height: 24px;
|
|
773
|
+
padding: 0;
|
|
774
|
+
display: flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
border-radius: 6px;
|
|
778
|
+
background: none;
|
|
779
|
+
border-color: transparent;
|
|
780
|
+
box-shadow: none;
|
|
781
|
+
color: rgba(0, 0, 0, .35);
|
|
782
|
+
opacity: 0;
|
|
783
|
+
cursor: pointer;
|
|
784
|
+
transition: opacity .12s ease;
|
|
785
|
+
}
|
|
786
|
+
.postit .flag svg { width: 15px; height: 15px; display: block; }
|
|
787
|
+
.postit:hover .flag, .postit .flag:focus-visible { opacity: 1; }
|
|
788
|
+
.postit .flag:hover { background: rgba(255, 255, 255, .72); border-color: rgba(0, 0, 0, .14); }
|
|
789
|
+
/* On: the colour, and no longer waiting to be hovered. */
|
|
790
|
+
.postit .flag.on { opacity: 1; color: var(--warn); }
|
|
791
|
+
.postit.flagged { border: 2px solid var(--warn); }
|
|
792
|
+
/* And the two pieces of writing at the top take it with them, so a flagged note is told apart from
|
|
793
|
+
the ones beside it by more than a hairline of border. */
|
|
794
|
+
.postit.flagged .key, .postit.flagged .title { color: var(--warn); }
|
|
795
|
+
/* The room the flag needs. It sits over the row of buttons, which is empty space until you hover:
|
|
796
|
+
this is what stops the last of them running underneath it once they come out. */
|
|
797
|
+
.postit .actions { padding-right: 22px; }
|
|
798
|
+
|
|
454
799
|
/* ── The last column: every repository ────────────────────────────────
|
|
455
800
|
Headings only, one per repository, in the order you last used them. No cards and no counts:
|
|
456
801
|
this is how you get into the files of a directory you are not working on — which without this
|
|
@@ -566,7 +911,7 @@ body {
|
|
|
566
911
|
.postit .load.hot { color: var(--alert); }
|
|
567
912
|
|
|
568
913
|
/* The corner icon: the pencil opens the editor, and on a finished card — where there is nothing
|
|
569
|
-
left to edit — the bin takes its place. */
|
|
914
|
+
left to edit — the bin takes its place. The flag at the bottom right is hung the same way. */
|
|
570
915
|
.postit .corner {
|
|
571
916
|
position: absolute;
|
|
572
917
|
top: 6px;
|
|
@@ -591,9 +936,12 @@ body {
|
|
|
591
936
|
git mark stays next to the repository name instead of next to the pencil. */
|
|
592
937
|
.postit .head, .postit .title { padding-right: 26px; }
|
|
593
938
|
|
|
594
|
-
/* The buttons stay hidden until you hover: the dashboard stays clean.
|
|
939
|
+
/* The buttons stay hidden until you hover: the dashboard stays clean. The row wraps and the words
|
|
940
|
+
in it do not — a note is 268px wide and three buttons can outgrow that, but a label broken over
|
|
941
|
+
two lines reads as two buttons and made the post-it grow a row taller than the ones beside it. */
|
|
595
942
|
.postit .actions {
|
|
596
943
|
display: flex;
|
|
944
|
+
flex-wrap: wrap;
|
|
597
945
|
gap: 7px;
|
|
598
946
|
margin-top: 11px;
|
|
599
947
|
opacity: 0;
|
|
@@ -603,6 +951,7 @@ body {
|
|
|
603
951
|
.postit .actions button {
|
|
604
952
|
font-size: .84rem;
|
|
605
953
|
font-weight: 600;
|
|
954
|
+
white-space: nowrap;
|
|
606
955
|
padding: 6px 13px;
|
|
607
956
|
background: rgba(255, 255, 255, .72);
|
|
608
957
|
border-color: rgba(0, 0, 0, .14);
|
|
@@ -625,6 +974,28 @@ body {
|
|
|
625
974
|
The dialog and its buttons live in base.css, which belongs to both pages: the file viewer opens
|
|
626
975
|
one too. What is left here is only what belongs to a card's editor and to nothing else. */
|
|
627
976
|
|
|
977
|
+
/* An aside on the label's own line rather than under it. base.css makes a dialog's `small` a block,
|
|
978
|
+
which is right where it explains a field you have already filled in and wrong where it is a
|
|
979
|
+
parenthesis after the field's name — there it costs a whole line to say four words. */
|
|
980
|
+
dialog label small.aside { display: inline; margin: 0; font-weight: 400; }
|
|
981
|
+
|
|
982
|
+
/* The epic dialog says what pressing the button will actually do, because what it does is open a
|
|
983
|
+
terminal — and a dialog whose one button starts a conversation somewhere else has to say so. */
|
|
984
|
+
#epic-new select {
|
|
985
|
+
display: block;
|
|
986
|
+
width: 100%;
|
|
987
|
+
margin-top: 6px;
|
|
988
|
+
font: inherit;
|
|
989
|
+
font-size: 1rem;
|
|
990
|
+
color: var(--ink);
|
|
991
|
+
padding: 10px 12px;
|
|
992
|
+
border: 1px solid var(--line);
|
|
993
|
+
border-radius: 8px;
|
|
994
|
+
background: #fff;
|
|
995
|
+
}
|
|
996
|
+
#epic-new .note { margin: 0 0 18px; font-size: .88rem; line-height: 1.5; color: var(--muted); }
|
|
997
|
+
#epic-new .note code { font: .85em var(--mono); background: var(--bg); border-radius: 4px; padding: 1px 5px; }
|
|
998
|
+
|
|
628
999
|
/* Choosing the repository: a searchable field, most used at the top. */
|
|
629
1000
|
.picker { position: relative; }
|
|
630
1001
|
.picker-list {
|
|
@@ -673,6 +1044,9 @@ body {
|
|
|
673
1044
|
transition: opacity .2s, transform .2s;
|
|
674
1045
|
max-width: 80vw;
|
|
675
1046
|
font-size: .92rem;
|
|
1047
|
+
/* The one refusal in k0 is a paragraph and then the decisions it is about, one to a line. The
|
|
1048
|
+
line breaks are in the text, and without this they collapse into a wall. */
|
|
1049
|
+
white-space: pre-line;
|
|
676
1050
|
}
|
|
677
1051
|
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
678
1052
|
|
|
@@ -718,6 +1092,12 @@ html[data-driving] .postit .corner svg { width: 20px; height: 20px; }
|
|
|
718
1092
|
html[data-driving] .zoom button { width: 40px; height: 40px; }
|
|
719
1093
|
html[data-driving] .zoom button svg { width: 22px; height: 22px; }
|
|
720
1094
|
|
|
1095
|
+
/* The flag. It is exactly what you are meant to
|
|
1096
|
+
pick out from a distance, so it grows with everything else. The border round the note does the
|
|
1097
|
+
work at that range; this is what says which mark it is once you are closer. */
|
|
1098
|
+
html[data-driving] .postit .flag { width: 28px; height: 28px; }
|
|
1099
|
+
html[data-driving] .postit .flag svg { width: 21px; height: 21px; }
|
|
1100
|
+
|
|
721
1101
|
/* The card is in pixels too: with the text at 22 it would choke inside 268. And since the card is
|
|
722
1102
|
never taller than it is wide, raising this raises the ceiling with it. */
|
|
723
1103
|
html[data-driving] { --card-w: 340px; }
|