@fuzdev/fuz_app 0.114.0 → 0.116.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 (68) hide show
  1. package/dist/auth/CLAUDE.md +52 -8
  2. package/dist/auth/admin_actions.d.ts.map +1 -1
  3. package/dist/auth/admin_actions.js +4 -3
  4. package/dist/auth/audit_log_schema.d.ts +3 -1
  5. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  6. package/dist/auth/audit_log_schema.js +13 -3
  7. package/dist/auth/cell_action_specs.js +1 -1
  8. package/dist/auth/cell_actions.d.ts.map +1 -1
  9. package/dist/auth/cell_actions.js +33 -4
  10. package/dist/auth/cell_authorize.d.ts +3 -0
  11. package/dist/auth/cell_authorize.d.ts.map +1 -1
  12. package/dist/auth/cell_authorize.js +7 -4
  13. package/dist/auth/request_context.d.ts +12 -3
  14. package/dist/auth/request_context.d.ts.map +1 -1
  15. package/dist/auth/request_context.js +12 -3
  16. package/dist/auth/role_grant_offer_action_specs.d.ts +30 -5
  17. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  18. package/dist/auth/role_grant_offer_action_specs.js +53 -17
  19. package/dist/auth/role_grant_offer_actions.d.ts +23 -12
  20. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  21. package/dist/auth/role_grant_offer_actions.js +132 -38
  22. package/dist/auth/role_grant_offer_queries.d.ts +36 -0
  23. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  24. package/dist/auth/role_grant_offer_queries.js +76 -0
  25. package/dist/auth/role_schema.d.ts +12 -0
  26. package/dist/auth/role_schema.d.ts.map +1 -1
  27. package/dist/auth/role_schema.js +12 -0
  28. package/dist/auth/self_service_role_actions.d.ts +14 -0
  29. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  30. package/dist/auth/self_service_role_actions.js +25 -1
  31. package/dist/server/serve_fact_route.d.ts +3 -1
  32. package/dist/server/serve_fact_route.d.ts.map +1 -1
  33. package/dist/server/serve_fact_route.js +7 -4
  34. package/dist/testing/CLAUDE.md +12 -4
  35. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  36. package/dist/testing/cross_backend/cell_gated_create.js +23 -1
  37. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  38. package/dist/testing/cross_backend/role_grant_participation.js +34 -0
  39. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +12 -1
  40. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  41. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +13 -1
  42. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  43. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +9 -7
  44. package/dist/testing/entities.d.ts +6 -3
  45. package/dist/testing/entities.d.ts.map +1 -1
  46. package/dist/testing/entities.js +4 -1
  47. package/dist/ui/AppShell.svelte +303 -33
  48. package/dist/ui/AppShell.svelte.d.ts +80 -11
  49. package/dist/ui/AppShell.svelte.d.ts.map +1 -1
  50. package/dist/ui/BootstrapForm.svelte +14 -10
  51. package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -1
  52. package/dist/ui/CLAUDE.md +152 -12
  53. package/dist/ui/LoginForm.svelte +8 -4
  54. package/dist/ui/LoginForm.svelte.d.ts.map +1 -1
  55. package/dist/ui/RoleGrantOfferForm.svelte +29 -14
  56. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +2 -3
  57. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -1
  58. package/dist/ui/SignupForm.svelte +14 -13
  59. package/dist/ui/form_state.svelte.d.ts +15 -7
  60. package/dist/ui/form_state.svelte.d.ts.map +1 -1
  61. package/dist/ui/form_state.svelte.js +10 -2
  62. package/dist/ui/role_grant_offers_state.svelte.d.ts +25 -15
  63. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  64. package/dist/ui/role_grant_offers_state.svelte.js +7 -1
  65. package/dist/ui/sidebar_state.svelte.d.ts +62 -2
  66. package/dist/ui/sidebar_state.svelte.d.ts.map +1 -1
  67. package/dist/ui/sidebar_state.svelte.js +78 -9
  68. package/package.json +1 -21
package/dist/ui/CLAUDE.md CHANGED
@@ -77,9 +77,125 @@ pattern rather than reintroducing prop-drilling.
77
77
 
78
78
  - `AppShell.svelte` — sidebar-and-main shell. Props: `children`,
79
79
  `sidebar` (Snippet), `sidebar_width = 180`, `sidebar_state?`,
