@colixsystems/widget-sdk 0.241.0 → 0.244.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
@@ -24,7 +24,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
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
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. |
27
- | **CORE** | `useNavigation()` | `{ goTo, goToStartPage, scrollToSection, 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; `goToStartPage()` opens the signed-in user's own start page; `scrollToSection(id)` scrolls this page to a section picked with a `sectionRef` prop (`openLink` for a link of unknown shape; a known external URL can also use the `Linking` primitive) |
27
+ | **CORE** | `useNavigation()` | `{ goTo, goToStartPage, scrollToSection, scrollIntoView, 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; `goToStartPage()` opens the signed-in user's own start page; `scrollToSection(id)` scrolls this page to a section picked with a `sectionRef` prop; `scrollIntoView(ref)` scrolls it to an element inside your widget (a heading your table of contents jumps to) (`openLink` for a link of unknown shape; a known external URL can also use the `Linking` primitive) |
28
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. |
29
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). |
30
30
  | **CORE** | `useRouteParams()` | `{ [paramKey]: value }` | `ctx.navigation.currentRoute.params` — no scope. The nav params the previous page passed via `goTo(pageId, params)`; the flat accessor for master→detail (read `recordId` on a detail page). Empty object when none. |
@@ -84,7 +84,59 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
84
84
 
85
85
  ## Status
86
86
 
87
- `v0.241.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.244.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.244.0 (contract 1.198.0)
90
+
91
+ **A widget can scroll its page to an element inside itself — `useNavigation().scrollIntoView(ref)`.** A long article, FAQ or handbook widget can show a table of contents whose entries jump to its own headings. The page itself scrolls, so the widget keeps its full height and never needs a scroll box of its own. Attach a ref to the element and pass the ref:
92
+
93
+ ```jsx
94
+ import { useRef } from "react";
95
+ import { useNavigation, Pressable, Text, View } from "@colixsystems/widget-sdk";
96
+
97
+ export default function Handbook({ sections }) {
98
+ const { scrollIntoView } = useNavigation();
99
+ const headings = useRef({});
100
+ return (
101
+ <View>
102
+ {sections.map((s) => (
103
+ <Pressable key={s.id} onPress={() => scrollIntoView({ current: headings.current[s.id] })}>
104
+ <Text>{s.title}</Text>
105
+ </Pressable>
106
+ ))}
107
+ {sections.map((s) => (
108
+ <View key={s.id} ref={(el) => { headings.current[s.id] = el; }}>
109
+ <Text>{s.title}</Text>
110
+ <Text>{s.body}</Text>
111
+ </View>
112
+ ))}
113
+ </View>
114
+ );
115
+ }
116
+ ```
117
+
118
+ Both hosts use the same scroll as `scrollToSection`: smooth (instant under reduced motion), stopping with the element just below a pinned site header, on a normal page and on a Fit to screen page. A ref that is not mounted does nothing, and so does the Studio canvas. To jump to another part of the PAGE, keep using `scrollToSection` with a `sectionRef` prop. Additive.
119
+
120
+ ### What's new in 0.243.0 (contract 1.197.0)
121
+
122
+ **`bankIdAppUrl(autoStartToken)` opens BankID on this device** (from 0.243.0). The link a signing or identification widget opens from its "Open BankID on this device" button: `Linking.openURL(bankIdAppUrl(autoStartToken))`. It is the same link the built-in Files and Identification widgets and the signing page use. For BankID on another device, show the order's `qr` with `<Image>`.
123
+
124
+ `useFileSignature` does not poll by itself: while `status` is `"pending"`, call `refresh()` every 2 seconds, and render each answer's `qr`. It signs a file of any type in a shared space; outside a signing round the signature is kept beside the file, and in a round the signature is added to the PDF, which is then sealed. A `verify()` verdict reads as: `valid` (the BankID signature), `content_status` (`unchanged`, `changed` or `unavailable`), `signed_by_others_since` (later signatures in a round) and `seal.status` (`valid`, `invalid`, `certificate_revoked`, `unavailable`, or `not_applicable` for a signature kept beside the file). `useFileSignatures` is described as what it is: the signed-in user's own signatures.
125
+
126
+ ```jsx
127
+ import { Button, Image, Linking, bankIdAppUrl, useFileSignature } from "@colixsystems/widget-sdk";
128
+
129
+ const { status, qr, autoStartToken, initiate, refresh } = useFileSignature(fileId);
130
+ useEffect(() => {
131
+ if (status !== "pending") return undefined;
132
+ const timer = setInterval(refresh, 2000);
133
+ return () => clearInterval(timer);
134
+ }, [status, refresh]);
135
+ // bankIdAppUrl exists from 0.243.0; a widget that must load on an older SDK
136
+ // reads it as sdk.bankIdAppUrl from `import * as sdk` and builds the same link itself.
137
+ // autoStartToken && <Button title="Open BankID on this device" onPress={() => Linking.openURL(bankIdAppUrl(autoStartToken))} />
138
+ // qr && <Image source={{ uri: qr }} style={{ width: 200, height: 200 }} />
139
+ ```
88
140
 
89
141
  ### What's new in 0.241.0 (contract 1.195.0)
90
142
 
@@ -2964,7 +3016,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
2964
3016
 
2965
3017
  - `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
