@skillcdn/console 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/README.md +20 -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 +579 -45
  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 +277 -6
  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 +60 -7
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli/plugin-check.d.ts +21 -0
  18. package/dist/cli/plugin-check.d.ts.map +1 -0
  19. package/dist/cli/plugin-check.js +374 -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 +123 -0
  24. package/dist/cli/plugin-cli.js.map +1 -0
  25. package/dist/cli/plugin-init.d.ts +20 -0
  26. package/dist/cli/plugin-init.d.ts.map +1 -0
  27. package/dist/cli/plugin-init.js +74 -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 +109 -2
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +67 -2
  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/event-feed.d.ts +1 -1
  44. package/dist/components/event-feed.d.ts.map +1 -1
  45. package/dist/components/event-feed.js +33 -1
  46. package/dist/components/event-feed.js.map +1 -1
  47. package/dist/components/markdown.d.ts.map +1 -1
  48. package/dist/components/markdown.js +6 -3
  49. package/dist/components/markdown.js.map +1 -1
  50. package/dist/components/pack-connect.d.ts +3 -1
  51. package/dist/components/pack-connect.d.ts.map +1 -1
  52. package/dist/components/pack-connect.js +4 -4
  53. package/dist/components/pack-connect.js.map +1 -1
  54. package/dist/components/pack-token.d.ts +3 -1
  55. package/dist/components/pack-token.d.ts.map +1 -1
  56. package/dist/components/pack-token.js +1 -1
  57. package/dist/components/pack-token.js.map +1 -1
  58. package/dist/components/people.d.ts.map +1 -1
  59. package/dist/components/people.js +14 -3
  60. package/dist/components/people.js.map +1 -1
  61. package/dist/components/plugin-frame.d.ts +52 -0
  62. package/dist/components/plugin-frame.d.ts.map +1 -0
  63. package/dist/components/plugin-frame.js +314 -0
  64. package/dist/components/plugin-frame.js.map +1 -0
  65. package/dist/components/plugin-settings.d.ts +38 -0
  66. package/dist/components/plugin-settings.d.ts.map +1 -0
  67. package/dist/components/plugin-settings.js +348 -0
  68. package/dist/components/plugin-settings.js.map +1 -0
  69. package/dist/components/remote-pictures.d.ts +6 -0
  70. package/dist/components/remote-pictures.d.ts.map +1 -0
  71. package/dist/components/remote-pictures.js +8 -0
  72. package/dist/components/remote-pictures.js.map +1 -0
  73. package/dist/components/shell.d.ts +2 -0
  74. package/dist/components/shell.d.ts.map +1 -1
  75. package/dist/components/shell.js +26 -1
  76. package/dist/components/shell.js.map +1 -1
  77. package/dist/components/sign-in.d.ts.map +1 -1
  78. package/dist/components/sign-in.js +49 -2
  79. package/dist/components/sign-in.js.map +1 -1
  80. package/dist/components/ui.d.ts +3 -2
  81. package/dist/components/ui.d.ts.map +1 -1
  82. package/dist/components/ui.js +6 -3
  83. package/dist/components/ui.js.map +1 -1
  84. package/dist/components/workspace.d.ts +7 -1
  85. package/dist/components/workspace.d.ts.map +1 -1
  86. package/dist/components/workspace.js +1 -1
  87. package/dist/components/workspace.js.map +1 -1
  88. package/dist/console.css +384 -5
  89. package/dist/console.d.ts.map +1 -1
  90. package/dist/console.js +146 -29
  91. package/dist/console.js.map +1 -1
  92. package/dist/data.d.ts +11 -2
  93. package/dist/data.d.ts.map +1 -1
  94. package/dist/data.js +35 -21
  95. package/dist/data.js.map +1 -1
  96. package/dist/i18n/en.d.ts +163 -1
  97. package/dist/i18n/en.d.ts.map +1 -1
  98. package/dist/i18n/en.js +174 -1
  99. package/dist/i18n/en.js.map +1 -1
  100. package/dist/i18n/ko.d.ts.map +1 -1
  101. package/dist/i18n/ko.js +154 -1
  102. package/dist/i18n/ko.js.map +1 -1
  103. package/dist/index.d.ts +3 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +3 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/limits.d.ts +14 -0
  108. package/dist/limits.d.ts.map +1 -1
  109. package/dist/limits.js +14 -0
  110. package/dist/limits.js.map +1 -1
  111. package/dist/plugin/args.d.ts +148 -0
  112. package/dist/plugin/args.d.ts.map +1 -0
  113. package/dist/plugin/args.js +369 -0
  114. package/dist/plugin/args.js.map +1 -0
  115. package/dist/plugin/bridge.d.ts +146 -0
  116. package/dist/plugin/bridge.d.ts.map +1 -0
  117. package/dist/plugin/bridge.js +125 -0
  118. package/dist/plugin/bridge.js.map +1 -0
  119. package/dist/plugin/capabilities.d.ts +20 -0
  120. package/dist/plugin/capabilities.d.ts.map +1 -0
  121. package/dist/plugin/capabilities.js +133 -0
  122. package/dist/plugin/capabilities.js.map +1 -0
  123. package/dist/plugin/context.d.ts +374 -0
  124. package/dist/plugin/context.d.ts.map +1 -0
  125. package/dist/plugin/context.js +2 -0
  126. package/dist/plugin/context.js.map +1 -0
  127. package/dist/plugin/define.d.ts +208 -0
  128. package/dist/plugin/define.d.ts.map +1 -0
  129. package/dist/plugin/define.js +482 -0
  130. package/dist/plugin/define.js.map +1 -0
  131. package/dist/plugin/definition-error.d.ts +11 -0
  132. package/dist/plugin/definition-error.d.ts.map +1 -0
  133. package/dist/plugin/definition-error.js +17 -0
  134. package/dist/plugin/definition-error.js.map +1 -0
  135. package/dist/plugin/limits.d.ts +67 -0
  136. package/dist/plugin/limits.d.ts.map +1 -0
  137. package/dist/plugin/limits.js +70 -0
  138. package/dist/plugin/limits.js.map +1 -0
  139. package/dist/plugin/load-worker.d.ts +7 -0
  140. package/dist/plugin/load-worker.d.ts.map +1 -0
  141. package/dist/plugin/load-worker.js +41 -0
  142. package/dist/plugin/load-worker.js.map +1 -0
  143. package/dist/plugin/load.d.ts +18 -0
  144. package/dist/plugin/load.d.ts.map +1 -0
  145. package/dist/plugin/load.js +62 -0
  146. package/dist/plugin/load.js.map +1 -0
  147. package/dist/plugin/manifest.d.ts +237 -0
  148. package/dist/plugin/manifest.d.ts.map +1 -0
  149. package/dist/plugin/manifest.js +411 -0
  150. package/dist/plugin/manifest.js.map +1 -0
  151. package/dist/plugin/refusals.d.ts +49 -0
  152. package/dist/plugin/refusals.d.ts.map +1 -0
  153. package/dist/plugin/refusals.js +79 -0
  154. package/dist/plugin/refusals.js.map +1 -0
  155. package/dist/plugin/rest-schemas.d.ts +556 -0
  156. package/dist/plugin/rest-schemas.d.ts.map +1 -0
  157. package/dist/plugin/rest-schemas.js +262 -0
  158. package/dist/plugin/rest-schemas.js.map +1 -0
  159. package/dist/plugin/sandbox.d.ts +23 -0
  160. package/dist/plugin/sandbox.d.ts.map +1 -0
  161. package/dist/plugin/sandbox.js +99 -0
  162. package/dist/plugin/sandbox.js.map +1 -0
  163. package/dist/plugin/testing.d.ts +45 -0
  164. package/dist/plugin/testing.d.ts.map +1 -0
  165. package/dist/plugin/testing.js +420 -0
  166. package/dist/plugin/testing.js.map +1 -0
  167. package/dist/plugin/web-react.d.ts +11 -0
  168. package/dist/plugin/web-react.d.ts.map +1 -0
  169. package/dist/plugin/web-react.js +34 -0
  170. package/dist/plugin/web-react.js.map +1 -0
  171. package/dist/plugin/web.d.ts +95 -0
  172. package/dist/plugin/web.d.ts.map +1 -0
  173. package/dist/plugin/web.js +245 -0
  174. package/dist/plugin/web.js.map +1 -0
  175. package/dist/plugin-nav.d.ts +29 -0
  176. package/dist/plugin-nav.d.ts.map +1 -0
  177. package/dist/plugin-nav.js +92 -0
  178. package/dist/plugin-nav.js.map +1 -0
  179. package/dist/plugin-testing.d.ts +2 -0
  180. package/dist/plugin-testing.d.ts.map +1 -0
  181. package/dist/plugin-testing.js +4 -0
  182. package/dist/plugin-testing.js.map +1 -0
  183. package/dist/plugin-web-react.d.ts +3 -0
  184. package/dist/plugin-web-react.d.ts.map +1 -0
  185. package/dist/plugin-web-react.js +3 -0
  186. package/dist/plugin-web-react.js.map +1 -0
  187. package/dist/plugin-web.d.ts +4 -0
  188. package/dist/plugin-web.d.ts.map +1 -0
  189. package/dist/plugin-web.js +5 -0
  190. package/dist/plugin-web.js.map +1 -0
  191. package/dist/plugin.d.ts +11 -0
  192. package/dist/plugin.d.ts.map +1 -0
  193. package/dist/plugin.js +15 -0
  194. package/dist/plugin.js.map +1 -0
  195. package/dist/router.d.ts +28 -0
  196. package/dist/router.d.ts.map +1 -1
  197. package/dist/router.js +47 -0
  198. package/dist/router.js.map +1 -1
  199. package/dist/routes.d.ts +6 -0
  200. package/dist/routes.d.ts.map +1 -1
  201. package/dist/routes.js +8 -0
  202. package/dist/routes.js.map +1 -1
  203. package/dist/schemas.d.ts +147 -1
  204. package/dist/schemas.d.ts.map +1 -1
  205. package/dist/schemas.js +55 -5
  206. package/dist/schemas.js.map +1 -1
  207. package/dist/tokens.d.ts +2 -0
  208. package/dist/tokens.d.ts.map +1 -0
  209. package/dist/tokens.js +101 -0
  210. package/dist/tokens.js.map +1 -0
  211. package/dist/vocabulary.d.ts +11 -1
  212. package/dist/vocabulary.d.ts.map +1 -1
  213. package/dist/vocabulary.js +24 -0
  214. package/dist/vocabulary.js.map +1 -1
  215. package/dist/web.d.ts.map +1 -1
  216. package/dist/web.js +5 -2
  217. package/dist/web.js.map +1 -1
  218. package/docs/specs/cli.md +16 -9
  219. package/docs/specs/plugins.md +487 -0
  220. package/docs/specs/rest.md +25 -13
  221. package/package.json +21 -3
  222. package/skills/building-a-console/SKILL.md +25 -26
  223. package/skills/building-a-console/references/configuring.md +40 -0
  224. package/skills/building-a-console/references/the-default-console.md +1 -1
  225. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  226. package/skills/building-a-plugin/SKILL.md +137 -0
  227. package/skills/deploying-a-console/SKILL.md +24 -26
  228. package/skills/deploying-a-console/references/access.md +13 -9
  229. package/skills/deploying-a-console/references/resources.md +25 -10
  230. package/skills/deploying-a-console/references/running-it-again.md +15 -0
  231. package/skills/deploying-a-console/references/sizing.md +2 -0
  232. package/skills/planning-the-board/SKILL.md +96 -0
  233. package/skills/planning-the-board/references/a-task.md +36 -0
  234. package/skills/planning-the-board/references/setting-up.md +44 -0
  235. package/skills/working-the-board/SKILL.md +43 -34
  236. package/skills/working-the-board/references/the-pack.md +19 -0
  237. package/src/api.ts +16 -0
  238. package/src/cli/cli.ts +736 -45
  239. package/src/cli/format.ts +356 -9
  240. package/src/cli/help.ts +71 -6
  241. package/src/cli/plugin-check.ts +470 -0
  242. package/src/cli/plugin-cli.ts +148 -0
  243. package/src/cli/plugin-init.ts +90 -0
  244. package/src/cli/plugin-main.ts +30 -0
  245. package/src/client.ts +324 -1
  246. package/src/compat.ts +2 -2
  247. package/src/components/board.tsx +62 -14
  248. package/src/components/event-feed.tsx +42 -1
  249. package/src/components/markdown.tsx +6 -3
  250. package/src/components/pack-connect.tsx +10 -7
  251. package/src/components/pack-token.tsx +4 -1
  252. package/src/components/people.tsx +17 -7
  253. package/src/components/plugin-frame.tsx +437 -0
  254. package/src/components/plugin-settings.tsx +1111 -0
  255. package/src/components/remote-pictures.ts +19 -0
  256. package/src/components/shell.tsx +82 -14
  257. package/src/components/sign-in.tsx +114 -13
  258. package/src/components/ui.tsx +6 -3
  259. package/src/components/workspace.tsx +18 -0
  260. package/src/console.tsx +309 -23
  261. package/src/data.ts +47 -22
  262. package/src/i18n/en.ts +183 -1
  263. package/src/i18n/ko.ts +159 -1
  264. package/src/index.ts +12 -0
  265. package/src/limits.ts +14 -0
  266. package/src/plugin/args.ts +526 -0
  267. package/src/plugin/bridge.ts +314 -0
  268. package/src/plugin/capabilities.ts +150 -0
  269. package/src/plugin/context.ts +416 -0
  270. package/src/plugin/define.ts +792 -0
  271. package/src/plugin/definition-error.ts +19 -0
  272. package/src/plugin/limits.ts +70 -0
  273. package/src/plugin/load-worker.ts +50 -0
  274. package/src/plugin/load.ts +82 -0
  275. package/src/plugin/manifest.ts +561 -0
  276. package/src/plugin/refusals.ts +94 -0
  277. package/src/plugin/rest-schemas.ts +354 -0
  278. package/src/plugin/sandbox.ts +123 -0
  279. package/src/plugin/testing.ts +612 -0
  280. package/src/plugin/web-react.ts +45 -0
  281. package/src/plugin/web.ts +403 -0
  282. package/src/plugin-nav.ts +132 -0
  283. package/src/plugin-testing.ts +10 -0
  284. package/src/plugin-web-react.ts +4 -0
  285. package/src/plugin-web.ts +35 -0
  286. package/src/plugin.ts +126 -0
  287. package/src/router.ts +73 -0
  288. package/src/routes.ts +9 -0
  289. package/src/schemas.ts +60 -4
  290. package/src/styles/console.css +384 -5
  291. package/src/tokens.ts +101 -0
  292. package/src/vocabulary.ts +30 -0
  293. package/src/web.ts +5 -2
  294. package/template/plugin/.gitignore +2 -0
  295. package/template/plugin/README.md +26 -0
  296. package/template/plugin/SKILLCDN.md +13 -0
  297. package/template/plugin/build.mjs +40 -0
  298. package/template/plugin/console-plugin.json +50 -0
  299. package/template/plugin/package.json +27 -0
  300. package/template/plugin/skills/__key__/SKILL.md +23 -0
  301. package/template/plugin/src/check.test.ts +34 -0
  302. package/template/plugin/src/server/index.test.ts +41 -0
  303. package/template/plugin/src/server/index.ts +72 -0
  304. package/template/plugin/src/web/index.css +51 -0
  305. package/template/plugin/src/web/index.ts +115 -0
  306. package/template/plugin/tsconfig.json +17 -0
  307. package/skills/working-the-board/references/setting-up.md +0 -3
  308. /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