80
- `keyboard_shortcut?`, `show_toggle?`, `toggle_button?`.
81
- Provisions `sidebar_state_context` internally (creates a fresh
82
- `SidebarState` if `sidebar_state` prop is not supplied).
80
+ `keyboard_shortcut?`, `show_toggle?`, `toggle_button?` (receives
81
+ `{title, show_sidebar, toggle, sidebar_id}`). Provisions
82
+ `sidebar_state_context` internally (creates one `SidebarState` if the
83
+ `sidebar_state` prop is not supplied). Requires the SvelteKit runtime
84
+ (`afterNavigate` from `$app/navigation`). The toggle renders only while
85
+ `show_toggle && sidebar_state.enabled`, before the sidebar in the DOM;
86
+ the built-in one is a disclosure (`aria-expanded` + `aria-controls`
87
+ pointing at the sidebar's `$props.id()` id) with the static name
88
+ `aria-label="sidebar"`, a dynamic `title`, `aria-keyshortcuts` when
89
+ `keyboard_shortcut` is set, and a `←` glyph CSS mirrors when closed.
90
+ The shortcut acts only while the sidebar is enabled, on the current
91
+ mode's field (so it drives the overlay at narrow widths).
92
+ - **Custom toggles** (`toggle_button`): give the button
93
+ `class="app-shell-toggle"` — it renders as a direct child of the root,
94
+ where a `:global` rule (the snippet's markup carries no scope hash)
95
+ stacks it one above the sidebar; the z-index applies only to a
96
+ positioned button, so position it (e.g. `position: fixed`, like the
97
+ built-in one) — and `aria-controls={sidebar_id}`, which the
98
+ focus fallback looks up (the first match that takes focus wins).
99
+ `show_sidebar` is viewport-dependent (the server renders it as wide):
100
+ attributes are repaired on hydration, but visible content keyed on it
101
+ changes at narrow widths, so key visible content on CSS or the
102
+ viewport-free fields.
103
+ - **Two modes, split by `SidebarState.narrow_query`.** Wide: the sidebar
104
+ sits beside the content (padded by `--sidebar_offset`) and follows
105
+ `show_sidebar_wide`. Narrow: it starts closed and opens as an overlay
106
+ (capped at `85vw`) over a scrim, following `show_sidebar_narrow`; the
107
+ content is `inert` while open. The overlay closes on navigation
108
+ (`afterNavigate` inside `AppShell` — consumers write nothing), Escape,
109
+ a scrim click, and widening out of narrow mode (a crossing `$effect`).
110
+ Not `role="dialog"` — the same element is the persistent sidebar at
111
+ wide, so no focus trap.
112
+ - **Escape** is a window `keydown` listener registered by an `$effect`
113
+ only while the overlay is open, so listeners registered earlier (a
114
+ contextmenu's, mounted with the page) see Escape first and can swallow
115
+ it. It never calls `preventDefault`, so the browser's close requests
116
+ (dialog `cancel`, `popover="auto"`) stay intact, and it bails while a
117
+ modal is open (`document.querySelector(':modal')` matches). An open
118
+ `popover="auto"` isn't `:modal`, so one Escape closes it and the overlay
119
+ together. Closing is `flushSync`ed so the content leaves `inert` before
120
+ focus returns. When focus was inside the sidebar or had fallen to `body`
121
+ (opening from the content makes it `inert`, which drops focus there), it
122
+ goes back to the opener if still connected and focusable, else to the
123
+ first `[aria-controls="<sidebar_id>"]` that takes focus (Safari doesn't
124
+ focus a clicked button, so the opener can be `null`). A narrow-mode
125
+ close from `keyboard_shortcut` goes through the same close-and-return
126
+ path; wide mode just toggles. The shortcut bails on an open modal too,
127
+ so pressing it inside a dialog opened from the overlay doesn't close the
128
+ overlay behind it.
129
+ - **No hydration flash.** The mode rules sit in two cascade layers
130
+ nested under `fuz_app`. `@layer fuz_app.app_shell_base` in the scoped
131
+ `<style>` holds the base and state rules, which apply in both modes;
132
+ the narrow overrides are one `<style media={narrow_query}>` in
133
+ `<svelte:head>` (Svelte attribute-escapes the query; the CSS text is
134
+ constant), inside `@layer fuz_app.app_shell_narrow`, rendered only
135
+ while `narrow_query` isn't `null`. Both sheets open with
136
+ `@layer fuz_app.app_shell_base, fuz_app.app_shell_narrow;`, so the
137
+ narrow layer wins whatever the selectors' specificity (the scoped ones
138
+ carry the hash) and whichever sheet the browser meets first — a
139
+ client-mounted shell appending its head style late changes nothing.
140
+ The root's state classes read only the viewport-free fields, so the
141
+ server render and hydration agree and CSS picks the mode; `narrow` only
142
+ drives `inert`, ARIA, and input routing. The narrow rules are unscoped
143
+ (they match every `.app-shell` by plain class names), so shells mounted
144
+ at the same time must share a `narrow_query`. CSP: needs
145
+ `style-src-elem 'unsafe-inline'` (fuz_ui's CSP default allows it) or
146
+ the style's hash.
147
+ - **Layer names are namespaced** under one top-level `fuz_app` layer, so
148
+ they can't collide with a consumer's and a consumer that layers its own
149
+ CSS has one name to order against: `@layer fuz_app, overrides;` in a
150
+ sheet that loads before the shell puts `overrides` above it.
151
+ - **Never layer a property the reset sets.** fuz_css's reset
152
+ (`*, ::before, ::after, ::backdrop`) sets `box-sizing`, `border`,
153
+ `margin`, and `padding`, and it's unlayered, so it beats every layered
154
+ declaration of those at any specificity. The shell routes such a value
155
+ through a custom property the layers set and one unlayered rule reads.
156
+ The content offset is the one case: the layers set `--sidebar_offset`
157
+ on `.app-shell-content` (`--sidebar_width` under `.wide-open`, `0px`
158
+ otherwise and in the narrow layer), and
159
+ `:global(div:where(.app-shell > .app-shell-content)) { padding-left: var(--sidebar_offset, 0px) }`
160
+ reads it, unscoped at (0,0,1) — above the reset's (0,0,0), below any
161
+ class selector. The SSR test fails any layered `padding*`, `margin*`,
162
+ `border*`, or `box-sizing` declaration. No other layered declaration
163
+ overlaps the reset or fuz_css's element base rules (the sidebar, scrim,
164
+ and content are `div`s and the glyph a `span`, which fuz_css leaves
165
+ unstyled; the toggle's only layered property is `z-index`, which
166
+ `button`'s base rule doesn't set).
167
+ - **Public class hooks**: `.app-shell` (state classes `.wide-open` /
168
+ `.narrow-open`), `.app-shell-content`, `.app-shell-sidebar`,
169
+ `.app-shell-scrim`, `.app-shell-toggle`, `.app-shell-toggle-glyph`.
170
+ **Any unlayered rule overrides the shell**, at any specificity and in
171
+ any sheet order, so no specificity matching is needed. The flip side
172
+ is the reset case above: an unlayered rule that sets a property the
173
+ shell layers wins over it, which is why the shell keeps reset-owned
174
+ properties out of its layers. A consumer's own layered rules order
175
+ against `fuz_app` by first appearance in the document, so keep
176
+ overrides unlayered or order `fuz_app` first. The rules that must
177
+ reach consumer markup are `:global` under the scoped root: the toggle's
178
+ z-index (`.app-shell > .app-shell-toggle`), the glyph mirroring
179
+ (`.app-shell > .app-shell-toggle .app-shell-toggle-glyph`, so a custom
180
+ toggle can reuse `.app-shell-toggle-glyph` inside its
181
+ `.app-shell-toggle`; going through the root's own toggle keeps an outer
182
+ shell's state off a nested shell's glyph), and
183
+ `.app-shell-sidebar :modal { visibility: visible }`, which keeps a
184
+ top-layer dialog opened from the sidebar shown while the sidebar hides.
185
+ - **CSS variables**: `--sidebar_width` (always the configured width, set
186
+ from the prop — not the effective offset), `--sidebar_offset` (the
187
+ effective offset, set on `.app-shell-content`: `--sidebar_width` while
188
+ the wide sidebar is open, else `0px`; the content's descendants can
189
+ read it, e.g. to inset a fixed header), `--sidebar_bg` (default
190
+ `--shade_05`), `--sidebar_z_index` (default `200` in both modes; the
191
+ scrim shares it, the toggle sits one above), `--sidebar_scrim_bg`
192
+ (default `--darken_40`). Override the offset by setting
193
+ `--sidebar_offset` or writing a `padding-left` rule on
194
+ `.app-shell-content`; a consumer `padding` shorthand there overrides it
195
+ too. Either applies in both modes and open or closed, since the state
196
+ classes don't track the viewport — wrap it in the narrow query's negation
197
+ to keep narrow mode at `0px`. To change only the wide open offset, target
198
+ `.app-shell.wide-open > .app-shell-content`, also inside that negation.
83
199
  - `ColumnLayout.svelte` — fixed `aside` column + fluid `children`
84
200
  column; `column_width = '280px'`.
85
201
  - `MenuLink.svelte` — SvelteKit `<a>` with `selected` derived from
@@ -87,13 +203,30 @@ pattern rather than reintroducing prop-drilling.
87
203
  Exact matches additionally get `aria-current="page"`. Takes `path`
88
204
  (resolved via `resolve` from `$app/paths`). The `.highlighted` name is
89
205
  intentionally free for orthogonal emphasis (badges, recent activity).
90
- - `sidebar_state.svelte.ts` — `SidebarState` (with `activate()` cleanup
91
- pattern, optional reactive `enabled` getter), `sidebar_state_context`.
206
+ - `sidebar_state.svelte.ts` — `SidebarState`, `sidebar_state_context`,
207
+ `SIDEBAR_NARROW_QUERY_DEFAULT` (`'(max-width: 800px)'`). Options:
208
+ `enabled?` (reactive getter overriding the internal field) and
209
+ `narrow_query?` (`null` disables narrow mode; must contain parentheses
210
+ since it feeds both CSS `media` and `MediaQuery`, which would auto-wrap a
211
+ paren-less query while CSS wouldn't — throws in DEV). Visibility is two
212
+ viewport-free fields, `show_sidebar_wide` (default `true`, the desktop
213
+ preference narrow interactions never touch) and `show_sidebar_narrow`
214
+ (default `false`). `narrow` is the viewport match (`false` in SSR);
215
+ `show_sidebar` is the effective visibility for the current mode and its
216
+ setter writes that mode's field, as does `toggle_sidebar` (routed at call
217
+ time). `close_narrow()` closes the overlay. `activate()` enables and
218
+ shows wide; its disposer clears `enabled` and both fields — pair with
219
+ `$effect` for scoped activation.
92
220
 
93
221
  ## Auth forms
94
222
 
95
223
  All four consume `auth_state_context.get()`; all three form-driven ones
96
- attach a `FormState` for Enter-advance + blur-touched validation.
224
+ attach a `FormState` for Enter-advance + blur-touched validation. While a
225
+ submit is in flight their fields are `readonly` and the `PendingButton` is
226
+ `aria-disabled` (with `disabled={false}` overriding its disable-while-pending
227
+ default), never `disabled` — disabling the focused element drops focus to
228
+ `<body>`. So the handler guards re-entry itself (`if (auth_state.verifying)
229
+ return;`), since Enter and clicks still reach it.
97
230
 
98
231
  - `LoginForm.svelte` — props `username_label = 'username or email'`,
99
232
  `redirect_on_login`. Clears `auth_state.verify_error` on input.
@@ -170,13 +303,18 @@ destructive actions.
170
303
  to a specific actor on the recipient account; default account-grain),
171
304
  `roles: Array<string>` (pre-filtered upstream by admin-grant-path —
172
305
  `RoleSpec.grant_paths` includes `'admin'`),
173
- `scope_id = null`, `on_created?`, `format_role?`. Surfaces five
174
- reason codes with friendly copy: `ERROR_ROLE_GRANT_OFFER_SELF_TARGET`,
306
+ `scope_kind` + `scope_id` (`RoleGrantOfferScope` — together, or neither
307
+ for a global offer; the prop types refuse one alone), `on_created?`,
308
+ `format_role?`. Surfaces the RPC error reasons with friendly copy:
309
+ `ERROR_ROLE_GRANT_OFFER_SELF_TARGET`,
175
310
  `ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE`, `ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED`,
311
+ `ERROR_ROLE_GRANT_BUILTIN_SCOPED`,
176
312
  `ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH`, `ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH`
177
- — imported from `auth/role_grant_offer_action_specs.js` (see
313
+ — imported from `auth/role_grant_offer_action_specs.ts` (see
178
314
  `auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` +
179
- `role_grant_offer_actions.ts`).
315
+ `role_grant_offer_actions.ts`). Submits like the auth forms (`readonly`
316
+ textarea, `aria-disabled` button, re-entry guard); its select stays
317
+ `disabled`, having no `readonly` — safe, since the button has focus.
180
318
  - `RoleGrantOfferHistory.svelte` — both-directions history (recipient +
181
319
  grantor, including terminal). Props: `current_actor_id: string | null`
182
320
  (classifies row as "sent" vs "received"), `format_actor?`,
@@ -259,8 +397,10 @@ destructive actions.
259
397
  Methods: `form()` (returns a Svelte `Attachment` for the form
260
398
  element), `show(field)` (touched OR attempted), `is_touched(field)`,
261
399
  `touch(field)` (programmatic), `focus(field)` (queries by `name`),
262
- `attempt()`, `reset()`. In DEV throws if an input loses focus
263
- without a `name` attribute — all tracked inputs must be named.
400
+ `attempt()`, `reset()`. Generic over its field names —
401
+ `new FormState<'username' | 'password'>()` makes a misspelled field a
402
+ compile error (defaults to `string`). In DEV throws if an input loses
403
+ focus without a `name` attribute — all tracked inputs must be named.
264
404
  - `sidebar_state.svelte.ts` — see Shell + layout above.
265
405
 
266
406
  ## Per-domain state modules
@@ -34,12 +34,15 @@
34
34
  } = $props();
