beads-ui 0.2.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 (58) hide show
  1. package/CHANGES.md +14 -0
  2. package/README.md +4 -4
  3. package/app/data/list-selectors.js +103 -0
  4. package/app/data/providers.js +7 -138
  5. package/app/data/sort.js +47 -0
  6. package/app/data/subscription-issue-store.js +161 -0
  7. package/app/data/subscription-issue-stores.js +128 -0
  8. package/app/data/subscriptions-store.js +227 -0
  9. package/app/main.js +346 -66
  10. package/app/protocol.js +23 -17
  11. package/app/protocol.md +18 -15
  12. package/app/router.js +3 -0
  13. package/app/state.js +2 -0
  14. package/app/styles.css +222 -197
  15. package/app/utils/issue-id-renderer.js +2 -1
  16. package/app/utils/issue-id.js +1 -0
  17. package/app/utils/issue-type.js +2 -0
  18. package/app/utils/issue-url.js +1 -0
  19. package/app/utils/markdown.js +13 -198
  20. package/app/utils/priority-badge.js +1 -2
  21. package/app/utils/status-badge.js +1 -1
  22. package/app/utils/status.js +2 -0
  23. package/app/utils/toast.js +1 -1
  24. package/app/utils/type-badge.js +1 -3
  25. package/app/views/board.js +172 -148
  26. package/app/views/detail.js +79 -66
  27. package/app/views/epics.js +127 -74
  28. package/app/views/issue-dialog.js +9 -15
  29. package/app/views/issue-row.js +2 -3
  30. package/app/views/list.js +105 -104
  31. package/app/views/nav.js +1 -0
  32. package/app/views/new-issue-dialog.js +30 -34
  33. package/app/ws.js +10 -10
  34. package/bin/bdui.js +1 -1
  35. package/docs/adr/001-push-only-lists.md +134 -0
  36. package/docs/adr/002-per-subscription-stores-and-full-issue-push.md +200 -0
  37. package/docs/architecture.md +34 -84
  38. package/docs/data-exchange-subscription-plan.md +198 -0
  39. package/docs/db-watching.md +2 -1
  40. package/docs/migration-v2.md +54 -0
  41. package/docs/protocol/issues-push-v2.md +179 -0
  42. package/docs/subscription-issue-store.md +112 -0
  43. package/package.json +5 -4
  44. package/server/app.js +2 -0
  45. package/server/bd.js +4 -2
  46. package/server/cli/commands.js +5 -2
  47. package/server/cli/daemon.js +19 -5
  48. package/server/cli/index.js +2 -2
  49. package/server/cli/open.js +3 -0
  50. package/server/cli/usage.js +2 -1
  51. package/server/config.js +13 -6
  52. package/server/db.js +3 -1
  53. package/server/index.js +9 -5
  54. package/server/list-adapters.js +224 -0
  55. package/server/subscriptions.js +289 -0
  56. package/server/validators.js +113 -0
  57. package/server/watcher.js +8 -8
  58. package/server/ws.js +457 -229
package/app/styles.css CHANGED
@@ -64,7 +64,22 @@
64
64
  --pre-fg: #e5e7eb;
65
65
 
66
66
  /* focus ring */
67
- --outline-offset: 6px;
67
+ --outline-offset-l: 6px;
68
+ --outline-offset-m: 2px;
69
+ --outline-offset-s: 0px;
70
+
71
+ /* Spacing scale (2px baseline) */
72
+ --space-0: 0;
73
+ --space-1: 2px;
74
+ --space-2: 4px;
75
+ --space-3: 6px;
76
+ --space-4: 8px;
77
+ --space-5: 10px;
78
+ --space-6: 12px;
79
+ --space-7: 14px;
80
+ --space-8: 16px;
81
+ --space-9: 18px;
82
+ --space-10: 20px;
68
83
  }
69
84
 
70
85
  html,
