@skillcdn/console 0.1.5 → 0.1.7

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 (89) hide show
  1. package/README.md +50 -2
  2. package/SKILLCDN.md +29 -0
  3. package/dist/cli/cli.d.ts +3 -0
  4. package/dist/cli/cli.d.ts.map +1 -1
  5. package/dist/cli/cli.js +61 -0
  6. package/dist/cli/cli.js.map +1 -1
  7. package/dist/cli/help.d.ts.map +1 -1
  8. package/dist/cli/help.js +5 -0
  9. package/dist/cli/help.js.map +1 -1
  10. package/dist/cli/main.js +2 -0
  11. package/dist/cli/main.js.map +1 -1
  12. package/dist/cli/serve.d.ts +37 -0
  13. package/dist/cli/serve.d.ts.map +1 -0
  14. package/dist/cli/serve.js +218 -0
  15. package/dist/cli/serve.js.map +1 -0
  16. package/dist/console.css +33 -10
  17. package/dist/console.d.ts +6 -0
  18. package/dist/console.d.ts.map +1 -1
  19. package/dist/console.js +78 -13
  20. package/dist/console.js.map +1 -1
  21. package/dist/data.d.ts.map +1 -1
  22. package/dist/data.js +5 -1
  23. package/dist/data.js.map +1 -1
  24. package/dist/i18n/en.d.ts +4 -0
  25. package/dist/i18n/en.d.ts.map +1 -1
  26. package/dist/i18n/en.js +4 -0
  27. package/dist/i18n/en.js.map +1 -1
  28. package/dist/i18n/ko.d.ts.map +1 -1
  29. package/dist/i18n/ko.js +4 -0
  30. package/dist/i18n/ko.js.map +1 -1
  31. package/dist/schemas.d.ts +1 -0
  32. package/dist/schemas.d.ts.map +1 -1
  33. package/dist/schemas.js +5 -0
  34. package/dist/schemas.js.map +1 -1
  35. package/dist/web.d.ts +16 -0
  36. package/dist/web.d.ts.map +1 -0
  37. package/dist/web.js +149 -0
  38. package/dist/web.js.map +1 -0
  39. package/docs/specs/cli.md +92 -0
  40. package/docs/specs/rest.md +220 -0
  41. package/package.json +14 -3
  42. package/skills/building-a-console/SKILL.md +79 -0
  43. package/skills/working-the-board/SKILL.md +78 -0
  44. package/src/api.ts +235 -0
  45. package/src/cli/cli.ts +1114 -0
  46. package/src/cli/credentials.ts +68 -0
  47. package/src/cli/directory.ts +59 -0
  48. package/src/cli/format.ts +284 -0
  49. package/src/cli/help.ts +326 -0
  50. package/src/cli/main.ts +39 -0
  51. package/src/cli/serve.ts +276 -0
  52. package/src/cli/terminal.ts +28 -0
  53. package/src/client.ts +533 -0
  54. package/src/components/board.tsx +112 -0
  55. package/src/components/connect.tsx +126 -0
  56. package/src/components/decision-form.tsx +136 -0
  57. package/src/components/decision-list.tsx +278 -0
  58. package/src/components/documents.tsx +569 -0
  59. package/src/components/event-feed.tsx +129 -0
  60. package/src/components/markdown.tsx +72 -0
  61. package/src/components/people.tsx +59 -0
  62. package/src/components/projects.tsx +364 -0
  63. package/src/components/runs.tsx +175 -0
  64. package/src/components/shell.tsx +284 -0
  65. package/src/components/sign-in.tsx +61 -0
  66. package/src/components/skills.tsx +78 -0
  67. package/src/components/task-form.tsx +229 -0
  68. package/src/components/task-view.tsx +272 -0
  69. package/src/components/tokens.tsx +223 -0
  70. package/src/components/ui.tsx +219 -0
  71. package/src/connect-page.tsx +148 -0
  72. package/src/connect.ts +27 -0
  73. package/src/console.tsx +1166 -0
  74. package/src/data.ts +493 -0
  75. package/src/docs-page.tsx +334 -0
  76. package/src/documents.ts +76 -0
  77. package/src/i18n/en.ts +537 -0
  78. package/src/i18n/index.ts +76 -0
  79. package/src/i18n/ko.ts +511 -0
  80. package/src/index.ts +142 -0
  81. package/src/limits.ts +61 -0
  82. package/src/router.ts +295 -0
  83. package/src/routes.ts +106 -0
  84. package/src/schemas.ts +776 -0
  85. package/src/styles/console.css +2432 -0
  86. package/src/text.ts +33 -0
  87. package/src/use-action.ts +37 -0
  88. package/src/vocabulary.ts +109 -0
  89. package/src/web.ts +170 -0
