@skillcdn/console 0.1.14 → 0.1.16
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 +5 -4
- package/SKILLCDN.md +2 -0
- package/dist/agent-name.d.ts +3 -0
- package/dist/agent-name.d.ts.map +1 -0
- package/dist/agent-name.js +9 -0
- package/dist/agent-name.js.map +1 -0
- package/dist/api.d.ts +6 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +225 -24
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +14 -4
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +59 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +28 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -2
- package/dist/client.js.map +1 -1
- package/dist/components/calendar.d.ts +35 -2
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +80 -31
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/connect.d.ts +4 -1
- package/dist/components/connect.d.ts.map +1 -1
- package/dist/components/connect.js +7 -3
- package/dist/components/connect.js.map +1 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +2 -2
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts +5 -0
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -2
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +10 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/people.d.ts +24 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +35 -3
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts +10 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +8 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/rules.d.ts +40 -0
- package/dist/components/rules.d.ts.map +1 -0
- package/dist/components/rules.js +46 -0
- package/dist/components/rules.js.map +1 -0
- package/dist/components/runs.d.ts +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +2 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +5 -1
- package/dist/components/skills.js.map +1 -1
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +1 -1
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +2 -9
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +13 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +5 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +1 -1
- package/dist/components/work.js.map +1 -1
- package/dist/console.css +263 -17
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +197 -43
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +19 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +27 -1
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +84 -9
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +84 -13
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +84 -13
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +30 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +50 -10
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +14 -4
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +17 -2
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +28 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +102 -0
- package/dist/rules.js.map +1 -0
- package/dist/schemas.d.ts +652 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -6
- 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 +21 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +13 -9
- package/docs/specs/rest.md +33 -9
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +1 -1
- package/skills/deploying-a-console/SKILL.md +5 -5
- package/skills/deploying-a-console/assets/secrets.example.json +2 -1
- package/skills/deploying-a-console/references/access.md +10 -6
- package/skills/deploying-a-console/references/resources.md +10 -7
- package/skills/working-the-board/SKILL.md +10 -9
- package/skills/working-the-board/references/project-rules.md +36 -0
- package/src/agent-name.ts +9 -0
- package/src/api.ts +37 -0
- package/src/cli/cli.ts +264 -26
- package/src/cli/format.ts +74 -10
- package/src/cli/help.ts +31 -11
- package/src/cli/main.ts +5 -0
- package/src/client.ts +47 -1
- package/src/components/calendar.tsx +252 -51
- package/src/components/connect.tsx +12 -6
- package/src/components/decision-list.tsx +2 -1
- package/src/components/documents.tsx +7 -3
- package/src/components/event-feed.tsx +16 -3
- package/src/components/people.tsx +244 -35
- package/src/components/projects.tsx +37 -14
- package/src/components/rules.tsx +167 -0
- package/src/components/runs.tsx +4 -2
- package/src/components/skills.tsx +22 -10
- package/src/components/task-form.tsx +1 -0
- package/src/components/tokens.tsx +2 -14
- package/src/components/ui.tsx +25 -2
- package/src/components/work.tsx +34 -28
- package/src/console.tsx +524 -45
- package/src/data.ts +52 -2
- package/src/i18n/en.ts +94 -14
- package/src/i18n/ko.ts +90 -14
- package/src/index.ts +16 -1
- package/src/limits.ts +4 -0
- package/src/router.ts +67 -12
- package/src/routes.ts +23 -3
- package/src/rules.ts +109 -0
- package/src/schemas.ts +121 -4
- package/src/styles/console.css +263 -17
- package/src/vocabulary.ts +30 -0
package/dist/console.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StrictMode, useCallback, useEffect, useMemo, useState, } from "react";
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
|
-
import { createClient, OPEN_TASK_STATES, } from "./api.js";
|
|
4
|
+
import { ApiError, createClient, OPEN_TASK_STATES, PROJECT_RULES_TEMPLATE, REFERENCE_RULES, RULES_PATH, RULES_TITLE, } from "./api.js";
|
|
5
5
|
import { Board, BoardFilters, byDue, filterTasks } from "./components/board.js";
|
|
6
6
|
import { Calendar, Workload } from "./components/calendar.js";
|
|
7
7
|
import { CategoryList } from "./components/categories.js";
|
|
@@ -10,8 +10,9 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
10
10
|
import { DecisionCard, DecisionList } from "./components/decision-list.js";
|
|
11
11
|
import { EventFeed } from "./components/event-feed.js";
|
|
12
12
|
import { GoalForm, GoalList, GoalView } from "./components/goals.js";
|
|
13
|
-
import { PeopleList } from "./components/people.js";
|
|
13
|
+
import { ListingForm, PeopleList } from "./components/people.js";
|
|
14
14
|
import { MemberList, ProjectForm, ProjectList, ProjectMark, } from "./components/projects.js";
|
|
15
|
+
import { RulesForm, RulesView } from "./components/rules.js";
|
|
15
16
|
import { isPlainClick, Shell, } from "./components/shell.js";
|
|
16
17
|
import { SignIn } from "./components/sign-in.js";
|
|
17
18
|
import { SkillList } from "./components/skills.js";
|
|
@@ -19,14 +20,14 @@ import { TaskForm } from "./components/task-form.js";
|
|
|
19
20
|
import { TaskList } from "./components/task-list.js";
|
|
20
21
|
import { TaskView } from "./components/task-view.js";
|
|
21
22
|
import { NewToken, TokenForm, TokenList } from "./components/tokens.js";
|
|
22
|
-
import { Button, Callout, EmptyState, personName, Spinner } from "./components/ui.js";
|
|
23
|
+
import { Button, Callout, CategoryBadge, EmptyState, personName, Spinner, } from "./components/ui.js";
|
|
23
24
|
import { WorkList } from "./components/work.js";
|
|
24
25
|
import { ConnectPage } from "./connect-page.js";
|
|
25
26
|
import { useConsoleData } from "./data.js";
|
|
26
27
|
import { monthOf, today } from "./dates.js";
|
|
27
28
|
import { DocsPage } from "./docs-page.js";
|
|
28
29
|
import { chooseLanguage, DEFAULT_LANGUAGES, LanguageContext, useWords, } from "./i18n/index.js";
|
|
29
|
-
import { boardHref, calendarHref, customPageHref, decisionHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, projectHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
|
|
30
|
+
import { boardHref, calendarHref, customPageHref, decisionHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, personWorkHref, projectHref, projectSettingsHref, settingsHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
|
|
30
31
|
import { projectPath } from "./routes.js";
|
|
31
32
|
import { errorWords, useAction } from "./use-action.js";
|
|
32
33
|
export const DEFAULT_COMPONENTS = {
|
|
@@ -192,7 +193,7 @@ function ProjectsPage(props) {
|
|
|
192
193
|
const { data, components, navigation, making } = props;
|
|
193
194
|
const { busy, error, act } = useAction();
|
|
194
195
|
const href = (project) => projectHref(project.key);
|
|
195
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), !making && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.form.aria, children: _jsx(ProjectForm, { busy: busy, onSubmit: (input, icon) => void act(async () => {
|
|
196
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), !making && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.form.aria, children: _jsx(ProjectForm, { busy: busy, skillsSource: data.me?.workspace.skills.source, organizationAddress: data.me?.workspace.skills.address ?? null, onSubmit: (input, icon) => void act(async () => {
|
|
196
197
|
const made = await data.actions.createProject(input, icon instanceof File ? uploadOf(icon) : undefined);
|
|
197
198
|
navigation.replace(projectHref(made.key));
|
|
198
199
|
}), onCancel: () => navigation.back(PATHS.projects) }) })), _jsx(components.ProjectList, { projects: data.projects, projectHref: href, onOpen: (project) => navigation.go(href(project)), empty: _jsx(EmptyState, { title: words.empty.title, body: words.empty.body, action: _jsx(LinkButton, { href: PATHS.newProject, children: words.newProject }) }) })] }));
|
|
@@ -235,7 +236,7 @@ function TaskPage(props) {
|
|
|
235
236
|
return (_jsx(EmptyState, { title: words.task.noSuch.title, body: words.task.noSuch.body, action: _jsx(LinkButton, { href: projectHref(key), children: words.nav.board }) }));
|
|
236
237
|
}
|
|
237
238
|
const here = taskHref(key, task.number);
|
|
238
|
-
return (_jsx(components.TaskView, { task: task, people: data.people, tasks: data.tasks, decisions: data.decisions, runs: data.runs, history: history, categories: data.categories, goals: data.goals, taskHref: (candidate) => taskHref(key, candidate.number), decisionHref: (decisionId) => decisionHref(key, decisionId), goalHref: (goalId) => goalHref(key, goalId), fileHref: (artifact) => projectPath(key, "files", artifact.id), docHref: (path) => docHref(key, path), form: route.form, formHref: (form) => taskHref(key, task.number, form), onCancel: () => navigation.back(here), busy: busy, error: error, onChange: (target, patch) => void act(async () => {
|
|
239
|
+
return (_jsx(components.TaskView, { task: task, people: data.people.filter(inWorkspace), tasks: data.tasks, decisions: data.decisions, runs: data.runs, history: history, categories: data.categories, goals: data.goals, taskHref: (candidate) => taskHref(key, candidate.number), decisionHref: (decisionId) => decisionHref(key, decisionId), goalHref: (goalId) => goalHref(key, goalId), fileHref: (artifact) => projectPath(key, "files", artifact.id), docHref: (path) => docHref(key, path), form: route.form, formHref: (form) => taskHref(key, task.number, form), onCancel: () => navigation.back(here), busy: busy, error: error, onChange: (target, patch) => void act(async () => {
|
|
239
240
|
await data.actions.updateTask(target.id, patch);
|
|
240
241
|
navigation.replace(here);
|
|
241
242
|
}), onMove: (target, state) => void act(() => data.actions.updateTask(target.id, { state })), onRaiseDecision: (input) => void act(async () => {
|
|
@@ -258,10 +259,10 @@ function ProjectPage(props) {
|
|
|
258
259
|
if (route.name === "board") {
|
|
259
260
|
const me = data.me?.person ?? undefined;
|
|
260
261
|
const shown = filterTasks(data.tasks, route.filter, me);
|
|
261
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: project.name }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), project.description.length > 0 && _jsx("p", { className: "sc-lead", children: project.description }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people, me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(boardHref(key, filter)) })), _jsx(components.Board, { tasks: shown, taskHref: href, onOpen: (task) => navigation.go(href(task)), onMove: onMove, empty: data.tasks.length === 0 ? (_jsx(EmptyState, { title: words.board.empty.title, body: words.board.empty.body, action: _jsx(LinkButton, { href: newTaskHref(key), children: words.board.writeTask }) })) : undefined })] }));
|
|
262
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: project.name }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), project.description.length > 0 && _jsx("p", { className: "sc-lead", children: project.description }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people.filter(inWorkspace), me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(boardHref(key, filter)) })), _jsx(components.Board, { tasks: shown, taskHref: href, onOpen: (task) => navigation.go(href(task)), onMove: onMove, empty: data.tasks.length === 0 ? (_jsx(EmptyState, { title: words.board.empty.title, body: words.board.empty.body, action: _jsx(LinkButton, { href: newTaskHref(key), children: words.board.writeTask }) })) : undefined })] }));
|
|
262
263
|
}
|
|
263
264
|
if (route.name === "new-task") {
|
|
264
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.task.newTitle }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.task.newAria, children: _jsx(TaskForm, { people: data.people, parents: data.tasks, categories: data.categories, goals: data.goals, busy: busy, onSubmit: (input) => void act(async () => {
|
|
265
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.task.newTitle }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.task.newAria, children: _jsx(TaskForm, { people: data.people.filter(inWorkspace), parents: data.tasks, categories: data.categories, goals: data.goals, busy: busy, onSubmit: (input) => void act(async () => {
|
|
265
266
|
const made = await data.actions.createTask(input);
|
|
266
267
|
navigation.replace(taskHref(key, made.number));
|
|
267
268
|
}), onCancel: () => navigation.back(projectHref(key)) }) })] }));
|
|
@@ -278,7 +279,7 @@ function ProjectPage(props) {
|
|
|
278
279
|
const overdue = open.filter((task) => task.dueOn !== null && task.dueOn < now).sort(byDue);
|
|
279
280
|
const unscheduled = open.filter((task) => task.dueOn === null).sort(byDue);
|
|
280
281
|
const calendar = words.calendar;
|
|
281
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: calendar.title }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people, me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(calendarHref(key, route.month, filter)) })), _jsx(components.Calendar, { tasks: shown, month: month, today: now, categories: data.categories, taskHref: href, monthHref: (wanted) => calendarHref(key, wanted, route.filter) }), _jsxs("div", { className: "sc-calendar-below", children: [_jsxs("section", { className: "sc-task-section", "aria-label": calendar.overdue, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.overdue, " ", _jsx("span", { className: "sc-column-count", children: overdue.length })] }), _jsx(TaskList, { tasks: overdue, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.nothingOverdue }) })] }), _jsxs("section", { className: "sc-task-section", "aria-label": calendar.unscheduled, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.unscheduled, " ", _jsx("span", { className: "sc-column-count", children: unscheduled.length })] }), _jsx(TaskList, { tasks: unscheduled, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.allScheduled }) })] })] }), open.length > 0 && (_jsxs("section", { className: "sc-task-section", "aria-label": calendar.workload.title, children: [_jsx("h2", { className: "sc-section-title", children: calendar.workload.title }), _jsx(Workload, { tasks: shown, today: now, personHref: (person) => boardHref(key, person === undefined ? {} : { person: person.id }) })] }))] }));
|
|
282
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: calendar.title }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people.filter(inWorkspace), me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(calendarHref(key, route.month, filter)) })), _jsx(components.Calendar, { tasks: shown, month: month, today: now, categories: data.categories, goals: data.goals, taskHref: href, monthHref: (wanted) => calendarHref(key, wanted, route.filter) }), _jsxs("div", { className: "sc-calendar-below", children: [_jsxs("section", { className: "sc-task-section", "aria-label": calendar.overdue, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.overdue, " ", _jsx("span", { className: "sc-column-count", children: overdue.length })] }), _jsx(TaskList, { tasks: overdue, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.nothingOverdue }) })] }), _jsxs("section", { className: "sc-task-section", "aria-label": calendar.unscheduled, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.unscheduled, " ", _jsx("span", { className: "sc-column-count", children: unscheduled.length })] }), _jsx(TaskList, { tasks: unscheduled, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.allScheduled }) })] })] }), open.length > 0 && (_jsxs("section", { className: "sc-task-section", "aria-label": calendar.workload.title, children: [_jsx("h2", { className: "sc-section-title", children: calendar.workload.title }), _jsx(Workload, { tasks: shown, today: now, personHref: (person) => boardHref(key, person === undefined ? {} : { person: person.id }) })] }))] }));
|
|
282
283
|
}
|
|
283
284
|
if (route.name === "roadmap" || route.name === "new-goal") {
|
|
284
285
|
const roadmap = words.roadmap;
|
|
@@ -339,6 +340,10 @@ function ProjectPage(props) {
|
|
|
339
340
|
if (event.goalId !== null) {
|
|
340
341
|
return goalHref(key, event.goalId);
|
|
341
342
|
}
|
|
343
|
+
// The rules are a page shown on the settings, not among the documents (ADR-0023).
|
|
344
|
+
if (event.data.path === RULES_PATH) {
|
|
345
|
+
return projectSettingsHref(key);
|
|
346
|
+
}
|
|
342
347
|
return event.data.path === undefined ? undefined : toDoc(event.data.path);
|
|
343
348
|
}, empty: _jsx(EmptyState, { title: words.feed.empty }) })] }));
|
|
344
349
|
}
|
|
@@ -349,18 +354,53 @@ function ProjectPage(props) {
|
|
|
349
354
|
const me = data.me?.person ?? undefined;
|
|
350
355
|
const configures = owner;
|
|
351
356
|
const members = words.projects.members;
|
|
352
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsx("p", { className: "sc-lead", children: words.vocabulary.visibility[project.visibility] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people, me: me, busy: busy, onAdd: configures ? (input) => void act(() => data.actions.addMember(input)) : undefined, onChangeRole: configures
|
|
357
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsx("p", { className: "sc-lead", children: words.vocabulary.visibility[project.visibility] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people.filter(inWorkspace), me: me, busy: busy, onAdd: configures ? (input) => void act(() => data.actions.addMember(input)) : undefined, onChangeRole: configures
|
|
353
358
|
? (member, role) => void act(() => data.actions.updateMember(member.person.id, { role }))
|
|
354
359
|
: undefined, onRemove: configures
|
|
355
360
|
? (member) => void act(() => data.actions.removeMember(member.person.id))
|
|
356
|
-
: undefined, empty: _jsx(EmptyState, { title: members.empty.title, body: project.visibility === "workspace" ? members.empty.workspace : members.empty.private }) })] }));
|
|
361
|
+
: undefined, workHref: owner ? (member) => personWorkHref(member.person.id) : undefined, empty: _jsx(EmptyState, { title: members.empty.title, body: project.visibility === "workspace" ? members.empty.workspace : members.empty.private }) })] }));
|
|
357
362
|
}
|
|
358
363
|
if (route.name === "settings") {
|
|
359
|
-
|
|
364
|
+
return (_jsx(ProjectSettingsPage, { route: route, project: project, data: data, navigation: navigation }));
|
|
365
|
+
}
|
|
366
|
+
return _jsx(NothingHere, {});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* A project's settings (ADR-0023): read by every member, so that everyone sees what the project
|
|
370
|
+
* is and what its agents are asked; changed by an owner, on the forms at their own addresses.
|
|
371
|
+
*/
|
|
372
|
+
function ProjectSettingsPage(props) {
|
|
373
|
+
const words = useWords();
|
|
374
|
+
const { route, project, data, navigation } = props;
|
|
375
|
+
const { busy, error, act } = useAction();
|
|
376
|
+
const settings = words.projects.settings;
|
|
377
|
+
const form = words.projects.form;
|
|
378
|
+
const owner = project.role === "owner";
|
|
379
|
+
const key = project.key;
|
|
380
|
+
const here = projectSettingsHref(key);
|
|
381
|
+
const skills = data.me?.workspace.skills;
|
|
382
|
+
const [rules, setRules] = useState(undefined);
|
|
383
|
+
const feedLength = data.events.length;
|
|
384
|
+
// The rules, asked for again whenever the project's feed grows.
|
|
385
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: the feed's length is the trigger
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
const controller = new AbortController();
|
|
388
|
+
data.actions
|
|
389
|
+
.projectRules(controller.signal)
|
|
390
|
+
.then((found) => {
|
|
391
|
+
if (!controller.signal.aborted) {
|
|
392
|
+
setRules(found);
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
.catch(() => undefined);
|
|
396
|
+
return () => controller.abort();
|
|
397
|
+
}, [data.actions, feedLength]);
|
|
398
|
+
const head = (_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: settings.title }), owner && route.form === undefined && (_jsx(LinkButton, { href: projectSettingsHref(key, "edit"), children: words.common.edit }))] }));
|
|
399
|
+
if (route.form === "edit") {
|
|
360
400
|
if (!owner) {
|
|
361
|
-
return _jsx(
|
|
401
|
+
return _jsx(NothingHere, {});
|
|
362
402
|
}
|
|
363
|
-
return (_jsxs(_Fragment, { children: [
|
|
403
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": settings.aria, children: _jsx(ProjectForm, { project: project, busy: busy, skillsSource: skills?.source, organizationAddress: skills?.address ?? null, onSubmit: (input, icon) => void act(async () => {
|
|
364
404
|
await data.actions.updateProject(input);
|
|
365
405
|
if (icon === null) {
|
|
366
406
|
await data.actions.removeProjectIcon();
|
|
@@ -368,11 +408,80 @@ function ProjectPage(props) {
|
|
|
368
408
|
else if (icon !== undefined) {
|
|
369
409
|
await data.actions.setProjectIcon(uploadOf(icon));
|
|
370
410
|
}
|
|
371
|
-
|
|
411
|
+
navigation.replace(here);
|
|
412
|
+
}), onCancel: () => navigation.back(here) }) })] }));
|
|
372
413
|
}
|
|
373
|
-
|
|
414
|
+
if (route.form === "rules") {
|
|
415
|
+
if (!owner) {
|
|
416
|
+
return _jsx(NothingHere, {});
|
|
417
|
+
}
|
|
418
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.rules.projectRules, children: rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesForm, { rules: rules.project, scope: "project", template: PROJECT_RULES_TEMPLATE, busy: busy, onSubmit: (input) => void act(async () => {
|
|
419
|
+
await data.actions.writeDocument(RULES_PATH, {
|
|
420
|
+
title: RULES_TITLE,
|
|
421
|
+
body: input.body,
|
|
422
|
+
...(input.baseVersion === undefined ? {} : { baseVersion: input.baseVersion }),
|
|
423
|
+
});
|
|
424
|
+
navigation.replace(here);
|
|
425
|
+
}), onCancel: () => navigation.back(here) })) })] }));
|
|
426
|
+
}
|
|
427
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel", "aria-label": settings.aria, children: _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: form.name }), _jsx("dd", { children: project.name })] }), _jsxs("div", { children: [_jsx("dt", { children: form.key }), _jsx("dd", { children: _jsx("code", { className: "sc-project-key", children: project.key }) })] }), _jsxs("div", { children: [_jsx("dt", { children: form.whoIsMember }), _jsx("dd", { children: words.vocabulary.visibility[project.visibility] })] }), _jsxs("div", { children: [_jsx("dt", { children: form.skillsAddress }), _jsx("dd", { children: project.skillsAddress !== null ? (_jsxs("code", { children: [skills?.source ?? "", project.skillsAddress] })) : skills?.address != null ? (settings.organizationsSkills(skills.address)) : (settings.noSkills) })] }), _jsxs("div", { children: [_jsx("dt", { children: form.symbol }), _jsx("dd", { children: _jsx(ProjectMark, { project: project, size: "sm" }) })] }), project.description.length > 0 && (_jsxs("div", { className: "sc-facts-wide", children: [_jsx("dt", { children: form.description }), _jsx("dd", { children: project.description })] }))] }) }), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.projectRules, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.projectRules }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules.project, scope: "project", docHref: (path) => docHref(key, path), editHref: owner ? projectSettingsHref(key, "rules") : undefined, onArchive: owner
|
|
428
|
+
? () => void act(async () => {
|
|
429
|
+
await data.actions.archiveDocument(RULES_PATH);
|
|
430
|
+
setRules(await data.actions.projectRules());
|
|
431
|
+
})
|
|
432
|
+
: undefined, busy: busy }))] }), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.workspaceRules, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.rules.workspaceRules }), _jsx("a", { className: "sc-muted", href: settingsHref(), children: words.rules.onWorkspaceSettings })] }), rules !== undefined && _jsx(RulesView, { rules: rules.workspace, scope: "workspace", folded: true })] })] }));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The workspace's settings (ADR-0023): its rules, the console's own until an administrator
|
|
436
|
+
* writes the organization's; its categories, kept on their own page; and where its skills come
|
|
437
|
+
* from, which whoever runs the console set. Read by everyone; the rules written by an administrator.
|
|
438
|
+
*/
|
|
439
|
+
function WorkspaceSettingsPage(props) {
|
|
440
|
+
const words = useWords();
|
|
441
|
+
const { form, data, navigation } = props;
|
|
442
|
+
const { busy, error, act } = useAction();
|
|
443
|
+
const administrator = data.me?.person?.role === "admin";
|
|
444
|
+
const skills = data.me?.workspace.skills;
|
|
445
|
+
const [rules, setRules] = useState(undefined);
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
const controller = new AbortController();
|
|
448
|
+
data.actions
|
|
449
|
+
.rules(controller.signal)
|
|
450
|
+
.then((found) => {
|
|
451
|
+
if (!controller.signal.aborted) {
|
|
452
|
+
setRules(found);
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
.catch(() => undefined);
|
|
456
|
+
return () => controller.abort();
|
|
457
|
+
}, [data.actions]);
|
|
458
|
+
const head = (_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.settings.title }) }));
|
|
459
|
+
if (form === "rules") {
|
|
460
|
+
if (!administrator) {
|
|
461
|
+
return _jsx(NothingHere, {});
|
|
462
|
+
}
|
|
463
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.rules.title, children: rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesForm, { rules: rules, scope: "workspace", template: REFERENCE_RULES, busy: busy, onSubmit: (input) => void act(async () => {
|
|
464
|
+
setRules(await data.actions.writeRules({
|
|
465
|
+
body: input.body,
|
|
466
|
+
...(input.baseVersion === undefined
|
|
467
|
+
? {}
|
|
468
|
+
: { baseVersion: input.baseVersion }),
|
|
469
|
+
}));
|
|
470
|
+
navigation.replace(PATHS.settings);
|
|
471
|
+
}), onCancel: () => navigation.back(PATHS.settings), onReset: () => void act(async () => {
|
|
472
|
+
setRules(await data.actions.archiveRules());
|
|
473
|
+
navigation.replace(PATHS.settings);
|
|
474
|
+
}) })) })] }));
|
|
475
|
+
}
|
|
476
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.title, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.title }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules, scope: "workspace", editHref: administrator ? settingsHref("rules") : undefined, onArchive: administrator
|
|
477
|
+
? () => void act(async () => {
|
|
478
|
+
setRules(await data.actions.archiveRules());
|
|
479
|
+
})
|
|
480
|
+
: undefined, busy: busy }))] }), _jsxs("section", { className: "sc-panel", "aria-label": words.settings.categories, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.categories }), data.categories.length > 0 && (_jsx("p", { className: "sc-category-chips", children: data.categories.map((category) => (_jsx(CategoryBadge, { category: category }, category.key))) })), _jsx(LinkButton, { href: PATHS.categories, children: words.settings.manageCategories })] }), skills !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.skills, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.skills }), _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsSource }), _jsx("dd", { children: _jsx("a", { href: skills.source, target: "_blank", rel: "noopener noreferrer", children: skills.source }) })] }), _jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsAddress }), _jsx("dd", { children: skills.address === null ? (words.settings.noSkillsAddress) : (_jsx("a", { href: `${skills.source}${skills.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx("code", { children: skills.address }) })) })] })] }), _jsx("p", { className: "sc-muted", children: words.settings.skillsFromConfiguration })] }))] }));
|
|
374
481
|
}
|
|
375
482
|
/** A file a person chose, as the client sends it: its name, its bytes and the type the browser says. */
|
|
483
|
+
/** Whether a person is in: who a picker of assignees and of members offers (ADR-0022). */
|
|
484
|
+
const inWorkspace = (person) => person.status === "active";
|
|
376
485
|
const uploadOf = (file) => ({
|
|
377
486
|
name: file.name,
|
|
378
487
|
bytes: file,
|
|
@@ -416,30 +525,59 @@ function PersonAgentsPage(props) {
|
|
|
416
525
|
setGeneration((current) => current + 1);
|
|
417
526
|
}), empty: _jsx(EmptyState, { title: words.people.noAgent }) }))] }));
|
|
418
527
|
}
|
|
419
|
-
/**
|
|
420
|
-
|
|
528
|
+
/**
|
|
529
|
+
* A plate, read when the page is opened: the person's own, or another person's for whoever
|
|
530
|
+
* oversees them. The week at the top is the address's.
|
|
531
|
+
*/
|
|
532
|
+
function PlatePage(props) {
|
|
421
533
|
const words = useWords();
|
|
422
|
-
const {
|
|
423
|
-
const [
|
|
534
|
+
const { components, load, couldNotLoad, refused } = props;
|
|
535
|
+
const [plate, setPlate] = useState(undefined);
|
|
424
536
|
const [problem, setProblem] = useState(undefined);
|
|
425
|
-
const
|
|
426
|
-
const couldNotLoad = words.work.couldNotLoad;
|
|
537
|
+
const [forbidden, setForbidden] = useState(false);
|
|
427
538
|
useEffect(() => {
|
|
428
539
|
const controller = new AbortController();
|
|
540
|
+
setPlate(undefined);
|
|
541
|
+
setProblem(undefined);
|
|
542
|
+
setForbidden(false);
|
|
429
543
|
load(controller.signal)
|
|
430
544
|
.then((answer) => {
|
|
431
545
|
if (!controller.signal.aborted) {
|
|
432
|
-
|
|
546
|
+
setPlate(answer);
|
|
433
547
|
}
|
|
434
548
|
})
|
|
435
549
|
.catch((failure) => {
|
|
436
|
-
if (
|
|
550
|
+
if (controller.signal.aborted) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (refused !== undefined &&
|
|
554
|
+
failure instanceof ApiError &&
|
|
555
|
+
failure.code === "auth.forbidden") {
|
|
556
|
+
setForbidden(true);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
437
559
|
setProblem(errorWords(failure, couldNotLoad));
|
|
438
560
|
}
|
|
439
561
|
});
|
|
440
562
|
return () => controller.abort();
|
|
441
|
-
}, [load, couldNotLoad]);
|
|
442
|
-
return (_jsxs(_Fragment, { children: [
|
|
563
|
+
}, [load, couldNotLoad, refused]);
|
|
564
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: props.title }), props.aside] }), problem !== undefined && _jsx(Callout, { tone: "danger", children: problem }), forbidden && refused !== undefined ? (_jsx(EmptyState, { title: refused.title, body: refused.body, action: _jsx(LinkButton, { href: PATHS.work, children: words.nav.work }) })) : plate === undefined ? (problem === undefined && (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: props.loading }) }))) : plate.tasks.length === 0 && (plate.decisions?.length ?? 0) === 0 ? (_jsx(EmptyState, { title: props.empty.title, body: props.empty.body })) : (_jsx(components.WorkList, { work: plate, week: props.week, taskHref: (projectKey, task) => taskHref(projectKey, task.number), decisionHref: (projectKey, decisionId) => decisionHref(projectKey, decisionId), weekHref: props.weekHref }))] }));
|
|
565
|
+
}
|
|
566
|
+
/** What is on the person's own plate, across their projects. */
|
|
567
|
+
function WorkPage(props) {
|
|
568
|
+
const words = useWords();
|
|
569
|
+
const { work } = props.data.actions;
|
|
570
|
+
return (_jsx(PlatePage, { components: props.components, load: work, title: words.work.title, week: props.week, weekHref: workHref, loading: words.work.loading, couldNotLoad: words.work.couldNotLoad, empty: words.work.empty }));
|
|
571
|
+
}
|
|
572
|
+
/** Another person's plate, for whoever oversees them: an administrator, or an owner of a project they are in. */
|
|
573
|
+
function PersonWorkPage(props) {
|
|
574
|
+
const words = useWords();
|
|
575
|
+
const { id, data } = props;
|
|
576
|
+
const person = data.people.find((candidate) => candidate.id === id);
|
|
577
|
+
const name = person === undefined ? "?" : personName(person);
|
|
578
|
+
const { personWork } = data.actions;
|
|
579
|
+
const load = useCallback((signal) => personWork(id, signal), [personWork, id]);
|
|
580
|
+
return (_jsx(PlatePage, { components: props.components, load: load, title: words.people.workOf(name), aside: _jsx(LinkButton, { href: PATHS.people, children: words.nav.people }), week: props.week, weekHref: (day) => personWorkHref(id, day), loading: words.people.loadingWork, couldNotLoad: words.people.couldNotLoadWork, empty: { title: words.people.noWork.title(name), body: words.people.noWork.body }, refused: words.people.notOverseen }));
|
|
443
581
|
}
|
|
444
582
|
function WorkspacePage(props) {
|
|
445
583
|
const words = useWords();
|
|
@@ -449,6 +587,12 @@ function WorkspacePage(props) {
|
|
|
449
587
|
if (route.name === "work") {
|
|
450
588
|
return _jsx(WorkPage, { data: data, components: components, week: route.week });
|
|
451
589
|
}
|
|
590
|
+
if (route.name === "person-work") {
|
|
591
|
+
return _jsx(PersonWorkPage, { id: route.id, data: data, components: components, week: route.week });
|
|
592
|
+
}
|
|
593
|
+
if (route.name === "workspace-settings") {
|
|
594
|
+
return _jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation });
|
|
595
|
+
}
|
|
452
596
|
if (route.name === "categories") {
|
|
453
597
|
const me = data.me?.person ?? undefined;
|
|
454
598
|
const administrator = me?.role === "admin";
|
|
@@ -461,12 +605,22 @@ function WorkspacePage(props) {
|
|
|
461
605
|
? (category) => void act(() => data.actions.removeCategory(category.key))
|
|
462
606
|
: undefined, empty: _jsx(EmptyState, { title: categories.empty.title, body: categories.empty.body }) })] }));
|
|
463
607
|
}
|
|
464
|
-
if (route.name === "people") {
|
|
608
|
+
if (route.name === "people" || route.name === "new-listing") {
|
|
465
609
|
const me = data.me?.person ?? undefined;
|
|
466
610
|
const administrator = me?.role === "admin";
|
|
467
|
-
|
|
611
|
+
const making = route.name === "new-listing" && administrator;
|
|
612
|
+
// An owner sees the work of the people in their projects; the console refuses the rest.
|
|
613
|
+
const oversees = administrator || data.projects.some((project) => project.role === "owner");
|
|
614
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.people.title }), administrator && !making && (_jsx(LinkButton, { href: PATHS.newListing, variant: "primary", children: words.people.letIn }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.people.letInAria, children: _jsx(ListingForm, { providers: data.me?.signIn ?? [], busy: busy, onSubmit: (input) => void act(async () => {
|
|
615
|
+
await data.actions.createListing(input);
|
|
616
|
+
navigation.replace(PATHS.people);
|
|
617
|
+
}), onCancel: () => navigation.back(PATHS.people) }) })), _jsx(components.PeopleList, { people: data.people, listed: data.listed, organization: data.organization, me: me, busy: busy, onChangeRole: administrator
|
|
468
618
|
? (person, role) => void act(() => data.actions.updatePerson(person.id, { role }))
|
|
469
|
-
: undefined,
|
|
619
|
+
: undefined, onChangeStatus: administrator
|
|
620
|
+
? (person, status) => void act(() => data.actions.updatePerson(person.id, { status }))
|
|
621
|
+
: undefined, onUnlist: administrator
|
|
622
|
+
? (listing) => void act(() => data.actions.removeListing(listing.id))
|
|
623
|
+
: undefined, workHref: oversees ? (person) => personWorkHref(person.id) : undefined, agentsHref: administrator ? (person) => personAgentsHref(person.id) : undefined })] }));
|
|
470
624
|
}
|
|
471
625
|
if (route.name === "person-agents") {
|
|
472
626
|
return _jsx(PersonAgentsPage, { id: route.id, data: data, components: components });
|
|
@@ -536,7 +690,9 @@ export function createConsole(config = {}) {
|
|
|
536
690
|
{
|
|
537
691
|
href: PATHS.people,
|
|
538
692
|
label: words.nav.people,
|
|
539
|
-
current: route.name === "people" ||
|
|
693
|
+
current: route.name === "people" ||
|
|
694
|
+
route.name === "new-listing" ||
|
|
695
|
+
route.name === "person-agents",
|
|
540
696
|
},
|
|
541
697
|
...(viaToken
|
|
542
698
|
? []
|
|
@@ -547,11 +703,12 @@ export function createConsole(config = {}) {
|
|
|
547
703
|
current: route.name === "agents" || route.name === "new-token",
|
|
548
704
|
},
|
|
549
705
|
]),
|
|
550
|
-
// The
|
|
706
|
+
// The settings are read by everyone: the rules every agent follows, the categories and
|
|
707
|
+
// what the colours mean, where the skills come from; kept by an administrator.
|
|
551
708
|
{
|
|
552
|
-
href: PATHS.
|
|
553
|
-
label: words.nav.
|
|
554
|
-
current: route.name === "categories",
|
|
709
|
+
href: PATHS.settings,
|
|
710
|
+
label: words.nav.settings,
|
|
711
|
+
current: route.name === "workspace-settings" || route.name === "categories",
|
|
555
712
|
},
|
|
556
713
|
...pages
|
|
557
714
|
.filter((candidate) => candidate.level === "workspace")
|
|
@@ -609,15 +766,12 @@ export function createConsole(config = {}) {
|
|
|
609
766
|
label: words.nav.members,
|
|
610
767
|
current: route.name === "members",
|
|
611
768
|
},
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
},
|
|
619
|
-
]
|
|
620
|
-
: []),
|
|
769
|
+
// Read by every member: what the project is, and what its agents are asked (ADR-0023).
|
|
770
|
+
{
|
|
771
|
+
href: projectHref(projectKey, "settings"),
|
|
772
|
+
label: words.nav.settings,
|
|
773
|
+
current: route.name === "settings",
|
|
774
|
+
},
|
|
621
775
|
...pages
|
|
622
776
|
.filter((candidate) => candidate.level === "project")
|
|
623
777
|
.map((candidate) => ({
|