@skillcdn/console 0.3.2 → 0.4.0
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 +3 -3
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +96 -7
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +6 -4
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +48 -17
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +7 -2
- package/dist/cli/pack.d.ts.map +1 -1
- package/dist/cli/pack.js +16 -2
- package/dist/cli/pack.js.map +1 -1
- package/dist/client.d.ts +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +12 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +52 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +4 -0
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +61 -22
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-form.d.ts +12 -4
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -6
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +4 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +9 -6
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -3
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +63 -8
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/my-decisions.d.ts +20 -0
- package/dist/components/my-decisions.d.ts.map +1 -0
- package/dist/components/my-decisions.js +36 -0
- package/dist/components/my-decisions.js.map +1 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +1 -1
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +3 -3
- package/dist/components/projects.js.map +1 -1
- package/dist/components/roadmap.js +1 -1
- package/dist/components/roadmap.js.map +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +1 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +36 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/task-list.js +1 -1
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -2
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +10 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -11
- package/dist/components/ui.js.map +1 -1
- package/dist/console.css +1642 -433
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +70 -7
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +11 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +2 -0
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +34 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +36 -2
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +37 -3
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +9 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +3 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +3 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.js +1 -1
- package/dist/rules.js.map +1 -1
- package/dist/schemas.d.ts +242 -3
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +18 -4
- package/dist/schemas.js.map +1 -1
- package/docs/specs/cli.md +6 -5
- package/docs/specs/rest.md +11 -6
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +3 -3
- package/skills/building-a-console/references/the-default-console.md +4 -4
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/working-the-board/SKILL.md +6 -4
- package/skills/working-the-board/references/terms.md +1 -1
- package/src/api.ts +2 -0
- package/src/cli/cli.ts +123 -7
- package/src/cli/format.ts +11 -4
- package/src/cli/help.ts +49 -16
- package/src/cli/pack.ts +17 -1
- package/src/client.ts +21 -1
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +114 -21
- package/src/components/calendar.tsx +207 -102
- package/src/components/decision-form.tsx +71 -23
- package/src/components/decision-list.tsx +30 -3
- package/src/components/documents.tsx +27 -11
- package/src/components/event-feed.tsx +116 -33
- package/src/components/my-decisions.tsx +138 -0
- package/src/components/people.tsx +3 -1
- package/src/components/projects.tsx +7 -5
- package/src/components/roadmap.tsx +1 -1
- package/src/components/runs.tsx +5 -1
- package/src/components/shell.tsx +36 -1
- package/src/components/task-list.tsx +1 -1
- package/src/components/task-view.tsx +48 -47
- package/src/components/ui.tsx +64 -11
- package/src/console.tsx +129 -27
- package/src/data.ts +17 -0
- package/src/i18n/en.ts +38 -2
- package/src/i18n/ko.ts +39 -3
- package/src/index.ts +3 -0
- package/src/router.ts +18 -1
- package/src/routes.ts +3 -1
- package/src/rules.ts +1 -1
- package/src/schemas.ts +20 -4
- package/src/styles/console.css +1642 -433
package/src/cli/help.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
MAX_FILES_PER_COMMENT,
|
|
14
14
|
MAX_FILES_PER_DOCUMENT,
|
|
15
15
|
MAX_LINK_LABEL_LENGTH,
|
|
16
|
+
MAX_NOTE_LENGTH,
|
|
16
17
|
MAX_OPTION_LABEL_LENGTH,
|
|
17
18
|
MAX_OPTIONS,
|
|
18
19
|
MAX_PROJECT_DESCRIPTION_LENGTH,
|
|
@@ -147,9 +148,9 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
147
148
|
work: {
|
|
148
149
|
usage: "work [--person <login>]",
|
|
149
150
|
synopsis:
|
|
150
|
-
"What is on your plate across your projects: your open tasks, and the decisions waiting.",
|
|
151
|
+
"What is on your plate across your projects: your open tasks, and the decisions waiting for you.",
|
|
151
152
|
about:
|
|
152
|
-
"What is on your plate across the projects you may see: the open tasks you work on, the sooner due first, each with its project; and the decisions waiting for
|
|
153
|
+
"What is on your plate across the projects you may see: the open tasks you work on, the sooner due first, each with its project; and the decisions waiting for your answer (ADR-0042). Needs no project. --person <login> shows another person's plate, for an administrator or for an owner of a project they are in: the open tasks they work on within the projects you may see, without the decisions.",
|
|
153
154
|
},
|
|
154
155
|
tasks: {
|
|
155
156
|
usage:
|
|
@@ -313,11 +314,11 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
313
314
|
},
|
|
314
315
|
skills: {
|
|
315
316
|
usage:
|
|
316
|
-
"skills | skills <folder> | skills --search <words> [<folder>] [--limit <n>] [--cursor <cursor>]",
|
|
317
|
+
"skills | skills <folder|file> | skills --search <words> [<folder>] [--limit <n>] [--cursor <cursor>]",
|
|
317
318
|
synopsis:
|
|
318
|
-
"The project's pack: its skills; what a folder of it holds; or what words find in it.",
|
|
319
|
+
"The project's pack: its skills; what a folder of it holds, or a document's outline; or what words find in it.",
|
|
319
320
|
about:
|
|
320
|
-
"The project's pack, read by the console at the project's address, or the organization's: at a SkillCDN deployment, with the pack token the project holds where the pack is private (ADR-0028), or from a directory on the console's machine for a local address (ADR-0031). Alone: where the pack is and what a client is told of it on connect, then every skill with the path of its SKILL.md and every document, as a connection of your own would list them, and where the console's own skills are on this machine. With a folder (. for the root): what it holds, whole, as browse_repo lays it out for a model: its skills, its folders with what each holds and the README that introduces it, its files. With --search: what the words find, as search_repo does: skills first, with the files of theirs that matched, then documents, in relevance order; words
|
|
321
|
+
"The project's pack, read by the console at the project's address, or the organization's: at a SkillCDN deployment, with the pack token the project holds where the pack is private (ADR-0028), or from a directory on the console's machine for a local address (ADR-0031). Alone: where the pack is and what a client is told of it on connect, then every skill with the path of its SKILL.md and every document, as a connection of your own would list them, and where the console's own skills are on this machine. With a folder (. for the root): what it holds, whole, as browse_repo lays it out for a model: its skills, its folders with what each holds and the README that introduces it, its files. With a file's path: the file's outline, every heading with its level and the offset and limit that read its section with console skill --file, whole; a file that is not Markdown has none. With --search: what the words find, as search_repo does: skills first, with the files of theirs that matched, then documents, each with the sections of it that matched and what reads each, in relevance order; words in the language the pack is written in, and meaning too where the deployment searches by it, which the result then says; within the folder when one is named; 10 unless --limit says otherwise, up to 25, and a nextCursor line says when more matched, which --cursor continues. The text is the one an MCP client's model is given, with the reads named as these commands. --json answers with the console's own JSON: what the pack answered, in the shapes of the main repository's REST API, the pages of a folder or an outline joined.",
|
|
321
322
|
limits: ["search: 1 to 500 characters; limit: 1 to 25"],
|
|
322
323
|
refusals: [
|
|
323
324
|
"status not_found: the pack is not served to the console; private, with no pack token on the project",
|
|
@@ -325,11 +326,12 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
325
326
|
],
|
|
326
327
|
},
|
|
327
328
|
skill: {
|
|
328
|
-
usage: "skill <path> | skill --file <path>",
|
|
329
|
+
usage: "skill <path> | skill --file <path> [--offset <n>] [--limit <n>]",
|
|
329
330
|
synopsis:
|
|
330
|
-
"One skill of the pack whole, its rules first; or with --file a file of the pack, or what a folder holds.",
|
|
331
|
+
"One skill of the pack whole, its rules first; or with --file a file of the pack, one section of it, or what a folder holds.",
|
|
331
332
|
about:
|
|
332
|
-
"One skill of the project's pack, whole, as load_skill lays it out for a model: the rules of the repository from its root to the nearest manifest, the skill's instructions, the files it includes, then its supporting files and what it refers to; the pages it came in are joined. Read by the console from the pack, with the pack token the project holds where the pack is private, so you need no connection of your own. <path> is the path of the SKILL.md from the repository's root, as console skills lists it, or the URI the pack names it by (skill://<host>/<owner>/<name>/<path>). --file <path> reads a file of the pack instead, whole, as read_repo_file does, or what a folder holds, and takes no skill's path beside it; console skills <folder> shows a folder with more. --json answers with what the pack answered, in the shapes of the main repository's REST API, the pages joined.",
|
|
333
|
+
"One skill of the project's pack, whole, as load_skill lays it out for a model: the rules of the repository from its root to the nearest manifest, the skill's instructions, the files it includes, then its supporting files and what it refers to; the pages it came in are joined. Read by the console from the pack, with the pack token the project holds where the pack is private, so you need no connection of your own. <path> is the path of the SKILL.md from the repository's root, as console skills lists it, or the URI the pack names it by (skill://<host>/<owner>/<name>/<path>). --file <path> reads a file of the pack instead, whole, as read_repo_file does, or what a folder holds, and takes no skill's path beside it; console skills <folder> shows a folder with more. --offset <n> and --limit <n>, with --file, read one page of the file as asked, from that character on and at most that many characters, as a search or an outline (console skills <file>) names a section, instead of the file whole; the result says where the file continues. --json answers with what the pack answered, in the shapes of the main repository's REST API, the pages joined.",
|
|
334
|
+
limits: ["offset: 0 or more; limit: 1 to 100000 characters; both with --file"],
|
|
333
335
|
refusals: [
|
|
334
336
|
"status not_found: the pack is not served to the console; private, with no pack token on the project",
|
|
335
337
|
"status token_invalid: the pack token the project holds no longer works; an owner sets a new one",
|
|
@@ -393,18 +395,38 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
393
395
|
},
|
|
394
396
|
ask: {
|
|
395
397
|
usage:
|
|
396
|
-
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--wait <seconds>] [--run <id>]',
|
|
398
|
+
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--to <login|me>] [--wait <seconds>] [--run <id>]',
|
|
397
399
|
overview:
|
|
398
|
-
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--wait <seconds>]',
|
|
400
|
+
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--to <login|me>] [--wait <seconds>]',
|
|
399
401
|
synopsis:
|
|
400
402
|
"Raises a decision a person must make, with options, and waits a while for the answer.",
|
|
401
|
-
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. The person chooses one of the options, or answers in their own words: the answer then names one more option, their words, marked own; act on it as given. ${ON_THE_RUN}`,
|
|
403
|
+
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. It is for the person you work for unless --to names another of the project, who then finds it among their decisions (ADR-0042); anyone of the project may still answer. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. The person chooses one of the options, or answers in their own words: the answer then names one more option, their words, marked own; act on it as given. ${ON_THE_RUN}`,
|
|
402
404
|
limits: [
|
|
403
405
|
`question: one line, up to ${MAX_QUESTION_LENGTH} characters`,
|
|
404
406
|
`options: ${MIN_OPTIONS} to ${MAX_OPTIONS}, each one line up to ${MAX_OPTION_LABEL_LENGTH} characters`,
|
|
405
407
|
`body: ${MARKDOWN}`,
|
|
408
|
+
"to: a login of the project, or me",
|
|
409
|
+
],
|
|
410
|
+
refusals: [
|
|
411
|
+
...RUN_CHOICE,
|
|
412
|
+
"decision.invalid_run: the run is not yours, or is over",
|
|
413
|
+
"decision.invalid_decider: the person named may not work in the project",
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
answer: {
|
|
417
|
+
usage: 'answer <id> --option <n>|--own "<answer>" [--note <markdown>|--file <path>]',
|
|
418
|
+
synopsis: "Writes a person's answer on a decision: the option they chose, or their own words.",
|
|
419
|
+
about:
|
|
420
|
+
"Writes on the board the answer a person gave: one of the decision's options by its number, or their own words, which become one more option of the decision marked as theirs; and a note about it. The answer is the person's, written in their name through you (ADR-0021): a choice they made in chat, in a meeting or by changing the task is written here, with where and when they made it in the note, so that the record is whole and a run that waited goes on. A decision is answered once; what followed is written with console decision <id> --outcome.",
|
|
421
|
+
limits: [
|
|
422
|
+
`own: one line, up to ${MAX_OPTION_LABEL_LENGTH} characters`,
|
|
423
|
+
`note: Markdown, up to ${MAX_NOTE_LENGTH} characters`,
|
|
424
|
+
],
|
|
425
|
+
refusals: [
|
|
426
|
+
"decision.not_found: no decision has that id",
|
|
427
|
+
"decision.answered: the decision has an answer already",
|
|
428
|
+
"decision.no_such_option: the option is not one of the decision's",
|
|
406
429
|
],
|
|
407
|
-
refusals: [...RUN_CHOICE, "decision.invalid_run: the run is not yours, or is over"],
|
|
408
430
|
},
|
|
409
431
|
decision: {
|
|
410
432
|
usage: "decision <id> [--wait <seconds>] | decision <id> --outcome <markdown>|--file <path>",
|
|
@@ -416,9 +438,9 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
416
438
|
refusals: ["decision.not_found: no decision has that id"],
|
|
417
439
|
},
|
|
418
440
|
decisions: {
|
|
419
|
-
usage: "decisions [--open] [--limit <n>] [--offset <n>]",
|
|
420
|
-
synopsis: "The decisions on the board, the waiting first.",
|
|
421
|
-
about: `The decisions on the board, the waiting first; --open keeps only those. A page at a time: ${DEFAULT_PAGE_LIMIT} unless --limit says otherwise, from --offset on.`,
|
|
441
|
+
usage: "decisions [--open] [--mine|--raised] [--limit <n>] [--offset <n>]",
|
|
442
|
+
synopsis: "The decisions on the board, the waiting first; or your own across the projects.",
|
|
443
|
+
about: `The decisions on the board, the waiting first; --open keeps only those. --mine lists the decisions addressed to you instead (ADR-0042), and --raised those you asked of someone else, through your agent or yourself, each across the projects you may see with its project's key; neither needs a project. A page at a time: ${DEFAULT_PAGE_LIMIT} unless --limit says otherwise, from --offset on.`,
|
|
422
444
|
},
|
|
423
445
|
finish: {
|
|
424
446
|
usage: 'finish [--summary "<markdown>"|--file <path>] [--run <id>]',
|
|
@@ -622,7 +644,18 @@ const SECTIONS: readonly { readonly title: string; readonly commands: readonly s
|
|
|
622
644
|
},
|
|
623
645
|
{
|
|
624
646
|
title: "Working",
|
|
625
|
-
commands: [
|
|
647
|
+
commands: [
|
|
648
|
+
"take",
|
|
649
|
+
"report",
|
|
650
|
+
"hand-in",
|
|
651
|
+
"comment",
|
|
652
|
+
"file",
|
|
653
|
+
"ask",
|
|
654
|
+
"answer",
|
|
655
|
+
"finish",
|
|
656
|
+
"fail",
|
|
657
|
+
"abandon",
|
|
658
|
+
],
|
|
626
659
|
},
|
|
627
660
|
{
|
|
628
661
|
title: "The documents: the project's pages of Markdown, in folders, by path",
|
package/src/cli/pack.ts
CHANGED
|
@@ -264,11 +264,20 @@ export function joinFolder(
|
|
|
264
264
|
if (first === undefined || last === undefined) {
|
|
265
265
|
return undefined;
|
|
266
266
|
}
|
|
267
|
+
// A file's path answers its outline (SkillCDN 0.4), its headings paged as a folder's entries are.
|
|
268
|
+
const outline =
|
|
269
|
+
first.outline === undefined
|
|
270
|
+
? undefined
|
|
271
|
+
: {
|
|
272
|
+
...first.outline,
|
|
273
|
+
headings: results.flatMap((result) => result.outline?.headings ?? []),
|
|
274
|
+
};
|
|
267
275
|
return {
|
|
268
276
|
...first,
|
|
269
277
|
mount: last.mount,
|
|
270
278
|
entries: results.flatMap((result) => result.entries),
|
|
271
279
|
nextCursor: last.nextCursor,
|
|
280
|
+
...(outline === undefined ? {} : { outline }),
|
|
272
281
|
};
|
|
273
282
|
}
|
|
274
283
|
|
|
@@ -304,10 +313,16 @@ export function formatSkillFind(found: RestSkillFind, summary: PackSummary | und
|
|
|
304
313
|
return renderFindResult(findResultOf(found.read, summary), CONSOLE_WORDS);
|
|
305
314
|
}
|
|
306
315
|
|
|
307
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* The pages of a file, as the console answered them, as the file whole; or the folder the path
|
|
318
|
+
* named. With `requested`, the one page is a section read as asked (`--offset`, `--limit`): the
|
|
319
|
+
* result carries the length asked for, so that the renderer says where the file continues rather
|
|
320
|
+
* than asking for the rest.
|
|
321
|
+
*/
|
|
308
322
|
export function joinFile(
|
|
309
323
|
pages: readonly RestSkillFile[],
|
|
310
324
|
summary: PackSummary,
|
|
325
|
+
requested?: number,
|
|
311
326
|
): FileResult | DirectoryResult | undefined {
|
|
312
327
|
const reads = pages.flatMap((page) => (page.read === null ? [] : [page.read]));
|
|
313
328
|
const first = reads[0];
|
|
@@ -331,6 +346,7 @@ export function joinFile(
|
|
|
331
346
|
content: files.map((file) => file.content).join(""),
|
|
332
347
|
nextOffset: closing.nextOffset,
|
|
333
348
|
totalLength: closing.totalLength,
|
|
349
|
+
...(requested === undefined ? {} : { requestedLength: requested }),
|
|
334
350
|
};
|
|
335
351
|
}
|
|
336
352
|
|
package/src/client.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type RestMemberPatch,
|
|
45
45
|
type RestMembers,
|
|
46
46
|
type RestMePatch,
|
|
47
|
+
type RestMyDecisions,
|
|
47
48
|
type RestPackConnect,
|
|
48
49
|
type RestPackConnectStarted,
|
|
49
50
|
type RestPackTokenInput,
|
|
@@ -101,6 +102,7 @@ import {
|
|
|
101
102
|
restMemberSchema,
|
|
102
103
|
restMembersSchema,
|
|
103
104
|
restMeSchema,
|
|
105
|
+
restMyDecisionsSchema,
|
|
104
106
|
restPackConnectSchema,
|
|
105
107
|
restPackConnectStartedSchema,
|
|
106
108
|
restPeopleSchema,
|
|
@@ -449,8 +451,13 @@ export interface ConsoleClient {
|
|
|
449
451
|
updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
|
|
450
452
|
/** Removes a category no task uses; for an administrator signed in. */
|
|
451
453
|
removeCategory(key: string): Promise<void>;
|
|
452
|
-
/** What is on the asker's plate across the projects they may see: their open tasks, and the decisions waiting. */
|
|
454
|
+
/** What is on the asker's plate across the projects they may see: their open tasks, and the decisions waiting for them. */
|
|
453
455
|
work(signal?: AbortSignal): Promise<RestWork>;
|
|
456
|
+
/** The decisions addressed to the asker (ADR-0042) across the projects they may see, or with `raised` those they raised for someone else: a page, the waiting first; or only those. */
|
|
457
|
+
myDecisions(
|
|
458
|
+
filter?: { readonly open?: boolean; readonly raised?: boolean } & Page,
|
|
459
|
+
signal?: AbortSignal,
|
|
460
|
+
): Promise<RestMyDecisions>;
|
|
454
461
|
/** Another person's plate, within what the asker may see: for an administrator, or an owner of a project they are in. */
|
|
455
462
|
personWork(personId: string, signal?: AbortSignal): Promise<RestPersonWork>;
|
|
456
463
|
/** The tokens of whoever asks. */
|
|
@@ -961,6 +968,19 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
961
968
|
removeCategory: (key) => request("DELETE", `${base}${categoryPath(key)}`, undefined, undefined),
|
|
962
969
|
work: (signal) =>
|
|
963
970
|
request("GET", `${base}${REST_ROUTES.work}`, undefined, restWorkSchema, signal),
|
|
971
|
+
myDecisions: (filter = {}, signal) =>
|
|
972
|
+
request(
|
|
973
|
+
"GET",
|
|
974
|
+
withQuery(REST_ROUTES.decisions, {
|
|
975
|
+
open: flag(filter.open),
|
|
976
|
+
raised: filter.raised === true ? "true" : undefined,
|
|
977
|
+
limit: number(filter.limit),
|
|
978
|
+
offset: number(filter.offset),
|
|
979
|
+
}),
|
|
980
|
+
undefined,
|
|
981
|
+
restMyDecisionsSchema,
|
|
982
|
+
signal,
|
|
983
|
+
),
|
|
964
984
|
personWork: (personId, signal) =>
|
|
965
985
|
request("GET", `${base}${personWorkPath(personId)}`, undefined, restPersonWorkSchema, signal),
|
|
966
986
|
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.
|
|
116
|
+
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.0" };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* The oldest console this command works with (ADR-0038): raised by the release that last
|
|
@@ -122,7 +122,7 @@ export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.3.2" };
|
|
|
122
122
|
* itself, naming the command to install instead; `pnpm check` holds it as it holds the
|
|
123
123
|
* console's floor.
|
|
124
124
|
*/
|
|
125
|
-
export const CONSOLE_AT_LEAST = "0.
|
|
125
|
+
export const CONSOLE_AT_LEAST = "0.4.0";
|
|
126
126
|
|
|
127
127
|
/** `common` from `atLeast` on, where that floor is within it; `common` as it is otherwise. */
|
|
128
128
|
function narrowed(common: VersionRange, atLeast: string | undefined): VersionRange {
|
package/src/components/board.tsx
CHANGED
|
@@ -59,8 +59,17 @@ export interface BoardProps {
|
|
|
59
59
|
readonly onEdit?: ((task: RestTask) => void) | undefined;
|
|
60
60
|
/** Called when a task is put away from its card's menu (ADR-0029, ADR-0030). Left out, the menu offers nothing of it. */
|
|
61
61
|
readonly onArchive?: ((task: RestTask) => void) | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The columns folded to a strip until a person opens them, with their count and the way to open
|
|
64
|
+
* them; a folded column still takes a card dragged onto it. What is finished, by default, so that
|
|
65
|
+
* the work at hand has the room; `[]` folds none.
|
|
66
|
+
*/
|
|
67
|
+
readonly folded?: readonly TaskState[] | undefined;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/** The columns a board folds unless told otherwise: the work that is over. */
|
|
71
|
+
export const FOLDED_STATES: readonly TaskState[] = ["done", "dropped"];
|
|
72
|
+
|
|
64
73
|
/** How far a mouse travels on a card before the press is a drag and not a click. */
|
|
65
74
|
const DRAG_SLACK = 4;
|
|
66
75
|
/**
|
|
@@ -160,7 +169,7 @@ export function TaskCard(props: TaskCardProps) {
|
|
|
160
169
|
<DueBadge dueOn={task.dueOn} state={task.state} today={props.today} />
|
|
161
170
|
)}
|
|
162
171
|
{task.openDecisions > 0 && (
|
|
163
|
-
<Badge tone="warning" title={words.board.decisionsTitle}>
|
|
172
|
+
<Badge tone="warning" kind="count" title={words.board.decisionsTitle}>
|
|
164
173
|
{words.board.decisions(task.openDecisions)}
|
|
165
174
|
</Badge>
|
|
166
175
|
)}
|
|
@@ -364,6 +373,19 @@ export function Board(props: BoardProps) {
|
|
|
364
373
|
const { onMove } = props;
|
|
365
374
|
const board = useRef<HTMLDivElement>(null);
|
|
366
375
|
const [drag, setDrag] = useState<Drag | null>(null);
|
|
376
|
+
// The foldable columns a person has opened on this board; the others of them stay folded.
|
|
377
|
+
const [opened, setOpened] = useState<ReadonlySet<TaskState>>(() => new Set());
|
|
378
|
+
const foldable = props.folded ?? FOLDED_STATES;
|
|
379
|
+
const toggle = (state: TaskState) =>
|
|
380
|
+
setOpened((before) => {
|
|
381
|
+
const after = new Set(before);
|
|
382
|
+
if (after.has(state)) {
|
|
383
|
+
after.delete(state);
|
|
384
|
+
} else {
|
|
385
|
+
after.add(state);
|
|
386
|
+
}
|
|
387
|
+
return after;
|
|
388
|
+
});
|
|
367
389
|
// What the pointer's release reads, so that the move is made outside a state update.
|
|
368
390
|
const current = useRef<Drag | null>(null);
|
|
369
391
|
current.current = drag;
|
|
@@ -511,38 +533,65 @@ export function Board(props: BoardProps) {
|
|
|
511
533
|
>
|
|
512
534
|
{TASK_STATES.map((state) => {
|
|
513
535
|
const tasks = props.tasks.filter((task) => task.state === state).sort(byDue);
|
|
536
|
+
const name = words.vocabulary.state[state];
|
|
537
|
+
const canFold = foldable.includes(state);
|
|
538
|
+
const folded = canFold && !opened.has(state);
|
|
514
539
|
return (
|
|
515
540
|
<section
|
|
516
541
|
key={state}
|
|
517
542
|
className={cx(
|
|
518
543
|
"sc-column",
|
|
519
544
|
`sc-column-${state}`,
|
|
545
|
+
folded && "sc-column-folded",
|
|
520
546
|
dragging?.over === state && "sc-column-over",
|
|
521
547
|
)}
|
|
522
|
-
aria-label={
|
|
548
|
+
aria-label={name}
|
|
523
549
|
data-column={state}
|
|
524
550
|
>
|
|
525
551
|
<header className="sc-column-header">
|
|
526
|
-
<h2 className="sc-column-title">{
|
|
552
|
+
<h2 className="sc-column-title">{name}</h2>
|
|
527
553
|
<span className="sc-column-count">{tasks.length}</span>
|
|
554
|
+
{canFold && (
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
className="sc-column-toggle"
|
|
558
|
+
aria-expanded={!folded}
|
|
559
|
+
aria-label={folded ? words.board.unfold(name) : words.board.fold(name)}
|
|
560
|
+
title={folded ? words.board.unfold(name) : words.board.fold(name)}
|
|
561
|
+
onClick={() => toggle(state)}
|
|
562
|
+
>
|
|
563
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
564
|
+
<path
|
|
565
|
+
d={folded ? "M6 4l4 4-4 4" : "M10 4l-4 4 4 4"}
|
|
566
|
+
fill="none"
|
|
567
|
+
stroke="currentColor"
|
|
568
|
+
strokeWidth="1.5"
|
|
569
|
+
strokeLinecap="round"
|
|
570
|
+
strokeLinejoin="round"
|
|
571
|
+
/>
|
|
572
|
+
</svg>
|
|
573
|
+
</button>
|
|
574
|
+
)}
|
|
528
575
|
</header>
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
576
|
+
{!folded && (
|
|
577
|
+
<div className="sc-column-body">
|
|
578
|
+
{tasks.map((task) => (
|
|
579
|
+
<TaskCard
|
|
580
|
+
key={task.id}
|
|
581
|
+
task={task}
|
|
582
|
+
href={props.taskHref(task)}
|
|
583
|
+
onOpen={props.onOpen}
|
|
584
|
+
today={props.today}
|
|
585
|
+
onPress={onMove === undefined ? undefined : press}
|
|
586
|
+
dragging={dragging?.task.id === task.id}
|
|
587
|
+
editHref={props.editHref?.(task)}
|
|
588
|
+
onEdit={props.onEdit}
|
|
589
|
+
onArchive={props.onArchive}
|
|
590
|
+
/>
|
|
591
|
+
))}
|
|
592
|
+
{tasks.length === 0 && state === "idea" && props.empty}
|
|
593
|
+
</div>
|
|
594
|
+
)}
|
|
546
595
|
</section>
|
|
547
596
|
);
|
|
548
597
|
})}
|
|
@@ -571,13 +620,43 @@ export function Board(props: BoardProps) {
|
|
|
571
620
|
);
|
|
572
621
|
}
|
|
573
622
|
|
|
574
|
-
/** What
|
|
623
|
+
/** What a task is found by: its title, its number, its owner and its workers, its category. */
|
|
624
|
+
function wordsOf(task: RestTask): string {
|
|
625
|
+
return [
|
|
626
|
+
task.title,
|
|
627
|
+
`#${task.number}`,
|
|
628
|
+
personName(task.owner),
|
|
629
|
+
task.owner.login,
|
|
630
|
+
...task.workers.flatMap((worker) => [personName(worker), worker.login]),
|
|
631
|
+
task.category?.name ?? "",
|
|
632
|
+
]
|
|
633
|
+
.join("\n")
|
|
634
|
+
.toLowerCase();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* What the filter keeps of the board: a category or none, a goal or none, a person, one's own,
|
|
639
|
+
* and the tasks that contain every word typed, whatever the case.
|
|
640
|
+
*/
|
|
575
641
|
export function filterTasks(
|
|
576
642
|
tasks: readonly RestTask[],
|
|
577
643
|
filter: BoardFilter,
|
|
578
644
|
me?: RestPerson | undefined,
|
|
579
645
|
): RestTask[] {
|
|
646
|
+
const wanted =
|
|
647
|
+
filter.words === undefined
|
|
648
|
+
? []
|
|
649
|
+
: filter.words
|
|
650
|
+
.toLowerCase()
|
|
651
|
+
.split(/\s+/)
|
|
652
|
+
.filter((word) => word.length > 0);
|
|
580
653
|
return tasks.filter((task) => {
|
|
654
|
+
if (wanted.length > 0) {
|
|
655
|
+
const found = wordsOf(task);
|
|
656
|
+
if (!wanted.every((word) => found.includes(word))) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
581
660
|
if (filter.category !== undefined && (task.category?.key ?? "") !== filter.category) {
|
|
582
661
|
return false;
|
|
583
662
|
}
|
|
@@ -633,6 +712,20 @@ export function BoardFilters(props: BoardFiltersProps) {
|
|
|
633
712
|
aria-label={words.aria}
|
|
634
713
|
onSubmit={(event) => event.preventDefault()}
|
|
635
714
|
>
|
|
715
|
+
<label className="sc-field sc-field-inline sc-filters-words">
|
|
716
|
+
<span className="sc-visually-hidden">{words.words}</span>
|
|
717
|
+
<input
|
|
718
|
+
className="sc-input"
|
|
719
|
+
type="search"
|
|
720
|
+
placeholder={words.words}
|
|
721
|
+
value={filter.words ?? ""}
|
|
722
|
+
onChange={(event) =>
|
|
723
|
+
event.target.value.trim() === ""
|
|
724
|
+
? change({}, "words")
|
|
725
|
+
: change({ words: event.target.value })
|
|
726
|
+
}
|
|
727
|
+
/>
|
|
728
|
+
</label>
|
|
636
729
|
<label className="sc-field sc-field-inline">
|
|
637
730
|
<span className="sc-visually-hidden">{words.anyCategory}</span>
|
|
638
731
|
<select
|