@@ -0,0 +1,218 @@
1
+ import { createServer } from "node:http";
2
+ import { Readable } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ // A console of a person's own, served from their machine (ADR-0014): the files of a build, and
5
+ // everything under /api/ carried to the organization's console with the token the command
6
+ // keeps. The page and the API share one origin on the loopback, so the page needs no token and
7
+ // the organization's console no allowance: it sees a token's requests, as it sees the command's.
8
+ /** Where the command listens when nothing is said: next to the api role's 11199 and the dev UI's 11198. */
9
+ export const DEFAULT_SERVE_PORT = 11197;
10
+ /** The one address the command listens on. */
11
+ export const LOOPBACK = "127.0.0.1";
12
+ /** Headers that belong to one connection and never travel over the next. */
13
+ const HOP_BY_HOP = new Set([
14
+ "connection",
15
+ "keep-alive",
16
+ "proxy-authenticate",
17
+ "proxy-authorization",
18
+ "proxy-connection",
19
+ "te",
20
+ "trailer",
21
+ "transfer-encoding",
22
+ "upgrade",
23
+ ]);
24
+ /**
25
+ * What of the browser's request does not go to the console: the loopback's host, anything of a
26
+ * session (the token is the credential), what names the page's origin (a token's request needs
27
+ * none), and the encodings the browser takes, since what comes back is passed on decoded.
28
+ */
29
+ const NOT_FORWARDED = new Set([
30
+ "host",
31
+ "cookie",
32
+ "authorization",
33
+ "origin",
34
+ "referer",
35
+ "accept-encoding",
36
+ ]);
37
+ /** What of the console's answer does not go to the browser: a cookie would be the loopback's, and the body arrives decoded. */
38
+ const NOT_ANSWERED = new Set([
39
+ "set-cookie",
40
+ "content-encoding",
41
+ "content-length",
42
+ ]);
43
+ const json = (status, code, message) => new Response(JSON.stringify({ error: { code, message } }), {
44
+ status,
45
+ headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
46
+ });
47
+ const plain = (status, message) => new Response(`${message}\n`, {
48
+ status,
49
+ headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
50
+ });
51
+ /**
52
+ * Whether a request to the API comes from the console's own page, or from no page at all.
53
+ * Browsers say which site a request comes from, and one from another site is that site's
54
+ * request made with the person's standing: refused. A browser too old to say names the page's
55
+ * origin on a request that changes something, which is compared instead. A request that says
56
+ * nothing of either is not a browser's.
57
+ */
58
+ function fromOwnPage(request, origin) {
59
+ const site = request.headers.get("sec-fetch-site");
60
+ if (site !== null) {
61
+ return site === "same-origin" || site === "none";
62
+ }
63
+ const from = request.headers.get("origin");
64
+ return from === null || from === origin;
65
+ }
66
+ async function forward(request, url, upstream, options) {
67
+ const headers = new Headers();
68
+ request.headers.forEach((value, name) => {
69
+ if (!HOP_BY_HOP.has(name) && !NOT_FORWARDED.has(name) && !name.startsWith("sec-")) {
70
+ headers.set(name, value);
71
+ }
72
+ });
73
+ headers.set("authorization", `Bearer ${options.token}`);
74
+ headers.set("accept-encoding", "identity");
75
+ const withBody = request.method !== "GET" && request.method !== "HEAD";
76
+ let answer;
77
+ try {
78
+ answer = await options.fetch(`${upstream}${url.pathname}${url.search}`, {
79
+ method: request.method,
80
+ headers,
81
+ body: withBody ? request.body : undefined,
82
+ redirect: "manual",
83
+ signal: request.signal,
84
+ // A streamed body has to be declared as such.
85
+ ...(withBody ? { duplex: "half" } : {}),
86
+ });
87
+ }
88
+ catch {
89
+ return json(502, "network", "The console could not be reached.");
90
+ }
91
+ const out = new Headers();
92
+ answer.headers.forEach((value, name) => {
93
+ if (!HOP_BY_HOP.has(name) && !NOT_ANSWERED.has(name)) {
94
+ out.set(name, value);
95
+ }
96
+ });
97
+ return new Response(answer.body, { status: answer.status, headers: out });
98
+ }
99
+ /**
100
+ * What the command answers: the API carried to the console with the token, for the page's own
101
+ * requests; the build's files and its page for everything else, when there is a build; and
102
+ * nothing to a request whose host is not this loopback, which is a page elsewhere whose name
103
+ * was made to resolve here.
104
+ */
105
+ export function createServeHandler(options) {
106
+ const hosts = new Set([
107
+ `${LOOPBACK}:${options.port}`,
108
+ `localhost:${options.port}`,
109
+ `[::1]:${options.port}`,
110
+ ]);
111
+ const upstream = new URL(options.upstream).origin;
112
+ return async (request) => {
113
+ const url = new URL(request.url);
114
+ if (!hosts.has(url.host)) {
115
+ return plain(421, "This address is not the console's.");
116
+ }
117
+ if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
118
+ if (!fromOwnPage(request, `http://${url.host}`)) {
119
+ return json(403, "auth.forbidden_origin", "This request must come from the console's own pages.");
120
+ }
121
+ return forward(request, url, upstream, options);
122
+ }
123
+ if (options.web !== undefined) {
124
+ const answer = await options.web.respond(request);
125
+ if (answer !== undefined) {
126
+ return answer;
127
+ }
128
+ }
129
+ return plain(404, options.web === undefined
130
+ ? "Nothing is served here but the API, under /api/."
131
+ : "Nothing here.");
132
+ };
133
+ }
134
+ async function answer(handler, req, res) {
135
+ // A browser that goes away takes its request with it, a stream of events included.
136
+ const controller = new AbortController();
137
+ res.on("close", () => {
138
+ if (!res.writableFinished) {
139
+ controller.abort();
140
+ }
141
+ });
142
+ const method = req.method ?? "GET";
143
+ const withBody = method !== "GET" && method !== "HEAD";
144
+ let request;
145
+ try {
146
+ const headers = new Headers();
147
+ for (const [name, value] of Object.entries(req.headers)) {
148
+ if (Array.isArray(value)) {
149
+ for (const each of value) {
150
+ headers.append(name, each);
151
+ }
152
+ }
153
+ else if (value !== undefined) {
154
+ headers.set(name, value);
155
+ }
156
+ }
157
+ request = new Request(`http://${req.headers.host ?? LOOPBACK}${req.url ?? "/"}`, {
158
+ method,
159
+ headers,
160
+ body: withBody ? Readable.toWeb(req) : undefined,
161
+ signal: controller.signal,
162
+ ...(withBody ? { duplex: "half" } : {}),
163
+ });
164
+ }
165
+ catch {
166
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
167
+ res.end("The request was not understood.\n");
168
+ return;
169
+ }
170
+ let response;
171
+ try {
172
+ response = await handler(request);
173
+ }
174
+ catch {
175
+ response = plain(500, "Something went wrong.");
176
+ }
177
+ res.statusCode = response.status;
178
+ response.headers.forEach((value, name) => {
179
+ res.setHeader(name, value);
180
+ });
181
+ if (response.body === null || method === "HEAD") {
182
+ res.end();
183
+ return;
184
+ }
185
+ try {
186
+ await pipeline(Readable.fromWeb(response.body), res);
187
+ }
188
+ catch {
189
+ // The browser went away before the answer ended: nothing is owed to it.
190
+ res.destroy();
191
+ }
192
+ }
193
+ /** Listens on the loopback, and only there, and answers every request through the handler. */
194
+ export const listenOn = (handlerFor, port) => new Promise((resolve, reject) => {
195
+ let handler;
196
+ const server = createServer((req, res) => {
197
+ void answer(handler ?? (() => Promise.resolve(plain(503, "Not ready."))), req, res);
198
+ });
199
+ server.once("error", reject);
200
+ server.listen(port, LOOPBACK, () => {
201
+ server.off("error", reject);
202
+ const address = server.address();
203
+ const bound = typeof address === "object" && address !== null ? address.port : port;
204
+ handler = handlerFor(bound);
205
+ const closed = new Promise((done) => {
206
+ server.once("close", () => done());
207
+ });
208
+ resolve({
209
+ url: `http://${LOOPBACK}:${bound}/`,
210
+ closed,
211
+ close: () => new Promise((done, fail) => {
212
+ server.closeAllConnections();
213
+ server.close((error) => (error === undefined ? done() : fail(error)));
214
+ }),
215
+ });
216
+ });
217
+ });
218
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"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"}
package/dist/console.css CHANGED
@@ -146,8 +146,8 @@
146
146
  --sc-width-aside: 18rem;
