@skillcdn/console 0.3.2 → 0.4.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 (146) hide show
  1. package/README.md +3 -3
  2. package/dist/api.d.ts +1 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +96 -7
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts.map +1 -1
  10. package/dist/cli/format.js +6 -4
  11. package/dist/cli/format.js.map +1 -1
  12. package/dist/cli/help.d.ts.map +1 -1
  13. package/dist/cli/help.js +48 -17
  14. package/dist/cli/help.js.map +1 -1
  15. package/dist/cli/pack.d.ts +7 -2
  16. package/dist/cli/pack.d.ts.map +1 -1
  17. package/dist/cli/pack.js +16 -2
  18. package/dist/cli/pack.js.map +1 -1
  19. package/dist/client.d.ts +7 -2
  20. package/dist/client.d.ts.map +1 -1
  21. package/dist/client.js +7 -1
  22. package/dist/client.js.map +1 -1
  23. package/dist/compat.d.ts +1 -1
  24. package/dist/compat.js +2 -2
  25. package/dist/components/board.d.ts +12 -1
  26. package/dist/components/board.d.ts.map +1 -1
  27. package/dist/components/board.js +52 -4
  28. package/dist/components/board.js.map +1 -1
  29. package/dist/components/calendar.d.ts +4 -0
  30. package/dist/components/calendar.d.ts.map +1 -1
  31. package/dist/components/calendar.js +61 -22
  32. package/dist/components/calendar.js.map +1 -1
  33. package/dist/components/decision-form.d.ts +12 -4
  34. package/dist/components/decision-form.d.ts.map +1 -1
  35. package/dist/components/decision-form.js +24 -6
  36. package/dist/components/decision-form.js.map +1 -1
  37. package/dist/components/decision-list.d.ts +4 -0
  38. package/dist/components/decision-list.d.ts.map +1 -1
  39. package/dist/components/decision-list.js +9 -6
  40. package/dist/components/decision-list.js.map +1 -1
  41. package/dist/components/documents.d.ts.map +1 -1
  42. package/dist/components/documents.js +3 -3
  43. package/dist/components/documents.js.map +1 -1
  44. package/dist/components/event-feed.d.ts.map +1 -1
  45. package/dist/components/event-feed.js +63 -8
  46. package/dist/components/event-feed.js.map +1 -1
  47. package/dist/components/my-decisions.d.ts +20 -0
  48. package/dist/components/my-decisions.d.ts.map +1 -0
  49. package/dist/components/my-decisions.js +36 -0
  50. package/dist/components/my-decisions.js.map +1 -0
  51. package/dist/components/people.d.ts.map +1 -1
  52. package/dist/components/people.js +1 -1
  53. package/dist/components/people.js.map +1 -1
  54. package/dist/components/projects.d.ts.map +1 -1
  55. package/dist/components/projects.js +3 -3
  56. package/dist/components/projects.js.map +1 -1
  57. package/dist/components/roadmap.js +1 -1
  58. package/dist/components/roadmap.js.map +1 -1
  59. package/dist/components/runs.d.ts.map +1 -1
  60. package/dist/components/runs.js +1 -1
  61. package/dist/components/runs.js.map +1 -1
  62. package/dist/components/shell.d.ts.map +1 -1
  63. package/dist/components/shell.js +36 -1
  64. package/dist/components/shell.js.map +1 -1
  65. package/dist/components/task-list.js +1 -1
  66. package/dist/components/task-list.js.map +1 -1
  67. package/dist/components/task-view.d.ts.map +1 -1
  68. package/dist/components/task-view.js +2 -2
  69. package/dist/components/task-view.js.map +1 -1
  70. package/dist/components/ui.d.ts +10 -0
  71. package/dist/components/ui.d.ts.map +1 -1
  72. package/dist/components/ui.js +15 -11
  73. package/dist/components/ui.js.map +1 -1
  74. package/dist/console.css +1642 -433
  75. package/dist/console.d.ts.map +1 -1
  76. package/dist/console.js +70 -7
  77. package/dist/console.js.map +1 -1
  78. package/dist/data.d.ts +11 -1
  79. package/dist/data.d.ts.map +1 -1
  80. package/dist/data.js +2 -0
  81. package/dist/data.js.map +1 -1
  82. package/dist/i18n/en.d.ts +34 -0
  83. package/dist/i18n/en.d.ts.map +1 -1
  84. package/dist/i18n/en.js +36 -2
  85. package/dist/i18n/en.js.map +1 -1
  86. package/dist/i18n/ko.d.ts.map +1 -1
  87. package/dist/i18n/ko.js +37 -3
  88. package/dist/i18n/ko.js.map +1 -1
  89. package/dist/index.d.ts +3 -2
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +3 -2
  92. package/dist/index.js.map +1 -1
  93. package/dist/router.d.ts +9 -1
  94. package/dist/router.d.ts.map +1 -1
  95. package/dist/router.js +12 -0
  96. package/dist/router.js.map +1 -1
  97. package/dist/routes.d.ts +3 -1
  98. package/dist/routes.d.ts.map +1 -1
  99. package/dist/routes.js +3 -1
  100. package/dist/routes.js.map +1 -1
  101. package/dist/rules.js +1 -1
  102. package/dist/rules.js.map +1 -1
  103. package/dist/schemas.d.ts +242 -3
  104. package/dist/schemas.d.ts.map +1 -1
  105. package/dist/schemas.js +18 -4
  106. package/dist/schemas.js.map +1 -1
  107. package/docs/specs/cli.md +6 -5
  108. package/docs/specs/rest.md +11 -6
  109. package/package.json +2 -2
  110. package/skills/building-a-console/SKILL.md +3 -3
  111. package/skills/building-a-console/references/the-default-console.md +4 -4
  112. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  113. package/skills/working-the-board/SKILL.md +6 -4
  114. package/skills/working-the-board/references/terms.md +1 -1
  115. package/src/api.ts +2 -0
  116. package/src/cli/cli.ts +123 -7
  117. package/src/cli/format.ts +11 -4
  118. package/src/cli/help.ts +49 -16
  119. package/src/cli/pack.ts +17 -1
  120. package/src/client.ts +21 -1
  121. package/src/compat.ts +2 -2
  122. package/src/components/board.tsx +114 -21
  123. package/src/components/calendar.tsx +207 -102
  124. package/src/components/decision-form.tsx +71 -23
  125. package/src/components/decision-list.tsx +30 -3
  126. package/src/components/documents.tsx +27 -11
  127. package/src/components/event-feed.tsx +116 -33
  128. package/src/components/my-decisions.tsx +138 -0
  129. package/src/components/people.tsx +3 -1
  130. package/src/components/projects.tsx +7 -5
  131. package/src/components/roadmap.tsx +1 -1
  132. package/src/components/runs.tsx +5 -1
  133. package/src/components/shell.tsx +36 -1
  134. package/src/components/task-list.tsx +1 -1
  135. package/src/components/task-view.tsx +48 -47
  136. package/src/components/ui.tsx +64 -11
  137. package/src/console.tsx +129 -27
  138. package/src/data.ts +17 -0
  139. package/src/i18n/en.ts +38 -2
  140. package/src/i18n/ko.ts +39 -3
  141. package/src/index.ts +3 -0
  142. package/src/router.ts +18 -1
  143. package/src/routes.ts +3 -1
  144. package/src/rules.ts +1 -1
  145. package/src/schemas.ts +20 -4
  146. package/src/styles/console.css +1642 -433
