@fuzdev/fuz_app 0.113.0 → 0.115.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/actions/CLAUDE.md +46 -20
- package/dist/actions/action_dispatcher.d.ts +1 -1
- package/dist/actions/action_dispatcher.d.ts.map +1 -1
- package/dist/actions/action_dispatcher.js +22 -16
- package/dist/actions/action_event.d.ts +18 -2
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +63 -19
- package/dist/actions/action_event_types.d.ts +22 -0
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/action_event_types.js +19 -0
- package/dist/actions/compile_action_registry.d.ts +4 -2
- package/dist/actions/compile_action_registry.d.ts.map +1 -1
- package/dist/actions/compile_action_registry.js +8 -3
- package/dist/actions/frontend_rpc_client.d.ts +6 -0
- package/dist/actions/frontend_rpc_client.d.ts.map +1 -1
- package/dist/actions/frontend_rpc_client.js +2 -1
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +8 -4
- package/dist/actions/peer_ping.d.ts.map +1 -1
- package/dist/actions/peer_ping.js +3 -1
- package/dist/actions/perform_action.d.ts +1 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +6 -4
- package/dist/actions/register_action_ws.d.ts +13 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/rpc_client.d.ts +1 -1
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +2 -1
- package/dist/actions/socket.svelte.d.ts +53 -10
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +84 -23
- package/dist/actions/transports.d.ts +24 -0
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +30 -0
- package/dist/actions/transports_http.d.ts +11 -4
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +63 -18
- package/dist/actions/transports_ws.d.ts +28 -3
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +33 -4
- package/dist/actions/ws_endpoint_spec.d.ts +7 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- 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/http/CLAUDE.md +7 -3
- package/dist/http/jsonrpc_helpers.d.ts +0 -5
- package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
- package/dist/http/jsonrpc_helpers.js +0 -13
- package/dist/http/schema_helpers.d.ts +56 -0
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +91 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -0
- 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 +29 -16
- 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/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +18 -0
- 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/cross_backend/ws_round_trip.d.ts +11 -3
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +74 -12
- 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/testing/transports/ws_client.d.ts +6 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +6 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +5 -0
- package/dist/ui/BootstrapForm.svelte +14 -10
- package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +19 -7
- 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 +24 -11
- package/dist/ui/form_state.svelte.d.ts.map +1 -1
- package/dist/ui/form_state.svelte.js +38 -10
- 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/package.json +2 -22
- package/dist/actions/request_tracker.svelte.d.ts +0 -77
- package/dist/actions/request_tracker.svelte.d.ts.map +0 -1
- package/dist/actions/request_tracker.svelte.js +0 -169
|
@@ -69,7 +69,9 @@ export const create_ws_transport = async (options) => {
|
|
|
69
69
|
socket.send(JSON.stringify(message));
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
|
-
|
|
72
|
+
let close_code = null;
|
|
73
|
+
socket.on('close', (code) => {
|
|
74
|
+
close_code = code;
|
|
73
75
|
for (const resolve of close_resolvers)
|
|
74
76
|
resolve();
|
|
75
77
|
close_resolvers = [];
|
|
@@ -140,6 +142,9 @@ export const create_ws_transport = async (options) => {
|
|
|
140
142
|
get messages() {
|
|
141
143
|
return received;
|
|
142
144
|
},
|
|
145
|
+
get close_code() {
|
|
146
|
+
return close_code;
|
|
147
|
+
},
|
|
143
148
|
send: send_impl,
|
|
144
149
|
async request(id, method, params, timeout_ms) {
|
|
145
150
|
await send_impl(create_jsonrpc_request(method, params, id));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
|
|
@@ -223,6 +223,7 @@ export const create_ws_test_harness = (options) => {
|
|
|
223
223
|
// test closed the socket.
|
|
224
224
|
const close_waiters = [];
|
|
225
225
|
let is_closed = false;
|
|
226
|
+
let close_code = null;
|
|
226
227
|
// Captured in `ws.close` below; `client.close(...)` returns it so
|
|
227
228
|
// tests can await async `on_socket_close` cleanup.
|
|
228
229
|
let close_pending = null;
|
|
@@ -240,6 +241,7 @@ export const create_ws_test_harness = (options) => {
|
|
|
240
241
|
if (is_closed)
|
|
241
242
|
return;
|
|
242
243
|
is_closed = true;
|
|
244
|
+
close_code = code ?? 1000;
|
|
243
245
|
for (const resolve of close_waiters.splice(0))
|
|
244
246
|
resolve();
|
|
245
247
|
const close_event = new Event('close');
|
|
@@ -326,6 +328,9 @@ export const create_ws_test_harness = (options) => {
|
|
|
326
328
|
if (close_pending)
|
|
327
329
|
await close_pending;
|
|
328
330
|
},
|
|
331
|
+
get close_code() {
|
|
332
|
+
return close_code;
|
|
333
|
+
},
|
|
329
334
|
wait_for: wait_for_impl,
|
|
330
335
|
wait_for_close: (timeout_ms = WS_CLIENT_DEFAULT_TIMEOUT_MS) => {
|
|
331
336
|
if (is_closed)
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
|
|
18
18
|
import { autofocus } from '@fuzdev/fuz_ui/autofocus.svelte.ts';
|
|
19
19
|
|
|
20
|
-
import { Username } from '../primitive_schemas.js';
|
|
20
|
+
import { Username, USERNAME_LENGTH_MAX, USERNAME_LENGTH_MIN } from '../primitive_schemas.js';
|
|
21
21
|
import { PASSWORD_LENGTH_MIN } from '../auth/password.js';
|
|
22
22
|
import { auth_state_context } from './auth_state.svelte.js';
|
|
23
23
|
import { FormState } from './form_state.svelte.js';
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
} = $props();
|
|
34
34
|
|
|
35
35
|
const auth_state = auth_state_context.get();
|
|
36
|
-
const form_state = new FormState();
|
|
36
|
+
const form_state = new FormState<'token' | 'username' | 'password' | 'password_confirm'>();
|
|
37
37
|
|
|
38
38
|
let token = $state.raw('');
|
|
39
39
|
let username = $state.raw('');
|
|
@@ -47,11 +47,13 @@
|
|
|
47
47
|
username.trim() &&
|
|
48
48
|
username_valid &&
|
|
49
49
|
password.length >= PASSWORD_LENGTH_MIN &&
|
|
50
|
-
passwords_match
|
|
51
|
-
!auth_state.verifying
|
|
50
|
+
passwords_match
|
|
52
51
|
);
|
|
53
52
|
|
|
54
53
|
const handle_bootstrap = async (): Promise<void> => {
|
|
54
|
+
// while verifying the fields are `readonly` and the button `aria-disabled` —
|
|
55
|
+
// `disabled` would drop focus to `<body>` — so Enter and clicks still land here
|
|
56
|
+
if (auth_state.verifying) return;
|
|
55
57
|
form_state.attempt();
|
|
56
58
|
if (!can_submit) {
|
|
57
59
|
if (!token.trim()) form_state.focus('token');
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
type="password"
|
|
87
89
|
bind:value={token}
|
|
88
90
|
placeholder="paste token"
|
|
89
|
-
|
|
91
|
+
readonly={auth_state.verifying}
|
|
90
92
|
{@attach autofocus()}
|
|
91
93
|
/>
|
|
92
94
|
</label>
|
|
@@ -98,12 +100,13 @@
|
|
|
98
100
|
bind:value={username}
|
|
99
101
|
placeholder="admin"
|
|
100
102
|
autocomplete="username"
|
|
101
|
-
|
|
103
|
+
readonly={auth_state.verifying}
|
|
102
104
|
/>
|
|
103
105
|
</label>
|
|
104
106
|
{#if form_state.show('username') && username && !username_valid}
|
|
105
107
|
<p class="color_c_50 font_size_sm mt_0 mb_xs">
|
|
106
|
-
|
|
108
|
+
{USERNAME_LENGTH_MIN}-{USERNAME_LENGTH_MAX} chars, starts with a letter, ends with
|
|
109
|
+
letter/number, middle allows dash/underscore
|
|
107
110
|
</p>
|
|
108
111
|
{/if}
|
|
109
112
|
<fieldset>
|
|
@@ -115,7 +118,7 @@
|
|
|
115
118
|
bind:value={password}
|
|
116
119
|
placeholder="password"
|
|
117
120
|
autocomplete="new-password"
|
|
118
|
-
|
|
121
|
+
readonly={auth_state.verifying}
|
|
119
122
|
/>
|
|
120
123
|
</label>
|
|
121
124
|
{#if form_state.show('password') && password && password.length < PASSWORD_LENGTH_MIN}
|
|
@@ -131,7 +134,7 @@
|
|
|
131
134
|
bind:value={password_confirm}
|
|
132
135
|
placeholder="confirm password"
|
|
133
136
|
autocomplete="new-password"
|
|
134
|
-
|
|
137
|
+
readonly={auth_state.verifying}
|
|
135
138
|
/>
|
|
136
139
|
</label>
|
|
137
140
|
{#if form_state.show('password_confirm') && password && password_confirm && !passwords_match}
|
|
@@ -141,7 +144,8 @@
|
|
|
141
144
|
<div class="row gap_sm">
|
|
142
145
|
<PendingButton
|
|
143
146
|
pending={auth_state.verifying}
|
|
144
|
-
disabled={
|
|
147
|
+
disabled={false}
|
|
148
|
+
aria-disabled={auth_state.verifying}
|
|
145
149
|
onclick={handle_bootstrap}
|
|
146
150
|
class={auth_state.verify_error ? 'color_c' : ''}
|
|
147
151
|
>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BootstrapForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/BootstrapForm.svelte"],"names":[],"mappings":"AAyBC,KAAK,gBAAgB,GAAI;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;
|
|
1
|
+
{"version":3,"file":"BootstrapForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/BootstrapForm.svelte"],"names":[],"mappings":"AAyBC,KAAK,gBAAgB,GAAI;IACxB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AA0GH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
package/dist/ui/CLAUDE.md
CHANGED
|
@@ -93,7 +93,12 @@ pattern rather than reintroducing prop-drilling.
|
|
|
93
93
|
## Auth forms
|
|
94
94
|
|
|
95
95
|
All four consume `auth_state_context.get()`; all three form-driven ones
|
|
96
|
-
attach a `FormState` for Enter-advance + blur-touched validation.
|
|
96
|
+
attach a `FormState` for Enter-advance + blur-touched validation. While a
|
|
97
|
+
submit is in flight their fields are `readonly` and the `PendingButton` is
|
|
98
|
+
`aria-disabled` (with `disabled={false}` overriding its disable-while-pending
|
|
99
|
+
default), never `disabled` — disabling the focused element drops focus to
|
|
100
|
+
`<body>`. So the handler guards re-entry itself (`if (auth_state.verifying)
|
|
101
|
+
return;`), since Enter and clicks still reach it.
|
|
97
102
|
|
|
98
103
|
- `LoginForm.svelte` — props `username_label = 'username or email'`,
|
|
99
104
|
`redirect_on_login`. Clears `auth_state.verify_error` on input.
|
|
@@ -170,13 +175,18 @@ destructive actions.
|
|
|
170
175
|
to a specific actor on the recipient account; default account-grain),
|
|
171
176
|
`roles: Array<string>` (pre-filtered upstream by admin-grant-path —
|
|
172
177
|
`RoleSpec.grant_paths` includes `'admin'`),
|
|
173
|
-
`
|
|
174
|
-
|
|
178
|
+
`scope_kind` + `scope_id` (`RoleGrantOfferScope` — together, or neither
|
|
179
|
+
for a global offer; the prop types refuse one alone), `on_created?`,
|
|
180
|
+
`format_role?`. Surfaces the RPC error reasons with friendly copy:
|
|
181
|
+
`ERROR_ROLE_GRANT_OFFER_SELF_TARGET`,
|
|
175
182
|
`ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE`, `ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED`,
|
|
183
|
+
`ERROR_ROLE_GRANT_BUILTIN_SCOPED`,
|
|
176
184
|
`ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH`, `ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH`
|
|
177
|
-
— imported from `auth/role_grant_offer_action_specs.
|
|
185
|
+
— imported from `auth/role_grant_offer_action_specs.ts` (see
|
|
178
186
|
`auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` +
|
|
179
|
-
`role_grant_offer_actions.ts`).
|
|
187
|
+
`role_grant_offer_actions.ts`). Submits like the auth forms (`readonly`
|
|
188
|
+
textarea, `aria-disabled` button, re-entry guard); its select stays
|
|
189
|
+
`disabled`, having no `readonly` — safe, since the button has focus.
|
|
180
190
|
- `RoleGrantOfferHistory.svelte` — both-directions history (recipient +
|
|
181
191
|
grantor, including terminal). Props: `current_actor_id: string | null`
|
|
182
192
|
(classifies row as "sent" vs "received"), `format_actor?`,
|
|
@@ -259,8 +269,10 @@ destructive actions.
|
|
|
259
269
|
Methods: `form()` (returns a Svelte `Attachment` for the form
|
|
260
270
|
element), `show(field)` (touched OR attempted), `is_touched(field)`,
|
|
261
271
|
`touch(field)` (programmatic), `focus(field)` (queries by `name`),
|
|
262
|
-
`attempt()`, `reset()`.
|
|
263
|
-
|
|
272
|
+
`attempt()`, `reset()`. Generic over its field names —
|
|
273
|
+
`new FormState<'username' | 'password'>()` makes a misspelled field a
|
|
274
|
+
compile error (defaults to `string`). In DEV throws if an input loses
|
|
275
|
+
focus without a `name` attribute — all tracked inputs must be named.
|
|
264
276
|
- `sidebar_state.svelte.ts` — see Shell + layout above.
|
|
265
277
|
|
|
266
278
|
## 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
|
>
|
|
@@ -5,16 +5,28 @@
|
|
|
5
5
|
* `attempted` state (set on submit attempt). Errors show after a field is blurred or
|
|
6
6
|
* after a submit attempt, avoiding premature validation while the user is still typing.
|
|
7
7
|
*
|
|
8
|
-
* The `FormState.form` attachment also handles Enter key
|
|
9
|
-
*
|
|
8
|
+
* The `FormState.form` attachment also handles the Enter key: in an input
|
|
9
|
+
* followed by another input it moves focus to that input (past any button
|
|
10
|
+
* between), and in the last input it submits the form (`requestSubmit`) —
|
|
11
|
+
* so Enter in a login form's password field logs in, even when the form's
|
|
12
|
+
* button is `type="button"`, as `PendingButton` is. Hidden inputs don't count, and an Enter that commits
|
|
13
|
+
* IME composition is left alone.
|
|
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.
|
|
10
22
|
*
|
|
11
23
|
* All trackable inputs must have a `name` attribute — an error is thrown in dev
|
|
12
24
|
* if an input without `name` loses focus.
|
|
13
25
|
*
|
|
14
26
|
* @example
|
|
15
27
|
* ```svelte
|
|
16
|
-
* <script>
|
|
17
|
-
* const form_state = new FormState();
|
|
28
|
+
* <script lang="ts">
|
|
29
|
+
* const form_state = new FormState<'username'>();
|
|
18
30
|
* let username = $state.raw('');
|
|
19
31
|
* const username_valid = $derived(Username.safeParse(username).success);
|
|
20
32
|
* const can_submit = $derived(username.trim() && username_valid);
|
|
@@ -41,15 +53,16 @@
|
|
|
41
53
|
* @module
|
|
42
54
|
*/
|
|
43
55
|
import type { Attachment } from 'svelte/attachments';
|
|
44
|
-
export declare class FormState {
|
|
56
|
+
export declare class FormState<TField extends string = string> {
|
|
45
57
|
#private;
|
|
46
58
|
/**
|
|
47
59
|
* Whether a submit attempt has been made.
|
|
48
60
|
*/
|
|
49
61
|
get attempted(): boolean;
|
|
50
62
|
/**
|
|
51
|
-
* Creates a form attachment that handles Enter key
|
|
52
|
-
*
|
|
63
|
+
* Creates a form attachment that handles the Enter key (advance focus to
|
|
64
|
+
* the next input, or submit from the last one) and tracks field touched
|
|
65
|
+
* state via delegated `focusout`.
|
|
53
66
|
*
|
|
54
67
|
* Fields are identified by their `name` attribute.
|
|
55
68
|
*
|
|
@@ -60,22 +73,22 @@ export declare class FormState {
|
|
|
60
73
|
/**
|
|
61
74
|
* Whether a field has been blurred at least once.
|
|
62
75
|
*/
|
|
63
|
-
is_touched(field:
|
|
76
|
+
is_touched(field: TField): boolean;
|
|
64
77
|
/**
|
|
65
78
|
* Whether to show validation errors for a field.
|
|
66
79
|
* Returns `true` if the field has been blurred or a submit attempt was made.
|
|
67
80
|
*/
|
|
68
|
-
show(field:
|
|
81
|
+
show(field: TField): boolean;
|
|
69
82
|
/**
|
|
70
83
|
* Programmatically marks a field as touched without requiring a blur event.
|
|
71
84
|
*
|
|
72
85
|
* @mutates `this`
|
|
73
86
|
*/
|
|
74
|
-
touch(field:
|
|
87
|
+
touch(field: TField): void;
|
|
75
88
|
/**
|
|
76
89
|
* Focuses the named input within the form.
|
|
77
90
|
*/
|
|
78
|
-
focus(field:
|
|
91
|
+
focus(field: TField): void;
|
|
79
92
|
/**
|
|
80
93
|
* Marks the form as having been submitted, causing all field errors to show.
|
|
81
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"}
|