147
147
  --sc-column-width: 13.5rem;
148
148
  --sc-header-height: 3.75rem;
149
- --sc-brand-size: 1.25rem;
150
- --sc-brand-size-compact: 1.125rem;
149
+ --sc-brand-size: 1rem;
150
+ --sc-brand-size-compact: 0.875rem;
151
151
  --sc-control-size: 2.5rem;
152
152
  --sc-focus-ring: 2px solid var(--sc-color-accent);
153
153
  --sc-transition: 120ms ease-out;
@@ -1631,7 +1631,7 @@ table {
1631
1631
  position: relative;
1632
1632
  display: grid;
1633
1633
  place-items: center;
1634
- height: 7rem;
1634
+ height: 6.5rem;
1635
1635
  }
1636
1636
 
1637
1637
  /* The light behind the mark, and the rings that leave it. */
@@ -1653,8 +1653,8 @@ table {
1653
1653
  position: relative;
1654
1654
  display: grid;
1655
1655
  place-items: center;
1656
- width: 4rem;
1657
- height: 4rem;
1656
+ width: 3.5rem;
1657
+ height: 3.5rem;
1658
1658
  border: 1px solid var(--sc-color-border-strong);
1659
1659
  border-radius: 28%;
1660
1660
  background: var(--sc-color-bg);
@@ -1689,14 +1689,14 @@ table {
1689
1689
  }
1690
1690
 
1691
1691
  .sc-sign-in-symbol {
1692
- width: 2.25rem;
1693
- height: 2.25rem;
1692
+ width: 2rem;
1693
+ height: 2rem;
1694
1694
  }
1695
1695
 
1696
1696
  .sc-sign-in-wordmark {
1697
1697
  display: block;
1698
1698
  max-width: 12rem;
1699
- height: 1.5rem;
1699
+ height: 1.25rem;
1700
1700
  margin-inline: auto;
1701
1701
  }
1702
1702
 
@@ -2332,6 +2332,25 @@ table {
2332
2332
  gap: var(--sc-space-2);
2333
2333
  }
2334
2334
 
2335
+ /* Under the wide layout a task's facts come first: changing its state and reaching its forms is
2336
+ what a phone is opened for, and two to a row they stay short. A document keeps its text first. */
2337
+ @media (max-width: 63.99rem) {
2338
+ .sc-task .sc-task-aside {
2339
+ order: -1;
2340
+ }
2341
+
2342
+ .sc-task .sc-task-aside .sc-facts {
2343
+ grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
2344
+ }
2345
+ }
2346
+
2347
+ /* Where there is no pointer to hover with, the control on a card is as tall as a finger needs. */
2348
+ @media (hover: none) {
2349
+ .sc-card-move select {
2350
+ min-height: 2rem;
2351
+ }
2352
+ }
2353
+
2335
2354
  /* Small screens. */
2336
2355
 
2337
2356
  @media (max-width: 40rem) {
@@ -2358,12 +2377,15 @@ table {
2358
2377
  display: none;
2359
2378
  }
2360
2379
 
2380
+ /* Tabs that do not fit wrap, so that none is hidden off the edge. */
2361
2381
  .sc-tabs {
2362
2382
  padding-inline: var(--sc-space-4);
2383
+ overflow: visible;
2363
2384
  }
2364
2385
 
2365
2386
  .sc-tabs-inner {
2366
- gap: var(--sc-space-4);
2387
+ flex-wrap: wrap;
2388
+ gap: 0 var(--sc-space-4);
2367
2389
  }
2368
2390
 
2369
2391
  .sc-menu-summary .sc-person-login {
@@ -2374,7 +2396,8 @@ table {
2374
2396
  padding: var(--sc-space-5) var(--sc-space-4) var(--sc-space-7);
2375
2397
  }
2376
2398
 
2377
- .sc-page-title {
2399
+ .sc-page-title,
2400
+ .sc-task-title {
2378
2401
  font-size: var(--sc-text-xl);
2379
2402
  }
2380
2403
 
package/dist/console.d.ts CHANGED
@@ -11,6 +11,7 @@ import { type SkillListProps } from "./components/skills.js";
11
11
  import { type TaskViewProps } from "./components/task-view.js";
12
12
  import { type TokenListProps } from "./components/tokens.js";
13
13
  import { type LanguagePack } from "./i18n/index.js";
14
+ import { type Route } from "./router.js";
14
15
  /** The components a custom console may replace, each with the props the default one takes. */
15
16
  export interface ConsoleComponents {
16
17
  readonly Shell: ComponentType<ShellProps>;
@@ -48,5 +49,10 @@ export interface ConsoleApp {
48
49
  /** Renders the console into `container`. Returns the way to take it down again. */
49
50
  mount(container: Element): () => void;
50
51
  }
52
+ /**
53
+ * The pages a console that holds a token does not offer: the tokens themselves and
54
+ * configuring, which are a person's own doing on the console's own pages, signed in.
55
+ */
56
+ export declare function onlyOnOwnPages(route: Route): boolean;
51
57
  export declare function createConsole(config?: ConsoleConfig): ConsoleApp;
52
58
  //# sourceMappingURL=console.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../src/console.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../src/console.tsx"],"names":[],"mappings":"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"}
package/dist/console.js CHANGED
@@ -139,6 +139,50 @@ const projectOf = (route) => "project" in route ? route.project : undefined;
139
139
  function Ground(props) {
140
140
  return (_jsx("div", { className: "sc-shell", children: _jsx("main", { className: "sc-main", children: props.children }) }));
141
141
  }
142
+ /**
143
+ * Whether the console holds a token rather than a session: a person's own console, served from
144
+ * their machine with the token the command keeps (ADR-0014). The API says so with `me.agent`.
145
+ */
146
+ function holdsToken(data) {
147
+ return data.me !== undefined && data.me.agent !== null;
148
+ }
149
+ /**
150
+ * The pages a console that holds a token does not offer: the tokens themselves and
151
+ * configuring, which are a person's own doing on the console's own pages, signed in.
152
+ */
153
+ export function onlyOnOwnPages(route) {
154
+ return (route.name === "new-project" ||
155
+ route.name === "settings" ||
156
+ route.name === "agents" ||
157
+ route.name === "new-token" ||
158
+ route.name === "person-agents" ||
159
+ route.name === "connect");
160
+ }
161
+ function NotWithToken() {
162
+ const words = useWords().common.notWithToken;
163
+ return _jsx(EmptyState, { title: words.title, body: words.body });
164
+ }
165
+ /**
166
+ * Where a console that holds a token keeps the person's choice of language: the browser's own
167
+ * storage, which may be absent or refuse, and then the browser's languages decide.
168
+ */
169
+ const LANGUAGE_KEY = "sc-language";
170
+ function storedLanguage() {
171
+ try {
172
+ return globalThis.localStorage?.getItem(LANGUAGE_KEY) ?? undefined;
173
+ }
174
+ catch {
175
+ return undefined;
176
+ }
177
+ }
178
+ function storeLanguage(tag) {
179
+ try {
180
+ globalThis.localStorage?.setItem(LANGUAGE_KEY, tag);
181
+ }
182
+ catch {
183
+ // A browser that keeps nothing: the choice holds for this page.
184
+ }
185
+ }
142
186
  /** A link that reads as a button: where a form is, for instance. */
143
187
  function LinkButton(props) {
144
188
  return (_jsx("a", { className: `sc-button sc-button-${props.variant ?? "secondary"}`, href: props.href, children: props.children }));
@@ -148,10 +192,11 @@ function ProjectsPage(props) {
148
192
  const { data, components, navigation, making } = props;
149
193
  const { busy, error, act } = useAction();
150
194
  const href = (project) => projectHref(project.key);
151
- 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 }))] }), _jsx("p", { className: "sc-lead", children: words.lead }), 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) => void act(async () => {
195
+ const configures = !holdsToken(data);
196
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.title }), !making && configures && (_jsx(LinkButton, { href: PATHS.newProject, variant: "primary", children: words.newProject }))] }), _jsx("p", { className: "sc-lead", children: words.lead }), 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) => void act(async () => {
152
197
  const made = await data.actions.createProject(input);
153
198
  navigation.replace(projectHref(made.key));
154
- }), 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 }) }) })] }));
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: configures ? (_jsx(LinkButton, { href: PATHS.newProject, children: words.newProject })) : undefined }) })] }));
155
200
  }
