@skillcdn/console 0.1.8 → 0.1.10

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 (147) hide show
  1. package/README.md +5 -5
  2. package/dist/api.d.ts +5 -5
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +4 -4
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +255 -10
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +9 -1
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +80 -0
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +62 -7
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/client.d.ts +31 -4
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +19 -3
  19. package/dist/client.js.map +1 -1
  20. package/dist/components/board.d.ts +27 -1
  21. package/dist/components/board.d.ts.map +1 -1
  22. package/dist/components/board.js +75 -7
  23. package/dist/components/board.js.map +1 -1
  24. package/dist/components/calendar.d.ts +34 -0
  25. package/dist/components/calendar.d.ts.map +1 -0
  26. package/dist/components/calendar.js +96 -0
  27. package/dist/components/calendar.js.map +1 -0
  28. package/dist/components/categories.d.ts +13 -0
  29. package/dist/components/categories.d.ts.map +1 -0
  30. package/dist/components/categories.js +52 -0
  31. package/dist/components/categories.js.map +1 -0
  32. package/dist/components/decision-list.d.ts +10 -0
  33. package/dist/components/decision-list.d.ts.map +1 -1
  34. package/dist/components/decision-list.js +14 -1
  35. package/dist/components/decision-list.js.map +1 -1
  36. package/dist/components/event-feed.d.ts.map +1 -1
  37. package/dist/components/event-feed.js +12 -0
  38. package/dist/components/event-feed.js.map +1 -1
  39. package/dist/components/goals.d.ts +35 -0
  40. package/dist/components/goals.d.ts.map +1 -0
  41. package/dist/components/goals.js +45 -0
  42. package/dist/components/goals.js.map +1 -0
  43. package/dist/components/task-form.d.ts +5 -1
  44. package/dist/components/task-form.d.ts.map +1 -1
  45. package/dist/components/task-form.js +13 -1
  46. package/dist/components/task-form.js.map +1 -1
  47. package/dist/components/task-list.d.ts +16 -0
  48. package/dist/components/task-list.d.ts.map +1 -0
  49. package/dist/components/task-list.js +14 -0
  50. package/dist/components/task-list.js.map +1 -0
  51. package/dist/components/task-view.d.ts +9 -1
  52. package/dist/components/task-view.d.ts.map +1 -1
  53. package/dist/components/task-view.js +10 -4
  54. package/dist/components/task-view.js.map +1 -1
  55. package/dist/components/ui.d.ts +30 -1
  56. package/dist/components/ui.d.ts.map +1 -1
  57. package/dist/components/ui.js +60 -0
  58. package/dist/components/ui.js.map +1 -1
  59. package/dist/components/work.d.ts +10 -0
  60. package/dist/components/work.d.ts.map +1 -0
  61. package/dist/components/work.js +29 -0
  62. package/dist/components/work.js.map +1 -0
  63. package/dist/console.css +511 -0
  64. package/dist/console.d.ts +8 -0
  65. package/dist/console.d.ts.map +1 -1
  66. package/dist/console.js +124 -8
  67. package/dist/console.js.map +1 -1
  68. package/dist/data.d.ts +14 -1
  69. package/dist/data.d.ts.map +1 -1
  70. package/dist/data.js +36 -2
  71. package/dist/data.js.map +1 -1
  72. package/dist/dates.d.ts +39 -0
  73. package/dist/dates.d.ts.map +1 -0
  74. package/dist/dates.js +120 -0
  75. package/dist/dates.js.map +1 -0
  76. package/dist/i18n/en.d.ts +162 -1
  77. package/dist/i18n/en.d.ts.map +1 -1
  78. package/dist/i18n/en.js +174 -1
  79. package/dist/i18n/en.js.map +1 -1
  80. package/dist/i18n/ko.d.ts.map +1 -1
  81. package/dist/i18n/ko.js +169 -1
  82. package/dist/i18n/ko.js.map +1 -1
  83. package/dist/index.d.ts +10 -4
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +10 -4
  86. package/dist/index.js.map +1 -1
  87. package/dist/limits.d.ts +5 -0
  88. package/dist/limits.d.ts.map +1 -1
  89. package/dist/limits.js +5 -0
  90. package/dist/limits.js.map +1 -1
  91. package/dist/router.d.ts +50 -1
  92. package/dist/router.d.ts.map +1 -1
  93. package/dist/router.js +74 -1
  94. package/dist/router.js.map +1 -1
  95. package/dist/routes.d.ts +7 -1
  96. package/dist/routes.d.ts.map +1 -1
  97. package/dist/routes.js +9 -0
  98. package/dist/routes.js.map +1 -1
  99. package/dist/schemas.d.ts +366 -0
  100. package/dist/schemas.d.ts.map +1 -1
  101. package/dist/schemas.js +112 -3
  102. package/dist/schemas.js.map +1 -1
  103. package/dist/vocabulary.d.ts +15 -1
  104. package/dist/vocabulary.d.ts.map +1 -1
  105. package/dist/vocabulary.js +26 -0
  106. package/dist/vocabulary.js.map +1 -1
  107. package/docs/specs/cli.md +11 -4
  108. package/docs/specs/rest.md +33 -8
  109. package/package.json +1 -1
  110. package/skills/building-a-console/SKILL.md +4 -4
  111. package/skills/building-a-console/references/the-default-console.md +1 -1
  112. package/skills/deploying-a-console/SKILL.md +11 -10
  113. package/skills/deploying-a-console/assets/deployer-policy.json +13 -12
  114. package/skills/deploying-a-console/assets/secrets.example.json +4 -4
  115. package/skills/deploying-a-console/references/access.md +10 -9
  116. package/skills/deploying-a-console/references/resources.md +107 -50
  117. package/skills/deploying-a-console/references/sizing.md +1 -1
  118. package/skills/deploying-a-console/scripts/store-secrets.mjs +1 -15
  119. package/skills/working-the-board/SKILL.md +7 -6
  120. package/src/api.ts +30 -0
  121. package/src/cli/cli.ts +311 -10
  122. package/src/cli/format.ts +88 -0
  123. package/src/cli/help.ts +69 -7
  124. package/src/client.ts +70 -2
  125. package/src/components/board.tsx +205 -12
  126. package/src/components/calendar.tsx +255 -0
  127. package/src/components/categories.tsx +209 -0
  128. package/src/components/decision-list.tsx +62 -5
  129. package/src/components/event-feed.tsx +12 -0
  130. package/src/components/goals.tsx +264 -0
  131. package/src/components/task-form.tsx +78 -1
  132. package/src/components/task-list.tsx +61 -0
  133. package/src/components/task-view.tsx +74 -3
  134. package/src/components/ui.tsx +116 -1
  135. package/src/components/work.tsx +83 -0
  136. package/src/console.tsx +337 -4
  137. package/src/data.ts +63 -8
  138. package/src/dates.ts +143 -0
  139. package/src/i18n/en.ts +184 -1
  140. package/src/i18n/ko.ts +175 -1
  141. package/src/index.ts +58 -1
  142. package/src/limits.ts +5 -0
  143. package/src/router.ts +119 -6
  144. package/src/routes.ts +10 -0
  145. package/src/schemas.ts +141 -1
  146. package/src/styles/console.css +511 -0
  147. package/src/vocabulary.ts +33 -0
