@skillcdn/console 0.4.1 → 0.4.3

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.
Files changed (299) hide show
  1. package/README.md +19 -2
  2. package/SKILLCDN.md +7 -5
  3. package/dist/api.d.ts +5 -4
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +5 -4
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +569 -48
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/format.d.ts +34 -3
  11. package/dist/cli/format.d.ts.map +1 -1
  12. package/dist/cli/format.js +283 -7
  13. package/dist/cli/format.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +44 -7
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +26 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +396 -0
  20. package/dist/cli/plugin-check.js.map +1 -0
  21. package/dist/cli/plugin-cli.d.ts +20 -0
  22. package/dist/cli/plugin-cli.d.ts.map +1 -0
  23. package/dist/cli/plugin-cli.js +189 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +37 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +206 -0
  28. package/dist/cli/plugin-init.js.map +1 -0
  29. package/dist/cli/plugin-main.d.ts +3 -0
  30. package/dist/cli/plugin-main.d.ts.map +1 -0
  31. package/dist/cli/plugin-main.js +28 -0
  32. package/dist/cli/plugin-main.js.map +1 -0
  33. package/dist/client.d.ts +100 -3
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +79 -7
  36. package/dist/client.js.map +1 -1
  37. package/dist/compat.d.ts +1 -1
  38. package/dist/compat.js +2 -2
  39. package/dist/components/board.d.ts +8 -0
  40. package/dist/components/board.d.ts.map +1 -1
  41. package/dist/components/board.js +59 -15
  42. package/dist/components/board.js.map +1 -1
  43. package/dist/components/comments.d.ts.map +1 -1
  44. package/dist/components/comments.js +1 -1
  45. package/dist/components/comments.js.map +1 -1
  46. package/dist/components/decision-form.d.ts +2 -1
  47. package/dist/components/decision-form.d.ts.map +1 -1
  48. package/dist/components/decision-form.js +36 -4
  49. package/dist/components/decision-form.js.map +1 -1
  50. package/dist/components/decision-list.d.ts +4 -0
  51. package/dist/components/decision-list.d.ts.map +1 -1
  52. package/dist/components/decision-list.js +29 -3
  53. package/dist/components/decision-list.js.map +1 -1
  54. package/dist/components/event-feed.d.ts +1 -1
  55. package/dist/components/event-feed.d.ts.map +1 -1
  56. package/dist/components/event-feed.js +33 -1
  57. package/dist/components/event-feed.js.map +1 -1
  58. package/dist/components/my-decisions.d.ts +2 -0
  59. package/dist/components/my-decisions.d.ts.map +1 -1
  60. package/dist/components/my-decisions.js +2 -2
  61. package/dist/components/my-decisions.js.map +1 -1
  62. package/dist/components/plugin-frame.d.ts +52 -0
  63. package/dist/components/plugin-frame.d.ts.map +1 -0
  64. package/dist/components/plugin-frame.js +314 -0
  65. package/dist/components/plugin-frame.js.map +1 -0
  66. package/dist/components/plugin-settings.d.ts +38 -0
  67. package/dist/components/plugin-settings.d.ts.map +1 -0
  68. package/dist/components/plugin-settings.js +348 -0
  69. package/dist/components/plugin-settings.js.map +1 -0
  70. package/dist/components/shell.d.ts +2 -0
  71. package/dist/components/shell.d.ts.map +1 -1
  72. package/dist/components/shell.js +26 -1
  73. package/dist/components/shell.js.map +1 -1
  74. package/dist/components/task-view.d.ts +2 -1
  75. package/dist/components/task-view.d.ts.map +1 -1
  76. package/dist/components/task-view.js.map +1 -1
  77. package/dist/components/workspace.d.ts +7 -1
  78. package/dist/components/workspace.d.ts.map +1 -1
  79. package/dist/components/workspace.js +1 -1
  80. package/dist/components/workspace.js.map +1 -1
  81. package/dist/console.css +330 -1
  82. package/dist/console.d.ts.map +1 -1
  83. package/dist/console.js +152 -41
  84. package/dist/console.js.map +1 -1
  85. package/dist/data.d.ts +9 -2
  86. package/dist/data.d.ts.map +1 -1
  87. package/dist/data.js +45 -9
  88. package/dist/data.js.map +1 -1
  89. package/dist/i18n/en.d.ts +155 -0
  90. package/dist/i18n/en.d.ts.map +1 -1
  91. package/dist/i18n/en.js +166 -0
  92. package/dist/i18n/en.js.map +1 -1
  93. package/dist/i18n/ko.d.ts.map +1 -1
  94. package/dist/i18n/ko.js +150 -0
  95. package/dist/i18n/ko.js.map +1 -1
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +3 -1
  99. package/dist/index.js.map +1 -1
  100. package/dist/limits.d.ts +2 -0
  101. package/dist/limits.d.ts.map +1 -1
  102. package/dist/limits.js +2 -0
  103. package/dist/limits.js.map +1 -1
  104. package/dist/plugin/args.d.ts +148 -0
  105. package/dist/plugin/args.d.ts.map +1 -0
  106. package/dist/plugin/args.js +369 -0
  107. package/dist/plugin/args.js.map +1 -0
  108. package/dist/plugin/bridge.d.ts +146 -0
  109. package/dist/plugin/bridge.d.ts.map +1 -0
  110. package/dist/plugin/bridge.js +125 -0
  111. package/dist/plugin/bridge.js.map +1 -0
  112. package/dist/plugin/capabilities.d.ts +20 -0
  113. package/dist/plugin/capabilities.d.ts.map +1 -0
  114. package/dist/plugin/capabilities.js +133 -0
  115. package/dist/plugin/capabilities.js.map +1 -0
  116. package/dist/plugin/context.d.ts +380 -0
  117. package/dist/plugin/context.d.ts.map +1 -0
  118. package/dist/plugin/context.js +2 -0
  119. package/dist/plugin/context.js.map +1 -0
  120. package/dist/plugin/define.d.ts +208 -0
  121. package/dist/plugin/define.d.ts.map +1 -0
  122. package/dist/plugin/define.js +482 -0
  123. package/dist/plugin/define.js.map +1 -0
  124. package/dist/plugin/definition-error.d.ts +11 -0
  125. package/dist/plugin/definition-error.d.ts.map +1 -0
  126. package/dist/plugin/definition-error.js +17 -0
  127. package/dist/plugin/definition-error.js.map +1 -0
  128. package/dist/plugin/limits.d.ts +67 -0
  129. package/dist/plugin/limits.d.ts.map +1 -0
  130. package/dist/plugin/limits.js +70 -0
  131. package/dist/plugin/limits.js.map +1 -0
  132. package/dist/plugin/load-worker.d.ts +7 -0
  133. package/dist/plugin/load-worker.d.ts.map +1 -0
  134. package/dist/plugin/load-worker.js +41 -0
  135. package/dist/plugin/load-worker.js.map +1 -0
  136. package/dist/plugin/load.d.ts +18 -0
  137. package/dist/plugin/load.d.ts.map +1 -0
  138. package/dist/plugin/load.js +62 -0
  139. package/dist/plugin/load.js.map +1 -0
  140. package/dist/plugin/manifest.d.ts +237 -0
  141. package/dist/plugin/manifest.d.ts.map +1 -0
  142. package/dist/plugin/manifest.js +411 -0
  143. package/dist/plugin/manifest.js.map +1 -0
  144. package/dist/plugin/refusals.d.ts +49 -0
  145. package/dist/plugin/refusals.d.ts.map +1 -0
  146. package/dist/plugin/refusals.js +79 -0
  147. package/dist/plugin/refusals.js.map +1 -0
  148. package/dist/plugin/rest-schemas.d.ts +556 -0
  149. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  150. package/dist/plugin/rest-schemas.js +262 -0
  151. package/dist/plugin/rest-schemas.js.map +1 -0
  152. package/dist/plugin/sandbox.d.ts +23 -0
  153. package/dist/plugin/sandbox.d.ts.map +1 -0
  154. package/dist/plugin/sandbox.js +99 -0
  155. package/dist/plugin/sandbox.js.map +1 -0
  156. package/dist/plugin/testing.d.ts +45 -0
  157. package/dist/plugin/testing.d.ts.map +1 -0
  158. package/dist/plugin/testing.js +420 -0
  159. package/dist/plugin/testing.js.map +1 -0
  160. package/dist/plugin/web-react.d.ts +11 -0
  161. package/dist/plugin/web-react.d.ts.map +1 -0
  162. package/dist/plugin/web-react.js +34 -0
  163. package/dist/plugin/web-react.js.map +1 -0
  164. package/dist/plugin/web.d.ts +95 -0
  165. package/dist/plugin/web.d.ts.map +1 -0
  166. package/dist/plugin/web.js +245 -0
  167. package/dist/plugin/web.js.map +1 -0
  168. package/dist/plugin-nav.d.ts +29 -0
  169. package/dist/plugin-nav.d.ts.map +1 -0
  170. package/dist/plugin-nav.js +92 -0
  171. package/dist/plugin-nav.js.map +1 -0
  172. package/dist/plugin-testing.d.ts +2 -0
  173. package/dist/plugin-testing.d.ts.map +1 -0
  174. package/dist/plugin-testing.js +4 -0
  175. package/dist/plugin-testing.js.map +1 -0
  176. package/dist/plugin-web-react.d.ts +3 -0
  177. package/dist/plugin-web-react.d.ts.map +1 -0
  178. package/dist/plugin-web-react.js +3 -0
  179. package/dist/plugin-web-react.js.map +1 -0
  180. package/dist/plugin-web.d.ts +4 -0
  181. package/dist/plugin-web.d.ts.map +1 -0
  182. package/dist/plugin-web.js +5 -0
  183. package/dist/plugin-web.js.map +1 -0
  184. package/dist/plugin.d.ts +11 -0
  185. package/dist/plugin.d.ts.map +1 -0
  186. package/dist/plugin.js +15 -0
  187. package/dist/plugin.js.map +1 -0
  188. package/dist/router.d.ts +28 -0
  189. package/dist/router.d.ts.map +1 -1
  190. package/dist/router.js +47 -0
  191. package/dist/router.js.map +1 -1
  192. package/dist/routes.d.ts +4 -0
  193. package/dist/routes.d.ts.map +1 -1
  194. package/dist/routes.js +6 -0
  195. package/dist/routes.js.map +1 -1
  196. package/dist/schemas.d.ts +194 -9
  197. package/dist/schemas.d.ts.map +1 -1
  198. package/dist/schemas.js +98 -17
  199. package/dist/schemas.js.map +1 -1
  200. package/dist/tokens.d.ts +2 -0
  201. package/dist/tokens.d.ts.map +1 -0
  202. package/dist/tokens.js +101 -0
  203. package/dist/tokens.js.map +1 -0
  204. package/dist/vocabulary.d.ts +11 -1
  205. package/dist/vocabulary.d.ts.map +1 -1
  206. package/dist/vocabulary.js +24 -0
  207. package/dist/vocabulary.js.map +1 -1
  208. package/dist/web.d.ts.map +1 -1
  209. package/dist/web.js +2 -0
  210. package/dist/web.js.map +1 -1
  211. package/docs/specs/cli.md +12 -5
  212. package/docs/specs/plugins.md +488 -0
  213. package/docs/specs/rest.md +11 -9
  214. package/package.json +26 -8
  215. package/skills/building-a-console/SKILL.md +25 -26
  216. package/skills/building-a-console/references/configuring.md +40 -0
  217. package/skills/building-a-console/references/the-default-console.md +1 -1
  218. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  219. package/skills/building-a-plugin/SKILL.md +137 -0
  220. package/skills/deploying-a-console/SKILL.md +24 -26
  221. package/skills/deploying-a-console/references/access.md +13 -9
  222. package/skills/deploying-a-console/references/resources.md +25 -10
  223. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  224. package/skills/deploying-a-console/references/sizing.md +2 -0
  225. package/skills/planning-the-board/SKILL.md +96 -0
  226. package/skills/planning-the-board/references/a-task.md +36 -0
  227. package/skills/planning-the-board/references/setting-up.md +44 -0
  228. package/skills/working-the-board/SKILL.md +43 -34
  229. package/skills/working-the-board/references/the-pack.md +19 -0
  230. package/src/api.ts +12 -0
  231. package/src/cli/cli.ts +737 -55
  232. package/src/cli/format.ts +368 -10
  233. package/src/cli/help.ts +50 -6
  234. package/src/cli/plugin-check.ts +497 -0
  235. package/src/cli/plugin-cli.ts +237 -0
  236. package/src/cli/plugin-init.ts +257 -0
  237. package/src/cli/plugin-main.ts +30 -0
  238. package/src/client.ts +322 -9
  239. package/src/compat.ts +2 -2
  240. package/src/components/board.tsx +62 -14
  241. package/src/components/comments.tsx +1 -0
  242. package/src/components/decision-form.tsx +92 -11
  243. package/src/components/decision-list.tsx +71 -2
  244. package/src/components/event-feed.tsx +42 -1
  245. package/src/components/my-decisions.tsx +4 -1
  246. package/src/components/plugin-frame.tsx +437 -0
  247. package/src/components/plugin-settings.tsx +1111 -0
  248. package/src/components/shell.tsx +82 -14
  249. package/src/components/task-view.tsx +2 -1
  250. package/src/components/workspace.tsx +18 -0
  251. package/src/console.tsx +304 -31
  252. package/src/data.ts +59 -11
  253. package/src/i18n/en.ts +175 -0
  254. package/src/i18n/ko.ts +155 -0
  255. package/src/index.ts +12 -0
  256. package/src/limits.ts +2 -0
  257. package/src/plugin/args.ts +526 -0
  258. package/src/plugin/bridge.ts +314 -0
  259. package/src/plugin/capabilities.ts +150 -0
  260. package/src/plugin/context.ts +421 -0
  261. package/src/plugin/define.ts +792 -0
  262. package/src/plugin/definition-error.ts +19 -0
  263. package/src/plugin/limits.ts +70 -0
  264. package/src/plugin/load-worker.ts +50 -0
  265. package/src/plugin/load.ts +82 -0
  266. package/src/plugin/manifest.ts +561 -0
  267. package/src/plugin/refusals.ts +94 -0
  268. package/src/plugin/rest-schemas.ts +354 -0
  269. package/src/plugin/sandbox.ts +123 -0
  270. package/src/plugin/testing.ts +612 -0
  271. package/src/plugin/web-react.ts +45 -0
  272. package/src/plugin/web.ts +403 -0
  273. package/src/plugin-nav.ts +132 -0
  274. package/src/plugin-testing.ts +10 -0
  275. package/src/plugin-web-react.ts +4 -0
  276. package/src/plugin-web.ts +35 -0
  277. package/src/plugin.ts +126 -0
  278. package/src/router.ts +73 -0
  279. package/src/routes.ts +7 -0
  280. package/src/schemas.ts +115 -18
  281. package/src/styles/console.css +330 -1
  282. package/src/tokens.ts +101 -0
  283. package/src/vocabulary.ts +30 -0
  284. package/src/web.ts +2 -0
  285. package/template/plugin/.gitignore +2 -0
  286. package/template/plugin/README.md +26 -0
  287. package/template/plugin/SKILLCDN.md +13 -0
  288. package/template/plugin/build.mjs +40 -0
  289. package/template/plugin/console-plugin.json +50 -0
  290. package/template/plugin/package.json +27 -0
  291. package/template/plugin/skills/__key__/SKILL.md +23 -0
  292. package/template/plugin/src/check.test.ts +34 -0
  293. package/template/plugin/src/server/index.test.ts +41 -0
  294. package/template/plugin/src/server/index.ts +72 -0
  295. package/template/plugin/src/web/index.css +51 -0
  296. package/template/plugin/src/web/index.ts +115 -0
  297. package/template/plugin/tsconfig.json +17 -0
  298. package/skills/working-the-board/references/setting-up.md +0 -3
  299. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/dist/console.js CHANGED
