@skillcdn/console 0.4.3 → 0.4.4
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 +4 -4
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -3
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +95 -20
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +4 -2
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +32 -9
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +17 -14
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +5 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +23 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +10 -1
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +2 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +6 -0
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +3 -3
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -0
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +23 -5
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +5 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +25 -5
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +7 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +2 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +15 -2
- package/dist/components/people.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -12
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +53 -20
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +151 -26
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +1 -13
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +5 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +761 -191
- package/dist/console.d.ts +10 -3
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +33 -13
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +4 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +5 -0
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +174 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +175 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +170 -3
- 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 +6 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +6 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/bridge.d.ts +44 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +26 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +14 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +18 -1
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +1 -0
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +1 -1
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +5 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +5 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +83 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +111 -7
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +76 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +23 -2
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +2 -1
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +10 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +34 -0
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +497 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -9
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +3 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +4 -3
- package/docs/specs/plugins.md +46 -33
- package/docs/specs/rest.md +16 -15
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +8 -7
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +6 -0
- package/src/cli/cli.ts +124 -19
- package/src/cli/format.ts +42 -9
- package/src/cli/help.ts +17 -13
- package/src/cli/plugin-check.ts +41 -2
- package/src/cli/plugin-cli.ts +10 -1
- package/src/cli/plugin-init.ts +8 -0
- package/src/client.ts +8 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +3 -3
- package/src/components/decision-form.tsx +75 -19
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +7 -1
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +136 -54
- package/src/components/plugin-frame.tsx +62 -63
- package/src/components/plugin-settings.tsx +428 -87
- package/src/components/projects.tsx +0 -55
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +6 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +80 -30
- package/src/data.ts +9 -2
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +178 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +174 -3
- package/src/index.ts +7 -5
- package/src/limits.ts +6 -0
- package/src/plugin/bridge.ts +77 -1
- package/src/plugin/capabilities.ts +17 -1
- package/src/plugin/context.ts +18 -1
- package/src/plugin/define.ts +4 -1
- package/src/plugin/limits.ts +5 -2
- package/src/plugin/manifest.ts +154 -10
- package/src/plugin/rest-schemas.ts +27 -1
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +2 -1
- package/src/plugin/web.ts +49 -0
- package/src/plugin.ts +18 -0
- package/src/rules.ts +3 -3
- package/src/schemas.ts +118 -10
- package/src/styles/console.css +761 -191
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +4 -0
- package/template/plugin/console-plugin.json +3 -1
package/src/client.ts
CHANGED
|
@@ -498,6 +498,8 @@ export interface PluginClient {
|
|
|
498
498
|
fileUrl(fileId: string): string;
|
|
499
499
|
/** The bytes of a record's file, with the name and the type the console says. */
|
|
500
500
|
readFile(fileId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
501
|
+
/** A person's picture, for a frame, which names the person and never an address (ADR-0063). */
|
|
502
|
+
personPicture(personId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
501
503
|
/** Where the frame of a page or a widget is (ADR-0054), on a project when its key is given. */
|
|
502
504
|
frameUrl(options: {
|
|
503
505
|
readonly page?: string | undefined;
|
|
@@ -543,6 +545,8 @@ export interface PluginClient {
|
|
|
543
545
|
disable(): Promise<RestPluginDetail>;
|
|
544
546
|
/** Changes the settings' values, without secrets; `null` clears one. */
|
|
545
547
|
updateSettings(patch: RestPluginSettingsPatch): Promise<RestPluginDetail>;
|
|
548
|
+
/** Says what an enabled plugin holds: those it requires, named again, and the optional ones to turn on. */
|
|
549
|
+
updateConsent(consent: readonly string[]): Promise<RestPluginDetail>;
|
|
546
550
|
/** Seals a secret for the plugin; it is answered by nothing from then on. */
|
|
547
551
|
setSecret(name: string, value: string): Promise<void>;
|
|
548
552
|
removeSecret(name: string): Promise<void>;
|
|
@@ -1143,6 +1147,8 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1143
1147
|
),
|
|
1144
1148
|
fileUrl: (fileId) => `${base}${at("files", fileId)}`,
|
|
1145
1149
|
readFile: (fileId, signal) => read(`${base}${at("files", fileId)}`, fileId, signal),
|
|
1150
|
+
personPicture: (personId, signal) =>
|
|
1151
|
+
read(`${base}${at("people", personId, "picture")}`, personId, signal),
|
|
1146
1152
|
frameUrl: (options) =>
|
|
1147
1153
|
withQuery(at("frame"), {
|
|
1148
1154
|
page: options.page,
|
|
@@ -1213,6 +1219,8 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1213
1219
|
disable: () => request("POST", `${base}${at("disable")}`, undefined, restPluginDetailSchema),
|
|
1214
1220
|
updateSettings: (patch) =>
|
|
1215
1221
|
request("PATCH", `${base}${at("settings")}`, patch, restPluginDetailSchema),
|
|
1222
|
+
updateConsent: (consent) =>
|
|
1223
|
+
request("PATCH", `${base}${at("consent")}`, { consent }, restPluginDetailSchema),
|
|
1216
1224
|
setSecret: (name, value) =>
|
|
1217
1225
|
request("PUT", `${base}${at("secrets", name)}`, { value }, undefined),
|
|
1218
1226
|
removeSecret: (name) =>
|
package/src/compat.ts
CHANGED
|
@@ -113,7 +113,7 @@ export interface ClientPolicy {
|
|
|
113
113
|
* last changed what the command reads or sends (ADR-0038), whose changelog entry begins with
|
|
114
114
|
* `Breaking:`; `pnpm check` holds it to a version the package has, or is about to have.
|
|
115
115
|
*/
|
|
116
|
-
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.
|
|
116
|
+
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.4" };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* The oldest console this command works with (ADR-0038): raised by the release that last
|
package/src/components/board.tsx
CHANGED
|
@@ -71,13 +71,13 @@ export interface BoardProps {
|
|
|
71
71
|
export const FOLDED_STATES: readonly TaskState[] = ["done", "dropped"];
|
|
72
72
|
|
|
73
73
|
/** How far a mouse travels on a card before the press is a drag and not a click. */
|
|
74
|
-
const DRAG_SLACK = 4;
|
|
74
|
+
export const DRAG_SLACK = 4;
|
|
75
75
|
/**
|
|
76
76
|
* How long a finger rests on a card before the card comes loose, and how far it may wander
|
|
77
77
|
* meanwhile; a finger that moves sooner is scrolling the page, not moving the card.
|
|
78
78
|
*/
|
|
79
|
-
const HOLD_MS = 300;
|
|
80
|
-
const HOLD_SLACK = 8;
|
|
79
|
+
export const HOLD_MS = 300;
|
|
80
|
+
export const HOLD_SLACK = 8;
|
|
81
81
|
/** How near the pointer comes to the board's edge before the board scrolls to show more columns. */
|
|
82
82
|
const EDGE = 48;
|
|
83
83
|
/** How fast the board scrolls, in pixels a second, with the pointer at the edge itself; slower further in. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ClipboardEvent, type FormEvent, useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
MAX_BODY_LENGTH,
|
|
4
|
+
MAX_DECIDERS,
|
|
4
5
|
MAX_FILES_PER_DECISION,
|
|
5
6
|
MAX_OPTION_LABEL_LENGTH,
|
|
6
7
|
MAX_OPTIONS,
|
|
@@ -39,6 +40,23 @@ export interface DecisionFormProps {
|
|
|
39
40
|
readonly onCancel?: (() => void) | undefined;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* What the form sends of whom the decision is for: nothing for the raiser alone, `deciderId` for
|
|
45
|
+
* one other person, `deciderIds` for several (ADR-0057), the first among them repeated as
|
|
46
|
+
* `deciderId` for a console of before.
|
|
47
|
+
*/
|
|
48
|
+
export function decidersInput(
|
|
49
|
+
deciders: readonly RestPerson[],
|
|
50
|
+
me: RestPerson | undefined,
|
|
51
|
+
): Pick<RestDecisionInput, "deciderId" | "deciderIds"> {
|
|
52
|
+
const ids = deciders.map((person) => person.id);
|
|
53
|
+
if (ids.length === 0 || (ids.length === 1 && ids[0] === me?.id)) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const first = ids[0] ?? "";
|
|
57
|
+
return ids.length === 1 ? { deciderId: first } : { deciderId: first, deciderIds: ids };
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
/** The people of the project found by the words typed, a page at a time, as a picker searches. */
|
|
43
61
|
export function peopleSearch(people: readonly RestPerson[]): PickerSearch<RestPerson> {
|
|
44
62
|
return async (query, page) => {
|
|
@@ -63,9 +81,20 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
63
81
|
const [body, setBody] = useState("");
|
|
64
82
|
const [about, setAbout] = useState<RestTask | undefined>(undefined);
|
|
65
83
|
const [options, setOptions] = useState<readonly string[]>(["", ""]);
|
|
66
|
-
|
|
84
|
+
// Whom it is for (ADR-0042, ADR-0057): the raiser, until others are named in their place.
|
|
85
|
+
const [deciders, setDeciders] = useState<readonly RestPerson[]>(
|
|
86
|
+
props.me === undefined ? [] : [props.me],
|
|
87
|
+
);
|
|
67
88
|
const [files, setFiles] = useState<readonly File[]>([]);
|
|
68
|
-
const findDeciders = useMemo(
|
|
89
|
+
const findDeciders = useMemo(
|
|
90
|
+
() =>
|
|
91
|
+
peopleSearch(
|
|
92
|
+
(props.deciders ?? []).filter(
|
|
93
|
+
(person) => !deciders.some((chosen) => chosen.id === person.id),
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
[props.deciders, deciders],
|
|
97
|
+
);
|
|
69
98
|
|
|
70
99
|
const labels = options.map((option) => option.trim()).filter((option) => option.length > 0);
|
|
71
100
|
const ready = question.trim().length > 0 && labels.length >= MIN_OPTIONS;
|
|
@@ -115,7 +144,7 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
115
144
|
body,
|
|
116
145
|
options: labels.map((label) => ({ label })),
|
|
117
146
|
taskId: props.taskId ?? about?.id ?? null,
|
|
118
|
-
...(
|
|
147
|
+
...decidersInput(deciders, props.me),
|
|
119
148
|
},
|
|
120
149
|
files,
|
|
121
150
|
);
|
|
@@ -195,24 +224,51 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
195
224
|
</div>
|
|
196
225
|
)}
|
|
197
226
|
{props.deciders !== undefined && (
|
|
198
|
-
<
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
render={(person) => (
|
|
205
|
-
<>
|
|
206
|
-
<PersonChip person={person} size="sm" />
|
|
227
|
+
<fieldset className="sc-field sc-workers-field">
|
|
228
|
+
<legend className="sc-field-label">{form.decider}</legend>
|
|
229
|
+
<div className="sc-chips">
|
|
230
|
+
{deciders.map((person) => (
|
|
231
|
+
<span key={person.id} className="sc-chip">
|
|
232
|
+
<PersonChip person={person} />
|
|
207
233
|
{person.id === props.me?.id && <span className="sc-muted"> {form.me}</span>}
|
|
208
|
-
|
|
234
|
+
<button
|
|
235
|
+
type="button"
|
|
236
|
+
className="sc-chip-remove"
|
|
237
|
+
aria-label={`${words.common.remove}: ${personName(person)}`}
|
|
238
|
+
disabled={props.busy}
|
|
239
|
+
onClick={() =>
|
|
240
|
+
setDeciders((current) => current.filter((chosen) => chosen.id !== person.id))
|
|
241
|
+
}
|
|
242
|
+
>
|
|
243
|
+
<span aria-hidden="true">×</span>
|
|
244
|
+
</button>
|
|
245
|
+
</span>
|
|
246
|
+
))}
|
|
247
|
+
{deciders.length === 0 && <span className="sc-muted">{form.deciderPlaceholder}</span>}
|
|
248
|
+
{deciders.length < MAX_DECIDERS && (
|
|
249
|
+
<Picker
|
|
250
|
+
value={undefined}
|
|
251
|
+
search={findDeciders}
|
|
252
|
+
keyOf={(person) => person.id}
|
|
253
|
+
render={(person) => (
|
|
254
|
+
<>
|
|
255
|
+
<PersonChip person={person} size="sm" />
|
|
256
|
+
{person.id === props.me?.id && <span className="sc-muted"> {form.me}</span>}
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
label={form.addDecider}
|
|
260
|
+
placeholder={form.addDecider}
|
|
261
|
+
disabled={props.busy}
|
|
262
|
+
onChange={(person) => {
|
|
263
|
+
if (person !== undefined) {
|
|
264
|
+
setDeciders((current) => [...current, person]);
|
|
265
|
+
}
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
209
268
|
)}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
onChange={(person) => setDecider(person ?? props.me)}
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
269
|
+
</div>
|
|
270
|
+
{deciders.length > 1 && <p className="sc-field-hint">{form.decidersHint}</p>}
|
|
271
|
+
</fieldset>
|
|
216
272
|
)}
|
|
217
273
|
<fieldset className="sc-field sc-options">
|
|
218
274
|
<legend className="sc-field-label">{form.options}</legend>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type RestDecision,
|
|
9
9
|
type RestDecisionFile,
|
|
10
10
|
type RestDecisionPatch,
|
|
11
|
+
type RestPerson,
|
|
11
12
|
type RestTask,
|
|
12
13
|
} from "../api.js";
|
|
13
14
|
import { useWords } from "../i18n/index.js";
|
|
@@ -43,13 +44,34 @@ export interface DecisionCardProps {
|
|
|
43
44
|
readonly busy?: boolean | undefined;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** The word beside who answered: through their agent, when the answer came that way (ADR-0056). */
|
|
48
|
+
function answeredWord(words: ReturnType<typeof useWords>, agent: string | null | undefined) {
|
|
49
|
+
return agent === null || agent === undefined
|
|
50
|
+
? words.decisions.answeredBy
|
|
51
|
+
: words.decisions.answeredThrough(agentLabel(agent));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Everyone a decision is addressed to (ADR-0057): `deciders` where the console answers it, else the one decider. */
|
|
55
|
+
export function decidersOf(decision: RestDecision): readonly RestPerson[] {
|
|
56
|
+
return decision.deciders !== undefined && decision.deciders.length > 0
|
|
57
|
+
? decision.deciders
|
|
58
|
+
: [decision.decider];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The names of everyone a decision is addressed to, one after another. */
|
|
62
|
+
export function deciderNames(decision: RestDecision): string {
|
|
63
|
+
return decidersOf(decision).map(personName).join(", ");
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
/**
|
|
47
67
|
* Who asked, and when: the person, through their agent when a run asked; and whom the decision
|
|
48
|
-
* is for, where that is
|
|
68
|
+
* is for, where that is not the asker alone (ADR-0042, ADR-0057).
|
|
49
69
|
*/
|
|
50
70
|
function Asked(props: { readonly decision: RestDecision; readonly day?: boolean }) {
|
|
51
71
|
const words = useWords();
|
|
52
72
|
const { decision } = props;
|
|
73
|
+
const deciders = decidersOf(decision);
|
|
74
|
+
const addressed = deciders.some((person) => person.id !== decision.raisedBy.id);
|
|
53
75
|
return (
|
|
54
76
|
<>
|
|
55
77
|
<PersonChip person={decision.raisedBy} />{" "}
|
|
@@ -57,18 +79,17 @@ function Asked(props: { readonly decision: RestDecision; readonly day?: boolean
|
|
|
57
79
|
? words.decisions.asked
|
|
58
80
|
: words.decisions.askedThrough(agentLabel(decision.run.agent))}{" "}
|
|
59
81
|
<Time iso={decision.createdAt} precision={props.day === true ? "day" : "minute"} />
|
|
60
|
-
{
|
|
82
|
+
{addressed && (
|
|
61
83
|
<span className="sc-decision-for">
|
|
62
84
|
<span className="sc-visually-hidden">
|
|
63
|
-
{words.decisions.peopleLabel(
|
|
64
|
-
personName(decision.raisedBy),
|
|
65
|
-
personName(decision.decider),
|
|
66
|
-
)}
|
|
85
|
+
{words.decisions.peopleLabel(personName(decision.raisedBy), deciderNames(decision))}
|
|
67
86
|
</span>
|
|
68
87
|
<span className="sc-decision-arrow" aria-hidden="true">
|
|
69
88
|
→
|
|
70
89
|
</span>{" "}
|
|
71
|
-
|
|
90
|
+
{deciders.map((person) => (
|
|
91
|
+
<PersonChip key={person.id} person={person} />
|
|
92
|
+
))}
|
|
72
93
|
</span>
|
|
73
94
|
)}
|
|
74
95
|
</>
|
|
@@ -233,7 +254,20 @@ function Outcome(props: {
|
|
|
233
254
|
{decision.outcome === null ? (
|
|
234
255
|
<p className="sc-muted">{words.decisions.nothingWritten}</p>
|
|
235
256
|
) : (
|
|
236
|
-
|
|
257
|
+
<>
|
|
258
|
+
<Markdown source={decision.outcome} docHref={props.docHref} />
|
|
259
|
+
{decision.outcomeBy != null && decision.outcomeAt != null && (
|
|
260
|
+
<p className="sc-decision-meta">
|
|
261
|
+
<PersonChip person={decision.outcomeBy} />{" "}
|
|
262
|
+
{decision.outcomeAgent == null
|
|
263
|
+
? words.decisions.wroteWhatFollowed
|
|
264
|
+
: words.decisions.wroteWhatFollowedThrough(
|
|
265
|
+
agentLabel(decision.outcomeAgent),
|
|
266
|
+
)}{" "}
|
|
267
|
+
<Time iso={decision.outcomeAt} />
|
|
268
|
+
</p>
|
|
269
|
+
)}
|
|
270
|
+
</>
|
|
237
271
|
)}
|
|
238
272
|
{props.onUpdate !== undefined && (
|
|
239
273
|
<Button
|
|
@@ -368,14 +402,12 @@ export function DecisionCard(props: DecisionCardProps) {
|
|
|
368
402
|
</div>
|
|
369
403
|
)}
|
|
370
404
|
<p className="sc-decision-meta">
|
|
371
|
-
<PersonChip person={decision.answer.by} /> {words.
|
|
405
|
+
<PersonChip person={decision.answer.by} /> {answeredWord(words, decision.answer.agent)}{" "}
|
|
372
406
|
<Time iso={decision.answer.at} />
|
|
373
407
|
</p>
|
|
374
408
|
</div>
|
|
375
409
|
) : props.onAnswer === undefined ? (
|
|
376
|
-
<p className="sc-decision-waiting">
|
|
377
|
-
{words.decisions.waitingFor(personName(decision.decider))}
|
|
378
|
-
</p>
|
|
410
|
+
<p className="sc-decision-waiting">{words.decisions.waitingFor(deciderNames(decision))}</p>
|
|
379
411
|
) : (
|
|
380
412
|
<AnswerForm decision={decision} onAnswer={props.onAnswer} busy={props.busy} />
|
|
381
413
|
)}
|
|
@@ -474,7 +506,7 @@ function RecordRow(props: {
|
|
|
474
506
|
</div>
|
|
475
507
|
)}
|
|
476
508
|
<p className="sc-decision-record-meta">
|
|
477
|
-
<PersonChip person={answer.by} /> {words.
|
|
509
|
+
<PersonChip person={answer.by} /> {answeredWord(words, answer.agent)}{" "}
|
|
478
510
|
<Time iso={answer.at} precision="day" />
|
|
479
511
|
</p>
|
|
480
512
|
</div>
|
|
@@ -63,6 +63,10 @@ function sentenceOf(event: RestEvent, words: Messages): string {
|
|
|
63
63
|
return feed.event.personDeactivated(login);
|
|
64
64
|
case "person.reactivated":
|
|
65
65
|
return feed.event.personReactivated(login);
|
|
66
|
+
case "person.manager_changed":
|
|
67
|
+
return data.manager === null || data.manager === undefined
|
|
68
|
+
? feed.event.managerCleared(login)
|
|
69
|
+
: feed.event.managerChanged(login, data.manager);
|
|
66
70
|
case "workspace.updated":
|
|
67
71
|
return feed.event.workspaceUpdated(fields);
|
|
68
72
|
case "project.created":
|
|
@@ -136,7 +140,9 @@ function sentenceOf(event: RestEvent, words: Messages): string {
|
|
|
136
140
|
case "plugin.disabled":
|
|
137
141
|
return feed.event.pluginDisabled(plugin);
|
|
138
142
|
case "plugin.configured":
|
|
139
|
-
return
|
|
143
|
+
return data.capabilities === undefined
|
|
144
|
+
? feed.event.pluginConfigured(fields, plugin)
|
|
145
|
+
: feed.event.pluginConsented(plugin);
|
|
140
146
|
case "plugin.granted":
|
|
141
147
|
return feed.event.pluginGranted(data.role ?? feed.role.other, grantee, plugin);
|
|
142
148
|
case "plugin.revoked":
|
package/src/components/goals.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FormEvent, type ReactNode, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
agentLabel,
|
|
3
4
|
GOAL_STATES,
|
|
4
5
|
type GoalState,
|
|
5
6
|
MAX_BODY_LENGTH,
|
|
@@ -355,7 +356,9 @@ export function GoalView(props: GoalViewProps) {
|
|
|
355
356
|
<div>
|
|
356
357
|
<dt>{roadmap.setBy}</dt>
|
|
357
358
|
<dd>
|
|
358
|
-
<PersonChip person={goal.createdBy} />
|
|
359
|
+
<PersonChip person={goal.createdBy} />{" "}
|
|
360
|
+
{goal.agent != null && <>{roadmap.setThrough(agentLabel(goal.agent))} </>}
|
|
361
|
+
<Time iso={goal.createdAt} />
|
|
359
362
|
</dd>
|
|
360
363
|
</div>
|
|
361
364
|
{props.editHref !== undefined && (
|