@skillcdn/console 0.4.3 → 0.4.5
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 +6 -6
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +255 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +20 -5
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +98 -26
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +39 -15
- 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 +32 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -2
- 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 +49 -18
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -3
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -18
- 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 +34 -8
- 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/my-decisions.d.ts +20 -3
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +14 -1
- package/dist/components/my-decisions.js.map +1 -1
- 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 +45 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +125 -4
- package/dist/components/people.js.map +1 -1
- package/dist/components/picker.d.ts +8 -0
- package/dist/components/picker.d.ts.map +1 -1
- package/dist/components/picker.js +2 -2
- package/dist/components/picker.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -13
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +75 -21
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts +5 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +283 -45
- 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 +4 -14
- package/dist/components/projects.js.map +1 -1
- package/dist/components/searches.d.ts +11 -0
- package/dist/components/searches.d.ts.map +1 -0
- package/dist/components/searches.js +30 -0
- package/dist/components/searches.js.map +1 -0
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +6 -5
- package/dist/components/task-form.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 +16 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -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 +1123 -258
- package/dist/console.d.ts +12 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +123 -27
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +18 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +41 -1
- 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 +264 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +260 -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 +255 -4
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +12 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +12 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +6 -1
- package/dist/plugin/args.d.ts.map +1 -1
- package/dist/plugin/args.js +16 -11
- package/dist/plugin/args.js.map +1 -1
- package/dist/plugin/bridge.d.ts +63 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +54 -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 +24 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +174 -12
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +14 -1
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +18 -2
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +9 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +9 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +98 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +171 -10
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +120 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +78 -5
- 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 +15 -1
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +42 -5
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +16 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +44 -1
- 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/router.d.ts +19 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +11 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +14 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +864 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +156 -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 +9 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +10 -6
- package/docs/specs/plugins.md +83 -62
- package/docs/specs/rest.md +34 -16
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +15 -12
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +23 -0
- package/src/cli/cli.ts +323 -30
- package/src/cli/format.ts +122 -26
- package/src/cli/help.ts +43 -14
- 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 +78 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +86 -45
- package/src/components/decision-form.tsx +73 -36
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +40 -7
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +50 -5
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +500 -61
- package/src/components/picker.tsx +11 -2
- package/src/components/plugin-frame.tsx +85 -63
- package/src/components/plugin-settings.tsx +735 -185
- package/src/components/projects.tsx +14 -69
- package/src/components/searches.ts +47 -0
- package/src/components/task-form.tsx +23 -28
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +42 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +303 -64
- package/src/data.ts +62 -3
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +274 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +267 -4
- package/src/index.ts +18 -5
- package/src/limits.ts +12 -0
- package/src/plugin/args.ts +16 -11
- package/src/plugin/bridge.ts +122 -1
- package/src/plugin/capabilities.ts +28 -1
- package/src/plugin/context.ts +186 -9
- package/src/plugin/define.ts +38 -1
- package/src/plugin/limits.ts +9 -2
- package/src/plugin/manifest.ts +228 -13
- package/src/plugin/rest-schemas.ts +89 -6
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +63 -6
- package/src/plugin/web.ts +66 -0
- package/src/plugin.ts +19 -0
- package/src/router.ts +25 -0
- package/src/routes.ts +17 -2
- package/src/rules.ts +3 -3
- package/src/schemas.ts +197 -10
- package/src/styles/console.css +1123 -258
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +10 -0
- package/template/plugin/console-plugin.json +3 -1
package/src/client.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
CLAIM_PATH,
|
|
4
4
|
categoryPath,
|
|
5
5
|
connectPath,
|
|
6
|
+
decisionFilePath,
|
|
7
|
+
decisionPath,
|
|
6
8
|
personTokensPath,
|
|
7
9
|
personWorkPath,
|
|
8
10
|
pluginPath,
|
|
@@ -73,6 +75,7 @@ import {
|
|
|
73
75
|
type RestProjectPatch,
|
|
74
76
|
type RestProjectRules,
|
|
75
77
|
type RestProjects,
|
|
78
|
+
type RestReachedDecision,
|
|
76
79
|
type RestReportInput,
|
|
77
80
|
type RestRules,
|
|
78
81
|
type RestRulesInput,
|
|
@@ -89,6 +92,10 @@ import {
|
|
|
89
92
|
type RestTaskInput,
|
|
90
93
|
type RestTaskPatch,
|
|
91
94
|
type RestTasks,
|
|
95
|
+
type RestTeam,
|
|
96
|
+
type RestTeamInput,
|
|
97
|
+
type RestTeamPatch,
|
|
98
|
+
type RestTeams,
|
|
92
99
|
type RestTokenCreated,
|
|
93
100
|
type RestTokenInput,
|
|
94
101
|
type RestTokens,
|
|
@@ -137,6 +144,7 @@ import {
|
|
|
137
144
|
restProjectRulesSchema,
|
|
138
145
|
restProjectSchema,
|
|
139
146
|
restProjectsSchema,
|
|
147
|
+
restReachedDecisionSchema,
|
|
140
148
|
restRulesSchema,
|
|
141
149
|
restRunSchema,
|
|
142
150
|
restRunsSchema,
|
|
@@ -147,6 +155,8 @@ import {
|
|
|
147
155
|
restSkillsSchema,
|
|
148
156
|
restTaskSchema,
|
|
149
157
|
restTasksSchema,
|
|
158
|
+
restTeamSchema,
|
|
159
|
+
restTeamsSchema,
|
|
150
160
|
restTokenCreatedSchema,
|
|
151
161
|
restTokensSchema,
|
|
152
162
|
restVersionSchema,
|
|
@@ -498,6 +508,8 @@ export interface PluginClient {
|
|
|
498
508
|
fileUrl(fileId: string): string;
|
|
499
509
|
/** The bytes of a record's file, with the name and the type the console says. */
|
|
500
510
|
readFile(fileId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
511
|
+
/** A person's picture, for a frame, which names the person and never an address (ADR-0063). */
|
|
512
|
+
personPicture(personId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
501
513
|
/** Where the frame of a page or a widget is (ADR-0054), on a project when its key is given. */
|
|
502
514
|
frameUrl(options: {
|
|
503
515
|
readonly page?: string | undefined;
|
|
@@ -543,6 +555,8 @@ export interface PluginClient {
|
|
|
543
555
|
disable(): Promise<RestPluginDetail>;
|
|
544
556
|
/** Changes the settings' values, without secrets; `null` clears one. */
|
|
545
557
|
updateSettings(patch: RestPluginSettingsPatch): Promise<RestPluginDetail>;
|
|
558
|
+
/** Says what an enabled plugin holds: those it requires, named again, and the optional ones to turn on. */
|
|
559
|
+
updateConsent(consent: readonly string[]): Promise<RestPluginDetail>;
|
|
546
560
|
/** Seals a secret for the plugin; it is answered by nothing from then on. */
|
|
547
561
|
setSecret(name: string, value: string): Promise<void>;
|
|
548
562
|
removeSecret(name: string): Promise<void>;
|
|
@@ -584,6 +598,17 @@ export interface ConsoleClient {
|
|
|
584
598
|
createListing(input: RestListingInput): Promise<RestListing>;
|
|
585
599
|
/** Takes a listing away before anyone signed in with it; for an administrator. */
|
|
586
600
|
removeListing(id: string): Promise<void>;
|
|
601
|
+
/** The teams of the workspace (ADR-0073), each with the members the asker may see. */
|
|
602
|
+
teams(signal?: AbortSignal): Promise<RestTeams>;
|
|
603
|
+
/**
|
|
604
|
+
* Makes, renames and removes a team, and puts a person in one or takes them out; for an
|
|
605
|
+
* administrator. A team is called by its key (ADR-0074), which never changes.
|
|
606
|
+
*/
|
|
607
|
+
createTeam(input: RestTeamInput): Promise<RestTeam>;
|
|
608
|
+
renameTeam(key: string, patch: RestTeamPatch): Promise<RestTeam>;
|
|
609
|
+
removeTeam(key: string): Promise<void>;
|
|
610
|
+
addTeamMember(key: string, personId: string): Promise<RestTeam>;
|
|
611
|
+
removeTeamMember(key: string, personId: string): Promise<RestTeam>;
|
|
587
612
|
/** The projects the asker may see, with what they are in each. */
|
|
588
613
|
projects(signal?: AbortSignal): Promise<RestProjects>;
|
|
589
614
|
/** Makes a project, whose owner the asker becomes. */
|
|
@@ -620,6 +645,26 @@ export interface ConsoleClient {
|
|
|
620
645
|
filter?: { readonly open?: boolean; readonly raised?: boolean } & Page,
|
|
621
646
|
signal?: AbortSignal,
|
|
622
647
|
): Promise<RestMyDecisions>;
|
|
648
|
+
/**
|
|
649
|
+
* One decision by its id, wherever it is (ADR-0068): in a project the asker may see, or one
|
|
650
|
+
* that reaches them from elsewhere, addressed to them, raised by them, or a plugin's addressed
|
|
651
|
+
* to someone they manage; with its project and whether they may answer it.
|
|
652
|
+
*/
|
|
653
|
+
decision(id: string, signal?: AbortSignal): Promise<RestReachedDecision>;
|
|
654
|
+
/** The same, after waiting up to `waitSeconds` for its answer, as a project's `awaitDecision` waits. */
|
|
655
|
+
awaitDecision(
|
|
656
|
+
id: string,
|
|
657
|
+
waitSeconds: number,
|
|
658
|
+
signal?: AbortSignal,
|
|
659
|
+
): Promise<RestReachedDecision>;
|
|
660
|
+
/** Answers a decision wherever it is, as whoever it reaches with the right to answer (ADR-0068). */
|
|
661
|
+
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
662
|
+
/**
|
|
663
|
+
* Where the bytes of a file put on a decision (ADR-0055) are read at the decision's own address,
|
|
664
|
+
* by the decision's id and the file's: for whoever the decision reaches, in a project they may
|
|
665
|
+
* see or not (ADR-0068), as the project's `fileUrl` is for whoever may see the project.
|
|
666
|
+
*/
|
|
667
|
+
decisionFileUrl(decisionId: string, fileId: string): string;
|
|
623
668
|
/** Another person's plate, within what the asker may see: for an administrator, or an owner of a project they are in. */
|
|
624
669
|
personWork(personId: string, signal?: AbortSignal): Promise<RestPersonWork>;
|
|
625
670
|
/** The tokens of whoever asks. */
|
|
@@ -1143,6 +1188,8 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1143
1188
|
),
|
|
1144
1189
|
fileUrl: (fileId) => `${base}${at("files", fileId)}`,
|
|
1145
1190
|
readFile: (fileId, signal) => read(`${base}${at("files", fileId)}`, fileId, signal),
|
|
1191
|
+
personPicture: (personId, signal) =>
|
|
1192
|
+
read(`${base}${at("people", personId, "picture")}`, personId, signal),
|
|
1146
1193
|
frameUrl: (options) =>
|
|
1147
1194
|
withQuery(at("frame"), {
|
|
1148
1195
|
page: options.page,
|
|
@@ -1213,6 +1260,8 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1213
1260
|
disable: () => request("POST", `${base}${at("disable")}`, undefined, restPluginDetailSchema),
|
|
1214
1261
|
updateSettings: (patch) =>
|
|
1215
1262
|
request("PATCH", `${base}${at("settings")}`, patch, restPluginDetailSchema),
|
|
1263
|
+
updateConsent: (consent) =>
|
|
1264
|
+
request("PATCH", `${base}${at("consent")}`, { consent }, restPluginDetailSchema),
|
|
1216
1265
|
setSecret: (name, value) =>
|
|
1217
1266
|
request("PUT", `${base}${at("secrets", name)}`, { value }, undefined),
|
|
1218
1267
|
removeSecret: (name) =>
|
|
@@ -1277,6 +1326,22 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1277
1326
|
request("POST", `${base}${REST_ROUTES.listings}`, input, restListingSchema),
|
|
1278
1327
|
removeListing: (id) =>
|
|
1279
1328
|
request("DELETE", `${base}${restPath("listings", id)}`, undefined, undefined),
|
|
1329
|
+
teams: (signal) =>
|
|
1330
|
+
request("GET", `${base}${REST_ROUTES.teams}`, undefined, restTeamsSchema, signal),
|
|
1331
|
+
createTeam: (input) => request("POST", `${base}${REST_ROUTES.teams}`, input, restTeamSchema),
|
|
1332
|
+
renameTeam: (key, patch) =>
|
|
1333
|
+
request("PATCH", `${base}${restPath("teams", key)}`, patch, restTeamSchema),
|
|
1334
|
+
removeTeam: (key) =>
|
|
1335
|
+
request("DELETE", `${base}${restPath("teams", key)}`, undefined, undefined),
|
|
1336
|
+
addTeamMember: (key, personId) =>
|
|
1337
|
+
request("POST", `${base}${restPath("teams", key)}/members`, { personId }, restTeamSchema),
|
|
1338
|
+
removeTeamMember: (key, personId) =>
|
|
1339
|
+
request(
|
|
1340
|
+
"DELETE",
|
|
1341
|
+
`${base}${restPath("teams", key)}/members/${encodeURIComponent(personId)}`,
|
|
1342
|
+
undefined,
|
|
1343
|
+
restTeamSchema,
|
|
1344
|
+
),
|
|
1280
1345
|
projects: (signal) =>
|
|
1281
1346
|
request("GET", `${base}${REST_ROUTES.projects}`, undefined, restProjectsSchema, signal),
|
|
1282
1347
|
createProject: (input) =>
|
|
@@ -1323,6 +1388,19 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
1323
1388
|
restMyDecisionsSchema,
|
|
1324
1389
|
signal,
|
|
1325
1390
|
),
|
|
1391
|
+
decision: (id, signal) =>
|
|
1392
|
+
request("GET", `${base}${decisionPath(id)}`, undefined, restReachedDecisionSchema, signal),
|
|
1393
|
+
awaitDecision: (id, waitSeconds, signal) =>
|
|
1394
|
+
request(
|
|
1395
|
+
"GET",
|
|
1396
|
+
withQuery(decisionPath(id), { wait: String(waitSeconds) }),
|
|
1397
|
+
undefined,
|
|
1398
|
+
restReachedDecisionSchema,
|
|
1399
|
+
signal,
|
|
1400
|
+
),
|
|
1401
|
+
answerDecision: (id, input) =>
|
|
1402
|
+
request("POST", `${base}${decisionPath(id)}/answer`, input, restDecisionSchema),
|
|
1403
|
+
decisionFileUrl: (decisionId, fileId) => `${base}${decisionFilePath(decisionId, fileId)}`,
|
|
1326
1404
|
personWork: (personId, signal) =>
|
|
1327
1405
|
request("GET", `${base}${personWorkPath(personId)}`, undefined, restPersonWorkSchema, signal),
|
|
1328
1406
|
tokens: (signal) =>
|
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.5" };
|
|
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
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
} from "../api.js";
|
|
19
19
|
import { useWords } from "../i18n/index.js";
|
|
20
20
|
import type { BoardFilter } from "../router.js";
|
|
21
|
+
import { Picker } from "./picker.js";
|
|
22
|
+
import { listSearch } from "./searches.js";
|
|
21
23
|
import { isPlainClick } from "./shell.js";
|
|
22
24
|
import {
|
|
23
25
|
Badge,
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
CategoryBadge,
|
|
26
28
|
cx,
|
|
27
29
|
DueBadge,
|
|
30
|
+
PersonChip,
|
|
28
31
|
PriorityBadge,
|
|
29
32
|
personName,
|
|
30
33
|
Workers,
|
|
@@ -71,13 +74,13 @@ export interface BoardProps {
|
|
|
71
74
|
export const FOLDED_STATES: readonly TaskState[] = ["done", "dropped"];
|
|
72
75
|
|
|
73
76
|
/** How far a mouse travels on a card before the press is a drag and not a click. */
|
|
74
|
-
const DRAG_SLACK = 4;
|
|
77
|
+
export const DRAG_SLACK = 4;
|
|
75
78
|
/**
|
|
76
79
|
* How long a finger rests on a card before the card comes loose, and how far it may wander
|
|
77
80
|
* meanwhile; a finger that moves sooner is scrolling the page, not moving the card.
|
|
78
81
|
*/
|
|
79
|
-
const HOLD_MS = 300;
|
|
80
|
-
const HOLD_SLACK = 8;
|
|
82
|
+
export const HOLD_MS = 300;
|
|
83
|
+
export const HOLD_SLACK = 8;
|
|
81
84
|
/** How near the pointer comes to the board's edge before the board scrolls to show more columns. */
|
|
82
85
|
const EDGE = 48;
|
|
83
86
|
/** How fast the board scrolls, in pixels a second, with the pointer at the edge itself; slower further in. */
|
|
@@ -727,6 +730,13 @@ export function filterTasks(
|
|
|
727
730
|
});
|
|
728
731
|
}
|
|
729
732
|
|
|
733
|
+
/** A goal to narrow the board to, or no goal at all. */
|
|
734
|
+
type GoalChoice = { readonly kind: "none" } | { readonly kind: "goal"; readonly goal: RestGoal };
|
|
735
|
+
/** A person to narrow the board to, or whoever is looking. */
|
|
736
|
+
type PersonChoice =
|
|
737
|
+
| { readonly kind: "mine" }
|
|
738
|
+
| { readonly kind: "person"; readonly person: RestPerson };
|
|
739
|
+
|
|
730
740
|
export interface BoardFiltersProps {
|
|
731
741
|
readonly filter: BoardFilter;
|
|
732
742
|
readonly categories: readonly RestCategory[];
|
|
@@ -754,6 +764,44 @@ export function BoardFilters(props: BoardFiltersProps) {
|
|
|
754
764
|
}
|
|
755
765
|
props.onChange(next as BoardFilter);
|
|
756
766
|
};
|
|
767
|
+
// The goal and the person are searched for (ADR-0037), among the page's own; what the address
|
|
768
|
+
// names and the page does not hold is held as it is, so that the filter still shows.
|
|
769
|
+
const goalChoices: readonly GoalChoice[] = [
|
|
770
|
+
{ kind: "none" },
|
|
771
|
+
...goals.map((goal) => ({ kind: "goal" as const, goal })),
|
|
772
|
+
];
|
|
773
|
+
const chosenGoal = goals.find((goal) => goal.id === filter.goal);
|
|
774
|
+
const goalChoice: GoalChoice | undefined =
|
|
775
|
+
filter.goal === undefined
|
|
776
|
+
? undefined
|
|
777
|
+
: filter.goal === ""
|
|
778
|
+
? { kind: "none" }
|
|
779
|
+
: chosenGoal === undefined
|
|
780
|
+
? undefined
|
|
781
|
+
: { kind: "goal", goal: chosenGoal };
|
|
782
|
+
const goalWords = (choice: GoalChoice) =>
|
|
783
|
+
choice.kind === "none" ? words.noGoal : choice.goal.title;
|
|
784
|
+
const findGoal = listSearch(goalChoices, (choice, typed) =>
|
|
785
|
+
goalWords(choice).toLowerCase().includes(typed),
|
|
786
|
+
);
|
|
787
|
+
const personChoices: readonly PersonChoice[] = [
|
|
788
|
+
...(props.me === undefined ? [] : [{ kind: "mine" as const }]),
|
|
789
|
+
...props.people
|
|
790
|
+
.filter((person) => person.id !== props.me?.id)
|
|
791
|
+
.map((person) => ({ kind: "person" as const, person })),
|
|
792
|
+
];
|
|
793
|
+
const chosenPerson = props.people.find((person) => person.id === filter.person);
|
|
794
|
+
const personChoice: PersonChoice | undefined =
|
|
795
|
+
filter.mine === true
|
|
796
|
+
? { kind: "mine" }
|
|
797
|
+
: filter.person === undefined || chosenPerson === undefined
|
|
798
|
+
? undefined
|
|
799
|
+
: { kind: "person", person: chosenPerson };
|
|
800
|
+
const personWords = (choice: PersonChoice) =>
|
|
801
|
+
choice.kind === "mine" ? words.mine : `${personName(choice.person)} ${choice.person.login}`;
|
|
802
|
+
const findPerson = listSearch(personChoices, (choice, typed) =>
|
|
803
|
+
personWords(choice).toLowerCase().includes(typed),
|
|
804
|
+
);
|
|
757
805
|
return (
|
|
758
806
|
<form
|
|
759
807
|
className="sc-filters"
|
|
@@ -795,54 +843,47 @@ export function BoardFilters(props: BoardFiltersProps) {
|
|
|
795
843
|
</select>
|
|
796
844
|
</label>
|
|
797
845
|
{goals.length > 0 && (
|
|
798
|
-
<
|
|
799
|
-
<
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
846
|
+
<div className="sc-field sc-field-inline">
|
|
847
|
+
<Picker
|
|
848
|
+
value={goalChoice}
|
|
849
|
+
held={goalChoice === undefined && filter.goal !== undefined ? filter.goal : undefined}
|
|
850
|
+
search={findGoal}
|
|
851
|
+
keyOf={(choice) => (choice.kind === "none" ? "" : choice.goal.id)}
|
|
852
|
+
render={goalWords}
|
|
853
|
+
label={words.anyGoal}
|
|
854
|
+
placeholder={words.anyGoal}
|
|
855
|
+
onChange={(choice) =>
|
|
856
|
+
choice === undefined
|
|
857
|
+
? change({}, "goal")
|
|
858
|
+
: change({ goal: choice.kind === "none" ? "" : choice.goal.id })
|
|
805
859
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
<option value="">{words.noGoal}</option>
|
|
809
|
-
{goals.map((goal) => (
|
|
810
|
-
<option key={goal.id} value={goal.id}>
|
|
811
|
-
{goal.title}
|
|
812
|
-
</option>
|
|
813
|
-
))}
|
|
814
|
-
</select>
|
|
815
|
-
</label>
|
|
860
|
+
/>
|
|
861
|
+
</div>
|
|
816
862
|
)}
|
|
817
|
-
<
|
|
818
|
-
<
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
863
|
+
<div className="sc-field sc-field-inline">
|
|
864
|
+
<Picker
|
|
865
|
+
value={personChoice}
|
|
866
|
+
held={
|
|
867
|
+
personChoice === undefined && filter.person !== undefined ? filter.person : undefined
|
|
868
|
+
}
|
|
869
|
+
search={findPerson}
|
|
870
|
+
keyOf={(choice) => (choice.kind === "mine" ? "mine" : choice.person.id)}
|
|
871
|
+
render={(choice) =>
|
|
872
|
+
choice.kind === "mine" ? words.mine : <PersonChip person={choice.person} size="sm" />
|
|
873
|
+
}
|
|
874
|
+
label={words.everyone}
|
|
875
|
+
placeholder={words.everyone}
|
|
876
|
+
onChange={(choice) => {
|
|
877
|
+
if (choice === undefined) {
|
|
825
878
|
change({ mine: undefined, person: undefined });
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
if (value === "mine") {
|
|
879
|
+
} else if (choice.kind === "mine") {
|
|
829
880
|
change({ mine: true, person: undefined });
|
|
830
|
-
|
|
881
|
+
} else {
|
|
882
|
+
change({ person: choice.person.id, mine: undefined });
|
|
831
883
|
}
|
|
832
|
-
change({ person: value, mine: undefined });
|
|
833
884
|
}}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
{props.me !== undefined && <option value="mine">{words.mine}</option>}
|
|
837
|
-
{props.people
|
|
838
|
-
.filter((person) => person.id !== props.me?.id)
|
|
839
|
-
.map((person) => (
|
|
840
|
-
<option key={person.id} value={person.id}>
|
|
841
|
-
{personName(person)}
|
|
842
|
-
</option>
|
|
843
|
-
))}
|
|
844
|
-
</select>
|
|
845
|
-
</label>
|
|
885
|
+
/>
|
|
886
|
+
</div>
|
|
846
887
|
<span className="sc-filters-count sc-muted">{words.showing(props.shown, props.total)}</span>
|
|
847
888
|
{narrowed && (
|
|
848
889
|
<Button variant="ghost" size="sm" onClick={() => props.onChange({})}>
|
|
@@ -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,
|
|
@@ -12,9 +13,10 @@ import {
|
|
|
12
13
|
} from "../api.js";
|
|
13
14
|
import { useWords } from "../i18n/index.js";
|
|
14
15
|
import { isPicture, placing, unusedName } from "./files.js";
|
|
15
|
-
import { Picker
|
|
16
|
+
import { Picker } from "./picker.js";
|
|
17
|
+
import { peopleSearch } from "./searches.js";
|
|
16
18
|
import { type TaskSearch, taskLabel } from "./task-form.js";
|
|
17
|
-
import { Button, formatBytes, PersonChip, personName } from "./ui.js";
|
|
19
|
+
import { Button, Chip, formatBytes, PersonChip, personName } from "./ui.js";
|
|
18
20
|
|
|
19
21
|
// Raising a decision: the question, what a person needs to know, the options to choose from,
|
|
20
22
|
// and who is to answer (ADR-0042): the person raising it, unless they name another of the
|
|
@@ -39,21 +41,21 @@ export interface DecisionFormProps {
|
|
|
39
41
|
readonly onCancel?: (() => void) | undefined;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
44
|
+
/**
|
|
45
|
+
* What the form sends of whom the decision is for: nothing for the raiser alone, `deciderId` for
|
|
46
|
+
* one other person, `deciderIds` for several (ADR-0057), the first among them repeated as
|
|
47
|
+
* `deciderId` for a console of before.
|
|
48
|
+
*/
|
|
49
|
+
export function decidersInput(
|
|
50
|
+
deciders: readonly RestPerson[],
|
|
51
|
+
me: RestPerson | undefined,
|
|
52
|
+
): Pick<RestDecisionInput, "deciderId" | "deciderIds"> {
|
|
53
|
+
const ids = deciders.map((person) => person.id);
|
|
54
|
+
if (ids.length === 0 || (ids.length === 1 && ids[0] === me?.id)) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
const first = ids[0] ?? "";
|
|
58
|
+
return ids.length === 1 ? { deciderId: first } : { deciderId: first, deciderIds: ids };
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export function DecisionForm(props: DecisionFormProps) {
|
|
@@ -63,9 +65,20 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
63
65
|
const [body, setBody] = useState("");
|
|
64
66
|
const [about, setAbout] = useState<RestTask | undefined>(undefined);
|
|
65
67
|
const [options, setOptions] = useState<readonly string[]>(["", ""]);
|
|
66
|
-
|
|
68
|
+
// Whom it is for (ADR-0042, ADR-0057): the raiser, until others are named in their place.
|
|
69
|
+
const [deciders, setDeciders] = useState<readonly RestPerson[]>(
|
|
70
|
+
props.me === undefined ? [] : [props.me],
|
|
71
|
+
);
|
|
67
72
|
const [files, setFiles] = useState<readonly File[]>([]);
|
|
68
|
-
const findDeciders = useMemo(
|
|
73
|
+
const findDeciders = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
peopleSearch(
|
|
76
|
+
(props.deciders ?? []).filter(
|
|
77
|
+
(person) => !deciders.some((chosen) => chosen.id === person.id),
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
[props.deciders, deciders],
|
|
81
|
+
);
|
|
69
82
|
|
|
70
83
|
const labels = options.map((option) => option.trim()).filter((option) => option.length > 0);
|
|
71
84
|
const ready = question.trim().length > 0 && labels.length >= MIN_OPTIONS;
|
|
@@ -115,7 +128,7 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
115
128
|
body,
|
|
116
129
|
options: labels.map((label) => ({ label })),
|
|
117
130
|
taskId: props.taskId ?? about?.id ?? null,
|
|
118
|
-
...(
|
|
131
|
+
...decidersInput(deciders, props.me),
|
|
119
132
|
},
|
|
120
133
|
files,
|
|
121
134
|
);
|
|
@@ -195,24 +208,48 @@ export function DecisionForm(props: DecisionFormProps) {
|
|
|
195
208
|
</div>
|
|
196
209
|
)}
|
|
197
210
|
{props.deciders !== undefined && (
|
|
198
|
-
<
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
<fieldset className="sc-field sc-workers-field">
|
|
212
|
+
<legend className="sc-field-label">{form.decider}</legend>
|
|
213
|
+
<div className="sc-chips">
|
|
214
|
+
{deciders.map((person) => (
|
|
215
|
+
<Chip
|
|
216
|
+
key={person.id}
|
|
217
|
+
onRemove={() =>
|
|
218
|
+
setDeciders((current) => current.filter((chosen) => chosen.id !== person.id))
|
|
219
|
+
}
|
|
220
|
+
removeLabel={`${words.common.remove}: ${personName(person)}`}
|
|
221
|
+
disabled={props.busy}
|
|
222
|
+
>
|
|
223
|
+
<PersonChip person={person} />
|
|
207
224
|
{person.id === props.me?.id && <span className="sc-muted"> {form.me}</span>}
|
|
208
|
-
|
|
225
|
+
</Chip>
|
|
226
|
+
))}
|
|
227
|
+
{deciders.length === 0 && <span className="sc-muted">{form.deciderPlaceholder}</span>}
|
|
228
|
+
{deciders.length < MAX_DECIDERS && (
|
|
229
|
+
<Picker
|
|
230
|
+
className="sc-picker-add"
|
|
231
|
+
value={undefined}
|
|
232
|
+
search={findDeciders}
|
|
233
|
+
keyOf={(person) => person.id}
|
|
234
|
+
render={(person) => (
|
|
235
|
+
<>
|
|
236
|
+
<PersonChip person={person} size="sm" />
|
|
237
|
+
{person.id === props.me?.id && <span className="sc-muted"> {form.me}</span>}
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
label={form.addDecider}
|
|
241
|
+
placeholder={form.addDecider}
|
|
242
|
+
disabled={props.busy}
|
|
243
|
+
onChange={(person) => {
|
|
244
|
+
if (person !== undefined) {
|
|
245
|
+
setDeciders((current) => [...current, person]);
|
|
246
|
+
}
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
209
249
|
)}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
onChange={(person) => setDecider(person ?? props.me)}
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
250
|
+
</div>
|
|
251
|
+
{deciders.length > 1 && <p className="sc-field-hint">{form.decidersHint}</p>}
|
|
252
|
+
</fieldset>
|
|
216
253
|
)}
|
|
217
254
|
<fieldset className="sc-field sc-options">
|
|
218
255
|
<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>
|