@@ -43,11 +43,23 @@ export function Button(
43
43
 
44
44
  export function Badge(props: {
45
45
  readonly tone?: "neutral" | "accent" | "point" | "success" | "warning" | "danger";
46
+ /** What the badge says, which the stylesheet shows in its shape: a state carries a dot, a priority is outlined, a due day is a square-cornered tag, a count is figures. */
47
+ readonly kind?: "state" | "priority" | "due" | "count" | undefined;
48
+ /** A class of the badge's own beside the tone's and the kind's, for a step within a kind. */
49
+ readonly className?: string | undefined;
46
50
  readonly title?: string;
47
51
  readonly children: ReactNode;
48
52
  }) {
49
53
  return (
50
- <span className={cx("sc-badge", `sc-badge-${props.tone ?? "neutral"}`)} title={props.title}>
54
+ <span
55
+ className={cx(
56
+ "sc-badge",
57
+ `sc-badge-${props.tone ?? "neutral"}`,
58
+ props.kind !== undefined && `sc-badge-kind-${props.kind}`,
59
+ props.className,
60
+ )}
61
+ title={props.title}
62
+ >
51
63
  {props.children}
52
64
  </span>
53
65
  );
@@ -195,7 +207,7 @@ export const ROLE_LABELS: Readonly<Record<PersonRole, string>> = {
195
207
  export function RoleBadge(props: { readonly role: PersonRole }) {
196
208
  const words = useWords();
197
209
  return (
198
- <Badge tone={props.role === "admin" ? "point" : "neutral"}>
210
+ <Badge tone={props.role === "admin" ? "accent" : "neutral"}>
199
211
  {words.vocabulary.role[props.role]}
200
212
  </Badge>
201
213
  );
@@ -211,7 +223,11 @@ export function StateBadge(props: { readonly state: TaskState }) {
211
223
  : props.state === "in_progress" || props.state === "in_review"
212
224
  ? "accent"
213
225
  : "neutral";
214
- return <Badge tone={tone}>{words.vocabulary.state[props.state]}</Badge>;
226
+ return (
227
+ <Badge tone={tone} kind="state">
228
+ {words.vocabulary.state[props.state]}
229
+ </Badge>
230
+ );
215
231
  }
216
232
 
217
233
  export function PriorityBadge(props: { readonly priority: TaskPriority }) {
@@ -221,7 +237,11 @@ export function PriorityBadge(props: { readonly priority: TaskPriority }) {
221
237
  }
222
238
  const tone =
223
239
  props.priority === "urgent" ? "danger" : props.priority === "high" ? "point" : "neutral";
224
- return <Badge tone={tone}>{words.vocabulary.priority[props.priority]}</Badge>;
240
+ return (
241
+ <Badge tone={tone} kind="priority">
242
+ {words.vocabulary.priority[props.priority]}
243
+ </Badge>
244
+ );
225
245
  }
226
246
 
227
247
  /** A category as a badge, in its colour: a class the stylesheet paints, never a value typed by hand. */
@@ -245,7 +265,11 @@ export function GoalStateBadge(props: { readonly state: GoalState }) {
245
265
  : props.state === "dropped"
246
266
  ? "neutral"
247
267
  : "neutral";
248
- return <Badge tone={tone}>{words.vocabulary.goalState[props.state]}</Badge>;
268
+ return (
269
+ <Badge tone={tone} kind="state">
270
+ {words.vocabulary.goalState[props.state]}
271
+ </Badge>
272
+ );
249
273
  }
250
274
 
251
275
  /**
@@ -266,29 +290,45 @@ export function DueBadge(props: {
266
290
  const days = daysBetween(now, props.dueOn);
267
291
  const day = formatDay(props.dueOn, tag);
268
292
  if (!open) {
269
- return <Badge tone="neutral">{words.board.due.on(day)}</Badge>;
293
+ return (
294
+ <Badge tone="neutral" kind="due">
295
+ {words.board.due.on(day)}
296
+ </Badge>
297
+ );
270
298
  }
271
299
  if (days < 0) {
272
300
  return (
273
- <Badge tone="danger" title={day}>
301
+ <Badge tone="danger" kind="due" title={day}>
274
302
  {words.board.due.overdue(-days)}
275
303
  </Badge>
276
304
  );
277
305
  }
278
306
  if (days === 0) {
279
- return <Badge tone="warning">{words.board.due.today}</Badge>;
307
+ return (
308
+ <Badge tone="point" kind="due" className="sc-badge-due-today">
309
+ {words.board.due.today}
310
+ </Badge>
311
+ );
280
312
  }
281
313
  if (days === 1) {
282
- return <Badge tone="warning">{words.board.due.tomorrow}</Badge>;
314
+ return (
315
+ <Badge tone="point" kind="due">
316
+ {words.board.due.tomorrow}
317
+ </Badge>
318
+ );
283
319
  }
284
320
  if (days <= 7) {
285
321
  return (
286
- <Badge tone="point" title={day}>
322
+ <Badge tone="warning" kind="due" title={day}>
287
323
  {words.board.due.inDays(days)}
288
324
  </Badge>
289
325
  );
290
326
  }
291
- return <Badge tone="neutral">{words.board.due.on(day)}</Badge>;
327
+ return (
328
+ <Badge tone="neutral" kind="due">
329
+ {words.board.due.on(day)}
330
+ </Badge>
331
+ );
292
332
  }
293
333
 
294
334
  /** How far a goal is, read from its tasks: the done ones, the ones at work, and the rest. */
@@ -402,3 +442,16 @@ export function Time(props: {
402
442
  </time>
403
443
  );
404
444
  }
445
+
446
+ /** A link that reads as a button: where a form is, for instance. */
447
+ export function LinkButton(props: {
448
+ readonly href: string;
449
+ readonly variant?: "primary" | "secondary";
450
+ readonly children: ReactNode;
451
+ }) {
452
+ return (
453
+ <a className={`sc-button sc-button-${props.variant ?? "secondary"}`} href={props.href}>
454
+ {props.children}
455
+ </a>
456
+ );
457
+ }
package/src/console.tsx CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  type RestDecisionInput,
23
23
  type RestDecisionPatch,
24
24
  type RestEvent,
25
+ type RestMyDecisions,
25
26
  type RestProject,
26
27
  type RestProjectInput,
27
28
  type RestProjectPatch,
@@ -45,6 +46,7 @@ import { DecisionForm } from "./components/decision-form.js";
45
46
  import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
46
47
  import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
47
48
  import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
49
+ import { MyDecisions } from "./components/my-decisions.js";
48
50
  import { PackTokenWays } from "./components/pack-token.js";
49
51
  import { PAGE_SIZE } from "./components/paged.js";
50
52
  import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
@@ -76,6 +78,7 @@ import {
76
78
  Callout,
77
79
  CategoryBadge,
78
80
  EmptyState,
81
+ LinkButton,
79
82
  personName,
80
83
  Spinner,
81
84
  StateBadge,
@@ -385,19 +388,6 @@ function NotWithToken() {
385
388
  return <EmptyState title={words.title} body={words.body} />;
386
389
  }
387
390
 
388
- /** A link that reads as a button: where a form is, for instance. */
389
- function LinkButton(props: {
390
- readonly href: string;
391
- readonly variant?: "primary" | "secondary";
392
- readonly children: React.ReactNode;
393
- }) {
394
- return (
395
- <a className={`sc-button sc-button-${props.variant ?? "secondary"}`} href={props.href}>
396
- {props.children}
397
- </a>
398
- );
399
- }
400
-
401
391
  function ProjectsPage(props: {
402
392
  readonly data: ConsoleData;
403
393
  readonly components: ConsoleComponents;
@@ -913,6 +903,19 @@ function ProjectPage(props: {
913
903
  empty={<p className="sc-muted">{calendar.nothingOverdue}</p>}
914
904
  />
915
905
  </section>
906
+ {open.length > 0 && (
907
+ <section className="sc-task-section" aria-label={calendar.workload.title}>
908
+ <h2 className="sc-section-title">{calendar.workload.title}</h2>
909
+ <Workload
910
+ tasks={shown}
911
+ today={now}
912
+ weekStart={weekStartOf(data.me?.workspace)}
913
+ personHref={(person) =>
914
+ boardHref(key, person === undefined ? {} : { person: person.id })
915
+ }
916
+ />
917
+ </section>
918
+ )}
916
919
  <section className="sc-task-section" aria-label={calendar.unscheduled}>
917
920
  <h2 className="sc-section-title">
918
921
  {calendar.unscheduled} <span className="sc-column-count">{unscheduled.length}</span>
@@ -926,19 +929,6 @@ function ProjectPage(props: {
926
929
  />
927
930
  </section>
928
931
  </div>
929
- {open.length > 0 && (
930
- <section className="sc-task-section" aria-label={calendar.workload.title}>
931
- <h2 className="sc-section-title">{calendar.workload.title}</h2>
932
- <Workload
933
- tasks={shown}
934
- today={now}
935
- weekStart={weekStartOf(data.me?.workspace)}
936
- personHref={(person) =>
937
- boardHref(key, person === undefined ? {} : { person: person.id })
938
- }
939
- />
940
- </section>
941
- )}
942
932
  </>
943
933
  );
944
934
  }
@@ -1169,7 +1159,9 @@ function ProjectPage(props: {
1169
1159
  {error !== undefined && <Callout tone="danger">{error}</Callout>}
1170
1160
  <section className="sc-panel sc-panel-form" aria-label={words.decisions.raiseAria}>
1171
1161
  <DecisionForm
1172
- tasks={data.tasks}
1162
+ findTasks={data.actions.findTasks}
1163
+ deciders={data.members.map((member) => member.person)}
1164
+ me={data.me?.person ?? undefined}
1173
1165
  busy={busy}
1174
1166
  onSubmit={(input: RestDecisionInput) =>
1175
1167
  void act(async () => {
@@ -2012,6 +2004,108 @@ function PlatePage(props: {
2012
2004
  }
2013
2005
 
2014
2006
  /** What is on the person's own plate, across their projects. */
2007
+ /** How many of a person's decisions are read at a time. */
2008
+ const MY_DECISIONS_PAGE = 50;
2009
+
2010
+ /** One kind of a person's decisions, read a page at a time, as the page shows it. */
2011
+ interface DecisionsRead {
2012
+ readonly items: RestMyDecisions["items"] | undefined;
2013
+ readonly more: boolean;
2014
+ readonly busy: boolean;
2015
+ readonly problem: string | undefined;
2016
+ readonly readMore: () => void;
2017
+ /** Reads the first page again, after an answer changed what waits. */
2018
+ readonly reload: () => void;
2019
+ }
2020
+
2021
+ /** The decisions addressed to the person, or, with `raised`, those they raised for someone else (ADR-0042). */
2022
+ function useMyDecisions(
2023
+ myDecisions: ConsoleData["actions"]["myDecisions"],
2024
+ raised: boolean,
2025
+ couldNotLoad: string,
2026
+ ): DecisionsRead {
2027
+ const [items, setItems] = useState<RestMyDecisions["items"] | undefined>(undefined);
2028
+ const [more, setMore] = useState(false);
2029
+ const [busy, setBusy] = useState(false);
2030
+ const [problem, setProblem] = useState<string | undefined>(undefined);
2031
+ const read = useCallback(
2032
+ async (offset: number, signal?: AbortSignal) => {
2033
+ setBusy(true);
2034
+ try {
2035
+ const page = await myDecisions({ raised, limit: MY_DECISIONS_PAGE, offset }, signal);
2036
+ if (signal?.aborted === true) {
2037
+ return;
2038
+ }
2039
+ setItems((before) => (offset === 0 ? page.items : [...(before ?? []), ...page.items]));
2040
+ setMore(page.more);
2041
+ } catch (failure: unknown) {
2042
+ if (signal?.aborted !== true) {
2043
+ setProblem(errorWords(failure, couldNotLoad));
2044
+ }
2045
+ } finally {
2046
+ if (signal?.aborted !== true) {
2047
+ setBusy(false);
2048
+ }
2049
+ }
2050
+ },
2051
+ [myDecisions, raised, couldNotLoad],
2052
+ );
2053
+ useEffect(() => {
2054
+ const controller = new AbortController();
2055
+ setItems(undefined);
2056
+ setProblem(undefined);
2057
+ void read(0, controller.signal);
2058
+ return () => controller.abort();
2059
+ }, [read]);
2060
+ const readMore = useCallback(() => void read(items?.length ?? 0), [read, items]);
2061
+ const reload = useCallback(() => void read(0), [read]);
2062
+ return { items, more, busy, problem, readMore, reload };
2063
+ }
2064
+
2065
+ /**
2066
+ * A person's decisions (ADR-0042), laid out as a project's decisions page is: those waiting for
2067
+ * their answer, answered here, those they asked of others, and the record of both.
2068
+ */
2069
+ function MyDecisionsPage(props: { readonly data: ConsoleData }) {
2070
+ const words = useWords();
2071
+ const { myDecisions, answerDecisionIn } = props.data.actions;
2072
+ const addressed = useMyDecisions(myDecisions, false, words.myDecisions.couldNotLoad);
2073
+ const raised = useMyDecisions(myDecisions, true, words.myDecisions.couldNotLoad);
2074
+ const { busy, error, act } = useAction();
2075
+ const problem = addressed.problem ?? raised.problem ?? error;
2076
+ const onAnswer = (projectKey: string, decision: RestDecision, input: RestAnswerInput) =>
2077
+ void act(async () => {
2078
+ await answerDecisionIn(projectKey, decision.id, input);
2079
+ addressed.reload();
2080
+ raised.reload();
2081
+ });
2082
+ return (
2083
+ <>
2084
+ <div className="sc-page-head">
2085
+ <h1 className="sc-page-title">{words.myDecisions.title}</h1>
2086
+ </div>
2087
+ {problem !== undefined && <Callout tone="danger">{problem}</Callout>}
2088
+ {addressed.items === undefined || raised.items === undefined ? (
2089
+ problem === undefined && (
2090
+ <div className="sc-loading">
2091
+ <Spinner label={words.myDecisions.loading} />
2092
+ </div>
2093
+ )
2094
+ ) : addressed.items.length === 0 && raised.items.length === 0 ? (
2095
+ <EmptyState title={words.myDecisions.empty.title} body={words.myDecisions.empty.body} />
2096
+ ) : (
2097
+ <MyDecisions
2098
+ addressed={{ items: addressed.items, more: addressed.more, onMore: addressed.readMore }}
2099
+ raised={{ items: raised.items, more: raised.more, onMore: raised.readMore }}
2100
+ decisionHref={decisionHref}
2101
+ onAnswer={onAnswer}
2102
+ busy={busy || addressed.busy || raised.busy}
2103
+ />
2104
+ )}
2105
+ </>
2106
+ );
2107
+ }
2108
+
2015
2109
  function WorkPage(props: {
2016
2110
  readonly data: ConsoleData;
2017
2111
  readonly components: ConsoleComponents;
@@ -2078,6 +2172,9 @@ function WorkspacePage(props: {
2078
2172
  if (route.name === "work") {
2079
2173
  return <WorkPage data={data} components={components} week={route.week} />;
2080
2174
  }
2175
+ if (route.name === "my-decisions") {
2176
+ return <MyDecisionsPage data={data} />;
2177
+ }
2081
2178
  if (route.name === "person-work") {
2082
2179
  return <PersonWorkPage id={route.id} data={data} components={components} week={route.week} />;
2083
2180
  }
@@ -2321,6 +2418,11 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
2321
2418
  current: route.name === "projects" || route.name === "new-project",
2322
2419
  },
2323
2420
  { href: PATHS.work, label: words.nav.work, current: route.name === "work" },
2421
+ {
2422
+ href: PATHS.decisions,
2423
+ label: words.nav.myDecisions,
2424
+ current: route.name === "my-decisions",
2425
+ },
2324
2426
  {
2325
2427
  href: PATHS.people,
2326
2428
  label: words.nav.people,
package/src/data.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  type RestMemberInput,
33
33
  type RestMemberPatch,
34
34
  type RestMePatch,
35
+ type RestMyDecisions,
35
36
  type RestOrganization,
36
37
  type RestPackConnect,
37
38
  type RestPackConnectStarted,
@@ -122,6 +123,8 @@ export interface ConsoleActions {
122
123
  ): Promise<RestGoals>;
123
124
  raiseDecision(input: RestDecisionInput): Promise<RestDecision>;
124
125
  answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
126
+ /** Answers a decision of any project the person may see, by its project's key: for the person's own page of decisions (ADR-0042). */
127
+ answerDecisionIn(projectKey: string, id: string, input: RestAnswerInput): Promise<RestDecision>;
125
128
  /** Grows a decision's record: its context, or what followed. */
126
129
  updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
127
130
  /** Sets a goal on the roadmap of the project the page is on; for an owner. */
@@ -129,6 +132,17 @@ export interface ConsoleActions {
129
132
  updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
130
133
  /** What is on the person's plate across their projects, read when the page asks. */
131
134
  work(signal?: AbortSignal): Promise<RestWork>;
135
+ /** The decisions addressed to the person (ADR-0042) across their projects, a page at a time, read when the page asks. */
136
+ myDecisions(
137
+ filter: {
138
+ readonly open?: boolean;
139
+ /** Those the person raised for someone else, in place of those addressed to them. */
140
+ readonly raised?: boolean;
141
+ readonly limit: number;
142
+ readonly offset: number;
143
+ },
144
+ signal?: AbortSignal,
145
+ ): Promise<RestMyDecisions>;
132
146
  /** The categories tasks are sorted into: kept by an administrator. */
133
147
  createCategory(input: RestCategoryInput): Promise<RestCategory>;
134
148
  updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
@@ -656,6 +670,8 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
656
670
  await refreshProject();
657
671
  return decision;
658
672
  },
673
+ answerDecisionIn: (projectKey, id, input) =>
674
+ client.project(projectKey).answerDecision(id, input),
659
675
  async answerDecision(id, input) {
660
676
  const decision = await scope().answerDecision(id, input);
661
677
  await refreshProject();
@@ -679,6 +695,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
679
695
  return goal;
680
696
  },
681
697
  work: (signal) => client.work(signal),
698
+ myDecisions: (filter, signal) => client.myDecisions(filter, signal),
682
699
  async createCategory(input) {
683
700
  const category = await client.createCategory(input);
684
701
  setCategories((await client.categories()).items);
package/src/i18n/en.ts CHANGED
@@ -131,6 +131,7 @@ export const en = {
131
131
  nav: {
132
132
  projects: "Projects",
133
133
  work: "My work",
134
+ myDecisions: "My decisions",
134
135
  people: "People",
135
136
  agents: "Agents",
136
137
  board: "Board",
@@ -279,8 +280,11 @@ export const en = {
279
280
  menu: "Task menu",
280
281
  decisions: (n: number) => count(n, "decision", "decisions"),
281
282
  decisionsTitle: "Decisions waiting for a person",
283
+ unfold: (column: string) => `Open ${column}`,
284
+ fold: (column: string) => `Fold ${column}`,
282
285
  filters: {
283
286
  aria: "Narrow the board",
287
+ words: "Title, number or person",
284
288
  everyone: "Everyone",
285
289
  mine: "Mine",
286
290
  anyCategory: "Any category",
@@ -380,6 +384,8 @@ export const en = {
380
384
  record: "The record",
381
385
  openRecord: "Open",
382
386
  waitingForPerson: "Waiting for a person.",
387
+ waitingFor: (name: string) => `Waiting for ${name}.`,
388
+ peopleLabel: (asker: string, decider: string) => `asked by ${asker}, for ${decider}`,
383
389
  options: "Options",
384
390
  notePlaceholder: "A word about it, if any",
385
391
  own: "Something else",
@@ -402,6 +408,9 @@ export const en = {
402
408
  contextPlaceholder: "What a person needs to know to answer, in Markdown.",
403
409
  about: "About",
404
410
  noTask: "No task in particular",
411
+ decider: "Who decides",
412
+ deciderPlaceholder: "You, unless you name someone",
413
+ me: "(you)",
405
414
  options: "Options",
406
415
  option: (n: number) => `Option ${n}`,
407
416
  addOption: "Add an option",
@@ -409,6 +418,23 @@ export const en = {
409
418
  },
410
419
  },
411
420
 
421
+ myDecisions: {
422
+ title: "My decisions",
423
+ loading: "Loading your decisions",
424
+ couldNotLoad: "Your decisions could not be loaded.",
425
+ empty: {
426
+ title: "No decision is yours",
427
+ body: "A decision for you, by you or your agent or by someone who named you, and one you asked of someone else, show up here.",
428
+ },
429
+ waiting: "Waiting for you",
430
+ noneWaiting: "Nothing waits for your answer.",
431
+ raised: "Asked of others",
432
+ noneRaised: "You have asked nothing of others.",
433
+ answered: "Answered",
434
+ noneAnswered: "Nothing answered yet.",
435
+ more: "Show more",
436
+ },
437
+
412
438
  calendar: {
413
439
  title: "Calendar",
414
440
  grid: "The month",
@@ -421,6 +447,11 @@ export const en = {
421
447
  nothingOverdue: "Nothing is overdue.",
422
448
  unscheduled: "No day set",
423
449
  allScheduled: "Every open task has a day.",
450
+ more: (n: number) => `${n} more`,
451
+ fewer: "Fewer",
452
+ onDay: (day: string) => `Tasks on ${day}`,
453
+ nothingOnDay: "Nothing is on this day.",
454
+ close: "Close",
424
455
  week: {
425
456
  title: "The week",
426
457
  previous: "The week before",
@@ -512,8 +543,8 @@ export const en = {
512
543
  thisWeek: "Due this week",
513
544
  later: "Later",
514
545
  noDay: "No day set",
515
- decisions: "Decisions waiting for a person",
516
- noDecisions: "No decision waits.",
546
+ decisions: "Decisions waiting for you",
547
+ noDecisions: "No decision waits for your answer.",
517
548
  inProject: (project: string) => `in ${project}`,
518
549
  },
519
550
 
@@ -677,6 +708,8 @@ export const en = {
677
708
  runFinished: (task: string) => `finished ${task}`,
678
709
  runFailed: (task: string) => `failed on ${task}`,
679
710
  runAbandoned: (task: string) => `gave up on ${task}`,
711
+ runUnheard: (task: string, agent: string) =>
712
+ `ended the run of ${agent} on ${task}, not heard from for too long`,
680
713
  documentWritten: (page: string, version: number | undefined) =>
681
714
  `wrote ${page}${version === undefined ? "" : `, version ${version}`}`,
682
715
  documentArchived: (page: string) => `archived ${page}`,
@@ -801,6 +834,7 @@ export const en = {
801
834
  version: (n: number) => `v${n}`,
802
835
  versionWord: "version",
803
836
  writtenBy: "written by",
837
+ author: "Written by",
804
838
  versionOf: (n: number, of: number) => `Version ${n} of ${of}`,
805
839
  showLatest: "Show the latest",
806
840
  archivedNotice: "This page is archived: out of the folders and the search, and not written to.",
@@ -813,6 +847,8 @@ export const en = {
813
847
  decisionPrefix: "Decision: ",
814
848
  files: "Files",
815
849
  fileToAttach: "A file to attach",
850
+ chooseFile: "Choose a file",
851
+ noFileChosen: "No file chosen",
816
852
  labelPlaceholder: "What to call it, if not its name",
817
853
  attach: "Attach",
818
854
  by: "by",
package/src/i18n/ko.ts CHANGED
@@ -93,6 +93,7 @@ export const ko: Messages = {
93
93
  nav: {
94
94
  projects: "프로젝트",
95
95
  work: "내 작업",
96
+ myDecisions: "내 결정",
96
97
  people: "사람들",
97
98
  agents: "에이전트",
98
99
  board: "보드",
@@ -239,8 +240,11 @@ export const ko: Messages = {
239
240
  menu: "작업 메뉴",
240
241
  decisions: (n) => `결정 ${n}건`,
241
242
  decisionsTitle: "사람을 기다리는 결정",
243
+ unfold: (column) => `${column} 펼치기`,
244
+ fold: (column) => `${column} 접기`,
242
245
  filters: {
243
246
  aria: "보드 좁혀 보기",
247
+ words: "제목, 번호, 사람",
244
248
  everyone: "모두",
245
249
  mine: "내 작업",
246
250
  anyCategory: "모든 카테고리",
@@ -340,6 +344,8 @@ export const ko: Messages = {
340
344
  record: "기록",
341
345
  openRecord: "열기",
342
346
  waitingForPerson: "사람을 기다리는 중입니다.",
347
+ waitingFor: (name) => `${name}의 답을 기다리는 중입니다.`,
348
+ peopleLabel: (asker, decider) => `올린 사람 ${asker}, 답할 사람 ${decider}`,
343
349
  options: "선택지",
344
350
  notePlaceholder: "덧붙일 말이 있다면",
345
351
  own: "직접 입력",
@@ -362,6 +368,9 @@ export const ko: Messages = {
362
368
  contextPlaceholder: "답하려면 알아야 할 것, Markdown으로.",
363
369
  about: "관련 작업",
364
370
  noTask: "특정 작업 없음",
371
+ decider: "결정할 사람",
372
+ deciderPlaceholder: "지정하지 않으면 본인",
373
+ me: "(나)",
365
374
  options: "선택지",
366
375
  option: (n) => `선택지 ${n}`,
367
376
  addOption: "선택지 추가",
@@ -369,6 +378,23 @@ export const ko: Messages = {
369
378
  },
370
379
  },
371
380
 
381
+ myDecisions: {
382
+ title: "내 결정",
383
+ loading: "내 결정을 불러오는 중",
384
+ couldNotLoad: "내 결정을 불러올 수 없습니다.",
385
+ empty: {
386
+ title: "내 결정이 없습니다",
387
+ body: "내게 온 결정과 내가 다른 사람에게 올린 결정이 여기에 모입니다. 내가 올린 결정은 내 에이전트가 올린 것을 포함합니다.",
388
+ },
389
+ waiting: "내 답을 기다리는 중",
390
+ noneWaiting: "내 답을 기다리는 결정이 없습니다.",
391
+ raised: "내가 다른 사람에게 올린 결정",
392
+ noneRaised: "다른 사람에게 올린 결정이 없습니다.",
393
+ answered: "답변됨",
394
+ noneAnswered: "아직 답한 결정이 없습니다.",
395
+ more: "더 보기",
396
+ },
397
+
372
398
  calendar: {
373
399
  title: "캘린더",
374
400
  grid: "이 달",
@@ -381,6 +407,11 @@ export const ko: Messages = {
381
407
  nothingOverdue: "기한이 지난 작업이 없습니다.",
382
408
  unscheduled: "날짜 없음",
383
409
  allScheduled: "열린 작업마다 날짜가 있습니다.",
410
+ more: (n) => `${n}개 더`,
411
+ fewer: "접기",
412
+ onDay: (day) => `${day}의 작업`,
413
+ nothingOnDay: "이 날에 잡힌 작업이 없습니다.",
414
+ close: "닫기",
384
415
  week: {
385
416
  title: "이 주",
386
417
  previous: "이전 주",
@@ -389,7 +420,7 @@ export const ko: Messages = {
389
420
  nothing: "이 주에 잡힌 작업이 없습니다.",
390
421
  },
391
422
  workload: {
392
- title: "누가 무엇을",
423
+ title: "담당자별 작업 현황",
393
424
  person: "사람",
394
425
  open: "열림",
395
426
  inProgress: "진행 중",
@@ -471,8 +502,8 @@ export const ko: Messages = {
471
502
  thisWeek: "이번 주 마감",
472
503
  later: "그 뒤",
473
504
  noDay: "날짜 없음",
474
- decisions: "사람을 기다리는 결정",
475
- noDecisions: "기다리는 결정이 없습니다.",
505
+ decisions: "내 답을 기다리는 결정",
506
+ noDecisions: "내 답을 기다리는 결정이 없습니다.",
476
507
  inProject: (project) => `${project}에서`,
477
508
  },
478
509
 
@@ -632,6 +663,8 @@ export const ko: Messages = {
632
663
  runFinished: (task) => `${task}을(를) 끝냈습니다`,
633
664
  runFailed: (task) => `${task}에 실패했습니다`,
634
665
  runAbandoned: (task) => `${task}을(를) 포기했습니다`,
666
+ runUnheard: (task, agent) =>
667
+ `${task}의 ${agent} 런을 끝냈습니다. 너무 오래 소식이 없었습니다`,
635
668
  documentWritten: (page, version) =>
636
669
  `${page}을(를) 썼습니다${version === undefined ? "" : `, 버전 ${version}`}`,
637
670
  documentArchived: (page) => `${page}을(를) 보관했습니다`,
@@ -757,6 +790,7 @@ export const ko: Messages = {
757
790
  version: (n) => `v${n}`,
758
791
  versionWord: "버전",
759
792
  writtenBy: "작성:",
793
+ author: "작성",
760
794
  versionOf: (n, of) => `버전 ${n} / ${of}`,
761
795
  showLatest: "최신 버전 보기",
762
796
  archivedNotice: "이 페이지는 보관됐습니다. 폴더와 검색에서 빠지고, 쓸 수 없습니다.",
@@ -769,6 +803,8 @@ export const ko: Messages = {
769
803
  decisionPrefix: "결정: ",
770
804
  files: "파일",
771
805
  fileToAttach: "첨부할 파일",
806
+ chooseFile: "파일 선택",
807
+ noFileChosen: "선택된 파일 없음",
772
808
  labelPlaceholder: "파일 이름 대신 부를 이름",
773
809
  attach: "첨부",
774
810
  by: "첨부:",
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export {
11
11
  type BoardProps,
12
12
  byDue,
13
13
  columnStateOf,
14
+ FOLDED_STATES,
14
15
  filterTasks,
15
16
  TaskCard,
16
17
  } from "./components/board.js";
@@ -82,6 +83,7 @@ export {
82
83
  goalMonth,
83
84
  } from "./components/goals.js";
84
85
  export { Markdown, type MarkdownProps } from "./components/markdown.js";
86
+ export { MyDecisions, type MyDecisionsProps } from "./components/my-decisions.js";
85
87
  export {
86
88
  PackConnect,
87
89
  type PackConnectProps,
@@ -207,6 +209,7 @@ export {
207
209
  formatBytes,
208
210
  formatInstant,
209
211
  GoalStateBadge,
212
+ LinkButton,
210
213
  PersonChip,
211
214
  PRIORITY_LABELS,
212
215
  PriorityBadge,