@@ -14,6 +14,8 @@ import { MyDecisions } from "./components/my-decisions.js";
14
14
  import { PackTokenWays } from "./components/pack-token.js";
15
15
  import { PAGE_SIZE } from "./components/paged.js";
16
16
  import { ListingForm, PeopleList } from "./components/people.js";
17
+ import { PluginFrame, PluginWidgets } from "./components/plugin-frame.js";
18
+ import { PluginSettingsList, PluginSettingsView } from "./components/plugin-settings.js";
17
19
  import { MemberList, ProjectForm, ProjectList, ProjectMark, } from "./components/projects.js";
18
20
  import { RemotePicturesContext } from "./components/remote-pictures.js";
19
21
  import { GoalTimeline } from "./components/roadmap.js";
@@ -33,7 +35,8 @@ import { useConsoleData } from "./data.js";
33
35
  import { monthOf, today } from "./dates.js";
34
36
  import { DocsPage } from "./docs-page.js";
35
37
  import { chooseLanguage, DEFAULT_LANGUAGES, LanguageContext, useWords, } from "./i18n/index.js";
36
- import { boardHref, calendarHref, customPageHref, decisionHref, decisionsHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, personWorkHref, projectHref, projectSettingsHref, roadmapHref, settingsHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
38
+ import { pluginNavItems, pluginPagesAt, pluginWordOf } from "./plugin-nav.js";
39
+ import { boardHref, calendarHref, customPageHref, decisionHref, decisionsHref, docHref, goalHref, matchRoute, newDecisionHref, newGoalHref, newTaskHref, PATHS, personAgentsHref, personWorkHref, pluginHref, pluginSettingsHref, projectHref, projectSettingsHref, roadmapHref, settingsHref, signInFailureOf, taskHref, withoutSignInParam, workHref, } from "./router.js";
37
40
  import { projectPath } from "./routes.js";
38
41
  import { errorWords, useAction } from "./use-action.js";
39
42
  export const DEFAULT_COMPONENTS = {
@@ -163,6 +166,12 @@ function useFollowLinks(navigation) {
163
166
  }, [navigation]);
164
167
  }
