@stapel/workspaces-react 0.16.0 → 0.19.0

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 (128) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +39 -8
  3. package/dist/api/generated/schema.d.ts +187 -60
  4. package/dist/api/generated/schema.d.ts.map +1 -1
  5. package/dist/default/ActiveWorkspace.d.ts +43 -0
  6. package/dist/default/ActiveWorkspace.d.ts.map +1 -0
  7. package/dist/default/ActiveWorkspace.js +33 -0
  8. package/dist/default/ActiveWorkspace.js.map +1 -0
  9. package/dist/default/AuditTrailPane.d.ts +12 -0
  10. package/dist/default/AuditTrailPane.d.ts.map +1 -0
  11. package/dist/default/AuditTrailPane.js +154 -0
  12. package/dist/default/AuditTrailPane.js.map +1 -0
  13. package/dist/default/InvitationsPane.d.ts +19 -0
  14. package/dist/default/InvitationsPane.d.ts.map +1 -0
  15. package/dist/default/InvitationsPane.js +217 -0
  16. package/dist/default/InvitationsPane.js.map +1 -0
  17. package/dist/default/InviteAcceptPage.d.ts +7 -0
  18. package/dist/default/InviteAcceptPage.d.ts.map +1 -1
  19. package/dist/default/InviteAcceptPage.js +74 -38
  20. package/dist/default/InviteAcceptPage.js.map +1 -1
  21. package/dist/default/MembersManager.d.ts +12 -4
  22. package/dist/default/MembersManager.d.ts.map +1 -1
  23. package/dist/default/MembersManager.js +320 -84
  24. package/dist/default/MembersManager.js.map +1 -1
  25. package/dist/default/RoleSelectField.d.ts +64 -0
  26. package/dist/default/RoleSelectField.d.ts.map +1 -0
  27. package/dist/default/RoleSelectField.js +78 -0
  28. package/dist/default/RoleSelectField.js.map +1 -0
  29. package/dist/default/WorkspaceSettings.d.ts +7 -1
  30. package/dist/default/WorkspaceSettings.d.ts.map +1 -1
  31. package/dist/default/WorkspaceSettings.js +203 -45
  32. package/dist/default/WorkspaceSettings.js.map +1 -1
  33. package/dist/default/WorkspacesPage.d.ts +13 -0
  34. package/dist/default/WorkspacesPage.d.ts.map +1 -0
  35. package/dist/default/WorkspacesPage.js +108 -0
  36. package/dist/default/WorkspacesPage.js.map +1 -0
  37. package/dist/default/index.d.ts +28 -7
  38. package/dist/default/index.d.ts.map +1 -1
  39. package/dist/default/index.js +24 -7
  40. package/dist/default/index.js.map +1 -1
  41. package/dist/default/parts.d.ts +104 -0
  42. package/dist/default/parts.d.ts.map +1 -0
  43. package/dist/default/parts.js +140 -0
  44. package/dist/default/parts.js.map +1 -0
  45. package/dist/headless/Members.d.ts +59 -14
  46. package/dist/headless/Members.d.ts.map +1 -1
  47. package/dist/headless/Members.js +35 -10
  48. package/dist/headless/Members.js.map +1 -1
  49. package/dist/headless/RoleSelect.d.ts +9 -3
  50. package/dist/headless/RoleSelect.d.ts.map +1 -1
  51. package/dist/headless/RoleSelect.js +3 -2
  52. package/dist/headless/RoleSelect.js.map +1 -1
  53. package/dist/i18n/es.d.ts +11 -12
  54. package/dist/i18n/es.d.ts.map +1 -1
  55. package/dist/i18n/es.js +267 -14
  56. package/dist/i18n/es.js.map +1 -1
  57. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -1
  58. package/dist/i18n/generated/errors.es.gen.js +5 -0
  59. package/dist/i18n/generated/errors.es.gen.js.map +1 -1
  60. package/dist/i18n/generated/errors.gen.d.ts +30 -0
  61. package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
  62. package/dist/i18n/generated/errors.gen.js +15 -0
  63. package/dist/i18n/generated/errors.gen.js.map +1 -1
  64. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
  65. package/dist/i18n/generated/errors.ru.gen.js +5 -0
  66. package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
  67. package/dist/i18n/keys.d.ts +209 -1
  68. package/dist/i18n/keys.d.ts.map +1 -1
  69. package/dist/i18n/keys.js +410 -4
  70. package/dist/i18n/keys.js.map +1 -1
  71. package/dist/i18n/ru.d.ts +7 -2
  72. package/dist/i18n/ru.d.ts.map +1 -1
  73. package/dist/i18n/ru.js +209 -4
  74. package/dist/i18n/ru.js.map +1 -1
  75. package/dist/index.d.ts +2 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +6 -2
  78. package/dist/index.js.map +1 -1
  79. package/dist/model/format.d.ts +56 -0
  80. package/dist/model/format.d.ts.map +1 -0
  81. package/dist/model/format.js +125 -0
  82. package/dist/model/format.js.map +1 -0
  83. package/dist/model/mandate.d.ts +23 -1
  84. package/dist/model/mandate.d.ts.map +1 -1
  85. package/dist/model/mandate.js +24 -0
  86. package/dist/model/mandate.js.map +1 -1
  87. package/dist/model/selection.d.ts +15 -0
  88. package/dist/model/selection.d.ts.map +1 -1
  89. package/dist/model/selection.js +18 -1
  90. package/dist/model/selection.js.map +1 -1
  91. package/dist/nav/manifest.d.ts +45 -0
  92. package/dist/nav/manifest.d.ts.map +1 -0
  93. package/dist/nav/manifest.js +77 -0
  94. package/dist/nav/manifest.js.map +1 -0
  95. package/llms.txt +11 -10
  96. package/manifest.json +282 -69
  97. package/package.json +18 -12
  98. package/src/analytics/generated/events.json +1 -1
  99. package/src/api/generated/schema.ts +187 -60
  100. package/src/default/ActiveWorkspace.tsx +97 -0
  101. package/src/default/AuditTrailPane.tsx +309 -0
  102. package/src/default/InvitationsPane.tsx +501 -0
  103. package/src/default/InviteAcceptPage.tsx +187 -90
  104. package/src/default/MembersManager.tsx +832 -222
  105. package/src/default/RoleSelectField.tsx +187 -0
  106. package/src/default/WorkspaceSettings.tsx +540 -125
  107. package/src/default/WorkspacesPage.tsx +387 -0
  108. package/src/default/index.ts +28 -7
  109. package/src/default/parts.tsx +308 -0
  110. package/src/headless/Members.tsx +84 -16
  111. package/src/headless/RoleSelect.tsx +12 -5
  112. package/src/i18n/es.ts +326 -14
  113. package/src/i18n/generated/errors.es.gen.ts +5 -0
  114. package/src/i18n/generated/errors.gen.ts +15 -0
  115. package/src/i18n/generated/errors.json +37 -0
  116. package/src/i18n/generated/errors.ru.gen.ts +5 -0
  117. package/src/i18n/keys.ts +462 -5
  118. package/src/i18n/ru.ts +259 -5
  119. package/src/index.ts +6 -1
  120. package/src/model/format.ts +158 -0
  121. package/src/model/mandate.ts +26 -1
  122. package/src/model/selection.tsx +19 -1
  123. package/src/nav/manifest.ts +120 -0
  124. package/dist/default/ErrorAlert.d.ts +0 -8
  125. package/dist/default/ErrorAlert.d.ts.map +0 -1
  126. package/dist/default/ErrorAlert.js +0 -30
  127. package/dist/default/ErrorAlert.js.map +0 -1
  128. package/src/default/ErrorAlert.tsx +0 -47
