@colixsystems/widget-sdk 0.219.0 → 0.220.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/README.md CHANGED
@@ -23,6 +23,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
23
23
  | **CORE** | `useWidgetStyle()` | `{ [styleField]: value }` | `ctx.props.style` — no scope. The author-set per-widget style values declared in `manifest.styleSchema`; apply each onto whatever element you choose. |
24
24
  | **CORE** | `useUser()` | `{ id, email, displayName, photoUrl, roles, groupIds }` | `ctx.user` (host-built context, **camelCase** — not a wire payload; `id` null when anonymous) — no scope. `photoUrl` is the user's own profile photo as an absolute URL, or `null`. |
25
25
  | **CORE** | `useUserPhoto()` | `{ url, canEdit, upload, remove, uploading, error }` | `ctx.user` + the optional `ctx.account` — no scope. Changes the signed-in user's OWN profile photo. `canEdit` is false when nobody is signed in or the host cannot change it; `upload` / `remove` then reject. |
26
+ | **CORE** | `useAnonymousAccount()` | `{ supported, canCreate, creating, error, create }` | The optional `ctx.account.anonymous` — no scope. Offers an account with a generated login code and password and no email. `create()` opens the app's own dialog, which confirms, creates the account, shows the pair once and signs the visitor in; the widget never receives the pair and `create()` resolves `{ created: true }`. `supported` is false where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview and for a badge counter; `canCreate` is false while someone is signed in. |
26
27
  | **CORE** | `useNavigation()` | `{ goTo, goBack, push, replace, back, currentRoute, openLink }` | `ctx.navigation` — no scope. `goTo(pageIdOrSlug, params?)` accepts a page UUID (a `pageRef` prop) **or its slug** (a row's page-key field) — both hosts resolve either (`openLink` for a link of unknown shape; a known external URL can also use the `Linking` primitive) |
27
28
  | **CORE** | `useFilePreview(file)` | `{ uri }` | No scope. A temporary address an `<Image>` can show for a file the person picked but has not uploaded yet (a photo in a composer). Native picks use their own uri; on the web the SDK creates and releases an object URL, which widgets may not do themselves. `uri` is null for no file. |
28
29
  | **CORE** | `usePageBadge(count)` | `{ supported }` | `ctx.navigation.setBadge` — no scope. Puts `count` on the menu entry or tab of the page the widget is on (a chat's unread messages). The host sums the page's widgets and keeps the last count after the widget leaves the screen, until it reports again; `0` clears this widget's share. `supported` is false where the host draws no page badges (the Builder canvas). |
@@ -32,7 +33,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
32
33
  | **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope. The hook IS the emitter: `const emitSlot = useWidgetEvent("slotChosen")`, then `emitSlot(payload)`. Never destructure the result — there is no `emit` member. A sibling's input reads the payload, and a page action the author binds to the event (Run action, Navigate) runs with it — fired from one control, only that control runs it. |
33
34
  | **CORE** | `useWidgetInput(inputName)` | the published payload, or `undefined` | `ctx.inputs` — no scope. Reads a value ANOTHER widget on the same page published with `useWidgetEvent`. Declare the input in `manifest.inputs`; the page author wires it to one sibling's declared event. The channel retains the last payload, so a widget that mounts later still reads it. `undefined` while unwired or before the first publish — always render a sensible default. Page-scoped and ephemeral: use `useRouteParams()` for state that must survive navigation, the datastore for state that must persist. |
34
35
  | **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
35
- | **CORE** | `useFill()` | `boolean` | `ctx.fill` — no scope. `true` when the host sized this widget to fill its page-grid tile's reserved height (containers + media fill by default; the author can override per tile). Media-style widgets switch to a `flex: 1` / `height: "100%"` layout; others ignore it. Defaults `false`. |
36
+ | **CORE** | `useFill()` | `boolean` | `ctx.fill` — no scope. `true` when the host sized this widget to fill a box of definite height: a widget in the section a fit-to-screen page stretches to the bottom of the screen, or one given a fixed slot height. Media-style widgets, and a chat or feed that scrolls its own content, switch to a `flex: 1` / `height: "100%"` layout and scroll inside it; others ignore it. Defaults `false`. |
36
37
  | **CORE** | `useContainerWidth()` | `[width, onLayout]` | No context slice, no scope. Measures the width the widget's OWN box has, so it can lay itself out for the space it is in rather than for the screen. Spread the handler onto your outermost primitive. Use it for any widget with a wide form and a narrow one (a table, a toolbar, a row of tiles) — never switch on the device or window width, because a widget in a one-of-three grid cell on desktop has phone-width room and a widget filling a phone page does not. Width is 0 until the first layout: render the WIDE form then. One implementation covers web (react-native-web) and the native export. |
37
38
  | **CORE** | `isNarrowWidth(width)` | `boolean` | No context slice, no scope. True when a MEASURED width is below `NARROW_WIDTH_PX` (480) — the one threshold every widget switches at, so a page reflows together rather than raggedly. An unmeasured width (0) is NOT narrow, so nothing flashes through the narrow form on first paint. |
38
39
  | **CORE** | `widthTier(width)` | `"narrow" \| "medium" \| "wide"` | No context slice, no scope. Which of THREE layouts a measured width calls for: narrow below `NARROW_WIDTH_PX` (480), wide at or above `WIDE_WIDTH_PX` (960), medium between. `widthTier(w) === "narrow"` exactly when `isNarrowWidth(w)`; an unmeasured width (0) is "wide". |
@@ -83,7 +84,54 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
83
84
 
84
85
  ## Status
85
86
 
86
- `v0.219.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
87
+ `v0.220.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
88
+
89
+ ### What's new in 0.220.0 (contract 1.176.0)
90
+
91
+ **An app can let a visitor continue without an email — `useAnonymousAccount()`.** Some apps must let a person act without identifying themselves and still come back later: someone files a report and returns to follow it. The new hook lets a widget offer an account that has no email address. The server generates a login code and a password, and the visitor signs in again later with that pair on the app's sign-in screen ("Sign in with a login code").
92
+
93
+ ```jsx
94
+ import { useAnonymousAccount, useUser, Button, Text, View } from "@colixsystems/widget-sdk";
95
+
96
+ export default function ContinueWithoutEmail() {
97
+ const user = useUser();
98
+ const { supported, canCreate, creating, error, create } = useAnonymousAccount();
99
+
100
+ if (!supported) {
101
+ return <Text>Continuing without an email is not available here.</Text>;
102
+ }
103
+ if (user.id) {
104
+ return <Text>You are signed in.</Text>;
105
+ }
106
+ if (!canCreate) return null;
107
+
108
+ return (
109
+ <View>
110
+ <Button
111
+ title="Continue without an email"
112
+ disabled={creating}
113
+ onPress={() => create().catch(() => {})}
114
+ />
115
+ {error && error.code !== "CANCELLED" ? (
116
+ <Text>Could not create the account. Try again.</Text>
117
+ ) : null}
118
+ </View>
119
+ );
120
+ }
121
+ ```
122
+
123
+ - **The widget only asks; the app does the rest.** `create()` takes no arguments and opens the app's own dialog. It asks the visitor to confirm, shows the captcha notice when a captcha will run, creates the account, shows the login code and password once with the warning that a lost password cannot be recovered, and signs the visitor in when they confirm they saved them (or opens the sign-in screen's two-factor step when the workspace requires one). Do not build a confirmation, a captcha notice or a credentials screen of your own.
124
+ - **The widget never receives the login code or the password.** `create()` resolves `{ created: true }` and nothing else. Widgets on a page share one context, so a credential that cannot be reset is kept out of it altogether. This is a contract boundary, not a sandbox: the SDK never hands the pair to a widget, but every widget on a page still runs inside the app.
125
+ - **Call `create()` from a user action.** A button's `onPress`, never on mount or in an effect. Nothing is created until the visitor confirms in the dialog.
126
+ - **Failures carry a `code`.** `create()` rejects with an Error whose `code` is the server's refusal (`ANONYMOUS_ACCOUNT_LIMIT_REACHED`, `RATE_LIMITED`, `CAPTCHA_FAILED`, …), `CAPTCHA_UNAVAILABLE` when the captcha could not run (there is no fallback), `ALREADY_SIGNED_IN`, `CANCELLED` when the visitor closes the dialog, or `UNSUPPORTED`. The dialog shows a failure itself and lets the visitor retry, so a rejection means they gave up; treat `CANCELLED` as no error. `error` holds the last one.
127
+ - **`supported` and `canCreate`.** `supported` is false where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview and for a badge counter: render a visible "not available" state. `canCreate` is false while someone is signed in. The hook never throws at render and needs no scope.
128
+ - **What the account can do.** It is not treated as a signed-in member: it gets only what the workspace grants to everyone or to the group chosen for these accounts, plus the records it owns. `useUser().email` is `null` for it.
129
+
130
+ A host backs the hook with the new optional `WidgetContext.account.anonymous` member, `{ canCreate, create() }`. `account`'s photo actions are now optional members, so a host may publish `account` with either or both. Additive.
131
+
132
+ ### What's new in 0.219.1
133
+
134
+ **`useFill()` says when it is true today.** It described a page-grid tile with a "Fill tile height" choice, a layout that no longer exists. A host now fills a widget in two cases: the widget sits in the section a fit-to-screen page stretches to the bottom of the screen, or the widget has a fixed slot height. A chat or feed that scrolls its own content is one of the widgets that should honour it. A documentation correction; `CONTRACT.version` is unchanged.
87
135
 
88
136
  ### What's new in 0.219.0 (contract 1.175.0)
89
137
 
package/dist/contract.cjs CHANGED
@@ -938,17 +938,50 @@ const HOOKS = [
938
938
  requiredContextSlice: ["user", "account.setPhoto", "account.removePhoto"],
939
939
  scopes: null,
940
940
  },
941
+ {
942
+ name: "useAnonymousAccount",
943
+ signature: "useAnonymousAccount()",
944
+ description:
945
+ "Offer an account that needs NO email address: the server generates a login code and a password, and the person signs in " +
946
+ "later with that pair on the app's sign-in screen. For apps where a visitor must be able to act without identifying " +
947
+ "themselves and still come back (a whistleblower report). The widget ONLY ASKS: create() takes NO arguments and opens the APP'S " +
948
+ "OWN dialog, which asks the visitor to confirm, runs the captcha, creates the account, shows the login code and password " +
949
+ "ONCE with the warning that a lost password cannot be recovered, and signs the visitor in when they confirm they saved " +
950
+ "them (or opens the sign-in screen's two-factor step when the workspace requires one). The widget NEVER receives the " +
951
+ "login code or the password: create() resolves { created: true } and nothing else. Do NOT build your own confirmation, " +
952
+ "captcha notice or credentials screen, and never call an endpoint yourself. Call create() from a user action (a button's " +
953
+ "onPress), never on mount or in an effect. It rejects with an Error carrying code: the server's refusal " +
954
+ "(ANONYMOUS_ACCOUNT_LIMIT_REACHED, RATE_LIMITED, CAPTCHA_FAILED, …), CAPTCHA_UNAVAILABLE when the " +
955
+ "captcha could not run (there is no fallback), ALREADY_SIGNED_IN, CANCELLED when the visitor closes the dialog, or " +
956
+ "UNSUPPORTED. The dialog already shows a failure and lets the visitor retry, so a rejection means they gave up: treat " +
957
+ "CANCELLED as no error. supported is false — and create() rejects with UNSUPPORTED instead of the hook throwing at " +
958
+ "render — where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview pane, in a draft " +
959
+ "preview and for a badge counter: render a visible 'not available' state then. canCreate is false while someone is " +
960
+ "signed in: hide the button then. After create() resolves the visitor is signed in and the page re-renders, so read " +
961
+ "useUser() for the new state. Needs no scope. Identical on web (Player) and the Expo export.",
962
+ returnShape: {
963
+ supported: "boolean // the host offers anonymous accounts here",
964
+ canCreate: "boolean // supported AND nobody is signed in",
965
+ creating: "boolean // a create() call is in flight: the app's dialog is open",
966
+ error: "(Error & { code?: string, status?: number }) | null // the last create() failure; cleared by the next attempt",
967
+ create:
968
+ "() => Promise<{ created: true }> // takes no arguments; opens the app's dialog; never returns the login code or password",
969
+ },
970
+ requiredContextSlice: ["account.anonymous"],
971
+ scopes: null,
972
+ },
941
973
  {
942
974
  name: "useFill",
943
975
  signature: "useFill()",
944
976
  description:
945
- "Returns true when the host has sized this widget to fill its layout " +
946
- 'slot\'s available height — a page-grid tile whose author chose "Fill ' +
947
- 'tile height", or a widget type that fills by default (containers and ' +
948
- "media). When true, a widget that has a meaningful filled form (Image, " +
949
- "Chart, Map, Video) should switch from its intrinsic height to a stretch " +
950
- 'style (flex: 1 / height: "100%"); widgets with no useful filled form ' +
951
- "may ignore it. Defaults to false wherever the host has not opted the " +
977
+ "Returns true when the host has sized this widget to fill a box of " +
978
+ "definite height — a widget in the section a fit-to-screen page " +
979
+ "stretches to the bottom of the screen, or a widget given a fixed slot " +
980
+ "height. When true, a widget that has a meaningful filled form (Image, " +
981
+ "Chart, Map, Video, a chat or feed that scrolls its own content) should " +
982
+ "switch from its intrinsic height to a stretch style (flex: 1 / " +
983
+ 'height: "100%") and scroll inside it; widgets with no useful filled ' +
984
+ "form may ignore it. Defaults to false wherever the host has not opted the " +
952
985
  "widget into filling, so calling it is always safe. The SAME value is " +
953
986
  "injected by the web Player and the native export (widget-parity skill), so a " +
954
987
  "widget's fill behaviour is identical on both platforms.",
@@ -3549,40 +3582,46 @@ const WIDGET_CONTEXT_SHAPE = {
3549
3582
  },
3550
3583
  },
3551
3584
  // REQ-LAY-08 — optional host layout hint backing useFill(). True when the
3552
- // host sized this widget to fill its layout slot's available height (a
3553
- // page-grid tile set to "Fill tile height", or a default-fill widget type).
3585
+ // host sized this widget to fill a box of definite height (a fit-to-screen
3586
+ // page's stretched section, or a fixed slot height).
3554
3587
  // Optional: a host that never fills a widget simply omits it and useFill()
3555
3588
  // returns false, so existing hosts need no change.
3556
3589
  fill: {
3557
3590
  description:
3558
3591
  "Optional host layout hint (boolean). True when the host sized this " +
3559
- "widget to fill its layout slot's available height (page-grid tile " +
3560
- "fill). Backs useFill(); defaults to false when absent.",
3592
+ "widget to fill a box of definite height (a fit-to-screen page's " +
3593
+ "stretched section, or a fixed slot height). Backs useFill(); defaults " +
3594
+ "to false when absent.",
3561
3595
  required: false,
3562
3596
  fields: {},
3563
3597
  },
3564
3598
  user: {
3565
3599
  description:
3566
- "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. Not a data-client.",
3600
+ "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. email is null for an account created without one. Not a data-client.",
3567
3601
  required: true,
3568
3602
  fields: {
3569
3603
  id: "string",
3570
- email: "string",
3604
+ email: "string | null",
3571
3605
  displayName: "string",
3572
3606
  photoUrl: "string | null",
3573
3607
  },
3574
3608
  },
3575
- // Optional so a host that cannot change the photo needs no stub:
3576
- // useUserPhoto() reports canEdit false when it is absent.
3609
+ // Optional, and so is every member: a host publishes only what it can do.
3577
3610
  account: {
3578
3611
  description:
3579
- "Optional self-service account actions for the signed-in user: " +
3580
- "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> }. " +
3581
- "Backs useUserPhoto(). The host performs the request AND updates its own session, so user.photoUrl changes for every " +
3582
- "widget. setPhoto takes a browser File on web and expo-file-system's File on native. Absent on a host that cannot " +
3583
- "change the photo (the Builder canvas preview), where useUserPhoto() degrades to canEdit: false rather than throwing.",
3612
+ "Optional self-service account actions; every member is optional too. " +
3613
+ "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> } back useUserPhoto() and are " +
3614
+ "present only while a user is signed in. The host performs the request AND updates its own session, so user.photoUrl " +
3615
+ "changes for every widget. setPhoto takes a browser File on web and expo-file-system's File on native. " +
3616
+ "anonymous backs useAnonymousAccount() and is present only where the workspace offers accounts without an email: " +
3617
+ "{ canCreate: boolean, create() -> Promise<{ created: true }> }. create() opens the HOST'S OWN dialog: " +
3618
+ "it asks the visitor to confirm, runs the captcha, calls the server, shows the login code and password once, and signs " +
3619
+ "the visitor in when they confirm they saved them. The pair lives only in that dialog and never reaches the slice, a " +
3620
+ "widget, an error or storage. " +
3621
+ "The whole slice is absent on the Builder canvas, in the preview panes and for a badge counter, where both hooks " +
3622
+ "degrade (canEdit: false, supported: false) rather than throwing.",
3584
3623
  required: false,
3585
- fields: { setPhoto: "function", removePhoto: "function" },
3624
+ fields: { setPhoto: "function", removePhoto: "function", anonymous: "object" },
3586
3625
  },
3587
3626
  workspace: {
3588
3627
  description:
@@ -5810,7 +5849,14 @@ const CONTRACT = deepFreeze({
5810
5849
  // new `ctx.filestore.signingRounds`. `useFileSignature` also returns
5811
5850
  // `hintCode` and `nameRefusal`, and its `cancel()` resolves to what
5812
5851
  // became of the order instead of always reporting "cancelled".
5813
- version: "1.175.0",
5852
+ // 1.176.0: additive (sc-10934) — `useAnonymousAccount()` asks the host to
5853
+ // create an account with a generated login code and password and no
5854
+ // email, through the new optional `ctx.account.anonymous`
5855
+ // (`{ canCreate, create }`). The host shows the pair in its own dialog;
5856
+ // a widget never receives it. `account`'s photo actions are now
5857
+ // optional members, and `user.email` is documented as nullable: an
5858
+ // account without an email has none.
5859
+ version: "1.176.0",
5814
5860
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5815
5861
  hooks: HOOKS,
5816
5862
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -938,17 +938,50 @@ const HOOKS = [
938
938
  requiredContextSlice: ["user", "account.setPhoto", "account.removePhoto"],
939
939
  scopes: null,
940
940
  },
941
+ {
942
+ name: "useAnonymousAccount",
943
+ signature: "useAnonymousAccount()",
944
+ description:
945
+ "Offer an account that needs NO email address: the server generates a login code and a password, and the person signs in " +
946
+ "later with that pair on the app's sign-in screen. For apps where a visitor must be able to act without identifying " +
947
+ "themselves and still come back (a whistleblower report). The widget ONLY ASKS: create() takes NO arguments and opens the APP'S " +
948
+ "OWN dialog, which asks the visitor to confirm, runs the captcha, creates the account, shows the login code and password " +
949
+ "ONCE with the warning that a lost password cannot be recovered, and signs the visitor in when they confirm they saved " +
950
+ "them (or opens the sign-in screen's two-factor step when the workspace requires one). The widget NEVER receives the " +
951
+ "login code or the password: create() resolves { created: true } and nothing else. Do NOT build your own confirmation, " +
952
+ "captcha notice or credentials screen, and never call an endpoint yourself. Call create() from a user action (a button's " +
953
+ "onPress), never on mount or in an effect. It rejects with an Error carrying code: the server's refusal " +
954
+ "(ANONYMOUS_ACCOUNT_LIMIT_REACHED, RATE_LIMITED, CAPTCHA_FAILED, …), CAPTCHA_UNAVAILABLE when the " +
955
+ "captcha could not run (there is no fallback), ALREADY_SIGNED_IN, CANCELLED when the visitor closes the dialog, or " +
956
+ "UNSUPPORTED. The dialog already shows a failure and lets the visitor retry, so a rejection means they gave up: treat " +
957
+ "CANCELLED as no error. supported is false — and create() rejects with UNSUPPORTED instead of the hook throwing at " +
958
+ "render — where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview pane, in a draft " +
959
+ "preview and for a badge counter: render a visible 'not available' state then. canCreate is false while someone is " +
960
+ "signed in: hide the button then. After create() resolves the visitor is signed in and the page re-renders, so read " +
961
+ "useUser() for the new state. Needs no scope. Identical on web (Player) and the Expo export.",
962
+ returnShape: {
963
+ supported: "boolean // the host offers anonymous accounts here",
964
+ canCreate: "boolean // supported AND nobody is signed in",
965
+ creating: "boolean // a create() call is in flight: the app's dialog is open",
966
+ error: "(Error & { code?: string, status?: number }) | null // the last create() failure; cleared by the next attempt",
967
+ create:
968
+ "() => Promise<{ created: true }> // takes no arguments; opens the app's dialog; never returns the login code or password",
969
+ },
970
+ requiredContextSlice: ["account.anonymous"],
971
+ scopes: null,
972
+ },
941
973
  {
942
974
  name: "useFill",
943
975
  signature: "useFill()",
944
976
  description:
945
- "Returns true when the host has sized this widget to fill its layout " +
946
- 'slot\'s available height — a page-grid tile whose author chose "Fill ' +
947
- 'tile height", or a widget type that fills by default (containers and ' +
948
- "media). When true, a widget that has a meaningful filled form (Image, " +
949
- "Chart, Map, Video) should switch from its intrinsic height to a stretch " +
950
- 'style (flex: 1 / height: "100%"); widgets with no useful filled form ' +
951
- "may ignore it. Defaults to false wherever the host has not opted the " +
977
+ "Returns true when the host has sized this widget to fill a box of " +
978
+ "definite height — a widget in the section a fit-to-screen page " +
979
+ "stretches to the bottom of the screen, or a widget given a fixed slot " +
980
+ "height. When true, a widget that has a meaningful filled form (Image, " +
981
+ "Chart, Map, Video, a chat or feed that scrolls its own content) should " +
982
+ "switch from its intrinsic height to a stretch style (flex: 1 / " +
983
+ 'height: "100%") and scroll inside it; widgets with no useful filled ' +
984
+ "form may ignore it. Defaults to false wherever the host has not opted the " +
952
985
  "widget into filling, so calling it is always safe. The SAME value is " +
953
986
  "injected by the web Player and the native export (widget-parity skill), so a " +
954
987
  "widget's fill behaviour is identical on both platforms.",
@@ -3545,40 +3578,46 @@ const WIDGET_CONTEXT_SHAPE = {
3545
3578
  },
3546
3579
  },
3547
3580
  // REQ-LAY-08 — optional host layout hint backing useFill(). True when the
3548
- // host sized this widget to fill its layout slot's available height (a
3549
- // page-grid tile set to "Fill tile height", or a default-fill widget type).
3581
+ // host sized this widget to fill a box of definite height (a fit-to-screen
3582
+ // page's stretched section, or a fixed slot height).
3550
3583
  // Optional: a host that never fills a widget simply omits it and useFill()
3551
3584
  // returns false, so existing hosts need no change.
3552
3585
  fill: {
3553
3586
  description:
3554
3587
  "Optional host layout hint (boolean). True when the host sized this " +
3555
- "widget to fill its layout slot's available height (page-grid tile " +
3556
- "fill). Backs useFill(); defaults to false when absent.",
3588
+ "widget to fill a box of definite height (a fit-to-screen page's " +
3589
+ "stretched section, or a fixed slot height). Backs useFill(); defaults " +
3590
+ "to false when absent.",
3557
3591
  required: false,
3558
3592
  fields: {},
3559
3593
  },
3560
3594
  user: {
3561
3595
  description:
3562
- "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. Not a data-client.",
3596
+ "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. email is null for an account created without one. Not a data-client.",
3563
3597
  required: true,
3564
3598
  fields: {
3565
3599
  id: "string",
3566
- email: "string",
3600
+ email: "string | null",
3567
3601
  displayName: "string",
3568
3602
  photoUrl: "string | null",
3569
3603
  },
3570
3604
  },
3571
- // Optional so a host that cannot change the photo needs no stub:
3572
- // useUserPhoto() reports canEdit false when it is absent.
3605
+ // Optional, and so is every member: a host publishes only what it can do.
3573
3606
  account: {
3574
3607
  description:
3575
- "Optional self-service account actions for the signed-in user: " +
3576
- "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> }. " +
3577
- "Backs useUserPhoto(). The host performs the request AND updates its own session, so user.photoUrl changes for every " +
3578
- "widget. setPhoto takes a browser File on web and expo-file-system's File on native. Absent on a host that cannot " +
3579
- "change the photo (the Builder canvas preview), where useUserPhoto() degrades to canEdit: false rather than throwing.",
3608
+ "Optional self-service account actions; every member is optional too. " +
3609
+ "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> } back useUserPhoto() and are " +
3610
+ "present only while a user is signed in. The host performs the request AND updates its own session, so user.photoUrl " +
3611
+ "changes for every widget. setPhoto takes a browser File on web and expo-file-system's File on native. " +
3612
+ "anonymous backs useAnonymousAccount() and is present only where the workspace offers accounts without an email: " +
3613
+ "{ canCreate: boolean, create() -> Promise<{ created: true }> }. create() opens the HOST'S OWN dialog: " +
3614
+ "it asks the visitor to confirm, runs the captcha, calls the server, shows the login code and password once, and signs " +
3615
+ "the visitor in when they confirm they saved them. The pair lives only in that dialog and never reaches the slice, a " +
3616
+ "widget, an error or storage. " +
3617
+ "The whole slice is absent on the Builder canvas, in the preview panes and for a badge counter, where both hooks " +
3618
+ "degrade (canEdit: false, supported: false) rather than throwing.",
3580
3619
  required: false,
3581
- fields: { setPhoto: "function", removePhoto: "function" },
3620
+ fields: { setPhoto: "function", removePhoto: "function", anonymous: "object" },
3582
3621
  },
3583
3622
  workspace: {
3584
3623
  description:
@@ -5806,7 +5845,14 @@ const CONTRACT = deepFreeze({
5806
5845
  // new `ctx.filestore.signingRounds`. `useFileSignature` also returns
5807
5846
  // `hintCode` and `nameRefusal`, and its `cancel()` resolves to what
5808
5847
  // became of the order instead of always reporting "cancelled".
5809
- version: "1.175.0",
5848
+ // 1.176.0: additive (sc-10934) — `useAnonymousAccount()` asks the host to
5849
+ // create an account with a generated login code and password and no
5850
+ // email, through the new optional `ctx.account.anonymous`
5851
+ // (`{ canCreate, create }`). The host shows the pair in its own dialog;
5852
+ // a widget never receives it. `account`'s photo actions are now
5853
+ // optional members, and `user.email` is documented as nullable: an
5854
+ // account without an email has none.
5855
+ version: "1.176.0",
5810
5856
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5811
5857
  hooks: HOOKS,
5812
5858
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -391,15 +391,90 @@ export function useUserPhoto() {
391
391
  return { url, canEdit, upload, remove, uploading, error };
392
392
  }
393
393
 
394
+ const ANONYMOUS_ACCOUNT_UNSUPPORTED =
395
+ "Creating an account without an email is not available here";
396
+
397
+ /**
398
+ * Offer an account that needs no email address. The server generates a login
399
+ * code and a password; the person signs in later with that pair. Returns
400
+ * `{ supported, canCreate, creating, error, create }`.
401
+ *
402
+ * A widget only asks. `create()` takes no arguments and opens the app's own dialog
403
+ * (`ctx.account.anonymous`), which asks the visitor to confirm, runs the
404
+ * captcha, creates the account, shows the login code and password once and
405
+ * signs the visitor in when they confirm they saved them. The widget never
406
+ * receives the pair: `create()` resolves `{ created: true }`. It rejects with
407
+ * an Error carrying `code` — the server's refusal, `CAPTCHA_UNAVAILABLE`,
408
+ * `ALREADY_SIGNED_IN`, `CANCELLED` when the visitor closes the dialog, or
409
+ * `UNSUPPORTED`.
410
+ *
411
+ * Call `create()` from a user action (a button press), never on mount.
412
+ * `ctx.account.anonymous` is optional: where it is absent (the workspace has
413
+ * the feature off, the Builder canvas, a preview, a badge counter)
414
+ * `supported` is false and `create()` rejects instead of the hook throwing at
415
+ * render. `canCreate` is false while someone is signed in.
416
+ */
417
+ export function useAnonymousAccount() {
418
+ const ctx = useWidgetContextOrThrow("useAnonymousAccount");
419
+ const member = ctx.account ? ctx.account.anonymous : null;
420
+ const supported = Boolean(member) && typeof member.create === "function";
421
+
422
+ const hostRef = useRef(null);
423
+ hostRef.current = supported ? member : null;
424
+ const inFlightRef = useRef(null);
425
+ const [creating, setCreating] = useState(false);
426
+ const [error, setError] = useState(null);
427
+
428
+ const create = useCallback(() => {
429
+ if (inFlightRef.current) return inFlightRef.current;
430
+ const host = hostRef.current;
431
+ if (!host) {
432
+ const err = Object.assign(new Error(ANONYMOUS_ACCOUNT_UNSUPPORTED), {
433
+ code: "UNSUPPORTED",
434
+ });
435
+ setError(err);
436
+ return Promise.reject(err);
437
+ }
438
+ setCreating(true);
439
+ setError(null);
440
+ const attempt = Promise.resolve()
441
+ .then(() => host.create())
442
+ .then(
443
+ () => {
444
+ inFlightRef.current = null;
445
+ setCreating(false);
446
+ return { created: true };
447
+ },
448
+ (err) => {
449
+ inFlightRef.current = null;
450
+ setCreating(false);
451
+ setError(err);
452
+ throw err;
453
+ },
454
+ );
455
+ inFlightRef.current = attempt;
456
+ return attempt;
457
+ }, []);
458
+
459
+ return {
460
+ supported,
461
+ canCreate: supported && member.canCreate === true,
462
+ creating,
463
+ error,
464
+ create,
465
+ };
466
+ }
467
+
394
468
  /**
395
- * Returns `true` when the host has sized this widget to fill the available
396
- * height of its layout slot — today that means a page-grid tile whose author
397
- * chose "Fill tile height" (or a widget type that fills by default, like
398
- * containers and media). When `true`, a widget that has a meaningful filled
399
- * form (Image, Chart, Map, Video, …) should switch from its intrinsic height
400
- * to a stretch style (`flex: 1` / `height: "100%"`); widgets with no useful
401
- * filled form may ignore it. Defaults to `false` everywhere the host has not
402
- * opted the widget into filling, so calling it is always safe.
469
+ * Returns `true` when the host has sized this widget to fill a box of
470
+ * definite height — a widget in the section a fit-to-screen page stretches to
471
+ * the bottom of the screen, or a widget given a fixed slot height. When
472
+ * `true`, a widget that has a meaningful filled form (Image, Chart, Map,
473
+ * Video, a chat or feed that scrolls its own content, …) should switch from
474
+ * its intrinsic height to a stretch style (`flex: 1` / `height: "100%"`) and
475
+ * scroll inside it; widgets with no useful filled form may ignore it.
476
+ * Defaults to `false` everywhere the host has not opted the widget into
477
+ * filling, so calling it is always safe.
403
478
  *
404
479
  * The SAME value is injected by the web Player host and the native export
405
480
  * host (widget-parity skill), so a widget's fill behaviour is identical on both
package/dist/index.d.ts CHANGED
@@ -728,8 +728,8 @@ export interface WidgetContext<TProps = unknown> {
728
728
  };
729
729
  /**
730
730
  * REQ-LAY-08 — optional host layout hint backing `useFill()`. `true` when
731
- * the host sized this widget to fill its layout slot's available height (a
732
- * page-grid tile set to "Fill tile height", or a default-fill widget type).
731
+ * the host sized this widget to fill a box of definite height (a
732
+ * fit-to-screen page's stretched section, or a fixed slot height).
733
733
  * Absent / `false` everywhere the host has not opted the widget into filling.
734
734
  */
735
735
  fill?: boolean;
@@ -744,14 +744,18 @@ export interface WidgetContext<TProps = unknown> {
744
744
  groupIds: string[];
745
745
  };
746
746
  /**
747
- * Optional self-service account actions backing `useUserPhoto()`. The host
748
- * performs the request and updates its own session, so `user.photoUrl`
749
- * changes for every widget. Absent where the photo cannot be changed (the
750
- * Builder canvas preview); `useUserPhoto().canEdit` is then false.
747
+ * Optional self-service account actions; every member is optional too.
748
+ * `setPhoto` / `removePhoto` back `useUserPhoto()` and are present only
749
+ * while a user is signed in: the host performs the request and updates its
750
+ * own session, so `user.photoUrl` changes for every widget. `anonymous`
751
+ * backs `useAnonymousAccount()` and is present only where the workspace
752
+ * offers accounts without an email. The whole slice is absent on the
753
+ * Builder canvas, in the preview panes and for a badge counter.
751
754
  */
752
755
  account?: {
753
- setPhoto(file: UserPhotoFile): Promise<{ photoUrl: string | null }>;
754
- removePhoto(): Promise<{ photoUrl: null }>;
756
+ setPhoto?(file: UserPhotoFile): Promise<{ photoUrl: string | null }>;
757
+ removePhoto?(): Promise<{ photoUrl: null }>;
758
+ anonymous?: AnonymousAccountHost;
755
759
  };
756
760
  workspace: {
757
761
  id: string;
@@ -1947,12 +1951,67 @@ export interface UserPhotoApi {
1947
1951
  */
1948
1952
  export function useUserPhoto(): UserPhotoApi;
1949
1953
 
1954
+ /** A `create()` failure. `code` names the refusal. */
1955
+ export type AnonymousAccountError = Error & {
1956
+ /**
1957
+ * The server's refusal (`ANONYMOUS_ACCOUNT_LIMIT_REACHED`, `RATE_LIMITED`,
1958
+ * `CAPTCHA_FAILED`, …), or `CAPTCHA_UNAVAILABLE`
1959
+ * (the captcha could not run), `ALREADY_SIGNED_IN`, `CANCELLED` (the
1960
+ * visitor closed the dialog) or `UNSUPPORTED`. Absent when the failure has
1961
+ * no code.
1962
+ */
1963
+ code?: string;
1964
+ /** The HTTP status of a server refusal; 0 when there was no response. */
1965
+ status?: number;
1966
+ };
1967
+
1968
+ /** `WidgetContext.account.anonymous`: what a host provides for `useAnonymousAccount()`. */
1969
+ export interface AnonymousAccountHost {
1970
+ /** True only while nobody is signed in and no two-factor step is pending. */
1971
+ canCreate: boolean;
1972
+ /**
1973
+ * Opens the host's own dialog, which confirms, creates the account, shows
1974
+ * the login code and password once and signs the visitor in. The pair
1975
+ * never leaves that dialog.
1976
+ */
1977
+ create(): Promise<{ created: true }>;
1978
+ }
1979
+
1980
+ export interface AnonymousAccountApi {
1981
+ /** False where the host does not offer accounts without an email. */
1982
+ supported: boolean;
1983
+ /** True when `supported` and nobody is signed in. */
1984
+ canCreate: boolean;
1985
+ /** True while a `create()` call is in flight: the app's dialog is open. */
1986
+ creating: boolean;
1987
+ /** The last `create()` failure; cleared by the next attempt. */
1988
+ error: AnonymousAccountError | null;
1989
+ /**
1990
+ * Ask the app to create the account. Call it from a user action. The app
1991
+ * shows the login code and password itself; the widget never receives
1992
+ * them. Rejects with an `AnonymousAccountError`.
1993
+ */
1994
+ create(): Promise<{ created: true }>;
1995
+ }
1996
+
1997
+ /**
1998
+ * Offer an account that needs no email address: the server generates a
1999
+ * login code and a password, and the person signs in later with that pair.
2000
+ * The app confirms, shows the pair once and signs the visitor in, all in its
2001
+ * own dialog. Never throws at render: where the host does not offer it (the
2002
+ * workspace has it off, the Builder canvas, a preview, a badge counter)
2003
+ * `supported` is false and `create()` rejects with `code: "UNSUPPORTED"`.
2004
+ */
2005
+ export function useAnonymousAccount(): AnonymousAccountApi;
2006
+
1950
2007
  /**
1951
- * REQ-LAY-08 — returns `true` when the host has sized this widget to fill its
1952
- * layout slot's available height (a page-grid tile set to "Fill tile height",
1953
- * or a default-fill widget type — containers + media). Widgets that can
1954
- * stretch (Image, Chart, Map, Video, …) should switch to a fill style
1955
- * (`flex: 1` / `height: "100%"`) when this is `true`; others may ignore it.
2008
+ * REQ-LAY-08 — returns `true` when the host has sized this widget to fill a
2009
+ * box of definite height (a widget in the section a fit-to-screen page
2010
+ * stretches to the bottom of the screen, or one given a fixed slot height).
2011
+ * Widgets that can stretch (Image, Chart, Map, Video, a chat or feed that
2012
+ * scrolls itself, …) should switch to a fill style (`flex: 1` /
2013
+ * `height: "100%"`) and scroll inside it when this is `true`; others may
2014
+ * ignore it.
1956
2015
  * Defaults to `false`, so calling it is always safe, and the same value is
1957
2016
  * injected on web and native so fill behaviour is identical on both platforms.
1958
2017
  */
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ export {
78
78
  LinkPreviewError,
79
79
  useUser,
80
80
  useUserPhoto,
81
+ useAnonymousAccount,
81
82
  useFill,
82
83
  useNavigation,
83
84
  useRouteParams,
@@ -78,6 +78,7 @@ export {
78
78
  LinkPreviewError,
79
79
  useUser,
80
80
  useUserPhoto,
81
+ useAnonymousAccount,
81
82
  useFill,
82
83
  useNavigation,
83
84
  useRouteParams,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.219.0",
3
+ "version": "0.220.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",
package/src/contract.js CHANGED
@@ -938,17 +938,50 @@ const HOOKS = [
938
938
  requiredContextSlice: ["user", "account.setPhoto", "account.removePhoto"],
939
939
  scopes: null,
940
940
  },
941
+ {
942
+ name: "useAnonymousAccount",
943
+ signature: "useAnonymousAccount()",
944
+ description:
945
+ "Offer an account that needs NO email address: the server generates a login code and a password, and the person signs in " +
946
+ "later with that pair on the app's sign-in screen. For apps where a visitor must be able to act without identifying " +
947
+ "themselves and still come back (a whistleblower report). The widget ONLY ASKS: create() takes NO arguments and opens the APP'S " +
948
+ "OWN dialog, which asks the visitor to confirm, runs the captcha, creates the account, shows the login code and password " +
949
+ "ONCE with the warning that a lost password cannot be recovered, and signs the visitor in when they confirm they saved " +
950
+ "them (or opens the sign-in screen's two-factor step when the workspace requires one). The widget NEVER receives the " +
951
+ "login code or the password: create() resolves { created: true } and nothing else. Do NOT build your own confirmation, " +
952
+ "captcha notice or credentials screen, and never call an endpoint yourself. Call create() from a user action (a button's " +
953
+ "onPress), never on mount or in an effect. It rejects with an Error carrying code: the server's refusal " +
954
+ "(ANONYMOUS_ACCOUNT_LIMIT_REACHED, RATE_LIMITED, CAPTCHA_FAILED, …), CAPTCHA_UNAVAILABLE when the " +
955
+ "captcha could not run (there is no fallback), ALREADY_SIGNED_IN, CANCELLED when the visitor closes the dialog, or " +
956
+ "UNSUPPORTED. The dialog already shows a failure and lets the visitor retry, so a rejection means they gave up: treat " +
957
+ "CANCELLED as no error. supported is false — and create() rejects with UNSUPPORTED instead of the hook throwing at " +
958
+ "render — where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview pane, in a draft " +
959
+ "preview and for a badge counter: render a visible 'not available' state then. canCreate is false while someone is " +
960
+ "signed in: hide the button then. After create() resolves the visitor is signed in and the page re-renders, so read " +
961
+ "useUser() for the new state. Needs no scope. Identical on web (Player) and the Expo export.",
962
+ returnShape: {
963
+ supported: "boolean // the host offers anonymous accounts here",
964
+ canCreate: "boolean // supported AND nobody is signed in",
965
+ creating: "boolean // a create() call is in flight: the app's dialog is open",
966
+ error: "(Error & { code?: string, status?: number }) | null // the last create() failure; cleared by the next attempt",
967
+ create:
968
+ "() => Promise<{ created: true }> // takes no arguments; opens the app's dialog; never returns the login code or password",
969
+ },
970
+ requiredContextSlice: ["account.anonymous"],
971
+ scopes: null,
972
+ },
941
973
  {
942
974
  name: "useFill",
943
975
  signature: "useFill()",
944
976
  description:
945
- "Returns true when the host has sized this widget to fill its layout " +
946
- 'slot\'s available height — a page-grid tile whose author chose "Fill ' +
947
- 'tile height", or a widget type that fills by default (containers and ' +
948
- "media). When true, a widget that has a meaningful filled form (Image, " +
949
- "Chart, Map, Video) should switch from its intrinsic height to a stretch " +
950
- 'style (flex: 1 / height: "100%"); widgets with no useful filled form ' +
951
- "may ignore it. Defaults to false wherever the host has not opted the " +
977
+ "Returns true when the host has sized this widget to fill a box of " +
978
+ "definite height — a widget in the section a fit-to-screen page " +
979
+ "stretches to the bottom of the screen, or a widget given a fixed slot " +
980
+ "height. When true, a widget that has a meaningful filled form (Image, " +
981
+ "Chart, Map, Video, a chat or feed that scrolls its own content) should " +
982
+ "switch from its intrinsic height to a stretch style (flex: 1 / " +
983
+ 'height: "100%") and scroll inside it; widgets with no useful filled ' +
984
+ "form may ignore it. Defaults to false wherever the host has not opted the " +
952
985
  "widget into filling, so calling it is always safe. The SAME value is " +
953
986
  "injected by the web Player and the native export (widget-parity skill), so a " +
954
987
  "widget's fill behaviour is identical on both platforms.",
@@ -3545,40 +3578,46 @@ const WIDGET_CONTEXT_SHAPE = {
3545
3578
  },
3546
3579
  },
3547
3580
  // REQ-LAY-08 — optional host layout hint backing useFill(). True when the
3548
- // host sized this widget to fill its layout slot's available height (a
3549
- // page-grid tile set to "Fill tile height", or a default-fill widget type).
3581
+ // host sized this widget to fill a box of definite height (a fit-to-screen
3582
+ // page's stretched section, or a fixed slot height).
3550
3583
  // Optional: a host that never fills a widget simply omits it and useFill()
3551
3584
  // returns false, so existing hosts need no change.
3552
3585
  fill: {
3553
3586
  description:
3554
3587
  "Optional host layout hint (boolean). True when the host sized this " +
3555
- "widget to fill its layout slot's available height (page-grid tile " +
3556
- "fill). Backs useFill(); defaults to false when absent.",
3588
+ "widget to fill a box of definite height (a fit-to-screen page's " +
3589
+ "stretched section, or a fixed slot height). Backs useFill(); defaults " +
3590
+ "to false when absent.",
3557
3591
  required: false,
3558
3592
  fields: {},
3559
3593
  },
3560
3594
  user: {
3561
3595
  description:
3562
- "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. Not a data-client.",
3596
+ "Signed-in user, host-built context object (camelCase: { id, email, displayName, photoUrl, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. photoUrl is the user's own profile photo as an absolute URL, or null. email is null for an account created without one. Not a data-client.",
3563
3597
  required: true,
3564
3598
  fields: {
3565
3599
  id: "string",
3566
- email: "string",
3600
+ email: "string | null",
3567
3601
  displayName: "string",
3568
3602
  photoUrl: "string | null",
3569
3603
  },
3570
3604
  },
3571
- // Optional so a host that cannot change the photo needs no stub:
3572
- // useUserPhoto() reports canEdit false when it is absent.
3605
+ // Optional, and so is every member: a host publishes only what it can do.
3573
3606
  account: {
3574
3607
  description:
3575
- "Optional self-service account actions for the signed-in user: " +
3576
- "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> }. " +
3577
- "Backs useUserPhoto(). The host performs the request AND updates its own session, so user.photoUrl changes for every " +
3578
- "widget. setPhoto takes a browser File on web and expo-file-system's File on native. Absent on a host that cannot " +
3579
- "change the photo (the Builder canvas preview), where useUserPhoto() degrades to canEdit: false rather than throwing.",
3608
+ "Optional self-service account actions; every member is optional too. " +
3609
+ "{ setPhoto(file) -> Promise<{ photoUrl }>, removePhoto() -> Promise<{ photoUrl: null }> } back useUserPhoto() and are " +
3610
+ "present only while a user is signed in. The host performs the request AND updates its own session, so user.photoUrl " +
3611
+ "changes for every widget. setPhoto takes a browser File on web and expo-file-system's File on native. " +
3612
+ "anonymous backs useAnonymousAccount() and is present only where the workspace offers accounts without an email: " +
3613
+ "{ canCreate: boolean, create() -> Promise<{ created: true }> }. create() opens the HOST'S OWN dialog: " +
3614
+ "it asks the visitor to confirm, runs the captcha, calls the server, shows the login code and password once, and signs " +
3615
+ "the visitor in when they confirm they saved them. The pair lives only in that dialog and never reaches the slice, a " +
3616
+ "widget, an error or storage. " +
3617
+ "The whole slice is absent on the Builder canvas, in the preview panes and for a badge counter, where both hooks " +
3618
+ "degrade (canEdit: false, supported: false) rather than throwing.",
3580
3619
  required: false,
3581
- fields: { setPhoto: "function", removePhoto: "function" },
3620
+ fields: { setPhoto: "function", removePhoto: "function", anonymous: "object" },
3582
3621
  },
3583
3622
  workspace: {
3584
3623
  description:
@@ -5806,7 +5845,14 @@ const CONTRACT = deepFreeze({
5806
5845
  // new `ctx.filestore.signingRounds`. `useFileSignature` also returns
5807
5846
  // `hintCode` and `nameRefusal`, and its `cancel()` resolves to what
5808
5847
  // became of the order instead of always reporting "cancelled".
5809
- version: "1.175.0",
5848
+ // 1.176.0: additive (sc-10934) — `useAnonymousAccount()` asks the host to
5849
+ // create an account with a generated login code and password and no
5850
+ // email, through the new optional `ctx.account.anonymous`
5851
+ // (`{ canCreate, create }`). The host shows the pair in its own dialog;
5852
+ // a widget never receives it. `account`'s photo actions are now
5853
+ // optional members, and `user.email` is documented as nullable: an
5854
+ // account without an email has none.
5855
+ version: "1.176.0",
5810
5856
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5811
5857
  hooks: HOOKS,
5812
5858
  primitives: PRIMITIVES,