165
168
  /** The key of the project a route is on, or nothing. */
169
+ /** The files chosen or pasted on a page, as the client sends them: each by its name, its bytes and its type where the browser knows one. */
170
+ const uploadsOf = (files) => files.map((file) => ({
171
+ name: file.name,
172
+ bytes: file,
173
+ ...(file.type === "" ? {} : { contentType: file.type }),
174
+ }));
166
175
  const projectOf = (route) => "project" in route ? route.project : undefined;
167
176
  /** The field the pages lie on, for a page without the shell: signing in, and the wait before it. */
168
177
  function Ground(props) {
@@ -192,17 +201,87 @@ function NotWithToken() {
192
201
  }
193
202
  function ProjectsPage(props) {
194
203
  const words = useWords().projects;
195
- const { data, components, navigation } = props;
204
+ const { data, components, navigation, client, language } = props;
205
+ const me = data.me;
206
+ /** The plugins' widgets at a place of this page (ADR-0054), for the person's roles. */
207
+ const widgets = (place) => me === undefined ? null : (_jsx(PluginWidgets, { plugins: data.plugins, place: place, me: me, language: language, client: client, navigation: navigation, nameOf: (name) => pluginWordOf(name, language) }));
196
208
  const { busy, error, act } = useAction();
197
209
  const href = (project) => projectHref(project.key);
198
210
  // A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
199
211
  // address included.
200
212
  const administrator = data.me?.person?.role === "admin";
201
213
  const making = props.making && administrator;
202
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), administrator && !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 () => {
214
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), administrator && !making && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), widgets("home:top"), 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 () => {
203
215
  const made = await data.actions.createProject(input, icon instanceof File ? uploadOf(icon) : undefined);
204
216
  navigation.replace(projectHref(made.key));
205
- }), 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: administrator ? words.empty.body : words.empty.bodyMember, action: administrator ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) })] }));
217
+ }), 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: administrator ? words.empty.body : words.empty.bodyMember, action: administrator ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) }), widgets("home:bottom")] }));
218
+ }
219
+ /**
220
+ * A plugin's page (ADR-0054): the sub-tabs of its pages at the level, where it has several, above
221
+ * the frame of the one shown, the first when the address names none. Nothing for a plugin the
222
+ * person has no page of, or that is not on the project.
223
+ */
224
+ function PluginPage(props) {
225
+ const words = useWords();
226
+ const { route, data, project, client, language, navigation } = props;
227
+ const plugin = data.plugins.find((candidate) => candidate.key === route.plugin && candidate.state === "enabled");
228
+ const bound = route.level === "workspace" ||
229
+ data.projectPlugins.some((candidate) => candidate.key === route.plugin);
230
+ const pages = plugin === undefined ? [] : pluginPagesAt(plugin, route.level);
231
+ const page = route.page === undefined ? pages[0] : pages.find((p) => p.key === route.page);
232
+ if (plugin === undefined || !bound || page === undefined || data.me === undefined) {
233
+ return _jsx(EmptyState, { title: words.plugins.noPage.title, body: words.plugins.noPage.body });
234
+ }
235
+ const projectKey = route.level === "project" ? route.project : undefined;
236
+ return (_jsxs(_Fragment, { children: [pages.length > 1 && (_jsx("nav", { className: "sc-subtabs", "aria-label": pluginWordOf(plugin.name, language), children: pages.map((candidate) => (_jsx("a", { href: pluginHref(plugin.key, candidate.key, projectKey), className: candidate.key === page.key ? "sc-tab sc-tab-current" : "sc-tab", "aria-current": candidate.key === page.key ? "page" : undefined, children: pluginWordOf(candidate.name, language) }, candidate.key))) })), _jsx(PluginFrame, { plugin: plugin, page: page.key, project: project, path: route.rest, me: data.me, language: language, client: client, navigation: navigation, events: project === undefined ? undefined : data.events, title: words.plugins.frame(pluginWordOf(page.name, language)) }, `${plugin.key}:${page.key}:${projectKey ?? ""}`)] }));
237
+ }
238
+ /**
239
+ * The plugins' settings (docs/specs/plugins.md, "The settings pages"): the list, or one plugin
240
+ * whole, read again after every change; for an administrator.
241
+ */
242
+ function PluginSettingsPage(props) {
243
+ const words = useWords();
244
+ const { data, client, language, navigation } = props;
245
+ const settings = words.plugins.settings;
246
+ const administrator = data.me?.person?.role === "admin";
247
+ const [detail, setDetail] = useState(undefined);
248
+ const [failure, setFailure] = useState(undefined);
249
+ const key = props.plugin;
250
+ const read = useCallback(async () => {
251
+ if (key === undefined) {
252
+ return;
253
+ }
254
+ try {
255
+ setDetail(await client.plugin(key).get());
256
+ setFailure(undefined);
257
+ }
258
+ catch (error) {
259
+ setFailure(error instanceof ApiError ? error.message : words.common.somethingWentWrong);
260
+ }
261
+ }, [client, key, words.common.somethingWentWrong]);
262
+ useEffect(() => {
263
+ setDetail(undefined);
264
+ void read();
265
+ }, [read]);
266
+ if (!administrator) {
267
+ return _jsx(NothingHere, {});
268
+ }
269
+ if (key === undefined) {
270
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: settings.title }) }), _jsx(PluginSettingsList, { plugins: data.plugins, language: language, href: (plugin) => pluginSettingsHref(plugin.key) })] }));
271
+ }
272
+ if (failure !== undefined) {
273
+ return _jsx(EmptyState, { title: words.common.somethingWentWrong, body: failure });
274
+ }
275
+ if (detail === undefined) {
276
+ return (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.common.loading }) }));
277
+ }
278
+ return (_jsx(PluginSettingsView, { detail: detail, projects: data.projects, people: data.people, language: language, client: client, onChanged: async () => {
279
+ await read();
280
+ await data.actions.refreshPlugins();
281
+ }, onPurged: () => {
282
+ void data.actions.refreshPlugins();
283
+ navigation.replace(pluginSettingsHref());
284
+ } }));
206
285
  }
