@skillcdn/console 0.1.14 → 0.1.16
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 +5 -4
- package/SKILLCDN.md +2 -0
- package/dist/agent-name.d.ts +3 -0
- package/dist/agent-name.d.ts.map +1 -0
- package/dist/agent-name.js +9 -0
- package/dist/agent-name.js.map +1 -0
- package/dist/api.d.ts +6 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -4
- 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 +225 -24
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +14 -4
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +59 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +28 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -2
- package/dist/client.js.map +1 -1
- package/dist/components/calendar.d.ts +35 -2
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +80 -31
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/connect.d.ts +4 -1
- package/dist/components/connect.d.ts.map +1 -1
- package/dist/components/connect.js +7 -3
- package/dist/components/connect.js.map +1 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +2 -2
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts +5 -0
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -2
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +10 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/people.d.ts +24 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +35 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts +10 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +8 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/rules.d.ts +40 -0
- package/dist/components/rules.d.ts.map +1 -0
- package/dist/components/rules.js +46 -0
- package/dist/components/rules.js.map +1 -0
- package/dist/components/runs.d.ts +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +2 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +5 -1
- package/dist/components/skills.js.map +1 -1
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +1 -1
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +2 -9
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +13 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +5 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +1 -1
- package/dist/components/work.js.map +1 -1
- package/dist/console.css +263 -17
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +197 -43
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +19 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +27 -1
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +84 -9
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +84 -13
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +84 -13
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +30 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +50 -10
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +14 -4
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +17 -2
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +28 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +102 -0
- package/dist/rules.js.map +1 -0
- package/dist/schemas.d.ts +652 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -6
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +21 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +21 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +13 -9
- package/docs/specs/rest.md +33 -9
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +1 -1
- package/skills/deploying-a-console/SKILL.md +5 -5
- package/skills/deploying-a-console/assets/secrets.example.json +2 -1
- package/skills/deploying-a-console/references/access.md +10 -6
- package/skills/deploying-a-console/references/resources.md +10 -7
- package/skills/working-the-board/SKILL.md +10 -9
- package/skills/working-the-board/references/project-rules.md +36 -0
- package/src/agent-name.ts +9 -0
- package/src/api.ts +37 -0
- package/src/cli/cli.ts +264 -26
- package/src/cli/format.ts +74 -10
- package/src/cli/help.ts +31 -11
- package/src/cli/main.ts +5 -0
- package/src/client.ts +47 -1
- package/src/components/calendar.tsx +252 -51
- package/src/components/connect.tsx +12 -6
- package/src/components/decision-list.tsx +2 -1
- package/src/components/documents.tsx +7 -3
- package/src/components/event-feed.tsx +16 -3
- package/src/components/people.tsx +244 -35
- package/src/components/projects.tsx +37 -14
- package/src/components/rules.tsx +167 -0
- package/src/components/runs.tsx +4 -2
- package/src/components/skills.tsx +22 -10
- package/src/components/task-form.tsx +1 -0
- package/src/components/tokens.tsx +2 -14
- package/src/components/ui.tsx +25 -2
- package/src/components/work.tsx +34 -28
- package/src/console.tsx +524 -45
- package/src/data.ts +52 -2
- package/src/i18n/en.ts +94 -14
- package/src/i18n/ko.ts +90 -14
- package/src/index.ts +16 -1
- package/src/limits.ts +4 -0
- package/src/router.ts +67 -12
- package/src/routes.ts +23 -3
- package/src/rules.ts +109 -0
- package/src/schemas.ts +121 -4
- package/src/styles/console.css +263 -17
- package/src/vocabulary.ts +30 -0
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import { type ReactNode, type SyntheticEvent, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
OPEN_TASK_STATES,
|
|
4
|
+
type RestCategory,
|
|
5
|
+
type RestGoal,
|
|
6
|
+
type RestPerson,
|
|
7
|
+
type RestTask,
|
|
8
|
+
} from "../api.js";
|
|
3
9
|
import {
|
|
4
10
|
addDays,
|
|
5
11
|
addMonths,
|
|
6
12
|
daysBetween,
|
|
7
13
|
firstDayOfWeek,
|
|
14
|
+
formatDay,
|
|
8
15
|
formatDays,
|
|
9
16
|
formatMonth,
|
|
10
17
|
formatWeekday,
|
|
@@ -16,14 +23,16 @@ import {
|
|
|
16
23
|
weekOf,
|
|
17
24
|
} from "../dates.js";
|
|
18
25
|
import { useLanguage, useWords } from "../i18n/index.js";
|
|
19
|
-
import { cx, PersonChip, personName } from "./ui.js";
|
|
26
|
+
import { Avatar, CategoryBadge, cx, PersonChip, personName, StateBadge, Time } from "./ui.js";
|
|
20
27
|
|
|
21
28
|
// The calendar of a project (ADR-0019): the month as a grid, or one week on its own. A task
|
|
22
29
|
// planned from a start to a due day lies across its days as a bar, in a lane of its own within
|
|
23
|
-
// each week so that the bars of one week never cross; a task with
|
|
24
|
-
// a
|
|
25
|
-
//
|
|
26
|
-
//
|
|
30
|
+
// each week so that the bars of one week never cross; a task with one day alone, a due day or
|
|
31
|
+
// a start, sits on that day as a dot. On a project's calendar the person who has a task stands
|
|
32
|
+
// before its title, since the days hold several people's work. Who has what says whether the
|
|
33
|
+
// work is on its days. The task under the pointer is shown in full within its item, with the
|
|
34
|
+
// facts its own page lists, above or below it where there is more room. Takes its data as props
|
|
35
|
+
// and nothing from the network; the month or the week shown is the address's (ADR-0010).
|
|
27
36
|
|
|
28
37
|
const isOpen = (task: RestTask): boolean => OPEN_TASK_STATES.includes(task.state);
|
|
29
38
|
|
|
@@ -39,6 +48,8 @@ export interface CalendarProps {
|
|
|
39
48
|
readonly today?: string | undefined;
|
|
40
49
|
/** The workspace's categories, for the legend to say what the colours mean. */
|
|
41
50
|
readonly categories?: readonly RestCategory[] | undefined;
|
|
51
|
+
/** The project's goals, so that the task under the pointer is shown with its goal's title. */
|
|
52
|
+
readonly goals?: readonly RestGoal[] | undefined;
|
|
42
53
|
readonly taskHref: (task: RestTask) => string;
|
|
43
54
|
/** Where the calendar is on another month. */
|
|
44
55
|
readonly monthHref: (month: string) => string;
|
|
@@ -104,49 +115,239 @@ function push(map: Map<string, RestTask[]>, day: string, task: RestTask): void {
|
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
/** The tasks that sit on one day each: due on it, or beginning on it with no due day yet. */
|
|
108
|
-
function onDays(tasks: readonly RestTask[]): {
|
|
109
|
-
|
|
110
|
-
readonly starting: ReadonlyMap<string, readonly RestTask[]>;
|
|
111
|
-
} {
|
|
112
|
-
const due = new Map<string, RestTask[]>();
|
|
113
|
-
const starting = new Map<string, RestTask[]>();
|
|
118
|
+
/** The tasks that sit on one day each, by day: due on it, or beginning on it with no due day yet. */
|
|
119
|
+
function onDays(tasks: readonly RestTask[]): ReadonlyMap<string, readonly RestTask[]> {
|
|
120
|
+
const on = new Map<string, RestTask[]>();
|
|
114
121
|
for (const task of tasks) {
|
|
115
122
|
if (spans(task)) {
|
|
116
123
|
continue;
|
|
117
124
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
push(starting, task.startsOn, task);
|
|
125
|
+
const day = task.dueOn ?? task.startsOn;
|
|
126
|
+
if (day !== null) {
|
|
127
|
+
push(on, day, task);
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
130
|
// Within a day, what is still open comes first; the rest reads as what was.
|
|
125
|
-
for (const list of
|
|
131
|
+
for (const list of on.values()) {
|
|
126
132
|
list.sort((a, b) => Number(isOpen(b)) - Number(isOpen(a)) || a.number - b.number);
|
|
127
133
|
}
|
|
128
|
-
return
|
|
134
|
+
return on;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** How a task reads: struck through once it is over, and marked once its due day is past. */
|
|
138
|
+
const tone = (task: RestTask, now: string): string =>
|
|
139
|
+
cx(
|
|
140
|
+
!isOpen(task) && "sc-calendar-over",
|
|
141
|
+
isOpen(task) && task.dueOn !== null && task.dueOn < now && "sc-calendar-late",
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
/** Where the card of the item under the pointer goes: the side with more room, kept within the window. */
|
|
145
|
+
export interface TipPlace {
|
|
146
|
+
readonly side: "above" | "below";
|
|
147
|
+
/** How far the card moves from the item's left edge, in pixels, to stay within the window: zero or less. */
|
|
148
|
+
readonly shift: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** What the card may grow to, in pixels: 20rem at the usual root size, as the style says. */
|
|
152
|
+
const TIP_WIDTH = 320;
|
|
153
|
+
/** What stays between the card and the window's edge, in pixels. */
|
|
154
|
+
const TIP_MARGIN = 8;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Places the card beside an item, from the item's box and the window's size, both in pixels from
|
|
158
|
+
* the window's top left corner: above the item where the room above is at least the room below,
|
|
159
|
+
* else below it, and moved left where it would otherwise leave the window on the right.
|
|
160
|
+
*/
|
|
161
|
+
export function placeTip(
|
|
162
|
+
item: { readonly top: number; readonly bottom: number; readonly left: number },
|
|
163
|
+
window: { readonly width: number; readonly height: number },
|
|
164
|
+
): TipPlace {
|
|
165
|
+
const side = item.top >= window.height - item.bottom ? "above" : "below";
|
|
166
|
+
const left = Math.max(TIP_MARGIN, Math.min(item.left, window.width - TIP_WIDTH - TIP_MARGIN));
|
|
167
|
+
return { side, shift: Math.min(0, left - item.left) };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The item the pointer is on: a task on a day, since a bar has an item on each of its days, and where its card goes. */
|
|
171
|
+
interface Pointed {
|
|
172
|
+
readonly task: RestTask;
|
|
173
|
+
readonly day: string;
|
|
174
|
+
readonly place: TipPlace;
|
|
129
175
|
}
|
|
130
176
|
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
|
|
177
|
+
/** The item under the pointer, measured as the pointer arrives, since only the browser knows where it is. */
|
|
178
|
+
function usePointed(): [
|
|
179
|
+
Pointed | null,
|
|
180
|
+
(task: RestTask, day: string, item: HTMLElement) => void,
|
|
181
|
+
() => void,
|
|
182
|
+
] {
|
|
183
|
+
const [pointed, setPointed] = useState<Pointed | null>(null);
|
|
184
|
+
const point = (task: RestTask, day: string, item: HTMLElement): void =>
|
|
185
|
+
setPointed({
|
|
186
|
+
task,
|
|
187
|
+
day,
|
|
188
|
+
place: placeTip(item.getBoundingClientRect(), {
|
|
189
|
+
width: window.innerWidth,
|
|
190
|
+
height: window.innerHeight,
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
const leave = (): void => setPointed(null);
|
|
194
|
+
return [pointed, point, leave];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface TaskTipProps {
|
|
198
|
+
readonly task: RestTask;
|
|
199
|
+
/** The project's goals, so that the task's goal is named by its title; without them the row is left out. */
|
|
200
|
+
readonly goals?: readonly RestGoal[] | undefined;
|
|
201
|
+
readonly place: TipPlace;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The task under the pointer in full: its number and its title, then the facts its own page lists
|
|
206
|
+
* beside the text, those that are set. Lies within the item it belongs to, above or below it, and
|
|
207
|
+
* out of the accessible tree: the item's text is the title, and the facts are on the task's page.
|
|
208
|
+
*/
|
|
209
|
+
export function TaskTip(props: TaskTipProps) {
|
|
210
|
+
const words = useWords();
|
|
211
|
+
const { tag } = useLanguage();
|
|
212
|
+
const { task, place } = props;
|
|
213
|
+
const goal = props.goals?.find((candidate) => candidate.id === task.goalId);
|
|
214
|
+
const day = (value: string): string => formatDay(value, tag);
|
|
215
|
+
return (
|
|
216
|
+
<div
|
|
217
|
+
className={cx(
|
|
218
|
+
"sc-task-tip",
|
|
219
|
+
place.side === "above" ? "sc-task-tip-above" : "sc-task-tip-below",
|
|
220
|
+
)}
|
|
221
|
+
style={{ left: place.shift }}
|
|
222
|
+
aria-hidden="true"
|
|
223
|
+
>
|
|
224
|
+
<p className="sc-task-tip-title">
|
|
225
|
+
#{task.number} {task.title}
|
|
226
|
+
</p>
|
|
227
|
+
<dl className="sc-task-tip-facts">
|
|
228
|
+
<div>
|
|
229
|
+
<dt>{words.task.state}</dt>
|
|
230
|
+
<dd>
|
|
231
|
+
<StateBadge state={task.state} />
|
|
232
|
+
</dd>
|
|
233
|
+
</div>
|
|
234
|
+
<div>
|
|
235
|
+
<dt>{words.task.form.priority}</dt>
|
|
236
|
+
<dd>{words.vocabulary.priority[task.priority]}</dd>
|
|
237
|
+
</div>
|
|
238
|
+
{task.category !== null && (
|
|
239
|
+
<div>
|
|
240
|
+
<dt>{words.task.category}</dt>
|
|
241
|
+
<dd>
|
|
242
|
+
<CategoryBadge category={task.category} />
|
|
243
|
+
</dd>
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
{goal !== undefined && (
|
|
247
|
+
<div>
|
|
248
|
+
<dt>{words.task.goal}</dt>
|
|
249
|
+
<dd>{goal.title}</dd>
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
<div>
|
|
253
|
+
<dt>{words.task.owner}</dt>
|
|
254
|
+
<dd>
|
|
255
|
+
<PersonChip person={task.owner} />
|
|
256
|
+
</dd>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<dt>{words.task.assignee}</dt>
|
|
260
|
+
<dd>
|
|
261
|
+
{task.assignee === null ? (
|
|
262
|
+
<span className="sc-muted">{words.common.nobody}</span>
|
|
263
|
+
) : (
|
|
264
|
+
<PersonChip person={task.assignee} />
|
|
265
|
+
)}
|
|
266
|
+
</dd>
|
|
267
|
+
</div>
|
|
268
|
+
{(task.startsOn !== null || task.dueOn !== null) && (
|
|
269
|
+
<div>
|
|
270
|
+
<dt>{words.task.plan}</dt>
|
|
271
|
+
<dd>
|
|
272
|
+
{words.task.planned(
|
|
273
|
+
task.startsOn === null ? undefined : day(task.startsOn),
|
|
274
|
+
task.dueOn === null ? undefined : day(task.dueOn),
|
|
275
|
+
)}
|
|
276
|
+
</dd>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
{task.startedAt !== null && (
|
|
280
|
+
<div>
|
|
281
|
+
<dt>{words.task.started}</dt>
|
|
282
|
+
<dd>
|
|
283
|
+
<Time iso={task.startedAt} />
|
|
284
|
+
</dd>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
{task.doneAt !== null && (
|
|
288
|
+
<div>
|
|
289
|
+
<dt>{words.task.done}</dt>
|
|
290
|
+
<dd>
|
|
291
|
+
<Time iso={task.doneAt} />
|
|
292
|
+
</dd>
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
<div>
|
|
296
|
+
<dt>{words.task.written}</dt>
|
|
297
|
+
<dd>
|
|
298
|
+
<Time iso={task.createdAt} />
|
|
299
|
+
</dd>
|
|
300
|
+
</div>
|
|
301
|
+
<div>
|
|
302
|
+
<dt>{words.task.changed}</dt>
|
|
303
|
+
<dd>
|
|
304
|
+
<Time iso={task.updatedAt} />
|
|
305
|
+
</dd>
|
|
306
|
+
</div>
|
|
307
|
+
</dl>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
134
311
|
|
|
135
312
|
interface WeekRowProps {
|
|
136
313
|
readonly week: readonly GridDay[];
|
|
137
314
|
readonly segments: readonly WeekSegment[];
|
|
138
|
-
|
|
139
|
-
readonly
|
|
315
|
+
/** The tasks on one day each, by day. */
|
|
316
|
+
readonly on: ReadonlyMap<string, readonly RestTask[]>;
|
|
140
317
|
readonly now: string;
|
|
141
318
|
readonly taskHref: (task: RestTask) => string;
|
|
142
319
|
/** What stands before a task's title where the tasks are of several projects: the project's mark. */
|
|
143
320
|
readonly markOf?: ((task: RestTask) => ReactNode) | undefined;
|
|
321
|
+
/** Whether the person who has a task stands before its title, where the days hold several people's work. */
|
|
322
|
+
readonly people?: boolean | undefined;
|
|
323
|
+
/** The item under the pointer, shown in full within that item, or none. */
|
|
324
|
+
readonly pointed: Pointed | null;
|
|
325
|
+
/** The project's goals, so that the item under the pointer names its goal; none across projects. */
|
|
326
|
+
readonly goals?: readonly RestGoal[] | undefined;
|
|
327
|
+
/** The pointer, or the focus, arrived on a task's item on a day. */
|
|
328
|
+
readonly onPoint: (task: RestTask, day: string, item: HTMLElement) => void;
|
|
329
|
+
/** It left. */
|
|
330
|
+
readonly onLeave: () => void;
|
|
144
331
|
}
|
|
145
332
|
|
|
146
333
|
/** One week as a row of cells: the bars across the days in their lanes, then the tasks on each day. */
|
|
147
334
|
function WeekRow(props: WeekRowProps) {
|
|
148
|
-
const words = useWords().calendar;
|
|
149
335
|
const { week, segments, now } = props;
|
|
336
|
+
const pointing = (task: RestTask, day: string) => ({
|
|
337
|
+
onMouseEnter: (event: SyntheticEvent<HTMLElement>) =>
|
|
338
|
+
props.onPoint(task, day, event.currentTarget),
|
|
339
|
+
onMouseLeave: props.onLeave,
|
|
340
|
+
onFocus: (event: SyntheticEvent<HTMLElement>) => props.onPoint(task, day, event.currentTarget),
|
|
341
|
+
onBlur: props.onLeave,
|
|
342
|
+
});
|
|
343
|
+
const tipIn = (task: RestTask, day: string): ReactNode =>
|
|
344
|
+
props.pointed !== null && props.pointed.task.id === task.id && props.pointed.day === day ? (
|
|
345
|
+
<TaskTip task={task} goals={props.goals} place={props.pointed.place} />
|
|
346
|
+
) : null;
|
|
347
|
+
const who = (task: RestTask): ReactNode =>
|
|
348
|
+
props.people === true && task.assignee !== null ? (
|
|
349
|
+
<Avatar person={task.assignee} size="xs" />
|
|
350
|
+
) : null;
|
|
150
351
|
const lanes = segments.reduce((most, segment) => Math.max(most, segment.lane + 1), 0);
|
|
151
352
|
return (
|
|
152
353
|
<tr>
|
|
@@ -179,28 +380,30 @@ function WeekRow(props: WeekRowProps) {
|
|
|
179
380
|
`sc-category-${task.category?.color ?? "gray"}`,
|
|
180
381
|
segment.from === column && segment.begins && "sc-calendar-bar-begins",
|
|
181
382
|
segment.to === column && segment.ends && "sc-calendar-bar-ends",
|
|
182
|
-
tone(task,
|
|
383
|
+
tone(task, now),
|
|
183
384
|
)}
|
|
184
385
|
href={props.taskHref(task)}
|
|
185
|
-
|
|
386
|
+
{...pointing(task, cell.day)}
|
|
186
387
|
>
|
|
187
388
|
{segment.from === column && (
|
|
188
389
|
<>
|
|
189
390
|
{props.markOf?.(task)}
|
|
391
|
+
{who(task)}
|
|
190
392
|
<span className="sc-calendar-task-title">{task.title}</span>
|
|
191
393
|
</>
|
|
192
394
|
)}
|
|
395
|
+
{tipIn(task, cell.day)}
|
|
193
396
|
</a>
|
|
194
397
|
);
|
|
195
398
|
})}
|
|
196
|
-
{(props.
|
|
399
|
+
{(props.on.get(cell.day) ?? []).map((task) => {
|
|
197
400
|
const mark = props.markOf?.(task);
|
|
198
401
|
return (
|
|
199
402
|
<a
|
|
200
403
|
key={task.id}
|
|
201
|
-
className={cx("sc-calendar-task", tone(task,
|
|
404
|
+
className={cx("sc-calendar-task", tone(task, now))}
|
|
202
405
|
href={props.taskHref(task)}
|
|
203
|
-
|
|
406
|
+
{...pointing(task, cell.day)}
|
|
204
407
|
>
|
|
205
408
|
{mark === undefined || mark === null ? (
|
|
206
409
|
<span
|
|
@@ -212,21 +415,12 @@ function WeekRow(props: WeekRowProps) {
|
|
|
212
415
|
) : (
|
|
213
416
|
mark
|
|
214
417
|
)}
|
|
418
|
+
{who(task)}
|
|
215
419
|
<span className="sc-calendar-task-title">{task.title}</span>
|
|
420
|
+
{tipIn(task, cell.day)}
|
|
216
421
|
</a>
|
|
217
422
|
);
|
|
218
423
|
})}
|
|
219
|
-
{(props.starting.get(cell.day) ?? []).map((task) => (
|
|
220
|
-
<a
|
|
221
|
-
key={task.id}
|
|
222
|
-
className="sc-calendar-task sc-calendar-task-start"
|
|
223
|
-
href={props.taskHref(task)}
|
|
224
|
-
title={`#${task.number} ${task.title}: ${words.startsOnly}`}
|
|
225
|
-
>
|
|
226
|
-
{props.markOf?.(task)}
|
|
227
|
-
<span className="sc-calendar-task-title">{task.title}</span>
|
|
228
|
-
</a>
|
|
229
|
-
))}
|
|
230
424
|
</td>
|
|
231
425
|
))}
|
|
232
426
|
</tr>
|
|
@@ -236,15 +430,15 @@ function WeekRow(props: WeekRowProps) {
|
|
|
236
430
|
export function Calendar(props: CalendarProps) {
|
|
237
431
|
const words = useWords().calendar;
|
|
238
432
|
const { tag } = useLanguage();
|
|
433
|
+
const [pointed, point, leave] = usePointed();
|
|
239
434
|
const now = props.today ?? todayOf();
|
|
240
435
|
const firstDay = firstDayOfWeek(tag);
|
|
241
436
|
const weeks = monthGrid(props.month, firstDay);
|
|
242
|
-
const
|
|
437
|
+
const on = onDays(props.tasks);
|
|
243
438
|
const inMonth = (day: string) => monthOf(day) === props.month;
|
|
244
439
|
const monthWeeks = weeks.map((week) => ({ week, segments: weekSegments(week, props.tasks) }));
|
|
245
440
|
const nothing =
|
|
246
|
-
[...
|
|
247
|
-
[...starting.keys()].every((day) => !inMonth(day)) &&
|
|
441
|
+
[...on.keys()].every((day) => !inMonth(day)) &&
|
|
248
442
|
monthWeeks.every(({ week, segments }) =>
|
|
249
443
|
segments.every(
|
|
250
444
|
(segment) => !week.slice(segment.from, segment.to + 1).some((cell) => cell.inMonth),
|
|
@@ -293,10 +487,14 @@ export function Calendar(props: CalendarProps) {
|
|
|
293
487
|
key={week[0]?.day}
|
|
294
488
|
week={week}
|
|
295
489
|
segments={segments}
|
|
296
|
-
|
|
297
|
-
starting={starting}
|
|
490
|
+
on={on}
|
|
298
491
|
now={now}
|
|
299
492
|
taskHref={props.taskHref}
|
|
493
|
+
pointed={pointed}
|
|
494
|
+
goals={props.goals}
|
|
495
|
+
onPoint={point}
|
|
496
|
+
onLeave={leave}
|
|
497
|
+
people
|
|
300
498
|
/>
|
|
301
499
|
))}
|
|
302
500
|
</tbody>
|
|
@@ -335,15 +533,16 @@ export interface WeekProps {
|
|
|
335
533
|
export function Week(props: WeekProps) {
|
|
336
534
|
const words = useWords().calendar.week;
|
|
337
535
|
const { tag } = useLanguage();
|
|
536
|
+
const [pointed, point, leave] = usePointed();
|
|
338
537
|
const now = props.today ?? todayOf();
|
|
339
538
|
const firstDay = firstDayOfWeek(tag);
|
|
340
539
|
const days = weekOf(props.day ?? now, firstDay);
|
|
341
540
|
const first = days[0] ?? now;
|
|
342
541
|
const last = days[days.length - 1] ?? now;
|
|
343
542
|
const week: GridDay[] = days.map((day) => ({ day, inMonth: true }));
|
|
344
|
-
const
|
|
543
|
+
const on = onDays(props.tasks);
|
|
345
544
|
const segments = weekSegments(week, props.tasks);
|
|
346
|
-
const nothing = segments.length === 0 && days.every((day) => !
|
|
545
|
+
const nothing = segments.length === 0 && days.every((day) => !on.has(day));
|
|
347
546
|
return (
|
|
348
547
|
<div className="sc-calendar sc-week">
|
|
349
548
|
<div className="sc-calendar-head">
|
|
@@ -382,11 +581,13 @@ export function Week(props: WeekProps) {
|
|
|
382
581
|
<WeekRow
|
|
383
582
|
week={week}
|
|
384
583
|
segments={segments}
|
|
385
|
-
|
|
386
|
-
starting={starting}
|
|
584
|
+
on={on}
|
|
387
585
|
now={now}
|
|
388
586
|
taskHref={props.taskHref}
|
|
389
587
|
markOf={props.markOf}
|
|
588
|
+
pointed={pointed}
|
|
589
|
+
onPoint={point}
|
|
590
|
+
onLeave={leave}
|
|
390
591
|
/>
|
|
391
592
|
</tbody>
|
|
392
593
|
</table>
|
|
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|
|
2
2
|
import { normalizeConnectCode, type RestConnectRequest, type RestTokenInput } from "../api.js";
|
|
3
3
|
import { useWords } from "../i18n/index.js";
|
|
4
4
|
import { TokenForm } from "./tokens.js";
|
|
5
|
-
import { Button, Callout, Time } from "./ui.js";
|
|
5
|
+
import { Button, Callout, CopyButton, Time } from "./ui.js";
|
|
6
6
|
|
|
7
7
|
// Connecting an agent (ADR-0011): where a person types the code their agent showed, what asks
|
|
8
8
|
// to connect under a code with the way to approve it, and the words that tell a person what to
|
|
@@ -106,16 +106,22 @@ export interface ConnectWordsProps {
|
|
|
106
106
|
readonly origin: string;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* What a person says to their agent to connect it: the words to paste into its chat, as they
|
|
111
|
+
* are, with the way to copy them; and the commands for a person who runs them by hand.
|
|
112
|
+
*/
|
|
110
113
|
export function ConnectWords(props: ConnectWordsProps) {
|
|
111
114
|
const words = useWords().connect.words;
|
|
115
|
+
const quote = words.quote(props.origin);
|
|
112
116
|
return (
|
|
113
117
|
<section className="sc-panel sc-connect" aria-label={words.title}>
|
|
114
118
|
<h2 className="sc-section-title">{words.title}</h2>
|
|
115
|
-
<p>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
<p>{words.tellYourAgent}</p>
|
|
120
|
+
<div className="sc-copyable">
|
|
121
|
+
<pre className="sc-code">{quote}</pre>
|
|
122
|
+
<CopyButton text={quote} size="sm" />
|
|
123
|
+
</div>
|
|
124
|
+
<p>{words.doesTheRest}</p>
|
|
119
125
|
<p>{words.byHand}</p>
|
|
120
126
|
<pre className="sc-code">
|
|
121
127
|
npm install -g @skillcdn/console{"\n"}console login --url {props.origin}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
agentLabel,
|
|
3
4
|
MAX_BODY_LENGTH,
|
|
4
5
|
MAX_NOTE_LENGTH,
|
|
5
6
|
type RestAnswerInput,
|
|
@@ -128,7 +129,7 @@ export function DecisionCard(props: DecisionCardProps) {
|
|
|
128
129
|
<PersonChip person={decision.raisedBy} />{" "}
|
|
129
130
|
{decision.run === null
|
|
130
131
|
? words.decisions.asked
|
|
131
|
-
: words.decisions.askedThrough(decision.run.agent)}{" "}
|
|
132
|
+
: words.decisions.askedThrough(agentLabel(decision.run.agent))}{" "}
|
|
132
133
|
<Time iso={decision.createdAt} />
|
|
133
134
|
{task !== undefined && (
|
|
134
135
|
<>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FormEvent, type ReactNode, useState } from "react";
|
|
2
|
+
import { agentLabel } from "../agent-name.js";
|
|
2
3
|
import {
|
|
3
4
|
foldersAbove,
|
|
4
5
|
isDocumentPath,
|
|
@@ -59,7 +60,7 @@ export function DocumentCrumbs(props: {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/** Who wrote a version, and through which agent, as a chip and a word. */
|
|
62
|
-
function Writer(props: {
|
|
63
|
+
export function Writer(props: {
|
|
63
64
|
readonly by: RestDocumentSummary["updatedBy"];
|
|
64
65
|
readonly agent: string | null;
|
|
65
66
|
}) {
|
|
@@ -68,8 +69,11 @@ function Writer(props: {
|
|
|
68
69
|
<>
|
|
69
70
|
<PersonChip person={props.by} />
|
|
70
71
|
{props.agent !== null && (
|
|
71
|
-
<span
|
|
72
|
-
|
|
72
|
+
<span
|
|
73
|
+
className="sc-feed-agent"
|
|
74
|
+
title={agentLabel(props.agent) === props.agent ? words.docs.asAgentTitle : props.agent}
|
|
75
|
+
>
|
|
76
|
+
{words.feed.as(agentLabel(props.agent))}
|
|
73
77
|
</span>
|
|
74
78
|
)}
|
|
75
79
|
</>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { agentLabel, type RestEvent } from "../api.js";
|
|
3
3
|
import { en, type Messages } from "../i18n/en.js";
|
|
4
4
|
import { useWords } from "../i18n/index.js";
|
|
5
5
|
import { ShowMore, usePaged } from "./paged.js";
|
|
@@ -38,6 +38,14 @@ export function describeEvent(event: RestEvent, words: Messages = en): string {
|
|
|
38
38
|
return feed.event.personJoined();
|
|
39
39
|
case "person.role_changed":
|
|
40
40
|
return feed.event.roleChanged(login, role(data.role));
|
|
41
|
+
case "person.listed":
|
|
42
|
+
return feed.event.personListed(login);
|
|
43
|
+
case "person.unlisted":
|
|
44
|
+
return feed.event.personUnlisted(login);
|
|
45
|
+
case "person.deactivated":
|
|
46
|
+
return feed.event.personDeactivated(login);
|
|
47
|
+
case "person.reactivated":
|
|
48
|
+
return feed.event.personReactivated(login);
|
|
41
49
|
case "project.created":
|
|
42
50
|
return feed.event.projectCreated(project);
|
|
43
51
|
case "project.updated":
|
|
@@ -129,8 +137,13 @@ export function EventFeed(props: EventFeedProps) {
|
|
|
129
137
|
<PersonChip person={event.actor} />
|
|
130
138
|
)}
|
|
131
139
|
{event.agent !== null && (
|
|
132
|
-
<span
|
|
133
|
-
|
|
140
|
+
<span
|
|
141
|
+
className="sc-feed-agent"
|
|
142
|
+
title={
|
|
143
|
+
agentLabel(event.agent) === event.agent ? words.feed.asTitle : event.agent
|
|
144
|
+
}
|
|
145
|
+
>
|
|
146
|
+
{words.feed.as(agentLabel(event.agent))}
|
|
134
147
|
</span>
|
|
135
148
|
)}
|
|
136
149
|
</span>
|