156
201
  function TaskPage(props) {
157
202
  const words = useWords();
@@ -256,10 +301,11 @@ function ProjectPage(props) {
256
301
  }
257
302
  if (route.name === "members") {
258
303
  const me = data.me?.person ?? undefined;
304
+ const configures = owner && !holdsToken(data);
259
305
  const members = words.projects.members;
260
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsxs("p", { className: "sc-lead", children: [project.visibility === "workspace" ? members.leadWorkspace : members.leadPrivate, " ", members.leadRoles] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people, me: me, busy: busy, onAdd: owner ? (input) => void act(() => data.actions.addMember(input)) : undefined, onChangeRole: owner
306
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), _jsxs("p", { className: "sc-lead", children: [project.visibility === "workspace" ? members.leadWorkspace : members.leadPrivate, " ", members.leadRoles] }), 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
261
307
  ? (member, role) => void act(() => data.actions.updateMember(member.person.id, { role }))
262
- : undefined, onRemove: owner
308
+ : undefined, onRemove: configures
263
309
  ? (member) => void act(() => data.actions.removeMember(member.person.id))
264
310
  : undefined, empty: _jsx(EmptyState, { title: members.empty.title, body: project.visibility === "workspace" ? members.empty.workspace : members.empty.private }) })] }));
265
311
  }