207
286
  /**
208
287
  * The archive of the board (ADR-0029): the tasks put away, newest first, a page at a time from
@@ -348,8 +427,8 @@ function TaskPage(props) {
348
427
  return (_jsx(components.TaskView, { task: task, people: data.people.filter(inWorkspace), tasks: data.tasks, parent: parent, findTasks: data.actions.findTasks, findGoals: data.actions.findGoals, decisions: data.decisions, runs: data.runs, comments: comments, 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: (fileId) => projectPath(key, "files", fileId), 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 () => {
349
428
  await data.actions.updateTask(target.id, patch);
350
429
  navigation.replace(here);
351
- }), onMove: (target, state) => void act(() => data.actions.updateTask(target.id, { state })), onRaiseDecision: (input) => void act(async () => {
352
- await data.actions.raiseDecision(input);
430
+ }), onMove: (target, state) => void act(() => data.actions.updateTask(target.id, { state })), onRaiseDecision: (input, files) => void act(async () => {
431
+ await data.actions.raiseDecision(input, uploadsOf(files));
353
432
  navigation.replace(here);
354
433
  }), onAnswer: (decision, input) => void act(() => data.actions.answerDecision(decision.id, input)), onUpdateDecision: (decision, patch) => void act(() => data.actions.updateDecision(decision.id, patch)), onAbandonRun: (run) => void act(() => data.actions.abandonRun(run.id)), onArchive: (target) => void act(async () => {
355
434
  await data.actions.archiveTask(target.id);
@@ -357,14 +436,7 @@ function TaskPage(props) {
357
436
  }), onRestore: (target) => void act(async () => {
358
437
  setFetched(await data.actions.restoreTask(target.id));
359
438
  }), onComment: (draft) => void act(async () => {
360
- await data.actions.comment(task.id, {
361
- body: draft.body,
362
- files: draft.files.map((file) => ({
363
- name: file.name,
364
- bytes: file,
365
- ...(file.type === "" ? {} : { contentType: file.type }),
366
- })),
367
- });
439
+ await data.actions.comment(task.id, { body: draft.body, files: uploadsOf(draft.files) });
368
440
  setComments(await taskComments(task.id));
369
441
  }) }));
370
442
  }
@@ -455,17 +527,17 @@ function ProjectPage(props) {
455
527
  return (_jsx(DocsPage, { project: project, path: route.path, view: route.view, data: data, navigation: navigation }));
456
528
  }
457
529
  if (route.name === "decisions") {
458
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.decisions.title }), _jsx(LinkButton, { href: newDecisionHref(key), variant: "primary", children: words.decisions.raise })] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.DecisionList, { decisions: data.decisions, tasks: tasksById, taskHref: href, docHref: toDoc, decisionHref: (decision) => decisionHref(key, decision.id), page: route.page, pageHref: (page) => decisionsHref(key, page), onAnswer: onAnswer, onUpdate: onUpdate, busy: busy, empty: _jsx(EmptyState, { title: words.decisions.empty.title, body: words.decisions.empty.body }) })] }));
530
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.decisions.title }), _jsx(LinkButton, { href: newDecisionHref(key), variant: "primary", children: words.decisions.raise })] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.DecisionList, { decisions: data.decisions, tasks: tasksById, taskHref: href, docHref: toDoc, fileHref: (fileId) => projectPath(key, "files", fileId), decisionHref: (decision) => decisionHref(key, decision.id), page: route.page, pageHref: (page) => decisionsHref(key, page), onAnswer: onAnswer, onUpdate: onUpdate, busy: busy, empty: _jsx(EmptyState, { title: words.decisions.empty.title, body: words.decisions.empty.body }) })] }));
459
531
  }
460
532
  if (route.name === "new-decision") {
461
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.decisions.raise }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.decisions.raiseAria, children: _jsx(DecisionForm, { findTasks: data.actions.findTasks, deciders: data.members.map((member) => member.person), me: data.me?.person ?? undefined, busy: busy, onSubmit: (input) => void act(async () => {
462
- const made = await data.actions.raiseDecision(input);
533
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.decisions.raise }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.decisions.raiseAria, children: _jsx(DecisionForm, { findTasks: data.actions.findTasks, deciders: data.members.map((member) => member.person), me: data.me?.person ?? undefined, busy: busy, onSubmit: (input, files) => void act(async () => {
534
+ const made = await data.actions.raiseDecision(input, uploadsOf(files));
463
535
  navigation.replace(decisionHref(key, made.id));
464
536
  }), onCancel: () => navigation.back(projectHref(key, "decisions")) }) })] }));
465
537
  }
466
538
  if (route.name === "decision") {
467
539
  const decision = data.decisions.find((candidate) => candidate.id === route.id);
468
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.decisions.one }), _jsx(LinkButton, { href: projectHref(key, "decisions"), children: words.decisions.all })] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), decision === undefined ? (_jsx(EmptyState, { title: words.decisions.noSuch.title, body: words.decisions.noSuch.body })) : (_jsx(DecisionCard, { decision: decision, task: decision.taskId === null ? undefined : tasksById.get(decision.taskId), taskHref: href, docHref: toDoc, onAnswer: onAnswer, onUpdate: onUpdate, busy: busy }))] }));
540
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.decisions.one }), _jsx(LinkButton, { href: projectHref(key, "decisions"), children: words.decisions.all })] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), decision === undefined ? (_jsx(EmptyState, { title: words.decisions.noSuch.title, body: words.decisions.noSuch.body })) : (_jsx(DecisionCard, { decision: decision, task: decision.taskId === null ? undefined : tasksById.get(decision.taskId), taskHref: href, docHref: toDoc, fileHref: (fileId) => projectPath(key, "files", fileId), onAnswer: onAnswer, onUpdate: onUpdate, busy: busy }))] }));
469
541
  }
470
542
  if (route.name === "archive") {
471
543
  return _jsx(ArchivePage, { project: project, data: data });
@@ -513,6 +585,9 @@ function ProjectPage(props) {
513
585
  if (route.name === "settings") {
514
586
  return (_jsx(ProjectSettingsPage, { route: route, project: project, data: data, navigation: navigation }));
515
587
  }
588
+ if (route.name === "plugin") {
589
+ return (_jsx(PluginPage, { route: route, data: data, project: project, client: props.client, language: props.language, navigation: navigation }));
590
+ }
516
591
  return _jsx(NothingHere, {});
517
592
  }
518
593
  /**
@@ -602,7 +677,7 @@ function ProjectSettingsPage(props) {
602
677
  */
