proctolog 0.1.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 (228) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +178 -0
  4. data/app/controllers/proctolog/acceptances_controller.rb +29 -0
  5. data/app/controllers/proctolog/application_controller.rb +32 -0
  6. data/app/controllers/proctolog/bursts_controller.rb +34 -0
  7. data/app/controllers/proctolog/choices_controller.rb +30 -0
  8. data/app/controllers/proctolog/code_reviews_controller.rb +26 -0
  9. data/app/controllers/proctolog/decisions_controller.rb +33 -0
  10. data/app/controllers/proctolog/entries_controller.rb +14 -0
  11. data/app/controllers/proctolog/evidence_controller.rb +23 -0
  12. data/app/controllers/proctolog/freshness_controller.rb +27 -0
  13. data/app/controllers/proctolog/good_to_go_controller.rb +29 -0
  14. data/app/controllers/proctolog/memberships_controller.rb +20 -0
  15. data/app/controllers/proctolog/mentions_controller.rb +16 -0
  16. data/app/controllers/proctolog/moments_controller.rb +26 -0
  17. data/app/controllers/proctolog/note_params.rb +24 -0
  18. data/app/controllers/proctolog/notes_controller.rb +56 -0
  19. data/app/controllers/proctolog/open_pull_requests_controller.rb +59 -0
  20. data/app/controllers/proctolog/pictures_controller.rb +27 -0
  21. data/app/controllers/proctolog/prompts_controller.rb +28 -0
  22. data/app/controllers/proctolog/pull_request_scoped.rb +21 -0
  23. data/app/controllers/proctolog/pull_requests_controller.rb +34 -0
  24. data/app/controllers/proctolog/put_away_controller.rb +19 -0
  25. data/app/controllers/proctolog/recordings_controller.rb +28 -0
  26. data/app/controllers/proctolog/rendered_pieces_controller.rb +11 -0
  27. data/app/controllers/proctolog/review_data_controller.rb +12 -0
  28. data/app/controllers/proctolog/rules_controller.rb +20 -0
  29. data/app/controllers/proctolog/scenario_notes_controller.rb +26 -0
  30. data/app/controllers/proctolog/scenario_scoped.rb +21 -0
  31. data/app/controllers/proctolog/scenarios_controller.rb +67 -0
  32. data/app/controllers/proctolog/sitting_servers_controller.rb +30 -0
  33. data/app/controllers/proctolog/waiting_controller.rb +14 -0
  34. data/app/controllers/proctolog/with_decisions.rb +28 -0
  35. data/app/helpers/proctolog/bursts_helper.rb +52 -0
  36. data/app/helpers/proctolog/decisions_helper.rb +102 -0
  37. data/app/helpers/proctolog/desk_helper.rb +61 -0
  38. data/app/helpers/proctolog/mentions_helper.rb +27 -0
  39. data/app/helpers/proctolog/open_pull_requests_helper.rb +31 -0
  40. data/app/helpers/proctolog/prompts_helper.rb +21 -0
  41. data/app/helpers/proctolog/pull_requests_helper.rb +67 -0
  42. data/app/helpers/proctolog/recordings_helper.rb +30 -0
  43. data/app/helpers/proctolog/rounds_helper.rb +27 -0
  44. data/app/helpers/proctolog/scenarios_helper.rb +123 -0
  45. data/app/helpers/proctolog/standings_helper.rb +80 -0
  46. data/app/jobs/proctolog/application_job.rb +8 -0
  47. data/app/jobs/proctolog/ask_for_release_job.rb +9 -0
  48. data/app/jobs/proctolog/fetch_pictures_ahead_job.rb +36 -0
  49. data/app/jobs/proctolog/prune_fresh_profiles_job.rb +9 -0
  50. data/app/jobs/proctolog/read_mentions_ahead_job.rb +12 -0
  51. data/app/models/proctolog/acceptance.rb +47 -0
  52. data/app/models/proctolog/app.rb +92 -0
  53. data/app/models/proctolog/browser.rb +42 -0
  54. data/app/models/proctolog/burst.rb +268 -0
  55. data/app/models/proctolog/burst_notices.rb +143 -0
  56. data/app/models/proctolog/chapters.rb +42 -0
  57. data/app/models/proctolog/check.rb +122 -0
  58. data/app/models/proctolog/choices.rb +112 -0
  59. data/app/models/proctolog/code_review.rb +251 -0
  60. data/app/models/proctolog/decision_record.rb +12 -0
  61. data/app/models/proctolog/decisions.rb +199 -0
  62. data/app/models/proctolog/description.rb +165 -0
  63. data/app/models/proctolog/difference.rb +138 -0
  64. data/app/models/proctolog/doctor.rb +127 -0
  65. data/app/models/proctolog/evidence.rb +29 -0
  66. data/app/models/proctolog/failure.rb +7 -0
  67. data/app/models/proctolog/focus.rb +48 -0
  68. data/app/models/proctolog/fresh_sitting.rb +35 -0
  69. data/app/models/proctolog/github.rb +353 -0
  70. data/app/models/proctolog/handover/brief.rb +138 -0
  71. data/app/models/proctolog/handover/layers.rb +128 -0
  72. data/app/models/proctolog/handover/places.rb +98 -0
  73. data/app/models/proctolog/handover/taken.rb +41 -0
  74. data/app/models/proctolog/handover/worklist.rb +35 -0
  75. data/app/models/proctolog/handover.rb +123 -0
  76. data/app/models/proctolog/housekeeping.rb +23 -0
  77. data/app/models/proctolog/kit.rb +36 -0
  78. data/app/models/proctolog/marks.rb +130 -0
  79. data/app/models/proctolog/membership.rb +91 -0
  80. data/app/models/proctolog/mentions.rb +117 -0
  81. data/app/models/proctolog/note.rb +94 -0
  82. data/app/models/proctolog/outside.rb +45 -0
  83. data/app/models/proctolog/overlay/brief.rb +52 -0
  84. data/app/models/proctolog/overlay/written.rb +33 -0
  85. data/app/models/proctolog/overlay.rb +87 -0
  86. data/app/models/proctolog/owner.rb +16 -0
  87. data/app/models/proctolog/pick.rb +47 -0
  88. data/app/models/proctolog/picture.rb +138 -0
  89. data/app/models/proctolog/pieces_rendering.rb +40 -0
  90. data/app/models/proctolog/project.rb +118 -0
  91. data/app/models/proctolog/pull_request.rb +89 -0
  92. data/app/models/proctolog/recording.rb +194 -0
  93. data/app/models/proctolog/release.rb +59 -0
  94. data/app/models/proctolog/rendering.rb +303 -0
  95. data/app/models/proctolog/reports.rb +66 -0
  96. data/app/models/proctolog/response_files.rb +55 -0
  97. data/app/models/proctolog/rounds.rb +305 -0
  98. data/app/models/proctolog/route.rb +151 -0
  99. data/app/models/proctolog/rules.rb +121 -0
  100. data/app/models/proctolog/scenario.rb +126 -0
  101. data/app/models/proctolog/server.rb +273 -0
  102. data/app/models/proctolog/since.rb +108 -0
  103. data/app/models/proctolog/size.rb +30 -0
  104. data/app/models/proctolog/standing.rb +132 -0
  105. data/app/models/proctolog/theme.rb +40 -0
  106. data/app/models/proctolog/unopened/numbers.rb +48 -0
  107. data/app/models/proctolog/unopened/opening.rb +88 -0
  108. data/app/models/proctolog/unopened.rb +203 -0
  109. data/app/models/proctolog/visit.rb +26 -0
  110. data/app/views/layouts/proctolog/application.html.erb +58 -0
  111. data/app/views/proctolog/acceptances/new.html.erb +34 -0
  112. data/app/views/proctolog/bursts/_line.html.erb +28 -0
  113. data/app/views/proctolog/bursts/_merged.html.erb +23 -0
  114. data/app/views/proctolog/bursts/_moved.html.erb +16 -0
  115. data/app/views/proctolog/bursts/_notices.html.erb +39 -0
  116. data/app/views/proctolog/bursts/_put_away.html.erb +11 -0
  117. data/app/views/proctolog/bursts/_rules.html.erb +27 -0
  118. data/app/views/proctolog/bursts/_side.html.erb +49 -0
  119. data/app/views/proctolog/bursts/_side_stop.html.erb +10 -0
  120. data/app/views/proctolog/bursts/show.html.erb +54 -0
  121. data/app/views/proctolog/code_reviews/show.html.erb +33 -0
  122. data/app/views/proctolog/decisions/_alternative.html.erb +5 -0
  123. data/app/views/proctolog/decisions/_beneath.html.erb +21 -0
  124. data/app/views/proctolog/decisions/_box.html.erb +27 -0
  125. data/app/views/proctolog/decisions/_choices.html.erb +9 -0
  126. data/app/views/proctolog/decisions/_draft_tools.html.erb +47 -0
  127. data/app/views/proctolog/decisions/_part.html.erb +61 -0
  128. data/app/views/proctolog/decisions/_round_data.html.erb +1 -0
  129. data/app/views/proctolog/decisions/_theme.html.erb +13 -0
  130. data/app/views/proctolog/decisions/index.html.erb +75 -0
  131. data/app/views/proctolog/notes/_card.html.erb +19 -0
  132. data/app/views/proctolog/notes/_template.html.erb +41 -0
  133. data/app/views/proctolog/open_pull_requests/_side.html.erb +30 -0
  134. data/app/views/proctolog/open_pull_requests/index.html.erb +106 -0
  135. data/app/views/proctolog/prompts/show.html.erb +41 -0
  136. data/app/views/proctolog/pull_requests/_connections.html.erb +65 -0
  137. data/app/views/proctolog/pull_requests/_finish.html.erb +42 -0
  138. data/app/views/proctolog/pull_requests/_margin.html.erb +33 -0
  139. data/app/views/proctolog/pull_requests/_note.html.erb +4 -0
  140. data/app/views/proctolog/pull_requests/_outline.html.erb +26 -0
  141. data/app/views/proctolog/pull_requests/_paragraphs.html.erb +9 -0
  142. data/app/views/proctolog/pull_requests/_scenarios.html.erb +16 -0
  143. data/app/views/proctolog/pull_requests/_since.html.erb +42 -0
  144. data/app/views/proctolog/pull_requests/_standing.html.erb +51 -0
  145. data/app/views/proctolog/pull_requests/_status.html.erb +5 -0
  146. data/app/views/proctolog/pull_requests/_try.html.erb +122 -0
  147. data/app/views/proctolog/pull_requests/show.html.erb +145 -0
  148. data/app/views/proctolog/recordings/_dialog.html.erb +26 -0
  149. data/app/views/proctolog/recordings/_list.html.erb +8 -0
  150. data/app/views/proctolog/recordings/_note.html.erb +22 -0
  151. data/app/views/proctolog/recordings/index.html.erb +33 -0
  152. data/app/views/proctolog/recordings/show.html.erb +118 -0
  153. data/app/views/proctolog/scenarios/_dialog.html.erb +22 -0
  154. data/app/views/proctolog/scenarios/_note.html.erb +18 -0
  155. data/app/views/proctolog/scenarios/_open.html.erb +9 -0
  156. data/app/views/proctolog/scenarios/_scenario.html.erb +48 -0
  157. data/app/views/proctolog/scenarios/_server.html.erb +20 -0
  158. data/app/views/proctolog/scenarios/index.html.erb +62 -0
  159. data/bin/proctolog +330 -0
  160. data/completions/_proctolog +143 -0
  161. data/config/application.rb +94 -0
  162. data/config/database.yml +16 -0
  163. data/config/environment.rb +6 -0
  164. data/config/initializers/grouse.rb +113 -0
  165. data/config/initializers/grouse_notes.rb +9 -0
  166. data/config/keys.yml +6 -0
  167. data/config/routes.rb +71 -0
  168. data/config/storage.yml +4 -0
  169. data/db/migrate/20261019213542_create_grouse_tables.grouse.rb +128 -0
  170. data/db/migrate/20261019213550_create_active_storage_tables.active_storage.rb +58 -0
  171. data/docs/manual/app.md +157 -0
  172. data/docs/manual/bursts.md +129 -0
  173. data/docs/manual/deciding.md +89 -0
  174. data/docs/manual/desk.png +0 -0
  175. data/docs/manual/films.md +20 -0
  176. data/docs/manual/handover.md +93 -0
  177. data/docs/manual/pr-format.md +75 -0
  178. data/docs/manual/reading.md +233 -0
  179. data/docs/manual/unopened.md +80 -0
  180. data/docs/manual/updates.md +55 -0
  181. data/lib/proctolog/installation.rb +125 -0
  182. data/lib/proctolog/version.rb +7 -0
  183. data/public/asking.js +50 -0
  184. data/public/code-review.js +40 -0
  185. data/public/cursor.js +292 -0
  186. data/public/decision-screenshots.js +97 -0
  187. data/public/decisions.css +949 -0
  188. data/public/decisions.js +1289 -0
  189. data/public/desk.css +582 -0
  190. data/public/desk.js +126 -0
  191. data/public/favicon.png +0 -0
  192. data/public/keys.js +228 -0
  193. data/public/mentions.js +389 -0
  194. data/public/messages.js +103 -0
  195. data/public/notes.js +731 -0
  196. data/public/pick.css +99 -0
  197. data/public/plaque.png +0 -0
  198. data/public/pr-number.js +11 -0
  199. data/public/prompt.css +46 -0
  200. data/public/prompt.js +16 -0
  201. data/public/pull_request.css +805 -0
  202. data/public/recording.css +379 -0
  203. data/public/recording.js +394 -0
  204. data/public/release.js +25 -0
  205. data/public/respond.css +260 -0
  206. data/public/respond.js +95 -0
  207. data/public/review-drafts.js +949 -0
  208. data/public/review-rounds.js +1447 -0
  209. data/public/scenarios.css +253 -0
  210. data/public/scenarios.js +36 -0
  211. data/public/schemas/proctolog-response-v1.json +101 -0
  212. data/public/side.js +133 -0
  213. data/public/standing.css +478 -0
  214. data/public/terminal-apl.svg +6 -0
  215. data/public/terminal-cuneiform.svg +3 -0
  216. data/public/terminal-grain-dots.svg +6 -0
  217. data/public/terminal-grain-weave.svg +6 -0
  218. data/public/terminal-grain-wedges.svg +6 -0
  219. data/public/terminal-shades.svg +3 -0
  220. data/public/terminal.css +4426 -0
  221. data/public/toast.js +26 -0
  222. data/public/try.js +124 -0
  223. data/public/viewer.js +808 -0
  224. data/public/words.js +81 -0
  225. data/skills/proctolog-handover/SKILL.md +77 -0
  226. data/skills/proctolog-pr-writing/SKILL.md +228 -0
  227. data/skills/proctolog-pr-writing/references/example-description.md +33 -0
  228. metadata +366 -0
