@skillcdn/console 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +3 -3
  2. package/dist/api.d.ts +2 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +2 -2
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts +1 -1
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +185 -28
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/credentials.d.ts +5 -0
  11. package/dist/cli/credentials.d.ts.map +1 -1
  12. package/dist/cli/credentials.js +2 -0
  13. package/dist/cli/credentials.js.map +1 -1
  14. package/dist/cli/directory.d.ts +7 -0
  15. package/dist/cli/directory.d.ts.map +1 -1
  16. package/dist/cli/directory.js +7 -0
  17. package/dist/cli/directory.js.map +1 -1
  18. package/dist/cli/format.d.ts +10 -1
  19. package/dist/cli/format.d.ts.map +1 -1
  20. package/dist/cli/format.js +67 -13
  21. package/dist/cli/format.js.map +1 -1
  22. package/dist/cli/help.d.ts.map +1 -1
  23. package/dist/cli/help.js +30 -13
  24. package/dist/cli/help.js.map +1 -1
  25. package/dist/client.d.ts +26 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +14 -1
  28. package/dist/client.js.map +1 -1
  29. package/dist/components/calendar.d.ts +5 -1
  30. package/dist/components/calendar.d.ts.map +1 -1
  31. package/dist/components/calendar.js +3 -3
  32. package/dist/components/calendar.js.map +1 -1
  33. package/dist/components/event-feed.d.ts.map +1 -1
  34. package/dist/components/event-feed.js +2 -0
  35. package/dist/components/event-feed.js.map +1 -1
  36. package/dist/components/pack-connect.d.ts +23 -0
  37. package/dist/components/pack-connect.d.ts.map +1 -0
  38. package/dist/components/pack-connect.js +86 -0
  39. package/dist/components/pack-connect.js.map +1 -0
  40. package/dist/components/pack-token.d.ts +27 -0
  41. package/dist/components/pack-token.d.ts.map +1 -0
  42. package/dist/components/pack-token.js +11 -0
  43. package/dist/components/pack-token.js.map +1 -0
  44. package/dist/components/projects.d.ts +13 -0
  45. package/dist/components/projects.d.ts.map +1 -1
  46. package/dist/components/projects.js +37 -5
  47. package/dist/components/projects.js.map +1 -1
  48. package/dist/components/work.d.ts +3 -1
  49. package/dist/components/work.d.ts.map +1 -1
  50. package/dist/components/work.js +1 -1
  51. package/dist/components/work.js.map +1 -1
  52. package/dist/components/workspace.d.ts +46 -0
  53. package/dist/components/workspace.d.ts.map +1 -0
  54. package/dist/components/workspace.js +124 -0
  55. package/dist/components/workspace.js.map +1 -0
  56. package/dist/console.css +156 -0
  57. package/dist/console.d.ts.map +1 -1
  58. package/dist/console.js +60 -21
  59. package/dist/console.js.map +1 -1
  60. package/dist/data.d.ts +16 -1
  61. package/dist/data.d.ts.map +1 -1
  62. package/dist/data.js +37 -0
  63. package/dist/data.js.map +1 -1
  64. package/dist/dates.d.ts +12 -2
  65. package/dist/dates.d.ts.map +1 -1
  66. package/dist/dates.js +20 -2
  67. package/dist/dates.js.map +1 -1
  68. package/dist/i18n/en.d.ts +53 -0
  69. package/dist/i18n/en.d.ts.map +1 -1
  70. package/dist/i18n/en.js +65 -8
  71. package/dist/i18n/en.js.map +1 -1
  72. package/dist/i18n/ko.d.ts.map +1 -1
  73. package/dist/i18n/ko.js +60 -7
  74. package/dist/i18n/ko.js.map +1 -1
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/router.d.ts +4 -4
  80. package/dist/router.d.ts.map +1 -1
  81. package/dist/router.js +11 -2
  82. package/dist/router.js.map +1 -1
  83. package/dist/routes.d.ts +4 -0
  84. package/dist/routes.d.ts.map +1 -1
  85. package/dist/routes.js +4 -0
  86. package/dist/routes.js.map +1 -1
  87. package/dist/schemas.d.ts +182 -2
  88. package/dist/schemas.d.ts.map +1 -1
  89. package/dist/schemas.js +110 -13
  90. package/dist/schemas.js.map +1 -1
  91. package/dist/vocabulary.d.ts +19 -1
  92. package/dist/vocabulary.d.ts.map +1 -1
  93. package/dist/vocabulary.js +16 -0
  94. package/dist/vocabulary.js.map +1 -1
  95. package/docs/specs/cli.md +16 -11
  96. package/docs/specs/rest.md +31 -8
  97. package/package.json +2 -2
  98. package/skills/building-a-console/SKILL.md +5 -5
  99. package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
  100. package/skills/deploying-a-console/SKILL.md +8 -7
  101. package/skills/working-the-board/SKILL.md +2 -2
  102. package/skills/working-the-board/references/setting-up.md +1 -1
  103. package/skills/working-the-board/references/terms.md +2 -0
  104. package/skills/working-the-board/references/the-sentence.md +1 -1
  105. package/src/api.ts +16 -0
  106. package/src/cli/cli.ts +253 -33
  107. package/src/cli/credentials.ts +7 -0
  108. package/src/cli/directory.ts +9 -0
  109. package/src/cli/format.ts +72 -13
  110. package/src/cli/help.ts +36 -13
  111. package/src/client.ts +84 -0
  112. package/src/components/calendar.tsx +8 -2
  113. package/src/components/event-feed.tsx +2 -0
  114. package/src/components/pack-connect.tsx +179 -0
  115. package/src/components/pack-token.tsx +74 -0
  116. package/src/components/projects.tsx +108 -9
  117. package/src/components/work.tsx +4 -1
  118. package/src/components/workspace.tsx +298 -0
  119. package/src/console.tsx +173 -29
  120. package/src/data.ts +57 -0
  121. package/src/dates.ts +23 -2
  122. package/src/i18n/en.ts +76 -8
  123. package/src/i18n/ko.ts +67 -7
  124. package/src/index.ts +16 -0
  125. package/src/router.ts +17 -5
  126. package/src/routes.ts +4 -0
  127. package/src/schemas.ts +129 -12
  128. package/src/styles/console.css +156 -0
  129. package/src/vocabulary.ts +25 -0