package/src/i18n/ko.ts CHANGED
@@ -27,6 +27,17 @@ export const ko: Messages = {
27
27
  failed: "실패",
28
28
  abandoned: "중단됨",
29
29
  },
30
+ goalState: { planned: "계획됨", active: "진행 중", done: "달성", dropped: "중단" },
31
+ categoryColor: {
32
+ gray: "회색",
33
+ blue: "파랑",
34
+ green: "초록",
35
+ orange: "주황",
36
+ pink: "분홍",
37
+ purple: "보라",
38
+ teal: "청록",
39
+ yellow: "노랑",
40
+ },
30
41
  },
31
42
 
32
43
  common: {
@@ -61,9 +72,13 @@ export const ko: Messages = {
61
72
 
62
73
  nav: {
63
74
  projects: "프로젝트",
75
+ work: "내 작업",
64
76
  people: "사람들",
65
77
  agents: "에이전트",
78
+ categories: "카테고리",
66
79
  board: "보드",
80
+ roadmap: "로드맵",
81
+ calendar: "캘린더",
67
82
  docs: "문서",
68
83
  decisions: "결정",
69
84
  feed: "피드",
@@ -166,7 +181,25 @@ export const ko: Messages = {
166
181
  decisionsTitle: "사람을 기다리는 결정",
167
182
  agentAtWork: "에이전트 작업 중",
168
183
  agentAtWorkTitle: "에이전트가 이 작업을 하고 있습니다",
169
- links: (n) => `링크 ${n}개`,
184
+ agentSince: (agent, since) => `${agent}, ${since}부터 작업 중`,
185
+ filters: {
186
+ aria: "보드 좁혀 보기",
187
+ everyone: "모두",
188
+ mine: "내 작업",
189
+ anyCategory: "모든 카테고리",
190
+ noCategory: "카테고리 없음",
191
+ anyGoal: "모든 목표",
192
+ noGoal: "목표 없음",
193
+ clear: "전체 보기",
194
+ showing: (n, of) => `작업 ${of}개 중 ${n}개`,
195
+ },
196
+ due: {
197
+ overdue: (days) => `${days}일 지남`,
198
+ today: "오늘 마감",
199
+ tomorrow: "내일 마감",
200
+ inDays: (days) => `${days}일 남음`,
201
+ on: (day) => `${day} 마감`,
202
+ },
170
203
  },
171
204
 
172
205
  task: {
@@ -192,6 +225,19 @@ export const ko: Messages = {
192
225
  history: "일어난 일",
193
226
  subtasks: "하위 작업",
194
227
  partOfIt: "하위 작업",
228
+ category: "카테고리",
229
+ goal: "목표",
230
+ plan: "일정",
231
+ planned: (from, to) =>
232
+ from !== undefined && to !== undefined
233
+ ? `${from}부터 ${to}까지`
234
+ : from !== undefined
235
+ ? `${from}부터`
236
+ : to !== undefined
237
+ ? `${to}까지`
238
+ : "",
239
+ started: "시작",
240
+ done: "완료",
195
241
  form: {
196
242
  title: "제목",
197
243
  body: "본문",
@@ -200,6 +246,12 @@ export const ko: Messages = {
200
246
  priority: "우선순위",
201
247
  assignee: "배정",
202
248
  partOf: "상위 작업",
249
+ category: "카테고리",
250
+ noCategory: "카테고리 없음",
251
+ goal: "목표",
252
+ noGoal: "목표 없음",
253
+ startsOn: "시작일",
254
+ dueOn: "기한",
203
255
  links: "링크",
204
256
  label: "이름",
205
257
  addLink: "링크 추가",
@@ -223,6 +275,10 @@ export const ko: Messages = {
223
275
  },
224
276
  waiting: "대기 중",
225
277
  answered: "답변됨",
278
+ record: "기록",
279
+ recordLead:
280
+ "답변된 모든 결정, 최신순. 무엇을 물었고, 무엇을 골랐고, 누가 골랐는지. 맥락과 근거, 그 뒤에 일어난 일은 각 결정을 열어 봅니다.",
281
+ openRecord: "열기",
226
282
  waitingForPerson: "사람을 기다리는 중입니다.",
227
283
  options: "선택지",
228
284
  notePlaceholder: "덧붙일 말이 있다면",
@@ -250,6 +306,111 @@ export const ko: Messages = {
250
306
  },
251
307
  },
252
308
 
309
+ calendar: {
310
+ title: "캘린더",
311
+ lead: "작업을 기한일에 놓고 봅니다. 시작일이 있는 작업은 그날부터 보이고, 날짜가 없는 작업은 아래에서 날짜를 기다립니다.",
312
+ grid: "이 달",
313
+ previous: "이전 달",
314
+ next: "다음 달",
315
+ thisMonth: "이번 달",
316
+ nothingDue: "이 달에 마감인 작업이 없습니다.",
317
+ overdue: "기한 지남",
318
+ nothingOverdue: "기한이 지난 작업이 없습니다.",
319
+ unscheduled: "날짜 없음",
320
+ allScheduled: "열린 작업마다 날짜가 있습니다.",
321
+ starts: "시작",
322
+ workload: {
323
+ title: "누가 무엇을",
324
+ lead: "사람마다 열린 작업과, 그중 늦은 것과 일주일 안에 마감인 것.",
325
+ person: "사람",
326
+ open: "열림",
327
+ inProgress: "진행 중",
328
+ dueSoon: "7일 내 마감",
329
+ overdue: "기한 지남",
330
+ unassigned: "아직 배정 없음",
331
+ },
332
+ },
333
+
334
+ roadmap: {
335
+ title: "로드맵",
336
+ lead: "프로젝트가 향하는 것. 목표마다 달성 목표일과, 그 아래 작업들이 어디까지 왔는지. 진행은 보드의 현재 상태에서 그대로 읽습니다.",
337
+ setGoal: "목표 세우기",
338
+ setGoalAria: "목표 세우기",
339
+ editAria: "목표 바꾸기",
340
+ one: "목표",
341
+ all: "모든 목표",
342
+ empty: {
343
+ title: "아직 세운 목표가 없습니다",
344
+ body: "소유자가 첫 목표를 세웁니다. 무엇을, 언제까지. 그 뒤 작업을 그 목표를 향해 씁니다.",
345
+ },
346
+ noSuch: {
347
+ title: "그런 목표가 없습니다",
348
+ body: "다른 프로젝트의 것이거나, 링크가 잘못됐습니다.",
349
+ },
350
+ onlyOwner: {
351
+ title: "로드맵은 소유자만 세웁니다",
352
+ body: "프로젝트 소유자나 워크스페이스 관리자에게 요청하세요.",
353
+ },
354
+ progress: (done, of) => `작업 ${of}개 중 ${done}개 완료`,
355
+ inProgress: (n) => `${n}개 진행 중`,
356
+ noTasks: "아직 이 목표를 향한 작업이 없습니다.",
357
+ tasks: "작업",
358
+ target: "목표일",
359
+ noTarget: "목표일 없음",
360
+ setBy: "세운 사람",
361
+ state: "상태",
362
+ nothingMore: "더 적힌 내용이 없습니다.",
363
+ form: {
364
+ title: "제목",
365
+ body: "달성의 의미",
366
+ bodyPlaceholder: "목표를 이루면 무엇이 참이 되는지, Markdown으로.",
367
+ state: "상태",
368
+ dueOn: "목표일",
369
+ set: "목표 세우기",
370
+ },
371
+ },
372
+
373
+ work: {
374
+ title: "내 작업",
375
+ lead: "내 프로젝트들에 걸쳐 내게 놓인 일. 내게 배정된 열린 작업을 기한순으로, 그리고 사람을 기다리는 결정.",
376
+ loading: "내 작업을 불러오는 중",
377
+ couldNotLoad: "내 작업을 불러올 수 없습니다.",
378
+ empty: {
379
+ title: "내게 배정된 작업이 없습니다",
380
+ body: "볼 수 있는 어느 프로젝트에서든 내게 배정된 작업이 여기에 나타납니다.",
381
+ },
382
+ overdue: "기한 지남",
383
+ thisWeek: "이번 주 마감",
384
+ later: "그 뒤",
385
+ noDay: "날짜 없음",
386
+ decisions: "사람을 기다리는 결정",
387
+ noDecisions: "기다리는 결정이 없습니다.",
388
+ inProject: (project) => `${project}에서`,
389
+ },
390
+
391
+ categories: {
392
+ title: "카테고리",
393
+ lead: "작업을 나누는 일의 종류이며, 모든 프로젝트에서 같습니다. 작업은 하나를 가지고, 보드는 하나로 좁혀 보며, 관리자가 목록을 관리합니다.",
394
+ onlyAdmin: "관리자가 추가하고 바꾸고 제거합니다. 다른 사람은 여기서 읽습니다.",
395
+ list: "카테고리",
396
+ add: "카테고리 추가",
397
+ addAria: "카테고리 추가",
398
+ empty: {
399
+ title: "카테고리가 없습니다",
400
+ body: "관리자가 여기서 첫 카테고리를 추가합니다.",
401
+ },
402
+ inUse: "작업이 속해 있는 카테고리는 그 작업들을 옮기기 전에는 제거할 수 없습니다.",
403
+ form: {
404
+ name: "이름",
405
+ key: "키",
406
+ keyHint: "소문자, 숫자, 하이픈. 명령과 필터가 부르는 이름이며 바뀌지 않습니다.",
407
+ color: "색",
408
+ add: "추가",
409
+ },
410
+ nameOf: (key) => `${key}의 이름`,
411
+ colorOf: (key) => `${key}의 색`,
412
+ },
413
+
253
414
  feed: {
254
415
  title: "일어난 일",
255
416
  list: "일어난 일",
@@ -263,6 +424,8 @@ export const ko: Messages = {
263
424
  something: "무언가",
264
425
  aQuestion: "질문",
265
426
  aFile: "파일",
427
+ aGoal: "목표",
428
+ aCategory: "카테고리",
266
429
  thePage: (name) => `페이지 ${name}`.trimEnd(),
267
430
  role: {
268
431
  admin: "관리자",
@@ -282,6 +445,12 @@ export const ko: Messages = {
282
445
  visibility: "멤버 범위",
283
446
  skillsAddress: "스킬 주소",
284
447
  outcome: "그 뒤에 일어난 일",
448
+ category: "카테고리",
449
+ goalId: "목표",
450
+ startsOn: "시작일",
451
+ dueOn: "기한",
452
+ state: "상태",
453
+ color: "색",
285
454
  },
286
455
  event: {
287
456
  personJoined: () => "보드에 들어왔습니다",
@@ -293,6 +462,11 @@ export const ko: Messages = {
293
462
  memberChanged: (login, role, project) =>
294
463
  `${login}을(를) ${project}의 ${role}(으)로 지정했습니다`,
295
464
  memberRemoved: (login, project) => `${login}을(를) ${project}에서 제외했습니다`,
465
+ categoryCreated: (category) => `카테고리 ${category}을(를) 만들었습니다`,
466
+ categoryUpdated: (fields, category) => `카테고리 ${category}의 ${fields}을(를) 바꿨습니다`,
467
+ categoryRemoved: (category) => `카테고리 ${category}을(를) 제거했습니다`,
468
+ goalCreated: (goal) => `목표 ${goal}을(를) 세웠습니다`,
469
+ goalUpdated: (fields, goal) => `목표 ${goal}의 ${fields}을(를) 바꿨습니다`,
296
470
  taskCreated: (task) => `${task}을(를) 썼습니다`,
297
471
  taskMoved: (task, from, to) => `${task}을(를) ${from}에서 ${to}(으)로 옮겼습니다`,
298
472
  taskUpdated: (fields, task) => `${task}의 ${fields}을(를) 바꿨습니다`,
package/src/index.ts CHANGED
@@ -4,7 +4,24 @@
4
4
  // a script that wants none of the pages. The styles are `@skillcdn/console/console.css`.
5
5
 
6
6
  export * from "./api.js";
7
- export { Board, type BoardProps, TaskCard } from "./components/board.js";
7
+ export {
8
+ Board,
9
+ BoardFilters,
10
+ type BoardFiltersProps,
11
+ type BoardProps,
12
+ byDue,
13
+ filterTasks,
14
+ TaskCard,
15
+ } from "./components/board.js";
16
+ export {
17
+ Calendar,
18
+ type CalendarProps,
19
+ Workload,
20
+ type WorkloadProps,
21
+ type WorkloadRow,
22
+ workloadOf,
23
+ } from "./components/calendar.js";
24
+ export { CategoryList, type CategoryListProps } from "./components/categories.js";
8
25
  export {
9
26
  ConnectApproval,
10
27
  type ConnectApprovalProps,
@@ -19,6 +36,8 @@ export {
19
36
  type DecisionCardProps,
20
37
  DecisionList,
21
38
  type DecisionListProps,
39
+ DecisionLog,
40
+ type DecisionLogProps,
22
41
  } from "./components/decision-list.js";
23
42
  export {
24
43
  DocumentCrumbs,
@@ -36,6 +55,14 @@ export {
36
55
  type FolderViewProps,
37
56
  } from "./components/documents.js";
38
57
  export { describeEvent, EventFeed, type EventFeedProps } from "./components/event-feed.js";
58
+ export {
59
+ GoalForm,
60
+ type GoalFormProps,
61
+ GoalList,
62
+ type GoalListProps,
63
+ GoalView,
64
+ type GoalViewProps,
65
+ } from "./components/goals.js";
39
66
  export { Markdown, type MarkdownProps } from "./components/markdown.js";
40
67
  export { PeopleList, type PeopleListProps } from "./components/people.js";
41
68
  export {
@@ -69,6 +96,7 @@ export {
69
96
  export { SignIn, type SignInProps } from "./components/sign-in.js";
70
97
  export { SkillList, type SkillListProps } from "./components/skills.js";
71
98
  export { TaskForm, type TaskFormProps } from "./components/task-form.js";
99
+ export { TaskList, type TaskListProps } from "./components/task-list.js";
72
100
  export { TaskView, type TaskViewProps } from "./components/task-view.js";
73
101
  export {
74
102
  NewToken,
@@ -83,13 +111,18 @@ export {
83
111
  Badge,
84
112
  Button,
85
113
  Callout,
114
+ CategoryBadge,
86
115
  cx,
116
+ Day,
117
+ DueBadge,
87
118
  EmptyState,
88
119
  formatBytes,
89
120
  formatInstant,
121
+ GoalStateBadge,
90
122
  PersonChip,
91
123
  PRIORITY_LABELS,
92
124
  PriorityBadge,
125
+ ProgressBar,
93
126
  ROLE_LABELS,
94
127
  RoleBadge,
95
128
  Spinner,
@@ -97,6 +130,7 @@ export {
97
130
  StateBadge,
98
131
  Time,
99
132
  } from "./components/ui.js";
133
+ export { WorkList, type WorkListProps } from "./components/work.js";
100
134
  export { ConnectPage, type ConnectPageProps } from "./connect-page.js";
101
135
  export {
102
136
  type ConsoleApp,
@@ -106,6 +140,24 @@ export {
106
140
  DEFAULT_COMPONENTS,
107
141
  } from "./console.js";
108
142
  export { type ConsoleActions, type ConsoleData, useConsoleData } from "./data.js";
143
+ export {
144
+ addDays,
145
+ addMonths,
146
+ dateOf,
147
+ dayOf,
148
+ daysBetween,
149
+ firstDayOfWeek,
150
+ formatDay,
151
+ formatMonth,
152
+ formatWeekday,
153
+ type GridDay,
154
+ isDay,
155
+ isMonth,
156
+ monthGrid,
157
+ monthOf,
158
+ today,
159
+ weekdays,
160
+ } from "./dates.js";
109
161
  export { DocsPage, type DocsPageProps } from "./docs-page.js";
110
162
  export {
111
163
  chooseLanguage,
@@ -120,14 +172,19 @@ export {
120
172
  useWords,
121
173
  } from "./i18n/index.js";
122
174
  export {
175
+ type BoardFilter,
176
+ boardHref,
177
+ calendarHref,
123
178
  connectHref,
124
179
  type DocsView,
125
180
  decisionHref,
126
181
  docHref,
182
+ goalHref,
127
183
  matchRoute,
128
184
  movedFrom,
129
185
  type Navigation,
130
186
  newDecisionHref,
187
+ newGoalHref,
131
188
  newTaskHref,
132
189
  PATHS,
133
190
  type ProjectPage,
package/src/limits.ts CHANGED
@@ -59,3 +59,8 @@ export const MAX_VERSIONS_PER_DOCUMENT = 500;
59
59
  export const MAX_FILES_PER_DOCUMENT = 50;
60
60
  /** The words a search of the documents takes. */
61
61
  export const MAX_SEARCH_LENGTH = 200;
62
+ /** A category of tasks (ADR-0019): its key, what the command and filters say, and its name. */
63
+ export const MAX_CATEGORY_KEY_LENGTH = 40;
64
+ export const MAX_CATEGORY_NAME_LENGTH = 60;
65
+ /** How many categories one workspace may have: a vocabulary, not a tag cloud. */
66
+ export const MAX_CATEGORIES = 50;
package/src/router.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { isFolderPath, isSignInFailure, SIGN_IN_PARAM, type SignInFailure } from "./api.js";
2
+ import { isMonth } from "./dates.js";
2
3
 
3
4
  // The addresses of the default console (ADR-0010): the router is a function from a URL to a
4
5
  // route, and the hrefs below are its inverse. A project's pages live under `/projects/<key>`,
5
6
  // the word the REST API uses; an address names what it shows (a task by its number, a decision
6
- // by its id, a document by its path), every form has one, and the query says how a page is
7
- // shown (`?edit`, `?ask`, `?new`, `?version=`, `?q=`). The old addresses are taken to the new.
7
+ // by its id, a document by its path, a goal by its id), every form has one, and the query says
8
+ // how a page is shown (`?edit`, `?ask`, `?new`, `?version=`, `?q=`, `?month=`, and the board's
9
+ // filters). The old addresses are taken to the new.
8
10
 
9
11
  /** How a task's page is shown: as it is, or with one of its forms open. */
10
12
  export type TaskFormKind = "edit" | "ask";
@@ -16,10 +18,22 @@ export type DocsView =
16
18
  | { readonly kind: "edit" }
17
19
  | { readonly kind: "search"; readonly q: string };
18
20
 
21
+ /**
22
+ * What the board is narrowed to (ADR-0019), from the query: a category by its key (`""` for
23
+ * the tasks with none), a goal by its id (`""` for the tasks with none), a person by their id,
24
+ * or the person's own.
25
+ */
26
+ export interface BoardFilter {
27
+ readonly category?: string | undefined;
28
+ readonly goal?: string | undefined;
29
+ readonly person?: string | undefined;
30
+ readonly mine?: boolean | undefined;
31
+ }
32
+
19
33
  export type Route =
20
34
  | { readonly name: "projects" }
21
35
  | { readonly name: "new-project" }
22
- | { readonly name: "board"; readonly project: string }
36
+ | { readonly name: "board"; readonly project: string; readonly filter: BoardFilter }
23
37
  | { readonly name: "new-task"; readonly project: string }
24
38
  | {
25
39
  readonly name: "task";
@@ -38,6 +52,16 @@ export type Route =
38
52
  | { readonly name: "decisions"; readonly project: string }
39
53
  | { readonly name: "new-decision"; readonly project: string }
40
54
  | { readonly name: "decision"; readonly project: string; readonly id: string }
55
+ /** The calendar of a project: the month shown, or this one when the address names none. */
56
+ | { readonly name: "calendar"; readonly project: string; readonly month: string | undefined }
57
+ | { readonly name: "roadmap"; readonly project: string }
58
+ | { readonly name: "new-goal"; readonly project: string }
59
+ | {
60
+ readonly name: "goal";
61
+ readonly project: string;
62
+ readonly id: string;
63
+ readonly form: "edit" | undefined;
64
+ }
41
65
  | { readonly name: "feed"; readonly project: string }
42
66
  | { readonly name: "skills"; readonly project: string }
43
67
  | { readonly name: "members"; readonly project: string }
@@ -47,6 +71,10 @@ export type Route =
47
71
  | { readonly name: "person-agents"; readonly id: string }
48
72
  | { readonly name: "agents" }
49
73
  | { readonly name: "new-token" }
74
+ /** What is on the person's plate across their projects. */
75
+ | { readonly name: "work" }
76
+ /** The categories tasks are sorted into; an administrator keeps them here. */
77
+ | { readonly name: "categories" }
50
78
  /** Where a person approves an agent's connection, by the code it showed, or types one. */
51
79
  | { readonly name: "connect"; readonly code: string | undefined }
52
80
  /** An address that moved: the page takes the person to `to` in its place. */
@@ -54,7 +82,15 @@ export type Route =
54
82
  | { readonly name: "not-found" };
55
83
 
56
84
  /** The pages of a project, after its key. */
57
- export type ProjectPage = "docs" | "decisions" | "feed" | "skills" | "members" | "settings";
85
+ export type ProjectPage =
86
+ | "docs"
87
+ | "decisions"
88
+ | "calendar"
89
+ | "roadmap"
90
+ | "feed"
91
+ | "skills"
92
+ | "members"
93
+ | "settings";
58
94
 
59
95
  export const PATHS = {
60
96
  /** The projects the person may see: the front page. */
@@ -62,11 +98,15 @@ export const PATHS = {
62
98
  /** Where a project's pages are: `/projects/<key>`, then the page. */
63
99
  project: "/projects",
64
100
  newProject: "/projects/new",
101
+ /** What is on the person's plate, across the projects they may see. */
102
+ work: "/work",
65
103
  people: "/people",
66
104
  /** The agents of whoever is signed in, which hold their tokens: their own page. */
67
105
  agents: "/agents",
68
106
  /** The form for a token made by hand, for a script or a console of one's own. */
69
107
  newToken: "/agents/new",
108
+ /** The categories tasks are sorted into, kept by an administrator. */
109
+ categories: "/categories",
70
110
  /** Where an agent's connection is approved (ADR-0011): `/connect/<code>`, or the code typed. */
71
111
  connect: "/connect",
72
112
  } as const;
@@ -77,7 +117,7 @@ const NUMBER = /^[1-9][0-9]{0,8}$/;
77
117
  /** A code as it may be typed into an address: checked for what it is by the page. */
78
118
  const CODE_TEXT = /^[A-Za-z0-9-]{1,16}$/;
79
119
  /** The pages of a project that take nothing after their name. */
80
- const PLAIN_PAGES: readonly Exclude<ProjectPage, "docs">[] = [
120
+ const PLAIN_PAGES: readonly Exclude<ProjectPage, "docs" | "calendar" | "roadmap">[] = [
81
121
  "decisions",
82
122
  "feed",
83
123
  "skills",
@@ -103,6 +143,19 @@ export function movedFrom(pathname: string): string | undefined {
103
143
  const taskFormOf = (params: URLSearchParams): TaskFormKind | undefined =>
104
144
  params.has("edit") ? "edit" : params.has("ask") ? "ask" : undefined;
105
145
 
146
+ /** The board's filters, as the query names them; a value that is not one is left out. */
147
+ const boardFilterOf = (params: URLSearchParams): BoardFilter => {
148
+ const category = params.get("category");
149
+ const goal = params.get("goal");
150
+ const person = params.get("person");
151
+ return {
152
+ ...(category !== null && (category === "" || PROJECT_KEY.test(category)) ? { category } : {}),
153
+ ...(goal !== null && (goal === "" || UUID.test(goal)) ? { goal } : {}),
154
+ ...(person !== null && UUID.test(person) ? { person } : {}),
155
+ ...(params.has("mine") ? { mine: true } : {}),
156
+ };
157
+ };
158
+
106
159
  /** The view the query asks for at a path, or nothing for one the path cannot be shown as. */
107
160
  const docsViewOf = (params: URLSearchParams, path: string): DocsView | undefined => {
108
161
  const q = params.get("q");
@@ -137,6 +190,9 @@ export function matchRoute(pathname: string, search = ""): Route {
137
190
  if (path === PATHS.newProject) {
138
191
  return { name: "new-project" };
139
192
  }
193
+ if (path === PATHS.work) {
194
+ return { name: "work" };
195
+ }
140
196
  if (path === PATHS.people) {
141
197
  return { name: "people" };
142
198
  }
@@ -146,6 +202,9 @@ export function matchRoute(pathname: string, search = ""): Route {
146
202
  if (path === PATHS.newToken) {
147
203
  return { name: "new-token" };
148
204
  }
205
+ if (path === PATHS.categories) {
206
+ return { name: "categories" };
207
+ }
149
208
  if (path === PATHS.connect) {
150
209
  return { name: "connect", code: undefined };
151
210
  }
@@ -167,7 +226,7 @@ export function matchRoute(pathname: string, search = ""): Route {
167
226
  return NOT_FOUND;
168
227
  }
169
228
  if (page === undefined) {
170
- return { name: "board", project };
229
+ return { name: "board", project, filter: boardFilterOf(params) };
171
230
  }
172
231
  if (page === "docs") {
173
232
  // The rest of the path is the document's, or a folder's, or nothing for the root.
@@ -205,9 +264,27 @@ export function matchRoute(pathname: string, search = ""): Route {
205
264
  }
206
265
  return UUID.test(third) ? { name: "decision", project, id: third } : NOT_FOUND;
207
266
  }
267
+ if (page === "roadmap" && third !== undefined) {
268
+ if (third === "new") {
269
+ return { name: "new-goal", project };
270
+ }
271
+ return UUID.test(third)
272
+ ? { name: "goal", project, id: third, form: params.has("edit") ? "edit" : undefined }
273
+ : NOT_FOUND;
274
+ }
208
275
  if (third !== undefined) {
209
276
  return NOT_FOUND;
210
277
  }
278
+ if (page === "calendar") {
279
+ const month = params.get("month");
280
+ if (month !== null && !isMonth(month)) {
281
+ return NOT_FOUND;
282
+ }
283
+ return { name: "calendar", project, month: month ?? undefined };
284
+ }
285
+ if (page === "roadmap") {
286
+ return { name: "roadmap", project };
287
+ }
211
288
  const found = PLAIN_PAGES.find((candidate) => candidate === page);
212
289
  return found === undefined ? NOT_FOUND : { name: found, project };
213
290
  }
@@ -228,6 +305,25 @@ export function projectHref(key: string, page?: ProjectPage): string {
228
305
  return page === undefined ? base : `${base}/${page}`;
229
306
  }
230
307
 
308
+ /** Where a project's board is, narrowed as the filter says; the plain board for an empty one. */
309
+ export function boardHref(key: string, filter: BoardFilter = {}): string {
310
+ const parts: string[] = [];
311
+ // An empty value means "none": it keeps its sign; "mine" is a flag and has no value.
312
+ if (filter.category !== undefined) {
313
+ parts.push(`category=${encodeURIComponent(filter.category)}`);
314
+ }
315
+ if (filter.goal !== undefined) {
316
+ parts.push(`goal=${encodeURIComponent(filter.goal)}`);
317
+ }
318
+ if (filter.person !== undefined) {
319
+ parts.push(`person=${encodeURIComponent(filter.person)}`);
320
+ }
321
+ if (filter.mine === true) {
322
+ parts.push("mine");
323
+ }
324
+ return parts.length === 0 ? projectHref(key) : `${projectHref(key)}?${parts.join("&")}`;
325
+ }
326
+
231
327
  /** Where a task is written: the form. */
232
328
  export function newTaskHref(key: string): string {
233
329
  return `${PATHS.project}/${key}/tasks/new`;
@@ -249,6 +345,23 @@ export function decisionHref(key: string, id: string): string {
249
345
  return `${PATHS.project}/${key}/decisions/${id}`;
250
346
  }
251
347
 
348
+ /** Where a project's calendar is, on a month, or on this one when none is named. */
349
+ export function calendarHref(key: string, month?: string): string {
350
+ const base = projectHref(key, "calendar");
351
+ return month === undefined ? base : `${base}?month=${month}`;
352
+ }
353
+
354
+ /** Where a goal is set: the form. */
355
+ export function newGoalHref(key: string): string {
356
+ return `${PATHS.project}/${key}/roadmap/new`;
357
+ }
358
+
359
+ /** Where one goal is read, or changed. */
360
+ export function goalHref(key: string, id: string, form?: "edit"): string {
361
+ const base = `${PATHS.project}/${key}/roadmap/${id}`;
362
+ return form === undefined ? base : `${base}?${form}`;
363
+ }
364
+
252
365
  /**
253
366
  * Where a document of a project is read, or a folder of them listed (`""` for the root), and
254
367
  * how: a form, a version, or a search over all of them.
package/src/routes.ts CHANGED
@@ -17,8 +17,17 @@ export const REST_ROUTES = {
17
17
  tokens: "/api/v1/tokens",
18
18
  /** Connecting an agent (ADR-0011): begun as nobody, approved by a person, claimed by the command. */
19
19
  connect: "/api/v1/connect",
20
+ /** The categories tasks are sorted into (ADR-0019): the workspace's vocabulary, kept by an administrator. */
21
+ categories: "/api/v1/categories",
22
+ /** What is on the asker's plate across the projects they may see: their open tasks, and the decisions waiting. */
23
+ work: "/api/v1/work",
20
24
  } as const;
21
25
 
26
+ /** The REST path of one category, by its key. */
27
+ export function categoryPath(key: string): string {
28
+ return `${REST_ROUTES.categories}/${encodeURIComponent(key)}`;
29
+ }
30
+
22
31
  /** Where the command claims the token of a connection, with its secret. */
23
32
  export const CLAIM_PATH = `${REST_ROUTES.connect}/claim`;
24
33
 
@@ -44,6 +53,7 @@ export const PROJECT_COLLECTIONS = [
44
53
  "skills",
45
54
  "members",
46
55
  "docs",
56
+ "goals",
47
57
  ] as const;
48
58
 
49
59
  export type ProjectCollection = (typeof PROJECT_COLLECTIONS)[number];