35
35
 
36
36
  const auth_state = auth_state_context.get();
37
- const form_state = new FormState();
37
+ const form_state = new FormState<'username' | 'password'>();
38
38
 
39
39
  let username = $state.raw('');
40
40
  let password = $state.raw('');
41
41
 
42
42
  const handle_login = async (): Promise<void> => {
43
+ // while verifying the fields are `readonly` and the button `aria-disabled` —
44
+ // `disabled` would drop focus to `<body>` — so Enter and clicks still land here
45
+ if (auth_state.verifying) return;
43
46
  const u = username.trim();
44
47
  const p = password;
45
48
  if (!u) {
@@ -79,7 +82,7 @@
79
82
  bind:value={username}
80
83
  placeholder={username_label}
81
84
  autocomplete="username"
82
- disabled={auth_state.verifying}
85
+ readonly={auth_state.verifying}
83
86
  {@attach autofocus()}
84
87
  />
85
88
  </label>
@@ -91,13 +94,14 @@
91
94
  bind:value={password}
92
95
  placeholder="password"
93
96
  autocomplete="current-password"
94
- disabled={auth_state.verifying}
97
+ readonly={auth_state.verifying}
95
98
  />
96
99
  </label>
97
100
  <div class="row gap_sm">
98
101
  <PendingButton
99
102
  pending={auth_state.verifying}
100
- disabled={auth_state.verifying}
103
+ disabled={false}
104
+ aria-disabled={auth_state.verifying}
101
105
  onclick={handle_login}
102
106
  class={auth_state.verify_error ? 'color_c' : ''}
103
107
  >
@@ -1 +1 @@
1
- {"version":3,"file":"LoginForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/LoginForm.svelte"],"names":[],"mappings":"AAmBC,KAAK,gBAAgB,GAAI;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsEH,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"LoginForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/LoginForm.svelte"],"names":[],"mappings":"AAmBC,KAAK,gBAAgB,GAAI;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAyEH,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -4,22 +4,29 @@
4
4
  *
5
5
  * Caller supplies `to_account_id`, the subset of roles the grantor may
6
6
  * offer (typically filtered by admin-grant-path — `RoleSpec.grant_paths`
7
- * includes `'admin'`), an optional `scope_id`,
8
- * and an optional `on_created` callback for post-submit UX. Errors from
9
- * the RPC surface the three distinct reason codes — self-target,
10
- * role-not-grantable, not-authorized — so consumers can render them
7
+ * includes `'admin'`), an optional scope — `scope_kind` and `scope_id`
8
+ * together, or neither for a global offer — and an optional `on_created`
9
+ * callback for post-submit UX. Errors from the RPC surface their distinct
10
+ * reason codes (self-target, role-not-grantable, not-authorized, a scoped
11
+ * builtin role, actor mismatches) so consumers can render them
11
12
  * appropriately.
13
+ *
14
+ * @module
12
15
  */
13
16
 
14
17
  import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
15
18
 
16
- import { role_grant_offers_state_context } from './role_grant_offers_state.svelte.js';
19
+ import {
20
+ role_grant_offers_state_context,
21
+ type RoleGrantOfferScope
22
+ } from './role_grant_offers_state.svelte.js';
17
23
  import { FormState } from './form_state.svelte.js';
18
24
  import {
19
25
  ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX,
20
26
  type RoleGrantOfferJson
21
27
  } from '../auth/role_grant_offer_schema.js';
22
28
  import {
29
+ ERROR_ROLE_GRANT_BUILTIN_SCOPED,
23
30
  ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH,
24
31
  ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH,
25
32
  ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED,
@@ -27,13 +34,15 @@
27
34
  ERROR_ROLE_GRANT_OFFER_SELF_TARGET
28
35
  } from '../auth/role_grant_offer_action_specs.js';
29
36
 
37
+ // `scope` is the offer's resource scope, `RoleGrantOfferScope`: `scope_kind`
38
+ // and `scope_id` together, or neither (the default) for a global offer
30
39
  const {
31
40
  to_account_id,
32
41
  to_actor_id = null,
33
42
  roles,
34
- scope_id = null,
35
43
  on_created,
36
- format_role = (role: string) => role
44
+ format_role = (role: string) => role,
45
+ ...scope
37
46
  }: {
38
47
  to_account_id: string;
39
48
  /**
@@ -44,14 +53,12 @@
44
53
  to_actor_id?: string | null;
45
54
  /** Roles the caller may offer — caller filters upstream (default: admin-grant-path). */
46
55
  roles: Array<string>;
47
- /** Resource scope for the offer; `null` (default) yields a global offer. */
48
- scope_id?: string | null;
49
56
  on_created?: (offer: RoleGrantOfferJson) => void;
50
57
  format_role?: (role: string) => string;
51
- } = $props();
58
+ } & RoleGrantOfferScope = $props();
52
59
 