603
678
  function WorkspaceSettingsPage(props) {
604
679
  const words = useWords();
605
- const { form, data, navigation } = props;
680
+ const { form, data, navigation, language } = props;
606
681
  const { busy, error, act } = useAction();
607
682
  const administrator = data.me?.person?.role === "admin";
608
683
  const workspace = data.me?.workspace;
@@ -662,11 +737,11 @@ function WorkspaceSettingsPage(props) {
662
737
  navigation.replace(PATHS.settings);
663
738
  }), onCancel: () => navigation.back(PATHS.settings) })] })] }));
664
739
  }
665
- return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), workspace !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.workspace.aria, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), administrator && (_jsx("a", { className: "sc-button sc-button-secondary sc-button-sm", href: settingsHref("workspace"), children: words.common.edit }))] }), _jsx(WorkspaceSettingsView, { workspace: workspace })] })), _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
740
+ return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), workspace !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.workspace.aria, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), administrator && (_jsx("a", { className: "sc-button sc-button-secondary sc-button-sm", href: settingsHref("workspace"), children: words.common.edit }))] }), _jsx(WorkspaceSettingsView, { workspace: workspace, console: data.me?.console })] })), _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
666
741
  ? () => void act(async () => {
667
742
  setRules(await data.actions.archiveRules());
668
743
  })