@@ -317,7 +363,7 @@ function WorkspacePage(props) {
317
363
  const { busy, error, act } = useAction();
318
364
  if (route.name === "people") {
319
365
  const me = data.me?.person ?? undefined;
320
- const administrator = me?.role === "admin";
366
+ const administrator = me?.role === "admin" && !holdsToken(data);
321
367
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.people.title }) }), _jsx("p", { className: "sc-lead", children: words.people.lead }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.PeopleList, { people: data.people, me: me, busy: busy, onChangeRole: administrator
322
368
  ? (person, role) => void act(() => data.actions.updatePerson(person.id, { role }))
323
369
  : undefined, agentsHref: administrator ? (person) => personAgentsHref(person.id) : undefined })] }));
@@ -352,8 +398,12 @@ export function createConsole(config = {}) {
352
398
  const data = useConsoleData(client, projectKey);
353
399
  const failure = signInFailureOf(location.search);
354
400
  const title = data.me?.workspace.name ?? fallbackTitle;
401
+ const viaToken = holdsToken(data);
355
402
  // The language: the person's choice, else the browser's, else the first the console speaks.
356
- const chosen = data.me?.language;
403
+ // A console that holds a token cannot keep the choice on the person, and keeps it in the
404
+ // browser instead.
405
+ const [ownChoice, setOwnChoice] = useState(storedLanguage);
406
+ const chosen = viaToken ? (ownChoice ?? data.me?.language) : data.me?.language;
357
407
  const pack = useMemo(() => chooseLanguage(chosen, typeof navigator === "undefined" ? [] : navigator.languages, languages), [chosen]);
358
408
  const words = pack.messages;
359
409
  // An address that moved is shown at its new one, in its place.
@@ -389,11 +439,15 @@ export function createConsole(config = {}) {
389
439
  label: words.nav.people,
390
440
  current: route.name === "people" || route.name === "person-agents",
391
441
  },
392
- {
393
- href: PATHS.agents,
394
- label: words.nav.agents,
395
- current: route.name === "agents" || route.name === "new-token",
396
- },
442
+ ...(viaToken
443
+ ? []
444
+ : [
445
+ {
446
+ href: PATHS.agents,
447
+ label: words.nav.agents,
448
+ current: route.name === "agents" || route.name === "new-token",
449
+ },
450
+ ]),
397
451
  ];