53
60
  const role_grant_offers = role_grant_offers_state_context.get();
54
- const form_state = new FormState();
61
+ const form_state = new FormState<'role' | 'message'>();
55
62
 
56
63
  let role: string | undefined = $state.raw();
57
64
  const selected_role = $derived(role ?? roles[0] ?? '');
@@ -68,6 +75,8 @@
68
75
  return 'That role cannot be offered through this form.';
69
76
  case ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED:
70
77
  return 'You are not authorized to offer that role.';
78
+ case ERROR_ROLE_GRANT_BUILTIN_SCOPED:
79
+ return 'That role can only be offered globally, not within a scope.';
71
80
  case ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH:
72
81
  return 'That actor is not on the recipient account.';
73
82
  case ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH:
@@ -78,6 +87,9 @@
78
87
  };
79
88
 
80
89
  const handle_submit = async (): Promise<void> => {
90
+ // while submitting the textarea is `readonly` and the button `aria-disabled` —
91
+ // `disabled` would drop focus to `<body>` — so clicks still land here
92
+ if (submitting) return;
81
93
  form_state.attempt();
82
94
  local_error = null;
83
95
  if (!selected_role) {
@@ -88,7 +100,7 @@
88
100
  to_account_id,
89
101
  to_actor_id,
90
102
  role: selected_role,
91
- scope_id,
103
+ ...scope,
92
104
  message: message.trim() || null
93
105
  });