669
- : 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 }) })) })] }), skills.address !== null && (_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsToken }), _jsxs("dd", { children: [skills.packToken == null ? (_jsx("span", { className: "sc-muted", children: words.projects.settings.packTokenNone })) : (_jsxs(_Fragment, { children: [words.projects.settings.packTokenSet, " ", _jsx(Time, { iso: skills.packToken.setAt })] })), administrator && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: settingsHref("pack-token"), children: skills.packToken == null
744
+ : undefined, busy: busy }))] }), administrator && (_jsxs("section", { className: "sc-panel", "aria-label": words.plugins.settings.title, children: [_jsx("h2", { className: "sc-section-title", children: words.plugins.settings.title }), data.plugins.length === 0 ? (_jsx("p", { className: "sc-settings-hint", children: words.plugins.settings.empty.body })) : (_jsx(PluginSettingsList, { plugins: data.plugins, language: language, href: (plugin) => pluginSettingsHref(plugin.key) }))] })), _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 }) })) })] }), skills.address !== null && (_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsToken }), _jsxs("dd", { children: [skills.packToken == null ? (_jsx("span", { className: "sc-muted", children: words.projects.settings.packTokenNone })) : (_jsxs(_Fragment, { children: [words.projects.settings.packTokenSet, " ", _jsx(Time, { iso: skills.packToken.setAt })] })), administrator && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: settingsHref("pack-token"), children: skills.packToken == null
670
745
  ? words.projects.settings.packTokenAdd
671
746
  : words.projects.settings.packTokenEdit })] }))] })] }))] }), _jsx("p", { className: "sc-muted", children: words.settings.skillsFromConfiguration })] }))] }));
