@skillcdn/console 0.3.0 → 0.3.2
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 +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 +5 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +290 -46
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/credentials.d.ts +5 -0
- package/dist/cli/credentials.d.ts.map +1 -1
- package/dist/cli/credentials.js +2 -0
- package/dist/cli/credentials.js.map +1 -1
- package/dist/cli/directory.d.ts +7 -0
- package/dist/cli/directory.d.ts.map +1 -1
- package/dist/cli/directory.js +7 -0
- package/dist/cli/directory.js.map +1 -1
- package/dist/cli/format.d.ts +10 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +76 -20
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +42 -23
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +39 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -3
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +28 -6
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +55 -14
- package/dist/compat.js.map +1 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +6 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +18 -8
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +39 -27
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/pack-connect.d.ts +23 -0
- package/dist/components/pack-connect.d.ts.map +1 -0
- package/dist/components/pack-connect.js +86 -0
- package/dist/components/pack-connect.js.map +1 -0
- package/dist/components/pack-token.d.ts +27 -0
- package/dist/components/pack-token.d.ts.map +1 -0
- package/dist/components/pack-token.js +11 -0
- package/dist/components/pack-token.js.map +1 -0
- package/dist/components/picker.d.ts +39 -0
- package/dist/components/picker.d.ts.map +1 -0
- package/dist/components/picker.js +155 -0
- package/dist/components/picker.js.map +1 -0
- package/dist/components/projects.d.ts +13 -0
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-form.d.ts +18 -4
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +56 -13
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-list.js +2 -2
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts +7 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +6 -5
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +7 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +11 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +3 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +6 -4
- package/dist/components/work.js.map +1 -1
- package/dist/components/workspace.d.ts +46 -0
- package/dist/components/workspace.d.ts.map +1 -0
- package/dist/components/workspace.js +124 -0
- package/dist/components/workspace.js.map +1 -0
- package/dist/console.css +319 -11
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +83 -24
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +26 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +59 -1
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +12 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +20 -2
- package/dist/dates.js.map +1 -1
- package/dist/i18n/en.d.ts +64 -3
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +81 -16
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +77 -16
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +4 -4
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +11 -2
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +4 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +194 -13
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +121 -21
- 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 +18 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +25 -20
- package/docs/specs/rest.md +42 -19
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +6 -6
- package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
- package/skills/deploying-a-console/SKILL.md +9 -8
- package/skills/deploying-a-console/references/resources.md +1 -1
- package/skills/working-the-board/SKILL.md +5 -5
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +3 -1
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +21 -0
- package/src/cli/cli.ts +384 -51
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +83 -20
- package/src/cli/help.ts +50 -23
- package/src/cli/main.ts +1 -0
- package/src/client.ts +107 -10
- package/src/compat.ts +63 -14
- package/src/components/board.tsx +10 -4
- package/src/components/calendar.tsx +76 -34
- package/src/components/event-feed.tsx +2 -0
- package/src/components/pack-connect.tsx +179 -0
- package/src/components/pack-token.tsx +74 -0
- package/src/components/picker.tsx +297 -0
- package/src/components/projects.tsx +108 -9
- package/src/components/task-form.tsx +149 -63
- package/src/components/task-list.tsx +2 -2
- package/src/components/task-view.tsx +23 -9
- package/src/components/ui.tsx +25 -0
- package/src/components/work.tsx +10 -5
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +202 -32
- package/src/data.ts +101 -1
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +92 -16
- package/src/i18n/ko.ts +84 -16
- package/src/index.ts +32 -1
- package/src/limits.ts +2 -0
- package/src/router.ts +17 -5
- package/src/routes.ts +4 -0
- package/src/schemas.ts +140 -19
- package/src/styles/console.css +319 -11
- package/src/vocabulary.ts +28 -0
package/src/client.ts
CHANGED
|
@@ -44,6 +44,8 @@ import {
|
|
|
44
44
|
type RestMemberPatch,
|
|
45
45
|
type RestMembers,
|
|
46
46
|
type RestMePatch,
|
|
47
|
+
type RestPackConnect,
|
|
48
|
+
type RestPackConnectStarted,
|
|
47
49
|
type RestPackTokenInput,
|
|
48
50
|
type RestPeople,
|
|
49
51
|
type RestPerson,
|
|
@@ -76,6 +78,9 @@ import {
|
|
|
76
78
|
type RestVersion,
|
|
77
79
|
type RestVersions,
|
|
78
80
|
type RestWork,
|
|
81
|
+
type RestWorkspace,
|
|
82
|
+
type RestWorkspacePackConnect,
|
|
83
|
+
type RestWorkspacePatch,
|
|
79
84
|
restArchivedTasksSchema,
|
|
80
85
|
restCategoriesSchema,
|
|
81
86
|
restCategorySchema,
|
|
@@ -96,6 +101,8 @@ import {
|
|
|
96
101
|
restMemberSchema,
|
|
97
102
|
restMembersSchema,
|
|
98
103
|
restMeSchema,
|
|
104
|
+
restPackConnectSchema,
|
|
105
|
+
restPackConnectStartedSchema,
|
|
99
106
|
restPeopleSchema,
|
|
100
107
|
restPersonSchema,
|
|
101
108
|
restPersonWorkSchema,
|
|
@@ -117,6 +124,8 @@ import {
|
|
|
117
124
|
restVersionSchema,
|
|
118
125
|
restVersionsSchema,
|
|
119
126
|
restWorkSchema,
|
|
127
|
+
restWorkspacePackConnectSchema,
|
|
128
|
+
restWorkspaceSchema,
|
|
120
129
|
} from "./schemas.js";
|
|
121
130
|
import type { GoalState, TaskState } from "./vocabulary.js";
|
|
122
131
|
|
|
@@ -205,14 +214,18 @@ export interface Page {
|
|
|
205
214
|
readonly offset?: number | undefined;
|
|
206
215
|
}
|
|
207
216
|
|
|
208
|
-
/** What the
|
|
209
|
-
|
|
210
|
-
export interface GoalFilter {
|
|
217
|
+
/** What the roadmap is narrowed by: the goals in one state, the open ones, those sorted into one category (ADR-0026), or those whose title has some words; and which page of it. */
|
|
218
|
+
export interface GoalFilter extends Page {
|
|
211
219
|
readonly state?: GoalState | undefined;
|
|
220
|
+
/** True for the goals planned or active; false for the reached and the dropped. */
|
|
221
|
+
readonly open?: boolean | undefined;
|
|
212
222
|
/** The key of a category of the workspace. */
|
|
213
223
|
readonly category?: string | undefined;
|
|
224
|
+
/** Words of the title, each found in it whatever the case. */
|
|
225
|
+
readonly q?: string | undefined;
|
|
214
226
|
}
|
|
215
227
|
|
|
228
|
+
/** What the board is narrowed to: one state, the open ones, a category, a goal, a worker, what is due by a day, or words of the title; and which page of it. */
|
|
216
229
|
export interface TaskFilter extends Page {
|
|
217
230
|
readonly state?: TaskState | undefined;
|
|
218
231
|
/** True for the tasks not done or dropped; false for the rest. */
|
|
@@ -221,10 +234,12 @@ export interface TaskFilter extends Page {
|
|
|
221
234
|
readonly category?: string | undefined;
|
|
222
235
|
/** The id of a goal. */
|
|
223
236
|
readonly goal?: string | undefined;
|
|
224
|
-
/** The id of
|
|
225
|
-
readonly
|
|
226
|
-
/** True for the tasks
|
|
237
|
+
/** The id of a person among the workers of the tasks. */
|
|
238
|
+
readonly worker?: string | undefined;
|
|
239
|
+
/** True for the tasks with no worker. */
|
|
227
240
|
readonly unassigned?: boolean | undefined;
|
|
241
|
+
/** Words of the title, each found in it whatever the case; `#12` or `12` finds the task of that number too. */
|
|
242
|
+
readonly q?: string | undefined;
|
|
228
243
|
/** A day (`2026-10-20`): the tasks due on it or before it. */
|
|
229
244
|
readonly dueBy?: string | undefined;
|
|
230
245
|
/** True for the tasks put away alone (ADR-0029); false, or left out, for the current ones. */
|
|
@@ -345,6 +360,12 @@ export interface ProjectClient {
|
|
|
345
360
|
setPackToken(input: RestPackTokenInput): Promise<RestProject>;
|
|
346
361
|
/** Takes the pack token away; for an owner. */
|
|
347
362
|
removePackToken(): Promise<RestProject>;
|
|
363
|
+
/** Asks the deployment to connect the project's pack with a code a person approves (ADR-0036); for an owner. */
|
|
364
|
+
connectPackToken(): Promise<RestPackConnectStarted>;
|
|
365
|
+
/** How that connection stands; the token is kept when the person approved. For an owner. */
|
|
366
|
+
packTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
367
|
+
/** Gives the connection up; for an owner. */
|
|
368
|
+
cancelPackTokenConnection(): Promise<void>;
|
|
348
369
|
/** The rules that hold in the project (ADR-0023): the workspace's, and what the project adds. */
|
|
349
370
|
rules(signal?: AbortSignal): Promise<RestProjectRules>;
|
|
350
371
|
/** A folder's pages and folders, or the pages a search finds. */
|
|
@@ -365,8 +386,7 @@ export interface ProjectClient {
|
|
|
365
386
|
attachFile(path: string, input: FileUpload): Promise<RestDocument>;
|
|
366
387
|
/** Where the bytes of a file attached to a document are read, by the file's id. */
|
|
367
388
|
documentFileUrl(path: string, fileId: string): string;
|
|
368
|
-
/** The roadmap (ADR-0019)
|
|
369
|
-
/** The roadmap, or what the filter keeps of it: the active goals first. */
|
|
389
|
+
/** The roadmap (ADR-0019), or what the filter keeps of it, a page at a time: the active goals first, each with its tasks counted by state. */
|
|
370
390
|
goals(filter?: GoalFilter, signal?: AbortSignal): Promise<RestGoals>;
|
|
371
391
|
goal(id: string, signal?: AbortSignal): Promise<RestGoal>;
|
|
372
392
|
/** Sets a goal; for an owner, with a session or a token. */
|
|
@@ -380,7 +400,26 @@ export interface ConsoleClient {
|
|
|
380
400
|
giveUpToken(): Promise<void>;
|
|
381
401
|
/** Keeps the person's choice of language, or none; for a person signed in. */
|
|
382
402
|
updateMe(patch: RestMePatch): Promise<RestMe>;
|
|
403
|
+
/** Changes the workspace's settings (ADR-0035); for an administrator, with a session or a token. */
|
|
404
|
+
updateWorkspace(patch: RestWorkspacePatch): Promise<RestWorkspace>;
|
|
405
|
+
/**
|
|
406
|
+
* The workspace's SkillCDN token (ADR-0036), which reads the organization's pack for every
|
|
407
|
+
* project that names no address and holds no token of its own; an administrator's. Set from
|
|
408
|
+
* a token made by hand, taken away, or connected with a code a person approves at SkillCDN,
|
|
409
|
+
* asked after until the token is kept or the request ended.
|
|
410
|
+
*/
|
|
411
|
+
setWorkspacePackToken(input: RestPackTokenInput): Promise<RestWorkspace>;
|
|
412
|
+
removeWorkspacePackToken(): Promise<RestWorkspace>;
|
|
413
|
+
connectWorkspacePackToken(): Promise<RestPackConnectStarted>;
|
|
414
|
+
workspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
415
|
+
cancelWorkspacePackTokenConnection(): Promise<void>;
|
|
416
|
+
/** The people the asker may see: everyone, or those they share a project with (ADR-0035). */
|
|
383
417
|
people(signal?: AbortSignal): Promise<RestPeople>;
|
|
418
|
+
/**
|
|
419
|
+
* One person of the workspace by their exact login, compared without regard to case, or
|
|
420
|
+
* nobody: what an owner lists someone by when the People page does not show them.
|
|
421
|
+
*/
|
|
422
|
+
findPerson(login: string, signal?: AbortSignal): Promise<RestPerson | undefined>;
|
|
384
423
|
/** Changes what a person is, or whether they are in; for an administrator. */
|
|
385
424
|
updatePerson(id: string, patch: RestPersonPatch): Promise<RestPerson>;
|
|
386
425
|
/** Lets a login in (ADR-0022): whoever signs in with that account becomes a person; for an administrator. */
|
|
@@ -608,9 +647,10 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
608
647
|
open: flag(filter.open),
|
|
609
648
|
category: filter.category,
|
|
610
649
|
goal: filter.goal,
|
|
611
|
-
|
|
650
|
+
worker: filter.worker,
|
|
612
651
|
unassigned: flag(filter.unassigned),
|
|
613
652
|
dueBy: filter.dueBy,
|
|
653
|
+
q: filter.q,
|
|
614
654
|
limit: number(filter.limit),
|
|
615
655
|
offset: number(filter.offset),
|
|
616
656
|
}),
|
|
@@ -766,6 +806,23 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
766
806
|
request("PUT", `${base}${at()}/pack-token`, input, restProjectSchema),
|
|
767
807
|
removePackToken: () =>
|
|
768
808
|
request("DELETE", `${base}${at()}/pack-token`, undefined, restProjectSchema),
|
|
809
|
+
connectPackToken: () =>
|
|
810
|
+
request(
|
|
811
|
+
"POST",
|
|
812
|
+
`${base}${at()}/pack-token/connect`,
|
|
813
|
+
undefined,
|
|
814
|
+
restPackConnectStartedSchema,
|
|
815
|
+
),
|
|
816
|
+
packTokenConnection: (signal) =>
|
|
817
|
+
request(
|
|
818
|
+
"GET",
|
|
819
|
+
`${base}${at()}/pack-token/connect`,
|
|
820
|
+
undefined,
|
|
821
|
+
restPackConnectSchema,
|
|
822
|
+
signal,
|
|
823
|
+
),
|
|
824
|
+
cancelPackTokenConnection: () =>
|
|
825
|
+
request("DELETE", `${base}${at()}/pack-token/connect`, undefined, undefined),
|
|
769
826
|
rules: (signal) =>
|
|
770
827
|
request("GET", `${base}${at("rules")}`, undefined, restProjectRulesSchema, signal),
|
|
771
828
|
documents: (filter = {}, signal) =>
|
|
@@ -811,7 +868,14 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
811
868
|
goals: (filter = {}, signal) =>
|
|
812
869
|
request(
|
|
813
870
|
"GET",
|
|
814
|
-
withQuery(at("goals"), {
|
|
871
|
+
withQuery(at("goals"), {
|
|
872
|
+
state: filter.state,
|
|
873
|
+
open: flag(filter.open),
|
|
874
|
+
category: filter.category,
|
|
875
|
+
q: filter.q,
|
|
876
|
+
limit: number(filter.limit),
|
|
877
|
+
offset: number(filter.offset),
|
|
878
|
+
}),
|
|
815
879
|
undefined,
|
|
816
880
|
restGoalsSchema,
|
|
817
881
|
signal,
|
|
@@ -829,6 +893,39 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
829
893
|
giveUpToken: () => request("DELETE", `${base}${REST_ROUTES.meToken}`, undefined, undefined),
|
|
830
894
|
people: (signal) =>
|
|
831
895
|
request("GET", `${base}${REST_ROUTES.people}`, undefined, restPeopleSchema, signal),
|
|
896
|
+
findPerson: async (login, signal) => {
|
|
897
|
+
const found = await request(
|
|
898
|
+
"GET",
|
|
899
|
+
withQuery(REST_ROUTES.people, { login }),
|
|
900
|
+
undefined,
|
|
901
|
+
restPeopleSchema,
|
|
902
|
+
signal,
|
|
903
|
+
);
|
|
904
|
+
return found.items[0];
|
|
905
|
+
},
|
|
906
|
+
updateWorkspace: (patch) =>
|
|
907
|
+
request("PATCH", `${base}${REST_ROUTES.workspace}`, patch, restWorkspaceSchema),
|
|
908
|
+
setWorkspacePackToken: (input) =>
|
|
909
|
+
request("PUT", `${base}${REST_ROUTES.workspacePackToken}`, input, restWorkspaceSchema),
|
|
910
|
+
removeWorkspacePackToken: () =>
|
|
911
|
+
request("DELETE", `${base}${REST_ROUTES.workspacePackToken}`, undefined, restWorkspaceSchema),
|
|
912
|
+
connectWorkspacePackToken: () =>
|
|
913
|
+
request(
|
|
914
|
+
"POST",
|
|
915
|
+
`${base}${REST_ROUTES.workspacePackToken}/connect`,
|
|
916
|
+
undefined,
|
|
917
|
+
restPackConnectStartedSchema,
|
|
918
|
+
),
|
|
919
|
+
workspacePackTokenConnection: (signal) =>
|
|
920
|
+
request(
|
|
921
|
+
"GET",
|
|
922
|
+
`${base}${REST_ROUTES.workspacePackToken}/connect`,
|
|
923
|
+
undefined,
|
|
924
|
+
restWorkspacePackConnectSchema,
|
|
925
|
+
signal,
|
|
926
|
+
),
|
|
927
|
+
cancelWorkspacePackTokenConnection: () =>
|
|
928
|
+
request("DELETE", `${base}${REST_ROUTES.workspacePackToken}/connect`, undefined, undefined),
|
|
832
929
|
updatePerson: (id, patch) =>
|
|
833
930
|
request("PATCH", `${base}${restPath("people", id)}`, patch, restPersonSchema),
|
|
834
931
|
createListing: (input) =>
|
package/src/compat.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// Whether the command and the console go together (ADR-0025). The command names its version on
|
|
1
|
+
// Whether the command and the console go together (ADR-0025, ADR-0038). The command names its version on
|
|
2
2
|
// every request, in the product token of its user-agent; the console answers every request of
|
|
3
3
|
// the API with its own version and the range of command versions it takes, and refuses a
|
|
4
4
|
// command outside the range with `426` and the words to update. One rule for both sides, kept
|
|
5
5
|
// here so that the command and the console read the same one, and narrowed by the console's
|
|
6
|
-
// policy below only, never widened: what the rule promises stays predictable.
|
|
6
|
+
// policy below only, never widened: what the rule promises stays predictable. The command holds
|
|
7
|
+
// the console to a floor of its own the same way (ADR-0038).
|
|
7
8
|
|
|
8
9
|
/** The product token the command names itself with: `skillcdn-console/<version>`. */
|
|
9
10
|
export const CLIENT_PRODUCT = "skillcdn-console";
|
|
@@ -74,15 +75,19 @@ export function inRange(version: Version, range: VersionRange): boolean {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* The common rule. Before 1.0, a command
|
|
78
|
-
*
|
|
79
|
-
* console's own
|
|
80
|
-
*
|
|
81
|
-
* major breaks, as the version
|
|
78
|
+
* The common rule. Before 1.0, the line: a command and a console of one major.minor go
|
|
79
|
+
* together whatever the patch, since the minor is SkillCDN's line and a patch is a release of
|
|
80
|
+
* the console's own (ADR-0034), and a release that changes what passes between them raises the
|
|
81
|
+
* floors below instead (ADR-0038). From 1.0, the console's major and a minor within one of the
|
|
82
|
+
* console's, either way, whatever the patch: a minor adds and a major breaks, as the version
|
|
83
|
+
* promises from then on.
|
|
82
84
|
*/
|
|
83
85
|
export function commonRange(server: Version): VersionRange {
|
|
84
86
|
if (server.major === 0) {
|
|
85
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
atLeast: { major: 0, minor: server.minor, patch: 0 },
|
|
89
|
+
below: { major: 0, minor: server.minor + 1, patch: 0 },
|
|
90
|
+
};
|
|
86
91
|
}
|
|
87
92
|
return {
|
|
88
93
|
atLeast: { major: server.major, minor: Math.max(0, server.minor - 1), patch: 0 },
|
|
@@ -103,13 +108,25 @@ export interface ClientPolicy {
|
|
|
103
108
|
readonly refused?: readonly string[] | undefined;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The console's policy: the oldest command this console answers, raised by the release that
|
|
113
|
+
* last changed what the command reads or sends (ADR-0038), whose changelog entry begins with
|
|
114
|
+
* `Breaking:`; `pnpm check` holds it to a version the package has, or is about to have.
|
|
115
|
+
*/
|
|
116
|
+
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.3.2" };
|
|
108
117
|
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The oldest console this command works with (ADR-0038): raised by the release that last
|
|
120
|
+
* changed what the command asks of the console, whose changelog entry begins with `Breaking:`.
|
|
121
|
+
* The command compares it with the version every answer names and refuses a console below it
|
|
122
|
+
* itself, naming the command to install instead; `pnpm check` holds it as it holds the
|
|
123
|
+
* console's floor.
|
|
124
|
+
*/
|
|
125
|
+
export const CONSOLE_AT_LEAST = "0.3.2";
|
|
126
|
+
|
|
127
|
+
/** `common` from `atLeast` on, where that floor is within it; `common` as it is otherwise. */
|
|
128
|
+
function narrowed(common: VersionRange, atLeast: string | undefined): VersionRange {
|
|
129
|
+
const floor = parseVersion(atLeast);
|
|
113
130
|
return floor !== undefined &&
|
|
114
131
|
compareVersions(floor, common.atLeast) > 0 &&
|
|
115
132
|
compareVersions(floor, common.below) < 0
|
|
@@ -117,6 +134,16 @@ export function clientRange(server: Version, policy: ClientPolicy = CLIENT_POLIC
|
|
|
117
134
|
: common;
|
|
118
135
|
}
|
|
119
136
|
|
|
137
|
+
/** The range of command versions a console of `server` takes: the common rule, narrowed by the policy's floor where that floor is within it. */
|
|
138
|
+
export function clientRange(server: Version, policy: ClientPolicy = CLIENT_POLICY): VersionRange {
|
|
139
|
+
return narrowed(commonRange(server), policy.atLeast);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The range of console versions a command of `client` works with: the common rule, narrowed by the command's floor where that floor is within it. */
|
|
143
|
+
export function consoleRange(client: Version, atLeast: string = CONSOLE_AT_LEAST): VersionRange {
|
|
144
|
+
return narrowed(commonRange(client), atLeast);
|
|
145
|
+
}
|
|
146
|
+
|
|
120
147
|
/**
|
|
121
148
|
* What a command of one version is to a console of another: within the range and the same
|
|
122
149
|
* (`current`), older (`behind`) or newer (`ahead`), which the console answers; or outside it,
|
|
@@ -152,6 +179,28 @@ export function judgeClient(
|
|
|
152
179
|
return { verdict: order === 0 ? "current" : order < 0 ? "behind" : "ahead", range };
|
|
153
180
|
}
|
|
154
181
|
|
|
182
|
+
/**
|
|
183
|
+
* What a console of one version is to a command of another (ADR-0038): within the range the
|
|
184
|
+
* command works with, the same (`current`), older (`behind`) or newer (`ahead`); below it
|
|
185
|
+
* (`outdated`), which the command refuses itself, naming the command to install; or beyond it
|
|
186
|
+
* (`too_new`), which the console refuses the command for.
|
|
187
|
+
*/
|
|
188
|
+
export function judgeConsole(
|
|
189
|
+
server: Version,
|
|
190
|
+
client: Version,
|
|
191
|
+
atLeast: string = CONSOLE_AT_LEAST,
|
|
192
|
+
): ClientJudgement {
|
|
193
|
+
const range = consoleRange(client, atLeast);
|
|
194
|
+
if (compareVersions(server, range.atLeast) < 0) {
|
|
195
|
+
return { verdict: "outdated", range };
|
|
196
|
+
}
|
|
197
|
+
if (compareVersions(server, range.below) >= 0) {
|
|
198
|
+
return { verdict: "too_new", range };
|
|
199
|
+
}
|
|
200
|
+
const order = compareVersions(server, client);
|
|
201
|
+
return { verdict: order === 0 ? "current" : order < 0 ? "behind" : "ahead", range };
|
|
202
|
+
}
|
|
203
|
+
|
|
155
204
|
/** What the command sends as its user-agent: its product token, and, in a comment, where it runs. */
|
|
156
205
|
export function userAgentOf(version: string, comment?: string): string {
|
|
157
206
|
// A comment holds printable ASCII without parentheses or backslashes; nothing else is said.
|
package/src/components/board.tsx
CHANGED
|
@@ -20,7 +20,6 @@ import { useWords } from "../i18n/index.js";
|
|
|
20
20
|
import type { BoardFilter } from "../router.js";
|
|
21
21
|
import { isPlainClick } from "./shell.js";
|
|
22
22
|
import {
|
|
23
|
-
Avatar,
|
|
24
23
|
Badge,
|
|
25
24
|
Button,
|
|
26
25
|
CategoryBadge,
|
|
@@ -28,6 +27,7 @@ import {
|
|
|
28
27
|
DueBadge,
|
|
29
28
|
PriorityBadge,
|
|
30
29
|
personName,
|
|
30
|
+
Workers,
|
|
31
31
|
} from "./ui.js";
|
|
32
32
|
|
|
33
33
|
// The board: one column per state, the tasks in it, the sooner due first and the newest after.
|
|
@@ -165,7 +165,7 @@ export function TaskCard(props: TaskCardProps) {
|
|
|
165
165
|
</Badge>
|
|
166
166
|
)}
|
|
167
167
|
<span className="sc-card-spacer" />
|
|
168
|
-
|
|
168
|
+
<Workers workers={task.workers} size="sm" />
|
|
169
169
|
</div>
|
|
170
170
|
</article>
|
|
171
171
|
);
|
|
@@ -584,10 +584,16 @@ export function filterTasks(
|
|
|
584
584
|
if (filter.goal !== undefined && (task.goalId ?? "") !== filter.goal) {
|
|
585
585
|
return false;
|
|
586
586
|
}
|
|
587
|
-
if (
|
|
587
|
+
if (
|
|
588
|
+
filter.person !== undefined &&
|
|
589
|
+
!task.workers.some((worker) => worker.id === filter.person)
|
|
590
|
+
) {
|
|
588
591
|
return false;
|
|
589
592
|
}
|
|
590
|
-
if (
|
|
593
|
+
if (
|
|
594
|
+
filter.mine === true &&
|
|
595
|
+
(me === undefined || !task.workers.some((worker) => worker.id === me.id))
|
|
596
|
+
) {
|
|
591
597
|
return false;
|
|
592
598
|
}
|
|
593
599
|
return true;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { type ReactNode, type SyntheticEvent, useState } from "react";
|
|
1
|
+
import { type CSSProperties, type ReactNode, type SyntheticEvent, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
OPEN_TASK_STATES,
|
|
4
4
|
type RestCategory,
|
|
5
5
|
type RestGoal,
|
|
6
6
|
type RestPerson,
|
|
7
7
|
type RestTask,
|
|
8
|
+
type WeekStart,
|
|
8
9
|
} from "../api.js";
|
|
9
10
|
import {
|
|
10
11
|
addDays,
|
|
11
12
|
addMonths,
|
|
12
13
|
daysBetween,
|
|
14
|
+
deviceLanguage,
|
|
13
15
|
firstDayOfWeek,
|
|
14
16
|
formatDay,
|
|
15
17
|
formatDays,
|
|
@@ -23,7 +25,7 @@ import {
|
|
|
23
25
|
weekOf,
|
|
24
26
|
} from "../dates.js";
|
|
25
27
|
import { useLanguage, useWords } from "../i18n/index.js";
|
|
26
|
-
import {
|
|
28
|
+
import { CategoryBadge, cx, PersonChip, personName, StateBadge, Time, Workers } from "./ui.js";
|
|
27
29
|
|
|
28
30
|
// The calendar of a project (ADR-0019): the month as a grid, or one week on its own. A task
|
|
29
31
|
// planned from a start to a due day lies across its days as a bar, in a lane of its own within
|
|
@@ -55,6 +57,8 @@ export interface CalendarProps {
|
|
|
55
57
|
readonly goalHref?: ((goal: RestGoal) => string) | undefined;
|
|
56
58
|
/** Where the calendar is on another month. */
|
|
57
59
|
readonly monthHref: (month: string) => string;
|
|
60
|
+
/** The day the weeks begin on, as the workspace says (ADR-0035); the language's when left out. */
|
|
61
|
+
readonly weekStart?: WeekStart | undefined;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/** One task's stretch across one week: which columns it covers, and in which lane it lies. */
|
|
@@ -258,12 +262,18 @@ export function TaskTip(props: TaskTipProps) {
|
|
|
258
262
|
</dd>
|
|
259
263
|
</div>
|
|
260
264
|
<div>
|
|
261
|
-
<dt>{words.task.
|
|
265
|
+
<dt>{words.task.workers}</dt>
|
|
262
266
|
<dd>
|
|
263
|
-
{task.
|
|
267
|
+
{task.workers.length === 0 ? (
|
|
264
268
|
<span className="sc-muted">{words.common.nobody}</span>
|
|
265
269
|
) : (
|
|
266
|
-
<
|
|
270
|
+
<ul className="sc-worker-list">
|
|
271
|
+
{task.workers.map((worker) => (
|
|
272
|
+
<li key={worker.id}>
|
|
273
|
+
<PersonChip person={worker} />
|
|
274
|
+
</li>
|
|
275
|
+
))}
|
|
276
|
+
</ul>
|
|
267
277
|
)}
|
|
268
278
|
</dd>
|
|
269
279
|
</div>
|
|
@@ -352,6 +362,9 @@ function goalsOnDays(goals: readonly RestGoal[]): ReadonlyMap<string, readonly R
|
|
|
352
362
|
return on;
|
|
353
363
|
}
|
|
354
364
|
|
|
365
|
+
/** How many days a bar has in the week, for its title to run along: a custom property the stylesheet reads. */
|
|
366
|
+
const spanStyle = (span: number): CSSProperties => ({ "--sc-span": span }) as CSSProperties;
|
|
367
|
+
|
|
355
368
|
/** One week as a row of cells: the bars across the days in their lanes, then the tasks on each day. */
|
|
356
369
|
function WeekRow(props: WeekRowProps) {
|
|
357
370
|
const { week, segments, now } = props;
|
|
@@ -367,9 +380,7 @@ function WeekRow(props: WeekRowProps) {
|
|
|
367
380
|
<TaskTip task={task} goals={props.goals} place={props.pointed.place} />
|
|
368
381
|
) : null;
|
|
369
382
|
const who = (task: RestTask): ReactNode =>
|
|
370
|
-
props.people === true
|
|
371
|
-
<Avatar person={task.assignee} size="xs" />
|
|
372
|
-
) : null;
|
|
383
|
+
props.people === true ? <Workers workers={task.workers} size="xs" shown={2} /> : null;
|
|
373
384
|
const lanes = segments.reduce((most, segment) => Math.max(most, segment.lane + 1), 0);
|
|
374
385
|
return (
|
|
375
386
|
<tr>
|
|
@@ -409,6 +420,13 @@ function WeekRow(props: WeekRowProps) {
|
|
|
409
420
|
return <span key={lane} className="sc-calendar-lane" aria-hidden="true" />;
|
|
410
421
|
}
|
|
411
422
|
const { task } = segment;
|
|
423
|
+
// The first piece carries the title; with more days in the week, the title runs along
|
|
424
|
+
// the bar as far as they reach, over the pieces after it, instead of stopping at the
|
|
425
|
+
// first day's edge.
|
|
426
|
+
const span = segment.to - segment.from + 1;
|
|
427
|
+
const spanning = segment.from === column && span > 1;
|
|
428
|
+
// Every piece of the task under the pointer lights together, in every week it crosses.
|
|
429
|
+
const pointed = props.pointed !== null && props.pointed.task.id === task.id;
|
|
412
430
|
return (
|
|
413
431
|
<a
|
|
414
432
|
key={task.id}
|
|
@@ -417,8 +435,11 @@ function WeekRow(props: WeekRowProps) {
|
|
|
417
435
|
`sc-category-${task.category?.color ?? "gray"}`,
|
|
418
436
|
segment.from === column && segment.begins && "sc-calendar-bar-begins",
|
|
419
437
|
segment.to === column && segment.ends && "sc-calendar-bar-ends",
|
|
438
|
+
spanning && "sc-calendar-bar-spanning",
|
|
439
|
+
pointed && "sc-calendar-bar-pointed",
|
|
420
440
|
tone(task, now),
|
|
421
441
|
)}
|
|
442
|
+
style={spanning ? spanStyle(span) : undefined}
|
|
422
443
|
href={props.taskHref(task)}
|
|
423
444
|
{...pointing(task, cell.day)}
|
|
424
445
|
>
|
|
@@ -426,7 +447,14 @@ function WeekRow(props: WeekRowProps) {
|
|
|
426
447
|
<>
|
|
427
448
|
{props.markOf?.(task)}
|
|
428
449
|
{who(task)}
|
|
429
|
-
<span
|
|
450
|
+
<span
|
|
451
|
+
className={cx(
|
|
452
|
+
"sc-calendar-task-title",
|
|
453
|
+
spanning && "sc-calendar-task-title-spanning",
|
|
454
|
+
)}
|
|
455
|
+
>
|
|
456
|
+
{task.title}
|
|
457
|
+
</span>
|
|
430
458
|
</>
|
|
431
459
|
)}
|
|
432
460
|
{tipIn(task, cell.day)}
|
|
@@ -469,7 +497,7 @@ export function Calendar(props: CalendarProps) {
|
|
|
469
497
|
const { tag } = useLanguage();
|
|
470
498
|
const [pointed, point, leave] = usePointed();
|
|
471
499
|
const now = props.today ?? todayOf();
|
|
472
|
-
const firstDay = firstDayOfWeek(tag);
|
|
500
|
+
const firstDay = firstDayOfWeek(deviceLanguage(tag), props.weekStart);
|
|
473
501
|
const weeks = monthGrid(props.month, firstDay);
|
|
474
502
|
const on = onDays(props.tasks);
|
|
475
503
|
const goalsOn = props.goalHref === undefined ? undefined : goalsOnDays(props.goals ?? []);
|
|
@@ -567,6 +595,8 @@ export interface WeekProps {
|
|
|
567
595
|
readonly weekHref: (day: string) => string;
|
|
568
596
|
/** What stands before a task's title where the tasks are of several projects: the project's mark. */
|
|
569
597
|
readonly markOf?: ((task: RestTask) => ReactNode) | undefined;
|
|
598
|
+
/** The day the week begins on, as the workspace says (ADR-0035); the language's when left out. */
|
|
599
|
+
readonly weekStart?: WeekStart | undefined;
|
|
570
600
|
}
|
|
571
601
|
|
|
572
602
|
/** One week on its own: the days as columns, the tasks on them, and the weeks around a step away. */
|
|
@@ -575,7 +605,7 @@ export function Week(props: WeekProps) {
|
|
|
575
605
|
const { tag } = useLanguage();
|
|
576
606
|
const [pointed, point, leave] = usePointed();
|
|
577
607
|
const now = props.today ?? todayOf();
|
|
578
|
-
const firstDay = firstDayOfWeek(tag);
|
|
608
|
+
const firstDay = firstDayOfWeek(deviceLanguage(tag), props.weekStart);
|
|
579
609
|
const days = weekOf(props.day ?? now, firstDay);
|
|
580
610
|
const first = days[0] ?? now;
|
|
581
611
|
const last = days[days.length - 1] ?? now;
|
|
@@ -638,39 +668,44 @@ export function Week(props: WeekProps) {
|
|
|
638
668
|
|
|
639
669
|
/** One person's share of the open work, as the table of who has what says it. */
|
|
640
670
|
export interface WorkloadRow {
|
|
641
|
-
/** The person, or nobody for the tasks
|
|
671
|
+
/** The person, or nobody for the tasks with no worker. */
|
|
642
672
|
readonly person: RestPerson | undefined;
|
|
643
673
|
readonly open: number;
|
|
644
674
|
readonly inProgress: number;
|
|
645
|
-
/** Open, and due
|
|
646
|
-
readonly
|
|
675
|
+
/** Open, and due from today through the last day of this week, as the workspace begins its weeks (ADR-0039). */
|
|
676
|
+
readonly dueThisWeek: number;
|
|
647
677
|
/** Open, and past its day. */
|
|
648
678
|
readonly overdue: number;
|
|
649
679
|
}
|
|
650
680
|
|
|
651
|
-
/**
|
|
652
|
-
|
|
681
|
+
/**
|
|
682
|
+
* Who has what, from the open tasks: a task counted for each of its workers, by name, and the
|
|
683
|
+
* tasks with none last; this week is the days from today through the week's last day, the week
|
|
684
|
+
* beginning on `firstDay` (1 for Monday, 7 for Sunday).
|
|
685
|
+
*/
|
|
686
|
+
export function workloadOf(tasks: readonly RestTask[], today: string, firstDay = 1): WorkloadRow[] {
|
|
687
|
+
const weekEnd = weekOf(today, firstDay).at(-1) ?? today;
|
|
653
688
|
const rows = new Map<string, { person: RestPerson | undefined; tasks: RestTask[] }>();
|
|
654
689
|
for (const task of tasks.filter(isOpen)) {
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
row
|
|
690
|
+
const workers: readonly (RestPerson | undefined)[] =
|
|
691
|
+
task.workers.length === 0 ? [undefined] : task.workers;
|
|
692
|
+
for (const worker of workers) {
|
|
693
|
+
const key = worker?.id ?? "";
|
|
694
|
+
const row = rows.get(key);
|
|
695
|
+
if (row === undefined) {
|
|
696
|
+
rows.set(key, { person: worker, tasks: [task] });
|
|
697
|
+
} else {
|
|
698
|
+
row.tasks.push(task);
|
|
699
|
+
}
|
|
661
700
|
}
|
|
662
701
|
}
|
|
663
702
|
const count = (list: readonly RestTask[]): Omit<WorkloadRow, "person"> => ({
|
|
664
703
|
open: list.length,
|
|
665
704
|
inProgress: list.filter((task) => task.state === "in_progress" || task.state === "in_review")
|
|
666
705
|
.length,
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
const days = daysBetween(today, task.dueOn);
|
|
672
|
-
return days >= 0 && days <= 7;
|
|
673
|
-
}).length,
|
|
706
|
+
dueThisWeek: list.filter(
|
|
707
|
+
(task) => task.dueOn !== null && task.dueOn >= today && task.dueOn <= weekEnd,
|
|
708
|
+
).length,
|
|
674
709
|
overdue: list.filter((task) => task.dueOn !== null && task.dueOn < today).length,
|
|
675
710
|
});
|
|
676
711
|
return [...rows.values()]
|
|
@@ -689,14 +724,21 @@ export function workloadOf(tasks: readonly RestTask[], today: string): WorkloadR
|
|
|
689
724
|
export interface WorkloadProps {
|
|
690
725
|
readonly tasks: readonly RestTask[];
|
|
691
726
|
readonly today?: string | undefined;
|
|
692
|
-
/**
|
|
727
|
+
/** The day the week begins on, as the workspace says (ADR-0035); the language's when left out. */
|
|
728
|
+
readonly weekStart?: WeekStart | undefined;
|
|
729
|
+
/** Where the board is, narrowed to one person's tasks, or to those with no worker. */
|
|
693
730
|
readonly personHref?: ((person: RestPerson | undefined) => string) | undefined;
|
|
694
731
|
}
|
|
695
732
|
|
|
696
|
-
/** Who has what: a row per person, and what of theirs is late or due
|
|
733
|
+
/** Who has what: a row per person, and what of theirs is late or due this week. */
|
|
697
734
|
export function Workload(props: WorkloadProps) {
|
|
698
735
|
const words = useWords().calendar.workload;
|
|
699
|
-
const
|
|
736
|
+
const { tag } = useLanguage();
|
|
737
|
+
const rows = workloadOf(
|
|
738
|
+
props.tasks,
|
|
739
|
+
props.today ?? todayOf(),
|
|
740
|
+
firstDayOfWeek(deviceLanguage(tag), props.weekStart),
|
|
741
|
+
);
|
|
700
742
|
if (rows.length === 0) {
|
|
701
743
|
return null;
|
|
702
744
|
}
|
|
@@ -707,7 +749,7 @@ export function Workload(props: WorkloadProps) {
|
|
|
707
749
|
<th scope="col">{words.person}</th>
|
|
708
750
|
<th scope="col">{words.open}</th>
|
|
709
751
|
<th scope="col">{words.inProgress}</th>
|
|
710
|
-
<th scope="col">{words.
|
|
752
|
+
<th scope="col">{words.dueThisWeek}</th>
|
|
711
753
|
<th scope="col">{words.overdue}</th>
|
|
712
754
|
</tr>
|
|
713
755
|
</thead>
|
|
@@ -725,7 +767,7 @@ export function Workload(props: WorkloadProps) {
|
|
|
725
767
|
<th scope="row">{href === undefined ? who : <a href={href}>{who}</a>}</th>
|
|
726
768
|
<td>{row.open}</td>
|
|
727
769
|
<td>{row.inProgress}</td>
|
|
728
|
-
<td className={cx(row.
|
|
770
|
+
<td className={cx(row.dueThisWeek > 0 && "sc-workload-soon")}>{row.dueThisWeek}</td>
|
|
729
771
|
<td className={cx(row.overdue > 0 && "sc-workload-late")}>{row.overdue}</td>
|
|
730
772
|
</tr>
|
|
731
773
|
);
|
|
@@ -46,6 +46,8 @@ export function describeEvent(event: RestEvent, words: Messages = en): string {
|
|
|
46
46
|
return feed.event.personDeactivated(login);
|
|
47
47
|
case "person.reactivated":
|
|
48
48
|
return feed.event.personReactivated(login);
|
|
49
|
+
case "workspace.updated":
|
|
50
|
+
return feed.event.workspaceUpdated(fields);
|
|
49
51
|
case "project.created":
|
|
50
52
|
return feed.event.projectCreated(project);
|
|
51
53
|
case "project.updated":
|