94
106
  if (offer) {
@@ -115,6 +127,8 @@
115
127
  >
116
128
  <label>
117
129
  <div class="title">role</div>
130
+ <!-- a select has no `readonly`, but disabling it can't drop focus: the only
131
+ submit is the button, which has focus when the select disables -->
118
132
  <select
119
133
  name="role"
120
134
  value={selected_role}
@@ -134,13 +148,14 @@
134
148
  bind:value={message}
135
149
  maxlength={ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX}
136
150
  placeholder="optional note for the recipient"
137
- disabled={submitting}></textarea>
151
+ readonly={submitting}></textarea>
138
152
  </label>
139
153
 
140
154
  <div class="row gap_sm">
141
155
  <PendingButton
142
156
  pending={submitting}
143
- disabled={submitting || !selected_role}
157
+ disabled={!selected_role}
158
+ aria-disabled={submitting}
144
159
  onclick={handle_submit}
145
160
  >
146
161
  send offer
@@ -1,3 +1,4 @@
1
+ import { type RoleGrantOfferScope } from './role_grant_offers_state.svelte.js';
1
2
  import { type RoleGrantOfferJson } from '../auth/role_grant_offer_schema.js';
2
3
  type $$ComponentProps = {
3
4
  to_account_id: string;
@@ -9,11 +10,9 @@ type $$ComponentProps = {
9
10
  to_actor_id?: string | null;
10
11
  /** Roles the caller may offer — caller filters upstream (default: admin-grant-path). */
11
12
  roles: Array<string>;
12
- /** Resource scope for the offer; `null` (default) yields a global offer. */
13
- scope_id?: string | null;
14
13
  on_created?: (offer: RoleGrantOfferJson) => void;
15
14
  format_role?: (role: string) => string;
16
- };
15
+ } & RoleGrantOfferScope;
17
16
  declare const RoleGrantOfferForm: import("svelte").Component<$$ComponentProps, {}, "">;
18
17
  type RoleGrantOfferForm = ReturnType<typeof RoleGrantOfferForm>;
19
18
  export default RoleGrantOfferForm;
@@ -1 +1 @@
1
- {"version":3,"file":"RoleGrantOfferForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/RoleGrantOfferForm.svelte"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,kBAAkB,EACvB,MAAM,oCAAoC,CAAC;AAS5C,KAAK,gBAAgB,GAAI;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wFAAwF;IACxF,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AA0GH,QAAA,MAAM,kBAAkB,sDAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"RoleGrantOfferForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/RoleGrantOfferForm.svelte"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,mBAAmB,EACxB,MAAM,qCAAqC,CAAC;AAE9C,OAAO,EAEL,KAAK,kBAAkB,EACvB,MAAM,oCAAoC,CAAC;AAU5C,KAAK,gBAAgB,GAAI;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wFAAwF;IACxF,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,GAAG,mBAAmB,CAAC;AAkHzB,QAAA,MAAM,kBAAkB,sDAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -16,7 +16,7 @@
16
16
  import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
17
17
  import { autofocus } from '@fuzdev/fuz_ui/autofocus.svelte.ts';
18
18
 
19
- import { Username } from '../primitive_schemas.js';
19
+ import { Username, USERNAME_LENGTH_MAX, USERNAME_LENGTH_MIN } from '../primitive_schemas.js';
20
20
  import { PASSWORD_LENGTH_MIN } from '../auth/password.js';
21
21
  import { auth_state_context } from './auth_state.svelte.js';
22
22
  import { FormState } from './form_state.svelte.js';
@@ -32,7 +32,7 @@
32
32
  } = $props();
33
33
 
34
34
  const auth_state = auth_state_context.get();
35
- const form_state = new FormState();
35
+ const form_state = new FormState<'username' | 'email' | 'password' | 'password_confirm'>();
36
36
 
37
37
  let username = $state.raw('');
38
38
  let email = $state.raw('');
@@ -42,14 +42,13 @@
42
42
  const username_valid = $derived(Username.safeParse(username).success);
43
43
  const passwords_match = $derived(password === password_confirm);
44
44
  const can_submit = $derived(
45
- username.trim() &&
46
- username_valid &&
47
- password.length >= PASSWORD_LENGTH_MIN &&
48
- passwords_match &&
49
- !auth_state.verifying
45
+ username.trim() && username_valid && password.length >= PASSWORD_LENGTH_MIN && passwords_match
50
46
  );
51
47
 
52
48
  const handle_signup = async (): Promise<void> => {
49
+ // while verifying the fields are `readonly` and the button `aria-disabled` —
50
+ // `disabled` would drop focus to `<body>` — so Enter and clicks still land here
51
+ if (auth_state.verifying) return;
53
52
  form_state.attempt();
54
53
  if (!can_submit) {
55
54
  if (!username.trim() || !username_valid) form_state.focus('username');
@@ -84,13 +83,14 @@
84
83
  bind:value={username}
85
84
  placeholder="username"
86
85
  autocomplete="username"
87
- disabled={auth_state.verifying}
86
+ readonly={auth_state.verifying}
88
87
  {@attach autofocus()}
89
88
  />
90
89
  </label>
91
90
  {#if form_state.show('username') && username && !username_valid}
92
91
  <p class="color_c_50 font_size_sm mt_0 mb_xs">
93
- 3-39 chars, starts with a letter, ends with letter/number, middle allows dash/underscore
92
+ {USERNAME_LENGTH_MIN}-{USERNAME_LENGTH_MAX} chars, starts with a letter, ends with
93
+ letter/number, middle allows dash/underscore
94
94
  </p>
95
95
  {/if}
96
96
  <label>
@@ -101,7 +101,7 @@
101
101
  bind:value={email}
102
102
  placeholder="email"
103
103
  autocomplete="email"
104
- disabled={auth_state.verifying}
104
+ readonly={auth_state.verifying}
105
105
  />
106
106
  </label>
107
107
  <fieldset>
@@ -113,7 +113,7 @@
113
113
  bind:value={password}
114
114
  placeholder="password"
115
115
  autocomplete="new-password"
116
- disabled={auth_state.verifying}
116
+ readonly={auth_state.verifying}
117
117
  />
118
118
  </label>
119
119
  {#if form_state.show('password') && password && password.length < PASSWORD_LENGTH_MIN}
@@ -129,7 +129,7 @@
129
129
  bind:value={password_confirm}
130
130
  placeholder="confirm password"
131
131
  autocomplete="new-password"
132
- disabled={auth_state.verifying}
132
+ readonly={auth_state.verifying}
133
133
  />
134
134
  </label>
135
135
  {#if form_state.show('password_confirm') && password && password_confirm && !passwords_match}
@@ -139,7 +139,8 @@
139
139
  <div class="row gap_sm">
140
140
  <PendingButton
141
141
  pending={auth_state.verifying}
142
- disabled={auth_state.verifying}
142
+ disabled={false}
143
+ aria-disabled={auth_state.verifying}
143
144
  onclick={handle_signup}
144
145
  class={auth_state.verify_error ? 'color_c' : ''}
145
146
  >
@@ -12,13 +12,21 @@
12
12
  * button is `type="button"`, as `PendingButton` is. Hidden inputs don't count, and an Enter that commits
13
13
  * IME composition is left alone.
14
14
  *
15
+ * While a submit is in flight, make the fields `readonly` rather than `disabled`
16
+ * (and the button `aria-disabled`, guarding the handler against re-entry) —
17
+ * disabling the focused element drops focus to `<body>`. Enter still advances
18
+ * through and submits from `readonly` inputs.
19
+ *
20
+ * The type parameter names the form's fields, so `focus`, `show`, `touch`,
21
+ * and `is_touched` reject a misspelled field at compile time.
22
+ *
15
23
  * All trackable inputs must have a `name` attribute — an error is thrown in dev
16
24
  * if an input without `name` loses focus.
17
25
  *
18
26
  * @example
19
27
  * ```svelte
20
- * <script>
21
- * const form_state = new FormState();
28
+ * <script lang="ts">
29
+ * const form_state = new FormState<'username'>();
22
30
  * let username = $state.raw('');
23
31
  * const username_valid = $derived(Username.safeParse(username).success);
24
32
  * const can_submit = $derived(username.trim() && username_valid);
@@ -45,7 +53,7 @@
45
53
  * @module
46
54
  */
47
55
  import type { Attachment } from 'svelte/attachments';
48
- export declare class FormState {
56
+ export declare class FormState<TField extends string = string> {
49
57
  #private;
50
58
  /**
51
59
  * Whether a submit attempt has been made.
@@ -65,22 +73,22 @@ export declare class FormState {
65
73
  /**
66
74
  * Whether a field has been blurred at least once.
67
75
  */
68
- is_touched(field: string): boolean;
76
+ is_touched(field: TField): boolean;
69
77
  /**
70
78
  * Whether to show validation errors for a field.
71
79
  * Returns `true` if the field has been blurred or a submit attempt was made.
72
80
  */
73
- show(field: string): boolean;
81
+ show(field: TField): boolean;
74
82
  /**
75
83
  * Programmatically marks a field as touched without requiring a blur event.
76
84
  *
77
85
  * @mutates `this`
78
86
  */
79
- touch(field: string): void;
87
+ touch(field: TField): void;
80
88
  /**
81
89
  * Focuses the named input within the form.
82
90
  */
83
- focus(field: string): void;
91
+ focus(field: TField): void;
84
92
  /**
85
93
  * Marks the form as having been submitted, causing all field errors to show.
86
94
  *
@@ -1 +1 @@
1
- {"version":3,"file":"form_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/form_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUrD,qBAAa,SAAS;;IAKrB;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;;;;OASG;IACH,IAAI,IAAI,UAAU,CAAC,eAAe,CAAC;IAmDnC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIlC;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5B;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1B;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiB1B;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAIb"}
1
+ {"version":3,"file":"form_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/form_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUrD,qBAAa,SAAS,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM;;IAKpD;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;;;;OASG;IACH,IAAI,IAAI,UAAU,CAAC,eAAe,CAAC;IAmDnC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIlC;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5B;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1B;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiB1B;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAIb"}
@@ -12,13 +12,21 @@
12
12
  * button is `type="button"`, as `PendingButton` is. Hidden inputs don't count, and an Enter that commits
13
13
  * IME composition is left alone.
14
14
  *
15
+ * While a submit is in flight, make the fields `readonly` rather than `disabled`
16
+ * (and the button `aria-disabled`, guarding the handler against re-entry) —
17
+ * disabling the focused element drops focus to `<body>`. Enter still advances
18
+ * through and submits from `readonly` inputs.
19
+ *
20
+ * The type parameter names the form's fields, so `focus`, `show`, `touch`,
21
+ * and `is_touched` reject a misspelled field at compile time.
22
+ *
15
23
  * All trackable inputs must have a `name` attribute — an error is thrown in dev
16
24
  * if an input without `name` loses focus.
17
25
  *
18
26
  * @example
19
27
  * ```svelte
20
- * <script>
21
- * const form_state = new FormState();
28
+ * <script lang="ts">
29
+ * const form_state = new FormState<'username'>();
22
30
  * let username = $state.raw('');
23
31
  * const username_valid = $derived(Username.safeParse(username).success);
24
32
  * const can_submit = $derived(username.trim() && username_valid);
@@ -26,6 +26,26 @@
26
26
  */
27
27
  import { AsyncSlot } from './async_slot.svelte.js';
28
28
  import type { RoleGrantOfferJson } from '../auth/role_grant_offer_schema.js';
29
+ /**
30
+ * The scope of an offer: global (both absent / `null`) or scoped (both set).
31
+ * `scope_kind` and `scope_id` are paired on the wire — a lone `scope_id` is an
32
+ * `invalid_params` refusal — so the pair is one union rather than two optional
33
+ * fields.
34
+ */
35
+ export type RoleGrantOfferScope = {
36
+ scope_kind?: null;
37
+ scope_id?: null;
38
+ } | {
39
+ scope_kind: string;
40
+ scope_id: string;
41
+ };
42
+ /** Params for creating an offer — `RoleGrantOffersRpc.create` and `submit_create`. */
43
+ export type RoleGrantOfferCreateParams = {
44
+ to_account_id: string;
45
+ to_actor_id?: string | null;
46
+ role: string;
47
+ message?: string | null;
48
+ } & RoleGrantOfferScope;
29
49
  /**
30
50
  * Svelte context for `RoleGrantOffersState`.
31
51
  * Use `role_grant_offers_state_context.set(state)` in the provider and
@@ -52,13 +72,7 @@ export interface RoleGrantOffersRpc {
52
72
  }) => Promise<{
53
73
  offers: Array<RoleGrantOfferJson>;
54
74
  }>;
55
- create: (params: {
56
- to_account_id: string;
57
- to_actor_id?: string | null;
58
- role: string;
59
- scope_id?: string | null;
60
- message?: string | null;
61
- }) => Promise<{
75
+ create: (params: RoleGrantOfferCreateParams) => Promise<{
62
76
  offer: RoleGrantOfferJson;
63
77
  }>;
64
78
  accept: (offer_id: string) => Promise<{
@@ -135,15 +149,11 @@ export declare class RoleGrantOffersState {
135
149
  *
136
150
  * `to_actor_id` (optional) narrows the offer to a specific actor on
137
151
  * `to_account_id`; omit / null for the account-grain default (any actor
138
- * on the recipient account may accept).
152
+ * on the recipient account may accept). A scoped offer passes `scope_kind`
153
+ * and `scope_id` together; an unpaired scope (reachable from untyped
154
+ * callers) fails with `GRANT_SCOPE_PAIRED_MESSAGE` without an RPC.
139
155
  */
140
- submit_create(params: {
141
- to_account_id: string;
142
- to_actor_id?: string | null;
143
- role: string;
144
- scope_id?: string | null;
145
- message?: string | null;
146
- }): Promise<RoleGrantOfferJson | undefined>;
156
+ submit_create(params: RoleGrantOfferCreateParams): Promise<RoleGrantOfferJson | undefined>;
147
157
  /** Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded. */
148
158
  submit_accept(offer_id: string): Promise<void>;
149
159
  submit_decline(offer_id: string, reason?: string | null): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offers_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/role_grant_offers_state.svelte.ts"],"names":[],"mappings":"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"}