672
747
  }
@@ -813,7 +888,7 @@ function MyDecisionsPage(props) {
813
888
  addressed.reload();
814
889
  raised.reload();
815
890
  });
816
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.myDecisions.title }) }), problem !== undefined && _jsx(Callout, { tone: "danger", children: problem }), addressed.items === undefined || raised.items === undefined ? (problem === undefined && (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.myDecisions.loading }) }))) : addressed.items.length === 0 && raised.items.length === 0 ? (_jsx(EmptyState, { title: words.myDecisions.empty.title, body: words.myDecisions.empty.body })) : (_jsx(MyDecisions, { addressed: { items: addressed.items, more: addressed.more, onMore: addressed.readMore }, raised: { items: raised.items, more: raised.more, onMore: raised.readMore }, decisionHref: decisionHref, onAnswer: onAnswer, busy: busy || addressed.busy || raised.busy }))] }));
891
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.myDecisions.title }) }), problem !== undefined && _jsx(Callout, { tone: "danger", children: problem }), addressed.items === undefined || raised.items === undefined ? (problem === undefined && (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.myDecisions.loading }) }))) : addressed.items.length === 0 && raised.items.length === 0 ? (_jsx(EmptyState, { title: words.myDecisions.empty.title, body: words.myDecisions.empty.body })) : (_jsx(MyDecisions, { addressed: { items: addressed.items, more: addressed.more, onMore: addressed.readMore }, raised: { items: raised.items, more: raised.more, onMore: raised.readMore }, decisionHref: decisionHref, fileHref: (projectKey, fileId) => projectPath(projectKey, "files", fileId), onAnswer: onAnswer, busy: busy || addressed.busy || raised.busy }))] }));
817
892
  }
818
893
  function WorkPage(props) {
819
894
  const words = useWords();
@@ -832,9 +907,15 @@ function PersonWorkPage(props) {
832
907
  }
833
908
  function WorkspacePage(props) {
834
909
  const words = useWords();
835
- const { route, data, components, navigation } = props;
910
+ const { route, data, components, navigation, client, language } = props;
836
911
  const [fresh, setFresh] = useState(undefined);
837
912
  const { busy, error, act } = useAction();
913
+ if (route.name === "plugin" && route.level === "workspace") {
914
+ return (_jsx(PluginPage, { route: route, data: data, project: undefined, client: client, language: language, navigation: navigation }));
915
+ }
916
+ if (route.name === "plugins-settings") {
917
+ return (_jsx(PluginSettingsPage, { plugin: route.plugin, data: data, client: client, language: language, navigation: navigation }));
918
+ }
838
919
  if (route.name === "work") {
839
920
  return _jsx(WorkPage, { data: data, components: components, week: route.week });
840
921
  }
@@ -845,7 +926,7 @@ function WorkspacePage(props) {
845
926
  return _jsx(PersonWorkPage, { id: route.id, data: data, components: components, week: route.week });
846
927
  }
847
928
  if (route.name === "workspace-settings") {
848
- return _jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation });
929
+ return (_jsx(WorkspaceSettingsPage, { form: route.form, data: data, navigation: navigation, language: language }));
849
930
  }