@@ -1,20 +1,41 @@
1
1
  /**
2
- * `@stapel/workspaces-react/default` — the pilot default skin for this
3
- * pair's settings surfaces (mirrors auth-react's `/default` split, §54): a
4
- * separate entry point so consumers who bring their own visuals never pull
5
- * `antd` into their bundle; importing this subpath is the opt-in.
2
+ * `@stapel/workspaces-react/default` — this pair's DEFAULT SKIN: the antd
3
+ * screens a host mounts to have a working workspace product, not a set of
4
+ * examples (§83, §54). A separate entry point so consumers who bring their
5
+ * own visuals never pull `antd` into their bundle; importing this subpath is
6
+ * the opt-in.
7
+ *
8
+ * Every screen self-themes through `<SkinTheme>` from
9
+ * `@stapel/tokens-antd/skin`, so it is correct inside a dark document with no
10
+ * host wiring, and every one of them needs this pair's `<WorkspacesProvider>`
11
+ * plus core's `<I18nProvider>` above it.
6
12
  *
7
13
  * ```tsx
8
- * import { WorkspaceSettings, MembersManager } from "@stapel/workspaces-react/default";
9
- * // under this pair's <WorkspacesProvider> + core <I18nProvider>:
10
- * <WorkspaceSettings workspaceId={id} />
14
+ * import {
15
+ * WorkspacesPage, WorkspaceSettings, MembersManager,
16
+ * InvitationsPane, AuditTrailPane, InviteAcceptPage,
17
+ * } from "@stapel/workspaces-react/default";
18
+ *
19
+ * <WorkspacesPage onOpen={(ws) => navigate(`/w/${ws.slug}`)} />
20
+ * <WorkspaceSettings workspaceId={id} onDeleted={() => navigate("/")} />
11
21
  * <MembersManager workspaceId={id} />
22
+ * <InvitationsPane workspaceId={id} />
23
+ * <AuditTrailPane workspaceId={id} />
24
+ * <InviteAcceptPage token={token} sessionEmail={email} />
12
25
  * ```
13
26
  */
27
+ export { WorkspacesPage } from "./WorkspacesPage.js";
28
+ export type { WorkspacesPageProps } from "./WorkspacesPage.js";
14
29
  export { WorkspaceSettings } from "./WorkspaceSettings.js";
15
30
  export type { WorkspaceSettingsProps } from "./WorkspaceSettings.js";
16
31
  export { MembersManager } from "./MembersManager.js";
17
32
  export type { MembersManagerProps } from "./MembersManager.js";
