k0-board 0.1.1 → 0.2.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 +96 -0
- package/CHANGELOG.md +163 -1
- package/README.md +223 -20
- package/bin/k0-board.js +37 -3
- package/menubar/K0MenuBar.swift +105 -22
- package/menubar/build.sh +7 -1
- package/package.json +6 -3
- package/platform/contract.js +30 -0
- package/platform/darwin/index.js +18 -3
- package/platform/darwin/servers.js +16 -0
- package/platform/index.js +9 -1
- package/platform/linux/index.js +18 -1
- package/platform/linux/servers.js +41 -0
- package/platform/linux/tray.py +42 -2
- package/platform/shared/listeners.js +121 -0
- package/platform/shared/servers.js +111 -0
- package/platform/win32/index.js +15 -1
- package/platform/win32/servers.js +73 -0
- package/platform/win32/tray.ps1 +11 -0
- package/server/changelog.js +242 -0
- package/server/db.js +58 -0
- package/server/git.js +135 -0
- package/server/guard.js +31 -0
- package/server/index.js +122 -35
- package/server/machine.js +9 -0
- package/server/servers.js +481 -0
- package/server/watcher.js +8 -1
- package/server/writer.js +128 -0
- package/web/board.css +162 -12
- package/web/board.js +278 -10
- package/web/changelog.css +185 -0
- package/web/changelog.html +43 -0
- package/web/changelog.js +298 -0
- package/web/index.html +9 -7
- package/web/recency.js +86 -0
- package/web/view.js +3 -1
package/web/board.css
CHANGED
|
@@ -95,7 +95,8 @@ body {
|
|
|
95
95
|
|
|
96
96
|
/* The "+" sits next to k0, at the start of the bar: that is where a card is born.
|
|
97
97
|
The sign is drawn, not typed: a font glyph never sits in the middle of the circle. */
|
|
98
|
-
.new
|
|
98
|
+
.new,
|
|
99
|
+
.log {
|
|
99
100
|
flex: none;
|
|
100
101
|
padding: 0;
|
|
101
102
|
width: 34px;
|
|
@@ -105,7 +106,17 @@ body {
|
|
|
105
106
|
align-items: center;
|
|
106
107
|
justify-content: center;
|
|
107
108
|
}
|
|
108
|
-
.new svg
|
|
109
|
+
.new svg,
|
|
110
|
+
.log svg { width: 17px; height: 17px; display: block; }
|
|
111
|
+
|
|
112
|
+
/* The way out to the ChangeLog. It is a link, not a button, because it goes somewhere — and it
|
|
113
|
+
is drawn quieter than the plus, because starting work matters more than reading about it. */
|
|
114
|
+
.log {
|
|
115
|
+
border: 1px solid var(--line);
|
|
116
|
+
background: var(--paper);
|
|
117
|
+
color: var(--muted);
|
|
118
|
+
}
|
|
119
|
+
.log:hover { background: #fff; border-color: #c9c3b4; color: var(--ink); }
|
|
109
120
|
|
|
110
121
|
/* ── The four modes ───────────────────────────────────────────────────
|
|
111
122
|
At the end of the bar, a row of four: how awake the machine has to stay. Switched off they are
|
|
@@ -180,12 +191,51 @@ body {
|
|
|
180
191
|
transform-origin: 0 0;
|
|
181
192
|
will-change: transform;
|
|
182
193
|
}
|
|
183
|
-
|
|
194
|
+
/* A column is exactly one card wide. The number lives in a variable because the card reads it
|
|
195
|
+
too: a post-it is never taller than it is wide. */
|
|
196
|
+
:root { --card-w: 268px; }
|
|
197
|
+
.column { flex: 0 0 var(--card-w); }
|
|
198
|
+
/* Two groups with the name between them: on the left what you press — the `+` and the fold — and
|
|
199
|
+
on the right what tells you how things stand, the git mark and the globe. The count of post-its
|
|
200
|
+
that used to follow the name is gone: it took the room the name now has, and answered nothing.
|
|
201
|
+
Flex is what lets the name give up characters instead of pushing the marks off the end. */
|
|
184
202
|
.column > h2 {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
185
205
|
margin: 0 0 14px 4px;
|
|
186
206
|
font: 600 1.5rem/1 var(--hand);
|
|
187
207
|
color: #5f5a4e;
|
|
188
208
|
}
|
|
209
|
+
.column > h2 .name {
|
|
210
|
+
flex: 1;
|
|
211
|
+
min-width: 0;
|
|
212
|
+
overflow: hidden;
|
|
213
|
+
text-overflow: ellipsis;
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
color: inherit;
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
}
|
|
218
|
+
/* With the server up the name is the way into the site, and it says so standing still — a dotted
|
|
219
|
+
line under it, there for as long as the server is. Waiting for a hover was the first idea and it
|
|
220
|
+
was the wrong one: the whole question the globe answers is "is this one up", and a heading you
|
|
221
|
+
have to go and touch to find out is a second place to ask it. Dotted rather than solid because
|
|
222
|
+
this is a hand-written heading, not a link in a paragraph: enough to say there is something
|
|
223
|
+
underneath, not enough to turn the row into a web page. Under the pointer it firms up. */
|
|
224
|
+
.column > h2 a.name {
|
|
225
|
+
/* Dashed, and the same green as the globe, so the two marks say one thing. The first attempt
|
|
226
|
+
was a grey dotted line at half opacity, which is in the stylesheet and invisible on the
|
|
227
|
+
board: next to a lit globe it read as nothing at all. What rescued it was the dash and the
|
|
228
|
+
colour, not weight — a hairline is enough once it is green, and anything thicker starts
|
|
229
|
+
underlining the heading rather than marking it. */
|
|
230
|
+
text-decoration: underline dashed;
|
|
231
|
+
text-decoration-thickness: 1px;
|
|
232
|
+
text-underline-offset: 4px;
|
|
233
|
+
text-decoration-color: var(--calm);
|
|
234
|
+
/* The ellipsis needs `overflow: hidden`, and that clips the underline too. This is the room it
|
|
235
|
+
needs to survive. */
|
|
236
|
+
padding-bottom: 3px;
|
|
237
|
+
}
|
|
238
|
+
.column > h2 a.name:hover { text-decoration-style: solid; text-decoration-color: currentColor; }
|
|
189
239
|
.column > h2 small {
|
|
190
240
|
font-family: var(--sans);
|
|
191
241
|
color: var(--muted);
|
|
@@ -195,8 +245,10 @@ body {
|
|
|
195
245
|
vertical-align: middle;
|
|
196
246
|
}
|
|
197
247
|
/* The column's "+": from here a card is born already addressed to this repository.
|
|
198
|
-
It sits to the left of the name, faded, and darkens when you hover over it.
|
|
199
|
-
|
|
248
|
+
It sits to the left of the name, faded, and darkens when you hover over it. The button that
|
|
249
|
+
puts the column away stands next to it and is cut from the same cloth. */
|
|
250
|
+
.column .add,
|
|
251
|
+
.column > h2 .fold {
|
|
200
252
|
padding: 0;
|
|
201
253
|
width: 19px;
|
|
202
254
|
height: 19px;
|
|
@@ -211,8 +263,17 @@ body {
|
|
|
211
263
|
align-items: center;
|
|
212
264
|
justify-content: center;
|
|
213
265
|
}
|
|
214
|
-
.column .add svg
|
|
266
|
+
.column .add svg,
|
|
267
|
+
.column > h2 .fold svg { width: 15px; height: 15px; display: block; }
|
|
215
268
|
.column .add:hover { opacity: 1; background: rgba(0, 0, 0, .07); }
|
|
269
|
+
/* And next to it, the one that puts the column away. It stands there in plain sight, exactly as
|
|
270
|
+
faint as the `+`: a button that only comes out on hover is a button nobody knows exists. Where
|
|
271
|
+
the column cannot be put away it goes fainter still and stops answering — it does not leave,
|
|
272
|
+
because a column runs and goes back to your turn every few seconds and the button would spend
|
|
273
|
+
the session blinking. */
|
|
274
|
+
.column > h2 .fold:hover { opacity: 1; background: rgba(0, 0, 0, .07); }
|
|
275
|
+
.column > h2 .fold[disabled] { opacity: .12; cursor: default; }
|
|
276
|
+
.column > h2 .fold[disabled]:hover { opacity: .12; background: none; }
|
|
216
277
|
|
|
217
278
|
.empty {
|
|
218
279
|
position: absolute;
|
|
@@ -278,6 +339,10 @@ body {
|
|
|
278
339
|
}
|
|
279
340
|
|
|
280
341
|
/* ── Cards ───────────────────────────────────────────────────────────── */
|
|
342
|
+
/* A square, at most. Text that keeps coming does not push the card taller — a post-it that grows
|
|
343
|
+
with what is written on it stops being a post-it and becomes a page. Everything but the body is
|
|
344
|
+
`flex: none`, so the repository, the title, the age and the buttons are always there to read;
|
|
345
|
+
the body is what gives way. */
|
|
281
346
|
.postit {
|
|
282
347
|
background: var(--BACKLOG);
|
|
283
348
|
border-radius: 2px;
|
|
@@ -287,6 +352,16 @@ body {
|
|
|
287
352
|
cursor: default;
|
|
288
353
|
transition: transform .12s ease, box-shadow .12s ease;
|
|
289
354
|
position: relative;
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
max-height: var(--card-w);
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
}
|
|
360
|
+
.postit > * { flex: none; }
|
|
361
|
+
.postit .body {
|
|
362
|
+
flex: 1 1 auto;
|
|
363
|
+
min-height: 0;
|
|
364
|
+
overflow: hidden;
|
|
290
365
|
}
|
|
291
366
|
.postit:hover { box-shadow: 3px 6px 14px rgba(0, 0, 0, .24); z-index: 2; }
|
|
292
367
|
.postit.ASK { background: var(--ASK); }
|
|
@@ -321,6 +396,12 @@ body {
|
|
|
321
396
|
font-size: .94rem;
|
|
322
397
|
letter-spacing: 0;
|
|
323
398
|
word-break: break-word;
|
|
399
|
+
/* Two lines and an ellipsis. A session name is a name: if it does not fit in two lines of
|
|
400
|
+
monospace it was never going to be read at a glance anyway. */
|
|
401
|
+
display: -webkit-box;
|
|
402
|
+
-webkit-line-clamp: 2;
|
|
403
|
+
-webkit-box-orient: vertical;
|
|
404
|
+
overflow: hidden;
|
|
324
405
|
}
|
|
325
406
|
/* What the card is about. On imported sessions it is the only thing that says what happened in
|
|
326
407
|
there, so it sits right under the title — and in italics, so it is not mistaken for the prompt,
|
|
@@ -331,7 +412,7 @@ body {
|
|
|
331
412
|
color: rgba(0, 0, 0, .56);
|
|
332
413
|
margin-top: 6px;
|
|
333
414
|
display: -webkit-box;
|
|
334
|
-
-webkit-line-clamp:
|
|
415
|
+
-webkit-line-clamp: 2;
|
|
335
416
|
-webkit-box-orient: vertical;
|
|
336
417
|
overflow: hidden;
|
|
337
418
|
}
|
|
@@ -342,7 +423,7 @@ body {
|
|
|
342
423
|
/* The prompt's line breaks stay line breaks: a list has to read like a list. */
|
|
343
424
|
white-space: pre-wrap;
|
|
344
425
|
display: -webkit-box;
|
|
345
|
-
-webkit-line-clamp:
|
|
426
|
+
-webkit-line-clamp: 3;
|
|
346
427
|
-webkit-box-orient: vertical;
|
|
347
428
|
overflow: hidden;
|
|
348
429
|
}
|
|
@@ -367,11 +448,11 @@ body {
|
|
|
367
448
|
display: flex;
|
|
368
449
|
align-items: center;
|
|
369
450
|
gap: 4px;
|
|
370
|
-
padding:
|
|
451
|
+
padding: 4px 0;
|
|
371
452
|
min-width: 0;
|
|
372
453
|
}
|
|
373
454
|
.column.repos .name {
|
|
374
|
-
font: 500 1.
|
|
455
|
+
font: 500 1.35rem/1.25 var(--hand);
|
|
375
456
|
color: var(--muted);
|
|
376
457
|
min-width: 0;
|
|
377
458
|
overflow-wrap: anywhere;
|
|
@@ -379,6 +460,20 @@ body {
|
|
|
379
460
|
.column.repos .repo:hover .name { color: var(--ink); }
|
|
380
461
|
.column.repos .git { margin-left: auto; }
|
|
381
462
|
|
|
463
|
+
/* ── Old ──────────────────────────────────────────────────────────────
|
|
464
|
+
The repositories that do have work on the board, just not lately. Built like `Others` next to
|
|
465
|
+
it, because it is the same gesture — one click from being a column again — with two things
|
|
466
|
+
`Others` has nothing to say about: how much work is parked in there, and how long it has been
|
|
467
|
+
quiet. The name is a button here, not a label: it is what brings the column back. */
|
|
468
|
+
.column.old .repo { cursor: pointer; }
|
|
469
|
+
.column.old .name {
|
|
470
|
+
padding: 0;
|
|
471
|
+
border: none;
|
|
472
|
+
background: none;
|
|
473
|
+
text-align: left;
|
|
474
|
+
cursor: pointer;
|
|
475
|
+
}
|
|
476
|
+
|
|
382
477
|
/* ── The git mark ─────────────────────────────────────────────────────
|
|
383
478
|
One symbol: the dot if there is work that is nowhere yet, the arrow if it is committed but not
|
|
384
479
|
pushed, the tick if everything is safe. The detail is in the title, which comes out on hover.
|
|
@@ -410,6 +505,38 @@ body {
|
|
|
410
505
|
.git.none { color: rgba(0, 0, 0, .32); }
|
|
411
506
|
.column > h2 .git { margin-left: 8px; vertical-align: middle; }
|
|
412
507
|
|
|
508
|
+
/* ── The dev server ───────────────────────────────────────────────────
|
|
509
|
+
The last thing on the right, after the git mark: is this repository's site up. Grey is off,
|
|
510
|
+
green is up — the same green as the git tick, because it means the same thing — and red is
|
|
511
|
+
tried and could not. One click switches it, two restart it.
|
|
512
|
+
|
|
513
|
+
While it is coming up it breathes rather than spins. Breathing is already what a card does
|
|
514
|
+
while a session is working (see `.postit.WORKING` above), and there is no reason for the board
|
|
515
|
+
to have two different ways of saying the same thing. */
|
|
516
|
+
.column > h2 .globe {
|
|
517
|
+
flex: none;
|
|
518
|
+
padding: 0;
|
|
519
|
+
margin-left: 7px;
|
|
520
|
+
width: 19px;
|
|
521
|
+
height: 19px;
|
|
522
|
+
border: none;
|
|
523
|
+
background: none;
|
|
524
|
+
border-radius: 5px;
|
|
525
|
+
display: inline-flex;
|
|
526
|
+
align-items: center;
|
|
527
|
+
justify-content: center;
|
|
528
|
+
cursor: pointer;
|
|
529
|
+
color: rgba(0, 0, 0, .32);
|
|
530
|
+
}
|
|
531
|
+
.column > h2 .globe svg { width: 14px; height: 14px; display: block; }
|
|
532
|
+
.column > h2 .globe:hover { background: rgba(0, 0, 0, .07); }
|
|
533
|
+
.column > h2 .globe.up { color: var(--calm); }
|
|
534
|
+
.column > h2 .globe.failed { color: var(--alert); }
|
|
535
|
+
.column > h2 .globe.starting { color: var(--muted); cursor: default; animation: pulse 1.4s ease-in-out infinite; }
|
|
536
|
+
.column > h2 .globe[disabled] { cursor: default; }
|
|
537
|
+
.column > h2 .globe[disabled]:hover { background: none; }
|
|
538
|
+
@keyframes pulse { 50% { opacity: .3; } }
|
|
539
|
+
|
|
413
540
|
/* ── What a session weighs ────────────────────────────────────────────
|
|
414
541
|
At the head of the card, after the repository name and the git mark: repository · changes ·
|
|
415
542
|
load. Grey like the rest of the row — the colour comes from the gauge at the top — except for
|
|
@@ -468,6 +595,19 @@ body {
|
|
|
468
595
|
background: rgba(255, 255, 255, .72);
|
|
469
596
|
border-color: rgba(0, 0, 0, .14);
|
|
470
597
|
}
|
|
598
|
+
/* The one you reach for rarely: a link at the end of the row, so it is there without competing
|
|
599
|
+
with the button next to it. */
|
|
600
|
+
.postit .actions button.link {
|
|
601
|
+
font-weight: 500;
|
|
602
|
+
padding: 6px 2px;
|
|
603
|
+
background: none;
|
|
604
|
+
border-color: transparent;
|
|
605
|
+
box-shadow: none;
|
|
606
|
+
text-decoration: underline;
|
|
607
|
+
text-underline-offset: 3px;
|
|
608
|
+
opacity: .55;
|
|
609
|
+
}
|
|
610
|
+
.postit .actions button.link:hover, .postit .actions button.link:focus-visible { opacity: 1; }
|
|
471
611
|
|
|
472
612
|
/* ── Editor ───────────────────────────────────────────────────────────
|
|
473
613
|
The dialog and its buttons live in base.css, which belongs to both pages: the file viewer opens
|
|
@@ -545,9 +685,18 @@ html[data-driving] .modes button svg { width: 27px; height: 27px; }
|
|
|
545
685
|
html[data-driving] .column .add { width: 26px; height: 26px; }
|
|
546
686
|
html[data-driving] .column .add svg { width: 20px; height: 20px; }
|
|
547
687
|
|
|
688
|
+
/* Putting a column away: the same target as the `+`, and it grows with it. */
|
|
689
|
+
html[data-driving] .column > h2 .fold { width: 26px; height: 26px; }
|
|
690
|
+
html[data-driving] .column > h2 .fold svg { width: 20px; height: 20px; }
|
|
691
|
+
html[data-driving] .column > h2 .fold[disabled] { opacity: .12; }
|
|
692
|
+
|
|
548
693
|
/* The git lens: it is the door to the files, and at 12px it is a target for sitting still. */
|
|
549
694
|
html[data-driving] .git svg { width: 18px; height: 18px; }
|
|
550
695
|
|
|
696
|
+
/* The globe, which is a switch and so needs a target rather than a glyph. */
|
|
697
|
+
html[data-driving] .column > h2 .globe { width: 26px; height: 26px; }
|
|
698
|
+
html[data-driving] .column > h2 .globe svg { width: 20px; height: 20px; }
|
|
699
|
+
|
|
551
700
|
/* The corner pencil. As well as growing, it stays visible: normally it appears on hover, and a
|
|
552
701
|
target that expects to be found before it will show itself is, in driving mode, not a target. */
|
|
553
702
|
html[data-driving] .postit .corner { width: 34px; height: 34px; opacity: 1; }
|
|
@@ -557,5 +706,6 @@ html[data-driving] .postit .corner svg { width: 20px; height: 20px; }
|
|
|
557
706
|
html[data-driving] .zoom button { width: 40px; height: 40px; }
|
|
558
707
|
html[data-driving] .zoom button svg { width: 22px; height: 22px; }
|
|
559
708
|
|
|
560
|
-
/* The
|
|
561
|
-
|
|
709
|
+
/* The card is in pixels too: with the text at 22 it would choke inside 268. And since the card is
|
|
710
|
+
never taller than it is wide, raising this raises the ceiling with it. */
|
|
711
|
+
html[data-driving] { --card-w: 340px; }
|