@skillcdn/console 0.1.16 → 0.1.17
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.
- package/README.md +2 -1
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +81 -7
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +7 -2
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +23 -2
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +27 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +2 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/terminal.d.ts +5 -0
- package/dist/cli/terminal.d.ts.map +1 -1
- package/dist/cli/terminal.js +11 -1
- package/dist/cli/terminal.js.map +1 -1
- package/dist/client.d.ts +28 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +62 -2
- package/dist/client.js.map +1 -1
- package/dist/components/comments.d.ts +59 -0
- package/dist/components/comments.d.ts.map +1 -0
- package/dist/components/comments.js +138 -0
- package/dist/components/comments.js.map +1 -0
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/markdown.d.ts +6 -0
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +2 -1
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +6 -1
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-view.d.ts +8 -3
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +6 -4
- package/dist/components/task-view.js.map +1 -1
- package/dist/console.css +110 -2
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +22 -4
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +5 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +2 -0
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +14 -2
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +14 -2
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +14 -2
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +3 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +3 -0
- package/dist/limits.js.map +1 -1
- package/dist/rules.js +2 -2
- package/dist/rules.js.map +1 -1
- package/dist/schemas.d.ts +105 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +32 -1
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +1 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +8 -4
- package/docs/specs/rest.md +8 -1
- package/package.json +1 -1
- package/skills/working-the-board/SKILL.md +6 -4
- package/src/api.ts +12 -0
- package/src/cli/cli.ts +95 -7
- package/src/cli/format.ts +28 -1
- package/src/cli/help.ts +31 -2
- package/src/cli/main.ts +2 -1
- package/src/cli/terminal.ts +12 -1
- package/src/client.ts +112 -3
- package/src/components/comments.tsx +342 -0
- package/src/components/event-feed.tsx +2 -0
- package/src/components/markdown.tsx +9 -1
- package/src/components/projects.tsx +16 -3
- package/src/components/task-view.tsx +32 -33
- package/src/console.tsx +26 -3
- package/src/data.ts +8 -0
- package/src/i18n/en.ts +16 -2
- package/src/i18n/ko.ts +16 -2
- package/src/index.ts +12 -0
- package/src/limits.ts +3 -0
- package/src/rules.ts +2 -2
- package/src/schemas.ts +40 -1
- package/src/styles/console.css +110 -2
- package/src/vocabulary.ts +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
RestAnswerInput,
|
|
3
|
-
RestArtifact,
|
|
4
3
|
RestCategory,
|
|
4
|
+
RestComment,
|
|
5
5
|
RestDecision,
|
|
6
6
|
RestDecisionInput,
|
|
7
7
|
RestDecisionPatch,
|
|
@@ -17,19 +17,20 @@ import { formatDay } from "../dates.js";
|
|
|
17
17
|
import { useLanguage, useWords } from "../i18n/index.js";
|
|
18
18
|
import type { TaskFormKind } from "../router.js";
|
|
19
19
|
import { TASK_STATES } from "../vocabulary.js";
|
|
20
|
+
import { type CommentDraft, TaskThread } from "./comments.js";
|
|
20
21
|
import { DecisionForm } from "./decision-form.js";
|
|
21
|
-
import { DecisionCard } from "./decision-list.js";
|
|
22
22
|
import { EventFeed } from "./event-feed.js";
|
|
23
23
|
import { Markdown } from "./markdown.js";
|
|
24
|
-
import { RunList } from "./runs.js";
|
|
25
24
|
import { TaskForm } from "./task-form.js";
|
|
26
25
|
import { CategoryBadge, DueBadge, PersonChip, PriorityBadge, StateBadge, Time } from "./ui.js";
|
|
27
26
|
|
|
28
|
-
// One task: what it is, who it is on, what it links to, the
|
|
29
|
-
// about it
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
27
|
+
// One task: what it is, who it is on, what it links to, the discussion on it, which is what was
|
|
28
|
+
// said about it with the agents at work on it and the decisions about it in the order they
|
|
29
|
+
// came (ADR-0024), the tasks that are part of it, everything that happened to it, and the ways
|
|
30
|
+
// to change it. Takes its data as props and nothing from the network; which of its forms is
|
|
31
|
+
// open is told by the page, since each form has an address of its own (ADR-0010). The facts
|
|
32
|
+
// beside the text say only what is set: a plan, a category or a goal the task has none of is
|
|
33
|
+
// not a row.
|
|
33
34
|
|
|
34
35
|
export interface TaskViewProps {
|
|
35
36
|
readonly task: RestTask;
|
|
@@ -39,6 +40,8 @@ export interface TaskViewProps {
|
|
|
39
40
|
readonly decisions: readonly RestDecision[];
|
|
40
41
|
/** The runs of the board, or of this task; the ones on this task are shown. */
|
|
41
42
|
readonly runs?: readonly RestRun[] | undefined;
|
|
43
|
+
/** The comments on the task, oldest first, when the page has read them. */
|
|
44
|
+
readonly comments?: readonly RestComment[] | undefined;
|
|
42
45
|
/** Everything that happened to the task, oldest first, when the page has read it. */
|
|
43
46
|
readonly history?: readonly RestEvent[] | undefined;
|
|
44
47
|
/** The categories of the workspace, for the form. */
|
|
@@ -50,8 +53,8 @@ export interface TaskViewProps {
|
|
|
50
53
|
readonly decisionHref?: ((decisionId: string) => string) | undefined;
|
|
51
54
|
/** Where a goal of the roadmap is read. */
|
|
52
55
|
readonly goalHref?: ((goalId: string) => string) | undefined;
|
|
53
|
-
/** Where a file a run handed in
|
|
54
|
-
readonly fileHref?: ((
|
|
56
|
+
/** Where a file of the project is read, by its id: one a run handed in, or one put in a comment. */
|
|
57
|
+
readonly fileHref?: ((fileId: string) => string) | undefined;
|
|
55
58
|
/** Where a document of the project is read, by its path, for the links in what was written. */
|
|
56
59
|
readonly docHref?: ((path: string) => string) | undefined;
|
|
57
60
|
/** The day to count due days from; the browser's today when left out. */
|
|
@@ -73,6 +76,8 @@ export interface TaskViewProps {
|
|
|
73
76
|
| ((decision: RestDecision, patch: RestDecisionPatch) => void)
|
|
74
77
|
| undefined;
|
|
75
78
|
readonly onAbandonRun?: ((run: RestRun) => void) | undefined;
|
|
79
|
+
/** Called with a word for the discussion, and the files put in it. Left out, nothing can be said here. */
|
|
80
|
+
readonly onComment?: ((draft: CommentDraft) => void) | undefined;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
export function TaskView(props: TaskViewProps) {
|
|
@@ -86,7 +91,7 @@ export function TaskView(props: TaskViewProps) {
|
|
|
86
91
|
const subtasks = props.tasks.filter((candidate) => candidate.parentId === task.id);
|
|
87
92
|
const about = props.decisions.filter((decision) => decision.taskId === task.id);
|
|
88
93
|
const runs = (props.runs ?? []).filter((run) => run.taskId === task.id);
|
|
89
|
-
const
|
|
94
|
+
const comments = (props.comments ?? []).filter((comment) => comment.taskId === task.id);
|
|
90
95
|
const goal =
|
|
91
96
|
task.goalId === null
|
|
92
97
|
? undefined
|
|
@@ -175,35 +180,29 @@ export function TaskView(props: TaskViewProps) {
|
|
|
175
180
|
/>
|
|
176
181
|
</section>
|
|
177
182
|
)}
|
|
178
|
-
{runs.length > 0
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
{(runs.length > 0 ||
|
|
184
|
+
about.length > 0 ||
|
|
185
|
+
comments.length > 0 ||
|
|
186
|
+
props.onComment !== undefined) && (
|
|
187
|
+
<section className="sc-task-section" aria-label={words.comments.title}>
|
|
188
|
+
<h2 className="sc-section-title">{words.comments.title}</h2>
|
|
189
|
+
<TaskThread
|
|
190
|
+
task={task}
|
|
191
|
+
comments={comments}
|
|
182
192
|
runs={runs}
|
|
183
|
-
|
|
193
|
+
decisions={about}
|
|
184
194
|
fileHref={props.fileHref}
|
|
195
|
+
decisionHref={props.decisionHref}
|
|
185
196
|
docHref={props.docHref}
|
|
186
|
-
|
|
197
|
+
onAnswer={props.onAnswer}
|
|
198
|
+
onUpdateDecision={props.onUpdateDecision}
|
|
199
|
+
onAbandonRun={props.onAbandonRun}
|
|
200
|
+
onComment={props.onComment}
|
|
187
201
|
busy={props.busy}
|
|
202
|
+
empty={<p className="sc-muted">{words.comments.empty}</p>}
|
|
188
203
|
/>
|
|
189
204
|
</section>
|
|
190
205
|
)}
|
|
191
|
-
{about.length > 0 && (
|
|
192
|
-
<section className="sc-task-section" aria-label={words.task.decisions}>
|
|
193
|
-
<h2 className="sc-section-title">{words.task.decisions}</h2>
|
|
194
|
-
{about.map((decision) => (
|
|
195
|
-
<DecisionCard
|
|
196
|
-
key={decision.id}
|
|
197
|
-
decision={decision}
|
|
198
|
-
task={decision.taskId === null ? undefined : tasksById.get(decision.taskId)}
|
|
199
|
-
docHref={props.docHref}
|
|
200
|
-
onAnswer={props.onAnswer}
|
|
201
|
-
onUpdate={props.onUpdateDecision}
|
|
202
|
-
busy={props.busy}
|
|
203
|
-
/>
|
|
204
|
-
))}
|
|
205
|
-
</section>
|
|
206
|
-
)}
|
|
207
206
|
{props.history !== undefined && props.history.length > 0 && (
|
|
208
207
|
<section className="sc-task-section" aria-label={words.task.history}>
|
|
209
208
|
<h2 className="sc-section-title">{words.task.history}</h2>
|
package/src/console.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
PROJECT_RULES_TEMPLATE,
|
|
18
18
|
REFERENCE_RULES,
|
|
19
19
|
type RestAnswerInput,
|
|
20
|
+
type RestComment,
|
|
20
21
|
type RestDecision,
|
|
21
22
|
type RestDecisionInput,
|
|
22
23
|
type RestDecisionPatch,
|
|
@@ -457,7 +458,8 @@ function TaskPage(props: {
|
|
|
457
458
|
? data.tasks.find((candidate) => candidate.id === route.id)
|
|
458
459
|
: data.tasks.find((candidate) => candidate.number === route.number);
|
|
459
460
|
const [history, setHistory] = useState<readonly RestEvent[] | undefined>(undefined);
|
|
460
|
-
const
|
|
461
|
+
const [comments, setComments] = useState<readonly RestComment[] | undefined>(undefined);
|
|
462
|
+
const { taskHistory, taskComments } = data.actions;
|
|
461
463
|
const taskId = task?.id;
|
|
462
464
|
const number = task?.number;
|
|
463
465
|
// A task named by its id is shown at its number's address.
|
|
@@ -481,8 +483,15 @@ function TaskPage(props: {
|
|
|
481
483
|
}
|
|
482
484
|
})
|
|
483
485
|
.catch(() => undefined);
|
|
486
|
+
taskComments(taskId, controller.signal)
|
|
487
|
+
.then((items) => {
|
|
488
|
+
if (!controller.signal.aborted) {
|
|
489
|
+
setComments(items);
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
.catch(() => undefined);
|
|
484
493
|
return () => controller.abort();
|
|
485
|
-
}, [taskId, taskHistory, feedLength]);
|
|
494
|
+
}, [taskId, taskHistory, taskComments, feedLength]);
|
|
486
495
|
if (task === undefined) {
|
|
487
496
|
return (
|
|
488
497
|
<EmptyState
|
|
@@ -500,13 +509,14 @@ function TaskPage(props: {
|
|
|
500
509
|
tasks={data.tasks}
|
|
501
510
|
decisions={data.decisions}
|
|
502
511
|
runs={data.runs}
|
|
512
|
+
comments={comments}
|
|
503
513
|
history={history}
|
|
504
514
|
categories={data.categories}
|
|
505
515
|
goals={data.goals}
|
|
506
516
|
taskHref={(candidate) => taskHref(key, candidate.number)}
|
|
507
517
|
decisionHref={(decisionId) => decisionHref(key, decisionId)}
|
|
508
518
|
goalHref={(goalId) => goalHref(key, goalId)}
|
|
509
|
-
fileHref={(
|
|
519
|
+
fileHref={(fileId) => projectPath(key, "files", fileId)}
|
|
510
520
|
docHref={(path) => docHref(key, path)}
|
|
511
521
|
form={route.form}
|
|
512
522
|
formHref={(form) => taskHref(key, task.number, form)}
|
|
@@ -533,6 +543,19 @@ function TaskPage(props: {
|
|
|
533
543
|
void act(() => data.actions.updateDecision(decision.id, patch))
|
|
534
544
|
}
|
|
535
545
|
onAbandonRun={(run) => void act(() => data.actions.abandonRun(run.id))}
|
|
546
|
+
onComment={(draft) =>
|
|
547
|
+
void act(async () => {
|
|
548
|
+
await data.actions.comment(task.id, {
|
|
549
|
+
body: draft.body,
|
|
550
|
+
files: draft.files.map((file) => ({
|
|
551
|
+
name: file.name,
|
|
552
|
+
bytes: file,
|
|
553
|
+
...(file.type === "" ? {} : { contentType: file.type }),
|
|
554
|
+
})),
|
|
555
|
+
});
|
|
556
|
+
setComments(await taskComments(task.id));
|
|
557
|
+
})
|
|
558
|
+
}
|
|
536
559
|
/>
|
|
537
560
|
);
|
|
538
561
|
}
|
package/src/data.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
ApiError,
|
|
4
|
+
type CommentInput,
|
|
4
5
|
type ConsoleClient,
|
|
5
6
|
type DocumentFilter,
|
|
6
7
|
EVENT_KINDS,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
type RestCategory,
|
|
10
11
|
type RestCategoryInput,
|
|
11
12
|
type RestCategoryPatch,
|
|
13
|
+
type RestComment,
|
|
12
14
|
type RestConnectRequest,
|
|
13
15
|
type RestDecision,
|
|
14
16
|
type RestDecisionInput,
|
|
@@ -117,6 +119,10 @@ export interface ConsoleActions {
|
|
|
117
119
|
abandonRun(id: string): Promise<RestRun>;
|
|
118
120
|
/** Everything that happened to one task of the project, oldest first. */
|
|
119
121
|
taskHistory(taskId: string, signal?: AbortSignal): Promise<RestEvent[]>;
|
|
122
|
+
/** The discussion on one task of the project, oldest first (ADR-0024). */
|
|
123
|
+
taskComments(taskId: string, signal?: AbortSignal): Promise<RestComment[]>;
|
|
124
|
+
/** A word in the discussion on a task, with the files put in it. */
|
|
125
|
+
comment(taskId: string, input: CommentInput): Promise<RestComment>;
|
|
120
126
|
/** Makes a token for whoever is signed in; the answer carries the secret, this once. */
|
|
121
127
|
createToken(input: RestTokenInput): Promise<RestTokenCreated>;
|
|
122
128
|
revokeToken(id: string): Promise<void>;
|
|
@@ -560,6 +566,8 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
560
566
|
}
|
|
561
567
|
return history;
|
|
562
568
|
},
|
|
569
|
+
taskComments: async (taskId, signal) => (await scope().comments(taskId, signal)).items,
|
|
570
|
+
comment: (taskId, input) => scope().comment(taskId, input),
|
|
563
571
|
async createToken(input) {
|
|
564
572
|
const made = await client.createToken(input);
|
|
565
573
|
setTokens((await client.tokens()).items);
|
package/src/i18n/en.ts
CHANGED
|
@@ -249,8 +249,6 @@ export const en = {
|
|
|
249
249
|
raiseDecision: "Raise a decision",
|
|
250
250
|
nothingMore: "Nothing more was written about it.",
|
|
251
251
|
links: "Links",
|
|
252
|
-
runs: "Runs",
|
|
253
|
-
decisions: "Decisions",
|
|
254
252
|
history: "What happened",
|
|
255
253
|
subtasks: "Subtasks",
|
|
256
254
|
partOfIt: "Part of it",
|
|
@@ -538,6 +536,8 @@ export const en = {
|
|
|
538
536
|
taskCreated: (task: string) => `wrote ${task}`,
|
|
539
537
|
taskMoved: (task: string, from: string, to: string) => `moved ${task} from ${from} to ${to}`,
|
|
540
538
|
taskUpdated: (fields: string, task: string) => `changed ${fields} of ${task}`,
|
|
539
|
+
taskCommented: (task: string, excerpt: string | undefined) =>
|
|
540
|
+
`commented on ${task}${excerpt === undefined || excerpt === "" ? "" : `: ${excerpt}`}`,
|
|
541
541
|
decisionRaised: (question: string) => `asked: ${question}`,
|
|
542
542
|
decisionAnswered: (question: string, option: string) => `answered "${question}": ${option}`,
|
|
543
543
|
decisionUpdated: (fields: string, question: string) =>
|
|
@@ -571,6 +571,20 @@ export const en = {
|
|
|
571
571
|
label: (agent: string, name: string) => `${agent} for ${name}`,
|
|
572
572
|
},
|
|
573
573
|
|
|
574
|
+
comments: {
|
|
575
|
+
title: "Discussion",
|
|
576
|
+
empty: "Nothing was said yet.",
|
|
577
|
+
one: "Comment",
|
|
578
|
+
write: "Write a comment",
|
|
579
|
+
placeholder: "A word in the discussion, in Markdown.",
|
|
580
|
+
files: "Files",
|
|
581
|
+
chooseFiles: "Add files",
|
|
582
|
+
hint: "A picture pasted or chosen here goes with the comment;  places it in the text.",
|
|
583
|
+
removeFile: (name: string) => `Take ${name} out`,
|
|
584
|
+
picture: (name: string) => `The picture ${name}`,
|
|
585
|
+
label: (name: string) => `A comment by ${name}`,
|
|
586
|
+
},
|
|
587
|
+
|
|
574
588
|
skills: {
|
|
575
589
|
loading: "Loading the skills",
|
|
576
590
|
list: "Skills",
|
package/src/i18n/ko.ts
CHANGED
|
@@ -218,8 +218,6 @@ export const ko: Messages = {
|
|
|
218
218
|
raiseDecision: "결정 요청",
|
|
219
219
|
nothingMore: "더 적힌 내용이 없습니다.",
|
|
220
220
|
links: "링크",
|
|
221
|
-
runs: "실행",
|
|
222
|
-
decisions: "결정",
|
|
223
221
|
history: "일어난 일",
|
|
224
222
|
subtasks: "하위 작업",
|
|
225
223
|
partOfIt: "하위 작업",
|
|
@@ -504,6 +502,8 @@ export const ko: Messages = {
|
|
|
504
502
|
taskCreated: (task) => `${task}을(를) 썼습니다`,
|
|
505
503
|
taskMoved: (task, from, to) => `${task}을(를) ${from}에서 ${to}(으)로 옮겼습니다`,
|
|
506
504
|
taskUpdated: (fields, task) => `${task}의 ${fields}을(를) 바꿨습니다`,
|
|
505
|
+
taskCommented: (task, excerpt) =>
|
|
506
|
+
`${task}에 댓글을 남겼습니다${excerpt === undefined || excerpt === "" ? "" : `: ${excerpt}`}`,
|
|
507
507
|
decisionRaised: (question) => `질문했습니다: ${question}`,
|
|
508
508
|
decisionAnswered: (question, option) => `"${question}"에 답했습니다: ${option}`,
|
|
509
509
|
decisionUpdated: (fields, question) => `결정 "${question}"의 ${fields}을(를) 썼습니다`,
|
|
@@ -536,6 +536,20 @@ export const ko: Messages = {
|
|
|
536
536
|
label: (agent, name) => `${name}을(를) 위한 ${agent}`,
|
|
537
537
|
},
|
|
538
538
|
|
|
539
|
+
comments: {
|
|
540
|
+
title: "논의",
|
|
541
|
+
empty: "아직 아무 말도 없습니다.",
|
|
542
|
+
one: "댓글 남기기",
|
|
543
|
+
write: "댓글 쓰기",
|
|
544
|
+
placeholder: "논의에 보탤 말을 마크다운으로.",
|
|
545
|
+
files: "파일",
|
|
546
|
+
chooseFiles: "파일 추가",
|
|
547
|
+
hint: "여기에 붙여 넣거나 고른 그림은 댓글과 함께 올라갑니다. 으로 본문에 넣을 수 있습니다.",
|
|
548
|
+
removeFile: (name) => `${name} 빼기`,
|
|
549
|
+
picture: (name) => `그림 ${name}`,
|
|
550
|
+
label: (name) => `${name}의 댓글`,
|
|
551
|
+
},
|
|
552
|
+
|
|
539
553
|
skills: {
|
|
540
554
|
loading: "스킬을 불러오는 중",
|
|
541
555
|
list: "스킬",
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,18 @@ export {
|
|
|
26
26
|
workloadOf,
|
|
27
27
|
} from "./components/calendar.js";
|
|
28
28
|
export { CategoryList, type CategoryListProps } from "./components/categories.js";
|
|
29
|
+
export {
|
|
30
|
+
CommentCard,
|
|
31
|
+
type CommentCardProps,
|
|
32
|
+
type CommentDraft,
|
|
33
|
+
CommentForm,
|
|
34
|
+
type CommentFormProps,
|
|
35
|
+
isPicture,
|
|
36
|
+
TaskThread,
|
|
37
|
+
type TaskThreadProps,
|
|
38
|
+
type ThreadItem,
|
|
39
|
+
threadOf,
|
|
40
|
+
} from "./components/comments.js";
|
|
29
41
|
export {
|
|
30
42
|
ConnectApproval,
|
|
31
43
|
type ConnectApprovalProps,
|
package/src/limits.ts
CHANGED
|
@@ -28,6 +28,9 @@ export const MAX_SUMMARY_LENGTH = 2000;
|
|
|
28
28
|
/** How many reports, and how many artifacts, one run may carry. */
|
|
29
29
|
export const MAX_REPORTS_PER_RUN = 500;
|
|
30
30
|
export const MAX_ARTIFACTS_PER_RUN = 50;
|
|
31
|
+
/** How many comments one task carries, and how many files one comment carries, together within `MAX_FILE_BYTES`. */
|
|
32
|
+
export const MAX_COMMENTS_PER_TASK = 500;
|
|
33
|
+
export const MAX_FILES_PER_COMMENT = 10;
|
|
31
34
|
/** A file a run hands in: how many bytes, and its name, one line without a path. */
|
|
32
35
|
export const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
33
36
|
export const MAX_FILE_NAME_LENGTH = 200;
|
package/src/rules.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const REFERENCE_RULES = [
|
|
|
23
23
|
"",
|
|
24
24
|
"## Start here, every time",
|
|
25
25
|
"",
|
|
26
|
-
"1. These rules come with the task: `console take` prints them under it, and `console rules` reads them any time. Read them before anything is made. The task says where the work is and what it links to; `console task <number>` shows a task in full, with its plan, its decisions and the
|
|
26
|
+
"1. These rules come with the task: `console take` prints them under it, and `console rules` reads them any time. Read them before anything is made. The task says where the work is and what it links to; `console task <number>` shows a task in full, with its plan, its decisions, the runs on it and the discussion on it, without taking it. How the board is worked is the skill working-the-board, shipped with the command (`console skills` says where it is); these rules say what this organization asks, not how the command works.",
|
|
27
27
|
"2. Act as the person whose token the command holds, and no further. What they may not do, the console refuses; that step goes back to them in a report, never around them.",
|
|
28
28
|
"",
|
|
29
29
|
"## The organization's skills",
|
|
@@ -44,7 +44,7 @@ export const REFERENCE_RULES = [
|
|
|
44
44
|
"",
|
|
45
45
|
"## Reports",
|
|
46
46
|
"",
|
|
47
|
-
"Report at the milestones, not every step: what was found, what was done, what is next, in words for people; a problem as a problem, with what it blocks. What people read on the board, the reports, the questions and their options, the tasks and the pages, is written in {language}. A task's title says what is to be done, in plain words that stand on their own; what it refers to, a milestone, a commit, a page, goes in the body, linked where it can be. The result goes in a last report or is handed in as a link or a file, with the check that ran and its result; a check that did not run is said to be not run.",
|
|
47
|
+
"Report at the milestones, not every step: what was found, what was done, what is next, in words for people; a problem as a problem, with what it blocks. What people read on the board, the reports, the questions and their options, the tasks and the pages, is written in {language}. A task's title says what is to be done, in plain words that stand on their own; what it refers to, a milestone, a commit, a page, goes in the body, linked where it can be. The result goes in a last report or is handed in as a link or a file, with the check that ran and its result; a check that did not run is said to be not run. A report is the run's record; a word to the task's people, an answer to what they asked in its comments, a question that is not a decision, a remark, is a comment on the task (`console comment`), and what they put in a comment is read with `console file`.",
|
|
48
48
|
"",
|
|
49
49
|
"## What never happens",
|
|
50
50
|
"",
|
package/src/schemas.ts
CHANGED
|
@@ -647,8 +647,10 @@ export const restEventDataSchema = z.object({
|
|
|
647
647
|
status: z.optional(z.enum(RUN_STATUSES)),
|
|
648
648
|
/** For `run.handed_in`: what the artifact is called. */
|
|
649
649
|
label: z.optional(z.string()),
|
|
650
|
-
/** For `run.reported`: the first words of
|
|
650
|
+
/** For `run.reported` and `task.commented`: the first words of what was written. */
|
|
651
651
|
excerpt: z.optional(z.string()),
|
|
652
|
+
/** For `task.commented`: how many files came with it. */
|
|
653
|
+
files: z.optional(z.int()),
|
|
652
654
|
});
|
|
653
655
|
export type RestEventData = z.infer<typeof restEventDataSchema>;
|
|
654
656
|
|
|
@@ -740,6 +742,34 @@ export type RestRun = z.infer<typeof restRunSchema>;
|
|
|
740
742
|
export const restRunsSchema = z.object({ items: z.array(restRunSchema), more: z.boolean() });
|
|
741
743
|
export type RestRuns = z.infer<typeof restRunsSchema>;
|
|
742
744
|
|
|
745
|
+
/** A file put in a comment, kept as a run's file is; its bytes are at `projectPath(key, "files", <id>)`. */
|
|
746
|
+
export const restCommentFileSchema = z.object({
|
|
747
|
+
id: uuid,
|
|
748
|
+
file: restFileSchema,
|
|
749
|
+
});
|
|
750
|
+
export type RestCommentFile = z.infer<typeof restCommentFileSchema>;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* A word in the discussion on a task (ADR-0024): by a person, or by their agent, with the
|
|
754
|
+
* files put in it. Never rewritten: a mistake is corrected in the next one.
|
|
755
|
+
*/
|
|
756
|
+
export const restCommentSchema = z.object({
|
|
757
|
+
id: uuid,
|
|
758
|
+
taskId: uuid,
|
|
759
|
+
author: restPersonSchema,
|
|
760
|
+
/** The agent the author wrote through, or `null` when a person wrote it themselves. */
|
|
761
|
+
agent: z.nullable(z.string()),
|
|
762
|
+
/** Markdown. A picture among the files is placed in it as ``. */
|
|
763
|
+
body: z.string(),
|
|
764
|
+
files: z.array(restCommentFileSchema),
|
|
765
|
+
createdAt: instant,
|
|
766
|
+
});
|
|
767
|
+
export type RestComment = z.infer<typeof restCommentSchema>;
|
|
768
|
+
|
|
769
|
+
/** `GET .../tasks/<id>/comments`: the discussion on a task, oldest first, whole. */
|
|
770
|
+
export const restCommentsSchema = z.object({ items: z.array(restCommentSchema) });
|
|
771
|
+
export type RestComments = z.infer<typeof restCommentsSchema>;
|
|
772
|
+
|
|
743
773
|
/** `GET .../events?after=`: what happened after that number, oldest first. */
|
|
744
774
|
export const restEventsSchema = z.object({
|
|
745
775
|
items: z.array(restEventSchema),
|
|
@@ -1032,6 +1062,15 @@ export const restReportInputSchema = z.object({
|
|
|
1032
1062
|
});
|
|
1033
1063
|
export type RestReportInput = z.infer<typeof restReportInputSchema>;
|
|
1034
1064
|
|
|
1065
|
+
/**
|
|
1066
|
+
* What `POST .../tasks/<id>/comments` is sent as JSON: the word, in Markdown. As a form, the
|
|
1067
|
+
* part `body` says the same and the parts `file` carry the files; then the body may be blank.
|
|
1068
|
+
*/
|
|
1069
|
+
export const restCommentInputSchema = z.object({
|
|
1070
|
+
body,
|
|
1071
|
+
});
|
|
1072
|
+
export type RestCommentInput = z.infer<typeof restCommentInputSchema>;
|
|
1073
|
+
|
|
1035
1074
|
/** What `POST .../runs/<id>/artifacts` is sent: a link to what was made. */
|
|
1036
1075
|
export const restArtifactInputSchema = z.object({
|
|
1037
1076
|
url,
|
package/src/styles/console.css
CHANGED
|
@@ -295,6 +295,7 @@ table {
|
|
|
295
295
|
.sc-answer-form > *,
|
|
296
296
|
.sc-run > *,
|
|
297
297
|
.sc-report > *,
|
|
298
|
+
.sc-comment > *,
|
|
298
299
|
.sc-feed > *,
|
|
299
300
|
.sc-card > *,
|
|
300
301
|
.sc-form > *,
|
|
@@ -468,12 +469,15 @@ table {
|
|
|
468
469
|
|
|
469
470
|
/* The tabs: the pages of the level the person is on, under the header's first row. */
|
|
470
471
|
|
|
472
|
+
/* The bar lies one pixel over the header's line, so that the current tab's underline sits on it;
|
|
473
|
+
the tabs scroll sideways where they do not fit, and nothing in the bar ever scrolls down. */
|
|
471
474
|
.sc-tabs {
|
|
472
475
|
width: 100%;
|
|
473
476
|
max-width: var(--sc-width-page);
|
|
474
477
|
margin-inline: auto;
|
|
478
|
+
margin-bottom: -1px;
|
|
475
479
|
padding-inline: var(--sc-space-5);
|
|
476
|
-
overflow
|
|
480
|
+
overflow: auto hidden;
|
|
477
481
|
scrollbar-width: none;
|
|
478
482
|
}
|
|
479
483
|
|
|
@@ -487,7 +491,6 @@ table {
|
|
|
487
491
|
align-items: center;
|
|
488
492
|
gap: var(--sc-space-2);
|
|
489
493
|
padding: var(--sc-space-2) 0 var(--sc-space-3);
|
|
490
|
-
margin-bottom: -1px;
|
|
491
494
|
border-bottom: 2px solid transparent;
|
|
492
495
|
color: var(--sc-color-text-muted);
|
|
493
496
|
font-size: var(--sc-text-sm);
|
|
@@ -743,6 +746,7 @@ table {
|
|
|
743
746
|
.sc-project,
|
|
744
747
|
.sc-decision,
|
|
745
748
|
.sc-run,
|
|
749
|
+
.sc-comment,
|
|
746
750
|
.sc-sign-in,
|
|
747
751
|
.sc-facts {
|
|
748
752
|
position: relative;
|
|
@@ -758,6 +762,7 @@ table {
|
|
|
758
762
|
.sc-project::before,
|
|
759
763
|
.sc-decision::before,
|
|
760
764
|
.sc-run::before,
|
|
765
|
+
.sc-comment::before,
|
|
761
766
|
.sc-sign-in::before,
|
|
762
767
|
.sc-facts::before {
|
|
763
768
|
content: "";
|
|
@@ -2135,6 +2140,109 @@ table {
|
|
|
2135
2140
|
border-top: var(--sc-border);
|
|
2136
2141
|
}
|
|
2137
2142
|
|
|
2143
|
+
/* The discussion on a task (ADR-0024): what was said, the runs and the decisions, one thread in
|
|
2144
|
+
the order they came, with the way to say something at its end. */
|
|
2145
|
+
.sc-thread {
|
|
2146
|
+
display: grid;
|
|
2147
|
+
gap: var(--sc-space-3);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
.sc-comment {
|
|
2151
|
+
display: grid;
|
|
2152
|
+
gap: var(--sc-space-3);
|
|
2153
|
+
padding: var(--sc-space-4) var(--sc-space-5);
|
|
2154
|
+
border-radius: var(--sc-radius-lg);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
.sc-comment-header {
|
|
2158
|
+
display: flex;
|
|
2159
|
+
flex-wrap: wrap;
|
|
2160
|
+
align-items: center;
|
|
2161
|
+
gap: var(--sc-space-2);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
.sc-comment-meta {
|
|
2165
|
+
color: var(--sc-color-text-muted);
|
|
2166
|
+
font-size: var(--sc-text-sm);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/* A picture in a comment is seen where it is, no taller than a screen's worth. */
|
|
2170
|
+
.sc-comment-picture img,
|
|
2171
|
+
.sc-comment .sc-prose img {
|
|
2172
|
+
display: block;
|
|
2173
|
+
max-width: 100%;
|
|
2174
|
+
max-height: 24rem;
|
|
2175
|
+
border: var(--sc-border);
|
|
2176
|
+
border-radius: var(--sc-radius-md);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
.sc-comment-files {
|
|
2180
|
+
display: grid;
|
|
2181
|
+
gap: var(--sc-space-2);
|
|
2182
|
+
margin: 0;
|
|
2183
|
+
padding: 0;
|
|
2184
|
+
list-style: none;
|
|
2185
|
+
font-size: var(--sc-text-sm);
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
.sc-comment-picture {
|
|
2189
|
+
display: grid;
|
|
2190
|
+
gap: var(--sc-space-1);
|
|
2191
|
+
justify-items: start;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
.sc-comment-form {
|
|
2195
|
+
display: grid;
|
|
2196
|
+
gap: var(--sc-space-2);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
.sc-comment-chosen {
|
|
2200
|
+
display: flex;
|
|
2201
|
+
flex-wrap: wrap;
|
|
2202
|
+
gap: var(--sc-space-2);
|
|
2203
|
+
margin: 0;
|
|
2204
|
+
padding: 0;
|
|
2205
|
+
list-style: none;
|
|
2206
|
+
font-size: var(--sc-text-sm);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.sc-comment-chosen-file {
|
|
2210
|
+
display: inline-flex;
|
|
2211
|
+
align-items: center;
|
|
2212
|
+
gap: var(--sc-space-1);
|
|
2213
|
+
padding: var(--sc-space-1) var(--sc-space-2);
|
|
2214
|
+
border: var(--sc-border);
|
|
2215
|
+
border-radius: var(--sc-radius-md);
|
|
2216
|
+
background: var(--sc-color-sheet);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
.sc-comment-form-actions {
|
|
2220
|
+
display: flex;
|
|
2221
|
+
flex-wrap: wrap;
|
|
2222
|
+
align-items: center;
|
|
2223
|
+
justify-content: flex-end;
|
|
2224
|
+
gap: var(--sc-space-2);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
.sc-comment-form-actions label {
|
|
2228
|
+
position: relative;
|
|
2229
|
+
cursor: pointer;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/* The file input is the label's: there for the keyboard and the screen reader, not seen. */
|
|
2233
|
+
.sc-comment-file-input {
|
|
2234
|
+
position: absolute;
|
|
2235
|
+
width: 1px;
|
|
2236
|
+
height: 1px;
|
|
2237
|
+
overflow: hidden;
|
|
2238
|
+
clip-path: inset(50%);
|
|
2239
|
+
opacity: 0;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.sc-comment-form .sc-field-hint {
|
|
2243
|
+
margin-top: 0;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2138
2246
|
.sc-connect {
|
|
2139
2247
|
display: grid;
|
|
2140
2248
|
gap: var(--sc-space-3);
|