33
+ export { InvitationsPane } from "./InvitationsPane.js";
34
+ export type { InvitationsPaneProps } from "./InvitationsPane.js";
35
+ export { AuditTrailPane } from "./AuditTrailPane.js";
36
+ export type { AuditTrailPaneProps } from "./AuditTrailPane.js";
37
+ export { RoleSelectField } from "./RoleSelectField.js";
38
+ export type { RoleSelectFieldProps } from "./RoleSelectField.js";
18
39
  export { InviteAcceptPage } from "./InviteAcceptPage.js";
19
40
  export type { InviteAcceptPageProps } from "./InviteAcceptPage.js";
20
41
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,17 +1,34 @@
1
1
  /**
2
- * `@stapel/workspaces-react/default` — the pilot default skin for this
3
- * pair's settings surfaces (mirrors auth-react's `/default` split, §54): a
4
- * separate entry point so consumers who bring their own visuals never pull
5
- * `antd` into their bundle; importing this subpath is the opt-in.
2
+ * `@stapel/workspaces-react/default` — this pair's DEFAULT SKIN: the antd
3
+ * screens a host mounts to have a working workspace product, not a set of
4
+ * examples (§83, §54). A separate entry point so consumers who bring their
5
+ * own visuals never pull `antd` into their bundle; importing this subpath is
6
+ * the opt-in.
7
+ *
8
+ * Every screen self-themes through `<SkinTheme>` from
9
+ * `@stapel/tokens-antd/skin`, so it is correct inside a dark document with no
10
+ * host wiring, and every one of them needs this pair's `<WorkspacesProvider>`
11
+ * plus core's `<I18nProvider>` above it.
6
12
  *
7
13
  * ```tsx
8
- * import { WorkspaceSettings, MembersManager } from "@stapel/workspaces-react/default";
9
- * // under this pair's <WorkspacesProvider> + core <I18nProvider>:
10
- * <WorkspaceSettings workspaceId={id} />
14
+ * import {
15
+ * WorkspacesPage, WorkspaceSettings, MembersManager,
16
+ * InvitationsPane, AuditTrailPane, InviteAcceptPage,
17
+ * } from "@stapel/workspaces-react/default";
18
+ *
19
+ * <WorkspacesPage onOpen={(ws) => navigate(`/w/${ws.slug}`)} />
20
+ * <WorkspaceSettings workspaceId={id} onDeleted={() => navigate("/")} />
11
21
  * <MembersManager workspaceId={id} />
22
+ * <InvitationsPane workspaceId={id} />
23
+ * <AuditTrailPane workspaceId={id} />
24
+ * <InviteAcceptPage token={token} sessionEmail={email} />
12
25
  * ```
13
26
  */
27
+ export { WorkspacesPage } from "./WorkspacesPage.js";
14
28
  export { WorkspaceSettings } from "./WorkspaceSettings.js";
15
29
  export { MembersManager } from "./MembersManager.js";
30
+ export { InvitationsPane } from "./InvitationsPane.js";
31
+ export { AuditTrailPane } from "./AuditTrailPane.js";
32
+ export { RoleSelectField } from "./RoleSelectField.js";
16
33
  export { InviteAcceptPage } from "./InviteAcceptPage.js";