package/src/i18n/ko.ts CHANGED
@@ -20,7 +20,12 @@ export const ko: Messages = {
20
20
  role: { admin: "관리자", member: "멤버" },
21
21
  status: { active: "활성", deactivated: "비활성화" },
22
22
  projectRole: { owner: "소유자", member: "멤버" },
23
- visibility: { workspace: "워크스페이스의 모두", private: "목록에 있는 사람만" },
23
+ visibility: { workspace: "공개: 워크스페이스의 모두", private: "비공개: 목록에 있는 사람만" },
24
+ peopleVisibility: {
25
+ everyone: "워크스페이스의 모두",
26
+ shared_projects: "같은 프로젝트에 속한 사람만",
27
+ },
28
+ weekStart: { auto: "시스템 기본", monday: "월요일", sunday: "일요일" },
24
29
  runStatus: {
25
30
  running: "열림",
26
31
  waiting: "결정 대기 중",
@@ -163,6 +168,12 @@ export const ko: Messages = {
163
168
  chooseSomeone: "선택하세요",
164
169
  as: "역할",
165
170
  add: "추가",
171
+ open: "공개 프로젝트입니다. 워크스페이스의 모든 구성원이 멤버이고, 소유자는 이 목록에서 지정합니다.",
172
+ addByLoginAria: "로그인으로 멤버 추가",
173
+ login: "로그인",
174
+ addByLogin: "로그인으로 추가",
175
+ lookupHint: "목록에 보이지 않는 사람: 깃 호스트의 로그인이나 주소를 정확히 적습니다.",
176
+ nobodyCalled: (login) => `워크스페이스에 ${login}이라는 사람이 없습니다.`,
166
177
  empty: {
167
178
  title: "목록에 아무도 없습니다",
168
179
  workspace: "워크스페이스의 모두가 속해 있고, 관리자가 소유합니다.",
@@ -170,11 +181,33 @@ export const ko: Messages = {
170
181
  },
171
182
  },
172
183
  packToken: {
173
- aria: "팩 토큰",
184
+ aria: "SkillCDN 토큰",
185
+ intro:
186
+ "비공개 팩은 저장소에 대한 SkillCDN 토큰으로 읽습니다. 공개 팩에는 필요 없습니다. SkillCDN에서 승인한 코드로 연결하거나, 거기서 직접 만든 토큰을 붙여 넣으세요. 콘솔은 토큰을 봉인해 보관하고 누구에게도 보여 주지 않습니다. 조직의 팩을 읽는 프로젝트는 워크스페이스 설정의 토큰을 그대로 쓸 수 있습니다.",
187
+ connectTitle: "SkillCDN에서 승인한 코드로",
188
+ pasteTitle: "직접 만든 토큰으로",
189
+ pasteAria: "토큰 붙여넣기",
174
190
  label: "토큰",
175
- hint: "사람이 SkillCDN 계정 페이지에서 팩의 저장소에 대해 만든 토큰입니다. 콘솔이 봉인해 보관하고, 프로젝트를 볼 수 있는 사람을 위해 팩을 읽는 데 쓰며, 누구에게도 보여 주지 않습니다.",
191
+ hint: "SkillCDN 계정 페이지에서 팩의 저장소에 대해 만든 토큰입니다.",
192
+ setSince: "토큰이 설정되어 있습니다:",
176
193
  placeholder: "scdn_repo_…",
194
+ connect: "SkillCDN에서 연결",
195
+ connectHint:
196
+ "콘솔이 SkillCDN에 코드를 요청합니다. 안내된 페이지를 저장소를 볼 수 있는 계정으로 열어, 코드를 확인하고 승인하세요.",
197
+ connectCode: "코드",
198
+ connectOpen: "SkillCDN 페이지 열기",
199
+ connectWaiting: "SkillCDN에서 승인을 기다리는 중입니다.",
200
+ connectExpires: "코드 만료:",
201
+ connectCancel: "기다리기 중단",
202
+ connectDone: "연결되었습니다. 콘솔이 이제 이 토큰으로 팩을 읽습니다.",
203
+ connectEnded: {
204
+ expired: "승인되기 전에 코드가 만료되었습니다. 다시 연결해 새 코드를 받으세요.",
205
+ declined: "SkillCDN에서 요청이 거절되었거나 코드가 이미 쓰였습니다.",
206
+ refused:
207
+ "SkillCDN이 요청을 거부했습니다. 그 사람이 볼 수 없는 저장소이거나, 공개 팩이라 토큰이 필요 없거나, 토큰을 더 만들 수 없습니다.",
208
+ },
177
209
  set: "토큰 넣기",
210
+ replace: "토큰 바꾸기",
178
211
  remove: "토큰 빼기",
179
212
  },
180
213
  settings: {
@@ -183,10 +216,11 @@ export const ko: Messages = {
183
216
  edit: "프로젝트 편집",
184
217
  organizationsSkills: (address) => `조직의 스킬: ${address}`,
185
218
  noSkills: "없음: 콘솔에 조직의 주소가 없습니다.",
186
- packToken: "팩 토큰",
187
- packTokenNone: "없음: 공개 팩은 필요 없고, 비공개 팩은 읽히지 않습니다.",
219
+ packToken: "SkillCDN 토큰",
220
+ packTokenNone: "없음",
188
221
  packTokenSet: "설정됨",
189
222
  packTokenEdit: "바꾸기",
223
+ packTokenAdd: "등록",
190
224
  },
191
225
  },
192
226
 
@@ -464,6 +498,26 @@ export const ko: Messages = {
464
498
  noSkillsAddress: "주소 없음: 프로젝트마다 자기 주소를 두거나, 두지 않습니다.",
465
499
  skillsFromConfiguration:
466
500
  "콘솔을 운영하는 쪽이 설정으로 정합니다. 스킬을 읽어 오는 배포와 조직의 주소.",
501
+ skillsToken: "SkillCDN 토큰",
502
+ skillsTokenIntro:
503
+ "자기 주소도 자기 토큰도 없는 모든 프로젝트가 이 토큰으로 조직의 팩을 읽습니다. 공개 팩에는 필요 없습니다. SkillCDN에서 승인한 코드로 연결하거나, 거기서 직접 만든 토큰을 붙여 넣으세요. 콘솔은 토큰을 봉인해 보관하고 누구에게도 보여 주지 않습니다.",
504
+ workspace: {
505
+ title: "워크스페이스",
506
+ aria: "워크스페이스 설정",
507
+ peopleVisibility: "멤버 공개 범위",
508
+ peopleVisibilityHint:
509
+ "멤버가 사람들 페이지와 선택 목록에서 보는 범위: 워크스페이스의 모두, 또는 같은 프로젝트에 속한 사람만. 관리자는 언제나 모두를 보고, 워크스페이스에 공개된 프로젝트에는 모두가 속합니다.",
510
+ weekStart: "주의 시작 요일",
511
+ weekStartHint:
512
+ "캘린더와 내 작업에서. 시스템 기본은 각 사람의 기기에 설정된 언어와 지역을 따르고, 월요일이나 일요일은 모두에게 같게 적용됩니다.",
513
+ timeZone: "타임존",
514
+ timeZoneHint:
515
+ "콘솔 명령과 에이전트가 시각을 이 타임존으로 찍습니다. 페이지는 각 기기의 타임존으로 보여 줍니다. 없음이면 명령은 UTC로 찍습니다.",
516
+ timeZoneNone: "없음",
517
+ timeZoneDevice: (zone) => `이 기기: ${zone}`,
518
+ timeZoneUseDevice: (zone) => `이 기기의 타임존 쓰기: ${zone}`,
519
+ save: "설정 저장",
520
+ },
467
521
  },
468
522
 
469
523
  rules: {
@@ -527,9 +581,14 @@ export const ko: Messages = {
527
581
  dueOn: "기한",
528
582
  state: "상태",
529
583
  color: "색",
584
+ peopleVisibility: "멤버 공개 범위",
585
+ weekStart: "주의 시작 요일",
586
+ timeZone: "타임존",
587
+ packToken: "SkillCDN 토큰",
530
588
  },
531
589
  event: {
532
590
  personJoined: () => "보드에 들어왔습니다",
591
+ workspaceUpdated: (fields) => `워크스페이스의 ${fields}을(를) 바꿨습니다`,
533
592
  roleChanged: (login, role) => `${login}을(를) ${role}(으)로 지정했습니다`,
534
593
  personListed: (login) => `${login}을(를) 추가했습니다`,
535
594
  personUnlisted: (login) => `${login}을(를) 목록에서 뺐습니다`,
@@ -647,9 +706,9 @@ export const ko: Messages = {
647
706
  indexing: "SkillCDN이 아직 저장소를 색인하고 있습니다. 잠시 후 다시 보세요.",
648
707
  failed: "SkillCDN이 저장소를 색인하지 못했습니다. 찾은 내용은 주소의 페이지에 있습니다.",
649
708
  not_found:
650
- "SkillCDN이 이 주소를 콘솔에 제공하지 않습니다. 그곳에 아무것도 없거나, 비공개 팩인데 프로젝트에 팩 토큰이 없습니다(설정).",
709
+ "SkillCDN이 이 주소를 콘솔에 제공하지 않습니다. 그곳에 아무것도 없거나, 비공개 팩인데 읽을 SkillCDN 토큰이 프로젝트에도 워크스페이스에도 없습니다. 소유자가 설정 페이지에서 SkillCDN의 승인 코드로 연결합니다.",
651
710
  token_invalid:
652
- "프로젝트가 가진 팩 토큰이 SkillCDN에서 더 이상 통하지 않습니다. 만료되었거나 지워졌거나, 만든 사람이 저장소 권한을 잃었습니다. 소유자가 설정 페이지에서 새로 넣습니다.",
711
+ "이 팩을 읽는 SkillCDN 토큰이 SkillCDN에서 더 이상 통하지 않습니다. 만료되었거나 지워졌거나, 만든 사람이 저장소 권한을 잃었습니다. 소유자가 설정 페이지에서 새로 넣습니다.",
653
712
  unavailable:
654
713
  "SkillCDN에 연결할 수 없습니다. 스킬은 이전 상태 그대로입니다. 나중에 다시 보세요.",
655
714
  },
@@ -727,6 +786,7 @@ export const ko: Messages = {
727
786
  people: {
728
787
  title: "사람들",
729
788
  list: "사람들",
789
+ narrowed: "같은 프로젝트에 속한 사람들과 관리자입니다.",
730
790
  roleOf: (name) => `${name}의 역할`,
731
791
  letIn: "사람 추가",
732
792
  letInAria: "사람 추가",
package/src/index.ts CHANGED
@@ -82,6 +82,12 @@ export {
82
82
  goalMonth,
83
83
  } from "./components/goals.js";
84
84
  export { Markdown, type MarkdownProps } from "./components/markdown.js";
85
+ export {
86
+ PackConnect,
87
+ type PackConnectProps,
88
+ type PackConnectState,
89
+ } from "./components/pack-connect.js";
90
+ export { PackTokenWays, type PackTokenWaysProps } from "./components/pack-token.js";
85
91
  export {
86
92
  PAGE_SIZE,
87
93
  Pager,
@@ -200,6 +206,16 @@ export {
200
206
  Time,
201
207
  } from "./components/ui.js";
202
208
  export { WorkList, type WorkListProps } from "./components/work.js";
209
+ export {
210
+ DEFAULT_PEOPLE_VISIBILITY,
211
+ peopleVisibilityOf,
212
+ timeZoneOf,
213
+ WorkspaceSettingsForm,
214
+ type WorkspaceSettingsFormProps,
215
+ WorkspaceSettingsView,
216
+ type WorkspaceSettingsViewProps,
217
+ weekStartOf,
218
+ } from "./components/workspace.js";
203
219
  export { ConnectPage, type ConnectPageProps } from "./connect-page.js";
204
220
  export {
205
221
  type ConsoleApp,
package/src/router.ts CHANGED
@@ -83,8 +83,11 @@ export type Route =
83
83
  readonly project: string;
84
84
  readonly form: "edit" | "rules" | "pack-token" | undefined;
85
85
  }
86
- /** The workspace's settings: its rules, its categories, its skills; or the form the rules are written on. */
87
- | { readonly name: "workspace-settings"; readonly form: "rules" | undefined }
86
+ /** The workspace's settings: its own, its rules, its categories, its skills; or the form the rules, or the settings (ADR-0035), are written on. */
87
+ | {
88
+ readonly name: "workspace-settings";
89
+ readonly form: "rules" | "workspace" | "pack-token" | undefined;
90
+ }
88
91
  | { readonly name: "people" }
89
92
  /** The form where an administrator lets someone in by login (ADR-0022). */
90
93
  | { readonly name: "new-listing" }
@@ -252,7 +255,16 @@ export function matchRoute(pathname: string, search = ""): Route {
252
255
  return { name: "categories" };
253
256
  }
254
257
  if (path === PATHS.settings) {
255
- return { name: "workspace-settings", form: params.has("rules") ? "rules" : undefined };
258
+ return {
259
+ name: "workspace-settings",
260
+ form: params.has("workspace")
261
+ ? "workspace"
262
+ : params.has("rules")
263
+ ? "rules"
264
+ : params.has("pack-token")
265
+ ? "pack-token"
266
+ : undefined,
267
+ };
256
268
  }
257
269
  if (path === PATHS.connect) {
258
270
  return { name: "connect", code: undefined };
@@ -423,8 +435,8 @@ export function projectSettingsHref(key: string, form?: "edit" | "rules" | "pack
423
435
  return form === undefined ? base : `${base}?${form}`;
424
436
  }
425
437
 
426
- /** Where the workspace's settings are, or the form its rules are written on. */
427
- export function settingsHref(form?: "rules"): string {
438
+ /** Where the workspace's settings are, or the form its rules, or its own settings, are written on. */
439
+ export function settingsHref(form?: "rules" | "workspace" | "pack-token"): string {
428
440
  return form === undefined ? PATHS.settings : `${PATHS.settings}?${form}`;
429
441
  }
430
442
 
package/src/routes.ts CHANGED
@@ -9,6 +9,10 @@ export const REST_ROUTES = {
9
9
  me: "/api/v1/me",
10
10
  /** The token a request is made with, for it to give itself up. */
11
11
  meToken: "/api/v1/me/token",
12
+ /** The workspace's settings (ADR-0035), changed by an administrator; read through `me`. */
13
+ workspace: "/api/v1/workspace",
14
+ /** The SkillCDN token the organization's pack is read with (ADR-0036), an administrator's; `/connect` under it connects one with a code. */
15
+ workspacePackToken: "/api/v1/workspace/pack-token",
12
16
  /** The people of the workspace: everyone who has signed in, and the logins let in that have not. */
13
17
  people: "/api/v1/people",
14
18
  /** The logins an administrator let in (ADR-0022), made and removed by one. */
package/src/schemas.ts CHANGED
@@ -50,6 +50,8 @@ import {
50
50
  GOAL_STATES,
51
51
  LINK_SOURCES,
52
52
  MEMBERSHIP_SOURCES,
53
+ PACK_CONNECT_ENDINGS,
54
+ PEOPLE_VISIBILITIES,
53
55
  PERSON_ROLES,
54
56
  PERSON_STATUSES,
55
57
  PROJECT_ICON_TYPES,
@@ -63,6 +65,7 @@ import {
63
65
  SKILLS_STATUSES,
64
66
  TASK_PRIORITIES,
65
67
  TASK_STATES,
68
+ WEEK_STARTS,
66
69
  } from "./vocabulary.js";
67
70
 
68
71
  /** A line of text a person wrote: trimmed, bounded, and free of characters that hide. */
@@ -166,6 +169,18 @@ export const restProviderSchema = z.object({
166
169
  });
167
170
  export type RestProvider = z.infer<typeof restProviderSchema>;
168
171
 
172
+ /**
173
+ * A time zone's name as the IANA database spells one (`Asia/Seoul`, `UTC`): its shape here; the
174
+ * server knows which names are zones.
175
+ */
176
+ export const timeZoneName = z
177
+ .string()
178
+ .check(
179
+ z.minLength(1),
180
+ z.maxLength(64),
181
+ z.regex(/^[A-Za-z0-9_+-]+(?:\/[A-Za-z0-9_+-]+)*$/, "must be a time zone name"),
182
+ );
183
+
169
184
  /** A language tag as a browser sends one (`en`, `ko-KR`): what the pages speak, by the person's choice. */
170
185
  const languageTag = z
171
186
  .string()
@@ -178,22 +193,67 @@ const languageTag = z
178
193
  export const restMePatchSchema = z.object({ language: z.nullable(languageTag) });
179
194
  export type RestMePatch = z.infer<typeof restMePatchSchema>;
180
195
 
196
+ /**
197
+ * The workspace as `GET /api/v1/me` describes it, and as `PATCH /api/v1/workspace` answers: what
198
+ * the board is called, what the deployment's configuration says of it, and the settings an
199
+ * administrator keeps on the Settings page (ADR-0035).
200
+ */
201
+ export const restWorkspaceSchema = z.object({
202
+ name: z.string(),
203
+ /** The most days a token may be good for here, or `null` when a person chooses. */
204
+ tokenDaysAtMost: z.nullable(z.int().check(z.positive())),
205
+ /**
206
+ * The organization's skills: the origin of the SkillCDN deployment they are read through,
207
+ * and the address a project without one of its own reads, canonical, or `null` for none.
208
+ */
209
+ skills: z.object({
210
+ source: z.string(),
211
+ address: z.nullable(z.string()),
212
+ /**
213
+ * The SkillCDN token the organization's pack is read with, by a project that names no
214
+ * address and holds no token of its own (ADR-0036): when it was set, never the token; `null`
215
+ * for none. Optional, so that a console older than it still answers.
216
+ */
217
+ packToken: z.optional(z.nullable(z.object({ setAt: instant }))),
218
+ }),
219
+ /**
220
+ * Who sees whom on the People page (ADR-0035): everyone, or the people one shares a project
221
+ * with. Read as optional, so that a console older than this setting still answers; `everyone`
222
+ * then, which is what it does.
223
+ */
224
+ peopleVisibility: z.optional(z.enum(PEOPLE_VISIBILITIES)),
225
+ /** The day the calendars begin a week on: the language's, Monday or Sunday. Optional as above; `auto` then. */
226
+ weekStart: z.optional(z.enum(WEEK_STARTS)),
227
+ /**
228
+ * The workspace's time zone, an IANA name, in which the command prints moments; `null` for
229
+ * none, when they stay as the API gives them, in UTC. Optional as above; none then.
230
+ */
231
+ timeZone: z.optional(z.nullable(timeZoneName)),
232
+ });
233
+ export type RestWorkspace = z.infer<typeof restWorkspaceSchema>;
234
+
235
+ /** What `PATCH /api/v1/workspace` is sent, by an administrator: the settings to change, at least one. */
236
+ export const restWorkspacePatchSchema = z
237
+ .object({
238
+ peopleVisibility: z.optional(z.enum(PEOPLE_VISIBILITIES)),
239
+ weekStart: z.optional(z.enum(WEEK_STARTS)),
240
+ /** A zone's name, or `null` for none. */
241
+ timeZone: z.optional(z.nullable(timeZoneName)),
242
+ })
243
+ .check(
244
+ z.refine((patch) => Object.values(patch).some((value) => value !== undefined), {
245
+ message: "names nothing to change",
246
+ }),
247
+ );
248
+ export type RestWorkspacePatch = z.infer<typeof restWorkspacePatchSchema>;
249
+
181
250
  /**
182
251
  * `GET /api/v1/me`: the workspace, who the session cookie says is signed in (or `null`), the
183
252
  * language they chose (or `null`), and the identity providers people sign in through, none
184
253
  * where nobody can.
185
254
  */
186
255
  export const restMeSchema = z.object({
187
- workspace: z.object({
188
- name: z.string(),
189
- /** The most days a token may be good for here, or `null` when a person chooses. */
190
- tokenDaysAtMost: z.nullable(z.int().check(z.positive())),
191
- /**
192
- * The organization's skills: the origin of the SkillCDN deployment they are read through,
193
- * and the address a project without one of its own reads, canonical, or `null` for none.
194
- */
195
- skills: z.object({ source: z.string(), address: z.nullable(z.string()) }),
196
- }),
256
+ workspace: restWorkspaceSchema,
197
257
  person: z.nullable(restPersonSchema),
198
258
  /**
199
259
  * What the token asking is called by its person, when a token asks: how a console of a
@@ -329,15 +389,25 @@ export const restProjectPatchSchema = z.object({
329
389
  });
330
390
  export type RestProjectPatch = z.infer<typeof restProjectPatchSchema>;
331
391
 
332
- /** A person listed in a project, with what they are in it. */
392
+ /**
393
+ * A member of a project, with what they are in it: a person listed in it, or, in a project open
394
+ * to the workspace, anyone of the workspace, who is a member of it without a listing (ADR-0035).
395
+ */
333
396
  export const restMemberSchema = z.object({
334
397
  person: restPersonSchema,
398
+ /** What they are in it: an owner as listed or as an administrator, else a member. */
335
399
  role: z.enum(PROJECT_ROLES),
400
+ /** Since when: the listing's day, or, for a member without one, the later of the project's making and their joining. */
336
401
  addedAt: instant,
402
+ /**
403
+ * Whether a listing holds them in the project. Read as optional, so that a console older than
404
+ * this field still answers; everyone it lists is listed.
405
+ */
406
+ listed: z.optional(z.boolean()),
337
407
  });
338
408
  export type RestMember = z.infer<typeof restMemberSchema>;
339
409
 
340
- /** `GET /api/v1/projects/<key>/members`: those listed, by login. */
410
+ /** `GET /api/v1/projects/<key>/members`: the members, by login; everyone of the workspace in a project open to it. */
341
411
  export const restMembersSchema = z.object({ items: z.array(restMemberSchema) });
342
412
  export type RestMembers = z.infer<typeof restMembersSchema>;
343
413
 
@@ -991,6 +1061,53 @@ export const restPackTokenInputSchema = z.object({
991
1061
  });
992
1062
  export type RestPackTokenInput = z.infer<typeof restPackTokenInputSchema>;
993
1063
 
1064
+ /**
1065
+ * `POST .../pack-token/connect` (ADR-0036): what the person is shown, as the deployment said it,
1066
+ * the code and the page to approve it on, until when, and how often the console asks again. The
1067
+ * secret the console claims the token with stays with the console.
1068
+ */
1069
+ export const restPackConnectStartedSchema = z.object({
1070
+ code: z.string(),
1071
+ url: z.string(),
1072
+ expiresAt: instant,
1073
+ intervalSeconds: z.int().check(z.positive()),
1074
+ });
1075
+ export type RestPackConnectStarted = z.infer<typeof restPackConnectStartedSchema>;
1076
+
1077
+ /**
1078
+ * `GET .../pack-token/connect`: how the connection stands. Nothing asked; waiting for the
1079
+ * person, with what they were shown; connected, the token kept and the project as it stands; or
1080
+ * ended short of a token, and why.
1081
+ */
1082
+ const packConnectNone = z.object({ status: z.literal("none") });
1083
+ const packConnectPending = z.object({
1084
+ status: z.literal("pending"),
1085
+ code: z.string(),
1086
+ url: z.string(),
1087
+ expiresAt: instant,
1088
+ intervalSeconds: z.int().check(z.positive()),
1089
+ });
1090
+ const packConnectEnded = z.object({
1091
+ status: z.literal("ended"),
1092
+ reason: z.enum(PACK_CONNECT_ENDINGS),
1093
+ });
1094
+ export const restPackConnectSchema = z.union([
1095
+ packConnectNone,
1096
+ packConnectPending,
1097
+ z.object({ status: z.literal("connected"), project: restProjectSchema }),
1098
+ packConnectEnded,
1099
+ ]);
1100
+ export type RestPackConnect = z.infer<typeof restPackConnectSchema>;
1101
+
1102
+ /** `GET /api/v1/workspace/pack-token/connect`: the same, for the workspace's token, with the workspace as it stands once connected. */
1103
+ export const restWorkspacePackConnectSchema = z.union([
1104
+ packConnectNone,
1105
+ packConnectPending,
1106
+ z.object({ status: z.literal("connected"), workspace: restWorkspaceSchema }),
1107
+ packConnectEnded,
1108
+ ]);
1109
+ export type RestWorkspacePackConnect = z.infer<typeof restWorkspacePackConnectSchema>;
1110
+
994
1111
  /** `GET .../skills/one?path=&cursor=`: a page of a skill's context, as the pack answers `load_skill` (`RestSkill` of `@skillcdn/core`); `read` is `null` unless `ready`. */
995
1112
  export const restSkillLoadSchema = z.object({
996
1113
  ...packEnvelope,
@@ -1177,6 +1177,7 @@ table {
1177
1177
  display: grid;
1178
1178
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
1179
1179
  gap: var(--sc-space-4);
1180
+ align-items: start;
1180
1181
  }
1181
1182
 
1182
1183
  .sc-input {
@@ -4130,3 +4131,158 @@ table {
4130
4131
  .sc-goal-view-list {
4131
4132
  display: block;
4132
4133
  }
4134
+
4135
+ /* The workspace's settings (ADR-0035): each setting a row, its name beside its value; on the form,
4136
+ its name and its hint beside a control of a bounded width, so that a short choice is not a
4137
+ field as wide as the page. */
4138
+ .sc-settings-list {
4139
+ display: grid;
4140
+ margin: 0;
4141
+ font-size: var(--sc-text-sm);
4142
+ }
4143
+
4144
+ .sc-settings-item {
4145
+ display: grid;
4146
+ grid-template-columns: minmax(8rem, 14rem) minmax(0, 1fr);
4147
+ gap: var(--sc-space-1) var(--sc-space-5);
4148
+ padding: var(--sc-space-3) 0;
4149
+ border-top: var(--sc-border);
4150
+ }
4151
+
4152
+ .sc-settings-item:first-child {
4153
+ padding-top: 0;
4154
+ border-top: 0;
4155
+ }
4156
+
4157
+ .sc-settings-item:last-child {
4158
+ padding-bottom: 0;
4159
+ }
4160
+
4161
+ .sc-settings-item dt {
4162
+ color: var(--sc-color-text-muted);
4163
+ }
4164
+
4165
+ .sc-settings-item dd {
4166
+ margin: 0;
4167
+ }
4168
+
4169
+ .sc-settings-note {
4170
+ margin-left: var(--sc-space-3);
4171
+ color: var(--sc-color-text-muted);
4172
+ }
4173
+
4174
+ .sc-settings-form {
4175
+ gap: 0;
4176
+ }
4177
+
4178
+ .sc-settings-row {
4179
+ display: grid;
4180
+ grid-template-columns: minmax(0, 1fr) minmax(14rem, 20rem);
4181
+ gap: var(--sc-space-3) var(--sc-space-6);
4182
+ align-items: start;
4183
+ padding: var(--sc-space-4) 0;
4184
+ border-top: var(--sc-border);
4185
+ }
4186
+
4187
+ .sc-settings-row:first-child {
4188
+ padding-top: 0;
4189
+ border-top: 0;
4190
+ }
4191
+
4192
+ .sc-settings-label {
4193
+ display: block;
4194
+ font-weight: var(--sc-weight-medium);
4195
+ }
4196
+
4197
+ .sc-settings-hint {
4198
+ max-width: var(--sc-width-prose);
4199
+ margin: var(--sc-space-1) 0 0;
4200
+ color: var(--sc-color-text-muted);
4201
+ font-size: var(--sc-text-sm);
4202
+ }
4203
+
4204
+ .sc-settings-control {
4205
+ display: grid;
4206
+ justify-items: start;
4207
+ gap: var(--sc-space-2);
4208
+ }
4209
+
4210
+ .sc-settings-control .sc-input {
4211
+ width: 100%;
4212
+ }
4213
+
4214
+ .sc-settings-form .sc-form-actions {
4215
+ padding-top: var(--sc-space-4);
4216
+ border-top: var(--sc-border);
4217
+ }
4218
+
4219
+ @media (max-width: 40rem) {
4220
+ .sc-settings-item,
4221
+ .sc-settings-row {
4222
+ grid-template-columns: minmax(0, 1fr);
4223
+ }
4224
+ }
4225
+
4226
+ /* The pack token (ADR-0028, ADR-0036): what it is for first, then the two ways to one side by
4227
+ side, each a card with its words before its controls, and the code shown large while the
4228
+ page waits for the approval. */
4229
+ .sc-pack-intro {
4230
+ max-width: var(--sc-width-prose);
4231
+ margin: 0 0 var(--sc-space-5);
4232
+ color: var(--sc-color-text-muted);
4233
+ font-size: var(--sc-text-sm);
4234
+ }
4235
+
4236
+ .sc-pack-ways {
4237
+ display: grid;
4238
+ grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
4239
+ gap: var(--sc-space-4);
4240
+ }
4241
+
4242
+ .sc-pack-way {
4243
+ display: grid;
4244
+ align-content: start;
4245
+ gap: var(--sc-space-3);
4246
+ padding: var(--sc-space-4);
4247
+ border: var(--sc-border);
4248
+ border-radius: var(--sc-radius-lg);
4249
+ background: var(--sc-color-sheet);
4250
+ }
4251
+
4252
+ .sc-pack-way-title {
4253
+ font-size: var(--sc-text-md);
4254
+ font-weight: var(--sc-weight-medium);
4255
+ }
4256
+
4257
+ .sc-pack-way .sc-field-hint {
4258
+ margin-top: 0;
4259
+ }
4260
+
4261
+ .sc-pack-connect {
4262
+ display: grid;
4263
+ gap: var(--sc-space-3);
4264
+ }
4265
+
4266
+ .sc-pack-connect-code {
4267
+ display: flex;
4268
+ flex-wrap: wrap;
4269
+ align-items: baseline;
4270
+ gap: var(--sc-space-3);
4271
+ }
4272
+
4273
+ .sc-pack-code {
4274
+ font-size: var(--sc-text-xl);
4275
+ letter-spacing: 0.12em;
4276
+ }
4277
+
4278
+ .sc-pack-connect-waiting {
4279
+ display: flex;
4280
+ align-items: center;
4281
+ gap: var(--sc-space-2);
4282
+ color: var(--sc-color-text-muted);
4283
+ font-size: var(--sc-text-sm);
4284
+ }
4285
+
4286
+ .sc-pack-actions {
4287
+ margin-top: var(--sc-space-5);
4288
+ }
package/src/vocabulary.ts CHANGED
@@ -97,6 +97,30 @@ export const PROJECT_VISIBILITIES = ["workspace", "private"] as const;
97
97
 
98
98
  export type ProjectVisibility = (typeof PROJECT_VISIBILITIES)[number];
99
99
 
100
+ /**
101
+ * Who sees whom on the People page and in the pickers (ADR-0035): everyone of the workspace,
102
+ * or only the people one shares a project with; an administrator always sees everyone.
103
+ */
104
+ export const PEOPLE_VISIBILITIES = ["everyone", "shared_projects"] as const;
105
+
106
+ export type PeopleVisibility = (typeof PEOPLE_VISIBILITIES)[number];
107
+
108
+ /**
109
+ * The day the calendars begin a week on (ADR-0035): as the language of the pages has it, or
110
+ * Monday, or Sunday, for the whole workspace.
111
+ */
112
+ export const WEEK_STARTS = ["auto", "monday", "sunday"] as const;
113
+
114
+ export type WeekStart = (typeof WEEK_STARTS)[number];
115
+
116
+ /**
117
+ * How a private pack's connection ended short of a token (ADR-0036): the code expired before
118
+ * anyone approved, the person declined at the deployment, or the deployment refused the request.
119
+ */
120
+ export const PACK_CONNECT_ENDINGS = ["expired", "declined", "refused"] as const;
121
+
122
+ export type PackConnectEnding = (typeof PACK_CONNECT_ENDINGS)[number];
123
+
100
124
  /** What a run is doing: at work, waiting for a person's decision, or over in one of three ways. */
101
125
  export const RUN_STATUSES = ["running", "waiting", "finished", "failed", "abandoned"] as const;
102
126
 
@@ -153,6 +177,7 @@ export const EVENT_KINDS = [
153
177
  "person.unlisted",
154
178
  "person.deactivated",
155
179
  "person.reactivated",
180
+ "workspace.updated",
156
181
  "category.created",
157
182
  "category.updated",
158
183
  "category.removed",