@@ -0,0 +1,19 @@
1
+ import { createContext, useContext } from "react";
2
+ import { REST_ROUTES } from "../routes.js";
3
+
4
+ // Where a picture from elsewhere is read (ADR-0048): through the console, which fetches it and
5
+ // keeps it a while, so that the person's browser reaches nobody's server and the pages load
6
+ // nothing from elsewhere. The composition provides the client's way to it; a component outside
7
+ // one reads it at the API's own path, which a console served beside its API answers.
8
+
9
+ /** The address a picture from elsewhere is read at, given the https URL that names it. */
10
+ export type RemotePictureSrc = (url: string) => string;
11
+
12
+ export const defaultRemotePictureSrc: RemotePictureSrc = (url) =>
13
+ `${REST_ROUTES.pictures}?src=${encodeURIComponent(url)}`;
14
+
15
+ export const RemotePicturesContext = createContext<RemotePictureSrc>(defaultRemotePictureSrc);
16
+
17
+ export function useRemotePictureSrc(): RemotePictureSrc {
18
+ return useContext(RemotePicturesContext);
19
+ }
@@ -13,6 +13,8 @@ export interface NavItem {
13
13
  readonly current: boolean;
14
14
  /** A count beside the label: the decisions waiting, for example. */
15
15
  readonly count?: number | undefined;
16
+ /** The pages the tab opens a list of, as a group of plugins does (ADR-0054); the tab itself leads to the first. */
17
+ readonly children?: readonly NavItem[] | undefined;
16
18
  }
