k0-board 0.3.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.
Files changed (82) hide show
  1. package/.claude/skills/k0-changelog/SKILL.md +2 -2
  2. package/.claude/skills/k0-discuss/SKILL.md +105 -0
  3. package/.claude/skills/k0-discuss/references/rounds.md +280 -0
  4. package/.claude/skills/k0-epic/SKILL.md +196 -0
  5. package/.claude/skills/k0-links/.claude-plugin/plugin.json +6 -0
  6. package/.claude/skills/k0-links/hooks/hooks.json +1 -0
  7. package/.claude/skills/k0-links/hooks/links.js +128 -0
  8. package/.claude/skills/k0-links/hooks/register.js +204 -0
  9. package/.claude/skills/k0-next/SKILL.md +82 -0
  10. package/.claude/skills/k0-order/SKILL.md +123 -0
  11. package/.claude/skills/k0-plan/SKILL.md +156 -0
  12. package/.claude/skills/k0-split/SKILL.md +169 -0
  13. package/.claude/skills/k0-story/SKILL.md +108 -0
  14. package/.claude/skills/k0-ultracode/SKILL.md +251 -0
  15. package/.claude/skills/k0-ultracode/references/agent-brief.md +71 -0
  16. package/.claude/skills/k0-ultracode/references/modes.md +49 -0
  17. package/.claude/skills/k0-verify/SKILL.md +219 -0
  18. package/.claude/skills/k0-whatsnew/SKILL.md +95 -0
  19. package/.claude/skills/k0-work/SKILL.md +212 -0
  20. package/CHANGELOG.md +426 -1
  21. package/README.md +1021 -119
  22. package/bin/k0-board.js +81 -11
  23. package/menubar/K0MenuBar.swift +35 -5
  24. package/package.json +13 -1
  25. package/platform/contract.js +37 -6
  26. package/platform/darwin/index.js +1 -0
  27. package/platform/darwin/shell.js +4 -1
  28. package/platform/darwin/terminal.js +230 -56
  29. package/platform/index.js +3 -1
  30. package/platform/linux/index.js +14 -0
  31. package/platform/linux/shell.js +10 -1
  32. package/platform/linux/tray.py +10 -10
  33. package/platform/shared/command.js +19 -3
  34. package/platform/shared/tmux.js +32 -14
  35. package/platform/win32/index.js +6 -2
  36. package/platform/win32/shell.js +4 -1
  37. package/platform/win32/terminal.js +24 -8
  38. package/platform/win32/tray.ps1 +13 -13
  39. package/server/backlog.js +618 -0
  40. package/server/changelog.js +13 -13
  41. package/server/db.js +1800 -146
  42. package/server/files.js +156 -26
  43. package/server/git.js +53 -14
  44. package/server/idle.js +15 -13
  45. package/server/index.js +1286 -189
  46. package/server/launcher.js +122 -14
  47. package/server/machine.js +10 -1
  48. package/server/mirror.js +1048 -0
  49. package/server/prices.js +59 -0
  50. package/server/projects.js +57 -12
  51. package/server/servers.js +22 -1
  52. package/server/sessions.js +5 -5
  53. package/server/settings.js +25 -2
  54. package/server/update.js +324 -0
  55. package/server/usage.js +642 -0
  56. package/server/watcher.js +67 -13
  57. package/server/worktree.js +600 -0
  58. package/server/writer.js +98 -26
  59. package/web/awake.js +62 -0
  60. package/web/base.css +4 -0
  61. package/web/board.css +404 -16
  62. package/web/board.js +1182 -152
  63. package/web/changelog.js +3 -3
  64. package/web/conf.js +118 -0
  65. package/web/files.css +206 -1
  66. package/web/files.html +10 -0
  67. package/web/files.js +533 -75
  68. package/web/fuzzy.js +73 -21
  69. package/web/index.html +109 -12
  70. package/web/json.js +145 -0
  71. package/web/list.css +536 -0
  72. package/web/list.js +1015 -0
  73. package/web/mentions.js +27 -9
  74. package/web/recency.js +10 -10
  75. package/web/units.js +1 -1
  76. package/web/usage.css +169 -0
  77. package/web/usage.html +78 -0
  78. package/web/usage.js +447 -0
  79. package/web/view.js +22 -3
  80. package/web/whatsnew.css +211 -0
  81. package/web/whatsnew.html +49 -0
  82. package/web/whatsnew.js +339 -0
