@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.
- package/dist/auth/CLAUDE.md +52 -8
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +4 -3
- package/dist/auth/audit_log_schema.d.ts +3 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +13 -3
- package/dist/auth/cell_action_specs.js +1 -1
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +33 -4
- package/dist/auth/cell_authorize.d.ts +3 -0
- package/dist/auth/cell_authorize.d.ts.map +1 -1
- package/dist/auth/cell_authorize.js +7 -4
- package/dist/auth/request_context.d.ts +12 -3
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +12 -3
- package/dist/auth/role_grant_offer_action_specs.d.ts +30 -5
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +53 -17
- package/dist/auth/role_grant_offer_actions.d.ts +23 -12
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +132 -38
- package/dist/auth/role_grant_offer_queries.d.ts +36 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +76 -0
- package/dist/auth/role_schema.d.ts +12 -0
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +12 -0
- package/dist/auth/self_service_role_actions.d.ts +14 -0
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +25 -1
- package/dist/server/serve_fact_route.d.ts +3 -1
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +7 -4
- package/dist/testing/CLAUDE.md +12 -4
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +23 -1
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_participation.js +34 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +12 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +13 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +9 -7
- package/dist/testing/entities.d.ts +6 -3
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +4 -1
- package/dist/ui/AppShell.svelte +303 -33
- package/dist/ui/AppShell.svelte.d.ts +80 -11
- package/dist/ui/AppShell.svelte.d.ts.map +1 -1
- package/dist/ui/BootstrapForm.svelte +14 -10
- package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +152 -12
- package/dist/ui/LoginForm.svelte +8 -4
- package/dist/ui/LoginForm.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferForm.svelte +29 -14
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts +2 -3
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -1
- package/dist/ui/SignupForm.svelte +14 -13
- package/dist/ui/form_state.svelte.d.ts +15 -7
- package/dist/ui/form_state.svelte.d.ts.map +1 -1
- package/dist/ui/form_state.svelte.js +10 -2
- package/dist/ui/role_grant_offers_state.svelte.d.ts +25 -15
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +7 -1
- package/dist/ui/sidebar_state.svelte.d.ts +62 -2
- package/dist/ui/sidebar_state.svelte.d.ts.map +1 -1
- package/dist/ui/sidebar_state.svelte.js +78 -9
- 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
|
-
|
|
82
|
-
`
|
|
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
|
|
91
|
-
|
|
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
|
-
`
|
|
174
|
-
|
|
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.
|
|
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()`.
|
|
263
|
-
|
|
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
|
package/dist/ui/LoginForm.svelte
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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={
|
|
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;
|
|
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`
|
|
9
|
-
*
|
|
10
|
-
* role-not-grantable, not-authorized
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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:
|
|
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:
|
|
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:
|
|
87
|
+
touch(field: TField): void;
|
|
80
88
|
/**
|
|
81
89
|
* Focuses the named input within the form.
|
|
82
90
|
*/
|
|
83
|
-
focus(field:
|
|
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
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;
|
|
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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"}
|