data/public/desk.css ADDED
@@ -0,0 +1,582 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --paper: #f3f0ea;
4
+ --ink: #1a1818;
5
+ --quiet: #6b655c;
6
+ --rule: #d9d3c8;
7
+ --chalk: #2f4a6b;
8
+ --pin: #a23b2a;
9
+ --rest: #e8e2d7;
10
+ --serif: ui-serif, "Iowan Old Style", Palatino, Georgia, serif;
11
+ }
12
+
13
+ @media (prefers-color-scheme: dark) {
14
+ :root {
15
+ --paper: #181716;
16
+ --ink: #ece8e1;
17
+ --quiet: #9c958a;
18
+ --rule: #34312d;
19
+ --chalk: #a9c1e0;
20
+ --pin: #e08a76;
21
+ --rest: #262321;
22
+ }
23
+ }
24
+
25
+ body {
26
+ margin: 0 auto;
27
+ max-width: 74rem;
28
+ padding: 2.5rem 1.5rem 5rem;
29
+ background: var(--paper);
30
+ color: var(--ink);
31
+ font:
32
+ 15px / 1.45 ui-sans-serif,
33
+ system-ui,
34
+ sans-serif;
35
+ font-variant-numeric: tabular-nums;
36
+ }
37
+
38
+ a {
39
+ color: var(--chalk);
40
+ text-underline-offset: 0.18em;
41
+ }
42
+
43
+ form[data-code] {
44
+ display: inline;
45
+ margin: 0 0 0 0.5rem;
46
+ }
47
+
48
+ .code-link {
49
+ padding: 0;
50
+ border: 0;
51
+ background: none;
52
+ color: var(--chalk);
53
+ font: inherit;
54
+ font-size: 0.8rem;
55
+ text-decoration: underline;
56
+ text-underline-offset: 0.18em;
57
+ cursor: pointer;
58
+ }
59
+
60
+ .desk {
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+ gap: 0.3rem 1.4rem;
64
+ margin-bottom: 1.6rem;
65
+ color: var(--quiet);
66
+ font-size: 0.75rem;
67
+ letter-spacing: 0.14em;
68
+ text-transform: uppercase;
69
+ }
70
+
71
+ .desk a {
72
+ color: var(--quiet);
73
+ text-decoration: none;
74
+ }
75
+
76
+ .desk a:hover {
77
+ color: var(--chalk);
78
+ }
79
+
80
+ .desk .name {
81
+ color: var(--ink);
82
+ }
83
+
84
+ .desk a[aria-current] {
85
+ color: var(--ink);
86
+ box-shadow: 0 2px var(--pin);
87
+ }
88
+
89
+ /* How far the decisions of a pull request are: a fraction and a thin bar. */
90
+
91
+ .progress {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: 0.5rem;
95
+ white-space: nowrap;
96
+ }
97
+
98
+ .progress.none {
99
+ color: var(--quiet);
100
+ }
101
+
102
+ .progress meter {
103
+ width: 3.5rem;
104
+ height: 0.35rem;
105
+ border: 0;
106
+ border-radius: 999px;
107
+ background: var(--rule);
108
+ }
109
+
110
+ .progress meter::-webkit-meter-bar {
111
+ height: 0.35rem;
112
+ border: 0;
113
+ border-radius: 999px;
114
+ background: var(--rule);
115
+ }
116
+
117
+ .progress meter::-webkit-meter-optimum-value {
118
+ border-radius: 999px;
119
+ background: var(--chalk);
120
+ }
121
+
122
+ .progress meter::-moz-meter-bar {
123
+ border-radius: 999px;
124
+ background: var(--chalk);
125
+ }
126
+
127
+ .progress[data-done="true"] .fraction::after {
128
+ content: " ✓";
129
+ }
130
+
131
+ /* The bar at the foot of a page with keys, and the sentence that answers one. */
132
+
133
+ .modebar {
134
+ position: fixed;
135
+ inset: auto 0 0;
136
+ z-index: 5;
137
+ display: flex;
138
+ align-items: baseline;
139
+ gap: 1.5rem;
140
+ padding: 0.45rem 1.5rem;
141
+ border-top: 1px solid var(--rule);
142
+ background: var(--paper);
143
+ color: var(--quiet);
144
+ font-size: 0.82rem;
145
+ }
146
+
147
+ .modebar .mode {
148
+ display: flex;
149
+ flex: 1;
150
+ flex-wrap: wrap;
151
+ gap: 0.2rem 1.1rem;
152
+ margin: 0;
153
+ }
154
+
155
+ .modebar .mode strong {
156
+ color: var(--ink);
157
+ font-weight: 600;
158
+ }
159
+
160
+ .modebar kbd + kbd {
161
+ margin-left: 0.2rem;
162
+ }
163
+
164
+ .modebar .all-keys {
165
+ padding: 0;
166
+ border: 0;
167
+ background: none;
168
+ color: var(--quiet);
169
+ font: inherit;
170
+ white-space: nowrap;
171
+ cursor: pointer;
172
+ }
173
+
174
+ .modebar .all-keys:hover {
175
+ color: var(--chalk);
176
+ }
177
+
178
+ .toast {
179
+ position: fixed;
180
+ right: 1.5rem;
181
+ bottom: 3rem;
182
+ z-index: 6;
183
+ max-width: min(70ch, 90vw);
184
+ margin: 0;
185
+ padding: 0.5rem 0.9rem;
186
+ border: 1px solid var(--rule);
187
+ border-radius: 6px;
188
+ background: var(--ink);
189
+ color: var(--paper);
190
+ font-size: 0.87rem;
191
+ white-space: pre-line;
192
+ opacity: 0;
193
+ transform: translateY(0.4rem);
194
+ transition:
195
+ opacity 0.15s,
196
+ transform 0.15s;
197
+ pointer-events: none;
198
+ }
199
+
200
+ .toast[data-shown] {
201
+ opacity: 1;
202
+ transform: none;
203
+ }
204
+
205
+ @media (prefers-reduced-motion: reduce) {
206
+ .toast {
207
+ transition: none;
208
+ }
209
+ }
210
+
211
+ .keys-help {
212
+ width: min(58rem, 92vw);
213
+ max-height: 86vh;
214
+ padding: 1.2rem 1.5rem 1.4rem;
215
+ border: 1px solid var(--rule);
216
+ border-radius: 8px;
217
+ background: var(--paper);
218
+ color: var(--ink);
219
+ }
220
+
221
+ .keys-help::backdrop {
222
+ background: rgb(0 0 0 / 0.45);
223
+ }
224
+
225
+ .keys-help .head {
226
+ display: flex;
227
+ align-items: baseline;
228
+ justify-content: space-between;
229
+ }
230
+
231
+ .keys-help h2 {
232
+ margin: 0;
233
+ font: 500 1.35rem / 1.2 var(--serif);
234
+ }
235
+
236
+ .keys-help .close {
237
+ padding: 0.05em 0.5em;
238
+ border: 1px solid var(--rule);
239
+ border-radius: 4px;
240
+ background: none;
241
+ color: var(--quiet);
242
+ font:
243
+ 0.78rem ui-monospace,
244
+ Menlo,
245
+ monospace;
246
+ cursor: pointer;
247
+ }
248
+
249
+ .keys-help .groups {
250
+ display: grid;
251
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
252
+ gap: 0.4rem 2rem;
253
+ }
254
+
255
+ .keys-help h3 {
256
+ margin: 1.1rem 0 0.35rem;
257
+ color: var(--quiet);
258
+ font-size: 0.72rem;
259
+ font-weight: 500;
260
+ letter-spacing: 0.08em;
261
+ text-transform: uppercase;
262
+ }
263
+
264
+ .keys-help dl {
265
+ margin: 0;
266
+ }
267
+
268
+ .keys-help dl div {
269
+ display: grid;
270
+ grid-template-columns: 6.5rem 1fr;
271
+ gap: 0.6rem;
272
+ padding: 0.16rem 0;
273
+ }
274
+
275
+ .keys-help dt {
276
+ white-space: nowrap;
277
+ }
278
+
279
+ .keys-help dt kbd + kbd {
280
+ margin-left: 0.25rem;
281
+ }
282
+
283
+ .keys-help dd {
284
+ margin: 0;
285
+ }
286
+
287
+ .keys-help .note {
288
+ margin: 1.1rem 0 0;
289
+ color: var(--quiet);
290
+ font-size: 0.82rem;
291
+ }
292
+
293
+ h1 {
294
+ margin: 0.2rem 0 0.6rem;
295
+ font: 500 2.1rem / 1.15 var(--serif);
296
+ letter-spacing: -0.01em;
297
+ }
298
+
299
+ .tally,
300
+ .gone {
301
+ margin: 0;
302
+ max-width: 62ch;
303
+ }
304
+
305
+ .gone {
306
+ color: var(--pin);
307
+ }
308
+
309
+ /* The desk's pages are named in a word or two, each on a line of its own;
310
+ the page the owner is on, marked and bold, may take two. */
311
+ .side nav.desk a:not([aria-current]) {
312
+ white-space: nowrap;
313
+ }
314
+
315
+ /* A burst put away says so beside its name in the pane. */
316
+ .side .name .away {
317
+ color: var(--quiet);
318
+ font-size: 0.867rem;
319
+ }
320
+
321
+ /* Done with a burst: a quiet line under its tally, with its button. */
322
+ .put-away {
323
+ display: flex;
324
+ flex-wrap: wrap;
325
+ gap: 1ch;
326
+ align-items: baseline;
327
+ margin: 6px 0 0;
328
+ color: var(--quiet);
329
+ }
330
+
331
+ .put-away p {
332
+ margin: 0;
333
+ }
334
+
335
+ /* What went in is settled, so grey, and each line shows whole: no cursor
336
+ rests on it to open it out. */
337
+ .merged .lines .title {
338
+ color: var(--quiet);
339
+ }
340
+
341
+ .merged .lines .facts {
342
+ display: block;
343
+ padding: 2px 0 6px 7ch;
344
+ }
345
+
346
+ .reading {
347
+ display: flex;
348
+ flex-wrap: wrap;
349
+ gap: 0.35rem 1.25rem;
350
+ margin-top: 0.8rem;
351
+ color: var(--quiet);
352
+ font-size: 0.87rem;
353
+ }
354
+
355
+ .number {
356
+ margin-right: 0.45rem;
357
+ text-decoration: none;
358
+ }
359
+
360
+ .number:hover {
361
+ text-decoration: underline;
362
+ }
363
+
364
+ [data-stop] {
365
+ scroll-margin-block: 3rem;
366
+ }
367
+
368
+ [data-stop][aria-current] {
369
+ background: var(--rest);
370
+ box-shadow: inset 3px 0 var(--pin);
371
+ }
372
+
373
+ .ledger {
374
+ width: 100%;
375
+ margin-top: 1.5rem;
376
+ border-collapse: collapse;
377
+ }
378
+
379
+ .ledger th {
380
+ padding: 0.4rem 0.75rem;
381
+ border-bottom: 1px solid var(--rule);
382
+ color: var(--quiet);
383
+ font-size: 0.72rem;
384
+ font-weight: 500;
385
+ letter-spacing: 0.08em;
386
+ text-align: left;
387
+ text-transform: uppercase;
388
+ white-space: nowrap;
389
+ }
390
+
391
+ .ledger td {
392
+ padding: 0.42rem 0.75rem;
393
+ border-bottom: 1px solid var(--rule);
394
+ vertical-align: baseline;
395
+ }
396
+
397
+ .ledger .leg th,
398
+ .ladder h2 {
399
+ padding: 1.9rem 0.75rem 0.45rem;
400
+ color: var(--ink);
401
+ font: italic 1.05rem / 1.3 var(--serif);
402
+ letter-spacing: 0;
403
+ text-transform: none;
404
+ }
405
+
406
+ .ledger .layer,
407
+ .ledger .count {
408
+ text-align: right;
409
+ white-space: nowrap;
410
+ }
411
+
412
+ .ledger td.layer,
413
+ .changed {
414
+ color: var(--quiet);
415
+ }
416
+
417
+ .changed {
418
+ white-space: nowrap;
419
+ }
420
+
421
+ .ladder h2 {
422
+ margin: 0;
423
+ padding-left: 0;
424
+ }
425
+
426
+ .ladder ol {
427
+ margin: 0 0 0 0.95rem;
428
+ padding: 0;
429
+ border-left: 2px solid var(--rule);
430
+ list-style: none;
431
+ }
432
+
433
+ .ladder.alone ol {
434
+ border-left-color: transparent;
435
+ }
436
+
437
+ .ladder li {
438
+ position: relative;
439
+ padding: 0.6rem 0.75rem 0.6rem 2rem;
440
+ }
441
+
442
+ .ladder .layer {
443
+ position: absolute;
444
+ top: 0.62rem;
445
+ left: -0.95rem;
446
+ display: grid;
447
+ width: 1.75rem;
448
+ height: 1.75rem;
449
+ border: 2px solid var(--rule);
450
+ border-radius: 50%;
451
+ background: var(--paper);
452
+ color: var(--quiet);
453
+ font-size: 0.72rem;
454
+ place-items: center;
455
+ }
456
+
457
+ .ladder p {
458
+ margin: 0;
459
+ }
460
+
461
+ .ladder .what {
462
+ font: 1.08rem / 1.3 var(--serif);
463
+ }
464
+
465
+ .ladder .number {
466
+ font:
467
+ 0.92rem / 1.3 ui-sans-serif,
468
+ system-ui,
469
+ sans-serif;
470
+ }
471
+
472
+ .ladder .facts {
473
+ margin-top: 0.15rem;
474
+ color: var(--quiet);
475
+ font-size: 0.87rem;
476
+ }
477
+
478
+ .keys {
479
+ display: flex;
480
+ flex-wrap: wrap;
481
+ gap: 0.4rem 1.5rem;
482
+ margin-top: 2.5rem;
483
+ color: var(--quiet);
484
+ font-size: 0.82rem;
485
+ }
486
+
487
+ kbd {
488
+ padding: 0.05em 0.4em;
489
+ border: 1px solid var(--rule);
490
+ border-radius: 4px;
491
+ font:
492
+ 0.78rem ui-monospace,
493
+ Menlo,
494
+ monospace;
495
+ }
496
+
497
+ .flash {
498
+ max-width: 70ch;
499
+ margin: 0 0 1.4rem;
500
+ padding: 0.6rem 1rem;
501
+ border-left: 3px solid var(--chalk);
502
+ background: var(--rest);
503
+ white-space: pre-line;
504
+ }
505
+
506
+ .flash.alert {
507
+ border-left-color: var(--pin);
508
+ }
509
+
510
+ /* The pull requests a # typed into a box may mean, just under the #. */
511
+
512
+ .mentions {
513
+ position: fixed;
514
+ z-index: 10;
515
+ width: min(38rem, calc(100vw - 2rem));
516
+ border: 1px solid var(--rule);
517
+ background: var(--paper);
518
+ color: var(--ink);
519
+ font-size: 0.87rem;
520
+ }
521
+
522
+ .mentions ul {
523
+ max-height: 16rem;
524
+ margin: 0;
525
+ padding: 0;
526
+ overflow-y: auto;
527
+ list-style: none;
528
+ }
529
+
530
+ .mentions li {
531
+ display: flex;
532
+ gap: 0.9rem;
533
+ align-items: baseline;
534
+ padding: 0.2rem 0.7rem;
535
+ cursor: pointer;
536
+ }
537
+
538
+ .mentions li .number {
539
+ min-width: 5ch;
540
+ font-variant-numeric: tabular-nums;
541
+ }
542
+
543
+ .mentions li .title {
544
+ flex: 1;
545
+ overflow: hidden;
546
+ text-overflow: ellipsis;
547
+ white-space: nowrap;
548
+ }
549
+
550
+ .mentions li .state {
551
+ color: var(--quiet);
552
+ }
553
+
554
+ .mentions li[aria-selected="true"],
555
+ .mentions li[aria-selected="true"] .state {
556
+ background: var(--ink);
557
+ color: var(--paper);
558
+ }
559
+
560
+ .mentions .looking,
561
+ .mentions .hint {
562
+ margin: 0;
563
+ padding: 0.3rem 0.7rem;
564
+ color: var(--quiet);
565
+ }
566
+
567
+ .mentions .hint {
568
+ border-top: 1px solid var(--rule);
569
+ font-size: 0.8rem;
570
+ }
571
+
572
+ .mentions .hint kbd + kbd {
573
+ margin-left: 0.2rem;
574
+ }
575
+
576
+ /* A # in the owner's words that names a pull request: its number goes to
577
+ it, and its title stands beside, quieter than the words around. */
578
+
579
+ .mention-title,
580
+ .mention-state {
581
+ color: var(--quiet);
582
+ }
data/public/desk.js ADDED
@@ -0,0 +1,126 @@
1
+ // The route by key: j and k rest on the next and the previous line, a pull
2
+ // request, a notice at the head of the route that one joined or left, or a
3
+ // rule made in the burst; Enter opens the one rested on, x dismisses a notice
4
+ // or withdraws a rule, c edits a rule, N opens the next pull
5
+ // request with decisions still to review, and a link that names a key answers
6
+ // to it. The address remembers where the cursor rests, across a reload and a
7
+ // change of layout.
8
+
9
+ const stops = [...document.querySelectorAll("[data-stop]")];
10
+ let at = Math.max(
11
+ 0,
12
+ stops.findIndex((stop) => `#${stop.id}` === location.hash),
13
+ );
14
+
15
+ const restOn = (index, { scroll = true } = {}) => {
16
+ stops[at].removeAttribute("aria-current");
17
+ at = Math.min(Math.max(index, 0), stops.length - 1);
18
+ stops[at].setAttribute("aria-current", "true");
19
+ if (scroll) stops[at].scrollIntoView({ block: "nearest" });
20
+ // The first stop has no fragment, so a reload there stays at the top of the page.
21
+ const address = at ? `#${stops[at].id}` : location.pathname + location.search;
22
+ history.replaceState(null, "", address);
23
+ };
24
+
25
+ const follow = (key) => {
26
+ const link = document.querySelector(`a[data-key="${key}"]`);
27
+ if (link) location.assign(link.href + location.hash);
28
+ };
29
+
30
+ // Opens the next pull request with decisions still to review: the one the
31
+ // cursor rests on when it has some, else the first after it, going round to
32
+ // the top of the route. With none left anywhere, the cursor's row says so.
33
+ const openUnreviewed = () => {
34
+ const ahead = [...stops.slice(at), ...stops.slice(0, at)];
35
+ // The non-blocking wait for one gesture on their pull request's page.
36
+ const next = ahead.find((stop) => Number(stop.dataset.blockingLeft) > 0);
37
+ if (!next) return said("Every decision of this burst is reviewed.");
38
+ restOn(stops.indexOf(next));
39
+ location.assign(next.querySelector("a").href);
40
+ };
41
+
42
+ // A sentence in answer to a key, in the toast every page uses: this script
43
+ // is not a module, so keys.js lends it by this name.
44
+ const said = (message) => globalThis.proctologSay(message);
45
+
46
+ // A rule's line opens its one line for editing: Enter saves it, Escape
47
+ // closes it as it was.
48
+ const editRule = (stop) => {
49
+ const form = stop.querySelector("form[data-edit-rule]");
50
+ if (!form) return;
51
+ restOn(stops.indexOf(stop));
52
+ form.hidden = false;
53
+ form.querySelector("input[type=text]").select();
54
+ };
55
+
56
+ document.addEventListener("click", (event) => {
57
+ const stop = event.target.closest("[data-stop]");
58
+ if (stop) restOn(stops.indexOf(stop), { scroll: false });
59
+ const button = event.target.closest("[data-edit]");
60
+ if (button) editRule(button.closest("[data-stop]"));
61
+ });
62
+
63
+ document.addEventListener("focusin", (event) => {
64
+ const stop = event.target.closest("[data-stop]");
65
+ if (stop) restOn(stops.indexOf(stop), { scroll: false });
66
+ });
67
+
68
+ document.addEventListener("keydown", (event) => {
69
+ const form = event.target.closest("form[data-edit-rule]");
70
+ if (!form || event.key !== "Escape") return;
71
+ form.reset();
72
+ form.hidden = true;
73
+ });
74
+
75
+ const keys = {
76
+ j: () => restOn(at + 1),
77
+ ArrowDown: () => restOn(at + 1),
78
+ k: () => restOn(at - 1),
79
+ ArrowUp: () => restOn(at - 1),
80
+ Home: () => restOn(0),
81
+ End: () => restOn(stops.length - 1),
82
+ N: openUnreviewed,
83
+ Enter: () => {
84
+ const link = stops[at].querySelector("a");
85
+ if (link) location.assign(link.href);
86
+ },
87
+ r: () => follow("r"),
88
+ d: () => stops[at].querySelector("form[data-code]")?.requestSubmit(),
89
+ G: () => {
90
+ const link = stops[at].querySelector('a[data-key="G"]');
91
+ if (link) link.click();
92
+ else said("Nobody has opened this pull request yet: GitHub has nothing of it.");
93
+ },
94
+ // A notice that a pull request joined or left goes once it is seen; a
95
+ // rule is withdrawn.
96
+ x: () => stops[at].querySelector("form[data-dismiss]")?.requestSubmit(),
97
+ c: () => editRule(stops[at]),
98
+ // The part the cursor is in, alone: its line offers it unless it is the focus already.
99
+ p: () => {
100
+ const link = stops[at].querySelector('a[data-key="p"]');
101
+ if (link) location.assign(link.href);
102
+ else said("The route shows this part alone already.");
103
+ },
104
+ };
105
+
106
+ document.addEventListener("keydown", (event) => {
107
+ if (event.metaKey || event.ctrlKey || event.altKey || !stops.length) return;
108
+ if (document.querySelector("dialog[open]")) return;
109
+ // The key as keys.js reads it: by where a letter sits, whatever the layout types.
110
+ const key = globalThis.proctologKey?.(event) ?? event.key;
111
+ // A link or a button keeps Enter for itself; the letters are the route's wherever the focus rests.
112
+ if (!Object.hasOwn(keys, key)) return;
113
+ if (event.target.closest("input, textarea, select")) return;
114
+ if (key === "Enter" && event.target.closest("a, button, summary")) return;
115
+
116
+ event.preventDefault();
117
+ keys[key]();
118
+ });
119
+
120
+ // A first visit rests on the first stop without leaving the top of the page.
121
+ if (stops.length) restOn(at, { scroll: at > 0 });
122
+
123
+ // A page the browser kept from before a choice was made would show old counts.
124
+ addEventListener("pageshow", (event) => {
125
+ if (event.persisted) location.reload();
126
+ });
Binary file