package/web/list.css ADDED
@@ -0,0 +1,536 @@
1
+ /* ── The list ─────────────────────────────────────────────────────────────────
2
+ The dashboard's other shape. The kanban is for working — one note at a time, in front of you;
3
+ this is for planning — everything at once, in the order you put it in, with what has been
4
+ decided and what has been checked counted on the row.
5
+
6
+ It is drawn in the same paper and the same ink as the board, deliberately: the same type scale,
7
+ the same line, the same restraint. What it must not become is a table out of another
8
+ application, and the two things that would have made it one are a grid of borders and a column
9
+ for every fact there is. There are eight columns and no rules between them.
10
+
11
+ The variables come from base.css, through board.css, which imports this file. */
12
+
13
+ /* ── Out of the pan and zoom ─────────────────────────────────────────────────
14
+ The board does not scroll, it moves: `view.js` writes a transform on `#board` and `#viewport` is
15
+ a fixed window with no bars. None of that is right for a list. The transform stays on the
16
+ element — it is written inline, which is why this needs `!important` — and the viewport becomes
17
+ an ordinary scroller. The zoom and the shadows along the edges go with it: on a page that
18
+ scrolls they are controls that answer nothing. */
19
+ .viewport.list { overflow: auto; }
20
+ .viewport.list .zoom, .viewport.list .edges { display: none; }
21
+ .board.list {
22
+ transform: none !important;
23
+ width: 100%;
24
+ display: grid;
25
+ grid-template-columns: minmax(0, 1fr);
26
+ gap: 26px;
27
+ align-items: start;
28
+ padding: 22px 26px 60px;
29
+ }
30
+ /* With a story open the rows give up a third of the width. Beside them and not underneath: what
31
+ scrolled away at the bottom of the old page is the one thing that was complained about. */
32
+ .board.list.open { grid-template-columns: minmax(0, 1fr) minmax(22rem, 34rem); }
33
+ /* Too narrow for two columns, and the promise still holds: the panel comes over the right-hand
34
+ side of the rows instead of dropping under them. Underneath is the old page again — opening a
35
+ story would push the list off the screen — and a narrow window is where that hurts most. */
36
+ @media (max-width: 1100px) {
37
+ .board.list.open { grid-template-columns: minmax(0, 1fr); }
38
+ #panel {
39
+ position: fixed;
40
+ z-index: 5;
41
+ top: var(--head, 96px);
42
+ right: 0;
43
+ bottom: 0;
44
+ width: min(34rem, 92vw);
45
+ max-height: none;
46
+ border-radius: 14px 0 0 14px;
47
+ border-right: none;
48
+ box-shadow: -14px 0 34px rgba(0, 0, 0, .16);
49
+ }
50
+ }
51
+
52
+ /* ── The rows ────────────────────────────────────────────────────────────────- */
53
+ .rows { min-width: 0; }
54
+
55
+ /* The repository is a heading and never a column. It has to take as little room as it does on the
56
+ board, where it is one hand-written word above a stack of notes — a column of repository names
57
+ repeated down a hundred rows would be the same word written a hundred times. */
58
+ .rows .repo {
59
+ display: flex;
60
+ align-items: baseline;
61
+ gap: 10px;
62
+ margin: 22px 0 6px;
63
+ padding-bottom: 5px;
64
+ border-bottom: 1px solid var(--line);
65
+ }
66
+ .rows section:first-child .repo { margin-top: 0; }
67
+ .rows .repo h2 { margin: 0; font: 600 1.4rem/1 var(--hand); color: #5f5a4e; }
68
+ .rows .repo small { margin-left: auto; font-size: .76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
69
+
70
+ /* ── An epic ─────────────────────────────────────────────────────────────────
71
+ A row that opens and closes, with how far it has got and — while somebody is being asked
72
+ questions in a terminal — which round the discussion is on. That last line is the only thing
73
+ moving on an epic that has not turned into stories yet, which is most of an epic's life. */
74
+ .rows .epic {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 10px;
78
+ width: 100%;
79
+ margin-top: 8px;
80
+ padding: 5px 8px;
81
+ border: none;
82
+ background: none;
83
+ border-radius: 7px;
84
+ text-align: left;
85
+ font: inherit;
86
+ color: var(--ink);
87
+ cursor: pointer;
88
+ }
89
+ .rows .epic:hover { background: #fbf8ee; border: none; }
90
+ .rows .epic .caret { flex: none; width: 12px; color: var(--muted); font-size: .8rem; line-height: 1; }
91
+ .rows .epic .key { flex: none; font: 700 .78rem var(--mono); color: hsl(var(--h) 52% 32%); }
92
+ .rows .epic .name {
93
+ min-width: 0;
94
+ overflow: hidden;
95
+ text-overflow: ellipsis;
96
+ white-space: nowrap;
97
+ font-size: .95rem;
98
+ font-weight: 600;
99
+ }
100
+ /* Done over total, no weighting: eleven stories with one finished is a bar one eleventh along,
101
+ however big that one was. Anything else is an estimate with a bar drawn round it. */
102
+ .rows .epic .prog {
103
+ flex: none;
104
+ margin-left: auto;
105
+ width: 88px;
106
+ height: 5px;
107
+ border-radius: 999px;
108
+ background: hsl(var(--h) 28% 88%);
109
+ overflow: hidden;
110
+ }
111
+ .rows .epic .prog i { display: block; height: 100%; background: hsl(var(--h) 52% 46%); }
112
+ .rows .epic .count { flex: none; font-size: .76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
113
+ /* Where there is no bar to push it over — an epic written from a story that merely names it, whose
114
+ progress nothing on this screen knows — the count takes the bar's place at the right. */
115
+ .rows .epic .count.push { margin-left: auto; }
116
+ .rows .epic .round { flex: none; font-size: .76rem; color: var(--muted); }
117
+ /* The group with no epic. It is a heading and not a card: there is no epic to say anything about. */
118
+ .rows .epic.loose .key, .rows .epic.loose .name { color: var(--muted); }
119
+
120
+ /* ── A story ─────────────────────────────────────────────────────────────────
121
+ Seven things and nothing else: what it is called, what it is, how far it has got, whether it is
122
+ flagged, what has been decided, what has been checked, and the one thing to do with it next.
123
+ Every column that was proposed after those seven was a column somebody would have read once. */
124
+ .rows .story {
125
+ display: grid;
126
+ /* The last track is a fixed width and not `auto`: the suggestion is a different length from one
127
+ row to the next — "Discuss" against "Resume" — and an auto track made each row set its own
128
+ right edge, so the state pills walked about instead of standing in a column. It is as wide as
129
+ the longest word that can land in it and not a millimetre more. */
130
+ grid-template-columns: 3.4rem minmax(0, 1fr) 5.6rem 1.2rem 3.4rem 3.4rem 5.4rem;
131
+ align-items: center;
132
+ gap: 0 10px;
133
+ padding: 4px 8px 4px 30px;
134
+ border-radius: 7px;
135
+ cursor: pointer;
136
+ }
137
+ .rows .story:hover { background: #fbf8ee; }
138
+ .rows .story.on { background: var(--bg); }
139
+ /* A story whose last counter-check found a decision broken. The mark is on the row itself and not
140
+ inside a column: it is the one thing here that has to be impossible to scroll past. */
141
+ .rows .story.broken { box-shadow: inset 3px 0 0 var(--alert); }
142
+ .rows .story .k { font: 600 .8rem var(--mono); }
143
+ /* The same face the title has on a post-it, which is the one piece of type that carries a note.
144
+ Everything else on this row is already mono or a board colour, and a proportional title in the
145
+ middle of it was the list reading as a table out of another application — and switching from the
146
+ kanban re-setting every title somebody had just been looking at. */
147
+ .rows .story .title {
148
+ min-width: 0;
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+ white-space: nowrap;
152
+ font: 600 .84rem var(--mono);
153
+ }
154
+ /* A task sits under its story, and the indent is how far down the tree it is. */
155
+ .rows .story .title .in { display: inline-block; padding-left: calc(var(--deep, 0) * 15px); }
156
+ .rows .story .flag { color: var(--warn); display: flex; }
157
+ .rows .story .flag svg { width: 13px; height: 13px; display: block; }
158
+ .rows .story .num { font: 400 .76rem var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
159
+ /* The decisions still standing, and how many the last run kept. Red the moment one is broken. */
160
+ .rows .story .num.bad { color: var(--alert); font-weight: 700; }
161
+ /* Always there, never on hover. This one column is the reason the list exists: you come here to
162
+ see what to do next across everything at once, and a suggestion you have to go looking for with
163
+ the pointer, one row at a time, is a suggestion that may as well not be written down. */
164
+ .rows .story .go {
165
+ padding: 3px 11px;
166
+ font-size: .78rem;
167
+ font-weight: 600;
168
+ white-space: nowrap;
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ justify-self: end;
172
+ max-width: 100%;
173
+ }
174
+ /* One suggested button per story, and it waits until you are on the row. A column of them down a
175
+ hundred rows would be a hundred things asking to be pressed, which is the same as none. It stays
176
+ out on the row that is open, because that is the one being looked at. */
177
+
178
+ /* Dragging: a story is picked up to be put in a different place in the order, or into a different
179
+ epic. It never changes state — states are the pills at the top and the road a story walks, not
180
+ somewhere it is dropped. */
181
+ .rows .story.dragging { opacity: .4; }
182
+ .rows .story.over { box-shadow: inset 0 2px 0 var(--ink); }
183
+ .rows .epic.over { background: hsl(var(--h) 48% 93%); box-shadow: inset 0 0 0 1px hsl(var(--h) 40% 70%); }
184
+ /* The group at the end has no epic and so no hue: without this the whole declaration above is
185
+ invalid on it, and the one target on the page that means "take it out of its epic" lights up
186
+ for nobody. */
187
+ .rows .epic.loose.over { background: var(--bg); box-shadow: inset 0 0 0 1px var(--line); }
188
+
189
+ .rows .none { color: var(--muted); font-size: .86rem; margin: 4px 0 4px 30px; }
190
+
191
+ /* ── The panel ───────────────────────────────────────────────────────────────
192
+ Whatever row you clicked, whole, beside the list rather than under it. It scrolls on its own and
193
+ sticks to the top of the view: the reason the old page was thrown away is that opening a story
194
+ pushed the table off the screen, and then you were reading a page instead of working a backlog.
195
+
196
+ From here down the drawing is the old page's, lifted rather than written again. It took a long
197
+ time to get a broken decision to be impossible to miss, and rewriting that from memory would
198
+ have lost exactly the details that do it. */
199
+ #panel {
200
+ position: sticky;
201
+ top: 0;
202
+ /* The height of the scroller it is sticking inside, measured by `list.js` rather than written
203
+ down here: what is above it is the top bar plus, inside an epic, the lane — two things that
204
+ change height on their own, one of them with the width of the window. A number kept in step
205
+ with an unrelated stylesheet is wrong the first time either of them moves, and wrong the way
206
+ that hides the bottom of a panel below the edge of the screen. */
207
+ max-height: calc(var(--port, 100vh) - 22px);
208
+ overflow-y: auto;
209
+ background: var(--paper);
210
+ border: 1px solid var(--line);
211
+ border-radius: 14px;
212
+ padding: 20px 22px 24px;
213
+ min-width: 0;
214
+ }
215
+ #panel[hidden] { display: none; }
216
+ #panel .none { color: var(--muted); font-size: .9rem; margin: 6px 0; }
217
+
218
+ #panel .head { position: relative; padding-right: 40px; border-bottom: 1px solid var(--line); padding-bottom: 14px; }
219
+ #panel .who { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
220
+ #panel .who .key { font: 700 1rem var(--mono); }
221
+ #panel .who h2 { margin: 0; font: 600 1.3rem/1.25 var(--sans); }
222
+ #panel .who .flag { color: var(--warn); display: flex; align-items: center; }
223
+ #panel .who .flag svg { width: 15px; height: 15px; display: block; }
224
+ #panel .where { margin: 6px 0 0; font-size: .82rem; color: var(--muted); }
225
+ #panel .live { display: flex; align-items: center; gap: 9px; margin: 10px 0 0; font-size: .84rem; flex-wrap: wrap; }
226
+ #panel .live b { font-weight: 700; }
227
+ #panel .x {
228
+ position: absolute;
229
+ top: -4px;
230
+ right: -6px;
231
+ width: 30px;
232
+ height: 30px;
233
+ padding: 0;
234
+ border: 0;
235
+ background: none;
236
+ border-radius: 999px;
237
+ color: var(--muted);
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ }
242
+ #panel .x svg { width: 17px; height: 17px; display: block; }
243
+ #panel .x:hover { background: var(--bg); color: var(--ink); border: 0; }
244
+
245
+ #panel .block { margin-top: 22px; }
246
+ #panel .block h3, #panel .counter h3 {
247
+ margin: 0 0 10px;
248
+ font-size: .72rem;
249
+ font-weight: 700;
250
+ letter-spacing: .09em;
251
+ text-transform: uppercase;
252
+ color: var(--muted);
253
+ }
254
+ #panel .block h3 small { margin-left: 8px; font-variant-numeric: tabular-nums; letter-spacing: 0; }
255
+
256
+ /* ── The six states, as a pill ───────────────────────────────────────────────
257
+ The colours are the board's own, out of base.css: nothing new was invented for this. */
258
+ .pill {
259
+ display: inline-block;
260
+ padding: 1px 9px;
261
+ border-radius: 999px;
262
+ background: var(--BACKLOG);
263
+ font-size: .74rem;
264
+ font-weight: 600;
265
+ white-space: nowrap;
266
+ }
267
+ .pill[data-state='Discussed'] { background: var(--IDLE); }
268
+ .pill[data-state='Planned'] { background: var(--PLANNED); }
269
+ .pill[data-state='Working'] { background: var(--WORKING); }
270
+ .pill[data-state='Review'] { background: var(--ASK); }
271
+ .pill[data-state='Done'] { background: var(--COMPLETED); }
272
+
273
+ /* What the live session is doing, which is the other axis entirely. */
274
+ .sess {
275
+ display: inline-block;
276
+ padding: 1px 9px;
277
+ border-radius: 999px;
278
+ background: var(--BACKLOG);
279
+ font: 600 .7rem var(--mono);
280
+ letter-spacing: .04em;
281
+ }
282
+ .sess[data-status='WORKING'] { background: var(--WORKING); }
283
+ .sess[data-status='PLANNING'] { background: var(--PLANNING); }
284
+ .sess[data-status='PLANNED'] { background: var(--PLANNED); }
285
+ .sess[data-status='ASK'] { background: var(--ASK); }
286
+
287
+ /* What a story waits for. The unfinished ones are red because they are the only ones that mean
288
+ anything: a dependency already done is a note about the past. */
289
+ #panel .dep {
290
+ border: 1px solid var(--line);
291
+ background: var(--bg);
292
+ border-radius: 999px;
293
+ padding: 1px 7px;
294
+ font: 600 .72rem var(--mono);
295
+ color: var(--muted);
296
+ cursor: pointer;
297
+ }
298
+ #panel .dep.out { color: var(--alert); border-color: #e7c3c3; background: #fff4f3; }
299
+ /* The two directions on one line under the title: what this story waits for, and what waits for
300
+ it. The second half is the one nothing else in k0 shows, and it is usually the reason to finish
301
+ this story before another. The words are the quiet part; the keys are what you click. */
302
+ #panel .waits { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
303
+ #panel .waits span { font-size: .78rem; color: var(--muted); }
304
+
305
+ /* ── The counter-check ───────────────────────────────────────────────────────
306
+ Directly under the title on every story, whether or not anything is wrong. It is why any of this
307
+ exists: a decision the last run found broken must not be something you have to scroll to find. */
308
+ #panel .counter {
309
+ margin-top: 18px;
310
+ border: 1px solid var(--line);
311
+ border-radius: 12px;
312
+ padding: 14px 16px;
313
+ background: var(--bg);
314
+ }
315
+ #panel .counter p { margin: 0; font-size: .9rem; }
316
+ #panel .counter.broken {
317
+ background: #fff5f4;
318
+ border-color: #e7bcbc;
319
+ box-shadow: inset 4px 0 0 var(--alert);
320
+ }
321
+ #panel .counter.broken h3 { color: var(--alert); }
322
+ #panel .counter .rule { margin-top: 12px; font-size: .84rem; color: #7a3b3b; }
323
+ /* The same sentence-under-a-block shape where nothing is wrong: what an epic's decisions mean for
324
+ the stories under it. Quiet, because it is an explanation and not a warning. */
325
+ #panel .block .rule { margin: 10px 0 0; font-size: .84rem; color: var(--muted); }
326
+
327
+ /* Side by side, which is the whole of it: what was decided, what the verdict was, the evidence.
328
+ `display: contents` puts each row's three pieces straight into the grid, so the three columns
329
+ line up down the whole block without a table. */
330
+ #panel .verdicts { display: grid; grid-template-columns: minmax(0, 2fr) 5rem minmax(0, 1.4fr); }
331
+ #panel .vhead, #panel .verdicts .v { display: contents; }
332
+ #panel .vhead span {
333
+ font-size: .66rem;
334
+ font-weight: 700;
335
+ letter-spacing: .09em;
336
+ text-transform: uppercase;
337
+ color: var(--muted);
338
+ padding: 0 10px 6px 0;
339
+ }
340
+ #panel .verdicts .what, #panel .verdicts .verdict, #panel .verdicts .ev {
341
+ border-top: 1px solid #e7cfcf;
342
+ padding: 9px 10px 9px 0;
343
+ font-size: .88rem;
344
+ min-width: 0;
345
+ }
346
+ #panel .verdicts .what b { display: block; font: 700 .78rem var(--mono); margin-bottom: 2px; }
347
+ #panel .verdicts .verdict {
348
+ color: var(--alert);
349
+ font-weight: 700;
350
+ font-size: .74rem;
351
+ letter-spacing: .06em;
352
+ text-transform: uppercase;
353
+ }
354
+ #panel .verdicts .ev { font: .78rem/1.5 var(--mono); color: var(--muted); overflow-wrap: anywhere; }
355
+ #panel .verdicts .ev i { font-family: var(--sans); }
356
+
357
+ #panel .runs { margin-top: 12px; border-collapse: collapse; font-size: .78rem; font-variant-numeric: tabular-nums; }
358
+ #panel .runs th {
359
+ text-align: left;
360
+ font-size: .64rem;
361
+ letter-spacing: .09em;
362
+ text-transform: uppercase;
363
+ color: var(--muted);
364
+ padding: 0 14px 4px 0;
365
+ }
366
+ #panel .runs td { padding: 2px 14px 2px 0; color: var(--muted); }
367
+ #panel .runs td:first-child { color: var(--ink); font-weight: 600; }
368
+ #panel .runs td.bad { color: var(--alert); font-weight: 700; }
369
+
370
+ /* ── The discussion ──────────────────────────────────────────────────────────- */
371
+ #panel .round { border-left: 2px solid var(--line); padding-left: 15px; margin-bottom: 16px; }
372
+ #panel .round h4 {
373
+ margin: 0 0 6px;
374
+ font: 700 .78rem var(--sans);
375
+ display: flex;
376
+ align-items: baseline;
377
+ gap: 10px;
378
+ }
379
+ #panel .round h4 time { font-weight: 400; font-size: .72rem; color: var(--muted); }
380
+ #panel .round .q, #panel .round .a { position: relative; padding-left: 1.7rem; font-size: .9rem; }
381
+ #panel .round .q::before, #panel .round .a::before {
382
+ position: absolute;
383
+ left: 0;
384
+ top: 0;
385
+ font: 700 .78rem var(--mono);
386
+ color: var(--muted);
387
+ }
388
+ #panel .round .q::before { content: 'Q'; }
389
+ #panel .round .a::before { content: 'A'; }
390
+ #panel .round .a { color: var(--ink); }
391
+ #panel .round .a.waiting { color: var(--muted); font-style: italic; margin: 4px 0 0; }
392
+
393
+ /* The rounds that happened on the epic, before this story existed. Folded away: they are the
394
+ argument behind the decisions, and you open them when the decisions surprise you. */
395
+ #panel .inherited { margin-bottom: 16px; }
396
+ #panel .inherited summary { cursor: pointer; font-size: .8rem; color: var(--muted); list-style: none; }
397
+ #panel .inherited summary::-webkit-details-marker { display: none; }
398
+ #panel .inherited summary::before { content: '▸ '; }
399
+ #panel .inherited[open] summary::before { content: '▾ '; }
400
+ #panel .inherited[open] { padding-bottom: 4px; }
401
+ #panel .inherited .round { margin-top: 12px; border-left-style: dashed; }
402
+
403
+ /* ── The decisions ───────────────────────────────────────────────────────────- */
404
+ #panel .decisions { list-style: none; margin: 0; padding: 0; }
405
+ #panel .decisions li {
406
+ display: grid;
407
+ grid-template-columns: 5rem minmax(0, 1fr);
408
+ gap: 2px 8px;
409
+ padding: 8px 0 8px 8px;
410
+ border-top: 1px solid var(--line);
411
+ font-size: .9rem;
412
+ }
413
+ #panel .decisions li b { font: 700 .78rem var(--mono); color: var(--ink); }
414
+ #panel .decisions li em { grid-column: 2; font-style: normal; font-size: .74rem; color: var(--muted); }
415
+ /* Inherited from the epic: the same rule, in force here, taken somewhere else. */
416
+ #panel .decisions li.up b { color: var(--muted); }
417
+ #panel .decisions li.gone span { text-decoration: line-through; color: var(--muted); }
418
+ #panel .decisions li.bad { background: #fff5f4; box-shadow: inset 3px 0 0 var(--alert); }
419
+ #panel .decisions li.bad b { color: var(--alert); }
420
+
421
+ /* ── The checklist ───────────────────────────────────────────────────────────- */
422
+ #panel .checks { list-style: none; margin: 0; padding: 0; }
423
+ #panel .checks li {
424
+ display: grid;
425
+ grid-template-columns: 1.7rem minmax(0, 1fr) auto;
426
+ gap: 2px 8px;
427
+ align-items: start;
428
+ padding: 6px 0;
429
+ border-top: 1px solid var(--line);
430
+ font-size: .9rem;
431
+ }
432
+ #panel .checks li em {
433
+ grid-column: 2;
434
+ font-style: normal;
435
+ font: .76rem/1.5 var(--mono);
436
+ color: var(--muted);
437
+ overflow-wrap: anywhere;
438
+ }
439
+ #panel .checks li .by {
440
+ font-style: normal;
441
+ font-size: .68rem;
442
+ color: var(--muted);
443
+ text-transform: uppercase;
444
+ letter-spacing: .06em;
445
+ }
446
+ #panel .checks li.pass span { color: var(--muted); }
447
+ #panel .tick {
448
+ width: 19px;
449
+ height: 19px;
450
+ padding: 0;
451
+ border-radius: 5px;
452
+ border: 1px solid var(--line);
453
+ background: #fff;
454
+ font-size: .8rem;
455
+ line-height: 1;
456
+ color: var(--muted);
457
+ cursor: pointer;
458
+ }
459
+ #panel .checks li.pass .tick { background: var(--calm); border-color: var(--calm); color: #fff; }
460
+ #panel .checks li.fail .tick { background: var(--alert); border-color: var(--alert); color: #fff; }
461
+
462
+ /* ── The log ─────────────────────────────────────────────────────────────────- */
463
+ #panel .log { list-style: none; margin: 0; padding: 0; }
464
+ /* When and who on one line, what was written underneath it. On the old page these were three
465
+ columns across the width of the sheet; in a panel a third of the screen wide the third column
466
+ had nowhere left to go, and a paragraph in a ten-character column is not a paragraph. */
467
+ #panel .log li { padding: 8px 0; border-top: 1px solid var(--line); }
468
+ #panel .log li time { font-size: .78rem; color: var(--muted); }
469
+ #panel .log li i { font-style: normal; font: .74rem var(--mono); color: var(--muted); margin-left: 8px; }
470
+ #panel .log li .prose { margin-top: 4px; }
471
+ #panel .sessions { margin: 12px 0 0; font: .74rem var(--mono); color: var(--muted); }
472
+
473
+ /* An epic's own panel: the bar, and the stories it turned into. */
474
+ #panel .bar { height: 4px; border-radius: 2px; background: var(--bg); margin: 14px 0 0; overflow: hidden; }
475
+ #panel .bar i { display: block; height: 100%; background: var(--calm); }
476
+ #panel .mini { list-style: none; margin: 0; padding: 0; }
477
+ #panel .mini li { display: flex; align-items: center; gap: 9px; padding: 3px 0; }
478
+ #panel .mini button {
479
+ display: flex;
480
+ align-items: baseline;
481
+ gap: 9px;
482
+ flex: 1;
483
+ min-width: 0;
484
+ border: 0;
485
+ background: none;
486
+ padding: 0;
487
+ font: inherit;
488
+ font-size: .88rem;
489
+ text-align: left;
490
+ cursor: pointer;
491
+ }
492
+ #panel .mini button:hover { background: none; border: 0; }
493
+ #panel .mini button:hover span { text-decoration: underline; }
494
+ /* The one thing this panel starts. It looks like the suggestion on a row rather than like the
495
+ quiet buttons around it: it opens a terminal, and that is not a thing to press by accident. */
496
+ #panel .run {
497
+ margin-top: 10px;
498
+ padding: 6px 14px;
499
+ border: 1px solid var(--line);
500
+ border-radius: 7px;
501
+ background: var(--paper);
502
+ font: inherit;
503
+ font-size: .84rem;
504
+ cursor: pointer;
505
+ }
506
+ #panel .run:hover { border-color: var(--calm); color: var(--calm); }
507
+ #panel .mini b { font: 600 .78rem var(--mono); }
508
+ #panel .mini span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
509
+
510
+ /* ── Prose ───────────────────────────────────────────────────────────────────
511
+ The same measure and the same rules the ChangeLog keeps: a paragraph is a paragraph on every
512
+ page of k0. */
513
+ #panel .prose { max-width: 44rem; }
514
+ #panel .prose > :first-child { margin-top: 0; }
515
+ #panel .prose > :last-child { margin-bottom: 0; }
516
+ #panel .prose p { margin: 0 0 10px; }
517
+ #panel .prose ul, #panel .prose ol { margin: 0 0 10px; padding-left: 20px; }
518
+ #panel .prose li { margin-bottom: 3px; }
519
+ #panel .prose h1, #panel .prose h2, #panel .prose h3 { margin: 14px 0 7px; font-size: .96rem; }
520
+ #panel .prose code { font: .85em var(--mono); background: var(--bg); border-radius: 4px; padding: 1px 5px; }
521
+ #panel .prose pre { background: var(--bg); border-radius: 8px; padding: 10px 12px; overflow-x: auto; font-size: .8rem; }
522
+ #panel .prose blockquote {
523
+ margin: 0 0 10px;
524
+ padding-left: 12px;
525
+ border-left: 2px solid var(--line);
526
+ color: var(--muted);
527
+ }
528
+ #panel .prose a { color: inherit; }
529
+ #panel .prose table { border-collapse: collapse; font-size: .86rem; margin-bottom: 10px; }
530
+ #panel .prose th, #panel .prose td { border: 1px solid var(--line); padding: 4px 9px; text-align: left; }
531
+
532
+ /* ── Driving mode ────────────────────────────────────────────────────────────
533
+ The list is text, and text goes up with the base size on its own. The one thing that does not is
534
+ the button at the end of a row — it is a target, and a target that only appears when the pointer
535
+ finds it is, from across the room, not a target at all. */
536
+ html[data-driving] .rows .story .go { padding: 5px 14px; }