17
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,104 @@
1
+ import type { CSSProperties, ReactElement, ReactNode } from "react";
2
+ import type { ActionAvailability } from "@stapel/core";
3
+ /** The muted sentence that sits under a control or beside a value. */
4
+ export declare function Muted(props: {
5
+ readonly children: ReactNode;
6
+ readonly testId?: string | undefined;
7
+ }): ReactElement;
8
+ /**
9
+ * A person, drawn the same way on every screen of this pair: monogram, the
10
+ * name they go by, the address underneath.
11
+ *
12
+ * The rule the visual pass forced: an id never reaches the glass. A member
13
+ * whose profile has no name shows the invited ADDRESS as the primary line and
14
+ * says so, rather than showing a truncated UUID or an em dash.
15
+ */
16
+ export declare function PersonLine(props: {
17
+ readonly name: string | null | undefined;
18
+ readonly email: string | null | undefined;
19
+ /** Rendered under the address — "Joined 3 days ago", a suspension reason. */
20
+ readonly caption?: ReactNode;
21
+ /** Tags beside the name (suspended, provisioned, two-factor). */
22
+ readonly tags?: ReactNode;
23
+ readonly testId?: string | undefined;
24
+ }): ReactElement;
25
+ /** A neutral status pill. `tone` picks antd's semantic colour, so it survives
26
+ * the dark swap — a hex here would not. */
27
+ export declare function StatusTag(props: {
28
+ readonly tone: "neutral" | "success" | "warning" | "danger" | "info";
29
+ readonly children: ReactNode;
30
+ readonly testId?: string | undefined;
31
+ }): ReactElement;
32
+ /**
33
+ * The anchor pager three screens share.
34
+ *
35
+ * Two buttons and a position, and nothing that pretends to be a page NUMBER
36
+ * the contract cannot supply: the wire addresses a page by the previous
37
+ * page's opaque cursor, so "jump to page 7" is not a question this API can be
38
+ * asked. The index shown is the count of steps THIS screen has taken, which
39
+ * is a true statement about the walk rather than about the roster.
40
+ */
41
+ export declare function AnchorPager(props: {
42
+ readonly hasPrev: boolean;
43
+ readonly hasNext: boolean;
44
+ readonly onPrev: () => void;
45
+ readonly onNext: () => void;
46
+ readonly prevLabel: string;
47
+ readonly nextLabel: string;
48
+ readonly position: string;
49
+ readonly testId?: string | undefined;
50
+ }): ReactElement | null;
51
+ /** One control on a list row, with the verdict that decides whether it opens. */
52
+ export interface RowActionSpec {
53
+ readonly key: string;
54
+ readonly gate: ActionAvailability;
55
+ readonly label: ReactNode;
56
+ readonly onClick: () => void;
57
+ readonly danger?: boolean;
58
+ readonly testId?: string | undefined;
59
+ }
60
+ /**
61
+ * A list row's controls, and the refusal behind them stated ONCE.
62
+ *
63
+ * `GatedButton` prints the reason under every control it switches off, which
64
+ * is right for a lone button and wrong for a row: three refused controls print
65
+ * the same sentence three times, and a list of such rows becomes a wall of
66
+ * repeated apology — six copies of "This invitation was already accepted,
67
+ * declined, revoked or expired" on one 390px screen. The reason is a fact
68
+ * about the ROW, not about each button, so it is rendered once, as a footnote
69
+ * spanning the row, with every disabled control's `aria-describedby` pointing
70
+ * at it.
71
+ *
72
+ * Two nodes come back, meant to be direct children of the row's flex
73
+ * container: the controls, and the full-width footnote. Keeping the footnote
74
+ * OUT of the action column is what stops a refused row from growing a second
75
+ * geometry while its neighbours stay on one line.
76
+ *
77
+ * `reason` overrides the collected sentences where the screen has a better
78
+ * single one — a terminal invitation refuses three controls for one reason
79
+ * that the row's own status tag already names.
80
+ */
81
+ export declare function RowActions(props: {
82
+ readonly actions: readonly RowActionSpec[];
83
+ readonly reason?: string | undefined;
84
+ readonly testId?: string | undefined;
85
+ }): ReactElement;
86
+ /**
87
+ * A failed read, said once, in a shape a 390px screen can hold.
88
+ *
89
+ * The substrate's `ErrorAlert` puts its retry in antd's `action` slot, which
90
+ * is a column beside the message — at phone width the sentence is squeezed
91
+ * into ~110px and breaks a word per line. Until the substrate stacks it
92
+ * (VC-B6, filed), the retry goes UNDER the alert here, where it has the whole
93
+ * width at any viewport.
94
+ */
95
+ export declare function LoadFailure(props: {
96
+ readonly error: unknown;
97
+ readonly onRetry?: (() => void) | undefined;
98
+ readonly testId?: string | undefined;
99
+ }): ReactElement;
100
+ /** The gap between the stacked blocks of a screen. One number, one place. */
101
+ export declare const SCREEN_STACK: CSSProperties;
102
+ /** The gap between the fields inside one block. */
103
+ export declare const FIELD_STACK: CSSProperties;
104
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/default/parts.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The small pieces every screen in this pair's default skin is built from.
4
+ *
5
+ * Not exported from `./index.ts`: these are the skin's own vocabulary, not
6
+ * product surfaces a host mounts. They live in one file because the visual
7
+ * pass found the same three things drawn differently on each screen — a
8
+ * person rendered as a bare email here and a truncated id there, a page walk
9
+ * with no way back, a muted caption at three different sizes.
10
+ *
11
+ * Every dimension is a `@stapel/tokens` step. Colour comes from antd's token
12
+ * layer through `SkinTheme`, never from a literal.
13
+ */
14
+ import { useId } from "react";
15
+ import { Avatar, Button, Flex, Tag, Typography } from "antd";
16
+ import { STAPEL_UI_KEYS, useT } from "@stapel/core";
17
+ import { ErrorAlert } from "@stapel/tokens-antd/skin";
18
+ import { spacing, fontSize } from "@stapel/tokens";
19
+ /** The muted sentence that sits under a control or beside a value. */
20
+ export function Muted(props) {
21
+ return (_jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: props.children }));
22
+ }
23
+ /** One letter for a person with no picture. Never an id: the monogram of a
24
+ * name, or of the mailbox part of an address, and a neutral glyph when there
25
+ * is neither. */
26
+ function monogram(name, email) {
27
+ const source = (name ?? email ?? "").trim();
28
+ const first = source.charAt(0);
29
+ return first === "" ? "·" : first.toUpperCase();
30
+ }
31
+ /**
32
+ * A person, drawn the same way on every screen of this pair: monogram, the
33
+ * name they go by, the address underneath.
34
+ *
35
+ * The rule the visual pass forced: an id never reaches the glass. A member
36
+ * whose profile has no name shows the invited ADDRESS as the primary line and
37
+ * says so, rather than showing a truncated UUID or an em dash.
38
+ */
39
+ export function PersonLine(props) {
40
+ const name = props.name?.trim() ? props.name.trim() : null;
41
+ const email = props.email?.trim() ? props.email.trim() : null;
42
+ const primary = name ?? email;
43
+ return (_jsxs(Flex, { gap: spacing["3"], align: "flex-start", ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: [_jsx(Avatar, { size: "small", "aria-hidden": true, children: monogram(name, email) }), _jsxs(Flex, { vertical: true, gap: spacing["0"], style: { minWidth: 0 }, children: [_jsxs(Flex, { gap: spacing["2"], align: "center", wrap: true, children: [_jsx(Typography.Text, { strong: true, children: primary }), props.tags] }), name !== null && email !== null && _jsx(Muted, { children: email }), props.caption !== undefined && _jsx(Muted, { children: props.caption })] })] }));
44
+ }
45
+ /** A neutral status pill. `tone` picks antd's semantic colour, so it survives
46
+ * the dark swap — a hex here would not. */
47
+ export function StatusTag(props) {
48
+ const color = props.tone === "success"
49
+ ? "success"
50
+ : props.tone === "warning"
51
+ ? "warning"
52
+ : props.tone === "danger"
53
+ ? "error"
54
+ : props.tone === "info"
55
+ ? "processing"
56
+ : "default";
57
+ return (_jsx(Tag, { color: color, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: props.children }));
58
+ }
59
+ /**
60
+ * The anchor pager three screens share.
61
+ *
62
+ * Two buttons and a position, and nothing that pretends to be a page NUMBER
63
+ * the contract cannot supply: the wire addresses a page by the previous
64
+ * page's opaque cursor, so "jump to page 7" is not a question this API can be
65
+ * asked. The index shown is the count of steps THIS screen has taken, which
66
+ * is a true statement about the walk rather than about the roster.
67
+ */
68
+ export function AnchorPager(props) {
69
+ if (!props.hasPrev && !props.hasNext)
70
+ return null;
71
+ return (_jsxs(Flex, { justify: "space-between", align: "center", gap: spacing["3"], style: { marginTop: spacing["4"] }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: [_jsx(Button, { size: "small", onClick: props.onPrev, disabled: !props.hasPrev, "data-disabled-reason": "first page of the walk \u2014 there is nothing before it", "data-analytics": "none", "data-analytics-reason": "local-ui-page-walk", "data-testid": "workspaces-pager-prev", children: props.prevLabel }), _jsx(Muted, { children: props.position }), _jsx(Button, { size: "small", onClick: props.onNext, disabled: !props.hasNext, "data-disabled-reason": "the server says this page is the last one", "data-analytics": "none", "data-analytics-reason": "local-ui-page-walk", "data-testid": "workspaces-pager-next", children: props.nextLabel })] }));
72
+ }
73
+ /**
74
+ * A list row's controls, and the refusal behind them stated ONCE.
75
+ *
76
+ * `GatedButton` prints the reason under every control it switches off, which
77
+ * is right for a lone button and wrong for a row: three refused controls print
78
+ * the same sentence three times, and a list of such rows becomes a wall of
79
+ * repeated apology — six copies of "This invitation was already accepted,
80
+ * declined, revoked or expired" on one 390px screen. The reason is a fact
81
+ * about the ROW, not about each button, so it is rendered once, as a footnote
82
+ * spanning the row, with every disabled control's `aria-describedby` pointing
83
+ * at it.
84
+ *
85
+ * Two nodes come back, meant to be direct children of the row's flex
86
+ * container: the controls, and the full-width footnote. Keeping the footnote
87
+ * OUT of the action column is what stops a refused row from growing a second
88
+ * geometry while its neighbours stay on one line.
89
+ *
90
+ * `reason` overrides the collected sentences where the screen has a better
91
+ * single one — a terminal invitation refuses three controls for one reason
92
+ * that the row's own status tag already names.
93
+ */
94
+ export function RowActions(props) {
95
+ const t = useT();
96
+ const reasonId = useId();
97
+ const collected = [];
98
+ if (props.reason !== undefined) {
99
+ collected.push(props.reason);
100
+ }
101
+ else {
102
+ const seen = new Set();
103
+ for (const action of props.actions) {
104
+ if (action.gate.available)
105
+ continue;
106
+ if (seen.has(action.gate.block.code))
107
+ continue;
108
+ seen.add(action.gate.block.code);
109
+ collected.push(t(action.gate.block.code, action.gate.block.params));
110
+ }
111
+ }
112
+ const anyBlocked = props.actions.some((action) => !action.gate.available);
113
+ return (_jsxs(_Fragment, { children: [_jsx(Flex, { gap: spacing["2"], align: "center", wrap: true, children: props.actions.map((action) => (_jsx(Button, { type: "link", size: "small", danger: action.danger === true, disabled: !action.gate.available, ...(anyBlocked && !action.gate.available
114
+ ? { "aria-describedby": reasonId }
115
+ : {}), onClick: action.onClick, "data-analytics": "none", "data-analytics-reason": "passthrough \u2014 the caller's onClick carries the tracked action", ...(action.testId !== undefined ? { "data-testid": action.testId } : {}), children: action.label }, action.key))) }), collected.length > 0 && (_jsx("div", { id: reasonId, "data-stapel-gated-reason": "", style: { flexBasis: "100%", minWidth: 0 }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: collected.map((sentence) => (_jsx("div", { children: _jsx(Muted, { children: sentence }) }, sentence))) }))] }));
116
+ }
117
+ /**
118
+ * A failed read, said once, in a shape a 390px screen can hold.
119
+ *
120
+ * The substrate's `ErrorAlert` puts its retry in antd's `action` slot, which
121
+ * is a column beside the message — at phone width the sentence is squeezed
122
+ * into ~110px and breaks a word per line. Until the substrate stacks it
123
+ * (VC-B6, filed), the retry goes UNDER the alert here, where it has the whole
124
+ * width at any viewport.
125
+ */
126
+ export function LoadFailure(props) {
127
+ const t = useT();
128
+ return (_jsxs(Flex, { vertical: true, gap: spacing["3"], align: "flex-start", children: [_jsx(ErrorAlert, { thrown: props.error, style: { width: "100%" }, ...(props.testId !== undefined ? { testId: props.testId } : {}) }), props.onRetry !== undefined && (_jsx(Button, { onClick: props.onRetry, "data-analytics": "none", "data-analytics-reason": "retry of a failed read; the read hook reports its own outcome", "data-testid": props.testId !== undefined ? `${props.testId}-retry` : undefined, children: t(STAPEL_UI_KEYS.retry) }))] }));
129
+ }
130
+ /** The gap between the stacked blocks of a screen. One number, one place. */
131
+ export const SCREEN_STACK = {
132
+ display: "grid",
133
+ gap: spacing["4"],
134
+ };
135
+ /** The gap between the fields inside one block. */
136
+ export const FIELD_STACK = {
137
+ display: "grid",
138
+ gap: spacing["3"],
139
+ };
140
+ //# sourceMappingURL=parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.js","sourceRoot":"","sources":["../../src/default/parts.tsx"],"names":[],"mappings":"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"}
@@ -1,22 +1,56 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { LoadState, StapelApiError } from "@stapel/core";
3
- import type { Member, MemberInvite } from "../api/types.js";
4
- import type { MemberRoleChange } from "../model/mutations.js";
3
+ import type { Member, MemberInvite, MembersParams } from "../api/types.js";
4
+ import type { MemberRename, MemberRoleChange } from "../model/mutations.js";
5
+ /**
6
+ * Where in the anchor walk the current page sits — the half of the roster a
7
+ * pager needs and a count of rows cannot supply.
8
+ *
9
+ * ANCHOR, not offset: a page is addressed by the opaque `next_anchor` of the
10
+ * page before it, so a member removed while an admin reads never skews the
11
+ * next page. There is no "page 3" to jump to, which is why the shape carries
12
+ * two cursors and no index.
13
+ */
14
+ export interface MembersPageInfo {
15
+ readonly hasNext: boolean;
16
+ readonly hasPrev: boolean;
17
+ /** Pass as `params.anchor` with `direction: "next"` to walk forward. */
18
+ readonly nextAnchor: string | null;
19
+ /** Pass as `params.anchor` with `direction: "prev"` to walk back. */
20
+ readonly prevAnchor: string | null;
21
+ /** Rows on THIS page. Never a count of the roster — see `rosterComplete`. */
22
+ readonly count: number;
23
+ }
5
24
  /** Render-prop bag for {@link Members}. */
