@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
@@ -27,6 +27,7 @@
27
27
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.ts';
28
28
  import { AsyncSlot } from './async_slot.svelte.js';
29
29
  import { is_iso8601_seconds_live } from '../timestamp.js';
30
+ import { GRANT_SCOPE_PAIRED_MESSAGE, is_grant_scope_paired } from '../auth/role_grant_offer_action_specs.js';
30
31
  import { ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, ROLE_GRANT_REVOKE_NOTIFICATION_METHOD } from '../auth/role_grant_offer_notifications.js';
31
32
  /**
32
33
  * Svelte context for `RoleGrantOffersState`.
@@ -121,10 +122,15 @@ export class RoleGrantOffersState {
121
122
  *
122
123
  * `to_actor_id` (optional) narrows the offer to a specific actor on
123
124
  * `to_account_id`; omit / null for the account-grain default (any actor
124
- * on the recipient account may accept).
125
+ * on the recipient account may accept). A scoped offer passes `scope_kind`
126
+ * and `scope_id` together; an unpaired scope (reachable from untyped
127
+ * callers) fails with `GRANT_SCOPE_PAIRED_MESSAGE` without an RPC.
125
128
  */
126
129
  async submit_create(params) {
127
130
  return this.create.run(async () => {
131
+ if (!is_grant_scope_paired(params)) {
132
+ throw new Error(GRANT_SCOPE_PAIRED_MESSAGE);
133
+ }
128
134
  const { offer } = await this.#rpc.create(params);
129
135
  this.#merge_offers([offer]);
130
136
  return offer;
@@ -3,8 +3,19 @@
3
3
  * `sidebar_state_context`; consumers read `show_sidebar` and call
4
4
  * `toggle_sidebar` / `activate`.
5
5
  *
6
+ * Visibility is two viewport-free fields — `show_sidebar_wide` (the persistent
7
+ * sidebar beside the content) and `show_sidebar_narrow` (the overlay below
8
+ * `narrow_query`) — so server and client render the same classes and CSS picks
9
+ * which field is in effect. `narrow` reads the viewport and is only for input
10
+ * handling and ARIA, never for markup that must match SSR.
11
+ *
6
12
  * @module
7
13
  */
14
+ /**
15
+ * Default `narrow_query` — below 800px the sidebar overlays the content and
16
+ * starts closed.
17
+ */
18
+ export declare const SIDEBAR_NARROW_QUERY_DEFAULT = "(max-width: 800px)";
8
19
  /**
9
20
  * Svelte context carrying a reactive `SidebarState` accessor. Set by
10
21
  * `ui/AppShell.svelte` (creates a fresh `SidebarState` if not supplied);
@@ -22,20 +33,69 @@ export interface SidebarStateOptions {
22
33
  * auto-returns `false` while the getter returns `false`.
23
34
  */
24
35
  enabled?: () => boolean;
36
+ /**
37
+ * Media query under which the sidebar overlays the content and starts
38
+ * closed. It feeds both `AppShell`'s CSS `media` attribute and a
39
+ * `MediaQuery`, so it must be a full query with parentheses
40
+ * (`'(max-width: 900px)'`, not `'max-width: 900px'`). `null` disables
41
+ * narrow mode.
42
+ * @default SIDEBAR_NARROW_QUERY_DEFAULT
43
+ */
44
+ narrow_query?: string | null;
25
45
  }
26
46
  export declare class SidebarState {
27
47
  #private;
48
+ /** The media query for narrow mode, or `null` when narrow mode is disabled. */
49
+ readonly narrow_query: string | null;
50
+ /**
51
+ * Whether the sidebar shows beside the content outside narrow mode — the
52
+ * desktop preference. Narrow-mode interactions never touch it.
53
+ */
54
+ show_sidebar_wide: boolean;
55
+ /** Whether the narrow-mode overlay is open. */
56
+ show_sidebar_narrow: boolean;
57
+ /**
58
+ * Creates the state, subscribing to `narrow_query` unless it is `null`.
59
+ *
60
+ * @param options - enabled getter and narrow-mode query
61
+ * @throws Error in DEV when `narrow_query` has no parentheses — `MediaQuery`
62
+ * would auto-wrap it but CSS `media` would not, so they'd disagree
63
+ */
64
+ constructor(options?: SidebarStateOptions);
28
65
  get enabled(): boolean;
29
66
  set enabled(value: boolean);
67
+ /**
68
+ * Whether the viewport matches `narrow_query`. Always `false` during SSR
69
+ * and when `narrow_query` is `null`. Read it for input handling and ARIA
70
+ * only — markup that must match the server render should key on
71
+ * `show_sidebar_wide` / `show_sidebar_narrow` instead.
72
+ */
73
+ get narrow(): boolean;
74
+ /**
75
+ * Effective visibility for the current mode: `false` while disabled,
76
+ * otherwise `show_sidebar_narrow` in narrow mode and `show_sidebar_wide`
77
+ * outside it. The setter writes the current mode's field.
78
+ */
30
79
  get show_sidebar(): boolean;
31
80
  set show_sidebar(value: boolean);
32
- constructor(options?: SidebarStateOptions);
81
+ /**
82
+ * Sets the current mode's visibility, routed by `narrow` at call time.
83
+ *
84
+ * @param value - the new visibility, defaults to the inverse of `show_sidebar`
85
+ */
33
86
  toggle_sidebar(value?: boolean): void;
87
+ /**
88
+ * Closes the narrow-mode overlay, leaving the wide preference untouched.
89
+ * `AppShell` calls it on navigation, Escape, scrim click, and when the
90
+ * viewport widens out of narrow mode.
91
+ */
92
+ close_narrow(): void;
34
93
  /**
35
94
  * Show the sidebar and enable the toggle. The returned disposer hides
36
95
  * and disables on cleanup — pair with `$effect` for scoped activation.
37
96
  *
38
- * @mutates `this` - sets `enabled` and `show_sidebar`; disposer clears both
97
+ * @mutates `this` - sets `enabled` and `show_sidebar_wide`; the disposer
98
+ * clears `enabled`, `show_sidebar_wide`, and `show_sidebar_narrow`
39
99
  */
40
100
  activate(): () => void;
41
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/sidebar_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;2CAAwB,YAAY;4BAAZ,YAAY;uBAAZ,YAAY,WAAZ,YAAY;CAAG,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,qBAAa,YAAY;;IAKxB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,YAAY,IAAI,OAAO,CAG1B;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAE9B;gBAEW,OAAO,CAAC,EAAE,mBAAmB;IAIzC,cAAc,CAAC,KAAK,GAAE,OAA4B,GAAG,IAAI;IAIzD;;;;;OAKG;IACH,QAAQ,IAAI,MAAM,IAAI;CAQtB"}
1
+ {"version":3,"file":"sidebar_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/sidebar_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;GAGG;AACH,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;2CAAwB,YAAY;4BAAZ,YAAY;uBAAZ,YAAY,WAAZ,YAAY;CAAG,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,qBAAa,YAAY;;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC;;;OAGG;IACH,iBAAiB,EAAE,OAAO,CAAoB;IAE9C,+CAA+C;IAC/C,mBAAmB,EAAE,OAAO,CAAqB;IAMjD;;;;;;OAMG;gBACS,OAAO,CAAC,EAAE,mBAAmB;IAazC,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED;;;;;OAKG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,CAG1B;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAM9B;IAED;;;;OAIG;IACH,cAAc,CAAC,KAAK,GAAE,OAA4B,GAAG,IAAI;IAIzD;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAIpB;;;;;;OAMG;IACH,QAAQ,IAAI,MAAM,IAAI;CAStB"}
@@ -3,9 +3,22 @@
3
3
  * `sidebar_state_context`; consumers read `show_sidebar` and call
4
4
  * `toggle_sidebar` / `activate`.
5
5
  *
6
+ * Visibility is two viewport-free fields — `show_sidebar_wide` (the persistent
7
+ * sidebar beside the content) and `show_sidebar_narrow` (the overlay below
8
+ * `narrow_query`) — so server and client render the same classes and CSS picks
9
+ * which field is in effect. `narrow` reads the viewport and is only for input
10
+ * handling and ARIA, never for markup that must match SSR.
11
+ *
6
12
  * @module
7
13
  */
14
+ import { DEV } from 'esm-env';
15
+ import { MediaQuery } from 'svelte/reactivity';
8
16
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.ts';
17
+ /**
18
+ * Default `narrow_query` — below 800px the sidebar overlays the content and
19
+ * starts closed.
20
+ */
21
+ export const SIDEBAR_NARROW_QUERY_DEFAULT = '(max-width: 800px)';
9
22
  /**
10
23
  * Svelte context carrying a reactive `SidebarState` accessor. Set by
11
24
  * `ui/AppShell.svelte` (creates a fresh `SidebarState` if not supplied);
@@ -13,41 +26,97 @@ import { create_context } from '@fuzdev/fuz_ui/context_helpers.ts';
13
26
  */
14
27
  export const sidebar_state_context = create_context();
15
28
  export class SidebarState {
29
+ /** The media query for narrow mode, or `null` when narrow mode is disabled. */
30
+ narrow_query;
31
+ /**
32
+ * Whether the sidebar shows beside the content outside narrow mode — the
33
+ * desktop preference. Narrow-mode interactions never touch it.
34
+ */
35
+ show_sidebar_wide = $state.raw(true);
36
+ /** Whether the narrow-mode overlay is open. */
37
+ show_sidebar_narrow = $state.raw(false);
16
38
  #get_enabled;
17
39
  #enabled = $state.raw(true);
18
- #show_sidebar = $state.raw(true);
40
+ #narrow_media;
41
+ /**
42
+ * Creates the state, subscribing to `narrow_query` unless it is `null`.
43
+ *
44
+ * @param options - enabled getter and narrow-mode query
45
+ * @throws Error in DEV when `narrow_query` has no parentheses — `MediaQuery`
46
+ * would auto-wrap it but CSS `media` would not, so they'd disagree
47
+ */
48
+ constructor(options) {
49
+ this.#get_enabled = options?.enabled;
50
+ const narrow_query = options?.narrow_query === undefined ? SIDEBAR_NARROW_QUERY_DEFAULT : options.narrow_query;
51
+ if (DEV && narrow_query !== null && !narrow_query.includes('(')) {
52
+ throw new Error(`SidebarState narrow_query must include parentheses, got '${narrow_query}' — try '(${narrow_query})'`);
53
+ }
54
+ this.narrow_query = narrow_query;
55
+ this.#narrow_media = narrow_query === null ? null : new MediaQuery(narrow_query, false);
56
+ }
19
57
  get enabled() {
20
58
  return this.#get_enabled?.() ?? this.#enabled;
21
59
  }
22
60
  set enabled(value) {
23
61
  this.#enabled = value;
24
62
  }
63
+ /**
64
+ * Whether the viewport matches `narrow_query`. Always `false` during SSR
65
+ * and when `narrow_query` is `null`. Read it for input handling and ARIA
66
+ * only — markup that must match the server render should key on
67
+ * `show_sidebar_wide` / `show_sidebar_narrow` instead.
68
+ */
69
+ get narrow() {
70
+ return this.#narrow_media?.current ?? false;
71
+ }
72
+ /**
73
+ * Effective visibility for the current mode: `false` while disabled,
74
+ * otherwise `show_sidebar_narrow` in narrow mode and `show_sidebar_wide`
75
+ * outside it. The setter writes the current mode's field.
76
+ */
25
77
  get show_sidebar() {
26
78
  if (!this.enabled)
27
79
  return false;
28
- return this.#show_sidebar;
80
+ return this.narrow ? this.show_sidebar_narrow : this.show_sidebar_wide;
29
81
  }
30
82
  set show_sidebar(value) {
31
- this.#show_sidebar = value;
32
- }
33
- constructor(options) {
34
- this.#get_enabled = options?.enabled;
83
+ if (this.narrow) {
84
+ this.show_sidebar_narrow = value;
85
+ }
86
+ else {
87
+ this.show_sidebar_wide = value;
88
+ }
35
89
  }
90
+ /**
91
+ * Sets the current mode's visibility, routed by `narrow` at call time.
92
+ *
93
+ * @param value - the new visibility, defaults to the inverse of `show_sidebar`
94
+ */
36
95
  toggle_sidebar(value = !this.show_sidebar) {
37
96
  this.show_sidebar = value;
38
97
  }
98
+ /**
99
+ * Closes the narrow-mode overlay, leaving the wide preference untouched.
100
+ * `AppShell` calls it on navigation, Escape, scrim click, and when the
101
+ * viewport widens out of narrow mode.
102
+ */
103
+ close_narrow() {
104
+ this.show_sidebar_narrow = false;
105
+ }
39
106
  /**
40
107
  * Show the sidebar and enable the toggle. The returned disposer hides
41
108
  * and disables on cleanup — pair with `$effect` for scoped activation.
42
109
  *
43
- * @mutates `this` - sets `enabled` and `show_sidebar`; disposer clears both
110
+ * @mutates `this` - sets `enabled` and `show_sidebar_wide`; the disposer
111
+ * clears `enabled`, `show_sidebar_wide`, and `show_sidebar_narrow`
44
112
  */
45
113
  activate() {
46
114
  this.enabled = true;
47
- this.show_sidebar = true;
115
+ this.show_sidebar_wide = true;
48
116
  return () => {
49
117
  this.enabled = false;
50
- this.show_sidebar = false;
118
+ this.show_sidebar_wide = false;
119
+ this.show_sidebar_narrow = false;
51
120
  };
52
121
  }
53
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.114.0",
3
+ "version": "0.116.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -100,8 +100,6 @@
100
100
  "jsdom": "^28.1.0",
101
101
  "magic-string": "^0.30.21",
102
102
  "pg": "^8.20.0",
103
- "prettier": "^3.9.6",
104
- "prettier-plugin-svelte": "^4.1.1",
105
103
  "svelte": "^5.56.4",
106
104
  "svelte-check": "^4.7.1",
107
105
  "svelte-docinfo": "^0.7.0",
@@ -115,24 +113,6 @@
115
113
  "zimmerframe": "^1.1.4",
116
114
  "zod": "^4.3.6"
117
115
  },
118
- "prettier": {
119
- "plugins": [
120
- "prettier-plugin-svelte"
121
- ],
122
- "useTabs": true,
123
- "printWidth": 100,
124
- "singleQuote": true,
125
- "bracketSpacing": true,
126
- "trailingComma": "none",
127
- "overrides": [
128
- {
129
- "files": "package.json",
130
- "options": {
131
- "useTabs": false
132
- }
133
- }
134
- ]
135
- },
136
116
  "sideEffects": [
137
117
  "**/*.css"
138
118
  ],