17
19
 
18
20
  /** The addresses of a brand's marks (ADR-0013): shown beside the workspace's name, and above it when signing in. */
@@ -67,6 +69,68 @@ export function isPlainClick(event: {
67
69
  return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
68
70
  }
69
71
 
72
+ /** A tab that opens a list of pages, as a group of several plugins does: a details, as the person's menu is. Closes once used. */
73
+ function TabGroup(props: { readonly item: NavItem; readonly onNavigate: (href: string) => void }) {
74
+ const details = useRef<HTMLDetailsElement>(null);
75
+ useEffect(() => {
76
+ const onClick = (event: MouseEvent) => {
77
+ const element = details.current;
78
+ if (
79
+ element?.open === true &&
80
+ !(event.target instanceof Node && element.contains(event.target))
81
+ ) {
82
+ element.open = false;
83
+ }
84
+ };
85
+ document.addEventListener("click", onClick);
86
+ return () => document.removeEventListener("click", onClick);
87
+ }, []);
88
+ const { item } = props;
89
+ return (
90
+ <details className="sc-tab-group" ref={details}>
91
+ <summary
92
+ className={cx("sc-tab", "sc-tab-summary", item.current && "sc-tab-current")}
93
+ aria-current={item.current ? "page" : undefined}
94
+ >
95
+ {item.label}
96
+ <svg className="sc-menu-caret" viewBox="0 0 16 16" aria-hidden="true">
97
+ <path
98
+ d="M4 6l4 4 4-4"
99
+ fill="none"
100
+ stroke="currentColor"
101
+ strokeWidth="1.5"
102
+ strokeLinecap="round"
103
+ strokeLinejoin="round"
104
+ />
105
+ </svg>
106
+ </summary>
107
+ <div className="sc-menu-panel sc-tab-group-panel">
108
+ <div className="sc-menu-list">
109
+ {(item.children ?? []).map((child) => (
110
+ <a
111
+ key={child.href}
112
+ href={child.href}
113
+ className="sc-menu-link"
114
+ aria-current={child.current ? "page" : undefined}
115
+ onClick={(event) => {
116
+ if (isPlainClick(event)) {
117
+ event.preventDefault();
118
+ if (details.current !== null) {
119
+ details.current.open = false;
120
+ }
121
+ props.onNavigate(child.href);
122
+ }
123
+ }}
124
+ >
125
+ {child.label}
126
+ </a>
127
+ ))}
128
+ </div>
129
+ </div>
130
+ </details>
131
+ );
132
+ }
133
+
70
134
  /** The person's menu: who they are, the workspace's pages, the languages, and the way out. Closes once used. */
71
135
  function PersonMenu(props: {
72
136
  readonly person: RestPerson;
@@ -296,20 +360,24 @@ export function Shell(props: ShellProps) {
296
360
  {props.nav.length > 0 && (
297
361
  <nav ref={tabs} className="sc-tabs" aria-label={words.shell.pages}>
298
362
  <div className="sc-tabs-inner">
299
- {props.nav.map((item) => (
300
- <a
301
- key={item.href}
302
- href={item.href}
303
- className={cx("sc-tab", item.current && "sc-tab-current")}
304
- aria-current={item.current ? "page" : undefined}
305
- onClick={follow(item.href)}
306
- >
307
- {item.label}
308
- {item.count !== undefined && item.count > 0 && (
309
- <span className="sc-nav-count">{item.count}</span>
310
- )}
311
- </a>
312
- ))}
363
+ {props.nav.map((item) =>
364
+ item.children !== undefined && item.children.length > 0 ? (
365
+ <TabGroup key={item.href} item={item} onNavigate={props.onNavigate} />
366
+ ) : (
367
+ <a
368
+ key={item.href}
369
+ href={item.href}
370
+ className={cx("sc-tab", item.current && "sc-tab-current")}
371
+ aria-current={item.current ? "page" : undefined}
372
+ onClick={follow(item.href)}
373
+ >
374
+ {item.label}
375
+ {item.count !== undefined && item.count > 0 && (
376
+ <span className="sc-nav-count">{item.count}</span>
377
+ )}
378
+ </a>
379
+ ),
380
+ )}
313
381
  </div>
314
382
  </nav>
315
383
  )}
@@ -1,10 +1,11 @@
1
- import { loginPath, type RestProvider, type SignInFailure } from "../api.js";
1
+ import { type MouseEvent, useEffect, useState } from "react";
2
+ import { loginPath, type ProviderKey, type RestProvider, type SignInFailure } from "../api.js";
2
3
  import { useWords } from "../i18n/index.js";
3
4
  import type { Brand } from "./shell.js";
4
- import { Callout } from "./ui.js";
5
+ import { Callout, cx } from "./ui.js";
5
6
 
6
7
  // The page a person sees before they are anyone: the way to each provider the deployment has,
7
- // and what went wrong the last time, when something did.
8
+ // who gets in through it, and what went wrong the last time, when something did.
8
9
 
9
10
  export interface SignInProps {
10
11
  readonly title: string;
@@ -19,6 +20,35 @@ export interface SignInProps {
19
20
 
20
21
  export function SignIn(props: SignInProps) {
21
22
  const words = useWords().signIn;
23
+ // The way being taken: its button says so from the click until the page leaves, and says so
24
+ // no more when the browser brings this page back from its cache, as on the way back.
25
+ const [taken, setTaken] = useState<ProviderKey | undefined>(undefined);
26
+ useEffect(() => {
27
+ const shown = (event: PageTransitionEvent): void => {
28
+ if (event.persisted) {
29
+ setTaken(undefined);
30
+ }
31
+ };
32
+ window.addEventListener("pageshow", shown);
33
+ return () => window.removeEventListener("pageshow", shown);
34
+ }, []);
35
+ const take = (provider: ProviderKey) => (event: MouseEvent<HTMLAnchorElement>) => {
36
+ if (taken !== undefined) {
37
+ // One way at a time: a second click, on any of the buttons, goes nowhere.
38
+ event.preventDefault();
39
+ return;
40
+ }
41
+ setTaken(provider);
42
+ };
43
+ const admitsOf = (admits: NonNullable<RestProvider["admits"]>): string => {
44
+ if (admits.organization !== null) {
45
+ return words.admits.organization(admits.organization);
46
+ }
47
+ if (admits.domains.length > 0) {
48
+ return words.admits.domains(admits.domains.join(", "));
49
+ }
50
+ return words.admits.listed;
51
+ };
22
52
  return (
23
53
  <div className="sc-sign-in">
24
54
  {props.brand?.symbol !== undefined && (
@@ -44,18 +74,89 @@ export function SignIn(props: SignInProps) {
44
74
  </Callout>
45
75
  ) : (
46
76
  <div className="sc-sign-in-ways">
47
- {props.providers.map((provider) => (
48
- // A real navigation: signing in happens at the provider, not in this page.
49
- <a
50
- key={provider.key}
51
- className="sc-button sc-button-primary sc-sign-in-button"
52
- href={loginPath(provider.key, props.returnTo)}
53
- >
54
- {words.continueWith(provider.label)}
55
- </a>
56
- ))}
77
+ {props.providers.map((provider) => {
78
+ const going = taken === provider.key;
79
+ return (
80
+ <div key={provider.key} className="sc-sign-in-way">
81
+ {/* A real navigation: signing in happens at the provider, not in this page. */}
82
+ <a
83
+ className={cx(
84
+ "sc-button sc-button-primary sc-sign-in-button",
85
+ going && "sc-sign-in-button-going",
86
+ )}
87
+ href={loginPath(provider.key, props.returnTo)}
88
+ aria-busy={going ? true : undefined}
89
+ aria-disabled={taken !== undefined && !going ? true : undefined}
90
+ onClick={take(provider.key)}
91
+ >
92
+ <ProviderMark provider={provider.key} />
93
+ <span className="sc-sign-in-button-label">
94
+ {going ? words.going(provider.label) : words.signInWith(provider.label)}
95
+ </span>
96
+ {going && (
97
+ <span className="sc-spinner sc-sign-in-spinner" aria-hidden="true">
98
+ <span className="sc-spinner-ring" />
99
+ </span>
100
+ )}
101
+ </a>
102
+ {provider.admits !== undefined && (
103
+ <p className="sc-sign-in-admits">{admitsOf(provider.admits)}</p>
104
+ )}
105
+ </div>
106
+ );
107
+ })}
57
108
  </div>
58
109
  )}
59
110
  </div>
60
111
  );
61
112
  }
113
+
114
+ /**
115
+ * The provider's mark, as people know it from sign-in buttons elsewhere, beside the label and
116
+ * decorative: the host's own mark in the button's text color, Google's lettermark on a light
117
+ * tile, since its colors are its own. A provider without a mark shows the label alone.
118
+ */
119
+ function ProviderMark(props: { readonly provider: ProviderKey }) {
120
+ if (props.provider === "gh") {
121
+ return (
122
+ <svg
123
+ className="sc-sign-in-mark"
124
+ viewBox="0 0 16 16"
125
+ width="20"
126
+ height="20"
127
+ aria-hidden="true"
128
+ focusable="false"
129
+ >
130
+ <path
131
+ fill="currentColor"
132
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
133
+ />
134
+ </svg>
135
+ );
136
+ }
137
+ if (props.provider === "google") {
138
+ return (
139
+ <span className="sc-sign-in-mark sc-sign-in-mark-tile" aria-hidden="true">
140
+ <svg viewBox="0 0 48 48" width="16" height="16" aria-hidden="true" focusable="false">
141
+ <path
142
+ fill="#FFC107"
143
+ d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"
144
+ />
145
+ <path
146
+ fill="#FF3D00"
147
+ d="M6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z"
148
+ />
149
+ <path
150
+ fill="#4CAF50"
151
+ d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z"
152
+ />
153
+ <path
154
+ fill="#1976D2"
155
+ d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z"
156
+ />
157
+ </svg>
158
+ </span>
159
+ );
160
+ }
161
+ return null;
162
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from "../api.js";
12
12
  import { dayOf, daysBetween, formatDay, today } from "../dates.js";
13
13
  import { useLanguage, useWords } from "../i18n/index.js";
14
+ import { useRemotePictureSrc } from "./remote-pictures.js";
14
15
 
15
16
  // Small building blocks. Each one is a class in styles/console.css and nothing more. Classes
16
17
  // are prefixed `sc-` so that a custom console's own styles never collide with them. The words
@@ -96,13 +97,15 @@ export function personName(person: {
96
97
  }
97
98
 
98
99
  /**
99
- * A person's picture as the git host serves it: loaded lazily, without a referrer, and gone as
100
- * a whole when there is none or it cannot be loaded, leaving the initial of the name instead.
100
+ * A person's picture as the git host serves it, read through the console (ADR-0048): loaded
101
+ * lazily, without a referrer, and gone as a whole when there is none or it cannot be loaded,
102
+ * leaving the initial of the name instead.
101
103
  */
102
104
  export function Avatar(props: {
103
105
  readonly person: RestPerson;
104
106
  readonly size?: "xs" | "sm" | "md" | "lg";
105
107
  }) {
108
+ const remote = useRemotePictureSrc();
106
109
  const size = props.size ?? "md";
107
110
  const initial = personName(props.person).slice(0, 1).toUpperCase();
108
111
  if (props.person.avatar === null) {
@@ -118,7 +121,7 @@ export function Avatar(props: {
118
121
  return (
119
122
  <img
120
123
  className={cx("sc-avatar", `sc-avatar-${size}`)}
121
- src={props.person.avatar}
124
+ src={remote(props.person.avatar)}
122
125
  alt=""
123
126
  loading="lazy"
124
127
  decoding="async"
@@ -2,6 +2,7 @@ import { type FormEvent, useId, useMemo, useState } from "react";
2
2
  import {
3
3
  PEOPLE_VISIBILITIES,
4
4
  type PeopleVisibility,
5
+ type RestMe,
5
6
  type RestWorkspace,
6
7
  type RestWorkspacePatch,
7
8
  WEEK_STARTS,
@@ -117,6 +118,12 @@ const labelOf = (zone: TimeZoneOption): string =>
117
118
 
118
119
  export interface WorkspaceSettingsViewProps {
119
120
  readonly workspace: RestWorkspace;
121
+ /**
122
+ * The console's version with the range of command versions it takes, as `GET /api/v1/me`
123
+ * says them (ADR-0025): shown first, so that whoever keeps the agents' commands up to date
124
+ * reads here which console they must match. Left out, nothing is said.
125
+ */
126
+ readonly console?: RestMe["console"];
120
127
  }
121
128
 
122
129
  /** The settings as they stand, one row each; where they are changed is the page's business. */
@@ -128,6 +135,17 @@ export function WorkspaceSettingsView(props: WorkspaceSettingsViewProps) {
128
135
  const offset = zone === undefined ? undefined : offsetOf(zone, new Date());
129
136
  return (
130
137
  <dl className="sc-settings-list">
138
+ {props.console !== undefined && (
139
+ <div className="sc-settings-item">
140
+ <dt>{settings.consoleVersion}</dt>
141
+ <dd>
142
+ {props.console.version}
143
+ <span className="sc-settings-note">
144
+ {settings.consoleTakes(props.console.clientRange)}
145
+ </span>
146
+ </dd>
147
+ </div>
148
+ )}
131
149
  <div className="sc-settings-item">
132
150
  <dt>{settings.peopleVisibility}</dt>
133
151
  <dd>{words.vocabulary.peopleVisibility[peopleVisibilityOf(props.workspace)]}</dd>