6
25
  export interface MembersBag {
7
26
  /**
8
27
  * The roster read, as a state a skin cannot flatten — render it with core's
9
- * `matchList`. This is one page (default 100); a full pager is out of scope
10
- * for this renderless view (bring your own via `useMembers`' `params` for
11
- * anchor pagination).
28
+ * `matchList`. This is ONE page: which one is decided by the `params` the
29
+ * caller passes (default: the newest page, limit 100), and {@link page}
30
+ * says whether there is another.
12
31
  *
13
32
  * Separate from {@link writeError} on purpose: "the roster could not be
14
33
  * loaded" and "your invite was rejected" are different sentences, and the
15
34
  * old merged `isError` could only produce one of them.
16
35
  */
17
36
  readonly state: LoadState<readonly Member[]>;
18
- /** An invite, a role change, or a removal failed. Never the read — that is
19
- * {@link state}. */
37
+ /**
38
+ * Whether {@link state} holds the WHOLE roster — the read succeeded and the
39
+ * page reports no `has_next`.
40
+ *
41
+ * A skin may only reason about the roster as a set — "is this the last
42
+ * owner?", "how many admins are there?" — while this is `true`. On one page
43
+ * of a longer roster those counts are counts of a page, and a control gated
44
+ * on one would refuse a removal the backend would have allowed, which is a
45
+ * worse defect than the ungated button it replaced. `false` while the read
46
+ * is loading or failed: an unknown roster is never a complete one.
47
+ */
48
+ readonly rosterComplete: boolean;
49
+ /** The walk position, or `null` while the page has not arrived. A pager
50
+ * with no page cannot claim there is a next one. */
51
+ readonly page: MembersPageInfo | null;
52
+ /** An invite, a role change, a rename or a removal failed. Never the read —
53
+ * that is {@link state}. */
20
54
  readonly writeError: StapelApiError | null;
21
55
  /** Invite one or more emails at a role. */
22
56
  invite(body: MemberInvite): void;
@@ -26,6 +60,11 @@ export interface MembersBag {
26
60
  updateRole(change: MemberRoleChange): void;
27
61
  /** A role-change call is in flight. */
28
62
  readonly isUpdatingRole: boolean;
63
+ /** Correct a member's display name — the CANONICAL one (stapel-profiles),
64
+ * not a workspace-local note. `displayName: null` clears it. */
65
+ rename(change: MemberRename): void;
66
+ /** A rename call is in flight. */
67
+ readonly isRenaming: boolean;
29
68
  /** Remove a member by `userId`. */
30
69
  remove(userId: string): void;
31
70
  /** A removal call is in flight. */
@@ -34,20 +73,26 @@ export interface MembersBag {
34
73
  refetch(): void;
35
74
  }
36
75
  /**
37
- * Headless member roster — a renderless view + invite / role-change / removal
38
- * controls over one workspace's members. Wires {@link useMembers},
39
- * {@link useInviteMembers}, {@link useUpdateMemberRole}, and
40
- * {@link useRemoveMember} and hands a {@link MembersBag} to `children`; bring your
41
- * own roster / invite form / role menu. Zero visual opinion (frontend-standard §2).
76
+ * Headless member roster — a renderless view + invite / role-change / rename /
77
+ * removal controls over one workspace's members. Wires {@link useMembers},
78
+ * {@link useInviteMembers}, {@link useUpdateMemberRole},
79
+ * {@link useRenameMember} and {@link useRemoveMember} and hands a
80
+ * {@link MembersBag} to `children`; bring your own roster / invite form / role
81
+ * menu. Zero visual opinion (frontend-standard §2).
82
+ *
83
+ * `params` is the anchor page to read — the caller owns the walk, because the
84
+ * cursor belongs to the screen's state, not to this component's. Omitted, it
85
+ * reads the newest page, exactly as before.
42
86
  *
43
87
  * ```tsx
44
- * <Members workspaceId={id}>
45
- * {({ state, invite, updateRole, remove }) => ( ... )}
88
+ * <Members workspaceId={id} params={{ anchor, direction: "next", search }}>
89
+ * {({ state, page, invite, updateRole, remove }) => ( ... )}
46
90
  * </Members>
47
91
  * ```
48
92
  */
49
93
  export declare function Members(props: {
50
94
  workspaceId: string;
95
+ params?: MembersParams;
51
96
  children: (bag: MembersBag) => ReactNode;
52
97
  }): ReactNode;
53
98
  //# sourceMappingURL=Members.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Members.d.ts","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC7C;wBACoB;IACpB,QAAQ,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3C,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,8BAA8B;IAC9B,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3C,uCAAuC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,mCAAmC;IACnC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC;CAC1C,GAAG,SAAS,CAyBZ"}
1
+ {"version":3,"file":"Members.d.ts","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQ3E,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC7C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;wDACoD;IACpD,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC;gCAC4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3C,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,8BAA8B;IAC9B,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3C,uCAAuC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;oEACgE;IAChE,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,mCAAmC;IACnC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC;CAC1C,GAAG,SAAS,CA8CZ"}
@@ -1,27 +1,48 @@
1
1
  import { loadStateFromQuery, mapLoad } from "@stapel/core";
2
2
  import { useMembers } from "../model/queries.js";
3
- import { useInviteMembers, useRemoveMember, useUpdateMemberRole, } from "../model/mutations.js";
3
+ import { useInviteMembers, useRemoveMember, useRenameMember, useUpdateMemberRole, } from "../model/mutations.js";
4
4
  /**
5
- * Headless member roster — a renderless view + invite / role-change / removal
6
- * controls over one workspace's members. Wires {@link useMembers},
7
- * {@link useInviteMembers}, {@link useUpdateMemberRole}, and
8
- * {@link useRemoveMember} and hands a {@link MembersBag} to `children`; bring your
9
- * own roster / invite form / role menu. Zero visual opinion (frontend-standard §2).
5
+ * Headless member roster — a renderless view + invite / role-change / rename /
6
+ * removal controls over one workspace's members. Wires {@link useMembers},
7
+ * {@link useInviteMembers}, {@link useUpdateMemberRole},
8
+ * {@link useRenameMember} and {@link useRemoveMember} and hands a
9
+ * {@link MembersBag} to `children`; bring your own roster / invite form / role
10
+ * menu. Zero visual opinion (frontend-standard §2).
11
+ *
12
+ * `params` is the anchor page to read — the caller owns the walk, because the
13
+ * cursor belongs to the screen's state, not to this component's. Omitted, it
14
+ * reads the newest page, exactly as before.
10
15
  *
11
16
  * ```tsx
12
- * <Members workspaceId={id}>
13
- * {({ state, invite, updateRole, remove }) => ( ... )}
17
+ * <Members workspaceId={id} params={{ anchor, direction: "next", search }}>
18
+ * {({ state, page, invite, updateRole, remove }) => ( ... )}
14
19
  * </Members>
15
20
  * ```
16
21
  */
17
22
  export function Members(props) {
18
- const query = useMembers(props.workspaceId);
23
+ const query = useMembers(props.workspaceId, props.params);
19
24
  const inviteMutation = useInviteMembers(props.workspaceId);
20
25
  const roleMutation = useUpdateMemberRole(props.workspaceId);
26
+ const renameMutation = useRenameMember(props.workspaceId);
21
27
  const removeMutation = useRemoveMember(props.workspaceId);
28
+ const data = query.data;
22
29
  return props.children({
23
30
  state: mapLoad(loadStateFromQuery(query), (page) => page.items),
24
- writeError: inviteMutation.error ?? roleMutation.error ?? removeMutation.error ?? null,
31
+ rosterComplete: data !== undefined && !data.has_next && !data.has_prev,
32
+ page: data === undefined
33
+ ? null
34
+ : {
35
+ hasNext: data.has_next,
36
+ hasPrev: data.has_prev,
37
+ nextAnchor: data.next_anchor ?? null,
38
+ prevAnchor: data.prev_anchor ?? null,
39
+ count: data.count,
40
+ },
41
+ writeError: inviteMutation.error ??
42
+ roleMutation.error ??
43
+ renameMutation.error ??
44
+ removeMutation.error ??
45
+ null,
25
46
  invite: (body) => {
26
47
  inviteMutation.mutate(body);
27
48
  },
@@ -30,6 +51,10 @@ export function Members(props) {
30
51
  roleMutation.mutate(change);
31
52
  },
32
53
  isUpdatingRole: roleMutation.isPending,
54
+ rename: (change) => {
55
+ renameMutation.mutate(change);
56
+ },
57
+ isRenaming: renameMutation.isPending,
33
58
  remove: (userId) => {
34
59
  removeMutation.mutate(userId);
35
60
  },
@@ -1 +1 @@
1
- {"version":3,"file":"Members.js","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAmC/B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO,CAAC,KAGvB;IACC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;QAC/D,UAAU,EACR,cAAc,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,IAAI,cAAc,CAAC,KAAK,IAAI,IAAI;QAC5E,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;YACrB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,cAAc,EAAE,YAAY,CAAC,SAAS;QACtC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;YACjB,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"Members.js","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AA2E/B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,OAAO,CAAC,KAIvB;IACC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,cAAc,GAAG,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;QAC/D,cAAc,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ;QACtE,IAAI,EACF,IAAI,KAAK,SAAS;YAChB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC;gBACE,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,UAAU,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;gBACpC,UAAU,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;gBACpC,KAAK,EAAE,IAAI,CAAC,KAAK;aAClB;QACP,UAAU,EACR,cAAc,CAAC,KAAK;YACpB,YAAY,CAAC,KAAK;YAClB,cAAc,CAAC,KAAK;YACpB,cAAc,CAAC,KAAK;YACpB,IAAI;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;YACrB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,cAAc,EAAE,YAAY,CAAC,SAAS;QACtC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;YACjB,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;YACjB,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -12,9 +12,15 @@ export interface RoleSelectBag {
12
12
  * Display label for a role key: the i18n key `workspaces.role.<key>` when
13
13
  * the current locale's merged bundle carries it (the pair ships the builtin
14
14
  * four; a client bundle merges its own — e.g. `workspaces.role.secretary`),
15
- * else the RAW role key. The raw-name fallback is deliberate: a
16
- * deployment-defined role without a translation must still be pickable,
17
- * never render as a dotted i18n key.
15
+ * else the role key TITLE-CASED (`secretary` → `Secretary`, `site_admin` →
16
+ * `Site admin`).
17
+ *
18
+ * Two things this fallback is not. It is never the dotted i18n key: a
19
+ * deployment-defined role without a translation must still be pickable. And
20
+ * since the 2026-08-24 visual pass it is no longer the raw token either —
21
+ * `secretary` rendered lowercase in a list of title-cased builtin roles,
22
+ * which reads as broken data rather than as a missing translation. The
23
+ * label is cosmetic; the VALUE stays the registry key everywhere it is sent.
18
24
  */
19
25
  labelFor(role: string): string;
20
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RoleSelect.d.ts","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;gEAG4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IAC/C;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CAC7C,GAAG,SAAS,CAWZ"}
1
+ {"version":3,"file":"RoleSelect.d.ts","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;gEAG4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IAC/C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CAC7C,GAAG,SAAS,CAWZ"}
@@ -1,4 +1,5 @@
1
1
  import { loadStateFromQuery, useI18n } from "@stapel/core";
2
+ import { titleCaseKey } from "../model/format.js";
2
3
  import { useRoles } from "../model/queries.js";
3
4
  /**
4
5
  * Headless role picker over the effective role registry (org-program §A2) —
@@ -28,11 +29,11 @@ export function RoleSelect(props) {
28
29
  const i18n = useI18n();
29
30
  // Raw bundle lookup, NOT `t()`: `t` falls back to the key itself, which is
30
31
  // exactly the wrong fallback here (a deployment role without a translation
31
- // must show its raw name, not `workspaces.role.secretary`).
32
+ // must show a word, not `workspaces.role.secretary`).
32
33
  const bundle = i18n.getBundle();
33
34
  return props.children({
34
35
  state: loadStateFromQuery(query),
35
- labelFor: (role) => bundle[`workspaces.role.${role}`] ?? role,
36
+ labelFor: (role) => bundle[`workspaces.role.${role}`] ?? titleCaseKey(role),
36
37
  });
37
38
  }
38
39
  //# sourceMappingURL=RoleSelect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RoleSelect.js","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAoB/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,UAAU,CAAC,KAE1B;IACC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,2EAA2E;IAC3E,2EAA2E;IAC3E,4DAA4D;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,kBAAkB,CAAC,KAAK,CAAC;QAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,IAAI,IAAI;KAC9D,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"RoleSelect.js","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AA0B/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,UAAU,CAAC,KAE1B;IACC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,2EAA2E;IAC3E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,kBAAkB,CAAC,KAAK,CAAC;QAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC;KAC5E,CAAC,CAAC;AACL,CAAC"}