@lovek4d/kickflip 0.1.0 → 0.3.1

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 (96) hide show
  1. package/CONTEXT.md +177 -78
  2. package/README.md +46 -5
  3. package/SKILL.md +58 -37
  4. package/bin/doctor.mjs +4 -3
  5. package/bin/install.mjs +3 -3
  6. package/bin/kickflip.mjs +87 -32
  7. package/bin/statusline.mjs +4 -3
  8. package/bin/wait-events.mjs +2 -2
  9. package/board/arch-review.mjs +5 -0
  10. package/board/bin/agent.mjs +1 -1
  11. package/board/bin/board.mjs +241 -69
  12. package/board/bin/builder-stop-hook.mjs +4 -3
  13. package/board/domain.mjs +5 -3
  14. package/board/on-host.mjs +1 -0
  15. package/board/public/app-arch.js +75 -0
  16. package/board/public/app-asks.js +33 -14
  17. package/board/public/app-cards.js +242 -49
  18. package/board/public/app-core.js +39 -5
  19. package/board/public/app-header.js +24 -6
  20. package/board/public/app-lanes.js +76 -26
  21. package/board/public/app-needs.js +1 -0
  22. package/board/public/app-snapshot.js +45 -11
  23. package/board/public/app-threads.js +13 -11
  24. package/board/public/app.html +4 -1
  25. package/board/public/app.js +131 -20
  26. package/board/public/dialogs.js +114 -116
  27. package/board/public/hub-kit.js +19 -2
  28. package/board/public/hub.css +24 -18
  29. package/board/public/linear-key.js +141 -0
  30. package/board/public/router.js +6 -3
  31. package/board/public/rules-editor.js +1 -1
  32. package/board/public/shell.js +1 -1
  33. package/board/public/sidebar.css +31 -11
  34. package/board/public/sidebar.js +88 -16
  35. package/board/public/style.css +141 -46
  36. package/board/public/toasts.js +6 -4
  37. package/board/public/ui-cards.js +102 -37
  38. package/board/public/ui-header.js +17 -8
  39. package/board/public/ui-hub.js +30 -12
  40. package/board/public/ui-links.js +18 -10
  41. package/board/public/ui-order.js +18 -0
  42. package/board/public/ui-page-header.js +4 -2
  43. package/board/public/ui-questions.js +37 -16
  44. package/board/public/ui-sidebar.js +49 -11
  45. package/board/public/ui-snapshot.js +15 -9
  46. package/board/public/ui-threads.js +25 -16
  47. package/board/public/ui.js +23 -11
  48. package/board/public/view-hub-settings.js +46 -30
  49. package/board/public/view-hub.js +64 -13
  50. package/board/public/view-notes.js +7 -4
  51. package/board/public/view-polling.js +3 -3
  52. package/board/public/view-questions.js +42 -22
  53. package/board/public/view-settings.js +90 -35
  54. package/board/review.mjs +1 -0
  55. package/board/server.mjs +12 -7
  56. package/board/store.mjs +14 -6
  57. package/board/tickets.mjs +3 -3
  58. package/board/worktree.mjs +14 -13
  59. package/docs/hub-session.md +3 -3
  60. package/hooks-plugin/hooks/codex-share-auto.mjs +7 -5
  61. package/hooks-plugin/hooks/watcher-stop.mjs +41 -0
  62. package/hub/awake.mjs +1 -0
  63. package/hub/board-repo.mjs +1 -1
  64. package/hub/boards.mjs +4 -4
  65. package/hub/close-board.mjs +1 -1
  66. package/hub/duration.mjs +1 -0
  67. package/hub/github.mjs +4 -5
  68. package/hub/home.mjs +1 -1
  69. package/hub/hub.mjs +12 -10
  70. package/hub/linear.mjs +2 -2
  71. package/hub/new-board.mjs +2 -2
  72. package/hub/pr-report.mjs +1 -0
  73. package/hub/push.mjs +2 -2
  74. package/hub/readiness.mjs +2 -0
  75. package/hub/repos.mjs +3 -2
  76. package/hub/restart-board.mjs +1 -0
  77. package/hub/routes.mjs +1 -1
  78. package/hub/schedule.mjs +1 -1
  79. package/hub/sessions.mjs +2 -2
  80. package/hub/shell.mjs +2 -1
  81. package/hub/slack.mjs +2 -0
  82. package/hub/stream.mjs +12 -12
  83. package/hub/sw.js +1 -1
  84. package/hub/trust.mjs +2 -0
  85. package/hub/try.mjs +4 -3
  86. package/hub/upgrade.mjs +1 -0
  87. package/hub/usage.mjs +3 -3
  88. package/hub/views.mjs +1 -1
  89. package/package.json +1 -1
  90. package/shared/colors.css +7 -5
  91. package/shared/fake-gh.mjs +54 -0
  92. package/shared/service.mjs +16 -16
  93. package/shared/skills.mjs +2 -0
  94. package/shared/usage-gate.mjs +1 -1
  95. package/starter/public/claude.js +3 -2
  96. package/bin/web-ts.mjs +0 -2
@@ -11,10 +11,10 @@ a:hover { text-decoration: underline; }
11
11
  button { font: inherit; color: inherit; cursor: pointer; border: 1px solid var(--line); background: var(--panel);
12
12
  border-radius: 7px; padding: 6px 10px; }
13
13
  button:disabled { opacity: .5; cursor: default; }