850
931
  if (route.name === "categories") {
851
932
  const me = data.me?.person ?? undefined;
@@ -903,6 +984,7 @@ export function createConsole(config = {}) {
903
984
  const fallbackTitle = config.title ?? "Console";
904
985
  const languages = config.languages ?? DEFAULT_LANGUAGES;
905
986
  const pages = config.pages ?? [];
987
+ // biome-ignore lint/correctness/noComponentHookFactories: createConsole runs once, when a console is built, never during a render; App is that console's one root component, closing over its config.
906
988
  function App(props) {
907
989
  const { location, navigation } = useNavigation(props.initialPath);
908
990
  useFollowLinks(navigation);
@@ -949,6 +1031,7 @@ export function createConsole(config = {}) {
949
1031
  href: PATHS.decisions,
950
1032
  label: words.nav.myDecisions,
951
1033
  current: route.name === "my-decisions",
1034
+ count: data.me?.decisions?.waiting,
952
1035
  },
953
1036
  {
954
1037
  href: PATHS.people,
@@ -966,13 +1049,6 @@ export function createConsole(config = {}) {
966
1049
  current: route.name === "agents" || route.name === "new-token",
967
1050
  },
968
1051
  ]),
969
- // The settings are read by everyone: the rules every agent follows, the categories and
970
- // what the colours mean, where the skills come from; kept by an administrator.
971
- {
972
- href: PATHS.settings,
973
- label: words.nav.settings,
974
- current: route.name === "workspace-settings" || route.name === "categories",
975
- },
976
1052
  ...pages
977
1053
  .filter((candidate) => candidate.level === "workspace")
978
1054
  .map((candidate) => ({
@@ -982,6 +1058,26 @@ export function createConsole(config = {}) {
982
1058
  route.level === "workspace" &&
983
1059
  route.page === candidate.key,
984
1060
  })),
1061
+ // The plugins' tabs (ADR-0054): the groups that hold a plugin the person has a page of.
1062
+ ...pluginNavItems({
1063
+ plugins: data.plugins,
1064
+ level: "workspace",
1065
+ current: {
1066
+ plugin: route.name === "plugin" && route.level === "workspace" ? route.plugin : undefined,
1067
+ },
1068
+ words,
1069
+ language: pack.tag,
1070
+ }),
1071
+ // The settings are read by everyone: the rules every agent follows, the categories and
1072
+ // what the colours mean, where the skills come from, the plugins; kept by an
1073
+ // administrator. Last, after the organization's own tabs and the plugins'.
1074
+ {
1075
+ href: PATHS.settings,
1076
+ label: words.nav.settings,
1077
+ current: route.name === "workspace-settings" ||
1078
+ route.name === "categories" ||
1079
+ route.name === "plugins-settings",
1080
+ },
985
1081
  ];
986
1082
  const nav = projectKey === undefined
987
1083
  ? workspaceNav
@@ -1029,12 +1125,6 @@ export function createConsole(config = {}) {
1029
1125
  label: words.nav.members,
1030
1126
  current: route.name === "members",
1031
1127
  },
1032
- // Read by every member: what the project is, and what its agents are asked (ADR-0023).
1033
- {
1034
- href: projectHref(projectKey, "settings"),
1035
- label: words.nav.settings,
1036
- current: route.name === "settings",
1037
- },
1038
1128
  ...pages
1039
1129
  .filter((candidate) => candidate.level === "project")
1040
1130
  .map((candidate) => ({
@@ -1044,6 +1134,23 @@ export function createConsole(config = {}) {
1044
1134
  route.level === "project" &&
1045
1135
  route.page === candidate.key,
1046
1136
  })),
1137
+ ...pluginNavItems({
1138
+ plugins: data.plugins,
1139
+ level: "project",
1140
+ project: { key: projectKey, plugins: data.projectPlugins },
1141
+ current: {
1142
+ plugin: route.name === "plugin" && route.level === "project" ? route.plugin : undefined,
1143
+ },
1144
+ words,
1145
+ language: pack.tag,
1146
+ }),
1147
+ // Read by every member: what the project is, and what its agents are asked
1148
+ // (ADR-0023). Last, after the organization's own tabs and the plugins'.
1149
+ {
1150
+ href: projectHref(projectKey, "settings"),
1151
+ label: words.nav.settings,
1152
+ current: route.name === "settings",
1153
+ },
1047
1154
  ];
1048
1155
  /** A page of the console's own at the address, or nothing there. */
1049
1156
  const customPageOf = (key, level) => {
@@ -1058,13 +1165,13 @@ export function createConsole(config = {}) {
1058
1165
  page = _jsx(NotWithToken, {});
1059
1166
  }
1060
1167
  else if (route.name === "projects" || route.name === "new-project") {
1061
- page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, making: route.name === "new-project" }));
1168
+ page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, client: client, language: pack.tag, making: route.name === "new-project" }));
1062
1169
  }
1063
1170
  else if (route.name === "custom" && route.level === "workspace") {
1064
1171
  page = customPageOf(route.page, "workspace");
1065
1172
  }
1066
1173
  else if (projectKey === undefined) {
1067
- page = (_jsx(WorkspacePage, { route: route, data: data, components: components, navigation: navigation }));
1174
+ page = (_jsx(WorkspacePage, { route: route, data: data, components: components, navigation: navigation, client: client, language: pack.tag }));
1068
1175
  }
1069
1176
  else if (data.projectError !== undefined) {
1070
1177
  page = (_jsx(EmptyState, { title: words.common.noSuchProject.title, body: words.common.noSuchProject.body, action: _jsx(LinkButton, { href: PATHS.projects, children: words.nav.projects }) }));
@@ -1076,7 +1183,7 @@ export function createConsole(config = {}) {
1076
1183
  page = customPageOf(route.page, "project");
1077
1184
  }
1078
1185
  else {
1079
- page = (_jsx(ProjectPage, { route: route, project: project, data: data, components: components, navigation: navigation }));
1186
+ page = (_jsx(ProjectPage, { route: route, project: project, data: data, components: components, navigation: navigation, client: client, language: pack.tag }));
1080
1187
  }
1081
1188
  content = (_jsx(components.Shell, { title: title, brand: config.brand, project: projectKey === undefined
1082
1189
  ? undefined
@@ -1092,7 +1199,11 @@ export function createConsole(config = {}) {
1092
1199
  route.name === "calendar" ||
1093
1200
  (route.name === "goal" && route.form === undefined) ||
1094
1201
  (route.name === "custom" &&
1095
- pages.some((candidate) => candidate.key === route.page && candidate.wide === true)), onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
1202
+ pages.some((candidate) => candidate.key === route.page && candidate.wide === true)) ||
1203
+ (route.name === "plugin" &&
1204
+ data.plugins.some((candidate) => candidate.key === route.plugin &&
1205
+ pluginPagesAt(candidate, route.level).some((candidate, index) => candidate.wide &&
1206
+ (route.page === undefined ? index === 0 : candidate.key === route.page)))), onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
1096
1207
  }
1097
1208
  return (_jsx(LanguageContext.Provider, { value: pack, children: _jsx(RemotePicturesContext.Provider, { value: pictureSrc, children: content }) }));
1098
1209
  }