@skillcdn/console 0.3.2 → 0.4.1
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 -3
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +132 -18
- 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 +72 -21
- 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 +19 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -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/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +6 -3
- package/dist/components/markdown.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/pack-connect.d.ts +3 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +4 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/pack-token.d.ts +3 -1
- package/dist/components/pack-token.d.ts.map +1 -1
- package/dist/components/pack-token.js +1 -1
- package/dist/components/pack-token.js.map +1 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +14 -3
- 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/remote-pictures.d.ts +6 -0
- package/dist/components/remote-pictures.d.ts.map +1 -0
- package/dist/components/remote-pictures.js +8 -0
- package/dist/components/remote-pictures.js.map +1 -0
- 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/sign-in.d.ts.map +1 -1
- package/dist/components/sign-in.js +49 -2
- package/dist/components/sign-in.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 +13 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +21 -14
- package/dist/components/ui.js.map +1 -1
- package/dist/console.css +1686 -425
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +83 -13
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +15 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +18 -19
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +48 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +50 -3
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +47 -4
- 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/limits.d.ts +14 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +14 -0
- package/dist/limits.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 +5 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +5 -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 +272 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +33 -8
- package/dist/schemas.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +3 -2
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +11 -10
- package/docs/specs/rest.md +33 -17
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +5 -5
- 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/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +1 -1
- package/src/api.ts +10 -0
- package/src/cli/cli.ts +162 -19
- package/src/cli/format.ts +11 -4
- package/src/cli/help.ts +79 -20
- package/src/cli/pack.ts +17 -1
- package/src/client.ts +51 -2
- 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/markdown.tsx +6 -3
- package/src/components/my-decisions.tsx +138 -0
- package/src/components/pack-connect.tsx +10 -7
- package/src/components/pack-token.tsx +4 -1
- package/src/components/people.tsx +20 -8
- package/src/components/projects.tsx +7 -5
- package/src/components/remote-pictures.ts +19 -0
- package/src/components/roadmap.tsx +1 -1
- package/src/components/runs.tsx +5 -1
- package/src/components/shell.tsx +36 -1
- package/src/components/sign-in.tsx +114 -13
- package/src/components/task-list.tsx +1 -1
- package/src/components/task-view.tsx +48 -47
- package/src/components/ui.tsx +70 -14
- package/src/console.tsx +153 -32
- package/src/data.ts +37 -20
- package/src/i18n/en.ts +53 -3
- package/src/i18n/ko.ts +50 -4
- package/src/index.ts +3 -0
- package/src/limits.ts +14 -0
- package/src/router.ts +18 -1
- package/src/routes.ts +5 -1
- package/src/rules.ts +1 -1
- package/src/schemas.ts +37 -8
- package/src/styles/console.css +1686 -425
- package/src/web.ts +3 -2
package/src/console.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type RestDecisionInput,
|
|
23
23
|
type RestDecisionPatch,
|
|
24
24
|
type RestEvent,
|
|
25
|
+
type RestMyDecisions,
|
|
25
26
|
type RestProject,
|
|
26
27
|
type RestProjectInput,
|
|
27
28
|
type RestProjectPatch,
|
|
@@ -45,6 +46,7 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
45
46
|
import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
|
|
46
47
|
import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
|
|
47
48
|
import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
|
|
49
|
+
import { MyDecisions } from "./components/my-decisions.js";
|
|
48
50
|
import { PackTokenWays } from "./components/pack-token.js";
|
|
49
51
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
50
52
|
import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
|
|
@@ -56,6 +58,7 @@ import {
|
|
|
56
58
|
type ProjectListProps,
|
|
57
59
|
ProjectMark,
|
|
58
60
|
} from "./components/projects.js";
|
|
61
|
+
import { type RemotePictureSrc, RemotePicturesContext } from "./components/remote-pictures.js";
|
|
59
62
|
import { GoalTimeline } from "./components/roadmap.js";
|
|
60
63
|
import { RulesForm, RulesView } from "./components/rules.js";
|
|
61
64
|
import {
|
|
@@ -76,6 +79,7 @@ import {
|
|
|
76
79
|
Callout,
|
|
77
80
|
CategoryBadge,
|
|
78
81
|
EmptyState,
|
|
82
|
+
LinkButton,
|
|
79
83
|
personName,
|
|
80
84
|
Spinner,
|
|
81
85
|
StateBadge,
|
|
@@ -385,19 +389,6 @@ function NotWithToken() {
|
|
|
385
389
|
return <EmptyState title={words.title} body={words.body} />;
|
|
386
390
|
}
|
|
387
391
|
|
|
388
|
-
/** A link that reads as a button: where a form is, for instance. */
|
|
389
|
-
function LinkButton(props: {
|
|
390
|
-
readonly href: string;
|
|
391
|
-
readonly variant?: "primary" | "secondary";
|
|
392
|
-
readonly children: React.ReactNode;
|
|
393
|
-
}) {
|
|
394
|
-
return (
|
|
395
|
-
<a className={`sc-button sc-button-${props.variant ?? "secondary"}`} href={props.href}>
|
|
396
|
-
{props.children}
|
|
397
|
-
</a>
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
392
|
function ProjectsPage(props: {
|
|
402
393
|
readonly data: ConsoleData;
|
|
403
394
|
readonly components: ConsoleComponents;
|
|
@@ -406,14 +397,18 @@ function ProjectsPage(props: {
|
|
|
406
397
|
readonly making: boolean;
|
|
407
398
|
}) {
|
|
408
399
|
const words = useWords().projects;
|
|
409
|
-
const { data, components, navigation
|
|
400
|
+
const { data, components, navigation } = props;
|
|
410
401
|
const { busy, error, act } = useAction();
|
|
411
402
|
const href = (project: RestProject) => projectHref(project.key);
|
|
403
|
+
// A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
|
|
404
|
+
// address included.
|
|
405
|
+
const administrator = data.me?.person?.role === "admin";
|
|
406
|
+
const making = props.making && administrator;
|
|
412
407
|
return (
|
|
413
408
|
<>
|
|
414
409
|
<div className="sc-page-head">
|
|
415
410
|
<h1 className="sc-page-title">{words.title}</h1>
|
|
416
|
-
{!making && (
|
|
411
|
+
{administrator && !making && (
|
|
417
412
|
<LinkButton href={PATHS.newProject} variant="primary">
|
|
418
413
|
{words.newProject}
|
|
419
414
|
</LinkButton>
|
|
@@ -446,8 +441,12 @@ function ProjectsPage(props: {
|
|
|
446
441
|
empty={
|
|
447
442
|
<EmptyState
|
|
448
443
|
title={words.empty.title}
|
|
449
|
-
body={words.empty.body}
|
|
450
|
-
action={
|
|
444
|
+
body={administrator ? words.empty.body : words.empty.bodyMember}
|
|
445
|
+
action={
|
|
446
|
+
administrator ? (
|
|
447
|
+
<LinkButton href={PATHS.newProject}>{words.newProject}</LinkButton>
|
|
448
|
+
) : undefined
|
|
449
|
+
}
|
|
451
450
|
/>
|
|
452
451
|
}
|
|
453
452
|
/>
|
|
@@ -913,6 +912,19 @@ function ProjectPage(props: {
|
|
|
913
912
|
empty={<p className="sc-muted">{calendar.nothingOverdue}</p>}
|
|
914
913
|
/>
|
|
915
914
|
</section>
|
|
915
|
+
{open.length > 0 && (
|
|
916
|
+
<section className="sc-task-section" aria-label={calendar.workload.title}>
|
|
917
|
+
<h2 className="sc-section-title">{calendar.workload.title}</h2>
|
|
918
|
+
<Workload
|
|
919
|
+
tasks={shown}
|
|
920
|
+
today={now}
|
|
921
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
922
|
+
personHref={(person) =>
|
|
923
|
+
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
924
|
+
}
|
|
925
|
+
/>
|
|
926
|
+
</section>
|
|
927
|
+
)}
|
|
916
928
|
<section className="sc-task-section" aria-label={calendar.unscheduled}>
|
|
917
929
|
<h2 className="sc-section-title">
|
|
918
930
|
{calendar.unscheduled} <span className="sc-column-count">{unscheduled.length}</span>
|
|
@@ -926,19 +938,6 @@ function ProjectPage(props: {
|
|
|
926
938
|
/>
|
|
927
939
|
</section>
|
|
928
940
|
</div>
|
|
929
|
-
{open.length > 0 && (
|
|
930
|
-
<section className="sc-task-section" aria-label={calendar.workload.title}>
|
|
931
|
-
<h2 className="sc-section-title">{calendar.workload.title}</h2>
|
|
932
|
-
<Workload
|
|
933
|
-
tasks={shown}
|
|
934
|
-
today={now}
|
|
935
|
-
weekStart={weekStartOf(data.me?.workspace)}
|
|
936
|
-
personHref={(person) =>
|
|
937
|
-
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
938
|
-
}
|
|
939
|
-
/>
|
|
940
|
-
</section>
|
|
941
|
-
)}
|
|
942
941
|
</>
|
|
943
942
|
);
|
|
944
943
|
}
|
|
@@ -1169,7 +1168,9 @@ function ProjectPage(props: {
|
|
|
1169
1168
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1170
1169
|
<section className="sc-panel sc-panel-form" aria-label={words.decisions.raiseAria}>
|
|
1171
1170
|
<DecisionForm
|
|
1172
|
-
|
|
1171
|
+
findTasks={data.actions.findTasks}
|
|
1172
|
+
deciders={data.members.map((member) => member.person)}
|
|
1173
|
+
me={data.me?.person ?? undefined}
|
|
1173
1174
|
busy={busy}
|
|
1174
1175
|
onSubmit={(input: RestDecisionInput) =>
|
|
1175
1176
|
void act(async () => {
|
|
@@ -1440,6 +1441,7 @@ function ProjectSettingsPage(props: {
|
|
|
1440
1441
|
busy={busy}
|
|
1441
1442
|
onStart={data.actions.connectPackToken}
|
|
1442
1443
|
poll={data.actions.packTokenConnection}
|
|
1444
|
+
claim={data.actions.claimPackTokenConnection}
|
|
1443
1445
|
onCancelConnect={data.actions.cancelPackTokenConnection}
|
|
1444
1446
|
onConnected={() => navigation.replace(here)}
|
|
1445
1447
|
onSubmit={(token) =>
|
|
@@ -1662,6 +1664,7 @@ function WorkspaceSettingsPage(props: {
|
|
|
1662
1664
|
busy={busy}
|
|
1663
1665
|
onStart={data.actions.connectWorkspacePackToken}
|
|
1664
1666
|
poll={data.actions.workspacePackTokenConnection}
|
|
1667
|
+
claim={data.actions.claimWorkspacePackTokenConnection}
|
|
1665
1668
|
onCancelConnect={data.actions.cancelWorkspacePackTokenConnection}
|
|
1666
1669
|
onConnected={() => navigation.replace(PATHS.settings)}
|
|
1667
1670
|
onSubmit={(token) =>
|
|
@@ -2012,6 +2015,108 @@ function PlatePage(props: {
|
|
|
2012
2015
|
}
|
|
2013
2016
|
|
|
2014
2017
|
/** What is on the person's own plate, across their projects. */
|
|
2018
|
+
/** How many of a person's decisions are read at a time. */
|
|
2019
|
+
const MY_DECISIONS_PAGE = 50;
|
|
2020
|
+
|
|
2021
|
+
/** One kind of a person's decisions, read a page at a time, as the page shows it. */
|
|
2022
|
+
interface DecisionsRead {
|
|
2023
|
+
readonly items: RestMyDecisions["items"] | undefined;
|
|
2024
|
+
readonly more: boolean;
|
|
2025
|
+
readonly busy: boolean;
|
|
2026
|
+
readonly problem: string | undefined;
|
|
2027
|
+
readonly readMore: () => void;
|
|
2028
|
+
/** Reads the first page again, after an answer changed what waits. */
|
|
2029
|
+
readonly reload: () => void;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/** The decisions addressed to the person, or, with `raised`, those they raised for someone else (ADR-0042). */
|
|
2033
|
+
function useMyDecisions(
|
|
2034
|
+
myDecisions: ConsoleData["actions"]["myDecisions"],
|
|
2035
|
+
raised: boolean,
|
|
2036
|
+
couldNotLoad: string,
|
|
2037
|
+
): DecisionsRead {
|
|
2038
|
+
const [items, setItems] = useState<RestMyDecisions["items"] | undefined>(undefined);
|
|
2039
|
+
const [more, setMore] = useState(false);
|
|
2040
|
+
const [busy, setBusy] = useState(false);
|
|
2041
|
+
const [problem, setProblem] = useState<string | undefined>(undefined);
|
|
2042
|
+
const read = useCallback(
|
|
2043
|
+
async (offset: number, signal?: AbortSignal) => {
|
|
2044
|
+
setBusy(true);
|
|
2045
|
+
try {
|
|
2046
|
+
const page = await myDecisions({ raised, limit: MY_DECISIONS_PAGE, offset }, signal);
|
|
2047
|
+
if (signal?.aborted === true) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
setItems((before) => (offset === 0 ? page.items : [...(before ?? []), ...page.items]));
|
|
2051
|
+
setMore(page.more);
|
|
2052
|
+
} catch (failure: unknown) {
|
|
2053
|
+
if (signal?.aborted !== true) {
|
|
2054
|
+
setProblem(errorWords(failure, couldNotLoad));
|
|
2055
|
+
}
|
|
2056
|
+
} finally {
|
|
2057
|
+
if (signal?.aborted !== true) {
|
|
2058
|
+
setBusy(false);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
},
|
|
2062
|
+
[myDecisions, raised, couldNotLoad],
|
|
2063
|
+
);
|
|
2064
|
+
useEffect(() => {
|
|
2065
|
+
const controller = new AbortController();
|
|
2066
|
+
setItems(undefined);
|
|
2067
|
+
setProblem(undefined);
|
|
2068
|
+
void read(0, controller.signal);
|
|
2069
|
+
return () => controller.abort();
|
|
2070
|
+
}, [read]);
|
|
2071
|
+
const readMore = useCallback(() => void read(items?.length ?? 0), [read, items]);
|
|
2072
|
+
const reload = useCallback(() => void read(0), [read]);
|
|
2073
|
+
return { items, more, busy, problem, readMore, reload };
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
/**
|
|
2077
|
+
* A person's decisions (ADR-0042), laid out as a project's decisions page is: those waiting for
|
|
2078
|
+
* their answer, answered here, those they asked of others, and the record of both.
|
|
2079
|
+
*/
|
|
2080
|
+
function MyDecisionsPage(props: { readonly data: ConsoleData }) {
|
|
2081
|
+
const words = useWords();
|
|
2082
|
+
const { myDecisions, answerDecisionIn } = props.data.actions;
|
|
2083
|
+
const addressed = useMyDecisions(myDecisions, false, words.myDecisions.couldNotLoad);
|
|
2084
|
+
const raised = useMyDecisions(myDecisions, true, words.myDecisions.couldNotLoad);
|
|
2085
|
+
const { busy, error, act } = useAction();
|
|
2086
|
+
const problem = addressed.problem ?? raised.problem ?? error;
|
|
2087
|
+
const onAnswer = (projectKey: string, decision: RestDecision, input: RestAnswerInput) =>
|
|
2088
|
+
void act(async () => {
|
|
2089
|
+
await answerDecisionIn(projectKey, decision.id, input);
|
|
2090
|
+
addressed.reload();
|
|
2091
|
+
raised.reload();
|
|
2092
|
+
});
|
|
2093
|
+
return (
|
|
2094
|
+
<>
|
|
2095
|
+
<div className="sc-page-head">
|
|
2096
|
+
<h1 className="sc-page-title">{words.myDecisions.title}</h1>
|
|
2097
|
+
</div>
|
|
2098
|
+
{problem !== undefined && <Callout tone="danger">{problem}</Callout>}
|
|
2099
|
+
{addressed.items === undefined || raised.items === undefined ? (
|
|
2100
|
+
problem === undefined && (
|
|
2101
|
+
<div className="sc-loading">
|
|
2102
|
+
<Spinner label={words.myDecisions.loading} />
|
|
2103
|
+
</div>
|
|
2104
|
+
)
|
|
2105
|
+
) : addressed.items.length === 0 && raised.items.length === 0 ? (
|
|
2106
|
+
<EmptyState title={words.myDecisions.empty.title} body={words.myDecisions.empty.body} />
|
|
2107
|
+
) : (
|
|
2108
|
+
<MyDecisions
|
|
2109
|
+
addressed={{ items: addressed.items, more: addressed.more, onMore: addressed.readMore }}
|
|
2110
|
+
raised={{ items: raised.items, more: raised.more, onMore: raised.readMore }}
|
|
2111
|
+
decisionHref={decisionHref}
|
|
2112
|
+
onAnswer={onAnswer}
|
|
2113
|
+
busy={busy || addressed.busy || raised.busy}
|
|
2114
|
+
/>
|
|
2115
|
+
)}
|
|
2116
|
+
</>
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2015
2120
|
function WorkPage(props: {
|
|
2016
2121
|
readonly data: ConsoleData;
|
|
2017
2122
|
readonly components: ConsoleComponents;
|
|
@@ -2078,6 +2183,9 @@ function WorkspacePage(props: {
|
|
|
2078
2183
|
if (route.name === "work") {
|
|
2079
2184
|
return <WorkPage data={data} components={components} week={route.week} />;
|
|
2080
2185
|
}
|
|
2186
|
+
if (route.name === "my-decisions") {
|
|
2187
|
+
return <MyDecisionsPage data={data} />;
|
|
2188
|
+
}
|
|
2081
2189
|
if (route.name === "person-work") {
|
|
2082
2190
|
return <PersonWorkPage id={route.id} data={data} components={components} week={route.week} />;
|
|
2083
2191
|
}
|
|
@@ -2237,6 +2345,8 @@ function WorkspacePage(props: {
|
|
|
2237
2345
|
export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
2238
2346
|
const components: ConsoleComponents = { ...DEFAULT_COMPONENTS, ...config.components };
|
|
2239
2347
|
const client = config.client ?? createClient({ baseUrl: config.baseUrl ?? "" });
|
|
2348
|
+
/** Where the pages read a picture from elsewhere (ADR-0048): through this client's console. */
|
|
2349
|
+
const pictureSrc: RemotePictureSrc = (url) => client.pictureUrl(url);
|
|
2240
2350
|
const fallbackTitle = config.title ?? "Console";
|
|
2241
2351
|
const languages = config.languages ?? DEFAULT_LANGUAGES;
|
|
2242
2352
|
const pages = config.pages ?? [];
|
|
@@ -2321,6 +2431,11 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2321
2431
|
current: route.name === "projects" || route.name === "new-project",
|
|
2322
2432
|
},
|
|
2323
2433
|
{ href: PATHS.work, label: words.nav.work, current: route.name === "work" },
|
|
2434
|
+
{
|
|
2435
|
+
href: PATHS.decisions,
|
|
2436
|
+
label: words.nav.myDecisions,
|
|
2437
|
+
current: route.name === "my-decisions",
|
|
2438
|
+
},
|
|
2324
2439
|
{
|
|
2325
2440
|
href: PATHS.people,
|
|
2326
2441
|
label: words.nav.people,
|
|
@@ -2526,7 +2641,13 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2526
2641
|
</components.Shell>
|
|
2527
2642
|
);
|
|
2528
2643
|
}
|
|
2529
|
-
return
|
|
2644
|
+
return (
|
|
2645
|
+
<LanguageContext.Provider value={pack}>
|
|
2646
|
+
<RemotePicturesContext.Provider value={pictureSrc}>
|
|
2647
|
+
{content}
|
|
2648
|
+
</RemotePicturesContext.Provider>
|
|
2649
|
+
</LanguageContext.Provider>
|
|
2650
|
+
);
|
|
2530
2651
|
}
|
|
2531
2652
|
|
|
2532
2653
|
return {
|
package/src/data.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type RestMemberInput,
|
|
33
33
|
type RestMemberPatch,
|
|
34
34
|
type RestMePatch,
|
|
35
|
+
type RestMyDecisions,
|
|
35
36
|
type RestOrganization,
|
|
36
37
|
type RestPackConnect,
|
|
37
38
|
type RestPackConnectStarted,
|
|
@@ -65,12 +66,12 @@ import {
|
|
|
65
66
|
|
|
66
67
|
// What the console shows, and how it stays current: the workspace (who is signed in, the
|
|
67
68
|
// projects, the people, the tokens) is loaded once; the project the page is on is loaded whole,
|
|
68
|
-
// its feed
|
|
69
|
+
// its feed as it stands, the newest page, and from then on the project's stream says when something
|
|
69
70
|
// changed: on every event the lists are loaded again, which is the boring way to be right.
|
|
70
71
|
|
|
71
72
|
/** How many events the feed keeps on the page. */
|
|
72
73
|
const FEED_KEEP = 200;
|
|
73
|
-
/** How many pages of
|
|
74
|
+
/** How many pages of a task's own events are read for its thread, which is read from the beginning. */
|
|
74
75
|
const FEED_MAX_PAGES = 20;
|
|
75
76
|
/** How long changes that arrive together are gathered before the lists are loaded once. */
|
|
76
77
|
const RELOAD_DELAY_MS = 150;
|
|
@@ -88,8 +89,10 @@ export interface ConsoleActions {
|
|
|
88
89
|
removePackToken(): Promise<RestProject>;
|
|
89
90
|
/** Asks the deployment to connect the project's pack with a code a person approves (ADR-0036); for an owner. */
|
|
90
91
|
connectPackToken(): Promise<RestPackConnectStarted>;
|
|
91
|
-
/** How that connection stands
|
|
92
|
+
/** How that connection stands, read and changed nothing. */
|
|
92
93
|
packTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
94
|
+
/** Asks the console to claim the token at the deployment (ADR-0046); once connected, the project is the one answered. */
|
|
95
|
+
claimPackTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
93
96
|
cancelPackTokenConnection(): Promise<void>;
|
|
94
97
|
addMember(input: RestMemberInput): Promise<RestMember>;
|
|
95
98
|
updateMember(personId: string, patch: RestMemberPatch): Promise<RestMember>;
|
|
@@ -122,6 +125,8 @@ export interface ConsoleActions {
|
|
|
122
125
|
): Promise<RestGoals>;
|
|
123
126
|
raiseDecision(input: RestDecisionInput): Promise<RestDecision>;
|
|
124
127
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
128
|
+
/** Answers a decision of any project the person may see, by its project's key: for the person's own page of decisions (ADR-0042). */
|
|
129
|
+
answerDecisionIn(projectKey: string, id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
125
130
|
/** Grows a decision's record: its context, or what followed. */
|
|
126
131
|
updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
|
|
127
132
|
/** Sets a goal on the roadmap of the project the page is on; for an owner. */
|
|
@@ -129,6 +134,17 @@ export interface ConsoleActions {
|
|
|
129
134
|
updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
|
|
130
135
|
/** What is on the person's plate across their projects, read when the page asks. */
|
|
131
136
|
work(signal?: AbortSignal): Promise<RestWork>;
|
|
137
|
+
/** The decisions addressed to the person (ADR-0042) across their projects, a page at a time, read when the page asks. */
|
|
138
|
+
myDecisions(
|
|
139
|
+
filter: {
|
|
140
|
+
readonly open?: boolean;
|
|
141
|
+
/** Those the person raised for someone else, in place of those addressed to them. */
|
|
142
|
+
readonly raised?: boolean;
|
|
143
|
+
readonly limit: number;
|
|
144
|
+
readonly offset: number;
|
|
145
|
+
},
|
|
146
|
+
signal?: AbortSignal,
|
|
147
|
+
): Promise<RestMyDecisions>;
|
|
132
148
|
/** The categories tasks are sorted into: kept by an administrator. */
|
|
133
149
|
createCategory(input: RestCategoryInput): Promise<RestCategory>;
|
|
134
150
|
updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
|
|
@@ -163,6 +179,7 @@ export interface ConsoleActions {
|
|
|
163
179
|
removeWorkspacePackToken(): Promise<RestWorkspace>;
|
|
164
180
|
connectWorkspacePackToken(): Promise<RestPackConnectStarted>;
|
|
165
181
|
workspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
182
|
+
claimWorkspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
166
183
|
cancelWorkspacePackTokenConnection(): Promise<void>;
|
|
167
184
|
/** One person of the workspace by their exact login, or nobody: what an owner lists someone the People page does not show by. */
|
|
168
185
|
findPerson(login: string, signal?: AbortSignal): Promise<RestPerson | undefined>;
|
|
@@ -233,23 +250,18 @@ export interface ConsoleData {
|
|
|
233
250
|
reload(): void;
|
|
234
251
|
}
|
|
235
252
|
|
|
236
|
-
/**
|
|
253
|
+
/**
|
|
254
|
+
* The feed of a project as it stands: its newest page, oldest first, from which the stream
|
|
255
|
+
* goes on. Read in one request, where reading it from the beginning was a round trip per
|
|
256
|
+
* hundred events, each waited for before the board was shown.
|
|
257
|
+
*/
|
|
237
258
|
async function readFeed(
|
|
238
259
|
client: ConsoleClient,
|
|
239
260
|
key: string,
|
|
240
261
|
signal: AbortSignal,
|
|
241
262
|
): Promise<RestEvent[]> {
|
|
242
|
-
const events:
|
|
243
|
-
|
|
244
|
-
for (let page = 0; page < FEED_MAX_PAGES; page += 1) {
|
|
245
|
-
const result = await client.project(key).events(after, {}, signal);
|
|
246
|
-
events.push(...result.items);
|
|
247
|
-
after = result.items.at(-1)?.id ?? after;
|
|
248
|
-
if (!result.more) {
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return events.slice(-FEED_KEEP);
|
|
263
|
+
const result = await client.project(key).events(0, { latest: true }, signal);
|
|
264
|
+
return result.items.slice(-FEED_KEEP);
|
|
253
265
|
}
|
|
254
266
|
|
|
255
267
|
/** How many pages of the current tasks are read before the board is shown as it stands. */
|
|
@@ -454,7 +466,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
454
466
|
return () => controller.abort();
|
|
455
467
|
}, [client, generation, refreshWorkspace]);
|
|
456
468
|
|
|
457
|
-
// The project the page is on: its lists, its feed
|
|
469
|
+
// The project the page is on: its lists, its feed as it stands, the newest page, and its stream from
|
|
458
470
|
// where the feed ends. Another project, or none, starts over.
|
|
459
471
|
// biome-ignore lint/correctness/useExhaustiveDependencies: `generation` is the trigger
|
|
460
472
|
useEffect(() => {
|
|
@@ -598,8 +610,9 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
598
610
|
return changed;
|
|
599
611
|
},
|
|
600
612
|
connectPackToken: () => scope().connectPackToken(),
|
|
601
|
-
|
|
602
|
-
|
|
613
|
+
packTokenConnection: (signal) => scope().packTokenConnection(signal),
|
|
614
|
+
async claimPackTokenConnection(signal) {
|
|
615
|
+
const state = await scope().claimPackTokenConnection(signal);
|
|
603
616
|
if (state.status === "connected") {
|
|
604
617
|
setProject(state.project);
|
|
605
618
|
}
|
|
@@ -656,6 +669,8 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
656
669
|
await refreshProject();
|
|
657
670
|
return decision;
|
|
658
671
|
},
|
|
672
|
+
answerDecisionIn: (projectKey, id, input) =>
|
|
673
|
+
client.project(projectKey).answerDecision(id, input),
|
|
659
674
|
async answerDecision(id, input) {
|
|
660
675
|
const decision = await scope().answerDecision(id, input);
|
|
661
676
|
await refreshProject();
|
|
@@ -679,6 +694,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
679
694
|
return goal;
|
|
680
695
|
},
|
|
681
696
|
work: (signal) => client.work(signal),
|
|
697
|
+
myDecisions: (filter, signal) => client.myDecisions(filter, signal),
|
|
682
698
|
async createCategory(input) {
|
|
683
699
|
const category = await client.createCategory(input);
|
|
684
700
|
setCategories((await client.categories()).items);
|
|
@@ -739,8 +755,9 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
739
755
|
return workspace;
|
|
740
756
|
},
|
|
741
757
|
connectWorkspacePackToken: () => client.connectWorkspacePackToken(),
|
|
742
|
-
|
|
743
|
-
|
|
758
|
+
workspacePackTokenConnection: (signal) => client.workspacePackTokenConnection(signal),
|
|
759
|
+
async claimWorkspacePackTokenConnection(signal) {
|
|
760
|
+
const state = await client.claimWorkspacePackTokenConnection(signal);
|
|
744
761
|
if (state.status === "connected") {
|
|
745
762
|
const { workspace } = state;
|
|
746
763
|
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
package/src/i18n/en.ts
CHANGED
|
@@ -131,6 +131,7 @@ export const en = {
|
|
|
131
131
|
nav: {
|
|
132
132
|
projects: "Projects",
|
|
133
133
|
work: "My work",
|
|
134
|
+
myDecisions: "My decisions",
|
|
134
135
|
people: "People",
|
|
135
136
|
agents: "Agents",
|
|
136
137
|
board: "Board",
|
|
@@ -156,7 +157,15 @@ export const en = {
|
|
|
156
157
|
|
|
157
158
|
signIn: {
|
|
158
159
|
lead: "Join an organization made of AI agents.",
|
|
159
|
-
|
|
160
|
+
signInWith: (provider: string) => `Sign in with ${provider}`,
|
|
161
|
+
/** The button while the browser is on its way to the provider. */
|
|
162
|
+
going: (provider: string) => `Going to ${provider}…`,
|
|
163
|
+
/** The line under a button: who gets in through that way. */
|
|
164
|
+
admits: {
|
|
165
|
+
organization: (name: string) => `For the members of ${name}, the organization at GitHub.`,
|
|
166
|
+
domains: (domains: string) => `For accounts at ${domains}.`,
|
|
167
|
+
listed: "For the people an administrator added.",
|
|
168
|
+
},
|
|
160
169
|
nobody: {
|
|
161
170
|
title: "Nobody can sign in here yet",
|
|
162
171
|
body: "Signing in is not configured on this console. Whoever runs it sets it up as deploy/README.md describes.",
|
|
@@ -175,6 +184,8 @@ export const en = {
|
|
|
175
184
|
empty: {
|
|
176
185
|
title: "No projects yet",
|
|
177
186
|
body: "Make the first one: what it is for, and who is in it. Or ask an owner to add you to theirs.",
|
|
187
|
+
bodyMember:
|
|
188
|
+
"An administrator makes the projects. Ask one for the first, or an owner to add you to theirs.",
|
|
178
189
|
},
|
|
179
190
|
list: "Projects",
|
|
180
191
|
private: "Private",
|
|
@@ -279,8 +290,11 @@ export const en = {
|
|
|
279
290
|
menu: "Task menu",
|
|
280
291
|
decisions: (n: number) => count(n, "decision", "decisions"),
|
|
281
292
|
decisionsTitle: "Decisions waiting for a person",
|
|
293
|
+
unfold: (column: string) => `Open ${column}`,
|
|
294
|
+
fold: (column: string) => `Fold ${column}`,
|
|
282
295
|
filters: {
|
|
283
296
|
aria: "Narrow the board",
|
|
297
|
+
words: "Title, number or person",
|
|
284
298
|
everyone: "Everyone",
|
|
285
299
|
mine: "Mine",
|
|
286
300
|
anyCategory: "Any category",
|
|
@@ -380,6 +394,8 @@ export const en = {
|
|
|
380
394
|
record: "The record",
|
|
381
395
|
openRecord: "Open",
|
|
382
396
|
waitingForPerson: "Waiting for a person.",
|
|
397
|
+
waitingFor: (name: string) => `Waiting for ${name}.`,
|
|
398
|
+
peopleLabel: (asker: string, decider: string) => `asked by ${asker}, for ${decider}`,
|
|
383
399
|
options: "Options",
|
|
384
400
|
notePlaceholder: "A word about it, if any",
|
|
385
401
|
own: "Something else",
|
|
@@ -402,6 +418,9 @@ export const en = {
|
|
|
402
418
|
contextPlaceholder: "What a person needs to know to answer, in Markdown.",
|
|
403
419
|
about: "About",
|
|
404
420
|
noTask: "No task in particular",
|
|
421
|
+
decider: "Who decides",
|
|
422
|
+
deciderPlaceholder: "You, unless you name someone",
|
|
423
|
+
me: "(you)",
|
|
405
424
|
options: "Options",
|
|
406
425
|
option: (n: number) => `Option ${n}`,
|
|
407
426
|
addOption: "Add an option",
|
|
@@ -409,6 +428,23 @@ export const en = {
|
|
|
409
428
|
},
|
|
410
429
|
},
|
|
411
430
|
|
|
431
|
+
myDecisions: {
|
|
432
|
+
title: "My decisions",
|
|
433
|
+
loading: "Loading your decisions",
|
|
434
|
+
couldNotLoad: "Your decisions could not be loaded.",
|
|
435
|
+
empty: {
|
|
436
|
+
title: "No decision is yours",
|
|
437
|
+
body: "A decision for you, by you or your agent or by someone who named you, and one you asked of someone else, show up here.",
|
|
438
|
+
},
|
|
439
|
+
waiting: "Waiting for you",
|
|
440
|
+
noneWaiting: "Nothing waits for your answer.",
|
|
441
|
+
raised: "Asked of others",
|
|
442
|
+
noneRaised: "You have asked nothing of others.",
|
|
443
|
+
answered: "Answered",
|
|
444
|
+
noneAnswered: "Nothing answered yet.",
|
|
445
|
+
more: "Show more",
|
|
446
|
+
},
|
|
447
|
+
|
|
412
448
|
calendar: {
|
|
413
449
|
title: "Calendar",
|
|
414
450
|
grid: "The month",
|
|
@@ -421,6 +457,11 @@ export const en = {
|
|
|
421
457
|
nothingOverdue: "Nothing is overdue.",
|
|
422
458
|
unscheduled: "No day set",
|
|
423
459
|
allScheduled: "Every open task has a day.",
|
|
460
|
+
more: (n: number) => `${n} more`,
|
|
461
|
+
fewer: "Fewer",
|
|
462
|
+
onDay: (day: string) => `Tasks on ${day}`,
|
|
463
|
+
nothingOnDay: "Nothing is on this day.",
|
|
464
|
+
close: "Close",
|
|
424
465
|
week: {
|
|
425
466
|
title: "The week",
|
|
426
467
|
previous: "The week before",
|
|
@@ -512,8 +553,8 @@ export const en = {
|
|
|
512
553
|
thisWeek: "Due this week",
|
|
513
554
|
later: "Later",
|
|
514
555
|
noDay: "No day set",
|
|
515
|
-
decisions: "Decisions waiting for
|
|
516
|
-
noDecisions: "No decision waits.",
|
|
556
|
+
decisions: "Decisions waiting for you",
|
|
557
|
+
noDecisions: "No decision waits for your answer.",
|
|
517
558
|
inProject: (project: string) => `in ${project}`,
|
|
518
559
|
},
|
|
519
560
|
|
|
@@ -677,6 +718,8 @@ export const en = {
|
|
|
677
718
|
runFinished: (task: string) => `finished ${task}`,
|
|
678
719
|
runFailed: (task: string) => `failed on ${task}`,
|
|
679
720
|
runAbandoned: (task: string) => `gave up on ${task}`,
|
|
721
|
+
runUnheard: (task: string, agent: string) =>
|
|
722
|
+
`ended the run of ${agent} on ${task}, not heard from for too long`,
|
|
680
723
|
documentWritten: (page: string, version: number | undefined) =>
|
|
681
724
|
`wrote ${page}${version === undefined ? "" : `, version ${version}`}`,
|
|
682
725
|
documentArchived: (page: string) => `archived ${page}`,
|
|
@@ -801,6 +844,7 @@ export const en = {
|
|
|
801
844
|
version: (n: number) => `v${n}`,
|
|
802
845
|
versionWord: "version",
|
|
803
846
|
writtenBy: "written by",
|
|
847
|
+
author: "Written by",
|
|
804
848
|
versionOf: (n: number, of: number) => `Version ${n} of ${of}`,
|
|
805
849
|
showLatest: "Show the latest",
|
|
806
850
|
archivedNotice: "This page is archived: out of the folders and the search, and not written to.",
|
|
@@ -813,6 +857,8 @@ export const en = {
|
|
|
813
857
|
decisionPrefix: "Decision: ",
|
|
814
858
|
files: "Files",
|
|
815
859
|
fileToAttach: "A file to attach",
|
|
860
|
+
chooseFile: "Choose a file",
|
|
861
|
+
noFileChosen: "No file chosen",
|
|
816
862
|
labelPlaceholder: "What to call it, if not its name",
|
|
817
863
|
attach: "Attach",
|
|
818
864
|
by: "by",
|
|
@@ -853,6 +899,10 @@ export const en = {
|
|
|
853
899
|
domain: "Of the domain",
|
|
854
900
|
listed: "Added by an administrator",
|
|
855
901
|
} satisfies Record<MembershipSource, string>,
|
|
902
|
+
/** A person the organization let in, by the organization's name: never read as their own. */
|
|
903
|
+
ofOrganization: (name: string) => `Member of ${name}`,
|
|
904
|
+
/** A person a Workspace domain let in, by the domain of their login. */
|
|
905
|
+
ofDomain: (domain: string) => `Account at ${domain}`,
|
|
856
906
|
at: { gh: "at GitHub", google: "at Google" } satisfies Record<ProviderKey, string>,
|
|
857
907
|
organization: (name: string) => `The organization ${name} at GitHub says who is in.`,
|
|
858
908
|
organizationRead: "Last read",
|