14
- button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
14
+ button.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
15
15
  a.button { display: inline-block; border: 1px solid var(--line); border-radius: 7px; padding: 6px 10px; background: var(--panel); color: var(--text); text-decoration: none; }
16
16
  /* resolves: the button that clears a card's Action needed (board/domain.mjs resolvedBy), in that badge's notice tone. */
17
- :is(a, button).resolves, button.build { border-color: var(--color-notice); background: var(--color-notice); color: #1c1407; }
17
+ :is(a, button).resolves, button.build, button.unhold, button.closes { border-color: var(--color-notice); background: var(--color-notice); color: #1c1407; }
18
18
  button.ghost { border-color: transparent; background: transparent; font-size: 18px; padding: 2px 8px; }
19
19
  textarea, input { font: inherit; color: inherit; background: var(--panel); border: 1px solid var(--line);
20
20
  border-radius: 8px; padding: 8px 10px; width: 100%; resize: vertical; }
@@ -30,13 +30,13 @@ textarea:focus, input:focus { outline: 2px solid var(--accent-soft); border-colo
30
30
  .brand :is(h1, h2) { font-size: 16px; margin: 0; }
31
31
  .nav { display: flex; align-items: center; gap: 6px; }
32
32
  .pill { border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; font-size: 12px; color: var(--text); }
33
- .pill.hot { background: var(--accent); border-color: var(--accent); color: #fff; }
33
+ .pill.hot { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
34
34
 
35
35
  main { max-width: 860px; margin: 0 auto; padding: 12px 16px 80px; }
36
36
 
37
- .waiting { background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
37
+ .waiting { background: color-mix(in srgb, var(--color-notice) 12%, var(--panel)); border: 1px solid color-mix(in srgb, var(--color-notice) 40%, var(--line));
38
38
  border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }
39
- .waiting h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 6px; color: var(--accent); }
39
+ .waiting h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 6px; color: var(--color-notice); }
40
40
  .waiting ul { margin: 0; padding-left: 18px; }
41
41
  .waiting li { margin: 3px 0; }
42
42
  .waiting li.prompt { list-style: none; margin: 6px 0 10px -18px; padding: 8px 10px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; }
@@ -74,16 +74,18 @@ button.chip { border: 0; }
74
74
  .chip.tone-quiet { color: var(--color-quiet); } .chip.tone-normal { color: var(--color-info); } .chip.tone-notice { color: var(--color-notice); }
75
75
  .chip.tone-danger { color: var(--color-danger); } .chip.tone-ready { color: var(--color-success); }
76
76
  /* Action needed keeps the notice orange; unread takes the accent so the two never read alike. */
77
- .chip.unread { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
78
77
  .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--color-quiet); }
79
78
  .dot.pass { background: var(--color-success); } .dot.pending { background: var(--color-notice); } .dot.fail { background: var(--color-danger); }
80
79
 
81
80
  .status { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--color-quiet); display: inline-block; position: relative; }
82
81
  .status.progress { border-color: var(--color-notice); background: conic-gradient(var(--color-notice) 0 50%, transparent 50% 100%); }
83
- .status.review { border-color: var(--color-success); background: conic-gradient(var(--color-success) 0 75%, transparent 75% 100%); }
84
- .status.todo { border-style: dashed; }
82
+ .status.review { border-color: var(--color-info); background: conic-gradient(var(--color-info) 0 75%, transparent 75% 100%); }
85
83
  .sub.agent { color: var(--color-notice); }
86
- .sub.next { color: var(--color-info); }
84
+ .sub.next { color: var(--color-notice); }
85
+ .card-hold, .card-blocked, .card-action { color: var(--color-notice); }
86
+ .card-ready { color: var(--color-success); }
87
+ .sub.next.idle { color: var(--muted); }
88
+ .sub.refused { color: var(--color-danger); }
87
89
  .toast-stack { position: fixed; left: 50%; bottom: calc(var(--app-bottom, 0px) + 16px + env(safe-area-inset-bottom));
88
90
  width: min(520px, calc(100vw - 32px)); height: 0; transform: translateX(-50%); z-index: 25; pointer-events: none; }
89
91
  .toast-row { position: absolute; top: 0; left: 0; width: 100%; display: flex; justify-content: center;
@@ -93,12 +95,14 @@ button.chip { border: 0; }
93
95
  box-shadow: 0 4px 16px rgba(0,0,0,.2); touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
94
96
  .toast:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
95
97
  @media (prefers-reduced-motion: reduce) { .toast-row { transition: none; } }
96
- .status.done { border-color: var(--color-info); background: var(--color-info); }
98
+ .status.done, .status.asks { border-color: var(--color-quiet); background: var(--color-quiet); }
97
99
 
98
100
  .detail { padding: 4px 12px 14px 38px; }
99
101
  .grid { display: grid; grid-template-columns: 110px 1fr; gap: 4px 10px; font-size: 13px; margin: 6px 0 10px; }
100
102
  .grid dt { color: var(--muted); }
101
103
  .grid dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
104
+ /* A link row's labels open their cards, with room to tap (docs/adr/0156-a-follow-up-is-a-work-card-with-a-parent.md). */
105
+ .grid dd a.card-link { display: inline-block; padding: 6px 4px; margin: -6px 0; text-decoration: underline; text-underline-offset: 2px; }
102
106
  .external-links-label { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
103
107
  .grid .external-links { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 0; }
104
108
  .external-link { white-space: nowrap; }
@@ -113,7 +117,7 @@ button.chip { border: 0; }
113
117
  .thread { border-top: 1px dashed var(--line); padding-top: 8px; }
114
118
  .msg { margin: 8px 0; padding: 7px 10px; border-radius: 10px; max-width: 100%; }
115
119
  .msg.claude { background: var(--claude); }
116
- .msg.love { background: var(--love); }
120
+ .msg.love { background: var(--accent-soft); }
117
121
  .msg .who { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
118
122
  .msg .body { white-space: pre-wrap; overflow-wrap: anywhere; }
119
123
  .msg .reply { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line); }
@@ -124,6 +128,8 @@ button.chip { border: 0; }
124
128
  .thread-more:active { background: var(--accent-soft); }
125
129
  .thread-more:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
126
130
  .thread-more .n { color: var(--muted); font-weight: 400; }
131
+ /* ADR 0144: Done's and Asks' Show more, the thread's look as a row of the lane's list */
132
+ .list > .thread-more { width: 100%; margin: 0; border-top: 1px solid var(--line); border-radius: 0; }
127
133
 
128
134
  @media (max-width: 560px) {
129
135
  .item > summary { grid-template-columns: 16px 1fr; }
@@ -133,19 +139,32 @@ button.chip { border: 0; }
133
139
  }
134
140
  .calls ul { margin: 4px 0 8px; padding-left: 18px; font-size: 13px; }
135
141
  button.link { background: none; border: 0; color: var(--accent); padding: 8px 12px; font-size: 13px; cursor: pointer; }
136
- .waves { display: grid; gap: 6px; margin-top: 4px; }
137
- .wave { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; text-align: left; background: var(--panel);
138
- border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; color: var(--text); }
139
- .wave.on { border-color: var(--accent); background: var(--accent-soft); }
140
- .wave-name { font-weight: 600; font-size: 13px; }
141
- .wave .bar { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
142
- .wave .bar span { display: block; height: 100%; background: var(--color-success); }
142
+ .wave-icon { display: inline-block; vertical-align: -2px; flex: none; }
143
+ .wv-row { padding: 10px 12px; border-top: 1px solid var(--line); min-width: 0; cursor: pointer; }
144
+ .wv-row:first-child { border-top: 0; }
145
+ .wv-row.on { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
146
+ .wv-id { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 8px; min-width: 0; }
147
+ .wv-name { display: inline-flex; align-items: center; gap: 5px; min-height: 30px; min-width: 0; padding: 0; border: 0; background: none;
148
+ font: inherit; font-weight: 600; color: var(--text); text-align: left; overflow-wrap: anywhere; cursor: pointer; }
149
+ .wv-name .wave-icon { color: var(--color-quiet); }
150
+ .wv-row.on .wv-name, .wv-row.on .wv-name .wave-icon { color: var(--accent); }
151
+ .wv-id .muted { white-space: nowrap; }
152
+ .wv-note { margin-top: 4px; font-size: 13px; color: var(--muted); overflow-wrap: anywhere; }
153
+ .wv-note b { color: var(--text); font-weight: 600; }
154
+ .wv-links { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 0; margin-top: 6px; font-size: 13px; }
155
+ .wv-link { border: 0; background: none; padding: 0; min-height: 0; color: var(--accent); font: inherit; cursor: pointer; }
156
+ .wv-end { margin-left: auto; padding-left: 8px; font-size: 13px; white-space: nowrap; }
157
+ .wv-busy { display: inline-flex; align-items: center; gap: 6px; color: var(--color-notice); }
158
+ .wv-showing { margin-top: 14px; }
159
+ .wv-all { border: 0; background: none; padding: 0; color: var(--accent); font: inherit; cursor: pointer; }
143
160
  .nav a.icon { display: inline-flex; align-items: center; padding: 4px; color: var(--muted); }
144
161
  .nav a.icon:hover { color: var(--text); }
145
162
  .refresh { display: inline-flex; align-items: center; gap: 2px; line-height: 1.3; min-width: 0; }
146
163
  .refresh #refresh { padding: 4px; margin-left: -4px; flex: none; }
147
164
  .refresh #status { font-size: 12px; min-width: 0; }
148
165
  .refresh .late { color: var(--color-notice); font-weight: 600; }
166
+ .refresh .down { color: var(--color-danger); font-weight: 600; }
167
+ .refresh .asleep { color: var(--muted); font-weight: 600; }
149
168
  .lane-github { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--muted); text-align: right; }
150
169
  .lane-github .late { color: var(--color-notice); }
151
170
  .msg.other { background: transparent; border: 1px solid var(--line); }
@@ -187,15 +206,15 @@ code { overflow-wrap: anywhere; }
187
206
  .msg .body:empty { display: none; }
188
207
  .note-tools { display: flex; align-items: flex-start; gap: 6px; }
189
208
  .note-tools .pending-files { flex: 1; margin-top: 4px; }
190
- .note-tools .attach { margin-left: auto; margin-top: 2px; }
209
+ .note-tools .attach { margin-top: 2px; }
191
210
 
192
211
  /* Compose button and sheet */
193
- main { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
194
- .fab { position: fixed; z-index: 6; right: calc(16px + env(safe-area-inset-right)); bottom: calc(var(--app-bottom, 0px) + 16px + env(safe-area-inset-bottom));
195
- width: 56px; height: 56px; padding: 0; border-radius: 50%; border: 0; background: var(--accent); color: #fff;
212
+ main { padding-bottom: calc(110px + env(safe-area-inset-bottom)); }
213
+ .fab { position: fixed; z-index: 6; right: calc((16px + max(env(safe-area-inset-right), env(safe-area-inset-bottom))) * 2 / 3); bottom: calc(var(--app-bottom, 0px) + (16px + env(safe-area-inset-bottom)) * 2 / 3);
214
+ width: 75px; height: 75px; padding: 0; border-radius: 50%; border: 0; background: var(--accent); color: var(--on-accent);
196
215
  display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
197
216
  .fab:active { transform: scale(.96); }
198
- .view-board .toast-stack { bottom: calc(var(--app-bottom, 0px) + 80px + env(safe-area-inset-bottom)); }
217
+ .view-board .toast-stack { bottom: calc(var(--app-bottom, 0px) + 94px + env(safe-area-inset-bottom)); }
199
218
  /* A phone sheet pads its content above the keyboard (--kb, dialogs.js fitSheet) and stays on the screen's bottom
200
219
  edge while the keyboard covers the bar: iOS reports its height late, so moving the sheet up leaves a strip below. */
201
220
  .sheet { position: fixed; inset: auto 0 0 0; margin: 0 auto; width: 100%; max-width: 560px;
@@ -235,22 +254,13 @@ main { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
235
254
  @keyframes sheet-in { from { transform: translateY(100%); } }
236
255
  @keyframes sheet-out { to { transform: translateY(100%); } }
237
256
  @keyframes fade-in { from { opacity: 0; } }
238
- .confirm-sheet { position: fixed; inset: 0; margin: 0; width: 100%; max-width: none; height: 100%; max-height: none; border: 0;
239
- display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
240
- gap: 8px; padding: 8px 8px calc(10px + env(safe-area-inset-bottom)); background: rgba(0,0,0,.4); animation: fade-in .18s ease-out; }
241
- .confirm-sheet .box { width: min(520px, 100%); background: var(--panel); border-radius: 14px; overflow: hidden; text-align: center;
242
- box-shadow: 0 10px 40px rgba(0,0,0,.3); animation: sheet-in .22s ease-out; }
243
- .confirm-sheet p { margin: 0; padding: 14px 16px; font-size: 13.5px; line-height: 1.4; color: var(--muted); border-bottom: 1px solid var(--line); }
244
- .confirm-sheet button { display: block; width: 100%; border: 0; border-radius: 0; background: transparent; padding: 14px 6px; font-size: 17px; color: var(--accent); }
245
- .confirm-sheet .cancel { font-weight: 600; }
246
- .confirm-sheet::backdrop { background: transparent; }
247
257
  @media (min-width: 700px) {
248
258
  .sheet { bottom: calc(var(--kb, 0px) + 24px); max-height: calc(var(--vh, 100dvh) - env(safe-area-inset-top) - 12px); padding-bottom: 0;
249
259
  border-bottom: 1px solid var(--line); border-radius: 16px; }
250
260
  .sheet form { padding-bottom: 14px; }
251
261
  }
252
262
  @media (prefers-reduced-motion: reduce) {
253
- .sheet[open], .sheet[open]::backdrop, .sheet.closing, .sheet.closing::backdrop, .confirm-sheet, .confirm-sheet .box { animation: none; }
263
+ .sheet[open], .sheet[open]::backdrop, .sheet.closing, .sheet.closing::backdrop { animation: none; }
254
264
  .fab:active { transform: none; }
255
265
  }
256
266
  /* The hub lives at the tailnet root, outside this site, so its link is root-absolute. */
@@ -270,6 +280,23 @@ main { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
270
280
  .ask-actions { float: right; display: flex; gap: 2px; margin: -6px -6px 0 8px; }
271
281
  .ask-actions .icon { padding: 6px; color: var(--muted); }
272
282
  .ask-actions .icon:disabled { opacity: .35; }
283
+ .ask-actions .icon.bumped { color: var(--color-notice); opacity: 1; }
284
+ .ask-actions .icon.bumped svg { fill: currentColor; }
285
+ /* A tap's bump fades the icon's fill in, and an unbump fades it out, on a toast's timing, which app.js fadeBump sets as --bump-fade */
286
+ .ask-actions .icon { --bump-fade: 0ms; }
287
+ @keyframes bump-in { from { color: var(--muted); } }
288
+ @keyframes bump-fill-in { from { fill: transparent; } }
289
+ @keyframes bump-out { from { color: var(--color-notice); opacity: 1; } }
290
+ @keyframes bump-fill-out { from { fill: var(--color-notice); } to { fill: transparent; } }
291
+ .ask-actions .icon.bump-in { animation: bump-in var(--bump-fade) linear; }
292
+ .ask-actions .icon.bump-in svg { animation: bump-fill-in var(--bump-fade) linear; }
293
+ .ask-actions .icon.bump-out { animation: bump-out var(--bump-fade) linear; }
294
+ .ask-actions .icon.bump-out svg { animation: bump-fill-out var(--bump-fade) linear; }
295
+ /* A work card's empty bump icon always shows on touch screens; with a hover pointer it shows while the card is hovered or holds focus */
296
+ @media (hover: hover) {
297
+ .item button[data-card-bump]:not(.bumped) { opacity: 0; }
298
+ .item:hover button[data-card-bump]:not(.bumped), .item:focus-within button[data-card-bump]:not(.bumped) { opacity: 1; }
299
+ }
273
300
  .working-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--color-notice); margin-right: 5px; vertical-align: 1px; }
274
301
  @media (prefers-reduced-motion: no-preference) { .working-dot { animation: pulse 1.4s ease-in-out infinite; } }
275
302
  form[data-reply] .row .quick { margin-left: auto; margin-right: 6px; }
@@ -283,6 +310,13 @@ button.attach:disabled { opacity: .35; }
283
310
  .line1 .chip { vertical-align: 1px; margin-right: 2px; }
284
311
  .line2.meta { justify-content: flex-start; margin-top: 4px; }
285
312
  .line3 { margin-top: 3px; }
313
+ /* The active bar blinks its fill, not its opacity: an opacity animation gives it its own layer, which Safari can snap a
314
+ pixel off its siblings (docs/adr/0160-a-building-cards-phase-bars-end-its-time-row.md). */
315
+ .card-time .phase-bars { display: inline-flex; gap: 2px; vertical-align: middle; position: relative; top: -1px; }
316
+ .phase-bars i { width: 9px; height: 4px; border-radius: 2px; background: var(--line); }
317
+ .phase-bars i.done, .phase-bars i.now { background: var(--color-notice); }
318
+ @keyframes pulse-fill { 50% { background-color: color-mix(in srgb, var(--color-notice) 35%, transparent); } }
319
+ @media (prefers-reduced-motion: no-preference) { .phase-bars i.now { animation: pulse-fill 1.4s ease-in-out infinite; } }
286
320
  .brand { display: grid; grid-template-columns: auto auto minmax(0, 1fr); column-gap: 10px; row-gap: 2px; }
287
321
  .brand .hub { grid-row: 1 / span 2; align-self: center; }
288
322
  .sub .working { color: var(--color-notice); }
@@ -292,9 +326,20 @@ button.attach:disabled { opacity: .35; }
292
326
  .msg.agent { padding: 0 10px; margin: 4px 0; font-size: 12px; font-weight: 600; overflow-wrap: anywhere; }
293
327
  .msg.agent.started { color: var(--color-notice); } .msg.agent.finished { color: var(--color-success); }
294
328
  .msg.agent .muted { font-weight: 400; }
329
+ /* A planner's line, dot included, is the owner's blue, on the card and in the thread; its finish stays green
330
+ (docs/adr/0167-planners-and-implementers-are-two-kinds-of-agent.md). */
331
+ .sub.agent.planner, .msg.builder-status.planner, .msg.agent.started.planner { color: var(--accent); }
332
+ .planner .working-dot { background: currentColor; }
333
+ .msg.calls { padding: 0 10px; margin: 4px 0; font-size: 12px; overflow-wrap: anywhere; }
334
+ .msg.calls summary { cursor: pointer; color: var(--muted); }
335
+ .msg.calls ol { margin: 4px 0; padding-left: 20px; }
295
336
  .agent-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 5px; vertical-align: 1px; }
296
337
  /* INT-30: card line order */
297
338
  .line5.meta { justify-content: flex-start; margin-top: 4px; }
339
+ .line6 { margin-top: 4px; }
340
+ .card-time time { font-variant-numeric: tabular-nums; }
341
+ .card-unread { color: var(--accent); font-weight: 600; }
342
+ .card-unread .sep { color: var(--muted); font-weight: 400; }
298
343
  /* Lane headers: Linear-style group bands */
299
344
  .lane > summary { display: flex; align-items: center; gap: 8px; margin: 0; min-height: 34px; padding: 5px 12px;
300
345
  background: var(--band); border: 1px solid var(--line); border-radius: 10px; font-size: 13px; font-weight: 500; color: var(--text); }
@@ -305,6 +350,7 @@ button.attach:disabled { opacity: .35; }
305
350
  .lane[open] > summary { border-bottom: 0; border-radius: 10px 10px 0 0; }
306
351
  .lane[open] > .list { border-radius: 0 0 10px 10px; }
307
352
  .lane > summary .count { color: var(--muted); font-weight: 400; }
353
+ /* Each lane's sort icon and its menu, grey (docs/adr/0096-colour-says-whos-waiting.md) */
308
354
  /* INT-35: offline header */
309
355
  .view-board.offline #compose { display: none; }
310
356
  .view-board.offline main > * { opacity: .72; }
@@ -327,6 +373,9 @@ button.attach:disabled { opacity: .35; }
327
373
  .lane-github { display: inline-flex; align-items: center; gap: 2px; }
328
374
  .lane-github .github { display: inline-flex; align-items: center; gap: 4px; }
329
375
  .lane-github .gh-mark { flex: none; }
376
+ .lane-github:has(.remote) { min-width: 0; }
377
+ .lane-github .remote { min-width: 0; }
378
+ .lane-github .remote-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
330
379
  .lane-github .gh-refresh { font-size: 15px; line-height: 1; padding: 2px 4px; margin-right: -6px; color: var(--muted); }
331
380
  .lane-github .gh-refresh[aria-disabled="true"] { opacity: .35; cursor: default; }
332
381
  /* INT-44: both refreshes rest and spin while they fetch */
@@ -342,6 +391,20 @@ button.attach:disabled { opacity: .35; }
342
391
  .top .nav #refresh, .top .nav a.icon { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px; border: 0; line-height: 1; }
343
392
  .top .nav #status { line-height: 1.1; white-space: nowrap; }
344
393
  .top .nav #refresh:disabled, .top .nav a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
394
+ /* ADR 0125: the bulb sits between refresh and the gear, and glows orange while an architecture review runs */
395
+ .top .nav .arch-bulb { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px; border: 0; line-height: 1; color: var(--muted); }
396
+ .top .nav .arch-bulb:hover { color: var(--text); }
397
+ .top .nav .arch-bulb[hidden] { display: none; }
398
+ .top .nav .arch-bulb.running { color: var(--color-notice); }
399
+ .arch-bulb.running svg { filter: drop-shadow(0 0 4px currentColor); }
400
+ @media (prefers-reduced-motion: no-preference) { .arch-bulb.running svg { animation: arch-glow 1.4s ease-in-out infinite; } }
401
+ @keyframes arch-glow { 50% { filter: drop-shadow(0 0 1px currentColor); opacity: .55; } }
402
+ .arch-body { display: grid; gap: 12px; padding-bottom: 6px; }
403
+ .arch-body p { margin: 0; font-size: 13.5px; line-height: 1.45; }
404
+ .arch-last { display: grid; gap: 3px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg); font-size: 13.5px; }
405
+ .arch-last .muted { font-size: 12.5px; }
406
+ .arch-body .arch-waits { color: var(--color-notice); }
407
+ .arch-body .arch-run { width: 100%; min-height: 44px; font-size: 15px; }
345
408
  /* INT-64: offline, the header's time and refresh turn red */
346
409
  .view-board.offline .refresh #refresh, .refresh .link-state.offline { color: var(--color-danger); }
347
410
  .back { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; color: var(--accent); text-decoration: none; }
@@ -354,16 +417,21 @@ button.attach:disabled { opacity: .35; }
354
417
  .top .back:active { background: var(--accent-soft); }
355
418
  .top .brand :is(h1, h2) { font-size: 17px; font-weight: 600; line-height: 22px; }
356
419
  /* The last visit paints dimmed until the fresh board lands; the controls ui-snapshot.js liveControls blocks rest meanwhile.
357
- A changed card, or other changed line, gets a builder-orange border that fades once (app-snapshot.js flashChanged). */
420
+ A changed line, and a card that changed lanes, gets a blue border that fades once; an updated card, open body included, takes
421
+ a blue tint that fades (app-snapshot.js, whose SIGNS holds each animation's length). */
358
422
  main > * { transition: opacity .25s ease; }
359
423
  .view-board[data-board="snapshot"]:not(.offline) main > * { opacity: .62; }
360
424
  .view-board[data-board="snapshot"] :is(.actions button, button[data-plan], button[data-bump], button[data-confirm="bump"], button[data-edit], button[data-withdraw], button[data-check-github], form[data-reply] button[type=submit], form[data-reply] button[data-quick], form[data-reply] button[data-proto], form[data-reply] button[data-confirm], .prompt button) { opacity: .45; pointer-events: none; }
361
- .flash { animation: snapshot-flash 1.6s ease-out 1; border-radius: 6px; }
362
- @keyframes snapshot-flash { from { box-shadow: inset 0 0 0 2px var(--color-notice); } to { box-shadow: inset 0 0 0 2px transparent; } }
425
+ .flash { animation: snapshot-flash 1.6s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
426
+ @keyframes snapshot-flash { from { box-shadow: inset 0 0 0 2px var(--color-info); } to { box-shadow: inset 0 0 0 2px transparent; } }
427
+ .item.tint { animation: card-tint 2.4s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
428
+ @keyframes card-tint { 0%, 25% { background: var(--accent-soft); } 100% { background: transparent; } }
363
429
  .msg .quote { margin: 4px 0 6px; padding: 2px 0 2px 8px; border-left: 2px solid var(--line); color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere; }
364
430
  /* ADR 0040: the GitHub mark opens the Hub links sheet, with a red dot while a link is broken; a new board's setup checklist */
365
431
  .lane-github .gh-links { position: relative; display: inline-flex; align-items: center; padding: 4px; margin: -4px -2px -4px -4px; font-size: inherit; line-height: 1; color: inherit; }
366
432
  .lane-github .links-dot { position: absolute; top: 1px; right: 0; width: 7px; height: 7px; border-radius: 50%; background: var(--color-danger); box-shadow: 0 0 0 1.5px var(--bg); }
433
+ .hold-sheet .hold-lede { margin: 0 0 2px; font-size: 13.5px; line-height: 1.4; }
434
+ .hold-sheet .hub-view .dur-chips .btn { min-height: 44px; font-size: 15px; }
367
435
  .sheet .sheet-body { padding: 10px 16px max(12px, calc(env(safe-area-inset-bottom) - var(--app-bottom, 0px))); }
368
436
  .hub-links { list-style: none; margin: 0; padding: 0; }
369
437
  .hub-links li { display: flex; align-items: baseline; gap: 10px; padding: 9px 0; border-top: 1px solid var(--line); min-width: 0; }
@@ -378,7 +446,7 @@ main > * { transition: opacity .25s ease; }
378
446
  .setup-steps .dot { flex: none; }
379
447
 
380
448
  /* The board's other views (view-settings.js, view-questions.js, view-notes.js) each scope their rules by the class on their root. */
381
- .view-settings .set h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); margin: 18px 0 6px; }
449
+ .view-settings .set h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 18px 0 6px; }
382
450
  .view-settings .who { display: flex; align-items: center; justify-content: space-between; padding: 9px 10px; border: 1px solid var(--line);
383
451
  border-radius: 9px; margin: 6px 0; background: var(--panel); }
384
452
  .view-settings .who.gone { opacity: .5; text-decoration: line-through; }
@@ -398,7 +466,7 @@ main > * { transition: opacity .25s ease; }
398
466
  .view-settings .close-board-sheet h2 { color: var(--text); font-size: 18px; text-transform: none; letter-spacing: normal; }
399
467
  .view-settings .close-board-sheet input { display: block; width: 100%; margin-top: 8px; }
400
468
  .view-settings .close-board-sheet .danger { color: var(--color-danger); }
401
- .view-settings .close-board-warning:has(li) { border: 1px solid var(--color-notice); padding: 0 12px; border-radius: 10px; margin-bottom: 14px; }
469
+ .view-settings .close-board-warning:has(li) { border: 1px solid var(--color-danger); padding: 0 12px; border-radius: 10px; margin-bottom: 14px; }
402
470
  .view-questions .q { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin: 12px 0; }
403
471
  .view-questions .q.urgent { border-color: var(--color-notice); }
404
472
  .view-questions .q h3 { font-size: 15px; margin: 6px 0 4px; line-height: 1.3; }
@@ -411,13 +479,13 @@ main > * { transition: opacity .25s ease; }
411
479
  .view-questions .opt .rec { color: var(--accent); font-weight: 500; font-size: 12px; margin-left: 4px; }
412
480
  .view-questions .opt .det { color: var(--muted); font-size: 13px; }
413
481
  .view-questions .note { margin-top: 4px; }
414
- .view-questions .ans { background: var(--love); border-radius: 9px; padding: 7px 10px; margin-top: 8px; font-size: 13px; }
482
+ .view-questions .ans { background: var(--accent-soft); border-radius: 9px; padding: 7px 10px; margin-top: 8px; font-size: 13px; }
415
483
  .view-questions .rep { background: var(--claude); border-radius: 9px; padding: 7px 10px; margin-top: 6px; font-size: 13px; }
416
484
  .view-questions .q details { margin-top: 8px; }
417
485
  .view-questions .q details > summary { cursor: pointer; color: var(--muted); font-size: 13px; }
418
- .view-questions .topics { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0 8px; margin-bottom: 4px; }
419
- .view-questions .topics button { flex: none; border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; font-size: 13px;
420
- background: var(--panel); color: var(--text); white-space: nowrap; }
486
+ .view-questions .topics { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 8px; margin-bottom: 4px; }
487
+ .view-questions .topics button { min-width: 0; border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; font-size: 13px;
488
+ background: var(--panel); color: var(--text); white-space: normal; text-align: left; overflow-wrap: anywhere; }
421
489
  .view-questions .topics button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
422
490
  .view-questions .topics .n { color: var(--muted); margin-left: 4px; }
423
491
  .view-questions .topichead { margin: 2px 0 6px; }
@@ -432,6 +500,21 @@ main > * { transition: opacity .25s ease; }
432
500
  .view-questions .bar #qstatus { min-width: 0; }
433
501
  .view-questions .bar #qstatus.sent { color: var(--color-success); font-weight: 600; }
434
502
  .view-questions .solo { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin: 4px 0; }
503
+ /* docs/adr/0111-a-prototype-round-ends-with-a-one-take-confirm.md */
504
+ .view-questions .topichead { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
505
+ .view-questions .topichead .try-spin, .view-questions .q .chip .try-spin { width: 9px; height: 9px; margin-right: 5px; vertical-align: -1px; }
506
+ /* A requested question: an accent tint and an accent border drawn inside the card, so the card keeps its size. */
507
+ .view-questions .q.pr-busy { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--panel)); box-shadow: inset 0 0 0 1px var(--accent); }
508
+ .view-questions .q.pr-busy .opt { opacity: .55; cursor: default; }
509
+ .view-questions .q.pr-busy .opt:has(input:checked) { opacity: 1; }
510
+ /* The source line is as tall as the state badge beside it, which a requested card leaves out. */
511
+ .view-questions .q > .row:first-child > .muted { padding: 1px 0; }
512
+ .view-questions .q.pr-busy textarea { opacity: .7; }
513
+ .view-questions .q.pr-busy .proto-ask { color: var(--accent); }
514
+ .view-questions .note-tools .chip { align-self: center; margin-top: 2px; }
515
+ .view-questions .q .pr-open { display: inline-block; margin: 0 0 8px; }
516
+ .view-questions .pr-rule { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; font-size: 12.5px; color: var(--muted); }
517
+ .view-questions .pr-rule svg { flex: none; }
435
518
 
436
519
  /* A question's takes sit in as many columns as keep each at least 380px wide, one on a phone; each its option above its
437
520
  preview, and each row's takes top-aligned, since each frame takes its own page's height. */
@@ -484,7 +567,7 @@ main > * { transition: opacity .25s ease; }
484
567
  .view-notes .plan ul, .view-notes .plan ol { padding-left: 22px; }
485
568
  .view-notes .plan li { margin: 3px 0; }
486
569
  .view-notes .plan li.nested { margin-left: 20px; }
487
- .view-notes .plan code { font-size: 12.5px; background: var(--accent-soft); border-radius: 4px; padding: 0 3px; }
570
+ .view-notes .plan code { font-size: 12.5px; background: var(--chip); border-radius: 4px; padding: 0 3px; }
488
571
  .view-notes .plan pre { overflow-x: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
489
572
  .view-notes .plan pre code { background: none; padding: 0; overflow-wrap: normal; }
490
573
  .view-notes .plan blockquote { border-left: 3px solid var(--line); padding-left: 10px; color: var(--muted); }
@@ -514,14 +597,13 @@ main > * { transition: opacity .25s ease; }
514
597
  border-bottom: 0; border-top: 1px solid var(--line); padding-top: 4px; padding-bottom: max(4px, env(safe-area-inset-bottom));
515
598
  }
516
599
  .top .phone-logo { display: inline-flex; margin-left: 0; }
517
- .top .nav #refresh, .top .nav a.icon { min-width: 44px; min-height: 44px; }
600
+ .top .nav #refresh, .top .nav a.icon, .top .nav .arch-bulb { min-width: 44px; min-height: 44px; }
518
601
  body > main, body.has-sidebar > :not(.sidebar) > main { padding-top: max(16px, env(safe-area-inset-top)); padding-bottom: calc(96px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
519
602
  .view-frame:not(.own-header) { padding-bottom: var(--app-bottom, 0px); }
520
603
  .sheet { bottom: max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px)));
521
604
  max-height: calc(var(--vh, 100dvh) + var(--kb, 0px) - max(var(--app-bottom, 0px), var(--kb, 0px)) - env(safe-area-inset-top) - 12px); }
522
605
  .sheet.keyboard-sheet:not(.kb)::after { height: 0; }
523
606
  .sheet.keyboard-sheet { --sheet-limit: max(80px, calc(var(--vh, 100dvh) + var(--kb, 0px) - max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px))) - max(12px, env(safe-area-inset-top)))); }
524
- .confirm-sheet { padding-bottom: calc(10px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
525
607
  }
526
608
 
527
609
  .settings-modal { position: fixed; inset: max(12px, env(safe-area-inset-top)) 0 calc(12px + max(var(--kb, 0px), var(--app-bottom, 0px))); margin: auto; width: min(680px, calc(100% - 24px));
@@ -536,6 +618,10 @@ main > * { transition: opacity .25s ease; }
536
618
  .settings-body { overflow-y: auto; overscroll-behavior: contain; min-height: 0; padding: 0 20px 16px; }
537
619
  .settings-body input { font-size: 16px; }
538
620
  .settings-footer { display: flex; justify-content: flex-end; padding: 12px 20px; border-top: 1px solid var(--line); }
621
+ .settings-footer button[aria-busy="true"] { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
622
+ .tk-saving, .lk-checking { display: inline-flex; align-items: center; gap: 6px; }
623
+ /* linear-key.js's modal over a page sits in .hub-view main only for hub.css's selectors. */
624
+ .lk-over, .lk-over > main { display: contents; }
539
625
 
540
626
  .tk-section { margin: 10px 0; }
541
627
  .tk-seg { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 4px; margin: 8px 0; border: 1px solid var(--line); border-radius: 9px; background: var(--chip); }
@@ -565,6 +651,15 @@ main > * { transition: opacity .25s ease; }
565
651
  .external-links .install.tone-danger { color: var(--color-danger); }
566
652
  .actions button[aria-disabled="true"] { opacity: .5; cursor: default; }
567
653
  .tb-main { margin-top: 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 9px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); font-size: 13px; }
568
- .tb-main svg { flex: none; color: var(--muted); }
654
+ .tb-main > svg { flex: none; color: var(--muted); }
569
655
  .tb-main-text { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
570
656
  .tb-main .meta { justify-content: flex-end; margin-left: auto; }
657
+ .main-log-source { margin: 0 0 8px; overflow-wrap: anywhere; }
658
+ .main-log-lines { margin: 0; padding: 8px 10px; border-radius: 8px; background: var(--chip); color: var(--text);
659
+ font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; line-height: 1.5;
660
+ max-height: calc(12 * 1.5 * 11.5px + 16px); white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: auto; }
661
+ .main-log-actions { display: flex; justify-content: flex-end; margin-top: 12px; }
662
+ .main-log-actions button { display: inline-flex; align-items: center; gap: 7px; min-height: 40px; padding: 6px 18px; }
663
+ .main-log-actions .main-log-going { border-color: color-mix(in srgb, var(--color-notice) 45%, var(--line));
664
+ background: color-mix(in srgb, var(--color-notice) 12%, var(--panel)); color: var(--color-notice); cursor: default; opacity: 1; }
665
+ #mainLogReason { color: var(--color-notice); overflow-wrap: anywhere; }
@@ -1,12 +1,14 @@
1
1
  (function () {
2
+ // How long a toast fades in and out; the board's bump icon fades its fill on the same timing (app.js fadeBump).
3
+ const reduced = () => matchMedia('(prefers-reduced-motion: reduce)').matches;
4
+ const enterTime = () => reduced() ? 80 : 150;
5
+ const exitTime = () => reduced() ? 100 : 200;
6
+
2
7
  function create(root) {
3
8
  const stack = document.createElement('div');
4
9
  stack.setAttribute('class', 'toast-stack');
5
10
  stack.setAttribute('aria-label', 'Notifications');
6
11
  root.appendChild(stack);
7
- const reduced = matchMedia('(prefers-reduced-motion: reduce)');
8
- const enterTime = () => reduced.matches ? 80 : 150;
9
- const exitTime = () => reduced.matches ? 100 : 200;
10
12
  let entries = [];
11
13
  let pending = [];
12
14
  let frame = 0;
@@ -175,5 +177,5 @@
175
177
  }
176
178
  return { toast, destroy };
177
179
  }
178
- globalThis.BoardToasts = { create };
180
+ globalThis.BoardToasts = { create, enterTime, exitTime };
179
181
  })();