2966
3018
  - `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
2967
- - `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `useWidgetInput`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useRouteParams`, `usePageContext`, `useWidgetRoute`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError` carrying `code`, the server's user-safe `message`, and `retryable` (`false` = this charge cannot succeed until the workspace, manifest, or amount changes — show the message, not a retry). `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goToStartPage, scrollToSection, goBack, push, replace, back, currentRoute, openLink }` for internal page navigation; `goToStartPage()` opens the signed-in user's own start page (the page the app opens on for them); `scrollToSection(sectionId)` scrolls the current page to one of its sections (bind the id with a `sectionRef` prop); `openLink(link)` follows an author- or data-supplied link of unknown shape through the host's shared resolver (in-app page → internal route, off-app http(s) → opened outside, unsafe → refused) and is the safe choice for any value your code did not construct, while `Linking.openURL(url)` is for an external URL you built yourself. `useRouteParams()` returns the current route's params object (`currentRoute.params`) — the flat master→detail accessor; read a param off it (e.g. `recordId`), never call it. `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case). `useWidgetInput(inputName)` returns the latest payload a sibling widget published on the event the page author wired to this widget's declared `inputs` entry (`undefined` when unwired or not yet published).
3019
+ - `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `useWidgetInput`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useRouteParams`, `usePageContext`, `useWidgetRoute`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError` carrying `code`, the server's user-safe `message`, and `retryable` (`false` = this charge cannot succeed until the workspace, manifest, or amount changes — show the message, not a retry). `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goToStartPage, scrollToSection, scrollIntoView, goBack, push, replace, back, currentRoute, openLink }` for internal page navigation; `goToStartPage()` opens the signed-in user's own start page (the page the app opens on for them); `scrollToSection(sectionId)` scrolls the current page to one of its sections (bind the id with a `sectionRef` prop); `scrollIntoView(ref)` scrolls it to an element inside the calling widget; `openLink(link)` follows an author- or data-supplied link of unknown shape through the host's shared resolver (in-app page → internal route, off-app http(s) → opened outside, unsafe → refused) and is the safe choice for any value your code did not construct, while `Linking.openURL(url)` is for an external URL you built yourself. `useRouteParams()` returns the current route's params object (`currentRoute.params`) — the flat master→detail accessor; read a param off it (e.g. `recordId`), never call it. `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case). `useWidgetInput(inputName)` returns the latest payload a sibling widget published on the event the page author wired to this widget's declared `inputs` entry (`undefined` when unwired or not yet published).
2968
3020
  - `WidgetTree({ node })` — component that renders an author-authored child node through the host's renderer; used by Tabs / Card / custom containers to host arbitrary child widgets.
2969
3021
  - `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet`, `Linking`, `Icon`, `DateTimePicker` — re-exported from `react-native` (the RN primitives) or implemented in the SDK (`Icon` wraps `lucide-react-native`; `DateTimePicker` wraps `@react-native-community/datetimepicker` on native and renders `<input type="date|time|datetime-local">` directly on web because the RN library has no react-native-web mapping). The web build aliases `react-native` to `react-native-web` so the RN-re-exported primitives render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. `Linking` is a static API (`Linking.openURL(url)`) — use it for external URLs, and use `useNavigation().goTo(pageId)` for internal page navigation. See https://reactnative.dev/docs/ for per-component props.
2970
3022
  - `Overlay` — the screen-level overlay (sc-6607). `<Overlay visible={!!preview} onRequestClose={() => setPreview(null)} size="full">…</Overlay>` renders its children OUTSIDE the widget's layout box on both hosts (web portals it to the document root, native uses the OS modal), so no clipping card, `ScrollView` or neighbouring widget can cut off a document/media preview, lightbox or confirm dialog — which absolute positioning inside the widget cannot achieve on either platform. Children stay in your own React tree, so state and hooks work normally. `onRequestClose` carries the backdrop press, Escape on web, and the Android back button; `size` is `sm` | `md` (default) | `lg` | `full`; the scrim, surface, radius, padding and elevation come from the workspace theme. An anchored dropdown still belongs inside your own root.
@@ -0,0 +1,6 @@
1
+ // The one BankID app launch link: widgets, the Player's login and the signing
2
+ // page. Imports nothing, so every host and the exported app share it as it is.
3
+
4
+ /** The URL that opens the BankID app on this device for the order of `autoStartToken`. */
5
+ export const bankIdAppUrl = (autoStartToken) =>
6
+ `bankid:///?autostarttoken=${encodeURIComponent(autoStartToken)}&redirect=null`;
package/dist/contract.cjs CHANGED
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -1624,10 +1626,30 @@ const HOOKS = [
1624
1626
  signature: "useFileSignature(fileId, existingSignatureId)",
1625
1627
  optionalArgs: ["existingSignatureId"],
1626
1628
  description:
1627
- "Drive a BankID signing flow for one Filestore file. initiate() opens a " +
1628
- "sign order (the backend hashes the bytes + binds the digest), refresh() " +
1629
- "polls (fresh QR while pending — render with the Image primitive), " +
1630
- "cancel() aborts, verify() returns the offline verdict. Pass " +
1629
+ "Drive a BankID signing flow for one Filestore file of ANY type in a " +
1630
+ "shared (project or public) space; a personal file is refused with " +
1631
+ "NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
1632
+ "the file and the file is not changed; a PDF in an open signing round " +
1633
+ "gets the signature added to the document itself, which is then " +
1634
+ "sealed against changes (see useSigningRounds). initiate() opens a " +
1635
+ "sign order (the backend hashes the bytes + binds the digest). The " +
1636
+ "hook does NOT poll by itself: while status is 'pending' call " +
1637
+ "refresh() every 2 seconds (a setInterval in an effect, cleared when " +
1638
+ "status leaves 'pending' or the widget unmounts); each answer carries " +
1639
+ "a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
1640
+ "button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
1641
+ "shown only while autoStartToken is set; BankID on ANOTHER device is " +
1642
+ "the qr rendered with the Image primitive, replaced by each poll's " +
1643
+ "frame. cancel() aborts, verify() returns the offline verdict: valid " +
1644
+ "is the BankID signature itself; content_status 'unchanged' means the " +
1645
+ "file still holds what was signed, 'changed' that it was altered " +
1646
+ "since, 'unavailable' that it could not be checked now; " +
1647
+ "signed_by_others_since counts later signatures in a round's PDF; " +
1648
+ "seal.status is 'valid' (protected against changes), 'invalid' or " +
1649
+ "'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
1650
+ "later) or 'not_applicable' (a signature kept beside the file). Say " +
1651
+ "each in plain words and never show a green mark when any of them " +
1652
+ "failed. Pass " +
1631
1653
  "existingSignatureId for an already-signed file to seed the complete " +
1632
1654
  "state + verify() without re-signing. autoStartToken is set only while " +
1633
1655
  "opening BankID can still help (null once the user has signed or the " +
@@ -1644,19 +1666,21 @@ const HOOKS = [
1644
1666
  "ctx.filestore.signatures.{initiate,status,cancel,verify}.",
1645
1667
  returnShape: {
1646
1668
  status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
1647
- qr: "string | null // PNG data-URL of the animated BankID QR",
1648
- autoStartToken: "string | null",
1669
+ qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
1670
+ autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
1649
1671
  message: "string | null",
1650
1672
  signerName: "string | null",
1651
1673
  signedAt: "string | null",
1652
1674
  hintCode: "string | null // where the order stands, e.g. 'sealing'",
1653
1675
  nameRefusal:
1654
1676
  "{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
1655
- verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1677
+ verdict:
1678
+ "{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
1679
+ "seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
1656
1680
  loading: "boolean",
1657
1681
  error: "PermissionError | null // { code, message, retryable }",
1658
1682
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
1659
- refresh: "() => Promise<void>",
1683
+ refresh: "() => Promise<void> // the hook never polls by itself",
1660
1684
  cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
1661
1685
  verify: "() => Promise<verdict>",
1662
1686
  },
@@ -1667,10 +1691,14 @@ const HOOKS = [
1667
1691
  name: "useFileSignatures",
1668
1692
  signature: "useFileSignatures(fileIds)",
1669
1693
  description:
1670
- "Batch 'is this file signed?' lookup for a set of file ids. Returns a map " +
1671
- "of each signed, accessible file id to its latest signature; unsigned " +
1672
- "files are absent. Lets a file browser mark already-signed files in one " +
1673
- "request. Reads ctx.filestore.signatures.list and unwraps { data, meta }.",
1694
+ "Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
1695
+ "a map of each file the SIGNED-IN USER has signed (and can still see) " +
1696
+ "to their own latest signature; a file only others have signed, or " +
1697
+ "nobody has, is absent, so it never tells who else signed. Pass a " +
1698
+ "file's signature_id to useFileSignature(fileId, signatureId) to show " +
1699
+ "and verify it. Who has signed is useFileRoster(), for someone who " +
1700
+ "manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
1701
+ "{ data, meta }.",
1674
1702
  returnShape: {
1675
1703
  signaturesByFileId:
1676
1704
  "{ [fileId]: { signature_id, signer_name, signed_at } }",
@@ -1709,8 +1737,10 @@ const HOOKS = [
1709
1737
  signature: "useSigningRounds(fileIds)",
1710
1738
  description:
1711
1739
  "What the signed-in user may know about the signing round of each file " +
1712
- "in a list, in one batched request. A signing round fixes a PDF and a " +
1713
- "list of signers: it is prepared and confirmed by someone who manages " +
1740
+ "in a list, in one batched request. A signing round fixes a PDF in the " +
1741
+ "project space and a list of signers (other file types are signed one " +
1742
+ "person at a time with useFileSignature() and no round): it is " +
1743
+ "prepared and confirmed by someone who manages " +
1714
1744
  "the file's folder, then each signer signs once with useFileSignature(), " +
1715
1745
  "in any order (there are no turns; the list only orders the places on " +
1716
1746
  "the signature page). byFileId maps a file id to { file_id, can_manage, " +
@@ -1719,7 +1749,8 @@ const HOOKS = [
1719
1749
  "manager nor one of its signers; a signer sees it once it is confirmed, " +
1720
1750
  "with their own place (my_place) and can_sign (the round is open and " +
1721
1751
  "their place is unsigned), and signed_count is null " +
1722
- "for them. locked is 'signing_round' while the file cannot be changed, " +
1752
+ "for them. Each signature is added to the PDF itself and the document " +
1753
+ "is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
1723
1754
  "'signed' once its content cannot be replaced. roundsEnabled is false " +
1724
1755
  "when the workspace cannot start signing rounds (offer no way to start " +
1725
1756
  "one), and null until the first answer. can_prepare is about the file " +
@@ -1790,7 +1821,11 @@ const HOOKS = [
1790
1821
  "document changed) the hook reads the round again by itself. Code " +
1791
1822
  "SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
1792
1823
  "rounds; check roundsEnabled from useSigningRounds() before offering to " +
1793
- "start one. When fileId or the signed-in user changes, round is null " +
1824
+ "start one, and when it is false say that document signing is not " +
1825
+ "turned on for this workspace and that the platform turns it on. " +
1826
+ "Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
1827
+ "signer signs their place with useFileSignature(fileId), the same " +
1828
+ "BankID flow as any file. When fileId or the signed-in user changes, round is null " +
1794
1829
  "and loading true until the new answer. Reads ctx.filestore.signingRounds.",
1795
1830
  returnShape: {
1796
1831
  round:
@@ -2619,8 +2654,9 @@ const HOOKS = [
2619
2654
  "with the Image primitive) and the hook then POLLS to completion itself " +
2620
2655
  "(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
2621
2656
  "cancel() aborts; reset() clears the flow. autoStartToken opens the " +
2622
- "provider app on the SAME device (bankid:///?autostarttoken=<token>" +
2623
- "&redirect=null). On complete, `identity` is { provider, name, given_name, " +
2657
+ "provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
2658
+ "autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
2659
+ "On complete, `identity` is { provider, name, given_name, " +
2624
2660
  "surname, personal_number_masked, subject_hash, identified_at } — there is " +
2625
2661
  "NO raw personal number by design: write personal_number_masked " +
2626
2662
  "(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
@@ -6117,7 +6153,13 @@ const CONTRACT = deepFreeze({
6117
6153
  // `{ name, email }` entries for signers without an account, and
6118
6154
  // `resendLink(signerId, { email })` sends such a signer a new link.
6119
6155
  // The manager's places carry id, kind, email and link.
6120
- version: "1.195.0",
6156
+ // 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
6157
+ // the link that opens BankID on this device. Signing hooks document
6158
+ // polling, the two ways to start BankID and how to read a verdict;
6159
+ // useFileSignatures is described as the caller's own signatures.
6160
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6161
+ // scrolls the page to an element inside the calling widget on both hosts.
6162
+ version: "1.198.0",
6121
6163
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6122
6164
  hooks: HOOKS,
6123
6165
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -1624,10 +1626,30 @@ const HOOKS = [
1624
1626
  signature: "useFileSignature(fileId, existingSignatureId)",
1625
1627
  optionalArgs: ["existingSignatureId"],
1626
1628
  description:
1627
- "Drive a BankID signing flow for one Filestore file. initiate() opens a " +
1628
- "sign order (the backend hashes the bytes + binds the digest), refresh() " +
1629
- "polls (fresh QR while pending — render with the Image primitive), " +
1630
- "cancel() aborts, verify() returns the offline verdict. Pass " +
1629
+ "Drive a BankID signing flow for one Filestore file of ANY type in a " +
1630
+ "shared (project or public) space; a personal file is refused with " +
1631
+ "NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
1632
+ "the file and the file is not changed; a PDF in an open signing round " +
1633
+ "gets the signature added to the document itself, which is then " +
1634
+ "sealed against changes (see useSigningRounds). initiate() opens a " +
1635
+ "sign order (the backend hashes the bytes + binds the digest). The " +
1636
+ "hook does NOT poll by itself: while status is 'pending' call " +
1637
+ "refresh() every 2 seconds (a setInterval in an effect, cleared when " +
1638
+ "status leaves 'pending' or the widget unmounts); each answer carries " +
1639
+ "a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
1640
+ "button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
1641
+ "shown only while autoStartToken is set; BankID on ANOTHER device is " +
1642
+ "the qr rendered with the Image primitive, replaced by each poll's " +
1643
+ "frame. cancel() aborts, verify() returns the offline verdict: valid " +
1644
+ "is the BankID signature itself; content_status 'unchanged' means the " +
1645
+ "file still holds what was signed, 'changed' that it was altered " +
1646
+ "since, 'unavailable' that it could not be checked now; " +
1647
+ "signed_by_others_since counts later signatures in a round's PDF; " +
1648
+ "seal.status is 'valid' (protected against changes), 'invalid' or " +
1649
+ "'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
1650
+ "later) or 'not_applicable' (a signature kept beside the file). Say " +
1651
+ "each in plain words and never show a green mark when any of them " +
1652
+ "failed. Pass " +
1631
1653
  "existingSignatureId for an already-signed file to seed the complete " +
1632
1654
  "state + verify() without re-signing. autoStartToken is set only while " +
1633
1655
  "opening BankID can still help (null once the user has signed or the " +
@@ -1644,19 +1666,21 @@ const HOOKS = [
1644
1666
  "ctx.filestore.signatures.{initiate,status,cancel,verify}.",
1645
1667
  returnShape: {
1646
1668
  status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
1647
- qr: "string | null // PNG data-URL of the animated BankID QR",
1648
- autoStartToken: "string | null",
1669
+ qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
1670
+ autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
1649
1671
  message: "string | null",
1650
1672
  signerName: "string | null",
1651
1673
  signedAt: "string | null",
1652
1674
  hintCode: "string | null // where the order stands, e.g. 'sealing'",
1653
1675
  nameRefusal:
1654
1676
  "{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
1655
- verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1677
+ verdict:
1678
+ "{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
1679
+ "seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
1656
1680
  loading: "boolean",
1657
1681
  error: "PermissionError | null // { code, message, retryable }",
1658
1682
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
1659
- refresh: "() => Promise<void>",
1683
+ refresh: "() => Promise<void> // the hook never polls by itself",
1660
1684
  cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
1661
1685
  verify: "() => Promise<verdict>",
1662
1686
  },
@@ -1667,10 +1691,14 @@ const HOOKS = [
1667
1691
  name: "useFileSignatures",
1668
1692
  signature: "useFileSignatures(fileIds)",
1669
1693
  description:
1670
- "Batch 'is this file signed?' lookup for a set of file ids. Returns a map " +
1671
- "of each signed, accessible file id to its latest signature; unsigned " +
1672
- "files are absent. Lets a file browser mark already-signed files in one " +
1673
- "request. Reads ctx.filestore.signatures.list and unwraps { data, meta }.",
1694
+ "Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
1695
+ "a map of each file the SIGNED-IN USER has signed (and can still see) " +
1696
+ "to their own latest signature; a file only others have signed, or " +
1697
+ "nobody has, is absent, so it never tells who else signed. Pass a " +
1698
+ "file's signature_id to useFileSignature(fileId, signatureId) to show " +
1699
+ "and verify it. Who has signed is useFileRoster(), for someone who " +
1700
+ "manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
1701
+ "{ data, meta }.",
1674
1702
  returnShape: {
1675
1703
  signaturesByFileId:
1676
1704
  "{ [fileId]: { signature_id, signer_name, signed_at } }",
@@ -1709,8 +1737,10 @@ const HOOKS = [
1709
1737
  signature: "useSigningRounds(fileIds)",
1710
1738
  description:
1711
1739
  "What the signed-in user may know about the signing round of each file " +
1712
- "in a list, in one batched request. A signing round fixes a PDF and a " +
1713
- "list of signers: it is prepared and confirmed by someone who manages " +
1740
+ "in a list, in one batched request. A signing round fixes a PDF in the " +
1741
+ "project space and a list of signers (other file types are signed one " +
1742
+ "person at a time with useFileSignature() and no round): it is " +
1743
+ "prepared and confirmed by someone who manages " +
1714
1744
  "the file's folder, then each signer signs once with useFileSignature(), " +
1715
1745
  "in any order (there are no turns; the list only orders the places on " +
1716
1746
  "the signature page). byFileId maps a file id to { file_id, can_manage, " +
@@ -1719,7 +1749,8 @@ const HOOKS = [
1719
1749
  "manager nor one of its signers; a signer sees it once it is confirmed, " +
1720
1750
  "with their own place (my_place) and can_sign (the round is open and " +
1721
1751
  "their place is unsigned), and signed_count is null " +
1722
- "for them. locked is 'signing_round' while the file cannot be changed, " +
1752
+ "for them. Each signature is added to the PDF itself and the document " +
1753
+ "is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
1723
1754
  "'signed' once its content cannot be replaced. roundsEnabled is false " +
1724
1755
  "when the workspace cannot start signing rounds (offer no way to start " +
1725
1756
  "one), and null until the first answer. can_prepare is about the file " +
@@ -1790,7 +1821,11 @@ const HOOKS = [
1790
1821
  "document changed) the hook reads the round again by itself. Code " +
1791
1822
  "SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
1792
1823
  "rounds; check roundsEnabled from useSigningRounds() before offering to " +
1793
- "start one. When fileId or the signed-in user changes, round is null " +
1824
+ "start one, and when it is false say that document signing is not " +
1825
+ "turned on for this workspace and that the platform turns it on. " +
1826
+ "Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
1827
+ "signer signs their place with useFileSignature(fileId), the same " +
1828
+ "BankID flow as any file. When fileId or the signed-in user changes, round is null " +
1794
1829
  "and loading true until the new answer. Reads ctx.filestore.signingRounds.",
1795
1830
  returnShape: {
1796
1831
  round:
@@ -2619,8 +2654,9 @@ const HOOKS = [
2619
2654
  "with the Image primitive) and the hook then POLLS to completion itself " +
2620
2655
  "(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
2621
2656
  "cancel() aborts; reset() clears the flow. autoStartToken opens the " +
2622
- "provider app on the SAME device (bankid:///?autostarttoken=<token>" +
2623
- "&redirect=null). On complete, `identity` is { provider, name, given_name, " +
2657
+ "provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
2658
+ "autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
2659
+ "On complete, `identity` is { provider, name, given_name, " +
2624
2660
  "surname, personal_number_masked, subject_hash, identified_at } — there is " +
2625
2661
  "NO raw personal number by design: write personal_number_masked " +
2626
2662
  "(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
@@ -6113,7 +6149,13 @@ const CONTRACT = deepFreeze({
6113
6149
  // `{ name, email }` entries for signers without an account, and
6114
6150
  // `resendLink(signerId, { email })` sends such a signer a new link.
6115
6151
  // The manager's places carry id, kind, email and link.
6116
- version: "1.195.0",
6152
+ // 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
6153
+ // the link that opens BankID on this device. Signing hooks document
6154
+ // polling, the two ways to start BankID and how to read a verdict;
6155
+ // useFileSignatures is described as the caller's own signatures.
6156
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6157
+ // scrolls the page to an element inside the calling widget on both hosts.
6158
+ version: "1.198.0",
6117
6159
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6118
6160
  hooks: HOOKS,
6119
6161
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -744,11 +744,14 @@ export function useContainerWidth() {
744
744
  }
745
745
 
746
746
  /**
747
- * Returns the host-provided navigation surface:
748
- * `{ goTo, goToStartPage, goBack, push, replace, back, currentRoute }`.
747
+ * Returns the host-provided navigation surface: `{ goTo, goToStartPage,
748
+ * scrollToSection, scrollIntoView, goBack, push, replace, back, currentRoute,
749
+ * openLink }`.
749
750
  *
750
751
  * `goTo(pageId, params?)` navigates to an internal app page. `goToStartPage()`
751
752
  * opens the signed-in user's own start page, which can differ per user.
753
+ * `scrollToSection(id)` scrolls the page to one of its sections, and (sc-12088)
754
+ * `scrollIntoView(ref)` to an element inside the calling widget.
752
755
  * `goBack()` pops the stack. `push` / `replace` / `back` are aliases that map to
753
756
  * the platform's native navigator (react-router on web, react-navigation
754
757
  * on native). Missing methods degrade to no-ops on the Studio canvas
package/dist/index.d.ts CHANGED
@@ -850,6 +850,7 @@ export interface WidgetContext<TProps = unknown> {
850
850
  goTo(pageId: string, params?: Record<string, string>): void;
851
851
  goToStartPage(): void;
852
852
  scrollToSection(sectionId: string): void;
853
+ scrollIntoView(ref: { readonly current: unknown } | object | null | undefined): void;
853
854
  goBack(): void;
854
855
  push(pageId: string, params?: Record<string, unknown>): void;
855
856
  replace(pageId: string, params?: Record<string, unknown>): void;
@@ -2221,6 +2222,8 @@ export function useFitScreenInsets(): FitScreenInsets;
2221
2222
  * opens the signed-in user's own start page, which can differ per user.
2222
2223
  * `scrollToSection(sectionId)` scrolls this page to one of its sections (the
2223
2224
  * id comes from a `sectionRef` prop); an unknown section is a no-op.
2225
+ * `scrollIntoView(ref)` scrolls this page to an element inside the calling
2226
+ * widget, such as a heading its table of contents jumps to.
2224
2227
  * Missing methods degrade to no-ops on the Studio canvas preview where no
2225
2228
  * live router is mounted. For external URLs use the `Linking` primitive.
2226
2229
  */
@@ -2228,6 +2231,7 @@ export function useNavigation(): {
2228
2231
  goTo(pageId: string, params?: Record<string, unknown>): void;
2229
2232
  goToStartPage(): void;
2230
2233
  scrollToSection(sectionId: string): void;
2234
+ scrollIntoView(ref: { readonly current: unknown } | object | null | undefined): void;
2231
2235
  goBack(): void;
2232
2236
  push(pageId: string, params?: Record<string, unknown>): void;
2233
2237
  replace(pageId: string, params?: Record<string, unknown>): void;
@@ -5065,6 +5069,13 @@ export function useFileSignature(
5065
5069
  verify(): Promise<FileSignatureVerdict | null>;
5066
5070
  };
5067
5071
 
5072
+ /**
5073
+ * The link that opens the BankID app on this device for a pending order:
5074
+ * `Linking.openURL(bankIdAppUrl(autoStartToken))` from a button. For BankID
5075
+ * on another device, show the order's `qr` instead.
5076
+ */
5077
+ export function bankIdAppUrl(autoStartToken: string): string;
5078
+
5068
5079
  export interface FileSignatureSummary {
5069
5080
  signature_id: string;
5070
5081
  subject_file_id: string;
package/dist/index.js CHANGED
@@ -135,6 +135,7 @@ export {
135
135
  } from "./stats-hooks.js";
136
136
  // sc-11025 — one source, bound to how each platform says the app is in front.
137
137
  export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
138
+ export { bankIdAppUrl } from "./bankid-app.js";
138
139
  export { useOnlineMembers, useOnlineVisibility } from "./presence.js";
139
140
  // sc-6607 — `<Overlay>` is one shared component (./overlay-view.js) bound to
140
141
  // each platform's primitives, so a widget's modal escapes its container box
@@ -133,6 +133,7 @@ export {
133
133
  } from "./stats-hooks.js";
134
134
  // sc-11025 — see the note on the web mirror in ./index.js.
135
135
  export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
136
+ export { bankIdAppUrl } from "./bankid-app.js";
136
137
  export { useOnlineMembers, useOnlineVisibility } from "./presence.native.js";
137
138
  // sc-6607 — see the note on the web mirror in ./index.js.
138
139
  export { Overlay } from "./overlay.native.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.241.0",
3
+ "version": "0.244.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
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -1624,10 +1626,30 @@ const HOOKS = [
1624
1626
  signature: "useFileSignature(fileId, existingSignatureId)",
1625
1627
  optionalArgs: ["existingSignatureId"],
1626
1628
  description:
1627
- "Drive a BankID signing flow for one Filestore file. initiate() opens a " +
1628
- "sign order (the backend hashes the bytes + binds the digest), refresh() " +
1629
- "polls (fresh QR while pending — render with the Image primitive), " +
1630
- "cancel() aborts, verify() returns the offline verdict. Pass " +
1629
+ "Drive a BankID signing flow for one Filestore file of ANY type in a " +
1630
+ "shared (project or public) space; a personal file is refused with " +
1631
+ "NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
1632
+ "the file and the file is not changed; a PDF in an open signing round " +
1633
+ "gets the signature added to the document itself, which is then " +
1634
+ "sealed against changes (see useSigningRounds). initiate() opens a " +
1635
+ "sign order (the backend hashes the bytes + binds the digest). The " +
1636
+ "hook does NOT poll by itself: while status is 'pending' call " +
1637
+ "refresh() every 2 seconds (a setInterval in an effect, cleared when " +
1638
+ "status leaves 'pending' or the widget unmounts); each answer carries " +
1639
+ "a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
1640
+ "button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
1641
+ "shown only while autoStartToken is set; BankID on ANOTHER device is " +
1642
+ "the qr rendered with the Image primitive, replaced by each poll's " +
1643
+ "frame. cancel() aborts, verify() returns the offline verdict: valid " +
1644
+ "is the BankID signature itself; content_status 'unchanged' means the " +
1645
+ "file still holds what was signed, 'changed' that it was altered " +
1646
+ "since, 'unavailable' that it could not be checked now; " +
1647
+ "signed_by_others_since counts later signatures in a round's PDF; " +
1648
+ "seal.status is 'valid' (protected against changes), 'invalid' or " +
1649
+ "'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
1650
+ "later) or 'not_applicable' (a signature kept beside the file). Say " +
1651
+ "each in plain words and never show a green mark when any of them " +
1652
+ "failed. Pass " +
1631
1653
  "existingSignatureId for an already-signed file to seed the complete " +
1632
1654
  "state + verify() without re-signing. autoStartToken is set only while " +
1633
1655
  "opening BankID can still help (null once the user has signed or the " +
@@ -1644,19 +1666,21 @@ const HOOKS = [
1644
1666
  "ctx.filestore.signatures.{initiate,status,cancel,verify}.",
1645
1667
  returnShape: {
1646
1668
  status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
1647
- qr: "string | null // PNG data-URL of the animated BankID QR",
1648
- autoStartToken: "string | null",
1669
+ qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
1670
+ autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
1649
1671
  message: "string | null",
1650
1672
  signerName: "string | null",
1651
1673
  signedAt: "string | null",
1652
1674
  hintCode: "string | null // where the order stands, e.g. 'sealing'",
1653
1675
  nameRefusal:
1654
1676
  "{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
1655
- verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1677
+ verdict:
1678
+ "{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
1679
+ "seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
1656
1680
  loading: "boolean",
1657
1681
  error: "PermissionError | null // { code, message, retryable }",
1658
1682
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
1659
- refresh: "() => Promise<void>",
1683
+ refresh: "() => Promise<void> // the hook never polls by itself",
1660
1684
  cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
1661
1685
  verify: "() => Promise<verdict>",
1662
1686
  },
@@ -1667,10 +1691,14 @@ const HOOKS = [
1667
1691
  name: "useFileSignatures",
1668
1692
  signature: "useFileSignatures(fileIds)",
1669
1693
  description:
1670
- "Batch 'is this file signed?' lookup for a set of file ids. Returns a map " +
1671
- "of each signed, accessible file id to its latest signature; unsigned " +
1672
- "files are absent. Lets a file browser mark already-signed files in one " +
1673
- "request. Reads ctx.filestore.signatures.list and unwraps { data, meta }.",
1694
+ "Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
1695
+ "a map of each file the SIGNED-IN USER has signed (and can still see) " +
1696
+ "to their own latest signature; a file only others have signed, or " +
1697
+ "nobody has, is absent, so it never tells who else signed. Pass a " +
1698
+ "file's signature_id to useFileSignature(fileId, signatureId) to show " +
1699
+ "and verify it. Who has signed is useFileRoster(), for someone who " +
1700
+ "manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
1701
+ "{ data, meta }.",
1674
1702
  returnShape: {
1675
1703
  signaturesByFileId:
1676
1704
  "{ [fileId]: { signature_id, signer_name, signed_at } }",
@@ -1709,8 +1737,10 @@ const HOOKS = [
1709
1737
  signature: "useSigningRounds(fileIds)",
1710
1738
  description:
1711
1739
  "What the signed-in user may know about the signing round of each file " +
1712
- "in a list, in one batched request. A signing round fixes a PDF and a " +
1713
- "list of signers: it is prepared and confirmed by someone who manages " +
1740
+ "in a list, in one batched request. A signing round fixes a PDF in the " +
1741
+ "project space and a list of signers (other file types are signed one " +
1742
+ "person at a time with useFileSignature() and no round): it is " +
1743
+ "prepared and confirmed by someone who manages " +
1714
1744
  "the file's folder, then each signer signs once with useFileSignature(), " +
1715
1745
  "in any order (there are no turns; the list only orders the places on " +
1716
1746
  "the signature page). byFileId maps a file id to { file_id, can_manage, " +
@@ -1719,7 +1749,8 @@ const HOOKS = [
1719
1749
  "manager nor one of its signers; a signer sees it once it is confirmed, " +
1720
1750
  "with their own place (my_place) and can_sign (the round is open and " +
1721
1751
  "their place is unsigned), and signed_count is null " +
1722
- "for them. locked is 'signing_round' while the file cannot be changed, " +
1752
+ "for them. Each signature is added to the PDF itself and the document " +
1753
+ "is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
1723
1754
  "'signed' once its content cannot be replaced. roundsEnabled is false " +
1724
1755
  "when the workspace cannot start signing rounds (offer no way to start " +
1725
1756
  "one), and null until the first answer. can_prepare is about the file " +
@@ -1790,7 +1821,11 @@ const HOOKS = [
1790
1821
  "document changed) the hook reads the round again by itself. Code " +
1791
1822
  "SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
1792
1823
  "rounds; check roundsEnabled from useSigningRounds() before offering to " +
1793
- "start one. When fileId or the signed-in user changes, round is null " +
1824
+ "start one, and when it is false say that document signing is not " +
1825
+ "turned on for this workspace and that the platform turns it on. " +
1826
+ "Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
1827
+ "signer signs their place with useFileSignature(fileId), the same " +
1828
+ "BankID flow as any file. When fileId or the signed-in user changes, round is null " +
1794
1829
  "and loading true until the new answer. Reads ctx.filestore.signingRounds.",
1795
1830
  returnShape: {
1796
1831
  round:
@@ -2619,8 +2654,9 @@ const HOOKS = [
2619
2654
  "with the Image primitive) and the hook then POLLS to completion itself " +
2620
2655
  "(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
2621
2656
  "cancel() aborts; reset() clears the flow. autoStartToken opens the " +
2622
- "provider app on the SAME device (bankid:///?autostarttoken=<token>" +
2623
- "&redirect=null). On complete, `identity` is { provider, name, given_name, " +
2657
+ "provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
2658
+ "autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
2659
+ "On complete, `identity` is { provider, name, given_name, " +
2624
2660
  "surname, personal_number_masked, subject_hash, identified_at } — there is " +
2625
2661
  "NO raw personal number by design: write personal_number_masked " +
2626
2662
  "(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
@@ -6113,7 +6149,13 @@ const CONTRACT = deepFreeze({
6113
6149
  // `{ name, email }` entries for signers without an account, and
6114
6150
  // `resendLink(signerId, { email })` sends such a signer a new link.
6115
6151
  // The manager's places carry id, kind, email and link.
6116
- version: "1.195.0",
6152
+ // 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
6153
+ // the link that opens BankID on this device. Signing hooks document
6154
+ // polling, the two ways to start BankID and how to read a verdict;
6155
+ // useFileSignatures is described as the caller's own signatures.
6156
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6157
+ // scrolls the page to an element inside the calling widget on both hosts.
6158
+ version: "1.198.0",
6117
6159
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6118
6160
  hooks: HOOKS,
6119
6161
  primitives: PRIMITIVES,