398
452
  const nav = projectKey === undefined
399
453
  ? workspaceNav
@@ -431,7 +485,7 @@ export function createConsole(config = {}) {
431
485
  label: words.nav.members,
432
486
  current: route.name === "members",
433
487
  },
434
- ...(project?.role === "owner"
488
+ ...(project?.role === "owner" && !viaToken
435
489
  ? [
436
490
  {
437
491
  href: projectHref(projectKey, "settings"),
@@ -445,6 +499,9 @@ export function createConsole(config = {}) {
445
499
  if (!data.loaded || route.name === "moved") {
446
500
  page = (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: words.common.loadingWorkspace }) }));
447
501
  }
502
+ else if (viaToken && onlyOnOwnPages(route)) {
503
+ page = _jsx(NotWithToken, {});
504
+ }
448
505
  else if (route.name === "projects" || route.name === "new-project") {
449
506
  page = (_jsx(ProjectsPage, { data: data, components: components, navigation: navigation, making: route.name === "new-project" }));
450
507
  }
@@ -466,7 +523,15 @@ export function createConsole(config = {}) {
466
523
  tag: candidate.tag,
467
524
  label: candidate.label,
468
525
  current: candidate.tag === pack.tag,
469
- })), onLanguage: (tag) => void data.actions.updateMe({ language: tag }).catch(() => undefined), person: data.me.person, live: projectKey === undefined ? undefined : data.live, wide: route.name === "board", onNavigate: (href) => navigation.go(href), onSignOut: () => void data.actions.signOut(), children: page }));
526
+ })), onLanguage: (tag) => {
527
+ if (viaToken) {
528
+ storeLanguage(tag);
529
+ setOwnChoice(tag);
530
+ }
531
+ else {
532
+ void data.actions.updateMe({ language: tag }).catch(() => undefined);
533
+ }
534
+ }, person: data.me.person, live: projectKey === undefined ? undefined : data.live, wide: route.name === "board", onNavigate: (href) => navigation.go(href), onSignOut: viaToken ? undefined : () => void data.actions.signOut(), children: page }));
470
535
  }
471
536
  return _jsx(LanguageContext.Provider, { value: pack, children: content });
472
537
  }