@@ -98,20 +113,21 @@ body {
98
113
 
99
114
  a {
100
115
  color: var(--link);
101
- }
102
- a:visited {
103
- color: var(--link-visited);
104
- }
105
- a:hover,
106
- a:focus {
107
- color: var(--link-hover);
116
+
117
+ &:visited {
118
+ color: var(--link-visited);
119
+ }
120
+ &:hover,
121
+ &:focus {
122
+ color: var(--link-hover);
123
+ }
108
124
  }
109
125
 
110
126
  .app-header {
111
127
  position: sticky;
112
128
  top: 0;
113
129
  z-index: 50;
114
- padding: 12px 18px 0 18px;
130
+ padding: var(--space-6) var(--space-9) 0 var(--space-9);
115
131
  border-bottom: 1px solid var(--border);
116
132
  display: flex;
117
133
  align-items: start;
@@ -133,88 +149,94 @@ a:focus {
133
149
  .header-left {
134
150
  display: flex;
135
151
  align-items: baseline;
136
- gap: 12px;
152
+ gap: var(--space-6);
137
153
  }
138
154
 
139
155
  /* Header navigation tabs (sit next to title) */
140
156
  .header-nav {
141
157
  display: flex;
142
158
  align-items: flex-end;
143
- gap: 6px;
144
- }
145
- .header-nav .tab {
146
- display: inline-block;
147
- padding: 8px 12px 9px;
148
- color: var(--muted);
149
- text-decoration: none;
150
- font-weight: 600;
151
- border: 1px solid transparent;
152
- border-top-left-radius: 8px;
153
- border-top-right-radius: 8px;
154
- border-bottom-left-radius: 0;
155
- border-bottom-right-radius: 0;
156
- line-height: 1.2;
157
- }
158
- .header-nav .tab:hover,
159
- .header-nav .tab:focus {
160
- color: var(--fg);
161
- outline-offset: 0px;
162
- }
163
- .header-nav .tab.active {
164
- color: var(--fg);
165
- background: var(--bg);
166
- border-color: var(--border);
167
- /* Blend into the page content below the header by covering the header border */
168
- border-bottom-color: var(--bg);
169
- margin-bottom: -1px; /* overlap header bottom border */
170
- padding-bottom: 10px;
171
- position: relative;
172
- z-index: 1;
159
+ gap: var(--space-3);
160
+
161
+ .tab {
162
+ display: inline-block;
163
+ padding: var(--space-4) var(--space-6) 9px;
164
+ color: var(--muted);
165
+ text-decoration: none;
166
+ font-weight: 600;
167
+ border: 1px solid transparent;
168
+ border-top-left-radius: 8px;
169
+ border-top-right-radius: 8px;
170
+ border-bottom-left-radius: 0;
171
+ border-bottom-right-radius: 0;
172
+ line-height: 1.2;
173
+
174
+ &:hover,
175
+ &:focus {
176
+ color: var(--fg);
177
+ outline-offset: var(--outline-offset-s);
178
+ }
179
+
180
+ &.active {
181
+ color: var(--fg);
182
+ background: var(--bg);
183
+ border-color: var(--border);
184
+ /* Blend into the page content below the header by covering the header border */
185
+ border-bottom-color: var(--bg);
186
+ margin-bottom: -1px; /* overlap header bottom border */
187
+ padding-bottom: 10px;
188
+ position: relative;
189
+ z-index: 1;
190
+ }
191
+ }
173
192
  }
174
193
 
175
194
  .header-actions {
176
195
  display: flex;
177
196
  align-items: center;
178
- gap: 10px;
197
+ gap: var(--space-5);
179
198
  }
180
199
  .theme-toggle {
181
200
  display: inline-flex;
182
201
  align-items: center;
183
- gap: 6px;
202
+ gap: var(--space-3);
184
203
  font-size: 12px;
185
204
  color: var(--muted);
186
- }
187
- .theme-toggle input[type='checkbox'] {
188
- --switch-h: 22px;
189
- appearance: none;
190
- position: relative;
191
- width: 44px;
192
- height: var(--switch-h);
193
- border-radius: var(--switch-h);
194
- border: 1px solid var(--control-border);
195
- background: var(--control-bg);
196
- transition:
197
- background 160ms ease,
198
- border-color 160ms ease;
199
- cursor: pointer;
200
- }
201
- .theme-toggle input[type='checkbox']::after {
202
- content: '';
203
- position: absolute;
204
- top: 50%;
205
- left: 2px;
206
- width: calc(var(--switch-h) - 6px);
207
- height: calc(var(--switch-h) - 6px);
208
- border-radius: 999px;
209
- background: var(--button-bg);
210
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
211
- transform: translate(0, -50%);
212
- transition: transform 180ms ease;
213
- }
214
- .theme-toggle input[type='checkbox']:checked::after {
215
- left: auto;
216
- right: 2px;
217
- transform: translate(0, -50%);
205
+
206
+ input[type='checkbox'] {
207
+ --switch-h: 22px;
208
+ appearance: none;
209
+ position: relative;
210
+ width: 44px;
211
+ height: var(--switch-h);
212
+ border-radius: var(--switch-h);
213
+ border: 1px solid var(--control-border);
214
+ background: var(--control-bg);
215
+ transition:
216
+ background 160ms ease,
217
+ border-color 160ms ease;
218
+ cursor: pointer;
219
+
220
+ &::after {
221
+ content: '';
222
+ position: absolute;
223
+ top: 50%;
224
+ left: 2px;
225
+ width: calc(var(--switch-h) - 6px);
226
+ height: calc(var(--switch-h) - 6px);
227
+ border-radius: 999px;
228
+ background: var(--button-bg);
229
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
230
+ transform: translate(0, -50%);
231
+ transition: transform 180ms ease;
232
+ }
233
+
234
+ &:checked::after {
235
+ left: auto;
236
+ right: 2px;
237
+ transform: translate(0, -50%);
238
+ }
239
+ }
218
240
  }
219
241
 
220
242
  .app-shell {
@@ -228,16 +250,20 @@ a:focus {
228
250
  }
229
251
 
230
252
  /* Board route: fill height and let columns scroll */
231
- .route.board {
232
- display: flex;
233
- flex-direction: column;
234
- min-height: 0;
235
- max-height: 100%;
236
- }
237
- .route.board .panel__body {
238
- flex: 1;
239
- min-height: 0;
240
- overflow-x: auto; /* scroll columns horizontal */
253
+ .route {
254
+ &.board {
255
+ display: flex;
256
+ flex-direction: column;
257
+ min-height: 0;
258
+ height: 100%;
259
+ max-height: 100%;
260
+
261
+ .panel__body {
262
+ flex: 1;
263
+ min-height: 0;
264
+ overflow-x: auto; /* scroll columns horizontal */
265
+ }
266
+ }
241
267
  }
242
268
 
243
269
  .panel {
@@ -254,13 +280,13 @@ a:focus {
254
280
  background: inherit;
255
281
  display: flex;
256
282
  align-items: center;
257
- gap: 10px;
283
+ gap: var(--space-5);
258
284
  min-height: 44px;
259
- padding: 6px 18px;
285
+ padding: var(--space-3) var(--space-9);
260
286
  }
261
287
 
262
288
  #detail-panel .panel__header {
263
- padding-left: 18px;
289
+ padding-left: var(--space-9);
264
290
  }
265
291
 
266
292
  .muted {
@@ -280,61 +306,63 @@ a:focus {
280
306
  }
281
307
  .editable:hover {
282
308
  outline: 2px solid var(--control-border, var(--border));
283
- outline-offset: var(--outline-offset);
309
+ outline-offset: var(--outline-offset-l);
284
310
  }
285
311
  .editable:focus-within {
286
312
  outline: 2px solid color-mix(in srgb, var(--link) 60%, transparent);
287
- outline-offset: var(--outline-offset);
313
+ outline-offset: var(--outline-offset-l);
288
314
  cursor: text;
289
315
  }
290
316
  .editable-actions {
291
- margin-top: 6px;
317
+ margin-top: var(--space-3);
292
318
  display: flex;
293
- gap: 8px;
319
+ gap: var(--space-4);
294
320
  }
295
321
 
296
322
  /* Minimal markdown styles */
297
- .md h1 {
298
- font-size: 20px;
299
- margin: 12px 0 6px;
300
- }
301
- .md h2 {
302
- font-size: 18px;
303
- margin: 12px 0 6px;
304
- }
305
- .md h3 {
306
- font-size: 16px;
307
- margin: 10px 0 6px;
308
- }
309
- .md h4 {
310
- font-size: 15px;
311
- margin: 8px 0 4px;
312
- }
313
- .md h5,
314
- .md h6 {
315
- font-size: 14px;
316
- margin: 6px 0 4px;
317
- }
318
- .md p {
319
- margin: 6px 0;
320
- }
321
- .md ul,
322
- .md ol {
323
- margin: 6px 0 6px 14px;
324
- padding-left: 14px;
325
- }
326
- .md code {
327
- background: var(--code-bg);
328
- color: var(--code-fg);
329
- padding: 3px 4px;
330
- border-radius: 3px;
331
- }
332
- .md pre {
333
- background: var(--pre-bg);
334
- color: var(--pre-fg);
335
- padding: 10px;
336
- overflow: auto;
337
- border-radius: 6px;
323
+ .md {
324
+ h1 {
325
+ font-size: 20px;
326
+ margin: var(--space-6) 0 var(--space-3);
327
+ }
328
+ h2 {
329
+ font-size: 18px;
330
+ margin: var(--space-6) 0 var(--space-3);
331
+ }
332
+ h3 {
333
+ font-size: 16px;
334
+ margin: var(--space-5) 0 var(--space-3);
335
+ }
336
+ h4 {
337
+ font-size: 15px;
338
+ margin: var(--space-4) 0 var(--space-2);
339
+ }
340
+ h5,
341
+ h6 {
342
+ font-size: 14px;
343
+ margin: var(--space-3) 0 var(--space-2);
344
+ }
345
+ p {
346
+ margin: var(--space-3) 0;
347
+ }
348
+ ul,
349
+ ol {
350
+ margin: var(--space-3) 0 var(--space-3) var(--space-7);
351
+ padding-left: var(--space-7);
352
+ }
353
+ code {
354
+ background: var(--code-bg);
355
+ color: var(--code-fg);
356
+ padding: 3px var(--space-2);
357
+ border-radius: 3px;
358
+ }
359
+ pre {
360
+ background: var(--pre-bg);
361
+ color: var(--pre-fg);
362
+ padding: var(--space-5);
363
+ overflow: auto;
364
+ border-radius: 6px;
365
+ }
338
366
  }
339
367
 
340
368
  /* Form controls */
@@ -347,7 +375,7 @@ textarea {
347
375
  color: var(--control-fg, var(--fg));
348
376
  border: 1px solid var(--control-border, var(--border));
349
377
  border-radius: 4px;
350
- padding: 4px 6px;
378
+ padding: var(--space-2) var(--space-3);
351
379
  line-height: 2;
352
380
  transition:
353
381
  border-color 160ms ease,
@@ -377,7 +405,7 @@ button {
377
405
  background: var(--button-bg, #f3f4f6);
378
406
  color: var(--button-fg, var(--fg));
379
407
  border: 1px solid var(--button-border, var(--border));
380
- padding: 4px 8px;
408
+ padding: var(--space-2) var(--space-4);
381
409
  border-radius: 4px;
382
410
  cursor: pointer;
383
411
  transition:
@@ -427,7 +455,7 @@ button:disabled {
427
455
  cursor: default;
428
456
  }
429
457
  .type-badge + .type-badge {
430
- margin-left: 4px;
458
+ margin-left: var(--space-2);
431
459
  }
432
460
  .type-badge--neutral {
433
461
  background: var(--badge-bg-neutral);
@@ -474,7 +502,7 @@ button:disabled {
474
502
  .status-badge + .status-badge,
475
503
  .priority-badge + .priority-badge,
476
504
  .badge-select + .badge-select {
477
- margin-left: 4px;
505
+ margin-left: var(--space-2);
478
506
  }
479
507
 
480
508
  /* Status + Priority palette (light) derived from base */
@@ -685,7 +713,7 @@ html[data-theme='dark'] {
685
713
  }
686
714
  .badge-select:focus {
687
715
  outline: 2px solid color-mix(in srgb, var(--link) 50%, transparent);
688
- outline-offset: 2px;
716
+ outline-offset: var(--outline-offset-m);
689
717
  box-shadow: none;
690
718
  }
691
719
 
@@ -702,7 +730,7 @@ input.inline-edit {
702
730
  }
703
731
  input.inline-edit:focus {
704
732
  outline: 2px solid color-mix(in srgb, var(--link) 50%, transparent);
705
- outline-offset: var(--outline-offset);
733
+ outline-offset: var(--outline-offset-l);
706
734
  box-shadow: none;
707
735
  }
708
736
 
@@ -753,7 +781,7 @@ input.inline-edit:focus {
753
781
  /* Keyboard focus ring helper */
754
782
  :focus-visible {
755
783
  outline: 2px solid color-mix(in srgb, var(--link) 60%, transparent);
756
- outline-offset: 2px;
784
+ outline-offset: var(--outline-offset-m);
757
785
  }
758
786
 
759
787
  /* Subtle scrollbars (WebKit/Blink) */
@@ -824,7 +852,7 @@ input.inline-edit:focus {
824
852
  border: none;
825
853
  background: transparent;
826
854
  outline: 2px solid var(--control-border, var(--border));
827
- outline-offset: var(--outline-offset);
855
+ outline-offset: var(--outline-offset-l);
828
856
  }
829
857
  #detail-root input[type='text']:focus,
830
858
  #detail-root textarea:focus {
@@ -852,49 +880,54 @@ input.inline-edit:focus {
852
880
  .epic-header {
853
881
  display: flex;
854
882
  align-items: center;
855
- gap: 8px;
856
- padding: 10px 12px;
883
+ gap: var(--space-4);
884
+ padding: var(--space-5) var(--space-6);
857
885
  cursor: pointer;
858
886
  background: color-mix(in srgb, var(--panel-bg) 90%, transparent);
859
887
  border-bottom: 1px solid var(--border);
860
- }
861
- .epic-header:hover {
862
- background: color-mix(in srgb, var(--control-bg) 60%, transparent);
888
+
889
+ &:hover {
890
+ background: color-mix(in srgb, var(--control-bg) 60%, transparent);
891
+ }
892
+
893
+ /* Compact native progress for epic status */
894
+ progress {
895
+ width: 140px;
896
+ height: 8px;
897
+ accent-color: var(--status-closed-base);
898
+ }
863
899
  }
864
900
  .epic-children {
865
901
  padding: 8px 12px 12px;
866
902
  }
867
- /* Compact native progress for epic status */
868
- .epic-header progress {
869
- width: 140px;
870
- height: 8px;
871
- accent-color: var(--status-closed-base);
872
- }
873
903
  .table {
874
904
  width: 100%;
875
905
  border-collapse: collapse;
876
906
  table-layout: fixed; /* keep column proportions stable while editing */
877
- }
878
- .table th,
879
- .table td {
880
- text-align: left;
881
- padding: 12px 16px;
882
- border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
883
- }
884
- /* Ensure form controls fill the cell */
885
- .table td input[type='text'],
886
- .table td select {
887
- width: 100%;
888
- box-sizing: border-box;
889
- }
890
- .table td .editable {
891
- display: block;
892
- width: 100%;
893
- }
894
- .table th {
895
- font-size: 12px;
896
- color: var(--muted);
897
- font-weight: 600;
907
+
908
+ th,
909
+ td {
910
+ text-align: left;
911
+ padding: var(--space-6) var(--space-8);
912
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
913
+ }
914
+ /* Ensure form controls fill the cell */
915
+ td {
916
+ input[type='text'],
917
+ select {
918
+ width: 100%;
919
+ box-sizing: border-box;
920
+ }
921
+ .editable {
922
+ display: block;
923
+ width: 100%;
924
+ }
925
+ }
926
+ th {
927
+ font-size: 12px;
928
+ color: var(--muted);
929
+ font-weight: 600;
930
+ }
898
931
  }
899
932
  .epic-row:hover {
900
933
  background: color-mix(in srgb, var(--control-bg) 55%, transparent);
@@ -904,17 +937,8 @@ input.inline-edit:focus {
904
937
  .board-root {
905
938
  display: grid;
906
939
  grid-template-columns: repeat(4, 1fr);
907
- gap: 16px;
908
- padding: 12px;
909
- }
910
-
911
- /* UI-121: stack two board columns vertically in a single grid cell */
912
- .board-stack-2 {
913
- display: grid;
914
- grid-template-rows: 1fr 1fr;
915
- gap: 16px;
916
- min-width: 380px;
917
- min-height: 0;
940
+ gap: var(--space-8);
941
+ padding: var(--space-6);
918
942
  }
919
943
  .board-column {
920
944
  display: flex;
@@ -942,28 +966,29 @@ input.inline-edit:focus {
942
966
  }
943
967
  /* Small, unobtrusive select for closed filter */
944
968
  .board-closed-filter {
945
- margin: -8px 0;
946
- }
947
- .board-closed-filter select {
948
- font-size: 13px;
949
- border-radius: 999px;
950
- border-color: var(--border);
951
- padding: 2px 18px 2px 12px;
969
+ margin: calc(var(--space-4) * -1) 0;
970
+
971
+ select {
972
+ font-size: 13px;
973
+ border-radius: 999px;
974
+ border-color: var(--border);
975
+ padding: 2px 18px 2px 12px;
976
+ }
952
977
  }
953
978
  .board-column__body {
954
- padding: 10px;
979
+ padding: var(--space-5);
955
980
  overflow: visible;
956
981
  display: flex;
957
982
  flex-direction: column;
958
983
  /* UI-77: increase vertical spacing between card rows */
959
- gap: 14px;
984
+ gap: var(--space-7);
960
985
  }
961
986
  .board-card {
962
987
  /* UI-76: remove borders and match page body background */
963
988
  border: none;
964
989
  border-radius: 6px;
965
990
  /* UI-77: increase internal padding for readability */
966
- padding: 12px 14px;
991
+ padding: var(--space-6) var(--space-7);
967
992
  /* Match the body background so cards feel integrated */
968
993
  background: var(--bg);
969
994
  cursor: pointer;
@@ -983,19 +1008,19 @@ input.inline-edit:focus {
983
1008
  }
984
1009
  .board-card:focus {
985
1010
  outline: 2px solid color-mix(in srgb, var(--link) 50%, transparent);
986
- outline-offset: 0px;
1011
+ outline-offset: var(--outline-offset-s);
987
1012
  }
988
1013
  .board-card__title {
989
1014
  font-weight: 600;
990
- margin-bottom: 4px;
1015
+ margin-bottom: var(--space-2);
991
1016
  }
992
1017
  .board-card__meta {
993
- margin-top: 8px;
1018
+ margin-top: var(--space-4);
994
1019
  font-size: 12px;
995
1020
  color: var(--muted);
996
1021
  display: flex;
997
1022
  align-items: center;
998
- gap: 8px;
1023
+ gap: var(--space-4);
999
1024
  }
1000
1025
 
1001
1026
  @media (max-width: 1100px) {
@@ -5,6 +5,7 @@ import { issueDisplayId } from './issue-id.js';
5
5
  * Looks like the current inline ID (monospace `#123`) but acts as a button
6
6
  * that copies the full, prefixed ID (e.g., `UI-123`) when activated.
7
7
  * Shows transient "Copied" feedback and then restores the ID.
8
+ *
8
9
  * @param {string} id - Full issue id including the prefix (e.g., "UI-123").
9
10
  * @param {{ class_name?: string, duration_ms?: number }} [opts]
10
11
  * @returns {HTMLButtonElement}
@@ -25,7 +26,7 @@ export function createIssueIdRenderer(id, opts) {
25
26
  const label = issueDisplayId(id);
26
27
  btn.textContent = label;
27
28
 
28
- /** Copy handler with feedback */
29
+ /** Copy handler with feedback. */
29
30
  async function doCopy() {
30
31
  // Prevent accidental row navigation and parent handlers
31
32
  // (click/key handlers call this inside an event context)
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Format a beads issue id as a user-facing display string `#${n}`.
3
3
  * Extracts the trailing numeric portion of the id and prefixes with '#'.
4
+ *
4
5
  * @param {string | null | undefined} id
5
6
  * @returns {string}
6
7
  */
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Known issue types in canonical order for dropdowns.
3
+ *
3
4
  * @type {Array<'bug'|'feature'|'task'|'epic'|'chore'>}
4
5
  */
5
6
  export const ISSUE_TYPES = ['bug', 'feature', 'task', 'epic', 'chore'];
6
7
 
7
8
  /**
8
9
  * Return a human-friendly label for an issue type.
10
+ *
9
11
  * @param {string | null | undefined} type
10
12
  * @returns {string}
11
13
  */
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Build a canonical issue hash that retains the view.
3
+ *
3
4
  * @param {'issues'|'epics'|'board'} view
4
5
  * @param {string} id
5
6
  */