@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/data.ts CHANGED
@@ -6,6 +6,9 @@ import {
6
6
  EVENT_KINDS,
7
7
  type FileUpload,
8
8
  type RestAnswerInput,
9
+ type RestCategory,
10
+ type RestCategoryInput,
11
+ type RestCategoryPatch,
9
12
  type RestConnectRequest,
10
13
  type RestDecision,
11
14
  type RestDecisionInput,
@@ -14,6 +17,9 @@ import {
14
17
  type RestDocumentInput,
15
18
  type RestDocuments,
16
19
  type RestEvent,
20
+ type RestGoal,
21
+ type RestGoalInput,
22
+ type RestGoalPatch,
17
23
  type RestMe,
18
24
  type RestMember,
19
25
  type RestMemberInput,
@@ -35,6 +41,7 @@ import {
35
41
  type RestTokens,
36
42
  type RestVersion,
37
43
  type RestVersions,
44
+ type RestWork,
38
45
  restEventSchema,
39
46
  } from "./api.js";
40
47
 
@@ -64,6 +71,15 @@ export interface ConsoleActions {
64
71
  answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
65
72
  /** Grows a decision's record: its context, or what followed. */
66
73
  updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
74
+ /** Sets a goal on the roadmap of the project the page is on; for an owner. */
75
+ createGoal(input: RestGoalInput): Promise<RestGoal>;
76
+ updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
77
+ /** What is on the person's plate across their projects, read when the page asks. */
78
+ work(signal?: AbortSignal): Promise<RestWork>;
79
+ /** The categories tasks are sorted into: kept by an administrator. */
80
+ createCategory(input: RestCategoryInput): Promise<RestCategory>;
81
+ updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
82
+ removeCategory(key: string): Promise<void>;
67
83
  /** A folder's pages and folders, or the pages a search finds, in the project the page is on. */
68
84
  documents(filter?: DocumentFilter, signal?: AbortSignal): Promise<RestDocuments>;
69
85
  document(path: string, signal?: AbortSignal): Promise<RestDocument>;
@@ -104,6 +120,8 @@ export interface ConsoleData {
104
120
  /** The projects the person may see, by name. */
105
121
  readonly projects: readonly RestProject[];
106
122
  readonly people: readonly RestPerson[];
123
+ /** The categories tasks are sorted into, by name. */
124
+ readonly categories: readonly RestCategory[];
107
125
  /** The tokens of whoever is signed in, newest first. */
108
126
  readonly tokens: readonly RestToken[];
109
127
  /** The project the page is on, once loaded; `undefined` while loading, or when the page is on none. */
@@ -116,6 +134,8 @@ export interface ConsoleData {
116
134
  readonly live: boolean;
117
135
  readonly tasks: readonly RestTask[];
118
136
  readonly decisions: readonly RestDecision[];
137
+ /** The roadmap of the project: its goals, the active first. */
138
+ readonly goals: readonly RestGoal[];
119
139
  /** The runs of the project, newest first. */
120
140
  readonly runs: readonly RestRun[];
121
141
  /** The project's events, oldest first. */
@@ -161,6 +181,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
161
181
  const [loaded, setLoaded] = useState(false);
162
182
  const [projects, setProjects] = useState<readonly RestProject[]>([]);
163
183
  const [people, setPeople] = useState<readonly RestPerson[]>([]);
184
+ const [categories, setCategories] = useState<readonly RestCategory[]>([]);
164
185
  const [tokens, setTokens] = useState<readonly RestToken[]>([]);
165
186
  const [project, setProject] = useState<RestProject | undefined>(undefined);
166
187
  const [projectLoaded, setProjectLoaded] = useState(false);
@@ -168,6 +189,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
168
189
  const [live, setLive] = useState(false);
169
190
  const [tasks, setTasks] = useState<readonly RestTask[]>([]);
170
191
  const [decisions, setDecisions] = useState<readonly RestDecision[]>([]);
192
+ const [goals, setGoals] = useState<readonly RestGoal[]>([]);
171
193
  const [runs, setRuns] = useState<readonly RestRun[]>([]);
172
194
  const [events, setEvents] = useState<readonly RestEvent[]>([]);
173
195
  const [members, setMembers] = useState<readonly RestMember[]>([]);
@@ -181,15 +203,17 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
181
203
  /** Loads the workspace's lists again. What fails stays as it was. */
182
204
  const refreshWorkspace = useCallback(
183
205
  async (signal?: AbortSignal) => {
184
- const [nextProjects, nextPeople] = await Promise.all([
206
+ const [nextProjects, nextPeople, nextCategories] = await Promise.all([
185
207
  client.projects(signal),
186
208
  client.people(signal),
209
+ client.categories(signal),
187
210
  ]);
188
211
  if (signal?.aborted === true) {
189
212
  return;
190
213
  }
191
214
  setProjects(nextProjects.items);
192
215
  setPeople(nextPeople.items);
216
+ setCategories(nextCategories.items);
193
217
  },
194
218
  [client],
195
219
  );
@@ -201,19 +225,22 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
201
225
  return;
202
226
  }
203
227
  const scope = client.project(projectKey);
204
- const [nextProject, nextTasks, nextDecisions, nextRuns, nextMembers] = await Promise.all([
205
- scope.get(signal),
206
- scope.tasks(undefined, signal),
207
- scope.decisions(undefined, signal),
208
- scope.runs(undefined, signal),
209
- scope.members(signal),
210
- ]);
228
+ const [nextProject, nextTasks, nextDecisions, nextGoals, nextRuns, nextMembers] =
229
+ await Promise.all([
230
+ scope.get(signal),
231
+ scope.tasks(undefined, signal),
232
+ scope.decisions(undefined, signal),
233
+ scope.goals(signal),
234
+ scope.runs(undefined, signal),
235
+ scope.members(signal),
236
+ ]);
211
237
  if (signal?.aborted === true) {
212
238
  return;
213
239
  }
214
240
  setProject(nextProject);
215
241
  setTasks(nextTasks.items);
216
242
  setDecisions(nextDecisions.items);
243
+ setGoals(nextGoals.items);
217
244
  setRuns(nextRuns.items);
218
245
  setMembers(nextMembers.items);
219
246
  },
@@ -274,6 +301,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
274
301
  setProjectError(undefined);
275
302
  setTasks([]);
276
303
  setDecisions([]);
304
+ setGoals([]);
277
305
  setRuns([]);
278
306
  setEvents([]);
279
307
  setMembers([]);
@@ -411,6 +439,31 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
411
439
  );
412
440
  return decision;
413
441
  },
442
+ async createGoal(input) {
443
+ const goal = await scope().createGoal(input);
444
+ await refreshProject();
445
+ return goal;
446
+ },
447
+ async updateGoal(id, patch) {
448
+ const goal = await scope().updateGoal(id, patch);
449
+ setGoals((current) => current.map((candidate) => (candidate.id === id ? goal : candidate)));
450
+ return goal;
451
+ },
452
+ work: (signal) => client.work(signal),
453
+ async createCategory(input) {
454
+ const category = await client.createCategory(input);
455
+ setCategories((await client.categories()).items);
456
+ return category;
457
+ },
458
+ async updateCategory(key, patch) {
459
+ const category = await client.updateCategory(key, patch);
460
+ setCategories((await client.categories()).items);
461
+ return category;
462
+ },
463
+ async removeCategory(key) {
464
+ await client.removeCategory(key);
465
+ setCategories((current) => current.filter((category) => category.key !== key));
466
+ },
414
467
  documents: (filter, signal) => scope().documents(filter, signal),
415
468
  document: (path, signal) => scope().document(path, signal),
416
469
  writeDocument: (path, input) => scope().writeDocument(path, input),
@@ -476,6 +529,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
476
529
  loaded,
477
530
  projects,
478
531
  people,
532
+ categories,
479
533
  tokens,
480
534
  project,
481
535
  projectLoaded,
@@ -483,6 +537,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
483
537
  live,
484
538
  tasks,
485
539
  decisions,
540
+ goals,
486
541
  runs,
487
542
  events,
488
543
  members,
package/src/dates.ts ADDED
@@ -0,0 +1,143 @@
1
+ // Days, as the plan is made in (ADR-0019): `2026-10-20`, read and written as the strings they
2
+ // are, so that a deadline is the same day wherever the people are. The browser's own clock says
3
+ // which day is today; nothing here looks at it unless asked.
4
+
5
+ const DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
6
+
7
+ /** Whether a string is a day as the API spells one. */
8
+ export function isDay(value: string | null | undefined): value is string {
9
+ return value !== null && value !== undefined && DAY.test(value);
10
+ }
11
+
12
+ /** A day from a local date: the date the browser's clock shows, not the UTC one. */
13
+ export function dayOf(date: Date): string {
14
+ const year = String(date.getFullYear()).padStart(4, "0");
15
+ const month = String(date.getMonth() + 1).padStart(2, "0");
16
+ const day = String(date.getDate()).padStart(2, "0");
17
+ return `${year}-${month}-${day}`;
18
+ }
19
+
20
+ /** Today, by the browser's clock. */
21
+ export function today(now: Date = new Date()): string {
22
+ return dayOf(now);
23
+ }
24
+
25
+ /** A local date at noon of the day, so that a daylight-saving change never moves it a day. */
26
+ export function dateOf(day: string): Date {
27
+ const match = DAY.exec(day);
28
+ if (match === null) {
29
+ return new Date(Number.NaN);
30
+ }
31
+ return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), 12);
32
+ }
33
+
34
+ /** The day `count` days after `day`; negative counts go back. */
35
+ export function addDays(day: string, count: number): string {
36
+ const date = dateOf(day);
37
+ date.setDate(date.getDate() + count);
38
+ return dayOf(date);
39
+ }
40
+
41
+ /** How many days `to` is after `from`; negative when it is before. */
42
+ export function daysBetween(from: string, to: string): number {
43
+ const millis = dateOf(to).getTime() - dateOf(from).getTime();
44
+ return Math.round(millis / 86_400_000);
45
+ }
46
+
47
+ /** The month a day is in: `2026-10`. */
48
+ export function monthOf(day: string): string {
49
+ return day.slice(0, 7);
50
+ }
51
+
52
+ /** Whether a string is a month as the calendar's address spells one. */
53
+ export function isMonth(value: string | null | undefined): value is string {
54
+ return value !== null && value !== undefined && /^\d{4}-(0[1-9]|1[0-2])$/.test(value);
55
+ }
56
+
57
+ /** The month `count` months after `month`; negative counts go back. */
58
+ export function addMonths(month: string, count: number): string {
59
+ const year = Number(month.slice(0, 4));
60
+ const index = Number(month.slice(5, 7)) - 1 + count;
61
+ const date = new Date(year, index, 1, 12);
62
+ return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}`;
63
+ }
64
+
65
+ /** The weekday a week begins on where the language is spoken: 1 for Monday, 7 for Sunday. */
66
+ export function firstDayOfWeek(language: string): number {
67
+ try {
68
+ const locale = new Intl.Locale(language) as Intl.Locale & {
69
+ readonly weekInfo?: { readonly firstDay: number };
70
+ readonly getWeekInfo?: () => { readonly firstDay: number };
71
+ };
72
+ const info = locale.getWeekInfo?.() ?? locale.weekInfo;
73
+ if (info !== undefined && info.firstDay >= 1 && info.firstDay <= 7) {
74
+ return info.firstDay;
75
+ }
76
+ } catch {
77
+ // A tag the browser does not know: the week begins on Monday.
78
+ }
79
+ return 1;
80
+ }
81
+
82
+ /** One cell of a month's grid: the day, and whether it is of the month shown or a neighbour's. */
83
+ export interface GridDay {
84
+ readonly day: string;
85
+ readonly inMonth: boolean;
86
+ }
87
+
88
+ /**
89
+ * The weeks of a month as a calendar shows them: full weeks, beginning on `firstDay` (1 for
90
+ * Monday, 7 for Sunday), with the neighbouring months' days filling the first and the last.
91
+ */
92
+ export function monthGrid(month: string, firstDay = 1): GridDay[][] {
93
+ const first = `${month}-01`;
94
+ const start = dateOf(first);
95
+ // getDay: 0 for Sunday; the grid counts Monday as 1 and Sunday as 7.
96
+ const weekday = start.getDay() === 0 ? 7 : start.getDay();
97
+ const back = (weekday - firstDay + 7) % 7;
98
+ let cursor = addDays(first, -back);
99
+ const weeks: GridDay[][] = [];
100
+ do {
101
+ const week: GridDay[] = [];
102
+ for (let column = 0; column < 7; column += 1) {
103
+ week.push({ day: cursor, inMonth: monthOf(cursor) === month });
104
+ cursor = addDays(cursor, 1);
105
+ }
106
+ weeks.push(week);
107
+ } while (monthOf(cursor) === month);
108
+ return weeks;
109
+ }
110
+
111
+ /** The weekdays in the order a week is shown, from `firstDay`, as the grid counts them. */
112
+ export function weekdays(firstDay = 1): number[] {
113
+ return Array.from({ length: 7 }, (_, index) => ((firstDay - 1 + index) % 7) + 1);
114
+ }
115
+
116
+ /** A day as people read one in a language: the month and the day, with the year when it is not this year's. */
117
+ export function formatDay(day: string, language?: string, now: Date = new Date()): string {
118
+ const date = dateOf(day);
119
+ if (Number.isNaN(date.getTime())) {
120
+ return day;
121
+ }
122
+ return date.toLocaleDateString(language, {
123
+ month: "short",
124
+ day: "numeric",
125
+ ...(date.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }),
126
+ });
127
+ }
128
+
129
+ /** A month as people read one: the month and the year. */
130
+ export function formatMonth(month: string, language?: string): string {
131
+ const date = dateOf(`${month}-01`);
132
+ if (Number.isNaN(date.getTime())) {
133
+ return month;
134
+ }
135
+ return date.toLocaleDateString(language, { month: "long", year: "numeric" });
136
+ }
137
+
138
+ /** A weekday as people read one, short, as the grid counts them: 1 for Monday, 7 for Sunday. */
139
+ export function formatWeekday(weekday: number, language?: string): string {
140
+ // The week of 2024-01-01 began on a Monday.
141
+ const date = new Date(2024, 0, weekday, 12);
142
+ return date.toLocaleDateString(language, { weekday: "short" });
143
+ }
package/src/i18n/en.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { SignInFailure } from "../routes.js";
2
2
  import type {
3
+ CategoryColor,
4
+ GoalState,
3
5
  PersonRole,
4
6
  ProjectRole,
5
7
  ProjectVisibility,
@@ -47,6 +49,22 @@ export const en = {
47
49
  failed: "Failed",
48
50
  abandoned: "Abandoned",
49
51
  } satisfies Record<RunStatus, string>,
52
+ goalState: {
53
+ planned: "Planned",
54
+ active: "Active",
55
+ done: "Reached",
56
+ dropped: "Dropped",
57
+ } satisfies Record<GoalState, string>,
58
+ categoryColor: {
59
+ gray: "Gray",
60
+ blue: "Blue",
61
+ green: "Green",
62
+ orange: "Orange",
63
+ pink: "Pink",
64
+ purple: "Purple",
65
+ teal: "Teal",
66
+ yellow: "Yellow",
67
+ } satisfies Record<CategoryColor, string>,
50
68
  },
51
69
 
52
70
  common: {
@@ -81,9 +99,13 @@ export const en = {
81
99
 
82
100
  nav: {
83
101
  projects: "Projects",
102
+ work: "My work",
84
103
  people: "People",
85
104
  agents: "Agents",
105
+ categories: "Categories",
86
106
  board: "Board",
107
+ roadmap: "Roadmap",
108
+ calendar: "Calendar",
87
109
  docs: "Docs",
88
110
  decisions: "Decisions",
89
111
  feed: "Feed",
@@ -187,7 +209,25 @@ export const en = {
187
209
  decisionsTitle: "Decisions waiting for a person",
188
210
  agentAtWork: "agent at work",
189
211
  agentAtWorkTitle: "An agent is at work on it",
190
- links: (n: number) => count(n, "link", "links"),
212
+ agentSince: (agent: string, since: string) => `${agent}, at work since ${since}`,
213
+ filters: {
214
+ aria: "Narrow the board",
215
+ everyone: "Everyone",
216
+ mine: "Mine",
217
+ anyCategory: "Any category",
218
+ noCategory: "No category",
219
+ anyGoal: "Any goal",
220
+ noGoal: "No goal",
221
+ clear: "Show everything",
222
+ showing: (n: number, of: number) => `${n} of ${count(of, "task", "tasks")}`,
223
+ },
224
+ due: {
225
+ overdue: (days: number) => `${count(days, "day", "days")} overdue`,
226
+ today: "Due today",
227
+ tomorrow: "Due tomorrow",
228
+ inDays: (days: number) => `Due in ${count(days, "day", "days")}`,
229
+ on: (day: string) => `Due ${day}`,
230
+ },
191
231
  },
192
232
 
193
233
  task: {
@@ -213,6 +253,19 @@ export const en = {
213
253
  history: "What happened",
214
254
  subtasks: "Subtasks",
215
255
  partOfIt: "Part of it",
256
+ category: "Category",
257
+ goal: "Goal",
258
+ plan: "Plan",
259
+ planned: (from: string | undefined, to: string | undefined) =>
260
+ from !== undefined && to !== undefined
261
+ ? `${from} to ${to}`
262
+ : from !== undefined
263
+ ? `from ${from}`
264
+ : to !== undefined
265
+ ? `by ${to}`
266
+ : "",
267
+ started: "Started",
268
+ done: "Done",
216
269
  form: {
217
270
  title: "Title",
218
271
  body: "Body",
@@ -221,6 +274,12 @@ export const en = {
221
274
  priority: "Priority",
222
275
  assignee: "Assignee",
223
276
  partOf: "Part of",
277
+ category: "Category",
278
+ noCategory: "No category",
279
+ goal: "Goal",
280
+ noGoal: "No goal",
281
+ startsOn: "Starts on",
282
+ dueOn: "Due on",
224
283
  links: "Links",
225
284
  label: "Label",
226
285
  addLink: "Add a link",
@@ -244,6 +303,10 @@ export const en = {
244
303
  },
245
304
  waiting: "Waiting",
246
305
  answered: "Answered",
306
+ record: "The record",
307
+ recordLead:
308
+ "Every decision answered, newest first: what was asked, what was chosen, by whom. Open one for its context, its rationale and what followed.",
309
+ openRecord: "Open",
247
310
  waitingForPerson: "Waiting for a person.",
248
311
  options: "Options",
249
312
  notePlaceholder: "A word about it, if any",
@@ -271,6 +334,112 @@ export const en = {
271
334
  },
272
335
  },
273
336
 
337
+ calendar: {
338
+ title: "Calendar",
339
+ lead: "The tasks by the day they are due. A task with a start is shown from it; one with no day waits at the bottom to be given one.",
340
+ grid: "The month",
341
+ previous: "The month before",
342
+ next: "The month after",
343
+ thisMonth: "This month",
344
+ nothingDue: "Nothing is due this month.",
345
+ overdue: "Overdue",
346
+ nothingOverdue: "Nothing is overdue.",
347
+ unscheduled: "No day set",
348
+ allScheduled: "Every open task has a day.",
349
+ starts: "starts",
350
+ workload: {
351
+ title: "Who has what",
352
+ lead: "The open tasks of each person, and what of them is late or due within the week.",
353
+ person: "Person",
354
+ open: "Open",
355
+ inProgress: "In progress",
356
+ dueSoon: "Due in 7 days",
357
+ overdue: "Overdue",
358
+ unassigned: "Nobody's yet",
359
+ },
360
+ },
361
+
362
+ roadmap: {
363
+ title: "Roadmap",
364
+ lead: "What the project works towards: each goal with the day it is meant to be reached by, and how far its tasks are. Progress is read from the board as it stands.",
365
+ setGoal: "Set a goal",
366
+ setGoalAria: "Set a goal",
367
+ editAria: "Change the goal",
368
+ one: "Goal",
369
+ all: "All goals",
370
+ empty: {
371
+ title: "No goal is set yet",
372
+ body: "An owner sets the first one: what to reach, and by when. Tasks are then written towards it.",
373
+ },
374
+ noSuch: {
375
+ title: "No such goal",
376
+ body: "It may belong to another project, or the link is wrong.",
377
+ },
378
+ onlyOwner: {
379
+ title: "Only an owner sets the roadmap",
380
+ body: "Ask one of the project's owners, or a workspace administrator.",
381
+ },
382
+ progress: (done: number, of: number) => `${done} of ${count(of, "task", "tasks")} done`,
383
+ inProgress: (n: number) => `${n} in progress`,
384
+ noTasks: "No task works towards it yet.",
385
+ tasks: "Tasks",
386
+ target: "Target",
387
+ noTarget: "No target day",
388
+ setBy: "Set by",
389
+ state: "State",
390
+ nothingMore: "Nothing more was written about it.",
391
+ form: {
392
+ title: "Title",
393
+ body: "What reaching it means",
394
+ bodyPlaceholder: "What is true once the goal is reached, in Markdown.",
395
+ state: "State",
396
+ dueOn: "Target day",
397
+ set: "Set the goal",
398
+ },
399
+ },
400
+
401
+ work: {
402
+ title: "My work",
403
+ lead: "What is on your plate across your projects: the open tasks assigned to you, by the day they are due, and the decisions that wait for a person.",
404
+ loading: "Loading your work",
405
+ couldNotLoad: "Your work could not be loaded.",
406
+ empty: {
407
+ title: "Nothing is assigned to you",
408
+ body: "A task assigned to you in any project you may see shows up here.",
409
+ },
410
+ overdue: "Overdue",
411
+ thisWeek: "Due this week",
412
+ later: "Later",
413
+ noDay: "No day set",
414
+ decisions: "Decisions waiting for a person",
415
+ noDecisions: "No decision waits.",
416
+ inProject: (project: string) => `in ${project}`,
417
+ },
418
+
419
+ categories: {
420
+ title: "Categories",
421
+ lead: "The kinds of work tasks are sorted into, the same in every project. A task carries one, the board is narrowed by one, and an administrator keeps the list.",
422
+ onlyAdmin: "An administrator adds, changes and removes them; everyone else reads them here.",
423
+ list: "Categories",
424
+ add: "Add a category",
425
+ addAria: "Add a category",
426
+ empty: {
427
+ title: "No categories",
428
+ body: "An administrator adds the first one here.",
429
+ },
430
+ inUse: "A category tasks are sorted into cannot be removed until they are moved out of it.",
431
+ form: {
432
+ name: "Name",
433
+ key: "Key",
434
+ keyHint:
435
+ "Lowercase letters, digits and hyphens: what the command and the filters say. It does not change.",
436
+ color: "Colour",
437
+ add: "Add",
438
+ },
439
+ nameOf: (key: string) => `Name of ${key}`,
440
+ colorOf: (key: string) => `Colour of ${key}`,
441
+ },
442
+
274
443
  feed: {
275
444
  title: "What happened",
276
445
  list: "What happened",
@@ -284,6 +453,8 @@ export const en = {
284
453
  something: "something",
285
454
  aQuestion: "a question",
286
455
  aFile: "a file",
456
+ aGoal: "a goal",
457
+ aCategory: "a category",
287
458
  thePage: (name: string) => `the page ${name}`.trimEnd(),
288
459
  role: {
289
460
  admin: "an administrator",
@@ -303,6 +474,12 @@ export const en = {
303
474
  visibility: "who is a member",
304
475
  skillsAddress: "the skills address",
305
476
  outcome: "what followed",
477
+ category: "the category",
478
+ goalId: "the goal",
479
+ startsOn: "the start",
480
+ dueOn: "the due date",
481
+ state: "the state",
482
+ color: "the colour",
306
483
  } as Readonly<Record<string, string>>,
307
484
  event: {
308
485
  personJoined: () => "joined the board",
@@ -315,6 +492,12 @@ export const en = {
315
492
  memberChanged: (login: string, role: string, project: string) =>
316
493
  `made ${login} ${role} of ${project}`,
317
494
  memberRemoved: (login: string, project: string) => `removed ${login} from ${project}`,
495
+ categoryCreated: (category: string) => `made the category ${category}`,
496
+ categoryUpdated: (fields: string, category: string) =>
497
+ `changed ${fields} of the category ${category}`,
498
+ categoryRemoved: (category: string) => `removed the category ${category}`,
499
+ goalCreated: (goal: string) => `set the goal ${goal}`,
500
+ goalUpdated: (fields: string, goal: string) => `changed ${fields} of the goal ${goal}`,
318
501
  taskCreated: (task: string) => `wrote ${task}`,
319
502
  taskMoved: (task: string, from: string, to: string) => `moved ${task} from ${from} to ${to}`,
320
503
  taskUpdated: (fields: string, task: string) => `changed ${fields} of ${task}`,